diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41f548119..53600b258 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: 2024Q2 + PKGSRC_BRANCH: 2024Q3 steps: - uses: actions/checkout@v4 - name: Build, Check, and Test diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 6243d1922..314711efb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -38,6 +38,7 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 with: + include-hidden-files: true name: windows_artifacts path: dist build_linux: diff --git a/base/runtime/core.odin b/base/runtime/core.odin index a5a3a4d8c..e47f3ecbc 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -171,14 +171,6 @@ Type_Info_Simd_Vector :: struct { elem_size: int, count: int, } -Type_Info_Relative_Pointer :: struct { - pointer: ^Type_Info, // ^T - base_integer: ^Type_Info, -} -Type_Info_Relative_Multi_Pointer :: struct { - pointer: ^Type_Info, // [^]T - base_integer: ^Type_Info, -} Type_Info_Matrix :: struct { elem: ^Type_Info, elem_size: int, @@ -241,8 +233,6 @@ Type_Info :: struct { Type_Info_Map, Type_Info_Bit_Set, Type_Info_Simd_Vector, - Type_Info_Relative_Pointer, - Type_Info_Relative_Multi_Pointer, Type_Info_Matrix, Type_Info_Soa_Pointer, Type_Info_Bit_Field, @@ -275,8 +265,6 @@ Typeid_Kind :: enum u8 { Map, Bit_Set, Simd_Vector, - Relative_Pointer, - Relative_Multi_Pointer, Matrix, Soa_Pointer, Bit_Field, diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 3dad2fdbc..d28dadd02 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -388,7 +388,7 @@ _make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, ali // // Note: Prefer using the procedure group `make`. @(builtin, require_results) -make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator) -> (m: T) { +make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator, loc := #caller_location) -> (m: T) { m.allocator = allocator return m } @@ -399,7 +399,7 @@ make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator) -> (m: T) // // Note: Prefer using the procedure group `make`. @(builtin, require_results) -make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1< (m: T, err: Allocator_Error) #optional_allocator_error { +make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error { make_map_expr_error_loc(loc, capacity) context.allocator = allocator @@ -939,19 +939,7 @@ map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) @builtin card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int { - when size_of(S) == 1 { - return int(intrinsics.count_ones(transmute(u8)s)) - } else when size_of(S) == 2 { - return int(intrinsics.count_ones(transmute(u16)s)) - } else when size_of(S) == 4 { - return int(intrinsics.count_ones(transmute(u32)s)) - } else when size_of(S) == 8 { - return int(intrinsics.count_ones(transmute(u64)s)) - } else when size_of(S) == 16 { - return int(intrinsics.count_ones(transmute(u128)s)) - } else { - #panic("Unhandled card bit_set size") - } + return int(intrinsics.count_ones(transmute(intrinsics.type_bit_set_underlying_type(S))s)) } diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 2980b7a9e..ff27a4559 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -142,6 +142,7 @@ make_soa_slice :: proc($T: typeid/#soa[]$E, #any_int length: int, allocator := c @(builtin, require_results) make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { context.allocator = allocator + array.allocator = allocator reserve_soa(&array, 0, loc) or_return return array, nil } @@ -149,6 +150,7 @@ make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context. @(builtin, require_results) make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { context.allocator = allocator + array.allocator = allocator resize_soa(&array, length, loc) or_return return array, nil } diff --git a/base/runtime/print.odin b/base/runtime/print.odin index 45f6f01ef..c28fd593d 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -486,18 +486,6 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { print_u64(u64(info.count)) print_byte(']') print_type(info.elem) - - case Type_Info_Relative_Pointer: - print_string("#relative(") - print_type(info.base_integer) - print_string(") ") - print_type(info.pointer) - - case Type_Info_Relative_Multi_Pointer: - print_string("#relative(") - print_type(info.base_integer) - print_string(") ") - print_type(info.pointer) case Type_Info_Matrix: print_string("matrix[") diff --git a/bin/RAD-LICENSE b/bin/RAD-LICENSE new file mode 100644 index 000000000..1a1ccbcd2 --- /dev/null +++ b/bin/RAD-LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Epic Games Tools + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/bin/radlink.exe b/bin/radlink.exe new file mode 100644 index 000000000..bb2fd206e Binary files /dev/null and b/bin/radlink.exe differ diff --git a/build.bat b/build.bat index 55c71ca9f..a788a8c04 100644 --- a/build.bat +++ b/build.bat @@ -19,7 +19,11 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" ( ) ) -for /f %%i in ('powershell get-date -format "{yyyyMMdd}"') do ( +pushd misc +cl /nologo get-date.c +popd + +for /f %%i in ('misc\get-date') do ( set CURR_DATE_TIME=%%i ) set curr_year=%CURR_DATE_TIME:~0,4% @@ -58,7 +62,6 @@ set V4=0 set odin_version_full="%V1%.%V2%.%V3%.%V4%" set odin_version_raw="dev-%V1%-%V2%" - set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF rem Parse source code as utf-8 even on shift-jis and other codepages rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 diff --git a/build_odin.sh b/build_odin.sh index c06004ea8..3547689d5 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -25,7 +25,8 @@ error() { # Brew advises people not to add llvm to their $PATH, so try and use brew to find it. if [ -z "$LLVM_CONFIG" ] && [ -n "$(command -v brew)" ]; then - if [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config" + if [ -n "$(command -v $(brew --prefix llvm@19)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@19)/bin/llvm-config" + elif [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config" elif [ -n "$(command -v $(brew --prefix llvm@17)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@17)/bin/llvm-config" elif [ -n "$(command -v $(brew --prefix llvm@14)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@14)/bin/llvm-config" fi @@ -33,13 +34,15 @@ fi if [ -z "$LLVM_CONFIG" ]; then # darwin, linux, openbsd - if [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18" + if [ -n "$(command -v llvm-config-19)" ]; then LLVM_CONFIG="llvm-config-19" + elif [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18" elif [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17" elif [ -n "$(command -v llvm-config-14)" ]; then LLVM_CONFIG="llvm-config-14" elif [ -n "$(command -v llvm-config-13)" ]; then LLVM_CONFIG="llvm-config-13" elif [ -n "$(command -v llvm-config-12)" ]; then LLVM_CONFIG="llvm-config-12" elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11" # freebsd + elif [ -n "$(command -v llvm-config19)" ]; then LLVM_CONFIG="llvm-config19" elif [ -n "$(command -v llvm-config18)" ]; then LLVM_CONFIG="llvm-config18" elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config17" elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config14" @@ -66,15 +69,15 @@ LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')" LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')" LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')" -if [ $LLVM_VERSION_MAJOR -lt 11 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 18 ]; then - error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17 or 18" +if [ $LLVM_VERSION_MAJOR -lt 11 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 19 ]; then + error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17, 18 or 19" fi case "$OS_NAME" in Darwin) if [ "$OS_ARCH" = "arm64" ]; then if [ $LLVM_VERSION_MAJOR -lt 13 ]; then - error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17 or 18" + error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17, 18 or 19" fi fi @@ -152,9 +155,7 @@ build_odin() { } run_demo() { - if [ $# -eq 0 ] || [ "$1" = "debug" ]; then - ./odin run examples/demo -vet -strict-style -- Hellope World - fi + ./odin run examples/demo -vet -strict-style -- Hellope World } if [ $# -eq 0 ]; then @@ -166,14 +167,20 @@ if [ $# -eq 0 ]; then elif [ $# -eq 1 ]; then case $1 in report) - [ ! -f "./odin" ] && build_odin debug + if [ ! -f "./odin" ]; then + build_odin debug + run_demo + fi ./odin report ;; + debug) + build_odin debug + run_demo + ;; *) build_odin $1 ;; esac - run_demo else error "Too many arguments!" fi diff --git a/core/c/frontend/preprocessor/const_expr.odin b/core/c/frontend/preprocessor/const_expr.odin deleted file mode 100644 index ff13f6432..000000000 --- a/core/c/frontend/preprocessor/const_expr.odin +++ /dev/null @@ -1,25 +0,0 @@ -package c_frontend_preprocess - -import "core:c/frontend/tokenizer" - -const_expr :: proc(rest: ^^Token, tok: ^Token) -> i64 { - // TODO(bill): Handle const_expr correctly - // This is effectively a mini-parser - - assert(rest != nil) - assert(tok != nil) - rest^ = tokenizer.new_eof(tok) - switch v in tok.val { - case i64: - return v - case f64: - return i64(v) - case string: - return 0 - case []u16: - // TODO - case []u32: - // TODO - } - return 0 -} diff --git a/core/c/frontend/preprocessor/preprocess.odin b/core/c/frontend/preprocessor/preprocess.odin deleted file mode 100644 index b5eab0bb3..000000000 --- a/core/c/frontend/preprocessor/preprocess.odin +++ /dev/null @@ -1,1510 +0,0 @@ -package c_frontend_preprocess - -import "../tokenizer" - -import "core:strings" -import "core:strconv" -import "core:path/filepath" -import "core:unicode/utf8" -import "core:unicode/utf16" -import "core:os" -import "core:io" - -@(private) -Tokenizer :: tokenizer.Tokenizer -@(private) -Token :: tokenizer.Token - -Error_Handler :: tokenizer.Error_Handler - -Macro_Param :: struct { - next: ^Macro_Param, - name: string, -} - -Macro_Arg :: struct { - next: ^Macro_Arg, - name: string, - tok: ^Token, - is_va_args: bool, -} - -Macro_Kind :: enum u8 { - Function_Like, - Value_Like, -} - -Macro_Handler :: #type proc(^Preprocessor, ^Token) -> ^Token - -Macro :: struct { - name: string, - kind: Macro_Kind, - params: ^Macro_Param, - va_args_name: string, - body: ^Token, - handler: Macro_Handler, -} - -Cond_Incl_State :: enum u8 { - In_Then, - In_Elif, - In_Else, -} - -Cond_Incl :: struct { - next: ^Cond_Incl, - tok: ^Token, - state: Cond_Incl_State, - included: bool, -} - -Pragma_Handler :: #type proc(^Preprocessor, ^Token) - -Preprocessor :: struct { - // Lookup tables - macros: map[string]^Macro, - pragma_once: map[string]bool, - include_guards: map[string]string, - filepath_cache: map[string]string, - - // Include path data - include_paths: []string, - - // Counter for __COUNTER__ macro - counter: i64, - - // Include information - cond_incl: ^Cond_Incl, - include_level: int, - include_next_index: int, - - wide_char_size: int, - - // Mutable data - err: Error_Handler, - warn: Error_Handler, - pragma_handler: Pragma_Handler, - error_count: int, - warning_count: int, -} - -MAX_INCLUDE_LEVEL :: 1024 - -error :: proc(cpp: ^Preprocessor, tok: ^Token, msg: string, args: ..any) { - if cpp.err != nil { - cpp.err(tok.pos, msg, ..args) - } - cpp.error_count += 1 -} - -warn :: proc(cpp: ^Preprocessor, tok: ^Token, msg: string, args: ..any) { - if cpp.warn != nil { - cpp.warn(tok.pos, msg, ..args) - } - cpp.warning_count += 1 -} - -is_hash :: proc(tok: ^Token) -> bool { - return tok.at_bol && tok.lit == "#" -} - -skip_line :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - tok := tok - if tok.at_bol { - return tok - } - warn(cpp, tok, "extra token") - for tok.at_bol { - tok = tok.next - } - return tok -} - - -append_token :: proc(a, b: ^Token) -> ^Token { - if a.kind == .EOF { - return b - } - - head: Token - curr := &head - - for tok := a; tok.kind != .EOF; tok = tok.next { - curr.next = tokenizer.copy_token(tok) - curr = curr.next - } - curr.next = b - return head.next -} - - -is_hex_digit :: proc(x: byte) -> bool { - switch x { - case '0'..='9', 'a'..='f', 'A'..='F': - return true - } - return false -} -from_hex :: proc(x: byte) -> i32 { - switch x { - case '0'..='9': - return i32(x) - '0' - case 'a'..='f': - return i32(x) - 'a' + 10 - case 'A'..='F': - return i32(x) - 'A' + 10 - } - return 16 -} - - -convert_pp_number :: proc(tok: ^Token) { - convert_pp_int :: proc(tok: ^Token) -> bool { - p := tok.lit - base := 10 - if len(p) > 2 { - if strings.equal_fold(p[:2], "0x") && is_hex_digit(p[2]) { - p = p[2:] - base = 16 - } else if strings.equal_fold(p[:2], "0b") && p[2] == '0' || p[2] == '1' { - p = p[2:] - base = 2 - } - } - if base == 10 && p[0] == '0' { - base = 8 - } - - - tok.val, _ = strconv.parse_i64_of_base(p, base) - - l, u: int - - suf: [3]byte - suf_n := 0 - i := len(p)-1 - for /**/; i >= 0 && suf_n < len(suf); i -= 1 { - switch p[i] { - case 'l', 'L': - suf[suf_n] = 'l' - l += 1 - suf_n += 1 - case 'u', 'U': - suf[suf_n] = 'u' - u += 1 - suf_n += 1 - } - } - if i < len(p) { - if !is_hex_digit(p[i]) && p[i] != '.' { - return false - } - } - if u > 1 { - return false - } - - if l > 2 { - return false - } - - if u == 1 { - switch l { - case 0: tok.type_hint = .Unsigned_Int - case 1: tok.type_hint = .Unsigned_Long - case 2: tok.type_hint = .Unsigned_Long_Long - } - } else { - switch l { - case 0: tok.type_hint = .Int - case 1: tok.type_hint = .Long - case 2: tok.type_hint = .Long_Long - } - } - return true - } - - if convert_pp_int(tok) { - return - } - - fval, _ := strconv.parse_f64(tok.lit) - tok.val = fval - - end := tok.lit[len(tok.lit)-1] - switch end { - case 'f', 'F': - tok.type_hint = .Float - case 'l', 'L': - tok.type_hint = .Long_Double - case: - tok.type_hint = .Double - } - -} - -convert_pp_char :: proc(tok: ^Token) { - assert(len(tok.lit) >= 2) - r, _, _, _ := unquote_char(tok.lit, tok.lit[0]) - tok.val = i64(r) - - tok.type_hint = .Int - switch tok.prefix { - case "u": tok.type_hint = .UTF_16 - case "U": tok.type_hint = .UTF_32 - case "L": tok.type_hint = .UTF_Wide - } -} - -wide_char_size :: proc(cpp: ^Preprocessor) -> int { - char_size := 4 - if cpp.wide_char_size > 0 { - char_size = clamp(cpp.wide_char_size, 1, 4) - assert(char_size & (char_size-1) == 0) - } - return char_size -} - -convert_pp_string :: proc(cpp: ^Preprocessor, tok: ^Token) { - assert(len(tok.lit) >= 2) - str, _, _ := unquote_string(tok.lit) - tok.val = str - - char_size := 1 - - switch tok.prefix { - case "u8": - tok.type_hint = .UTF_8 - char_size = 1 - case "u": - tok.type_hint = .UTF_16 - char_size = 2 - case "U": - tok.type_hint = .UTF_32 - char_size = 4 - case "L": - tok.type_hint = .UTF_Wide - char_size = wide_char_size(cpp) - } - - switch char_size { - case 2: - n: int - buf := make([]u16, len(str)) - for c in str { - ch := c - if ch < 0x10000 { - buf[n] = u16(ch) - n += 1 - } else { - ch -= 0x10000 - buf[n+0] = 0xd800 + u16((ch >> 10) & 0x3ff) - buf[n+1] = 0xdc00 + u16(ch & 0x3ff) - n += 2 - } - } - tok.val = buf[:n] - case 4: - n: int - buf := make([]u32, len(str)) - for ch in str { - buf[n] = u32(ch) - n += 1 - } - tok.val = buf[:n] - } - -} - -convert_pp_token :: proc(cpp: ^Preprocessor, t: ^Token, is_keyword: tokenizer.Is_Keyword_Proc) { - switch { - case t.kind == .Char: - convert_pp_char(t) - case t.kind == .String: - convert_pp_string(cpp, t) - case is_keyword != nil && is_keyword(t): - t.kind = .Keyword - case t.kind == .PP_Number: - convert_pp_number(t) - } -} -convert_pp_tokens :: proc(cpp: ^Preprocessor, tok: ^Token, is_keyword: tokenizer.Is_Keyword_Proc) { - for t := tok; t != nil && t.kind != .EOF; t = t.next { - convert_pp_token(cpp, tok, is_keyword) - } -} - -join_adjacent_string_literals :: proc(cpp: ^Preprocessor, initial_tok: ^Token) { - for tok1 := initial_tok; tok1.kind != .EOF; /**/ { - if tok1.kind != .String || tok1.next.kind != .String { - tok1 = tok1.next - continue - } - - type_hint := tokenizer.Token_Type_Hint.None - char_size := 1 - - start := tok1 - for t := tok1; t != nil && t.kind == .String; t = t.next { - if t.val == nil { - convert_pp_string(cpp, t) - } - tok1 = t.next - if type_hint != t.type_hint { - if t.type_hint != .None && type_hint != .None { - error(cpp, t, "unsupported non-standard concatenation of string literals of different types") - } - prev_char_size := char_size - - #partial switch type_hint { - case .UTF_8: char_size = max(char_size, 1) - case .UTF_16: char_size = max(char_size, 2) - case .UTF_32: char_size = max(char_size, 4) - case .UTF_Wide: char_size = max(char_size, wide_char_size(cpp)) - } - - if type_hint == .None || prev_char_size < char_size { - type_hint = t.type_hint - } - } - } - - // NOTE(bill): Verbose logic in order to correctly concantenate strings, even if they different in type - max_len := 0 - switch char_size { - case 1: - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: max_len += len(v) - case []u16: max_len += 2*len(v) - case []u32: max_len += 4*len(v) - } - } - n := 0 - buf := make([]byte, max_len) - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: - n += copy(buf[n:], v) - case []u16: - for i := 0; i < len(v); /**/ { - c1 := v[i] - r: rune - if !utf16.is_surrogate(rune(c1)) { - r = rune(c1) - i += 1 - } else if i+1 == len(v) { - r = utf16.REPLACEMENT_CHAR - i += 1 - } else { - c2 := v[i+1] - i += 2 - r = utf16.decode_surrogate_pair(rune(c1), rune(c2)) - } - - b, w := utf8.encode_rune(r) - n += copy(buf[n:], b[:w]) - } - case []u32: - for r in v { - b, w := utf8.encode_rune(rune(r)) - n += copy(buf[n:], b[:w]) - } - } - } - - new_tok := tokenizer.copy_token(start) - new_tok.lit = "" - new_tok.val = string(buf[:n]) - new_tok.next = tok1 - new_tok.type_hint = type_hint - start^ = new_tok^ - case 2: - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: max_len += len(v) - case []u16: max_len += len(v) - case []u32: max_len += 2*len(v) - } - } - n := 0 - buf := make([]u16, max_len) - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: - for r in v { - if r >= 0x10000 { - c1, c2 := utf16.encode_surrogate_pair(r) - buf[n+0] = u16(c1) - buf[n+1] = u16(c2) - n += 2 - } else { - buf[n] = u16(r) - n += 1 - } - } - case []u16: - n += copy(buf[n:], v) - case []u32: - for r in v { - if r >= 0x10000 { - c1, c2 := utf16.encode_surrogate_pair(rune(r)) - buf[n+0] = u16(c1) - buf[n+1] = u16(c2) - n += 2 - } else { - buf[n] = u16(r) - n += 1 - } - } - } - } - - new_tok := tokenizer.copy_token(start) - new_tok.lit = "" - new_tok.val = buf[:n] - new_tok.next = tok1 - new_tok.type_hint = type_hint - start^ = new_tok^ - case 4: - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: max_len += len(v) - case []u16: max_len += len(v) - case []u32: max_len += len(v) - } - } - n := 0 - buf := make([]u32, max_len) - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: - for r in v { - buf[n] = u32(r) - n += 1 - } - case []u16: - for i := 0; i < len(v); /**/ { - c1 := v[i] - if !utf16.is_surrogate(rune(c1)) { - buf[n] = u32(c1) - n += 1 - i += 1 - } else if i+1 == len(v) { - buf[n] = utf16.REPLACEMENT_CHAR - n += 1 - i += 1 - } else { - c2 := v[i+1] - i += 2 - r := utf16.decode_surrogate_pair(rune(c1), rune(c2)) - buf[n] = u32(r) - n += 1 - } - } - case []u32: - n += copy(buf[n:], v) - } - } - - new_tok := tokenizer.copy_token(start) - new_tok.lit = "" - new_tok.val = buf[:n] - new_tok.next = tok1 - new_tok.type_hint = type_hint - start^ = new_tok^ - } - } -} - - -quote_string :: proc(s: string) -> []byte { - b := strings.builder_make(0, len(s)+2) - io.write_quoted_string(strings.to_writer(&b), s, '"') - return b.buf[:] -} - - -_init_tokenizer_from_preprocessor :: proc(t: ^Tokenizer, cpp: ^Preprocessor) -> ^Tokenizer { - t.warn = cpp.warn - t.err = cpp.err - return t -} - -new_string_token :: proc(cpp: ^Preprocessor, str: string, tok: ^Token) -> ^Token { - assert(tok != nil) - assert(str != "") - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - src := quote_string(str) - return tokenizer.inline_tokenize(t, tok, src) -} - -stringize :: proc(cpp: ^Preprocessor, hash, arg: ^Token) -> ^Token { - s := join_tokens(arg, nil) - return new_string_token(cpp, s, hash) -} - - -new_number_token :: proc(cpp: ^Preprocessor, i: i64, tok: ^Token) -> ^Token { - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - buf: [32]byte - n := len(strconv.append_int(buf[:], i, 10)) - src := make([]byte, n) - copy(src, buf[:n]) - return tokenizer.inline_tokenize(t, tok, src) -} - - -find_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Macro { - if tok.kind != .Ident { - return nil - } - return cpp.macros[tok.lit] -} - -add_macro :: proc(cpp: ^Preprocessor, name: string, kind: Macro_Kind, body: ^Token) -> ^Macro { - m := new(Macro) - m.name = name - m.kind = kind - m.body = body - cpp.macros[name] = m - return m -} - - -undef_macro :: proc(cpp: ^Preprocessor, name: string) { - delete_key(&cpp.macros, name) -} - -add_builtin :: proc(cpp: ^Preprocessor, name: string, handler: Macro_Handler) -> ^Macro { - m := add_macro(cpp, name, .Value_Like, nil) - m.handler = handler - return m -} - - -skip :: proc(cpp: ^Preprocessor, tok: ^Token, op: string) -> ^Token { - if tok.lit != op { - error(cpp, tok, "expected '%q'", op) - } - return tok.next -} - -consume :: proc(rest: ^^Token, tok: ^Token, lit: string) -> bool { - if tok.lit == lit { - rest^ = tok.next - return true - } - rest^ = tok - return false -} - -read_macro_params :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (param: ^Macro_Param, va_args_name: string) { - head: Macro_Param - curr := &head - - tok := tok - for tok.lit != ")" && tok.kind != .EOF { - if curr != &head { - tok = skip(cpp, tok, ",") - } - - if tok.lit == "..." { - va_args_name = "__VA_ARGS__" - rest^ = skip(cpp, tok.next, ")") - param = head.next - return - } - - if tok.kind != .Ident { - error(cpp, tok, "expected an identifier") - } - - if tok.next.lit == "..." { - va_args_name = tok.lit - rest^ = skip(cpp, tok.next.next, ")") - param = head.next - return - } - - m := new(Macro_Param) - m.name = tok.lit - curr.next = m - curr = curr.next - tok = tok.next - } - - - rest^ = tok.next - param = head.next - return -} - -copy_line :: proc(rest: ^^Token, tok: ^Token) -> ^Token { - head: Token - curr := &head - - tok := tok - for ; !tok.at_bol; tok = tok.next { - curr.next = tokenizer.copy_token(tok) - curr = curr.next - } - curr.next = tokenizer.new_eof(tok) - rest^ = tok - return head.next -} - -read_macro_definition :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) { - tok := tok - if tok.kind != .Ident { - error(cpp, tok, "macro name must be an identifier") - } - name := tok.lit - tok = tok.next - - if !tok.has_space && tok.lit == "(" { - params, va_args_name := read_macro_params(cpp, &tok, tok.next) - - m := add_macro(cpp, name, .Function_Like, copy_line(rest, tok)) - m.params = params - m.va_args_name = va_args_name - } else { - add_macro(cpp, name, .Value_Like, copy_line(rest, tok)) - } -} - - -join_tokens :: proc(tok, end: ^Token) -> string { - n := 1 - for t := tok; t != end && t.kind != .EOF; t = t.next { - if t != tok && t.has_space { - n += 1 - } - n += len(t.lit) - } - - buf := make([]byte, n) - - pos := 0 - for t := tok; t != end && t.kind != .EOF; t = t.next { - if t != tok && t.has_space { - buf[pos] = ' ' - pos += 1 - } - copy(buf[pos:], t.lit) - pos += len(t.lit) - } - - return string(buf[:pos]) -} - -read_include_filename :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (filename: string, is_quote: bool) { - tok := tok - - if tok.kind == .String { - rest^ = skip_line(cpp, tok.next) - filename = tok.lit[1:len(tok.lit)-1] - is_quote = true - return - } - - if tok.lit == "<" { - start := tok - for ; tok.kind != .EOF; tok = tok.next { - if tok.at_bol || tok.kind == .EOF { - error(cpp, tok, "expected '>'") - } - is_quote = false - if tok.lit == ">" { - break - } - } - rest^ = skip_line(cpp, tok.next) - filename = join_tokens(start.next, tok) - return - } - - if tok.kind == .Ident { - tok2 := preprocess_internal(cpp, copy_line(rest, tok)) - return read_include_filename(cpp, &tok2, tok2) - } - - error(cpp, tok, "expected a filename") - return -} - -skip_cond_incl :: proc(tok: ^Token) -> ^Token { - next_skip :: proc(tok: ^Token) -> ^Token { - tok := tok - for tok.kind != .EOF { - if is_hash(tok) { - switch tok.next.lit { - case "if", "ifdef", "ifndef": - tok = next_skip(tok.next.next) - continue - - case "endif": - return tok.next.next - } - } - tok = tok.next - } - return tok - } - - tok := tok - - loop: for tok.kind != .EOF { - if is_hash(tok) { - switch tok.next.lit { - case "if", "ifdef", "ifndef": - tok = next_skip(tok.next.next) - continue loop - - case "elif", "else", "endif": - break loop - } - } - - tok = tok.next - } - return tok -} - -check_for_include_guard :: proc(tok: ^Token) -> (guard: string, ok: bool) { - if !is_hash(tok) || tok.next.lit != "ifndef" { - return - } - tok := tok - tok = tok.next.next - - if tok.kind != .Ident { - return - } - - m := tok.lit - tok = tok.next - - if !is_hash(tok) || tok.next.lit != "define" || tok.next.lit != "macro" { - return - } - - for tok.kind != .EOF { - if !is_hash(tok) { - tok = tok.next - continue - } - - if tok.next.lit == "endif" && tok.next.next.kind == .EOF { - return m, true - } - - switch tok.lit { - case "if", "ifdef", "ifndef": - tok = skip_cond_incl(tok.next) - case: - tok = tok.next - } - } - return -} - -include_file :: proc(cpp: ^Preprocessor, tok: ^Token, path: string, filename_tok: ^Token) -> ^Token { - if cpp.pragma_once[path] { - return tok - } - - guard_name, guard_name_found := cpp.include_guards[path] - if guard_name_found && cpp.macros[guard_name] != nil { - return tok - } - - if !os.exists(path) { - error(cpp, filename_tok, "%s: cannot open file", path) - return tok - } - - cpp.include_level += 1 - if cpp.include_level > MAX_INCLUDE_LEVEL { - error(cpp, tok, "exceeded maximum nest amount: %d", MAX_INCLUDE_LEVEL) - return tok - } - - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - tok2 := tokenizer.tokenize_file(t, path, /*file.id*/1) - if tok2 == nil { - error(cpp, filename_tok, "%s: cannot open file", path) - } - cpp.include_level -= 1 - - guard_name, guard_name_found = check_for_include_guard(tok2) - if guard_name_found { - cpp.include_guards[path] = guard_name - } - - return append_token(tok2, tok) -} - -find_arg :: proc(args: ^Macro_Arg, tok: ^Token) -> ^Macro_Arg { - for ap := args; ap != nil; ap = ap.next { - if tok.lit == ap.name { - return ap - } - } - return nil -} - -paste :: proc(cpp: ^Preprocessor, lhs, rhs: ^Token) -> ^Token { - buf := strings.concatenate({lhs.lit, rhs.lit}) - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - tok := tokenizer.inline_tokenize(t, lhs, transmute([]byte)buf) - if tok.next.kind != .EOF { - error(cpp, lhs, "pasting forms '%s', an invalid token", buf) - } - return tok -} - -has_varargs :: proc(args: ^Macro_Arg) -> bool { - for ap := args; ap != nil; ap = ap.next { - if ap.name == "__VA_ARGS__" { - return ap.tok.kind != .EOF - } - } - return false -} - -substitute_token :: proc(cpp: ^Preprocessor, tok: ^Token, args: ^Macro_Arg) -> ^Token { - head: Token - curr := &head - tok := tok - for tok.kind != .EOF { - if tok.lit == "#" { - arg := find_arg(args, tok.next) - if arg == nil { - error(cpp, tok.next, "'#' is not followed by a macro parameter") - } - arg_tok := arg.tok if arg != nil else tok.next - curr.next = stringize(cpp, tok, arg_tok) - curr = curr.next - tok = tok.next.next - continue - } - - if tok.lit == "," && tok.next.lit == "##" { - if arg := find_arg(args, tok.next.next); arg != nil && arg.is_va_args { - if arg.tok.kind == .EOF { - tok = tok.next.next.next - } else { - curr.next = tokenizer.copy_token(tok) - curr = curr.next - tok = tok.next.next - } - continue - } - } - - if tok.lit == "##" { - if curr == &head { - error(cpp, tok, "'##' cannot appear at start of macro expansion") - } - if tok.next.kind == .EOF { - error(cpp, tok, "'##' cannot appear at end of macro expansion") - } - - if arg := find_arg(args, tok.next); arg != nil { - if arg.tok.kind != .EOF { - curr^ = paste(cpp, curr, arg.tok)^ - for t := arg.tok.next; t.kind != .EOF; t = t.next { - curr.next = tokenizer.copy_token(t) - curr = curr.next - } - } - tok = tok.next.next - continue - } - - curr^ = paste(cpp, curr, tok.next)^ - tok = tok.next.next - continue - } - - arg := find_arg(args, tok) - - if arg != nil && tok.next.lit == "##" { - rhs := tok.next.next - - if arg.tok.kind == .EOF { - args2 := find_arg(args, rhs) - if args2 != nil { - for t := args.tok; t.kind != .EOF; t = t.next { - curr.next = tokenizer.copy_token(t) - curr = curr.next - } - } else { - curr.next = tokenizer.copy_token(rhs) - curr = curr.next - } - tok = rhs.next - continue - } - - for t := arg.tok; t.kind != .EOF; t = t.next { - curr.next = tokenizer.copy_token(t) - curr = curr.next - } - tok = tok.next - continue - } - - if tok.lit == "__VA_OPT__" && tok.next.lit == "(" { - opt_arg := read_macro_arg_one(cpp, &tok, tok.next.next, true) - if has_varargs(args) { - for t := opt_arg.tok; t.kind != .EOF; t = t.next { - curr.next = t - curr = curr.next - } - } - tok = skip(cpp, tok, ")") - continue - } - - if arg != nil { - t := preprocess_internal(cpp, arg.tok) - t.at_bol = tok.at_bol - t.has_space = tok.has_space - for ; t.kind != .EOF; t = t.next { - curr.next = tokenizer.copy_token(t) - curr = curr.next - } - tok = tok.next - continue - } - - curr.next = tokenizer.copy_token(tok) - curr = curr.next - tok = tok.next - continue - } - - curr.next = tok - return head.next -} - -read_macro_arg_one :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token, read_rest: bool) -> ^Macro_Arg { - tok := tok - head: Token - curr := &head - level := 0 - for { - if level == 0 && tok.lit == ")" { - break - } - if level == 0 && !read_rest && tok.lit == "," { - break - } - - if tok.kind == .EOF { - error(cpp, tok, "premature end of input") - } - - switch tok.lit { - case "(": level += 1 - case ")": level -= 1 - } - - curr.next = tokenizer.copy_token(tok) - curr = curr.next - tok = tok.next - } - curr.next = tokenizer.new_eof(tok) - - arg := new(Macro_Arg) - arg.tok = head.next - rest^ = tok - return arg -} - -read_macro_args :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token, params: ^Macro_Param, va_args_name: string) -> ^Macro_Arg { - tok := tok - start := tok - tok = tok.next.next - - head: Macro_Arg - curr := &head - - pp := params - for ; pp != nil; pp = pp.next { - if curr != &head { - tok = skip(cpp, tok, ",") - } - curr.next = read_macro_arg_one(cpp, &tok, tok, false) - curr = curr.next - curr.name = pp.name - } - - if va_args_name != "" { - arg: ^Macro_Arg - if tok.lit == ")" { - arg = new(Macro_Arg) - arg.tok = tokenizer.new_eof(tok) - } else { - if pp != params { - tok = skip(cpp, tok, ",") - } - arg = read_macro_arg_one(cpp, &tok, tok, true) - } - arg.name = va_args_name - arg.is_va_args = true - curr.next = arg - curr = curr.next - } else if pp != nil { - error(cpp, start, "too many arguments") - } - - skip(cpp, tok, ")") - rest^ = tok - return head.next -} - -expand_macro :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> bool { - if tokenizer.hide_set_contains(tok.hide_set, tok.lit) { - return false - } - tok := tok - m := find_macro(cpp, tok) - if m == nil { - return false - } - - if m.handler != nil { - rest^ = m.handler(cpp, tok) - rest^.next = tok.next - return true - } - - if m.kind == .Value_Like { - hs := tokenizer.hide_set_union(tok.hide_set, tokenizer.new_hide_set(m.name)) - body := tokenizer.add_hide_set(m.body, hs) - for t := body; t.kind != .EOF; t = t.next { - t.origin = tok - } - rest^ = append_token(body, tok.next) - rest^.at_bol = tok.at_bol - rest^.has_space = tok.has_space - return true - } - - if tok.next.lit != "(" { - return false - } - - macro_token := tok - args := read_macro_args(cpp, &tok, tok, m.params, m.va_args_name) - close_paren := tok - - hs := tokenizer.hide_set_intersection(macro_token.hide_set, close_paren.hide_set) - hs = tokenizer.hide_set_union(hs, tokenizer.new_hide_set(m.name)) - - body := substitute_token(cpp, m.body, args) - body = tokenizer.add_hide_set(body, hs) - for t := body; t.kind != .EOF; t = t.next { - t.origin = macro_token - } - rest^ = append_token(body, tok.next) - rest^.at_bol = macro_token.at_bol - rest^.has_space = macro_token.has_space - return true -} - -search_include_next :: proc(cpp: ^Preprocessor, filename: string) -> (path: string, ok: bool) { - for ; cpp.include_next_index < len(cpp.include_paths); cpp.include_next_index += 1 { - tpath := filepath.join({cpp.include_paths[cpp.include_next_index], filename}, allocator=context.temp_allocator) - if os.exists(tpath) { - return strings.clone(tpath), true - } - } - return -} - -search_include_paths :: proc(cpp: ^Preprocessor, filename: string) -> (path: string, ok: bool) { - if filepath.is_abs(filename) { - return filename, true - } - - if path, ok = cpp.filepath_cache[filename]; ok { - return - } - - for include_path in cpp.include_paths { - tpath := filepath.join({include_path, filename}, allocator=context.temp_allocator) - if os.exists(tpath) { - path, ok = strings.clone(tpath), true - cpp.filepath_cache[filename] = path - return - } - } - - return -} - -read_const_expr :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> ^Token { - tok := tok - tok = copy_line(rest, tok) - head: Token - curr := &head - for tok.kind != .EOF { - if tok.lit == "defined" { - start := tok - has_paren := consume(&tok, tok.next, "(") - if tok.kind != .Ident { - error(cpp, start, "macro name must be an identifier") - } - m := find_macro(cpp, tok) - tok = tok.next - - if has_paren { - tok = skip(cpp, tok, ")") - } - - curr.next = new_number_token(cpp, 1 if m != nil else 0, start) - curr = curr.next - continue - } - - curr.next = tok - curr = curr.next - tok = tok.next - } - - curr.next = tok - return head.next -} - -eval_const_expr :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (val: i64) { - tok := tok - start := tok - expr := read_const_expr(cpp, rest, tok.next) - expr = preprocess_internal(cpp, expr) - - if expr.kind == .EOF { - error(cpp, start, "no expression") - } - - for t := expr; t.kind != .EOF; t = t.next { - if t.kind == .Ident { - next := t.next - t^ = new_number_token(cpp, 0, t)^ - t.next = next - } - } - - val = 1 - convert_pp_tokens(cpp, expr, tokenizer.default_is_keyword) - - rest2: ^Token - val = const_expr(&rest2, expr) - if rest2 != nil && rest2.kind != .EOF { - error(cpp, rest2, "extra token") - } - return -} - -push_cond_incl :: proc(cpp: ^Preprocessor, tok: ^Token, included: bool) -> ^Cond_Incl { - ci := new(Cond_Incl) - ci.next = cpp.cond_incl - ci.state = .In_Then - ci.tok = tok - ci.included = included - cpp.cond_incl = ci - return ci -} - -read_line_marker:: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) { - tok := tok - start := tok - tok = preprocess(cpp, copy_line(rest, tok)) - if tok.kind != .Number { - error(cpp, tok, "invalid line marker") - } - ival, _ := tok.val.(i64) - start.file.line_delta = int(ival - i64(start.pos.line)) - tok = tok.next - if tok.kind == .EOF { - return - } - - if tok.kind != .String { - error(cpp, tok, "filename expected") - } - start.file.display_name = tok.lit -} - -preprocess_internal :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - head: Token - curr := &head - - tok := tok - for tok != nil && tok.kind != .EOF { - if expand_macro(cpp, &tok, tok) { - continue - } - - if !is_hash(tok) { - if tok.file != nil { - tok.line_delta = tok.file.line_delta - } - curr.next = tok - curr = curr.next - tok = tok.next - continue - } - - start := tok - tok = tok.next - - switch tok.lit { - case "include": - filename, is_quote := read_include_filename(cpp, &tok, tok.next) - is_absolute := filepath.is_abs(filename) - if is_absolute { - tok = include_file(cpp, tok, filename, start.next.next) - continue - } - - if is_quote { - dir := "" - if start.file != nil { - dir = filepath.dir(start.file.name) - } - path := filepath.join({dir, filename}) - if os.exists(path) { - tok = include_file(cpp, tok, path, start.next.next) - continue - } - } - - path, ok := search_include_paths(cpp, filename) - if !ok { - path = filename - } - tok = include_file(cpp, tok, path, start.next.next) - continue - - case "include_next": - filename, _ := read_include_filename(cpp, &tok, tok.next) - path, ok := search_include_next(cpp, filename) - if !ok { - path = filename - } - tok = include_file(cpp, tok, path, start.next.next) - continue - - case "define": - read_macro_definition(cpp, &tok, tok.next) - continue - - case "undef": - tok = tok.next - if tok.kind != .Ident { - error(cpp, tok, "macro name must be an identifier") - } - undef_macro(cpp, tok.lit) - tok = skip_line(cpp, tok.next) - continue - - case "if": - val := eval_const_expr(cpp, &tok, tok) - push_cond_incl(cpp, start, val != 0) - if val == 0 { - tok = skip_cond_incl(tok) - } - continue - - case "ifdef": - defined := find_macro(cpp, tok.next) - push_cond_incl(cpp, tok, defined != nil) - tok = skip_line(cpp, tok.next.next) - if defined == nil { - tok = skip_cond_incl(tok) - } - continue - - case "ifndef": - defined := find_macro(cpp, tok.next) - push_cond_incl(cpp, tok, defined != nil) - tok = skip_line(cpp, tok.next.next) - if !(defined == nil) { - tok = skip_cond_incl(tok) - } - continue - - case "elif": - if cpp.cond_incl == nil || cpp.cond_incl.state == .In_Else { - error(cpp, start, "stray #elif") - } - if cpp.cond_incl != nil { - cpp.cond_incl.state = .In_Elif - } - - if (cpp.cond_incl != nil && !cpp.cond_incl.included) && eval_const_expr(cpp, &tok, tok) != 0 { - cpp.cond_incl.included = true - } else { - tok = skip_cond_incl(tok) - } - continue - - case "else": - if cpp.cond_incl == nil || cpp.cond_incl.state == .In_Else { - error(cpp, start, "stray #else") - } - if cpp.cond_incl != nil { - cpp.cond_incl.state = .In_Else - } - tok = skip_line(cpp, tok.next) - - if cpp.cond_incl != nil { - tok = skip_cond_incl(tok) - } - continue - - case "endif": - if cpp.cond_incl == nil { - error(cpp, start, "stray #endif") - } else { - cpp.cond_incl = cpp.cond_incl.next - } - tok = skip_line(cpp, tok.next) - continue - - case "line": - read_line_marker(cpp, &tok, tok.next) - continue - - case "pragma": - if tok.next.lit == "once" { - cpp.pragma_once[tok.pos.file] = true - tok = skip_line(cpp, tok.next.next) - continue - } - - pragma_tok, pragma_end := tok, tok - - for tok != nil && tok.kind != .EOF { - pragma_end = tok - tok = tok.next - if tok.at_bol { - break - } - } - pragma_end.next = tokenizer.new_eof(tok) - if cpp.pragma_handler != nil { - cpp.pragma_handler(cpp, pragma_tok.next) - continue - } - - continue - - case "error": - error(cpp, tok, "error") - } - - if tok.kind == .PP_Number { - read_line_marker(cpp, &tok, tok) - continue - } - - if !tok.at_bol { - error(cpp, tok, "invalid preprocessor directive") - } - } - - curr.next = tok - return head.next -} - - -preprocess :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - tok := tok - tok = preprocess_internal(cpp, tok) - if cpp.cond_incl != nil { - error(cpp, tok, "unterminated conditional directive") - } - convert_pp_tokens(cpp, tok, tokenizer.default_is_keyword) - join_adjacent_string_literals(cpp, tok) - for t := tok; t != nil; t = t.next { - t.pos.line += t.line_delta - } - return tok -} - - -define_macro :: proc(cpp: ^Preprocessor, name, def: string) { - src := transmute([]byte)def - - file := new(tokenizer.File) - file.id = -1 - file.src = src - file.name = "" - file.display_name = file.name - - - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - tok := tokenizer.tokenize(t, file) - add_macro(cpp, name, .Value_Like, tok) -} - - -file_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - tok := tok - for tok.origin != nil { - tok = tok.origin - } - i := i64(tok.pos.line + tok.file.line_delta) - return new_number_token(cpp, i, tok) -} -line_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - tok := tok - for tok.origin != nil { - tok = tok.origin - } - return new_string_token(cpp, tok.file.display_name, tok) -} -counter_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - i := cpp.counter - cpp.counter += 1 - return new_number_token(cpp, i, tok) -} - -init_default_macros :: proc(cpp: ^Preprocessor) { - define_macro(cpp, "__C99_MACRO_WITH_VA_ARGS", "1") - define_macro(cpp, "__alignof__", "_Alignof") - define_macro(cpp, "__const__", "const") - define_macro(cpp, "__inline__", "inline") - define_macro(cpp, "__signed__", "signed") - define_macro(cpp, "__typeof__", "typeof") - define_macro(cpp, "__volatile__", "volatile") - - add_builtin(cpp, "__FILE__", file_macro) - add_builtin(cpp, "__LINE__", line_macro) - add_builtin(cpp, "__COUNTER__", counter_macro) -} - -init_lookup_tables :: proc(cpp: ^Preprocessor, allocator := context.allocator) { - context.allocator = allocator - reserve(&cpp.macros, max(16, cap(cpp.macros))) - reserve(&cpp.pragma_once, max(16, cap(cpp.pragma_once))) - reserve(&cpp.include_guards, max(16, cap(cpp.include_guards))) - reserve(&cpp.filepath_cache, max(16, cap(cpp.filepath_cache))) -} - - -init_defaults :: proc(cpp: ^Preprocessor, lookup_tables_allocator := context.allocator) { - if cpp.warn == nil { - cpp.warn = tokenizer.default_warn_handler - } - if cpp.err == nil { - cpp.err = tokenizer.default_error_handler - } - init_lookup_tables(cpp, lookup_tables_allocator) - init_default_macros(cpp) -} diff --git a/core/c/frontend/preprocessor/unquote.odin b/core/c/frontend/preprocessor/unquote.odin deleted file mode 100644 index 5869fa7ef..000000000 --- a/core/c/frontend/preprocessor/unquote.odin +++ /dev/null @@ -1,154 +0,0 @@ -package c_frontend_preprocess - -import "core:unicode/utf8" - -unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool, tail_string: string, success: bool) { - hex_to_int :: proc(c: byte) -> int { - switch c { - case '0'..='9': return int(c-'0') - case 'a'..='f': return int(c-'a')+10 - case 'A'..='F': return int(c-'A')+10 - } - return -1 - } - w: int - - if str[0] == quote && quote == '"' { - return - } else if str[0] >= 0x80 { - r, w = utf8.decode_rune_in_string(str) - return r, true, str[w:], true - } else if str[0] != '\\' { - return rune(str[0]), false, str[1:], true - } - - if len(str) <= 1 { - return - } - s := str - c := s[1] - s = s[2:] - - switch c { - case: r = rune(c) - - case 'a': r = '\a' - case 'b': r = '\b' - case 'e': r = '\e' - case 'f': r = '\f' - case 'n': r = '\n' - case 'r': r = '\r' - case 't': r = '\t' - case 'v': r = '\v' - case '\\': r = '\\' - - case '"': r = '"' - case '\'': r = '\'' - - case '0'..='7': - v := int(c-'0') - if len(s) < 2 { - return - } - for i in 0.. 7 { - return - } - v = (v<<3) | d - } - s = s[2:] - if v > 0xff { - return - } - r = rune(v) - - case 'x', 'u', 'U': - count: int - switch c { - case 'x': count = 2 - case 'u': count = 4 - case 'U': count = 8 - } - - if len(s) < count { - return - } - - for i in 0.. utf8.MAX_RUNE { - return - } - multiple_bytes = true - } - - success = true - tail_string = s - return -} - -unquote_string :: proc(lit: string, allocator := context.allocator) -> (res: string, allocated, success: bool) { - contains_rune :: proc(s: string, r: rune) -> int { - for c, offset in s { - if c == r { - return offset - } - } - return -1 - } - - assert(len(lit) >= 2) - - s := lit - quote := '"' - - if s == `""` { - return "", false, true - } - - if contains_rune(s, '\n') >= 0 { - return s, false, false - } - - if contains_rune(s, '\\') < 0 && contains_rune(s, quote) < 0 { - if quote == '"' { - return s, false, true - } - } - s = s[1:len(s)-1] - - - buf_len := 3*len(s) / 2 - buf := make([]byte, buf_len, allocator) - offset := 0 - for len(s) > 0 { - r, multiple_bytes, tail_string, ok := unquote_char(s, byte(quote)) - if !ok { - delete(buf) - return s, false, false - } - s = tail_string - if r < 0x80 || !multiple_bytes { - buf[offset] = byte(r) - offset += 1 - } else { - b, w := utf8.encode_rune(r) - copy(buf[offset:], b[:w]) - offset += w - } - } - - new_string := string(buf[:offset]) - - return new_string, true, true -} diff --git a/core/c/frontend/tokenizer/doc.odin b/core/c/frontend/tokenizer/doc.odin deleted file mode 100644 index 43747dfe8..000000000 --- a/core/c/frontend/tokenizer/doc.odin +++ /dev/null @@ -1,31 +0,0 @@ -/* -Example: - package demo - - import tokenizer "core:c/frontend/tokenizer" - import preprocessor "core:c/frontend/preprocessor" - import "core:fmt" - - main :: proc() { - t := &tokenizer.Tokenizer{}; - tokenizer.init_defaults(t); - - cpp := &preprocessor.Preprocessor{}; - cpp.warn, cpp.err = t.warn, t.err; - preprocessor.init_lookup_tables(cpp); - preprocessor.init_default_macros(cpp); - cpp.include_paths = {"my/path/to/include"}; - - tok := tokenizer.tokenize_file(t, "the/source/file.c", 1); - - tok = preprocessor.preprocess(cpp, tok); - if tok != nil { - for t := tok; t.kind != .EOF; t = t.next { - fmt.println(t.lit); - } - } - - fmt.println("[Done]"); - } -*/ -package c_frontend_tokenizer diff --git a/core/c/frontend/tokenizer/hide_set.odin b/core/c/frontend/tokenizer/hide_set.odin deleted file mode 100644 index ec8b77e6e..000000000 --- a/core/c/frontend/tokenizer/hide_set.odin +++ /dev/null @@ -1,68 +0,0 @@ -package c_frontend_tokenizer - -// NOTE(bill): This is a really dumb approach for a hide set, -// but it's really simple and probably fast enough in practice - - -Hide_Set :: struct { - next: ^Hide_Set, - name: string, -} - - -new_hide_set :: proc(name: string) -> ^Hide_Set { - hs := new(Hide_Set) - hs.name = name - return hs -} - -hide_set_contains :: proc(hs: ^Hide_Set, name: string) -> bool { - for h := hs; h != nil; h = h.next { - if h.name == name { - return true - } - } - return false -} - - -hide_set_union :: proc(a, b: ^Hide_Set) -> ^Hide_Set { - head: Hide_Set - curr := &head - - for h := a; h != nil; h = h.next { - curr.next = new_hide_set(h.name) - curr = curr.next - } - curr.next = b - return head.next -} - - -hide_set_intersection :: proc(a, b: ^Hide_Set) -> ^Hide_Set { - head: Hide_Set - curr := &head - - for h := a; h != nil; h = h.next { - if hide_set_contains(b, h.name) { - curr.next = new_hide_set(h.name) - curr = curr.next - } - } - return head.next -} - - -add_hide_set :: proc(tok: ^Token, hs: ^Hide_Set) -> ^Token { - head: Token - curr := &head - - tok := tok - for ; tok != nil; tok = tok.next { - t := copy_token(tok) - t.hide_set = hide_set_union(t.hide_set, hs) - curr.next = t - curr = curr.next - } - return head.next -} diff --git a/core/c/frontend/tokenizer/token.odin b/core/c/frontend/tokenizer/token.odin deleted file mode 100644 index 1376a651f..000000000 --- a/core/c/frontend/tokenizer/token.odin +++ /dev/null @@ -1,169 +0,0 @@ -package c_frontend_tokenizer - - -Pos :: struct { - file: string, - line: int, - column: int, - offset: int, -} - -Token_Kind :: enum { - Invalid, - Ident, - Punct, - Keyword, - Char, - String, - Number, - PP_Number, - Comment, - EOF, -} - -File :: struct { - name: string, - id: int, - src: []byte, - - display_name: string, - line_delta: int, -} - - -Token_Type_Hint :: enum u8 { - None, - - Int, - Long, - Long_Long, - - Unsigned_Int, - Unsigned_Long, - Unsigned_Long_Long, - - Float, - Double, - Long_Double, - - UTF_8, - UTF_16, - UTF_32, - UTF_Wide, -} - -Token_Value :: union { - i64, - f64, - string, - []u16, - []u32, -} - -Token :: struct { - kind: Token_Kind, - next: ^Token, - lit: string, - - pos: Pos, - file: ^File, - line_delta: int, - at_bol: bool, - has_space: bool, - - type_hint: Token_Type_Hint, - val: Token_Value, - prefix: string, - - // Preprocessor values - hide_set: ^Hide_Set, - origin: ^Token, -} - -Is_Keyword_Proc :: #type proc(tok: ^Token) -> bool - -copy_token :: proc(tok: ^Token) -> ^Token { - t, _ := new_clone(tok^) - t.next = nil - return t -} - -new_eof :: proc(tok: ^Token) -> ^Token { - t, _ := new_clone(tok^) - t.kind = .EOF - t.lit = "" - return t -} - -default_is_keyword :: proc(tok: ^Token) -> bool { - if tok.kind == .Keyword { - return true - } - if len(tok.lit) > 0 { - return default_keyword_set[tok.lit] - } - return false -} - - -token_name := [Token_Kind]string { - .Invalid = "invalid", - .Ident = "ident", - .Punct = "punct", - .Keyword = "keyword", - .Char = "char", - .String = "string", - .Number = "number", - .PP_Number = "preprocessor number", - .Comment = "comment", - .EOF = "eof", -} - -default_keyword_set := map[string]bool{ - "auto" = true, - "break" = true, - "case" = true, - "char" = true, - "const" = true, - "continue" = true, - "default" = true, - "do" = true, - "double" = true, - "else" = true, - "enum" = true, - "extern" = true, - "float" = true, - "for" = true, - "goto" = true, - "if" = true, - "int" = true, - "long" = true, - "register" = true, - "restrict" = true, - "return" = true, - "short" = true, - "signed" = true, - "sizeof" = true, - "static" = true, - "struct" = true, - "switch" = true, - "typedef" = true, - "union" = true, - "unsigned" = true, - "void" = true, - "volatile" = true, - "while" = true, - "_Alignas" = true, - "_Alignof" = true, - "_Atomic" = true, - "_Bool" = true, - "_Generic" = true, - "_Noreturn" = true, - "_Thread_local" = true, - "__restrict" = true, - "typeof" = true, - "asm" = true, - "__restrict__" = true, - "__thread" = true, - "__attribute__" = true, -} diff --git a/core/c/frontend/tokenizer/tokenizer.odin b/core/c/frontend/tokenizer/tokenizer.odin deleted file mode 100644 index 2415e06a0..000000000 --- a/core/c/frontend/tokenizer/tokenizer.odin +++ /dev/null @@ -1,667 +0,0 @@ -package c_frontend_tokenizer - -import "core:fmt" -import "core:os" -import "core:strings" -import "core:unicode/utf8" - - -Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any) - - -Tokenizer :: struct { - // Immutable data - path: string, - src: []byte, - - - // Tokenizing state - ch: rune, - offset: int, - read_offset: int, - line_offset: int, - line_count: int, - - // Extra information for tokens - at_bol: bool, - has_space: bool, - - // Mutable data - err: Error_Handler, - warn: Error_Handler, - error_count: int, - warning_count: int, -} - -init_defaults :: proc(t: ^Tokenizer, err: Error_Handler = default_error_handler, warn: Error_Handler = default_warn_handler) { - t.err = err - t.warn = warn -} - - -@(private) -offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> (pos: Pos) { - pos.file = t.path - pos.offset = offset - pos.line = t.line_count - pos.column = offset - t.line_offset + 1 - return -} - -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") -} - -default_warn_handler :: proc(pos: Pos, msg: string, args: ..any) { - fmt.eprintf("%s(%d:%d) warning: ", pos.file, pos.line, pos.column) - fmt.eprintf(msg, ..args) - fmt.eprintf("\n") -} - -error_offset :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { - pos := offset_to_pos(t, offset) - if t.err != nil { - t.err(pos, msg, ..args) - } - t.error_count += 1 -} - -warn_offset :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { - pos := offset_to_pos(t, offset) - if t.warn != nil { - t.warn(pos, msg, ..args) - } - t.warning_count += 1 -} - -error :: proc(t: ^Tokenizer, tok: ^Token, msg: string, args: ..any) { - pos := tok.pos - if t.err != nil { - t.err(pos, msg, ..args) - } - t.error_count += 1 -} - -warn :: proc(t: ^Tokenizer, tok: ^Token, msg: string, args: ..any) { - pos := tok.pos - if t.warn != nil { - t.warn(pos, msg, ..args) - } - t.warning_count += 1 -} - - -advance_rune :: proc(t: ^Tokenizer) { - if t.read_offset < len(t.src) { - t.offset = t.read_offset - if t.ch == '\n' { - t.at_bol = true - t.line_offset = t.offset - t.line_count += 1 - } - r, w := rune(t.src[t.read_offset]), 1 - switch { - case r == 0: - error_offset(t, t.offset, "illegal character NUL") - case r >= utf8.RUNE_SELF: - r, w = utf8.decode_rune(t.src[t.read_offset:]) - if r == utf8.RUNE_ERROR && w == 1 { - error_offset(t, t.offset, "illegal UTF-8 encoding") - } else if r == utf8.RUNE_BOM && t.offset > 0 { - error_offset(t, t.offset, "illegal byte order mark") - } - } - t.read_offset += w - t.ch = r - } else { - t.offset = len(t.src) - if t.ch == '\n' { - t.at_bol = true - t.line_offset = t.offset - t.line_count += 1 - } - t.ch = -1 - } -} - -advance_rune_n :: proc(t: ^Tokenizer, n: int) { - for _ in 0.. bool { - return '0' <= r && r <= '9' -} - -skip_whitespace :: proc(t: ^Tokenizer) { - for { - switch t.ch { - case ' ', '\t', '\r', '\v', '\f', '\n': - t.has_space = true - advance_rune(t) - case: - return - } - } -} - -scan_comment :: proc(t: ^Tokenizer) -> string { - offset := t.offset-1 - next := -1 - general: { - if t.ch == '/'{ // line comments - advance_rune(t) - for t.ch != '\n' && t.ch >= 0 { - advance_rune(t) - } - - next = t.offset - if t.ch == '\n' { - next += 1 - } - break general - } - - /* style comment */ - advance_rune(t) - for t.ch >= 0 { - ch := t.ch - advance_rune(t) - if ch == '*' && t.ch == '/' { - advance_rune(t) - next = t.offset - break general - } - } - - error_offset(t, offset, "comment not terminated") - } - - lit := t.src[offset : t.offset] - - // NOTE(bill): Strip CR for line comments - for len(lit) > 2 && lit[1] == '/' && lit[len(lit)-1] == '\r' { - lit = lit[:len(lit)-1] - } - - - return string(lit) -} - -scan_identifier :: proc(t: ^Tokenizer) -> string { - offset := t.offset - - for is_ident1(t.ch) { - advance_rune(t) - } - - return string(t.src[offset : t.offset]) -} - -scan_string :: proc(t: ^Tokenizer) -> string { - offset := t.offset-1 - - for { - ch := t.ch - if ch == '\n' || ch < 0 { - error_offset(t, offset, "string literal was not terminated") - break - } - advance_rune(t) - if ch == '"' { - break - } - if ch == '\\' { - scan_escape(t) - } - } - - return string(t.src[offset : t.offset]) -} - -digit_val :: proc(r: rune) -> int { - switch r { - case '0'..='9': - return int(r-'0') - case 'A'..='F': - return int(r-'A' + 10) - case 'a'..='f': - return int(r-'a' + 10) - } - return 16 -} - -scan_escape :: proc(t: ^Tokenizer) -> bool { - offset := t.offset - - esc := t.ch - n: int - base, max: u32 - switch esc { - case 'a', 'b', 'e', 'f', 'n', 't', 'v', 'r', '\\', '\'', '"': - advance_rune(t) - return true - - case '0'..='7': - for digit_val(t.ch) < 8 { - advance_rune(t) - } - return true - case 'x': - advance_rune(t) - for digit_val(t.ch) < 16 { - advance_rune(t) - } - return true - case 'u': - advance_rune(t) - n, base, max = 4, 16, utf8.MAX_RUNE - case 'U': - advance_rune(t) - n, base, max = 8, 16, utf8.MAX_RUNE - case: - if t.ch < 0 { - error_offset(t, offset, "escape sequence was not terminated") - } else { - break - } - return false - } - - x: u32 - main_loop: for n > 0 { - d := u32(digit_val(t.ch)) - if d >= base { - if t.ch == '"' || t.ch == '\'' { - break main_loop - } - if t.ch < 0 { - error_offset(t, t.offset, "escape sequence was not terminated") - } else { - error_offset(t, t.offset, "illegal character '%r' : %d in escape sequence", t.ch, t.ch) - } - return false - } - - x = x*base + d - advance_rune(t) - n -= 1 - } - - if x > max || 0xd800 <= x && x <= 0xe000 { - error_offset(t, offset, "escape sequence is an invalid Unicode code point") - return false - } - return true -} - -scan_rune :: proc(t: ^Tokenizer) -> string { - offset := t.offset-1 - valid := true - n := 0 - for { - ch := t.ch - if ch == '\n' || ch < 0 { - if valid { - error_offset(t, offset, "rune literal not terminated") - valid = false - } - break - } - advance_rune(t) - if ch == '\'' { - break - } - n += 1 - if ch == '\\' { - if !scan_escape(t) { - valid = false - } - } - } - - if valid && n != 1 { - error_offset(t, offset, "illegal rune literal") - } - - return string(t.src[offset : t.offset]) -} - -scan_number :: proc(t: ^Tokenizer, seen_decimal_point: bool) -> (Token_Kind, string) { - scan_mantissa :: proc(t: ^Tokenizer, base: int) { - for digit_val(t.ch) < base { - advance_rune(t) - } - } - scan_exponent :: proc(t: ^Tokenizer) { - if t.ch == 'e' || t.ch == 'E' || t.ch == 'p' || t.ch == 'P' { - advance_rune(t) - if t.ch == '-' || t.ch == '+' { - advance_rune(t) - } - if digit_val(t.ch) < 10 { - scan_mantissa(t, 10) - } else { - error_offset(t, t.offset, "illegal floating-point exponent") - } - } - } - scan_fraction :: proc(t: ^Tokenizer) -> (early_exit: bool) { - if t.ch == '.' && peek(t) == '.' { - return true - } - if t.ch == '.' { - advance_rune(t) - scan_mantissa(t, 10) - } - return false - } - - check_end := true - - - offset := t.offset - seen_point := seen_decimal_point - - if seen_point { - offset -= 1 - scan_mantissa(t, 10) - scan_exponent(t) - } else { - if t.ch == '0' { - int_base :: proc(t: ^Tokenizer, base: int, msg: string) { - prev := t.offset - advance_rune(t) - scan_mantissa(t, base) - if t.offset - prev <= 1 { - error_offset(t, t.offset, msg) - } - } - - advance_rune(t) - switch t.ch { - case 'b', 'B': - int_base(t, 2, "illegal binary integer") - case 'x', 'X': - int_base(t, 16, "illegal hexadecimal integer") - case: - seen_point = false - scan_mantissa(t, 10) - if t.ch == '.' { - seen_point = true - if scan_fraction(t) { - check_end = false - } - } - if check_end { - scan_exponent(t) - check_end = false - } - } - } - } - - if check_end { - scan_mantissa(t, 10) - - if !scan_fraction(t) { - scan_exponent(t) - } - } - - return .Number, string(t.src[offset : t.offset]) -} - -scan_punct :: proc(t: ^Tokenizer, ch: rune) -> (kind: Token_Kind) { - kind = .Punct - switch ch { - case: - kind = .Invalid - - case '<', '>': - if t.ch == ch { - advance_rune(t) - } - if t.ch == '=' { - advance_rune(t) - } - case '!', '+', '-', '*', '/', '%', '^', '=': - if t.ch == '=' { - advance_rune(t) - } - case '#': - if t.ch == '#' { - advance_rune(t) - } - case '&': - if t.ch == '=' || t.ch == '&' { - advance_rune(t) - } - case '|': - if t.ch == '=' || t.ch == '|' { - advance_rune(t) - } - case '(', ')', '[', ']', '{', '}': - // okay - case '~', ',', ':', ';', '?': - // okay - case '`': - // okay - case '.': - if t.ch == '.' && peek(t) == '.' { - advance_rune(t) - advance_rune(t) // consume last '.' - } - } - return -} - -peek :: proc(t: ^Tokenizer) -> byte { - if t.read_offset < len(t.src) { - return t.src[t.read_offset] - } - return 0 -} -peek_str :: proc(t: ^Tokenizer, str: string) -> bool { - if t.read_offset < len(t.src) { - return strings.has_prefix(string(t.src[t.offset:]), str) - } - return false -} - -scan_literal_prefix :: proc(t: ^Tokenizer, str: string, prefix: ^string) -> bool { - if peek_str(t, str) { - offset := t.offset - for _ in str { - advance_rune(t) - } - prefix^ = string(t.src[offset:][:len(str)-1]) - return true - } - return false -} - - -allow_next_to_be_newline :: proc(t: ^Tokenizer) -> bool { - if t.ch == '\n' { - advance_rune(t) - return true - } else if t.ch == '\r' && peek(t) == '\n' { // allow for MS-DOS style line endings - advance_rune(t) // \r - advance_rune(t) // \n - return true - } - return false -} - -scan :: proc(t: ^Tokenizer, f: ^File) -> ^Token { - skip_whitespace(t) - - offset := t.offset - - kind: Token_Kind - lit: string - prefix: string - - switch ch := t.ch; { - case scan_literal_prefix(t, `u8"`, &prefix): - kind = .String - lit = scan_string(t) - case scan_literal_prefix(t, `u"`, &prefix): - kind = .String - lit = scan_string(t) - case scan_literal_prefix(t, `L"`, &prefix): - kind = .String - lit = scan_string(t) - case scan_literal_prefix(t, `U"`, &prefix): - kind = .String - lit = scan_string(t) - case scan_literal_prefix(t, `u'`, &prefix): - kind = .Char - lit = scan_rune(t) - case scan_literal_prefix(t, `L'`, &prefix): - kind = .Char - lit = scan_rune(t) - case scan_literal_prefix(t, `U'`, &prefix): - kind = .Char - lit = scan_rune(t) - - case is_ident0(ch): - lit = scan_identifier(t) - kind = .Ident - case '0' <= ch && ch <= '9': - kind, lit = scan_number(t, false) - case: - advance_rune(t) - switch ch { - case -1: - kind = .EOF - case '\\': - kind = .Punct - if allow_next_to_be_newline(t) { - t.at_bol = true - t.has_space = false - return scan(t, f) - } - - case '.': - if is_digit(t.ch) { - kind, lit = scan_number(t, true) - } else { - kind = scan_punct(t, ch) - } - case '"': - kind = .String - lit = scan_string(t) - case '\'': - kind = .Char - lit = scan_rune(t) - case '/': - if t.ch == '/' || t.ch == '*' { - kind = .Comment - lit = scan_comment(t) - t.has_space = true - break - } - fallthrough - case: - kind = scan_punct(t, ch) - if kind == .Invalid && ch != utf8.RUNE_BOM { - error_offset(t, t.offset, "illegal character '%r': %d", ch, ch) - } - } - } - - if lit == "" { - lit = string(t.src[offset : t.offset]) - } - - if kind == .Comment { - return scan(t, f) - } - - tok := new(Token) - tok.kind = kind - tok.lit = lit - tok.pos = offset_to_pos(t, offset) - tok.file = f - tok.prefix = prefix - tok.at_bol = t.at_bol - tok.has_space = t.has_space - - t.at_bol, t.has_space = false, false - - return tok -} - -tokenize :: proc(t: ^Tokenizer, f: ^File) -> ^Token { - setup_tokenizer: { - t.src = f.src - t.ch = ' ' - t.offset = 0 - t.read_offset = 0 - t.line_offset = 0 - t.line_count = len(t.src) > 0 ? 1 : 0 - t.error_count = 0 - t.path = f.name - - - advance_rune(t) - if t.ch == utf8.RUNE_BOM { - advance_rune(t) - } - } - - - t.at_bol = true - t.has_space = false - - head: Token - curr := &head - for { - tok := scan(t, f) - if tok == nil { - break - } - curr.next = tok - curr = curr.next - if tok.kind == .EOF { - break - } - } - - return head.next -} - -add_new_file :: proc(t: ^Tokenizer, name: string, src: []byte, id: int) -> ^File { - file := new(File) - file.id = id - file.src = src - file.name = name - file.display_name = name - return file -} - -tokenize_file :: proc(t: ^Tokenizer, path: string, id: int, loc := #caller_location) -> ^Token { - src, ok := os.read_entire_file(path) - if !ok { - return nil - } - return tokenize(t, add_new_file(t, path, src, id)) -} - - -inline_tokenize :: proc(t: ^Tokenizer, tok: ^Token, src: []byte) -> ^Token { - file := new(File) - file.src = src - if tok.file != nil { - file.id = tok.file.id - file.name = tok.file.name - file.display_name = tok.file.name - } - - return tokenize(t, file) -} diff --git a/core/c/frontend/tokenizer/unicode.odin b/core/c/frontend/tokenizer/unicode.odin deleted file mode 100644 index 317ee160e..000000000 --- a/core/c/frontend/tokenizer/unicode.odin +++ /dev/null @@ -1,116 +0,0 @@ -package c_frontend_tokenizer - - -in_range :: proc(range: []rune, c: rune) -> bool #no_bounds_check { - for i := 0; range[i] != -1; i += 2 { - if range[i] <= c && c <= range[i+1] { - return true - } - } - return false -} - - -// [https://www.sigbus.info/n1570#D] C11 allows ASCII and some multibyte characters in certan Unicode ranges to be used in an identifier. -// -// is_ident0 returns true if a given character is acceptable as the first character of an identifier. -is_ident0 :: proc(c: rune) -> bool { - return in_range(_range_ident0, c) -} -// is_ident0 returns true if a given character is acceptable as a non-first character of an identifier. -is_ident1 :: proc(c: rune) -> bool { - return is_ident0(c) || in_range(_range_ident1, c) -} - -// Returns the number of columns needed to display a given character in a fixed-width font. -// Based on https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c -char_width :: proc(c: rune) -> int { - switch { - case in_range(_range_width0, c): - return 0 - case in_range(_range_width2, c): - return 2 - } - return 1 -} - -display_width :: proc(str: string) -> (w: int) { - for c in str { - w += char_width(c) - } - return -} - - - -_range_ident0 := []rune{ - '_', '_', 'a', 'z', 'A', 'Z', '$', '$', - 0x00A8, 0x00A8, 0x00AA, 0x00AA, 0x00AD, 0x00AD, 0x00AF, 0x00AF, - 0x00B2, 0x00B5, 0x00B7, 0x00BA, 0x00BC, 0x00BE, 0x00C0, 0x00D6, - 0x00D8, 0x00F6, 0x00F8, 0x00FF, 0x0100, 0x02FF, 0x0370, 0x167F, - 0x1681, 0x180D, 0x180F, 0x1DBF, 0x1E00, 0x1FFF, 0x200B, 0x200D, - 0x202A, 0x202E, 0x203F, 0x2040, 0x2054, 0x2054, 0x2060, 0x206F, - 0x2070, 0x20CF, 0x2100, 0x218F, 0x2460, 0x24FF, 0x2776, 0x2793, - 0x2C00, 0x2DFF, 0x2E80, 0x2FFF, 0x3004, 0x3007, 0x3021, 0x302F, - 0x3031, 0x303F, 0x3040, 0xD7FF, 0xF900, 0xFD3D, 0xFD40, 0xFDCF, - 0xFDF0, 0xFE1F, 0xFE30, 0xFE44, 0xFE47, 0xFFFD, - 0x10000, 0x1FFFD, 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, 0x40000, 0x4FFFD, - 0x50000, 0x5FFFD, 0x60000, 0x6FFFD, 0x70000, 0x7FFFD, 0x80000, 0x8FFFD, - 0x90000, 0x9FFFD, 0xA0000, 0xAFFFD, 0xB0000, 0xBFFFD, 0xC0000, 0xCFFFD, - 0xD0000, 0xDFFFD, 0xE0000, 0xEFFFD, - -1, -} - -_range_ident1 := []rune{ - '0', '9', '$', '$', 0x0300, 0x036F, 0x1DC0, 0x1DFF, 0x20D0, 0x20FF, 0xFE20, 0xFE2F, - -1, -} - - -_range_width0 := []rune{ - 0x0000, 0x001F, 0x007f, 0x00a0, 0x0300, 0x036F, 0x0483, 0x0486, - 0x0488, 0x0489, 0x0591, 0x05BD, 0x05BF, 0x05BF, 0x05C1, 0x05C2, - 0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x0600, 0x0603, 0x0610, 0x0615, - 0x064B, 0x065E, 0x0670, 0x0670, 0x06D6, 0x06E4, 0x06E7, 0x06E8, - 0x06EA, 0x06ED, 0x070F, 0x070F, 0x0711, 0x0711, 0x0730, 0x074A, - 0x07A6, 0x07B0, 0x07EB, 0x07F3, 0x0901, 0x0902, 0x093C, 0x093C, - 0x0941, 0x0948, 0x094D, 0x094D, 0x0951, 0x0954, 0x0962, 0x0963, - 0x0981, 0x0981, 0x09BC, 0x09BC, 0x09C1, 0x09C4, 0x09CD, 0x09CD, - 0x09E2, 0x09E3, 0x0A01, 0x0A02, 0x0A3C, 0x0A3C, 0x0A41, 0x0A42, - 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, 0x0A70, 0x0A71, 0x0A81, 0x0A82, - 0x0ABC, 0x0ABC, 0x0AC1, 0x0AC5, 0x0AC7, 0x0AC8, 0x0ACD, 0x0ACD, - 0x0AE2, 0x0AE3, 0x0B01, 0x0B01, 0x0B3C, 0x0B3C, 0x0B3F, 0x0B3F, - 0x0B41, 0x0B43, 0x0B4D, 0x0B4D, 0x0B56, 0x0B56, 0x0B82, 0x0B82, - 0x0BC0, 0x0BC0, 0x0BCD, 0x0BCD, 0x0C3E, 0x0C40, 0x0C46, 0x0C48, - 0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0CBC, 0x0CBC, 0x0CBF, 0x0CBF, - 0x0CC6, 0x0CC6, 0x0CCC, 0x0CCD, 0x0CE2, 0x0CE3, 0x0D41, 0x0D43, - 0x0D4D, 0x0D4D, 0x0DCA, 0x0DCA, 0x0DD2, 0x0DD4, 0x0DD6, 0x0DD6, - 0x0E31, 0x0E31, 0x0E34, 0x0E3A, 0x0E47, 0x0E4E, 0x0EB1, 0x0EB1, - 0x0EB4, 0x0EB9, 0x0EBB, 0x0EBC, 0x0EC8, 0x0ECD, 0x0F18, 0x0F19, - 0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F71, 0x0F7E, - 0x0F80, 0x0F84, 0x0F86, 0x0F87, 0x0F90, 0x0F97, 0x0F99, 0x0FBC, - 0x0FC6, 0x0FC6, 0x102D, 0x1030, 0x1032, 0x1032, 0x1036, 0x1037, - 0x1039, 0x1039, 0x1058, 0x1059, 0x1160, 0x11FF, 0x135F, 0x135F, - 0x1712, 0x1714, 0x1732, 0x1734, 0x1752, 0x1753, 0x1772, 0x1773, - 0x17B4, 0x17B5, 0x17B7, 0x17BD, 0x17C6, 0x17C6, 0x17C9, 0x17D3, - 0x17DD, 0x17DD, 0x180B, 0x180D, 0x18A9, 0x18A9, 0x1920, 0x1922, - 0x1927, 0x1928, 0x1932, 0x1932, 0x1939, 0x193B, 0x1A17, 0x1A18, - 0x1B00, 0x1B03, 0x1B34, 0x1B34, 0x1B36, 0x1B3A, 0x1B3C, 0x1B3C, - 0x1B42, 0x1B42, 0x1B6B, 0x1B73, 0x1DC0, 0x1DCA, 0x1DFE, 0x1DFF, - 0x200B, 0x200F, 0x202A, 0x202E, 0x2060, 0x2063, 0x206A, 0x206F, - 0x20D0, 0x20EF, 0x302A, 0x302F, 0x3099, 0x309A, 0xA806, 0xA806, - 0xA80B, 0xA80B, 0xA825, 0xA826, 0xFB1E, 0xFB1E, 0xFE00, 0xFE0F, - 0xFE20, 0xFE23, 0xFEFF, 0xFEFF, 0xFFF9, 0xFFFB, 0x10A01, 0x10A03, - 0x10A05, 0x10A06, 0x10A0C, 0x10A0F, 0x10A38, 0x10A3A, 0x10A3F, 0x10A3F, - 0x1D167, 0x1D169, 0x1D173, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, - 0x1D242, 0x1D244, 0xE0001, 0xE0001, 0xE0020, 0xE007F, 0xE0100, 0xE01EF, - -1, -} - -_range_width2 := []rune{ - 0x1100, 0x115F, 0x2329, 0x2329, 0x232A, 0x232A, 0x2E80, 0x303E, - 0x3040, 0xA4CF, 0xAC00, 0xD7A3, 0xF900, 0xFAFF, 0xFE10, 0xFE19, - 0xFE30, 0xFE6F, 0xFF00, 0xFF60, 0xFFE0, 0xFFE6, 0x1F000, 0x1F644, - 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, - -1, -} diff --git a/core/c/libc/README.md b/core/c/libc/README.md index 95053b963..08e789757 100644 --- a/core/c/libc/README.md +++ b/core/c/libc/README.md @@ -14,7 +14,7 @@ The following is a mostly-complete projection of the C11 standard library as def | `` | Fully projected | | `` | Not applicable, use Odin's operators | | `` | Not projected | -| `` | Not projected | +| `` | Fully projected | | `` | Mostly projected, see [limitations](#Limitations) | | `` | Fully projected | | `` | Fully projected | @@ -70,4 +70,4 @@ with the following copyright. ``` Copyright 2021 Dale Weiler . -``` \ No newline at end of file +``` diff --git a/core/c/libc/locale.odin b/core/c/libc/locale.odin new file mode 100644 index 000000000..371d755c5 --- /dev/null +++ b/core/c/libc/locale.odin @@ -0,0 +1,133 @@ +package libc + +import "core:c" + +when ODIN_OS == .Windows { + foreign import libc "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import libc "system:System.framework" +} else { + foreign import libc "system:c" +} + +// locale.h - category macros + +foreign libc { + /* + Sets the components of an object with the type lconv with the values appropriate for the + formatting of numeric quantities (monetary and otherwise) according to the rules of the current + locale. + + Returns: a pointer to the lconv structure, might be invalidated by subsequent calls to localeconv() and setlocale() + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localeconv.html ]] + */ + localeconv :: proc() -> ^lconv --- + + /* + Selects the appropriate piece of the global locale, as specified by the category and locale arguments, + and can be used to change or query the entire global locale or portions thereof. + + Returns: the current locale if `locale` is `nil`, the set locale otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html ]] + */ + @(link_name=LSETLOCALE) + setlocale :: proc(category: Locale_Category, locale: cstring) -> cstring --- +} + +Locale_Category :: enum c.int { + ALL = LC_ALL, + COLLATE = LC_COLLATE, + CTYPE = LC_CTYPE, + MESSAGES = LC_MESSAGES, + MONETARY = LC_MONETARY, + NUMERIC = LC_NUMERIC, + TIME = LC_TIME, +} + +when ODIN_OS == .NetBSD { + @(private) LSETLOCALE :: "__setlocale50" +} else { + @(private) LSETLOCALE :: "setlocale" +} + +when ODIN_OS == .Windows { + lconv :: struct { + decimal_point: cstring, + thousand_sep: cstring, + grouping: cstring, + int_curr_symbol: cstring, + currency_symbol: cstring, + mon_decimal_points: cstring, + mon_thousands_sep: cstring, + mon_grouping: cstring, + positive_sign: cstring, + negative_sign: cstring, + int_frac_digits: c.char, + frac_digits: c.char, + p_cs_precedes: c.char, + p_sep_by_space: c.char, + n_cs_precedes: c.char, + n_sep_by_space: c.char, + p_sign_posn: c.char, + n_sign_posn: c.char, + _W_decimal_point: [^]u16 `fmt:"s,0"`, + _W_thousands_sep: [^]u16 `fmt:"s,0"`, + _W_int_curr_symbol: [^]u16 `fmt:"s,0"`, + _W_currency_symbol: [^]u16 `fmt:"s,0"`, + _W_mon_decimal_point: [^]u16 `fmt:"s,0"`, + _W_mon_thousands_sep: [^]u16 `fmt:"s,0"`, + _W_positive_sign: [^]u16 `fmt:"s,0"`, + _W_negative_sign: [^]u16 `fmt:"s,0"`, + } +} else { + lconv :: struct { + decimal_point: cstring, + thousand_sep: cstring, + grouping: cstring, + int_curr_symbol: cstring, + currency_symbol: cstring, + mon_decimal_points: cstring, + mon_thousands_sep: cstring, + mon_grouping: cstring, + positive_sign: cstring, + negative_sign: cstring, + int_frac_digits: c.char, + frac_digits: c.char, + p_cs_precedes: c.char, + p_sep_by_space: c.char, + n_cs_precedes: c.char, + n_sep_by_space: c.char, + p_sign_posn: c.char, + n_sign_posn: c.char, + _int_p_cs_precedes: c.char, + _int_n_cs_precedes: c.char, + _int_p_sep_by_space: c.char, + _int_n_sep_by_space: c.char, + _int_p_sign_posn: c.char, + _int_n_sign_posn: c.char, + } +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Windows { + + LC_ALL :: 0 + LC_COLLATE :: 1 + LC_CTYPE :: 2 + LC_MESSAGES :: 6 + LC_MONETARY :: 3 + LC_NUMERIC :: 4 + LC_TIME :: 5 + +} else when ODIN_OS == .Linux { + + LC_CTYPE :: 0 + LC_NUMERIC :: 1 + LC_TIME :: 2 + LC_COLLATE :: 3 + LC_MONETARY :: 4 + LC_MESSAGES :: 5 + LC_ALL :: 6 + +} diff --git a/core/compress/zlib/doc.odin b/core/compress/zlib/doc.odin index 0a5b4eb40..6ae537a87 100644 --- a/core/compress/zlib/doc.odin +++ b/core/compress/zlib/doc.odin @@ -13,6 +13,7 @@ Example: package main import "core:bytes" + import "core:compress/zlib" import "core:fmt" main :: proc() { @@ -36,7 +37,7 @@ Example: buf: bytes.Buffer // We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one. - err := inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE) + err := zlib.inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE) defer bytes.buffer_destroy(&buf) if err != nil { diff --git a/core/dynlib/example/example.odin b/core/dynlib/example/example.odin index f12233b0a..78fb5a98c 100644 --- a/core/dynlib/example/example.odin +++ b/core/dynlib/example/example.odin @@ -21,12 +21,14 @@ Symbols :: struct { main :: proc() { sym: Symbols + LIB_PATH :: "lib." + dynlib.LIBRARY_FILE_EXTENSION + // Load symbols from `lib.dll` into Symbols struct. // Each struct field is prefixed with `foo_` before lookup in the DLL's symbol table. // The library's Handle (to unload) will be stored in `sym._my_lib_handle`. This way you can load multiple DLLs in one struct. - count, ok := dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle") + count, ok := dynlib.initialize_symbols(&sym, LIB_PATH, "foo_", "_my_lib_handle") defer dynlib.unload_library(sym._my_lib_handle) - fmt.printf("(Initial DLL Load) ok: %v. %v symbols loaded from lib.dll (%p).\n", ok, count, sym._my_lib_handle) + fmt.printf("(Initial DLL Load) ok: %v. %v symbols loaded from " + LIB_PATH + " (%p).\n", ok, count, sym._my_lib_handle) if count > 0 { fmt.println("42 + 42 =", sym.add(42, 42)) @@ -34,12 +36,12 @@ main :: proc() { fmt.println("hellope =", sym.hellope^) } - count, ok = dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle") - fmt.printf("(DLL Reload) ok: %v. %v symbols loaded from lib.dll (%p).\n", ok, count, sym._my_lib_handle) + count, ok = dynlib.initialize_symbols(&sym, LIB_PATH, "foo_", "_my_lib_handle") + fmt.printf("(DLL Reload) ok: %v. %v symbols loaded from " + LIB_PATH + " (%p).\n", ok, count, sym._my_lib_handle) if count > 0 { fmt.println("42 + 42 =", sym.add(42, 42)) fmt.println("84 - 13 =", sym.sub(84, 13)) fmt.println("hellope =", sym.hellope^) } -} \ No newline at end of file +} diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index 09e16002d..cd9eed5f8 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -12,6 +12,11 @@ A handle to a dynamically loaded library. */ Library :: distinct rawptr +/* +The file extension for dynamic libraries on the target OS. +*/ +LIBRARY_FILE_EXTENSION :: _LIBRARY_FILE_EXTENSION + /* Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded library available to resolve references in subsequently loaded libraries. @@ -37,8 +42,8 @@ Example: fmt.println("The library %q was successfully loaded", LIBRARY_PATH) } */ -load_library :: proc(path: string, global_symbols := false) -> (library: Library, did_load: bool) { - return _load_library(path, global_symbols) +load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (library: Library, did_load: bool) { + return _load_library(path, global_symbols, allocator) } /* @@ -98,8 +103,8 @@ Example: } } */ -symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok { - return _symbol_address(library, symbol) +symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) #optional_ok { + return _symbol_address(library, symbol, allocator) } /* @@ -123,31 +128,36 @@ initialize_symbols :: proc( ) -> (count: int = -1, ok: bool = false) where intrinsics.type_is_struct(T) { assert(symbol_table != nil) - handle := load_library(library_path) or_return + // First, (re)load the library. + handle: Library + for field in reflect.struct_fields_zipped(T) { + if field.name == handle_field_name { + field_ptr := rawptr(uintptr(symbol_table) + field.offset) + + // We appear to be hot reloading. Unload previous incarnation of the library. + if old_handle := (^Library)(field_ptr)^; old_handle != nil { + unload_library(old_handle) or_return + } + + handle = load_library(library_path) or_return + (^Library)(field_ptr)^ = handle + break + } + } // Buffer to concatenate the prefix + symbol name. prefixed_symbol_buf: [2048]u8 = --- count = 0 for field in reflect.struct_fields_zipped(T) { + // If we're not the library handle, the field needs to be a pointer type, be it a procedure pointer or an exported global. + if field.name == handle_field_name || !(reflect.is_procedure(field.type) || reflect.is_pointer(field.type)) { + continue + } + // Calculate address of struct member field_ptr := rawptr(uintptr(symbol_table) + field.offset) - // If we've come across the struct member for the handle, store it and continue scanning for other symbols. - if field.name == handle_field_name { - // We appear to be hot reloading. Unload previous incarnation of the library. - if old_handle := (^Library)(field_ptr)^; old_handle != nil { - unload_library(old_handle) or_return - } - (^Library)(field_ptr)^ = handle - continue - } - - // We're not the library handle, so the field needs to be a pointer type, be it a procedure pointer or an exported global. - if !(reflect.is_procedure(field.type) || reflect.is_pointer(field.type)) { - continue - } - // Let's look up or construct the symbol name to find in the library prefixed_name: string @@ -174,4 +184,4 @@ initialize_symbols :: proc( // Returns an error message for the last failed procedure call. last_error :: proc() -> string { return _last_error() -} \ No newline at end of file +} diff --git a/core/dynlib/lib_js.odin b/core/dynlib/lib_js.odin index 698cfee9c..99f486dd0 100644 --- a/core/dynlib/lib_js.odin +++ b/core/dynlib/lib_js.odin @@ -2,7 +2,11 @@ #+private package dynlib -_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { +import "base:runtime" + +_LIBRARY_FILE_EXTENSION :: "" + +_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { return nil, false } @@ -10,10 +14,10 @@ _unload_library :: proc(library: Library) -> bool { return false } -_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { +_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) { return nil, false } _last_error :: proc() -> string { return "" -} \ No newline at end of file +} diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index f467d730d..50ab1acc8 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -2,28 +2,40 @@ #+private package dynlib -import "core:os" +import "base:runtime" -_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { - flags := os.RTLD_NOW +import "core:strings" +import "core:sys/posix" + +_LIBRARY_FILE_EXTENSION :: "dylib" when ODIN_OS == .Darwin else "so" + +_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { + flags := posix.RTLD_Flags{.NOW} if global_symbols { - flags |= os.RTLD_GLOBAL + flags += {.GLOBAL} } - lib := os.dlopen(path, flags) + + cpath := strings.clone_to_cstring(path, allocator) + defer delete(cpath, allocator) + + lib := posix.dlopen(cpath, flags) return Library(lib), lib != nil } _unload_library :: proc(library: Library) -> bool { - return os.dlclose(rawptr(library)) + return posix.dlclose(posix.Symbol_Table(library)) == 0 } -_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { - ptr = os.dlsym(rawptr(library), symbol) +_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) { + csymbol := strings.clone_to_cstring(symbol, allocator) + defer delete(csymbol, allocator) + + ptr = posix.dlsym(posix.Symbol_Table(library), csymbol) found = ptr != nil return } _last_error :: proc() -> string { - err := os.dlerror() + err := string(posix.dlerror()) return "unknown" if err == "" else err -} \ No newline at end of file +} diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index 6c41a1a75..05cd2cb3c 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -2,11 +2,15 @@ #+private package dynlib +import "base:runtime" + import win32 "core:sys/windows" import "core:strings" import "core:reflect" -_load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (Library, bool) { +_LIBRARY_FILE_EXTENSION :: "dll" + +_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { // NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL wide_path := win32.utf8_to_wstring(path, allocator) defer free(wide_path, allocator) @@ -19,7 +23,7 @@ _unload_library :: proc(library: Library) -> bool { return bool(ok) } -_symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) { +_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) { c_str := strings.clone_to_cstring(symbol, allocator) defer delete(c_str, allocator) ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str) @@ -31,4 +35,4 @@ _last_error :: proc() -> string { err := win32.System_Error(win32.GetLastError()) err_msg := reflect.enum_string(err) return "unknown" if err_msg == "" else err_msg -} \ No newline at end of file +} diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index 692be0020..8eb829ed3 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -563,7 +563,7 @@ to_json :: proc(val: Value, allocator := context.allocator) -> (json.Value, mem. case: return false } } - return false + return true } if keys_all_strings(v) { diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index bf27171f4..c39255d9d 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -442,9 +442,6 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header loc := #caller_location, ) -> (out_of_space: bool, err: Unmarshal_Error) { for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 { - elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size)) - elem := any{elem_ptr, elemt.id} - hdr := _decode_header(d.reader) or_return // Double size if out of capacity. @@ -459,6 +456,10 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if !ok { return false, .Out_Of_Memory } } + // Set ptr after potential resizes to avoid invalidation. + elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size)) + elem := any{elem_ptr, elemt.id} + err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc) if length == -1 && err == .Break { break } if err != nil { return } @@ -509,7 +510,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header raw := (^mem.Raw_Dynamic_Array)(v.data) raw.data = raw_data(data) raw.len = 0 - raw.cap = length + raw.cap = scap raw.allocator = context.allocator _ = assign_array(d, raw, t.elem, length) or_return @@ -627,7 +628,8 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, unknown := length == -1 fields := reflect.struct_fields_zipped(ti.id) - for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 { + idx := 0 + for ; idx < len(fields) && (unknown || idx < length); idx += 1 { // Decode key, keys can only be strings. key: string if keyv, kerr := decode_key(d, v, context.temp_allocator); unknown && kerr == .Break { @@ -662,6 +664,8 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, // Skips unused map entries. if use_field_idx < 0 { + val := err_conv(_decode_from_decoder(d, allocator=context.temp_allocator)) or_return + destroy(val, context.temp_allocator) continue } } @@ -672,6 +676,17 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, fany := any{ptr, field.type.id} _unmarshal_value(d, fany, _decode_header(r) or_return) or_return } + + // If there are fields left in the map that did not get decoded into the struct, decode and discard them. + if !unknown { + for _ in idx.. max(type) { + if type > max(Layer_Data_Type) { if r.print_error { fmt.eprintf("HxA Error: file '%s' has layer data type %d. Maximum value is %d\n", r.filename, u8(type), u8(max(Layer_Data_Type))) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 009bf7ade..f0f0927a1 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -192,12 +192,6 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Simd_Vector: return .Unsupported_Type - - case runtime.Type_Info_Relative_Pointer: - return .Unsupported_Type - - case runtime.Type_Info_Relative_Multi_Pointer: - return .Unsupported_Type case runtime.Type_Info_Matrix: return .Unsupported_Type diff --git a/core/encoding/json/tokenizer.odin b/core/encoding/json/tokenizer.odin index 5c20a2cc3..e46d879a7 100644 --- a/core/encoding/json/tokenizer.odin +++ b/core/encoding/json/tokenizer.odin @@ -259,6 +259,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) { skip_digits(t) } if t.r == 'e' || t.r == 'E' { + token.kind = .Float switch r := next_rune(t); r { case '+', '-': next_rune(t) @@ -485,7 +486,7 @@ is_valid_string_literal :: proc(str: string, spec: Specification) -> bool { case '"': // okay case '\'': - if spec != .JSON { + if spec == .JSON { return false } // okay diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 738e20c68..c70b8d39a 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -172,20 +172,33 @@ assign_float :: proc(val: any, f: $T) -> bool { @(private) -unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> bool { +unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> (ok: bool, err: Error) { val := val switch &dst in val { case string: dst = str - return true + return true, nil case cstring: if str == "" { - dst = strings.clone_to_cstring("", p.allocator) + a_err: runtime.Allocator_Error + dst, a_err = strings.clone_to_cstring("", p.allocator) + #partial switch a_err { + case nil: + // okay + case .Out_Of_Memory: + err = .Out_Of_Memory + case: + err = .Invalid_Allocator + } + if err != nil { + return + } } else { // NOTE: This is valid because 'clone_string' appends a NUL terminator dst = cstring(raw_data(str)) } - return true + ok = true + return } #partial switch variant in ti.variant { @@ -193,31 +206,37 @@ unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.T for name, i in variant.names { if name == str { assign_int(val, variant.values[i]) - return true + return true, nil } } // TODO(bill): should this be an error or not? - return true + return true, nil case reflect.Type_Info_Integer: - i := strconv.parse_i128(str) or_return + i, pok := strconv.parse_i128(str) + if !pok { + return false, nil + } if assign_int(val, i) { - return true + return true, nil } if assign_float(val, i) { - return true + return true, nil } case reflect.Type_Info_Float: - f := strconv.parse_f64(str) or_return + f, pok := strconv.parse_f64(str) + if !pok { + return false, nil + } if assign_int(val, f) { - return true + return true, nil } if assign_float(val, f) { - return true + return true, nil } } - return false + return false, nil } @@ -304,7 +323,7 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { case .Ident: advance_token(p) if p.spec == .MJSON { - if unmarshal_string_token(p, any{v.data, ti.id}, token.text, ti) { + if unmarshal_string_token(p, any{v.data, ti.id}, token.text, ti) or_return { return nil } } @@ -312,13 +331,18 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { case .String: advance_token(p) - str := unquote_string(token, p.spec, p.allocator) or_return - if unmarshal_string_token(p, any{v.data, ti.id}, str, ti) { - return nil + str := unquote_string(token, p.spec, p.allocator) or_return + dest := any{v.data, ti.id} + if !(unmarshal_string_token(p, dest, str, ti) or_return) { + delete(str, p.allocator) + return UNSUPPORTED_TYPE } - delete(str, p.allocator) - return UNSUPPORTED_TYPE + switch destv in dest { + case string, cstring: + case: delete(str, p.allocator) + } + return nil case .Open_Brace: return unmarshal_object(p, v, .Close_Brace) @@ -393,15 +417,15 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm if .raw_union in t.flags { return UNSUPPORTED_TYPE } - + + fields := reflect.struct_fields_zipped(ti.id) + struct_loop: for p.curr_token.kind != end_token { - key, _ := parse_object_key(p, p.allocator) + key := parse_object_key(p, p.allocator) or_return defer delete(key, p.allocator) unmarshal_expect_token(p, .Colon) - fields := reflect.struct_fields_zipped(ti.id) - field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool { prev_set := field_used[offset/8] & byte(offset&7) != 0 field_used[offset/8] |= byte(offset&7) @@ -409,7 +433,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8 - field_used := intrinsics.alloca(field_used_bytes, 1) + field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types. intrinsics.mem_zero(field_used, field_used_bytes) use_field_idx := -1 diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 0d11ad8a9..49e9f2e6d 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1531,7 +1531,7 @@ fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) { case 'o': _fmt_int(fi, u, 8, false, 8*size_of(rawptr), __DIGITS_UPPER) case 'i', 'd': _fmt_int(fi, u, 10, false, 8*size_of(rawptr), __DIGITS_UPPER) case 'z': _fmt_int(fi, u, 12, false, 8*size_of(rawptr), __DIGITS_UPPER) - case 'x': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER) + case 'x': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_LOWER) case 'X': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER) case: @@ -3002,18 +3002,6 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { case runtime.Type_Info_Bit_Set: fmt_bit_set(fi, v, verb = verb) - case runtime.Type_Info_Relative_Pointer: - ptr := reflect.relative_pointer_to_absolute_raw(v.data, info.base_integer.id) - absolute_ptr := any{ptr, info.pointer.id} - - fmt_value(fi, absolute_ptr, verb) - - case runtime.Type_Info_Relative_Multi_Pointer: - ptr := reflect.relative_pointer_to_absolute_raw(v.data, info.base_integer.id) - absolute_ptr := any{ptr, info.pointer.id} - - fmt_value(fi, absolute_ptr, verb) - case runtime.Type_Info_Matrix: fmt_matrix(fi, v, verb, info) diff --git a/core/image/general.odin b/core/image/general.odin index 17a8f35ea..c4a884071 100644 --- a/core/image/general.odin +++ b/core/image/general.odin @@ -23,7 +23,15 @@ register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { loader := _internal_loaders[which(data)] if loader == nil { - return nil, .Unsupported_Format + + // Check if there is at least one loader, otherwise panic to let the user know about misuse. + for a_loader in _internal_loaders { + if a_loader != nil { + return nil, .Unsupported_Format + } + } + + panic("image.load called when no image loaders are registered. Register a loader by first importing a subpackage (eg: `import \"core:image/png\"`), or with image.register") } return loader(data, options, allocator) } @@ -185,7 +193,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .HDR case s[:4] == "\x38\x42\x50\x53": return .PSD - case s[:4] != "\x53\x80\xF6\x34" && s[88:92] == "PICT": + case s[:4] == "\x53\x80\xF6\x34" && s[88:92] == "PICT": return .PIC case s[:4] == "\x69\x63\x6e\x73": return .ICNS diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index f094b54a9..a9495ed4d 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -396,132 +396,4 @@ exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) { General helper functions */ -compute_buffer_size :: image.compute_buffer_size - -/* - PNG save helpers -*/ - -when false { - - make_chunk :: proc(c: any, t: Chunk_Type) -> (res: Chunk) { - - data: []u8 - if v, ok := c.([]u8); ok { - data = v - } else { - data = mem.any_to_bytes(c) - } - - res.header.length = u32be(len(data)) - res.header.type = t - res.data = data - - // CRC the type - crc := hash.crc32(mem.any_to_bytes(res.header.type)) - // Extend the CRC with the data - res.crc = u32be(hash.crc32(data, crc)) - return - } - - write_chunk :: proc(fd: os.Handle, chunk: Chunk) { - c := chunk - // Write length + type - os.write_ptr(fd, &c.header, 8) - // Write data - os.write_ptr(fd, mem.raw_data(c.data), int(c.header.length)) - // Write CRC32 - os.write_ptr(fd, &c.crc, 4) - } - - write_image_as_png :: proc(filename: string, image: Image) -> (err: Error) { - profiler.timed_proc() - using image - using os - flags: int = O_WRONLY|O_CREATE|O_TRUNC - - if len(image.pixels) == 0 || len(image.pixels) < image.width * image.height * int(image.channels) { - return .Invalid_Image_Dimensions - } - - mode: int = 0 - when ODIN_OS == .Linux || ODIN_OS == .Darwin { - // NOTE(justasd): 644 (owner read, write; group read; others read) - mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH - } - - fd, fderr := open(filename, flags, mode) - if fderr != nil { - return .Cannot_Open_File - } - defer close(fd) - - magic := Signature - - write_ptr(fd, &magic, 8) - - ihdr := IHDR{ - width = u32be(width), - height = u32be(height), - bit_depth = depth, - compression_method = 0, - filter_method = 0, - interlace_method = .None, - } - - switch channels { - case 1: ihdr.color_type = Color_Type{} - case 2: ihdr.color_type = Color_Type{.Alpha} - case 3: ihdr.color_type = Color_Type{.Color} - case 4: ihdr.color_type = Color_Type{.Color, .Alpha} - case:// Unhandled - return .Unknown_Color_Type - } - h := make_chunk(ihdr, .IHDR) - write_chunk(fd, h) - - bytes_needed := width * height * int(channels) + height - filter_bytes := mem.make_dynamic_array_len_cap([dynamic]u8, bytes_needed, bytes_needed, context.allocator) - defer delete(filter_bytes) - - i := 0; j := 0 - // Add a filter byte 0 per pixel row - for y := 0; y < height; y += 1 { - filter_bytes[j] = 0; j += 1 - for x := 0; x < width; x += 1 { - for z := 0; z < channels; z += 1 { - filter_bytes[j+z] = image.pixels[i+z] - } - i += channels; j += channels - } - } - assert(j == bytes_needed) - - a: []u8 = filter_bytes[:] - - out_buf: ^[dynamic]u8 - defer free(out_buf) - - ctx := zlib.ZLIB_Context{ - in_buf = &a, - out_buf = out_buf, - } - err = zlib.write_zlib_stream_from_memory(&ctx) - - b: []u8 - if err == nil { - b = ctx.out_buf[:] - } else { - return err - } - - idat := make_chunk(b, .IDAT) - - write_chunk(fd, idat) - - iend := make_chunk([]u8{}, .IEND) - write_chunk(fd, iend) - - return nil - } -} +compute_buffer_size :: image.compute_buffer_size \ No newline at end of file diff --git a/core/io/util.odin b/core/io/util.odin index e65a69fb3..fdbbd5b9f 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -132,9 +132,13 @@ write_encoded_rune :: proc(w: Writer, r: rune, write_quote := true, n_written: ^ buf: [2]byte s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { - case 0: write_string(w, "00", &n) or_return - case 1: write_byte(w, '0', &n) or_return - case 2: write_string(w, s, &n) or_return + case 0: + write_string(w, "00", &n) or_return + case 1: + write_byte(w, '0', &n) or_return + fallthrough + case 2: + write_string(w, s, &n) or_return } } else { write_rune(w, r, &n) or_return @@ -225,7 +229,7 @@ write_escaped_rune :: proc(w: Writer, r: rune, quote: byte, html_safe := false, } else { write_byte(w, '\\', &n) or_return write_byte(w, 'U', &n) or_return - for s := 24; s >= 0; s -= 4 { + for s := 28; s >= 0; s -= 4 { write_byte(w, DIGITS_LOWER[c>>uint(s) & 0xf], &n) or_return } } diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index e45f99523..6d93fb879 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -37,30 +37,30 @@ File_Console_Logger_Data :: struct { ident: string, } -create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "") -> Logger { - data := new(File_Console_Logger_Data) +create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { + data := new(File_Console_Logger_Data, allocator) data.file_handle = h data.ident = ident return Logger{file_console_logger_proc, data, lowest, opt} } -destroy_file_logger :: proc(log: Logger) { +destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { data := cast(^File_Console_Logger_Data)log.data if data.file_handle != os.INVALID_HANDLE { os.close(data.file_handle) } - free(data) + free(data, allocator) } -create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "") -> Logger { - data := new(File_Console_Logger_Data) +create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { + data := new(File_Console_Logger_Data, allocator) data.file_handle = os.INVALID_HANDLE data.ident = ident return Logger{file_console_logger_proc, data, lowest, opt} } -destroy_console_logger :: proc(log: Logger) { - free(log.data) +destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { + free(log.data, allocator) } file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { diff --git a/core/log/multi_logger.odin b/core/log/multi_logger.odin index 96d0f3dbd..6122554bb 100644 --- a/core/log/multi_logger.odin +++ b/core/log/multi_logger.odin @@ -5,17 +5,17 @@ Multi_Logger_Data :: struct { loggers: []Logger, } -create_multi_logger :: proc(logs: ..Logger) -> Logger { - data := new(Multi_Logger_Data) - data.loggers = make([]Logger, len(logs)) +create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> Logger { + data := new(Multi_Logger_Data, allocator) + data.loggers = make([]Logger, len(logs), allocator) copy(data.loggers, logs) return Logger{multi_logger_proc, data, Level.Debug, nil} } -destroy_multi_logger :: proc(log: Logger) { +destroy_multi_logger :: proc(log: Logger, allocator := context.allocator) { data := (^Multi_Logger_Data)(log.data) - delete(data.loggers) - free(data) + delete(data.loggers, allocator) + free(data, allocator) } multi_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, diff --git a/core/math/linalg/general.odin b/core/math/linalg/general.odin index 7587ad63f..8c4f2954a 100644 --- a/core/math/linalg/general.odin +++ b/core/math/linalg/general.odin @@ -53,15 +53,15 @@ vector_dot :: proc "contextless" (a, b: $T/[$N]$E) -> (c: E) where IS_NUMERIC(E) } @(require_results) quaternion64_dot :: proc "contextless" (a, b: $T/quaternion64) -> (c: f16) { - return a.w*a.w + a.x*b.x + a.y*b.y + a.z*b.z + return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z } @(require_results) quaternion128_dot :: proc "contextless" (a, b: $T/quaternion128) -> (c: f32) { - return a.w*a.w + a.x*b.x + a.y*b.y + a.z*b.z + return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z } @(require_results) quaternion256_dot :: proc "contextless" (a, b: $T/quaternion256) -> (c: f64) { - return a.w*a.w + a.x*b.x + a.y*b.y + a.z*b.z + return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z } dot :: proc{scalar_dot, vector_dot, quaternion64_dot, quaternion128_dot, quaternion256_dot} @@ -167,6 +167,18 @@ vector_triple_product :: proc "contextless" (a, b, c: $T/[$N]$E) -> T where IS_N length :: proc{vector_length, quaternion_length} length2 :: proc{vector_length2, quaternion_length2} + +@(require_results) +clamp_length :: proc "contextless" (v: $T/[$N]$E, a: E) -> T where IS_FLOAT(E) { + if a <= 0 { + return 0 + } + + m2 := length2(v) + return v if (m2 <= a*a) else (v / sqrt(m2) * a) // returns original when m2 is 0 +} + + @(require_results) projection :: proc "contextless" (x, normal: $T/[$N]$E) -> T where IS_NUMERIC(E) { return dot(x, normal) / dot(normal, normal) * normal diff --git a/core/math/linalg/glsl/linalg_glsl.odin b/core/math/linalg/glsl/linalg_glsl.odin index 5444f89e2..ca61891cb 100644 --- a/core/math/linalg/glsl/linalg_glsl.odin +++ b/core/math/linalg/glsl/linalg_glsl.odin @@ -473,6 +473,22 @@ floor :: proc{ @(require_results) floor_dvec3 :: proc "c" (x: dvec3) -> dvec3 { return {floor(x.x), floor(x.y), floor(x.z)} } @(require_results) floor_dvec4 :: proc "c" (x: dvec4) -> dvec4 { return {floor(x.x), floor(x.y), floor(x.z), floor(x.w)} } +trunc :: proc{ + trunc_f32, + trunc_f64, + trunc_vec2, + trunc_vec3, + trunc_vec4, + trunc_dvec2, + trunc_dvec3, + trunc_dvec4, +} +@(require_results) trunc_vec2 :: proc "c" (x: vec2) -> vec2 { return {trunc(x.x), trunc(x.y)} } +@(require_results) trunc_vec3 :: proc "c" (x: vec3) -> vec3 { return {trunc(x.x), trunc(x.y), trunc(x.z)} } +@(require_results) trunc_vec4 :: proc "c" (x: vec4) -> vec4 { return {trunc(x.x), trunc(x.y), trunc(x.z), trunc(x.w)} } +@(require_results) trunc_dvec2 :: proc "c" (x: dvec2) -> dvec2 { return {trunc(x.x), trunc(x.y)} } +@(require_results) trunc_dvec3 :: proc "c" (x: dvec3) -> dvec3 { return {trunc(x.x), trunc(x.y), trunc(x.z)} } +@(require_results) trunc_dvec4 :: proc "c" (x: dvec4) -> dvec4 { return {trunc(x.x), trunc(x.y), trunc(x.z), trunc(x.w)} } round :: proc{ diff --git a/core/math/linalg/glsl/linalg_glsl_math.odin b/core/math/linalg/glsl/linalg_glsl_math.odin index 82b1857ab..b4461ca3b 100644 --- a/core/math/linalg/glsl/linalg_glsl_math.odin +++ b/core/math/linalg/glsl/linalg_glsl_math.odin @@ -23,6 +23,7 @@ import "core:math" @(require_results) exp2_f32 :: proc "c" (x: f32) -> f32 { return math.pow(f32(2), x) } @(require_results) sign_f32 :: proc "c" (x: f32) -> f32 { return math.sign(x) } @(require_results) floor_f32 :: proc "c" (x: f32) -> f32 { return math.floor(x) } +@(require_results) trunc_f32 :: proc "c" (x: f32) -> f32 { return math.trunc(x) } @(require_results) round_f32 :: proc "c" (x: f32) -> f32 { return math.round(x) } @(require_results) ceil_f32 :: proc "c" (x: f32) -> f32 { return math.ceil(x) } @(require_results) mod_f32 :: proc "c" (x, y: f32) -> f32 { return math.mod(x, y) } @@ -55,6 +56,7 @@ fract_f32 :: proc "c" (x: f32) -> f32 { @(require_results) exp2_f64 :: proc "c" (x: f64) -> f64 { return math.pow(f64(2), x) } @(require_results) sign_f64 :: proc "c" (x: f64) -> f64 { return math.sign(x) } @(require_results) floor_f64 :: proc "c" (x: f64) -> f64 { return math.floor(x) } +@(require_results) trunc_f64 :: proc "c" (x: f64) -> f64 { return math.trunc(x) } @(require_results) round_f64 :: proc "c" (x: f64) -> f64 { return math.round(x) } @(require_results) ceil_f64 :: proc "c" (x: f64) -> f64 { return math.ceil(x) } @(require_results) mod_f64 :: proc "c" (x, y: f64) -> f64 { return math.mod(x, y) } diff --git a/core/math/math.odin b/core/math/math.odin index 0e21afa67..934842318 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -1271,7 +1271,7 @@ binomial :: proc "contextless" (n, k: int) -> int { } b := n - for i in 2.. (sin, cos: f64) #no_bounds_check { // sin coefficients @(private="file") _sin := [?]f64{ - 0h3de5d8fd1fd19ccd, // 1.58962301576546568060e-10 - 0hbe5ae5e5a9291f5d, // -2.50507477628578072866e-8 - 0h3ec71de3567d48a1, // 2.75573136213857245213e-6 - 0hbf2a01a019bfdf03, // -1.98412698295895385996e-4 - 0h3f8111111110f7d0, // 8.33333333332211858878e-3 - 0hbfc5555555555548, // -1.66666666666666307295e-1 + 0h3de5d8fd1fd19ccd, // 1.58962301576546568060e-10 + 0hbe5ae5e5a9291f5d, // -2.50507477628578072866e-8 + 0h3ec71de3567d48a1, // 2.75573136213857245213e-6 + 0hbf2a01a019bfdf03, // -1.98412698295895385996e-4 + 0h3f8111111110f7d0, // 8.33333333332211858878e-3 + 0hbfc5555555555548, // -1.66666666666666307295e-1 } // cos coefficients diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 61301cf8a..72d9400d7 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -29,30 +29,6 @@ Reset the seed used by the context.random_generator. Inputs: - seed: The seed value -Example: - import "core:math/rand" - import "core:fmt" - - set_global_seed_example :: proc() { - rand.set_global_seed(1) - fmt.println(rand.uint64()) - } - -Possible Output: - - 10 -*/ -@(deprecated="Prefer `rand.reset`") -set_global_seed :: proc(seed: u64) { - runtime.random_generator_reset_u64(context.random_generator, seed) -} - -/* -Reset the seed used by the context.random_generator. - -Inputs: -- seed: The seed value - Example: import "core:math/rand" import "core:fmt" @@ -670,20 +646,69 @@ choice :: proc(array: $T/[]$E, gen := context.random_generator) -> (res: E) { @(require_results) -choice_enum :: proc($T: typeid, gen := context.random_generator) -> T - where - intrinsics.type_is_enum(T), - size_of(T) <= 8, - len(T) == cap(T) /* Only allow contiguous enum types */ \ -{ - when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) && - u64(max(T)) > u64(max(i64)) { - i := uint64(gen) % u64(len(T)) - i += u64(min(T)) - return T(i) +choice_enum :: proc($T: typeid, gen := context.random_generator) -> T where intrinsics.type_is_enum(T) { + when size_of(T) <= 8 && len(T) == cap(T) { + when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) && + u64(max(T)) > u64(max(i64)) { + i := uint64(gen) % u64(len(T)) + i += u64(min(T)) + return T(i) + } else { + i := int63_max(i64(len(T)), gen) + i += i64(min(T)) + return T(i) + } } else { - i := int63_max(i64(len(T)), gen) - i += i64(min(T)) - return T(i) + values := runtime.type_info_base(type_info_of(T)).variant.(runtime.Type_Info_Enum).values + return T(choice(values)) } -} \ No newline at end of file +} + +/* +Returns a random *set* bit from the provided `bit_set`. + +Inputs: +- set: The `bit_set` to choose a random set bit from + +Returns: +- res: The randomly selected bit, or the zero value if `ok` is `false` +- ok: Whether the bit_set was not empty and thus `res` is actually a random set bit + +Example: + import "core:math/rand" + import "core:fmt" + + choice_bit_set_example :: proc() { + Flags :: enum { + A, + B = 10, + C, + } + + fmt.println(rand.choice_bit_set(bit_set[Flags]{})) + fmt.println(rand.choice_bit_set(bit_set[Flags]{.B})) + fmt.println(rand.choice_bit_set(bit_set[Flags]{.B, .C})) + fmt.println(rand.choice_bit_set(bit_set[0..<15]{5, 1, 4})) + } + +Possible Output: + A false + B true + C true + 5 true +*/ +@(require_results) +choice_bit_set :: proc(set: $T/bit_set[$E], gen := context.random_generator) -> (res: E, ok: bool) { + total_set := card(set) + if total_set == 0 { + return {}, false + } + + core_set := transmute(intrinsics.type_bit_set_underlying_type(T))set + + for target := int_max(total_set, gen); target > 0; target -= 1 { + core_set &= core_set - 1 + } + + return E(intrinsics.count_trailing_zeros(core_set)), true +} diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index 13d509f1e..028be58e3 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -140,14 +140,6 @@ arena_init :: proc(a: ^Arena, data: []byte) { a.temp_count = 0 } -@(deprecated="prefer 'mem.arena_init'") -init_arena :: proc(a: ^Arena, data: []byte) { - a.data = data - a.offset = 0 - a.peak_used = 0 - a.temp_count = 0 -} - /* Allocate memory from an arena. @@ -786,14 +778,6 @@ stack_init :: proc(s: ^Stack, data: []byte) { s.peak_used = 0 } -@(deprecated="prefer 'mem.stack_init'") -init_stack :: proc(s: ^Stack, data: []byte) { - s.data = data - s.prev_offset = 0 - s.curr_offset = 0 - s.peak_used = 0 -} - /* Allocate memory from stack. @@ -1162,13 +1146,6 @@ small_stack_init :: proc(s: ^Small_Stack, data: []byte) { s.peak_used = 0 } -@(deprecated="prefer 'small_stack_init'") -init_small_stack :: proc(s: ^Small_Stack, data: []byte) { - s.data = data - s.offset = 0 - s.peak_used = 0 -} - /* Small stack allocator. diff --git a/core/mem/mem.odin b/core/mem/mem.odin index 67ed56c39..b2a7158a1 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -461,10 +461,12 @@ Check if a pointer is aligned. This procedure checks whether a pointer `x` is aligned to a boundary specified by `align`, and returns `true` if the pointer is aligned, and false otherwise. + +The specified alignment must be a power of 2. */ is_aligned :: proc "contextless" (x: rawptr, align: int) -> bool { p := uintptr(x) - return (p & (1< (new_slice: T) { - new_slice, _ = make(T, len(slice), allocator, loc) - runtime.copy(new_slice, slice) - return new_slice -} +} \ No newline at end of file diff --git a/core/mem/virtual/virtual_posix.odin b/core/mem/virtual/virtual_posix.odin index 105849774..c3d6a9095 100644 --- a/core/mem/virtual/virtual_posix.odin +++ b/core/mem/virtual/virtual_posix.odin @@ -6,17 +6,13 @@ import "core:sys/posix" // Define non-posix needed flags: when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { - MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */ - - MADV_FREE :: 5 /* pages unneeded, discard contents */ + MADV_FREE :: 5 /* pages unneeded, discard contents */ } else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { - MAP_ANONYMOUS :: 0x1000 - - MADV_FREE :: 6 + MADV_FREE :: 6 } _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { - flags := posix.Map_Flags{ .PRIVATE } + transmute(posix.Map_Flags)i32(MAP_ANONYMOUS) + flags := posix.Map_Flags{ .ANONYMOUS, .PRIVATE } result := posix.mmap(nil, size, {}, flags) if result == posix.MAP_FAILED { return nil, .Out_Of_Memory diff --git a/core/odin/parser/file_tags.odin b/core/odin/parser/file_tags.odin index 84b172148..c5c6637c3 100644 --- a/core/odin/parser/file_tags.odin +++ b/core/odin/parser/file_tags.odin @@ -17,6 +17,9 @@ Build_Kind :: struct { arch: runtime.Odin_Arch_Types, } +// empty build kind acts as a marker for separating multiple lines with build tags +BUILD_KIND_NEWLINE_MARKER :: Build_Kind{} + File_Tags :: struct { build_project_name: [][]string, build: []Build_Kind, @@ -147,6 +150,11 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags append(build_project_names, build_project_name_strings[index_start:]) } case "build": + + if len(build_kinds) > 0 { + append(build_kinds, BUILD_KIND_NEWLINE_MARKER) + } + kinds_loop: for { os_positive: runtime.Odin_OS_Types os_negative: runtime.Odin_OS_Types @@ -248,10 +256,20 @@ match_build_tags :: proc(file_tags: File_Tags, target: Build_Target) -> bool { project_name_correct ||= group_correct } - os_and_arch_correct := len(file_tags.build) == 0 + os_and_arch_correct := true - for kind in file_tags.build { - os_and_arch_correct ||= target.os in kind.os && target.arch in kind.arch + if len(file_tags.build) > 0 { + os_and_arch_correct_line := false + + for kind in file_tags.build { + if kind == BUILD_KIND_NEWLINE_MARKER { + os_and_arch_correct &&= os_and_arch_correct_line + os_and_arch_correct_line = false + } else { + os_and_arch_correct_line ||= target.os in kind.os && target.arch in kind.arch + } + } + os_and_arch_correct &&= os_and_arch_correct_line } return !file_tags.ignore && project_name_correct && os_and_arch_correct diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 32246be3a..5a7440339 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -3696,6 +3696,8 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou } } + end := p.prev_tok + if p.expr_level >= 0 { end: ^ast.Expr if !is_mutable && len(values) > 0 { @@ -3715,7 +3717,7 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou } } - decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(p.prev_tok)) + decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(end)) decl.docs = docs decl.names = names decl.type = type diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index c3a30581c..d4da82c56 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -331,7 +331,7 @@ scan_escape :: proc(t: ^Tokenizer) -> bool { n -= 1 } - if x > max || 0xd800 <= x && x <= 0xe000 { + if x > max || 0xd800 <= x && x <= 0xdfff { error(t, offset, "escape sequence is an invalid Unicode code point") return false } diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin deleted file mode 100644 index 375da6aff..000000000 --- a/core/os/file_windows.odin +++ /dev/null @@ -1,584 +0,0 @@ -package os - -import win32 "core:sys/windows" -import "base:intrinsics" -import "base:runtime" -import "core:unicode/utf16" - -@(require_results) -is_path_separator :: proc(c: byte) -> bool { - return c == '/' || c == '\\' -} - -@(require_results) -open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { - if len(path) == 0 { - return INVALID_HANDLE, General_Error.Not_Exist - } - - access: u32 - switch mode & (O_RDONLY|O_WRONLY|O_RDWR) { - case O_RDONLY: access = win32.FILE_GENERIC_READ - case O_WRONLY: access = win32.FILE_GENERIC_WRITE - case O_RDWR: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE - } - - if mode&O_CREATE != 0 { - access |= win32.FILE_GENERIC_WRITE - } - if mode&O_APPEND != 0 { - access &~= win32.FILE_GENERIC_WRITE - access |= win32.FILE_APPEND_DATA - } - - share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE - sa: ^win32.SECURITY_ATTRIBUTES = nil - sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true} - if mode&O_CLOEXEC == 0 { - sa = &sa_inherit - } - - create_mode: u32 - switch { - case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL): - create_mode = win32.CREATE_NEW - case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC): - create_mode = win32.CREATE_ALWAYS - case mode&O_CREATE == O_CREATE: - create_mode = win32.OPEN_ALWAYS - case mode&O_TRUNC == O_TRUNC: - create_mode = win32.TRUNCATE_EXISTING - case: - create_mode = win32.OPEN_EXISTING - } - wide_path := win32.utf8_to_wstring(path) - handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil)) - if handle != INVALID_HANDLE { - return handle, nil - } - - return INVALID_HANDLE, get_last_error() -} - -close :: proc(fd: Handle) -> Error { - if !win32.CloseHandle(win32.HANDLE(fd)) { - return get_last_error() - } - return nil -} - -flush :: proc(fd: Handle) -> (err: Error) { - if !win32.FlushFileBuffers(win32.HANDLE(fd)) { - err = get_last_error() - } - return -} - - - -write :: proc(fd: Handle, data: []byte) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - single_write_length: win32.DWORD - total_write: i64 - length := i64(len(data)) - - for total_write < length { - remaining := length - total_write - to_write := win32.DWORD(min(i32(remaining), MAX_RW)) - - e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil) - if single_write_length <= 0 || !e { - return int(total_write), get_last_error() - } - total_write += i64(single_write_length) - } - return int(total_write), nil -} - -@(private="file", require_results) -read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { - if len(b) == 0 { - return 0, nil - } - - BUF_SIZE :: 386 - buf16: [BUF_SIZE]u16 - buf8: [4*BUF_SIZE]u8 - - for n < len(b) && err == nil { - min_read := max(len(b)/4, 1 if len(b) > 0 else 0) - max_read := u32(min(BUF_SIZE, min_read)) - if max_read == 0 { - break - } - - single_read_length: u32 - ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) - if !ok { - err = get_last_error() - } - - buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) - src := buf8[:buf8_len] - - ctrl_z := false - for i := 0; i < len(src) && n < len(b); i += 1 { - x := src[i] - if x == 0x1a { // ctrl-z - ctrl_z = true - break - } - b[n] = x - n += 1 - } - if ctrl_z || single_read_length < max_read { - break - } - - // NOTE(bill): if the last two values were a newline, then it is expected that - // this is the end of the input - if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" { - break - } - - } - - return -} - -read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - handle := win32.HANDLE(fd) - - m: u32 - is_console := win32.GetConsoleMode(handle, &m) - length := len(data) - - // NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that. - to_read := min(i64(length), MAX_RW) - - if is_console { - total_read, err = read_console(handle, data[total_read:][:to_read]) - if err != nil { - return total_read, err - } - } else { - // NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB) - bytes_read: win32.DWORD - if e := win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &bytes_read, nil); e { - // Successful read can mean two things, including EOF, see: - // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file - if bytes_read == 0 { - return 0, .EOF - } else { - return int(bytes_read), nil - } - } else { - return 0, get_last_error() - } - } - return total_read, nil -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - w: u32 - switch whence { - case 0: w = win32.FILE_BEGIN - case 1: w = win32.FILE_CURRENT - case 2: w = win32.FILE_END - case: - return 0, .Invalid_Whence - } - hi := i32(offset>>32) - lo := i32(offset) - ft := win32.GetFileType(win32.HANDLE(fd)) - if ft == win32.FILE_TYPE_PIPE { - return 0, .File_Is_Pipe - } - - dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w) - if dw_ptr == win32.INVALID_SET_FILE_POINTER { - err := get_last_error() - return 0, err - } - return i64(hi)<<32 + i64(dw_ptr), nil -} - -@(require_results) -file_size :: proc(fd: Handle) -> (i64, Error) { - length: win32.LARGE_INTEGER - err: Error - if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { - err = get_last_error() - } - return i64(length), err -} - - -@(private) -MAX_RW :: 1<<30 - -@(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr_off := seek(fd, 0, 1) or_return - defer seek(fd, curr_off, 0) - - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] - } - - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), - } - - // TODO(bill): Determine the correct behaviour for consoles - - h := win32.HANDLE(fd) - done: win32.DWORD - e: Error - if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - e = get_last_error() - done = 0 - } - return int(done), e -} -@(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr_off := seek(fd, 0, 1) or_return - defer seek(fd, curr_off, 0) - - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] - } - - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), - } - - h := win32.HANDLE(fd) - done: win32.DWORD - e: Error - if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - e = get_last_error() - done = 0 - } - return int(done), e -} - -/* -read_at returns n: 0, err: 0 on EOF -*/ -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if offset < 0 { - return 0, .Invalid_Offset - } - - b, offset := data, offset - for len(b) > 0 { - m, e := pread(fd, b, offset) - if e == ERROR_EOF { - err = nil - break - } - if e != nil { - err = e - break - } - n += m - b = b[m:] - offset += i64(m) - } - return -} - -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if offset < 0 { - return 0, .Invalid_Offset - } - - b, offset := data, offset - for len(b) > 0 { - m := pwrite(fd, b, offset) or_return - n += m - b = b[m:] - offset += i64(m) - } - return -} - - - -// NOTE(bill): Uses startup to initialize it -stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE)) -stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE)) -stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE)) - - -@(require_results) -get_std_handle :: proc "contextless" (h: uint) -> Handle { - fd := win32.GetStdHandle(win32.DWORD(h)) - return Handle(fd) -} - - -exists :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - - return attribs != win32.INVALID_FILE_ATTRIBUTES -} - -@(require_results) -is_file :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - - if attribs != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 - } - return false -} - -@(require_results) -is_dir :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - - if attribs != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 - } - return false -} - -// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName -@private cwd_lock := win32.SRWLOCK{} // zero is initialized - -@(require_results) -get_current_directory :: proc(allocator := context.allocator) -> string { - win32.AcquireSRWLockExclusive(&cwd_lock) - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - - sz_utf16 := win32.GetCurrentDirectoryW(0, nil) - dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL. - - 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) or_else "" -} - -set_current_directory :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wstr := win32.utf8_to_wstring(path, context.temp_allocator) - - win32.AcquireSRWLockExclusive(&cwd_lock) - - if !win32.SetCurrentDirectoryW(wstr) { - err = get_last_error() - } - - win32.ReleaseSRWLockExclusive(&cwd_lock) - - return -} -change_directory :: set_current_directory - -make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - // Mode is unused on Windows, but is needed on *nix - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.CreateDirectoryW(wpath, nil) { - err = get_last_error() - } - return -} - - -remove_directory :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.RemoveDirectoryW(wpath) { - err = get_last_error() - } - return -} - - - -@(private, require_results) -is_abs :: proc(path: string) -> bool { - if len(path) > 0 && path[0] == '/' { - return true - } - when ODIN_OS == .Windows { - if len(path) > 2 { - switch path[0] { - case 'A'..='Z', 'a'..='z': - return path[1] == ':' && is_path_separator(path[2]) - } - } - } - return false -} - -@(private, require_results) -fix_long_path :: proc(path: string) -> string { - if len(path) < 248 { - return path - } - - if len(path) >= 2 && path[:2] == `\\` { - return path - } - if !is_abs(path) { - return path - } - - prefix :: `\\?` - - path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) - copy(path_buf, prefix) - n := len(path) - r, w := 0, len(prefix) - for r < n { - switch { - case is_path_separator(path[r]): - r += 1 - case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): - r += 1 - case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): - return path - case: - path_buf[w] = '\\' - w += 1 - for ; r < n && !is_path_separator(path[r]); r += 1 { - path_buf[w] = path[r] - w += 1 - } - } - } - - if w == len(`\\?\c:`) { - path_buf[w] = '\\' - w += 1 - } - return string(path_buf[:w]) -} - - -link :: proc(old_name, new_name: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - n := win32.utf8_to_wstring(fix_long_path(new_name)) - o := win32.utf8_to_wstring(fix_long_path(old_name)) - return Platform_Error(win32.CreateHardLinkW(n, o, nil)) -} - -unlink :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.DeleteFileW(wpath) { - err = get_last_error() - } - return -} - - - -rename :: proc(old_path, new_path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - from := win32.utf8_to_wstring(old_path, context.temp_allocator) - to := win32.utf8_to_wstring(new_path, context.temp_allocator) - - if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { - err = get_last_error() - } - return -} - - -ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { - curr_off := seek(fd, 0, 1) or_return - defer seek(fd, curr_off, 0) - _= seek(fd, length, 0) or_return - ok := win32.SetEndOfFile(win32.HANDLE(fd)) - if !ok { - return get_last_error() - } - return nil -} - -truncate :: proc(path: string, length: i64) -> (err: Error) { - fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return - defer close(fd) - return ftruncate(fd, length) -} - - -remove :: proc(name: string) -> Error { - p := win32.utf8_to_wstring(fix_long_path(name)) - err, err1: win32.DWORD - if !win32.DeleteFileW(p) { - err = win32.GetLastError() - } - if err == 0 { - return nil - } - if !win32.RemoveDirectoryW(p) { - err1 = win32.GetLastError() - } - if err1 == 0 { - return nil - } - - if err != err1 { - a := win32.GetFileAttributesW(p) - if a == ~u32(0) { - err = win32.GetLastError() - } else { - if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - err = err1 - } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { - if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { - err = 0 - if !win32.DeleteFileW(p) { - err = win32.GetLastError() - } - } - } - } - } - - return Platform_Error(err) -} - - -@(require_results) -pipe :: proc() -> (r, w: Handle, err: Error) { - sa: win32.SECURITY_ATTRIBUTES - sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) - sa.bInheritHandle = true - if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) { - err = get_last_error() - } - return -} - diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin index 6a097e192..f26b4fc79 100644 --- a/core/os/os2/dir_linux.odin +++ b/core/os/os2/dir_linux.odin @@ -13,7 +13,7 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info @(require_results) _read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { - return {}, nil + return {}, .Unsupported } _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 09990aeec..f71e7e763 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -16,28 +16,25 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al } path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return + handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0) + defer win32.CloseHandle(handle) fi.fullpath = path fi.name = basename(path) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, d.dwReserved0) + fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, handle, d.dwReserved0) 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 } @@ -137,5 +134,6 @@ _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { return } file_info_delete(it.impl.prev_fi, file_allocator()) + delete(it.impl.path, file_allocator()) win32.FindClose(it.impl.find_handle) } diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index e9ce13447..20f179f77 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -272,28 +272,12 @@ _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 .NONE: - defer linux.close(fd) - if is_dir_fd(fd) { - return _get_platform_error(linux.rmdir(name_cstr)) - } - case: - return _get_platform_error(errno) + if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS); errno == .NONE { + linux.close(fd) + return _get_platform_error(linux.rmdir(name_cstr)) } return _get_platform_error(linux.unlink(name_cstr)) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 511935d74..b91a1bc3b 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -50,9 +50,9 @@ init_std_files :: proc() { } @(fini) fini_std_files :: proc() { - close(stdin) - close(stdout) - close(stderr) + _destroy((^File_Impl)(stdin.impl)) + _destroy((^File_Impl)(stdout.impl)) + _destroy((^File_Impl)(stderr.impl)) } diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index ede5eb2ac..8819dfac7 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -415,7 +415,7 @@ _region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_l back_idx := -1 idx: u16 infinite: for { - for i := 0; i < len(region_iter.hdr.free_list); i += 1 { + for i := 0; i < int(region_iter.hdr.free_list_len); i += 1 { idx = region_iter.hdr.free_list[i] if _get_block_count(region_iter.memory[idx]) >= new_block_count { break infinite diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 5b5a6e844..c90e3add2 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -290,12 +290,21 @@ process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Erro return _process_open(pid, flags) } + +/* +OS-specific process attributes. +*/ +Process_Attributes :: struct { + sys_attr: _Sys_Process_Attributes, +} + /* The description of how a process should be created. */ Process_Desc :: struct { // OS-specific attributes. - sys_attr: _Sys_Process_Attributes, + sys_attr: Process_Attributes, + // The working directory of the process. If the string has length 0, the // working directory is assumed to be the current working directory of the // current process. diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index 7eb4dfa44..936fbfc40 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -384,14 +384,6 @@ _Sys_Process_Attributes :: struct {} @(private="package") _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { - has_executable_permissions :: proc(fd: linux.Fd) -> bool { - backing: [48]u8 - b := strings.builder_from_bytes(backing[:]) - strings.write_string(&b, "/proc/self/fd/") - strings.write_int(&b, int(fd)) - return linux.access(strings.to_cstring(&b), linux.X_OK) == .NONE - } - TEMP_ALLOCATOR_GUARD() if len(desc.command) == 0 { @@ -411,7 +403,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { } // search PATH if just a plain name is provided - exe_fd: linux.Fd + exe_path: cstring executable_name := desc.command[0] if strings.index_byte(executable_name, '/') < 0 { path_env := get_env("PATH", temp_allocator()) @@ -426,16 +418,11 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { strings.write_byte(&exe_builder, '/') strings.write_string(&exe_builder, executable_name) - exe_path := strings.to_cstring(&exe_builder) - if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE { - continue + exe_path = strings.to_cstring(&exe_builder) + if linux.access(exe_path, linux.X_OK) == .NONE { + found = true + break } - if !has_executable_permissions(exe_fd) { - linux.close(exe_fd) - continue - } - found = true - break } if !found { // check in cwd to match windows behavior @@ -443,29 +430,18 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { strings.write_string(&exe_builder, "./") strings.write_string(&exe_builder, executable_name) - exe_path := strings.to_cstring(&exe_builder) - if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE { + exe_path = strings.to_cstring(&exe_builder) + if linux.access(exe_path, linux.X_OK) != .NONE { return process, .Not_Exist } - if !has_executable_permissions(exe_fd) { - linux.close(exe_fd) - return process, .Permission_Denied - } } } else { - exe_path := temp_cstring(executable_name) or_return - if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE { - return process, _get_platform_error(errno) - } - if !has_executable_permissions(exe_fd) { - linux.close(exe_fd) - return process, .Permission_Denied + exe_path = temp_cstring(executable_name) or_return + if linux.access(exe_path, linux.X_OK) != .NONE { + return process, .Not_Exist } } - // At this point, we have an executable. - defer linux.close(exe_fd) - // args and environment need to be a list of cstrings // that are terminated by a nil pointer. cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return @@ -492,7 +468,6 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { } defer linux.close(child_pipe_fds[READ]) - // TODO: This is the traditional textbook implementation with fork. // A more efficient implementation with vfork: // @@ -573,7 +548,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) } - errno = linux.execveat(exe_fd, "", &cargs[0], env, {.AT_EMPTY_PATH}) + errno = linux.execveat(dir_fd, exe_path, &cargs[0], env) assert(errno != nil) write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) } diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 201fa28e7..1984cbfdf 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -442,7 +442,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) } if desc.stdin != nil { - stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) + stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd) } working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 8ed2a6fed..31f5d9e88 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -72,7 +72,11 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { // Not a symlink - return _file_info_from_win32_file_attribute_data(&fa, name, allocator) + fi = _file_info_from_win32_file_attribute_data(&fa, name, allocator) or_return + if fi.type == .Undetermined { + fi.type = _file_type_from_create_file(wname, create_file_attributes) + } + return } err := 0 if ok else win32.GetLastError() @@ -86,7 +90,11 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt } win32.FindClose(sh) - return _file_info_from_win32_find_data(&fd, name, allocator) + fi = _file_info_from_win32_find_data(&fd, name, allocator) or_return + if fi.type == .Undetermined { + fi.type = _file_type_from_create_file(wname, create_file_attributes) + } + return } h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) @@ -194,28 +202,36 @@ file_type :: proc(h: win32.HANDLE) -> File_Type { return .Undetermined } +_file_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes: u32) -> File_Type { + h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) + if h == win32.INVALID_HANDLE_VALUE { + return .Undetermined + } + defer win32.CloseHandle(h) + return file_type(h) +} + _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 { type = .Symlink - } else { - if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - type = .Directory - mode |= 0o111 - } - if h != nil { - type = file_type(h) - } + } else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + type = .Directory + mode |= 0o111 + } else if h != nil { + type = file_type(h) } return } @@ -267,7 +283,7 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA fi.name = basename(path) 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) + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0) fi.type = type fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index c7e1750d6..d4435ec63 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -679,7 +679,7 @@ get_last_error_string :: proc() -> string { @(require_results) -open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (handle: Handle, err: Error) { +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (handle: Handle, err: Error) { isDir := is_dir_path(path) flags := flags if isDir { @@ -1026,7 +1026,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { } @(require_results) -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -1041,9 +1041,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { } defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(path_ptr)) - - return path, nil + return strings.clone(string(path_ptr), allocator) } access :: proc(path: string, mask: int) -> bool { diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index f617cf973..837e79f4d 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -789,7 +789,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { } @(require_results) -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -804,10 +804,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { } defer _unix_free(rawptr(path_ptr)) - - path = strings.clone(string(path_ptr)) - - return path, nil + return strings.clone(string(path_ptr), allocator) } access :: proc(path: string, mask: int) -> (bool, Error) { diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 0d2c334be..4ad370724 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -431,7 +431,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { } @(require_results) -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -447,9 +447,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { defer _unix_free(path_ptr) path_cstr := cstring(path_ptr) - path = strings.clone(string(path_cstr)) - - return path, nil + return strings.clone(string(path_cstr), allocator) } access :: proc(path: string, mask: int) -> (bool, Error) { diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index e9039ba20..e023ce7cb 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -490,7 +490,7 @@ foreign libc { @(link_name="free") _unix_free :: proc(ptr: rawptr) --- @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - @(link_name="execvp") _unix_execvp :: proc(path: cstring, argv: [^]cstring) -> int --- + @(link_name="execvp") _unix_execvp :: proc(path: cstring, argv: [^]cstring) -> c.int --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- @(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int --- @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- @@ -917,7 +917,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { } @(require_results) -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -932,9 +932,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { } defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(path_ptr)) - - return path, nil + return strings.clone(string(path_ptr), allocator) } access :: proc(path: string, mask: int) -> (bool, Error) { diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index 493527803..e3ba760a4 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -844,7 +844,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { } @(require_results) -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -859,9 +859,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { } defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(path_ptr)) - - return path, nil + return strings.clone(string(path_ptr), allocator) } access :: proc(path: string, mask: int) -> (bool, Error) { diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index 62872d9dc..3c377968c 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -758,7 +758,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { } @(require_results) -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -773,9 +773,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { } defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(path_ptr)) - - return path, nil + return strings.clone(string(path_ptr), allocator) } access :: proc(path: string, mask: int) -> (bool, Error) { diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index 552508f3b..0c532bf14 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -4,15 +4,13 @@ package os import win32 "core:sys/windows" import "base:runtime" import "base:intrinsics" +import "core:unicode/utf16" Handle :: distinct uintptr File_Time :: distinct u64 - INVALID_HANDLE :: ~Handle(0) - - O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 O_RDWR :: 0x00002 @@ -278,3 +276,580 @@ is_windows_11 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF) } + +@(require_results) +is_path_separator :: proc(c: byte) -> bool { + return c == '/' || c == '\\' +} + +@(require_results) +open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { + if len(path) == 0 { + return INVALID_HANDLE, General_Error.Not_Exist + } + + access: u32 + switch mode & (O_RDONLY|O_WRONLY|O_RDWR) { + case O_RDONLY: access = win32.FILE_GENERIC_READ + case O_WRONLY: access = win32.FILE_GENERIC_WRITE + case O_RDWR: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE + } + + if mode&O_CREATE != 0 { + access |= win32.FILE_GENERIC_WRITE + } + if mode&O_APPEND != 0 { + access &~= win32.FILE_GENERIC_WRITE + access |= win32.FILE_APPEND_DATA + } + + share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE + sa: ^win32.SECURITY_ATTRIBUTES = nil + sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true} + if mode&O_CLOEXEC == 0 { + sa = &sa_inherit + } + + create_mode: u32 + switch { + case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL): + create_mode = win32.CREATE_NEW + case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC): + create_mode = win32.CREATE_ALWAYS + case mode&O_CREATE == O_CREATE: + create_mode = win32.OPEN_ALWAYS + case mode&O_TRUNC == O_TRUNC: + create_mode = win32.TRUNCATE_EXISTING + case: + create_mode = win32.OPEN_EXISTING + } + wide_path := win32.utf8_to_wstring(path) + handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil)) + if handle != INVALID_HANDLE { + return handle, nil + } + + return INVALID_HANDLE, get_last_error() +} + +close :: proc(fd: Handle) -> Error { + if !win32.CloseHandle(win32.HANDLE(fd)) { + return get_last_error() + } + return nil +} + +flush :: proc(fd: Handle) -> (err: Error) { + if !win32.FlushFileBuffers(win32.HANDLE(fd)) { + err = get_last_error() + } + return +} + + + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + single_write_length: win32.DWORD + total_write: i64 + length := i64(len(data)) + + for total_write < length { + remaining := length - total_write + to_write := win32.DWORD(min(i32(remaining), MAX_RW)) + + e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil) + if single_write_length <= 0 || !e { + return int(total_write), get_last_error() + } + total_write += i64(single_write_length) + } + return int(total_write), nil +} + +@(private="file", require_results) +read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { + if len(b) == 0 { + return 0, nil + } + + BUF_SIZE :: 386 + buf16: [BUF_SIZE]u16 + buf8: [4*BUF_SIZE]u8 + + for n < len(b) && err == nil { + min_read := max(len(b)/4, 1 if len(b) > 0 else 0) + max_read := u32(min(BUF_SIZE, min_read)) + if max_read == 0 { + break + } + + single_read_length: u32 + ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) + if !ok { + err = get_last_error() + } + + buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) + src := buf8[:buf8_len] + + ctrl_z := false + for i := 0; i < len(src) && n < len(b); i += 1 { + x := src[i] + if x == 0x1a { // ctrl-z + ctrl_z = true + break + } + b[n] = x + n += 1 + } + if ctrl_z || single_read_length < max_read { + break + } + + // NOTE(bill): if the last two values were a newline, then it is expected that + // this is the end of the input + if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" { + break + } + + } + + return +} + +read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + handle := win32.HANDLE(fd) + + m: u32 + is_console := win32.GetConsoleMode(handle, &m) + length := len(data) + + // NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that. + to_read := min(i64(length), MAX_RW) + + if is_console { + total_read, err = read_console(handle, data[total_read:][:to_read]) + if err != nil { + return total_read, err + } + } else { + // NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB) + bytes_read: win32.DWORD + if e := win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &bytes_read, nil); e { + // Successful read can mean two things, including EOF, see: + // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file + if bytes_read == 0 { + return 0, .EOF + } else { + return int(bytes_read), nil + } + } else { + return 0, get_last_error() + } + } + return total_read, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + w: u32 + switch whence { + case 0: w = win32.FILE_BEGIN + case 1: w = win32.FILE_CURRENT + case 2: w = win32.FILE_END + case: + return 0, .Invalid_Whence + } + hi := i32(offset>>32) + lo := i32(offset) + ft := win32.GetFileType(win32.HANDLE(fd)) + if ft == win32.FILE_TYPE_PIPE { + return 0, .File_Is_Pipe + } + + dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w) + if dw_ptr == win32.INVALID_SET_FILE_POINTER { + err := get_last_error() + return 0, err + } + return i64(hi)<<32 + i64(dw_ptr), nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { + length: win32.LARGE_INTEGER + err: Error + if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { + err = get_last_error() + } + return i64(length), err +} + + +@(private) +MAX_RW :: 1<<30 + +@(private) +pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + } + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + // TODO(bill): Determine the correct behaviour for consoles + + h := win32.HANDLE(fd) + done: win32.DWORD + e: Error + if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + e = get_last_error() + done = 0 + } + return int(done), e +} +@(private) +pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + } + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + h := win32.HANDLE(fd) + done: win32.DWORD + e: Error + if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + e = get_last_error() + done = 0 + } + return int(done), e +} + +/* +read_at returns n: 0, err: 0 on EOF +*/ +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + b, offset := data, offset + for len(b) > 0 { + m, e := pread(fd, b, offset) + if e == ERROR_EOF { + err = nil + break + } + if e != nil { + err = e + break + } + n += m + b = b[m:] + offset += i64(m) + } + return +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + b, offset := data, offset + for len(b) > 0 { + m := pwrite(fd, b, offset) or_return + n += m + b = b[m:] + offset += i64(m) + } + return +} + + + +// NOTE(bill): Uses startup to initialize it +stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE)) +stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE)) +stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE)) + + +@(require_results) +get_std_handle :: proc "contextless" (h: uint) -> Handle { + fd := win32.GetStdHandle(win32.DWORD(h)) + return Handle(fd) +} + + +exists :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + + return attribs != win32.INVALID_FILE_ATTRIBUTES +} + +@(require_results) +is_file :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + + if attribs != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 + } + return false +} + +@(require_results) +is_dir :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + + if attribs != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 + } + return false +} + +// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName +@private cwd_lock := win32.SRWLOCK{} // zero is initialized + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + win32.AcquireSRWLockExclusive(&cwd_lock) + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + sz_utf16 := win32.GetCurrentDirectoryW(0, nil) + dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL. + + 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) or_else "" +} + +set_current_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wstr := win32.utf8_to_wstring(path, context.temp_allocator) + + win32.AcquireSRWLockExclusive(&cwd_lock) + + if !win32.SetCurrentDirectoryW(wstr) { + err = get_last_error() + } + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return +} +change_directory :: set_current_directory + +make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + // Mode is unused on Windows, but is needed on *nix + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + + if !win32.CreateDirectoryW(wpath, nil) { + err = get_last_error() + } + return +} + + +remove_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + + if !win32.RemoveDirectoryW(wpath) { + err = get_last_error() + } + return +} + + + +@(private, require_results) +is_abs :: proc(path: string) -> bool { + if len(path) > 0 && path[0] == '/' { + return true + } + when ODIN_OS == .Windows { + if len(path) > 2 { + switch path[0] { + case 'A'..='Z', 'a'..='z': + return path[1] == ':' && is_path_separator(path[2]) + } + } + } + return false +} + +@(private, require_results) +fix_long_path :: proc(path: string) -> string { + if len(path) < 248 { + return path + } + + if len(path) >= 2 && path[:2] == `\\` { + return path + } + if !is_abs(path) { + return path + } + + prefix :: `\\?` + + path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) + copy(path_buf, prefix) + n := len(path) + r, w := 0, len(prefix) + for r < n { + switch { + case is_path_separator(path[r]): + r += 1 + case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): + r += 1 + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): + return path + case: + path_buf[w] = '\\' + w += 1 + for ; r < n && !is_path_separator(path[r]); r += 1 { + path_buf[w] = path[r] + w += 1 + } + } + } + + if w == len(`\\?\c:`) { + path_buf[w] = '\\' + w += 1 + } + return string(path_buf[:w]) +} + + +link :: proc(old_name, new_name: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + n := win32.utf8_to_wstring(fix_long_path(new_name)) + o := win32.utf8_to_wstring(fix_long_path(old_name)) + return Platform_Error(win32.CreateHardLinkW(n, o, nil)) +} + +unlink :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + + if !win32.DeleteFileW(wpath) { + err = get_last_error() + } + return +} + + + +rename :: proc(old_path, new_path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + from := win32.utf8_to_wstring(old_path, context.temp_allocator) + to := win32.utf8_to_wstring(new_path, context.temp_allocator) + + if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { + err = get_last_error() + } + return +} + + +ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + _= seek(fd, length, 0) or_return + ok := win32.SetEndOfFile(win32.HANDLE(fd)) + if !ok { + return get_last_error() + } + return nil +} + +truncate :: proc(path: string, length: i64) -> (err: Error) { + fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return + defer close(fd) + return ftruncate(fd, length) +} + + +remove :: proc(name: string) -> Error { + p := win32.utf8_to_wstring(fix_long_path(name)) + err, err1: win32.DWORD + if !win32.DeleteFileW(p) { + err = win32.GetLastError() + } + if err == 0 { + return nil + } + if !win32.RemoveDirectoryW(p) { + err1 = win32.GetLastError() + } + if err1 == 0 { + return nil + } + + if err != err1 { + a := win32.GetFileAttributesW(p) + if a == ~u32(0) { + err = win32.GetLastError() + } else { + if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + err = err1 + } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { + if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { + err = 0 + if !win32.DeleteFileW(p) { + err = win32.GetLastError() + } + } + } + } + } + + return Platform_Error(err) +} + + +@(require_results) +pipe :: proc() -> (r, w: Handle, err: Error) { + sa: win32.SECURITY_ATTRIBUTES + sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) + sa.bInheritHandle = true + if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) { + err = get_last_error() + } + return +} \ No newline at end of file diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 7eb72b9a7..003f8046d 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -246,6 +246,13 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str if err != .None { return } + defer { + for s in m { + delete(s) + } + delete(m) + } + dmatches := make([dynamic]string, 0, 0) for d in m { dmatches, err = _glob(d, file, &dmatches) diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index a18dc739e..35b98a7ae 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,14 +1,10 @@ #+build linux, darwin, freebsd, openbsd, netbsd package filepath -when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" -} else { - foreign import libc "system:c" -} - import "base:runtime" + import "core:strings" +import "core:sys/posix" SEPARATOR :: '/' SEPARATOR_STRING :: `/` @@ -28,11 +24,11 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { rel = "." } rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) - path_ptr := realpath(rel_cstr, nil) + path_ptr := posix.realpath(rel_cstr, nil) if path_ptr == nil { - return "", __error()^ == 0 + return "", posix.errno() == nil } - defer _unix_free(rawptr(path_ptr)) + defer posix.free(path_ptr) path_str := strings.clone(string(path_ptr), allocator) return path_str, true @@ -48,26 +44,3 @@ join :: proc(elems: []string, allocator := context.allocator) -> (joined: string } return "", nil } - -@(private) -foreign libc { - realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - -} -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { - @(private) - foreign libc { - @(link_name="__error") __error :: proc() -> ^i32 --- - } -} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { - @(private) - foreign libc { - @(link_name="__errno") __error :: proc() -> ^i32 --- - } -} else { - @(private) - foreign libc { - @(link_name="__errno_location") __error :: proc() -> ^i32 --- - } -} diff --git a/core/prof/spall/doc.odin b/core/prof/spall/doc.odin index c81bad05f..b007ad4cb 100644 --- a/core/prof/spall/doc.odin +++ b/core/prof/spall/doc.odin @@ -18,6 +18,8 @@ Example: defer spall.context_destroy(&spall_ctx) buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE) + defer delete(buffer_backing) + spall_buffer = spall.buffer_create(buffer_backing, u32(sync.current_thread_id())) defer spall.buffer_destroy(&spall_ctx, &spall_buffer) diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index c04afb380..7f79acb77 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -31,8 +31,6 @@ Type_Info_Enum :: runtime.Type_Info_Enum Type_Info_Map :: runtime.Type_Info_Map Type_Info_Bit_Set :: runtime.Type_Info_Bit_Set Type_Info_Simd_Vector :: runtime.Type_Info_Simd_Vector -Type_Info_Relative_Pointer :: runtime.Type_Info_Relative_Pointer -Type_Info_Relative_Multi_Pointer :: runtime.Type_Info_Relative_Multi_Pointer Type_Info_Matrix :: runtime.Type_Info_Matrix Type_Info_Soa_Pointer :: runtime.Type_Info_Soa_Pointer Type_Info_Bit_Field :: runtime.Type_Info_Bit_Field @@ -67,8 +65,6 @@ Type_Kind :: enum { Map, Bit_Set, Simd_Vector, - Relative_Pointer, - Relative_Multi_Pointer, Matrix, Soa_Pointer, Bit_Field, @@ -80,35 +76,33 @@ type_kind :: proc(T: typeid) -> Type_Kind { ti := type_info_of(T) if ti != nil { switch _ in ti.variant { - case Type_Info_Named: return .Named - case Type_Info_Integer: return .Integer - case Type_Info_Rune: return .Rune - case Type_Info_Float: return .Float - case Type_Info_Complex: return .Complex - case Type_Info_Quaternion: return .Quaternion - case Type_Info_String: return .String - case Type_Info_Boolean: return .Boolean - case Type_Info_Any: return .Any - case Type_Info_Type_Id: return .Type_Id - case Type_Info_Pointer: return .Pointer - case Type_Info_Multi_Pointer: return .Multi_Pointer - case Type_Info_Procedure: return .Procedure - case Type_Info_Array: return .Array - case Type_Info_Enumerated_Array: return .Enumerated_Array - case Type_Info_Dynamic_Array: return .Dynamic_Array - case Type_Info_Slice: return .Slice - case Type_Info_Parameters: return .Tuple - case Type_Info_Struct: return .Struct - case Type_Info_Union: return .Union - case Type_Info_Enum: return .Enum - case Type_Info_Map: return .Map - case Type_Info_Bit_Set: return .Bit_Set - case Type_Info_Simd_Vector: return .Simd_Vector - case Type_Info_Relative_Pointer: return .Relative_Pointer - case Type_Info_Relative_Multi_Pointer: return .Relative_Multi_Pointer - case Type_Info_Matrix: return .Matrix - case Type_Info_Soa_Pointer: return .Soa_Pointer - case Type_Info_Bit_Field: return .Bit_Field + case Type_Info_Named: return .Named + case Type_Info_Integer: return .Integer + case Type_Info_Rune: return .Rune + case Type_Info_Float: return .Float + case Type_Info_Complex: return .Complex + case Type_Info_Quaternion: return .Quaternion + case Type_Info_String: return .String + case Type_Info_Boolean: return .Boolean + case Type_Info_Any: return .Any + case Type_Info_Type_Id: return .Type_Id + case Type_Info_Pointer: return .Pointer + case Type_Info_Multi_Pointer: return .Multi_Pointer + case Type_Info_Procedure: return .Procedure + case Type_Info_Array: return .Array + case Type_Info_Enumerated_Array: return .Enumerated_Array + case Type_Info_Dynamic_Array: return .Dynamic_Array + case Type_Info_Slice: return .Slice + case Type_Info_Parameters: return .Tuple + case Type_Info_Struct: return .Struct + case Type_Info_Union: return .Union + case Type_Info_Enum: return .Enum + case Type_Info_Map: return .Map + case Type_Info_Bit_Set: return .Bit_Set + case Type_Info_Simd_Vector: return .Simd_Vector + case Type_Info_Matrix: return .Matrix + case Type_Info_Soa_Pointer: return .Soa_Pointer + case Type_Info_Bit_Field: return .Bit_Field } } @@ -723,6 +717,27 @@ enum_name_from_value_any :: proc(value: any) -> (name: string, ok: bool) { return } +/* +Returns whether the value given has a defined name in the enum type. +*/ +@(require_results) +enum_value_has_name :: proc(value: $T) -> bool where intrinsics.type_is_enum(T) { + when len(T) == cap(T) { + return value >= min(T) && value <= max(T) + } else { + if value < min(T) || value > max(T) { + return false + } + + for valid_value in T { + if valid_value == value { + return true + } + } + + return false + } +} @@ -1467,21 +1482,6 @@ as_string :: proc(a: any) -> (value: string, valid: bool) { return } -@(require_results) -relative_pointer_to_absolute :: proc(a: any) -> rawptr { - if a == nil { return nil } - a := a - ti := runtime.type_info_core(type_info_of(a.id)) - a.id = ti.id - - #partial switch info in ti.variant { - case Type_Info_Relative_Pointer: - return relative_pointer_to_absolute_raw(a.data, info.base_integer.id) - } - return nil -} - - @(require_results) relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) -> rawptr { _handle :: proc(ptr: ^$T) -> rawptr where intrinsics.type_is_integer(T) { @@ -1543,10 +1543,6 @@ as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) { case cstring: value = rawptr(v) case: valid = false } - - case Type_Info_Relative_Pointer: - valid = true - value = relative_pointer_to_absolute_raw(a.data, info.base_integer.id) } return @@ -1656,8 +1652,6 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ Type_Info_Bit_Set, Type_Info_Enum, Type_Info_Simd_Vector, - Type_Info_Relative_Pointer, - Type_Info_Relative_Multi_Pointer, Type_Info_Soa_Pointer, Type_Info_Matrix: return runtime.memory_compare(a.data, b.data, t.size) == 0 diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 4f0674dc8..cb31a27e2 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -158,14 +158,6 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { case Type_Info_Simd_Vector: y := b.variant.(Type_Info_Simd_Vector) or_return return x.count == y.count && x.elem == y.elem - - case Type_Info_Relative_Pointer: - y := b.variant.(Type_Info_Relative_Pointer) or_return - return x.base_integer == y.base_integer && x.pointer == y.pointer - - case Type_Info_Relative_Multi_Pointer: - y := b.variant.(Type_Info_Relative_Multi_Pointer) or_return - return x.base_integer == y.base_integer && x.pointer == y.pointer case Type_Info_Matrix: y := b.variant.(Type_Info_Matrix) or_return @@ -392,18 +384,6 @@ is_simd_vector :: proc(info: ^Type_Info) -> bool { _, ok := type_info_base(info).variant.(Type_Info_Simd_Vector) return ok } -@(require_results) -is_relative_pointer :: proc(info: ^Type_Info) -> bool { - if info == nil { return false } - _, ok := type_info_base(info).variant.(Type_Info_Relative_Pointer) - return ok -} -@(require_results) -is_relative_multi_pointer :: proc(info: ^Type_Info) -> bool { - if info == nil { return false } - _, ok := type_info_base(info).variant.(Type_Info_Relative_Multi_Pointer) - return ok -} @(require_results) @@ -736,18 +716,6 @@ write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_writt io.write_i64(w, i64(info.count), 10, &n) or_return io.write_byte(w, ']', &n) or_return write_type(w, info.elem, &n) or_return - - case Type_Info_Relative_Pointer: - io.write_string(w, "#relative(", &n) or_return - write_type(w, info.base_integer, &n) or_return - io.write_string(w, ") ", &n) or_return - write_type(w, info.pointer, &n) or_return - - case Type_Info_Relative_Multi_Pointer: - io.write_string(w, "#relative(", &n) or_return - write_type(w, info.base_integer, &n) or_return - io.write_string(w, ") ", &n) or_return - write_type(w, info.pointer, &n) or_return case Type_Info_Matrix: if info.layout == .Row_Major { diff --git a/core/slice/slice.odin b/core/slice/slice.odin index cd12a8c70..66166bddb 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -48,22 +48,41 @@ to_type :: proc(buf: []u8, $T: typeid) -> (T, bool) #optional_ok { } /* - Turn a slice of one type, into a slice of another type. +Turn a slice of one type, into a slice of another type. - Only converts the type and length of the slice itself. - The length is rounded down to the nearest whole number of items. +Only converts the type and length of the slice itself. +The length is rounded down to the nearest whole number of items. + +Example: + + import "core:fmt" + import "core:slice" + + i64s_as_i32s :: proc() { + large_items := []i64{1, 2, 3, 4} + small_items := slice.reinterpret([]i32, large_items) + assert(len(small_items) == 8) + fmt.println(large_items, "->", small_items) + } + + bytes_as_i64s :: proc() { + small_items := [12]byte{} + small_items[0] = 1 + small_items[8] = 2 + large_items := slice.reinterpret([]i64, small_items[:]) + assert(len(large_items) == 1) // only enough bytes to make 1 x i64; two would need at least 8 bytes. + fmt.println(small_items, "->", large_items) + } + + reinterpret_example :: proc() { + i64s_as_i32s() + bytes_as_i64s() + } + +Output: + [1, 2, 3, 4] -> [1, 0, 2, 0, 3, 0, 4, 0] + [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0] -> [1] - ``` - large_items := []i64{1, 2, 3, 4} - small_items := slice.reinterpret([]i32, large_items) - assert(len(small_items) == 8) - ``` - ``` - small_items := []byte{1, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0} - large_items := slice.reinterpret([]i64, small_items) - assert(len(large_items) == 1) // only enough bytes to make 1 x i64; two would need at least 8 bytes. - ``` */ @(require_results) reinterpret :: proc "contextless" ($T: typeid/[]$U, s: []$V) -> []U { @@ -471,6 +490,12 @@ is_empty :: proc(a: $T/[]$E) -> bool { return len(a) == 0 } +// Gets the byte size of the backing data +@(require_results) +size :: proc "contextless" (a: $T/[]$E) -> int { + return len(a) * size_of(E) +} + @(require_results) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index b1155c22f..26a737bd1 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -1121,6 +1121,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { break trunc_block } f := f64(mantissa) + f_abs := f if neg { f = -f } @@ -1132,7 +1133,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { f *= pow10[exp-22] exp = 22 } - if f > 1e15 || f < 1e-15 { + if f_abs > 1e15 || f_abs < 1e-15 { break trunc_block } return f * pow10[exp], nr, true diff --git a/core/strings/strings.odin b/core/strings/strings.odin index af93ff33c..c014d2b2b 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1872,7 +1872,8 @@ index_multi :: proc(s: string, substrs: []string) -> (idx: int, width: int) { lowest_index := len(s) found := false for substr in substrs { - if i := index(s, substr); i >= 0 { + haystack := s[:min(len(s), lowest_index + len(substr))] + if i := index(haystack, substr); i >= 0 { if i < lowest_index { lowest_index = i width = len(substr) diff --git a/core/sync/futex_wasm.odin b/core/sync/futex_wasm.odin index 0f9659a02..16e69ca74 100644 --- a/core/sync/futex_wasm.odin +++ b/core/sync/futex_wasm.odin @@ -12,8 +12,8 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { when !intrinsics.has_target_feature("atomics") { panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { - s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1) - return s != 0 + _ = intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1) + return true } } @@ -22,7 +22,7 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration)) - return s != 0 + return s != 2 } } @@ -30,12 +30,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) { when !intrinsics.has_target_feature("atomics") { panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { - loop: for { - s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1) - if s >= 1 { - return - } - } + _ = intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1) } } @@ -43,12 +38,7 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { when !intrinsics.has_target_feature("atomics") { panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { - loop: for { - s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0)) - if s >= 0 { - return - } - } + _ = intrinsics.wasm_memory_atomic_notify32((^u32)(f), max(u32)) } } diff --git a/core/sync/primitives_atomic.odin b/core/sync/primitives_atomic.odin index 3c4324eb7..a8a84b2bc 100644 --- a/core/sync/primitives_atomic.odin +++ b/core/sync/primitives_atomic.odin @@ -67,7 +67,7 @@ atomic_mutex_unlock :: proc "contextless" (m: ^Atomic_Mutex) { switch atomic_exchange_explicit(&m.state, .Unlocked, .Release) { case .Unlocked: - unreachable() + // Kind of okay - unlocking while already unlocked. case .Locked: // Okay case .Waiting: diff --git a/core/sys/darwin/Foundation/NSApplication.odin b/core/sys/darwin/Foundation/NSApplication.odin index 7191f6d07..254da75ad 100644 --- a/core/sys/darwin/Foundation/NSApplication.odin +++ b/core/sys/darwin/Foundation/NSApplication.odin @@ -108,6 +108,16 @@ Application_setMainMenu :: proc "c" (self: ^Application, menu: ^Menu) { msgSend(nil, self, "setMainMenu:", menu) } +@(objc_type=Application, objc_name="mainWindow") +Application_mainWindow :: proc "c" (self: ^Application) -> ^Window { + return msgSend(^Window, self, "mainWindow") +} + +@(objc_type=Application, objc_name="keyWindow") +Application_keyWindow :: proc "c" (self: ^Application) -> ^Window { + return msgSend(^Window, self, "keyWindow") +} + @(objc_type=Application, objc_name="windows") Application_windows :: proc "c" (self: ^Application) -> ^Array { return msgSend(^Array, self, "windows") diff --git a/core/sys/darwin/Foundation/NSObjectProtocol.odin b/core/sys/darwin/Foundation/NSObjectProtocol.odin new file mode 100644 index 000000000..99d942579 --- /dev/null +++ b/core/sys/darwin/Foundation/NSObjectProtocol.odin @@ -0,0 +1,5 @@ +package objc_Foundation + +@(objc_class="NSObjectProtocol") +ObjectProtocol :: struct {using _: Object} +// TODO: implement NSObjectProtocol diff --git a/core/sys/darwin/Foundation/NSProcessInfo.odin b/core/sys/darwin/Foundation/NSProcessInfo.odin new file mode 100644 index 000000000..e070bf8e2 --- /dev/null +++ b/core/sys/darwin/Foundation/NSProcessInfo.odin @@ -0,0 +1,203 @@ +package objc_Foundation + +import "base:intrinsics" + +import "core:c" + +@(objc_class="NSProcessInfo") +ProcessInfo :: struct {using _: Object} + +// Getting the Process Information Agent + +@(objc_type=ProcessInfo, objc_name="processInfo", objc_is_class_method=true) +ProcessInfo_processInfo :: proc "c" () -> ^ProcessInfo { + return msgSend(^ProcessInfo, ProcessInfo, "processInfo") +} + +// Accessing Process Information + +@(objc_type=ProcessInfo, objc_name="arguments") +ProcessInfo_arguments :: proc "c" (self: ^ProcessInfo) -> ^Array { + return msgSend(^Array, self, "arguments") +} + +@(objc_type=ProcessInfo, objc_name="environment") +ProcessInfo_environment :: proc "c" (self: ^ProcessInfo) -> ^Dictionary { + return msgSend(^Dictionary, self, "environment") +} + +@(objc_type=ProcessInfo, objc_name="globallyUniqueString") +ProcessInfo_globallyUniqueString :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "globallyUniqueString") +} + +@(objc_type=ProcessInfo, objc_name="isMacCatalystApp") +ProcessInfo_isMacCatalystApp :: proc "c" (self: ^ProcessInfo) -> bool { + return msgSend(bool, self, "isMacCatalystApp") +} + +@(objc_type=ProcessInfo, objc_name="isiOSAppOnMac") +ProcessInfo_isiOSAppOnMac :: proc "c" (self: ^ProcessInfo) -> bool { + return msgSend(bool, self, "isiOSAppOnMac") +} + +@(objc_type=ProcessInfo, objc_name="processIdentifier") +ProcessInfo_processIdentifier :: proc "c" (self: ^ProcessInfo) -> c.int { + return msgSend(c.int, self, "processIdentifier") +} + +@(objc_type=ProcessInfo, objc_name="processName") +ProcessInfo_processName :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "processName") +} + +// Accessing User Information + +@(objc_type=ProcessInfo, objc_name="userName") +ProcessInfo_userName :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "userName") +} + +@(objc_type=ProcessInfo, objc_name="fullUserName") +ProcessInfo_fullUserName :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "fullUserName") +} + +// Sudden Application Termination + +@(objc_type=ProcessInfo, objc_name="disableSuddenTermination") +ProcessInfo_disableSuddenTermination :: proc "c" (self: ^ProcessInfo) { + msgSend(nil, self, "disableSuddenTermination") +} + +@(objc_type=ProcessInfo, objc_name="enableSuddenTermination") +ProcessInfo_enableSuddenTermination :: proc "c" (self: ^ProcessInfo) { + msgSend(nil, self, "enableSuddenTermination") +} + +// Controlling Automatic Termination + +@(objc_type=ProcessInfo, objc_name="disableAutomaticTermination") +ProcessInfo_disableAutomaticTermination :: proc "c" (self: ^ProcessInfo, reason: ^String) { + msgSend(nil, self, "disableAutomaticTermination:", reason) +} + +@(objc_type=ProcessInfo, objc_name="enableAutomaticTermination") +ProcessInfo_enableAutomaticTermination :: proc "c" (self: ^ProcessInfo, reason: ^String) { + msgSend(nil, self, "enableAutomaticTermination:", reason) +} + +@(objc_type=ProcessInfo, objc_name="automaticTerminationSupportEnabled") +ProcessInfo_automaticTerminationSupportEnabled :: proc "c" (self: ^ProcessInfo) -> bool { + return msgSend(bool, self, "automaticTerminationSupportEnabled") +} + +@(objc_type=ProcessInfo, objc_name="setAutomaticTerminationSupportEnabled") +ProcessInfo_setAutomaticTerminationSupportEnabled :: proc "c" (self: ^ProcessInfo, automaticTerminationSupportEnabled: bool) { + msgSend(nil, self, "setAutomaticTerminationSupportEnabled:", automaticTerminationSupportEnabled) +} + +// Getting Host Information + +@(objc_type=ProcessInfo, objc_name="hostName") +ProcessInfo_hostName :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "hostName") +} + +@(objc_type=ProcessInfo, objc_name="operatingSystemVersionString") +ProcessInfo_operatingSystemVersionString :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "operatingSystemVersionString") +} + +@(objc_type=ProcessInfo, objc_name="operatingSystemVersion") +ProcessInfo_operatingSystemVersion :: proc "c" (self: ^ProcessInfo) -> OperatingSystemVersion { + return msgSend(OperatingSystemVersion, self, "operatingSystemVersion") +} + +@(objc_type=ProcessInfo, objc_name="isOperatingSystemAtLeastVersion") +ProcessInfo_isOperatingSystemAtLeastVersion :: proc "c" (self: ^ProcessInfo, version: OperatingSystemVersion) -> bool { + return msgSend(bool, self, "isOperatingSystemAtLeastVersion:", version) +} + +// Getting Computer Information + +@(objc_type=ProcessInfo, objc_name="processorCount") +ProcessInfo_processorCount :: proc "c" (self: ^ProcessInfo) -> UInteger { + return msgSend(UInteger, self, "processorCount") +} + +@(objc_type=ProcessInfo, objc_name="activeProcessorCount") +ProcessInfo_activeProcessorCount :: proc "c" (self: ^ProcessInfo) -> UInteger { + return msgSend(UInteger, self, "activeProcessorCount") +} + +@(objc_type=ProcessInfo, objc_name="physicalMemory") +ProcessInfo_physicalMemory :: proc "c" (self: ^ProcessInfo) -> c.ulonglong { + return msgSend(c.ulonglong, self, "physicalMemory") +} + +@(objc_type=ProcessInfo, objc_name="systemUptime") +ProcessInfo_systemUptime :: proc "c" (self: ^ProcessInfo) -> TimeInterval { + return msgSend(TimeInterval, self, "systemUptime") +} + +// Managing Activities + +@(private) +log2 :: intrinsics.constant_log2 + +ActivityOptionsBits :: enum u64 { + IdleDisplaySleepDisabled = log2(1099511627776), // Require the screen to stay powered on. + IdleSystemSleepDisabled = log2(1048576), // Prevent idle sleep. + SuddenTerminationDisabled = log2(16384), // Prevent sudden termination. + AutomaticTerminationDisabled = log2(32768), // Prevent automatic termination. + AnimationTrackingEnabled = log2(35184372088832), // Track activity with an animation signpost interval. + TrackingEnabled = log2(70368744177664), // Track activity with a signpost interval. + UserInitiated = log2(16777215), // Performing a user-requested action. + UserInitiatedAllowingIdleSystemSleep = log2(15728639), // Performing a user-requested action, but the system can sleep on idle. + Background = log2(255), // Initiated some kind of work, but not as the direct result of a user request. + LatencyCritical = log2(1095216660480), // Requires the highest amount of timer and I/O precision available. + UserInteractive = log2(1095233437695), // Responding to user interaction. +} +ActivityOptions :: bit_set[ActivityOptionsBits; u64] + +@(objc_type=ProcessInfo, objc_name="beginActivityWithOptions") +ProcessInfo_beginActivityWithOptions :: proc "c" (self: ^ProcessInfo, options: ActivityOptions, reason: ^String) -> ^ObjectProtocol { + return msgSend(^ObjectProtocol, self, "beginActivityWithOptions:reason:", options, reason) +} + +@(objc_type=ProcessInfo, objc_name="endActivity") +ProcessInfo_endActivity :: proc "c" (self: ^ProcessInfo, activity: ^ObjectProtocol) { + msgSend(nil, self, "endActivity:", activity) +} + +@(objc_type=ProcessInfo, objc_name="performActivityWithOptions") +ProcessInfo_performActivityWithOptions :: proc "c" (self: ^ProcessInfo, options: ActivityOptions, reason: ^String, block: proc "c" ()) { + msgSend(nil, self, "performActivityWithOptions:reason:usingBlock:", options, reason, block) +} + +@(objc_type=ProcessInfo, objc_name="performExpiringActivityWithReason") +ProcessInfo_performExpiringActivityWithReason :: proc "c" (self: ^ProcessInfo, reason: ^String, block: proc "c" (expired: bool)) { + msgSend(nil, self, "performExpiringActivityWithReason:usingBlock:", reason, block) +} + +// Getting the Thermal State + +ProcessInfoThermalState :: enum c.long { + Nominal, + Fair, + Serious, + Critical, +} + +@(objc_type=ProcessInfo, objc_name="thermalState") +ProcessInfo_thermalState :: proc "c" (self: ^ProcessInfo) -> ProcessInfoThermalState { + return msgSend(ProcessInfoThermalState, self, "thermalState") +} + +// Determining Whether Low Power Mode is Enabled + +@(objc_type=ProcessInfo, objc_name="isLowPowerModeEnabled") +ProcessInfo_isLowPowerModeEnabled :: proc "c" (self: ^ProcessInfo) -> bool { + return msgSend(bool, self, "isLowPowerModeEnabled") +} diff --git a/core/sys/darwin/Foundation/NSTypes.odin b/core/sys/darwin/Foundation/NSTypes.odin index fbd883a8f..822a07ab1 100644 --- a/core/sys/darwin/Foundation/NSTypes.odin +++ b/core/sys/darwin/Foundation/NSTypes.odin @@ -20,7 +20,7 @@ BOOL :: bool // TODO(bill): should this be `distinct`? YES :: true NO :: false -OperatingSystemVersion :: struct #packed { +OperatingSystemVersion :: struct #align(8) { majorVersion: Integer, minorVersion: Integer, patchVersion: Integer, @@ -58,4 +58,4 @@ when size_of(Float) == 8 { } else { _POINT_ENCODING :: "{NSPoint=ff}" _SIZE_ENCODING :: "{NSSize=ff}" -} \ No newline at end of file +} diff --git a/core/sys/info/cpu_intel.odin b/core/sys/info/cpu_intel.odin index d6fa98507..95b53dda0 100644 --- a/core/sys/info/cpu_intel.odin +++ b/core/sys/info/cpu_intel.odin @@ -28,6 +28,23 @@ CPU_Feature :: enum u64 { ssse3, // Supplemental streaming SIMD extension 3 sse41, // Streaming SIMD extension 4 and 4.1 sse42, // Streaming SIMD extension 4 and 4.2 + + avx512bf16, // Vector Neural Network Instructions supporting bfloat16 + avx512bitalg, // Bit Algorithms + avx512bw, // Byte and Word instructions + avx512cd, // Conflict Detection instructions + avx512dq, // Doubleword and Quadword instructions + avx512er, // Exponential and Reciprocal instructions + avx512f, // Foundation + avx512fp16, // Vector 16-bit float instructions + avx512ifma, // Integer Fused Multiply Add + avx512pf, // Prefetch instructions + avx512vbmi, // Vector Byte Manipulation Instructions + avx512vbmi2, // Vector Byte Manipulation Instructions 2 + avx512vl, // Vector Length extensions + avx512vnni, // Vector Neural Network Instructions + avx512vp2intersect, // Vector Pair Intersection to a Pair of Mask Registers + avx512vpopcntdq, // Vector Population Count for Doubleword and Quadword } CPU_Features :: distinct bit_set[CPU_Feature; u64] @@ -82,9 +99,11 @@ init_cpu_features :: proc "c" () { // // See: crbug.com/375968 os_supports_avx := false + os_supports_avx512 := false if .os_xsave in set && is_set(26, ecx1) { eax, _ := xgetbv(0) os_supports_avx = is_set(1, eax) && is_set(2, eax) + os_supports_avx512 = is_set(5, eax) && is_set(6, eax) && is_set(7, eax) } if os_supports_avx { try_set(&set, .avx, 28, ecx1) @@ -94,11 +113,37 @@ init_cpu_features :: proc "c" () { return } - _, ebx7, _, _ := cpuid(7, 0) + _, ebx7, ecx7, edx7 := cpuid(7, 0) try_set(&set, .bmi1, 3, ebx7) if os_supports_avx { try_set(&set, .avx2, 5, ebx7) } + if os_supports_avx512 { + try_set(&set, .avx512f, 16, ebx7) + try_set(&set, .avx512dq, 17, ebx7) + try_set(&set, .avx512ifma, 21, ebx7) + try_set(&set, .avx512pf, 26, ebx7) + try_set(&set, .avx512er, 27, ebx7) + try_set(&set, .avx512cd, 28, ebx7) + try_set(&set, .avx512bw, 30, ebx7) + + // XMM/YMM are also required for 128/256-bit instructions + if os_supports_avx { + try_set(&set, .avx512vl, 31, ebx7) + } + + try_set(&set, .avx512vbmi, 1, ecx7) + try_set(&set, .avx512vbmi2, 6, ecx7) + try_set(&set, .avx512vnni, 11, ecx7) + try_set(&set, .avx512bitalg, 12, ecx7) + try_set(&set, .avx512vpopcntdq, 14, ecx7) + + try_set(&set, .avx512vp2intersect, 8, edx7) + try_set(&set, .avx512fp16, 23, edx7) + + eax7_1, _, _, _ := cpuid(7, 1) + try_set(&set, .avx512bf16, 5, eax7_1) + } try_set(&set, .bmi2, 8, ebx7) try_set(&set, .erms, 9, ebx7) try_set(&set, .rdseed, 18, ebx7) diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index b5cd62d81..2fd34b864 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -4,7 +4,7 @@ Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation. - Laytan: ARM and RISC-V CPU feature detection. + Laytan: ARM and RISC-V CPU feature detection, iOS/macOS platform overhaul. */ /* diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index 56cd883d3..7dc49bcd1 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -1,581 +1,101 @@ package sysinfo -import sys "core:sys/unix" -import "core:strconv" -import "core:strings" -import "base:runtime" +import "core:strconv" +import "core:strings" +import "core:sys/unix" +import NS "core:sys/darwin/Foundation" @(private) version_string_buf: [1024]u8 @(init, private) -init_os_version :: proc () { - os_version.platform = .MacOS +init_platform :: proc() { + ws :: strings.write_string + wi :: strings.write_int - // Start building display version b := strings.builder_from_bytes(version_string_buf[:]) - mib := []i32{sys.CTL_KERN, sys.KERN_OSVERSION} - build_buf: [12]u8 + version: NS.OperatingSystemVersion + { + NS.scoped_autoreleasepool() - ok := sys.sysctl(mib, &build_buf) - if !ok { - strings.write_string(&b, "macOS Unknown") - os_version.as_string = strings.to_string(b) - return + info := NS.ProcessInfo.processInfo() + version = info->operatingSystemVersion() + mem := info->physicalMemory() + + ram.total_ram = int(mem) } - build := string(cstring(&build_buf[0])) + macos_version = {int(version.majorVersion), int(version.minorVersion), int(version.patchVersion)} - // Do we have an exact match? - match: Darwin_Match - rel, exact := macos_release_map[build] - - if exact { - match = .Exact + when ODIN_PLATFORM_SUBTARGET == .iOS { + os_version.platform = .iOS + ws(&b, "iOS") } else { + os_version.platform = .MacOS + switch version.majorVersion { + case 15: ws(&b, "macOS Sequoia") + case 14: ws(&b, "macOS Sonoma") + case 13: ws(&b, "macOS Ventura") + case 12: ws(&b, "macOS Monterey") + case 11: ws(&b, "macOS Big Sur") + case 10: + switch version.minorVersion { + case 15: ws(&b, "macOS Catalina") + case 14: ws(&b, "macOS Mojave") + case 13: ws(&b, "macOS High Sierra") + case 12: ws(&b, "macOS Sierra") + case 11: ws(&b, "OS X El Capitan") + case 10: ws(&b, "OS X Yosemite") + case: + // `ProcessInfo.operatingSystemVersion` is 10.10 and up. + unreachable() + } + case: + // New version not yet added here. + assert(version.majorVersion > 15) + ws(&b, "macOS Unknown") + } + } + + ws(&b, " ") + wi(&b, int(version.majorVersion)) + ws(&b, ".") + wi(&b, int(version.minorVersion)) + ws(&b, ".") + wi(&b, int(version.patchVersion)) + + { + build_buf: [12]u8 + mib := []i32{unix.CTL_KERN, unix.KERN_OSVERSION} + ok := unix.sysctl(mib, &build_buf) + build := string(cstring(raw_data(build_buf[:]))) if ok else "Unknown" + + ws(&b, " (build ") + + build_start := len(b.buf) + ws(&b, build) + os_version.version = string(b.buf[build_start:][:len(build)]) + } + + { // Match on XNU kernel version - mib = []i32{sys.CTL_KERN, sys.KERN_OSRELEASE} version_bits: [12]u8 // enough for 999.999.999\x00 - have_kernel_version := sys.sysctl(mib, &version_bits) + mib := []i32{unix.CTL_KERN, unix.KERN_OSRELEASE} + ok := unix.sysctl(mib, &version_bits) + kernel := string(cstring(raw_data(version_bits[:]))) if ok else "Unknown" - major_ok, minor_ok, patch_ok: bool + major, _, tail := strings.partition(kernel, ".") + minor, _, patch := strings.partition(tail, ".") - tmp := runtime.default_temp_allocator_temp_begin() + os_version.major, _ = strconv.parse_int(major, 10) + os_version.minor, _ = strconv.parse_int(minor, 10) + os_version.patch, _ = strconv.parse_int(patch, 10) - triplet := strings.split(string(cstring(&version_bits[0])), ".", context.temp_allocator) - if len(triplet) != 3 { - have_kernel_version = false - } else { - rel.darwin.x, major_ok = strconv.parse_int(triplet[0]) - rel.darwin.y, minor_ok = strconv.parse_int(triplet[1]) - rel.darwin.z, patch_ok = strconv.parse_int(triplet[2]) - - if !(major_ok && minor_ok && patch_ok) { - have_kernel_version = false - } - } - - runtime.default_temp_allocator_temp_end(tmp) - - if !have_kernel_version { - // We don't know the kernel version, but we do know the build - strings.write_string(&b, "macOS Unknown (build ") - l := strings.builder_len(b) - strings.write_string(&b, build) - os_version.version = strings.to_string(b)[l:] - strings.write_rune(&b, ')') - os_version.as_string = strings.to_string(b) - return - } - rel, match = map_darwin_kernel_version_to_macos_release(build, rel.darwin) + ws(&b, ", kernel ") + ws(&b, kernel) + ws(&b, ")") } - os_version.major = rel.darwin.x - os_version.minor = rel.darwin.y - os_version.patch = rel.darwin.z - - macos_version = transmute(Version)rel.release.version - - strings.write_string(&b, rel.os_name) - if match == .Exact || match == .Nearest { - strings.write_rune(&b, ' ') - strings.write_string(&b, rel.release.name) - strings.write_rune(&b, ' ') - strings.write_int(&b, rel.release.version.x) - if rel.release.version.y > 0 || rel.release.version.z > 0 { - strings.write_rune(&b, '.') - strings.write_int(&b, rel.release.version.y) - } - if rel.release.version.z > 0 { - strings.write_rune(&b, '.') - strings.write_int(&b, rel.release.version.z) - } - if match == .Nearest { - strings.write_rune(&b, '?') - } - } else { - strings.write_string(&b, " Unknown") - } - - strings.write_string(&b, " (build ") - l := strings.builder_len(b) - strings.write_string(&b, build) - os_version.version = strings.to_string(b)[l:] - - strings.write_string(&b, ", kernel ") - strings.write_int(&b, rel.darwin.x) - strings.write_rune(&b, '.') - strings.write_int(&b, rel.darwin.y) - strings.write_rune(&b, '.') - strings.write_int(&b, rel.darwin.z) - strings.write_rune(&b, ')') - - os_version.as_string = strings.to_string(b) -} - -@(init, private) -init_ram :: proc() { - // Retrieve RAM info using `sysctl` - - mib := []i32{sys.CTL_HW, sys.HW_MEMSIZE} - mem_size: u64 - if sys.sysctl(mib, &mem_size) { - ram.total_ram = int(mem_size) - } -} - -@(private) -Darwin_To_Release :: struct { - darwin: [3]int, // Darwin kernel triplet - os_name: string, // OS X, MacOS - release: struct { - name: string, // Monterey, Mojave, etc. - version: [3]int, // 12.4, etc. - }, -} - -// Important: Order from lowest to highest kernel version -@(private) -macos_release_map: map[string]Darwin_To_Release = { - // MacOS Tiger - "8A428" = {{8, 0, 0}, "macOS", {"Tiger", {10, 4, 0}}}, - "8A432" = {{8, 0, 0}, "macOS", {"Tiger", {10, 4, 0}}}, - "8B15" = {{8, 1, 0}, "macOS", {"Tiger", {10, 4, 1}}}, - "8B17" = {{8, 1, 0}, "macOS", {"Tiger", {10, 4, 1}}}, - "8C46" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8C47" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8E102" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8E45" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8E90" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8F46" = {{8, 3, 0}, "macOS", {"Tiger", {10, 4, 3}}}, - "8G32" = {{8, 4, 0}, "macOS", {"Tiger", {10, 4, 4}}}, - "8G1165" = {{8, 4, 0}, "macOS", {"Tiger", {10, 4, 4}}}, - "8H14" = {{8, 5, 0}, "macOS", {"Tiger", {10, 4, 5}}}, - "8G1454" = {{8, 5, 0}, "macOS", {"Tiger", {10, 4, 5}}}, - "8I127" = {{8, 6, 0}, "macOS", {"Tiger", {10, 4, 6}}}, - "8I1119" = {{8, 6, 0}, "macOS", {"Tiger", {10, 4, 6}}}, - "8J135" = {{8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - "8J2135a" = {{8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - "8K1079" = {{8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - "8N5107" = {{8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - "8L127" = {{8, 8, 0}, "macOS", {"Tiger", {10, 4, 8}}}, - "8L2127" = {{8, 8, 0}, "macOS", {"Tiger", {10, 4, 8}}}, - "8P135" = {{8, 9, 0}, "macOS", {"Tiger", {10, 4, 9}}}, - "8P2137" = {{8, 9, 0}, "macOS", {"Tiger", {10, 4, 9}}}, - "8R218" = {{8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - "8R2218" = {{8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - "8R2232" = {{8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - "8S165" = {{8, 11, 0}, "macOS", {"Tiger", {10, 4, 11}}}, - "8S2167" = {{8, 11, 0}, "macOS", {"Tiger", {10, 4, 11}}}, - - // MacOS Leopard - "9A581" = {{9, 0, 0}, "macOS", {"Leopard", {10, 5, 0}}}, - "9B18" = {{9, 1, 0}, "macOS", {"Leopard", {10, 5, 1}}}, - "9B2117" = {{9, 1, 1}, "macOS", {"Leopard", {10, 5, 1}}}, - "9C31" = {{9, 2, 0}, "macOS", {"Leopard", {10, 5, 2}}}, - "9C7010" = {{9, 2, 0}, "macOS", {"Leopard", {10, 5, 2}}}, - "9D34" = {{9, 3, 0}, "macOS", {"Leopard", {10, 5, 3}}}, - "9E17" = {{9, 4, 0}, "macOS", {"Leopard", {10, 5, 4}}}, - "9F33" = {{9, 5, 0}, "macOS", {"Leopard", {10, 5, 5}}}, - "9G55" = {{9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - "9G66" = {{9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - "9G71" = {{9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - "9J61" = {{9, 7, 0}, "macOS", {"Leopard", {10, 5, 7}}}, - "9L30" = {{9, 8, 0}, "macOS", {"Leopard", {10, 5, 8}}}, - "9L34" = {{9, 8, 0}, "macOS", {"Leopard", {10, 5, 8}}}, - - // MacOS Snow Leopard - "10A432" = {{10, 0, 0}, "macOS", {"Snow Leopard", {10, 6, 0}}}, - "10A433" = {{10, 0, 0}, "macOS", {"Snow Leopard", {10, 6, 0}}}, - "10B504" = {{10, 1, 0}, "macOS", {"Snow Leopard", {10, 6, 1}}}, - "10C540" = {{10, 2, 0}, "macOS", {"Snow Leopard", {10, 6, 2}}}, - "10D573" = {{10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - "10D575" = {{10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - "10D578" = {{10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - "10F569" = {{10, 4, 0}, "macOS", {"Snow Leopard", {10, 6, 4}}}, - "10H574" = {{10, 5, 0}, "macOS", {"Snow Leopard", {10, 6, 5}}}, - "10J567" = {{10, 6, 0}, "macOS", {"Snow Leopard", {10, 6, 6}}}, - "10J869" = {{10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - "10J3250" = {{10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - "10J4138" = {{10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - "10K540" = {{10, 8, 0}, "macOS", {"Snow Leopard", {10, 6, 8}}}, - "10K549" = {{10, 8, 0}, "macOS", {"Snow Leopard", {10, 6, 8}}}, - - // MacOS Lion - "11A511" = {{11, 0, 0}, "macOS", {"Lion", {10, 7, 0}}}, - "11A511s" = {{11, 0, 0}, "macOS", {"Lion", {10, 7, 0}}}, - "11A2061" = {{11, 0, 2}, "macOS", {"Lion", {10, 7, 0}}}, - "11A2063" = {{11, 0, 2}, "macOS", {"Lion", {10, 7, 0}}}, - "11B26" = {{11, 1, 0}, "macOS", {"Lion", {10, 7, 1}}}, - "11B2118" = {{11, 1, 0}, "macOS", {"Lion", {10, 7, 1}}}, - "11C74" = {{11, 2, 0}, "macOS", {"Lion", {10, 7, 2}}}, - "11D50" = {{11, 3, 0}, "macOS", {"Lion", {10, 7, 3}}}, - "11E53" = {{11, 4, 0}, "macOS", {"Lion", {10, 7, 4}}}, - "11G56" = {{11, 4, 2}, "macOS", {"Lion", {10, 7, 5}}}, - "11G63" = {{11, 4, 2}, "macOS", {"Lion", {10, 7, 5}}}, - - // MacOS Mountain Lion - "12A269" = {{12, 0, 0}, "macOS", {"Mountain Lion", {10, 8, 0}}}, - "12B19" = {{12, 1, 0}, "macOS", {"Mountain Lion", {10, 8, 1}}}, - "12C54" = {{12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - "12C60" = {{12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - "12C2034" = {{12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - "12C3104" = {{12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - "12D78" = {{12, 3, 0}, "macOS", {"Mountain Lion", {10, 8, 3}}}, - "12E55" = {{12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - "12E3067" = {{12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - "12E4022" = {{12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - "12F37" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F45" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F2501" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F2518" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F2542" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F2560" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - - // MacOS Mavericks - "13A603" = {{13, 0, 0}, "macOS", {"Mavericks", {10, 9, 0}}}, - "13B42" = {{13, 0, 0}, "macOS", {"Mavericks", {10, 9, 1}}}, - "13C64" = {{13, 1, 0}, "macOS", {"Mavericks", {10, 9, 2}}}, - "13C1021" = {{13, 1, 0}, "macOS", {"Mavericks", {10, 9, 2}}}, - "13D65" = {{13, 2, 0}, "macOS", {"Mavericks", {10, 9, 3}}}, - "13E28" = {{13, 3, 0}, "macOS", {"Mavericks", {10, 9, 4}}}, - "13F34" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1066" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1077" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1096" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1112" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1134" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1507" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1603" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1712" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1808" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1911" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - - // MacOS Yosemite - "14A389" = {{14, 0, 0}, "macOS", {"Yosemite", {10, 10, 0}}}, - "14B25" = {{14, 0, 0}, "macOS", {"Yosemite", {10, 10, 1}}}, - "14C109" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14C1510" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14C2043" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14C1514" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14C2513" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14D131" = {{14, 3, 0}, "macOS", {"Yosemite", {10, 10, 3}}}, - "14D136" = {{14, 3, 0}, "macOS", {"Yosemite", {10, 10, 3}}}, - "14E46" = {{14, 4, 0}, "macOS", {"Yosemite", {10, 10, 4}}}, - "14F27" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1021" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1505" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1509" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1605" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1713" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1808" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1909" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1912" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2009" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2109" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2315" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2411" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2511" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - - // MacOS El Capitan - "15A284" = {{15, 0, 0}, "macOS", {"El Capitan", {10, 11, 0}}}, - "15B42" = {{15, 0, 0}, "macOS", {"El Capitan", {10, 11, 1}}}, - "15C50" = {{15, 2, 0}, "macOS", {"El Capitan", {10, 11, 2}}}, - "15D21" = {{15, 3, 0}, "macOS", {"El Capitan", {10, 11, 3}}}, - "15E65" = {{15, 4, 0}, "macOS", {"El Capitan", {10, 11, 4}}}, - "15F34" = {{15, 5, 0}, "macOS", {"El Capitan", {10, 11, 5}}}, - "15G31" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1004" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1011" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1108" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1212" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1217" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1421" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1510" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1611" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G17023" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G18013" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G19009" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G20015" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G21013" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G22010" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - - // MacOS Sierra - "16A323" = {{16, 0, 0}, "macOS", {"Sierra", {10, 12, 0}}}, - "16B2555" = {{16, 1, 0}, "macOS", {"Sierra", {10, 12, 1}}}, - "16B2657" = {{16, 1, 0}, "macOS", {"Sierra", {10, 12, 1}}}, - "16C67" = {{16, 3, 0}, "macOS", {"Sierra", {10, 12, 2}}}, - "16C68" = {{16, 3, 0}, "macOS", {"Sierra", {10, 12, 2}}}, - "16D32" = {{16, 4, 0}, "macOS", {"Sierra", {10, 12, 3}}}, - "16E195" = {{16, 5, 0}, "macOS", {"Sierra", {10, 12, 4}}}, - "16F73" = {{16, 6, 0}, "macOS", {"Sierra", {10, 12, 5}}}, - "16F2073" = {{16, 6, 0}, "macOS", {"Sierra", {10, 12, 5}}}, - "16G29" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1036" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1114" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1212" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1314" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1408" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1510" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1618" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1710" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1815" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1917" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1918" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G2016" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G2127" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G2128" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G2136" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - - // MacOS High Sierra - "17A365" = {{17, 0, 0}, "macOS", {"High Sierra", {10, 13, 0}}}, - "17A405" = {{17, 0, 0}, "macOS", {"High Sierra", {10, 13, 0}}}, - "17B48" = {{17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - "17B1002" = {{17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - "17B1003" = {{17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - "17C88" = {{17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - "17C89" = {{17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - "17C205" = {{17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - "17C2205" = {{17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - "17D47" = {{17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - "17D2047" = {{17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - "17D102" = {{17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - "17D2102" = {{17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - "17E199" = {{17, 5, 0}, "macOS", {"High Sierra", {10, 13, 4}}}, - "17E202" = {{17, 5, 0}, "macOS", {"High Sierra", {10, 13, 4}}}, - "17F77" = {{17, 6, 0}, "macOS", {"High Sierra", {10, 13, 5}}}, - "17G65" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G2208" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G2307" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G3025" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G4015" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G5019" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G6029" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G6030" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G7024" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G8029" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G8030" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G8037" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G9016" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G10021" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G11023" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G12034" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G13033" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G13035" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G14019" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G14033" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G14042" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - - // MacOS Mojave - "18A391" = {{18, 0, 0}, "macOS", {"Mojave", {10, 14, 0}}}, - "18B75" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - "18B2107" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - "18B3094" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - "18C54" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 2}}}, - "18D42" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - "18D43" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - "18D109" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - "18E226" = {{18, 5, 0}, "macOS", {"Mojave", {10, 14, 4}}}, - "18E227" = {{18, 5, 0}, "macOS", {"Mojave", {10, 14, 4}}}, - "18F132" = {{18, 6, 0}, "macOS", {"Mojave", {10, 14, 5}}}, - "18G84" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G87" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G95" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G103" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G1012" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G2022" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G3020" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G4032" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G5033" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G6020" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G6032" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G6042" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G7016" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G8012" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G8022" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G9028" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G9216" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G9323" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - - // MacOS Catalina - "19A583" = {{19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - "19A602" = {{19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - "19A603" = {{19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - "19B88" = {{19, 0, 0}, "macOS", {"Catalina", {10, 15, 1}}}, - "19C57" = {{19, 2, 0}, "macOS", {"Catalina", {10, 15, 2}}}, - "19C58" = {{19, 2, 0}, "macOS", {"Catalina", {10, 15, 2}}}, - "19D76" = {{19, 3, 0}, "macOS", {"Catalina", {10, 15, 3}}}, - "19E266" = {{19, 4, 0}, "macOS", {"Catalina", {10, 15, 4}}}, - "19E287" = {{19, 4, 0}, "macOS", {"Catalina", {10, 15, 4}}}, - "19F96" = {{19, 5, 0}, "macOS", {"Catalina", {10, 15, 5}}}, - "19F101" = {{19, 5, 0}, "macOS", {"Catalina", {10, 15, 5}}}, - "19G73" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 6}}}, - "19G2021" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 6}}}, - "19H2" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H4" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H15" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H114" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H512" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H524" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1030" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1217" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1323" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1417" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1419" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1519" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1615" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1713" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1715" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1824" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1922" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H2026" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - - // MacOS Big Sur - "20A2411" = {{20, 1, 0}, "macOS", {"Big Sur", {11, 0, 0}}}, - "20B29" = {{20, 1, 0}, "macOS", {"Big Sur", {11, 0, 1}}}, - "20B50" = {{20, 1, 0}, "macOS", {"Big Sur", {11, 0, 1}}}, - "20C69" = {{20, 2, 0}, "macOS", {"Big Sur", {11, 1, 0}}}, - "20D64" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 0}}}, - "20D74" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 1}}}, - "20D75" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 1}}}, - "20D80" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 2}}}, - "20D91" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 3}}}, - "20E232" = {{20, 4, 0}, "macOS", {"Big Sur", {11, 3, 0}}}, - "20E241" = {{20, 4, 0}, "macOS", {"Big Sur", {11, 3, 1}}}, - "20F71" = {{20, 5, 0}, "macOS", {"Big Sur", {11, 4, 0}}}, - "20G71" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 5, 0}}}, - "20G80" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 5, 1}}}, - "20G95" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 5, 2}}}, - "20G165" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 0}}}, - "20G224" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 1}}}, - "20G314" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 2}}}, - "20G415" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 3}}}, - "20G417" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 4}}}, - "20G527" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 5}}}, - "20G624" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 6}}}, - "20G630" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 7}}}, - "20G730" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 8}}}, - "20G817" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 0}}}, - "20G918" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 1}}}, - "20G1020" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 2}}}, - "20G1116" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 3}}}, - "20G1120" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 4}}}, - "20G1225" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 5}}}, - "20G1231" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 6}}}, - "20G1345" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 7}}}, - "20G1351" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 8}}}, - "20G1426" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 9}}}, - "20G1427" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 7, 10}}}, - - // MacOS Monterey - "21A344" = {{21, 0, 1}, "macOS", {"Monterey", {12, 0, 0}}}, - "21A559" = {{21, 1, 0}, "macOS", {"Monterey", {12, 0, 1}}}, - "21C52" = {{21, 2, 0}, "macOS", {"Monterey", {12, 1, 0}}}, - "21D49" = {{21, 3, 0}, "macOS", {"Monterey", {12, 2, 0}}}, - "21D62" = {{21, 3, 0}, "macOS", {"Monterey", {12, 2, 1}}}, - "21E230" = {{21, 4, 0}, "macOS", {"Monterey", {12, 3, 0}}}, - "21E258" = {{21, 4, 0}, "macOS", {"Monterey", {12, 3, 1}}}, - "21F79" = {{21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - "21F2081" = {{21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - "21F2092" = {{21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - "21G72" = {{21, 6, 0}, "macOS", {"Monterey", {12, 5, 0}}}, - "21G83" = {{21, 6, 0}, "macOS", {"Monterey", {12, 5, 1}}}, - "21G115" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 0}}}, - "21G217" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 1}}}, - "21G320" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 2}}}, - "21G419" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 3}}}, - "21G526" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 4}}}, - "21G531" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 5}}}, - "21G646" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 6}}}, - "21G651" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 7}}}, - "21G725" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 8}}}, - "21G726" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 9}}}, - "21G816" = {{21, 6, 0}, "macOS", {"Monterey", {12, 7, 0}}}, - "21G920" = {{21, 6, 0}, "macOS", {"Monterey", {12, 7, 1}}}, - "21G1974" = {{21, 6, 0}, "macOS", {"Monterey", {12, 7, 2}}}, - - // MacOS Ventura - "22A380" = {{22, 1, 0}, "macOS", {"Ventura", {13, 0, 0}}}, - "22A400" = {{22, 1, 0}, "macOS", {"Ventura", {13, 0, 1}}}, - "22C65" = {{22, 2, 0}, "macOS", {"Ventura", {13, 1, 0}}}, - "22D49" = {{22, 3, 0}, "macOS", {"Ventura", {13, 2, 0}}}, - "22D68" = {{22, 3, 0}, "macOS", {"Ventura", {13, 2, 1}}}, - "22E252" = {{22, 4, 0}, "macOS", {"Ventura", {13, 3, 0}}}, - "22E261" = {{22, 4, 0}, "macOS", {"Ventura", {13, 3, 1}}}, - "22F66" = {{22, 5, 0}, "macOS", {"Ventura", {13, 4, 0}}}, - "22F82" = {{22, 5, 0}, "macOS", {"Ventura", {13, 4, 1}}}, - "22E772610a" = {{22, 5, 0}, "macOS", {"Ventura", {13, 4, 1}}}, - "22F770820d" = {{22, 5, 0}, "macOS", {"Ventura", {13, 4, 1}}}, - "22G74" = {{22, 6, 0}, "macOS", {"Ventura", {13, 5, 0}}}, - "22G90" = {{22, 6, 0}, "macOS", {"Ventura", {13, 5, 1}}}, - "22G91" = {{22, 6, 0}, "macOS", {"Ventura", {13, 5, 2}}}, - "22G120" = {{22, 6, 0}, "macOS", {"Ventura", {13, 6, 0}}}, - "22G313" = {{22, 6, 0}, "macOS", {"Ventura", {13, 6, 1}}}, - "22G320" = {{22, 6, 0}, "macOS", {"Ventura", {13, 6, 2}}}, - - // MacOS Sonoma - "23A344" = {{23, 0, 0}, "macOS", {"Sonoma", {14, 0, 0}}}, - "23B74" = {{23, 1, 0}, "macOS", {"Sonoma", {14, 1, 0}}}, - "23B81" = {{23, 1, 0}, "macOS", {"Sonoma", {14, 1, 1}}}, - "23B2082" = {{23, 1, 0}, "macOS", {"Sonoma", {14, 1, 1}}}, - "23B92" = {{23, 1, 0}, "macOS", {"Sonoma", {14, 1, 2}}}, - "23B2091" = {{23, 1, 0}, "macOS", {"Sonoma", {14, 1, 2}}}, - "23C64" = {{23, 2, 0}, "macOS", {"Sonoma", {14, 2, 0}}}, - "23C71" = {{23, 2, 0}, "macOS", {"Sonoma", {14, 2, 1}}}, - "23D56" = {{23, 3, 0}, "macOS", {"Sonoma", {14, 3, 0}}}, - "23D60" = {{23, 3, 0}, "macOS", {"Sonoma", {14, 3, 1}}}, - "23E214" = {{23, 4, 0}, "macOS", {"Sonoma", {14, 4, 0}}}, - "23E224" = {{23, 4, 0}, "macOS", {"Sonoma", {14, 4, 1}}}, - "23F79" = {{23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}}, - "23G80" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 0}}}, - "23G93" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 1}}}, - "23H124" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 7, 0}}}, - - // MacOS Sequoia - "24A335" = {{24, 0, 0}, "macOS", {"Sequoia", {15, 0, 0}}}, - "24A348" = {{24, 0, 0}, "macOS", {"Sequoia", {15, 0, 1}}}, -} - -@(private) -Darwin_Match :: enum { - Unknown, - Exact, - Nearest, -} - -@(private) -map_darwin_kernel_version_to_macos_release :: proc(build: string, darwin: [3]int) -> (res: Darwin_To_Release, match: Darwin_Match) { - // Find exact release match if possible. - if v, v_ok := macos_release_map[build]; v_ok { - return v, .Exact - } - - nearest: Darwin_To_Release - for _, v in macos_release_map { - // Try an exact match on XNU version first. - if darwin == v.darwin { - return v, .Exact - } - - // Major kernel version needs to match exactly, - // otherwise the release is considered .Unknown - if darwin.x == v.darwin.x { - if nearest == {} { - nearest = v - } - if darwin.y >= v.darwin.y && v.darwin != nearest.darwin { - nearest = v - if darwin.z >= v.darwin.z && v.darwin != nearest.darwin { - nearest = v - } - } - } - } - - if nearest == {} { - return {darwin, "macOS", {"Unknown", {}}}, .Unknown - } else { - return nearest, .Nearest - } + os_version.as_string = string(b.buf[:]) } diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index 9f2c7a5d8..c304397de 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -152,43 +152,65 @@ Errno :: enum i32 { RDONLY flag is not present, because it has the value of 0, i.e. it is the default, unless WRONLY or RDWR is specified. */ -Open_Flags_Bits :: enum { - WRONLY = 0, - RDWR = 1, - CREAT = 6, - EXCL = 7, - NOCTTY = 8, - TRUNC = 9, - APPEND = 10, - NONBLOCK = 11, - DSYNC = 12, - ASYNC = 13, - DIRECT = 14, - LARGEFILE = 15, - DIRECTORY = 16, - NOFOLLOW = 17, - NOATIME = 18, - CLOEXEC = 19, - PATH = 21, +when ODIN_ARCH != .arm64 && ODIN_ARCH != .arm32 { + Open_Flags_Bits :: enum { + WRONLY = 0, + RDWR = 1, + CREAT = 6, + EXCL = 7, + NOCTTY = 8, + TRUNC = 9, + APPEND = 10, + NONBLOCK = 11, + DSYNC = 12, + ASYNC = 13, + DIRECT = 14, + LARGEFILE = 15, + DIRECTORY = 16, + NOFOLLOW = 17, + NOATIME = 18, + CLOEXEC = 19, + PATH = 21, + } + // https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19 + #assert(1 << uint(Open_Flags_Bits.WRONLY) == 0o0000000_1) + #assert(1 << uint(Open_Flags_Bits.RDWR) == 0o0000000_2) + #assert(1 << uint(Open_Flags_Bits.CREAT) == 0o00000_100) + #assert(1 << uint(Open_Flags_Bits.EXCL) == 0o00000_200) + #assert(1 << uint(Open_Flags_Bits.NOCTTY) == 0o00000_400) + #assert(1 << uint(Open_Flags_Bits.TRUNC) == 0o0000_1000) + #assert(1 << uint(Open_Flags_Bits.APPEND) == 0o0000_2000) + #assert(1 << uint(Open_Flags_Bits.NONBLOCK) == 0o0000_4000) + #assert(1 << uint(Open_Flags_Bits.DSYNC) == 0o000_10000) + #assert(1 << uint(Open_Flags_Bits.ASYNC) == 0o000_20000) + #assert(1 << uint(Open_Flags_Bits.DIRECT) == 0o000_40000) + #assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000) + #assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000) + #assert(1 << uint(Open_Flags_Bits.NOFOLLOW) == 0o00_400000) + #assert(1 << uint(Open_Flags_Bits.NOATIME) == 0o0_1000000) + #assert(1 << uint(Open_Flags_Bits.CLOEXEC) == 0o0_2000000) + #assert(1 << uint(Open_Flags_Bits.PATH) == 0o_10000000) +} else { + Open_Flags_Bits :: enum { + WRONLY = 0, + RDWR = 1, + CREAT = 6, + EXCL = 7, + NOCTTY = 8, + TRUNC = 9, + APPEND = 10, + NONBLOCK = 11, + DSYNC = 12, + ASYNC = 13, + DIRECTORY = 14, + NOFOLLOW = 15, + DIRECT = 16, + LARGEFILE = 17, + NOATIME = 18, + CLOEXEC = 19, + PATH = 21, + } } -// https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19 -#assert(1 << uint(Open_Flags_Bits.WRONLY) == 0o0000000_1) -#assert(1 << uint(Open_Flags_Bits.RDWR) == 0o0000000_2) -#assert(1 << uint(Open_Flags_Bits.CREAT) == 0o00000_100) -#assert(1 << uint(Open_Flags_Bits.EXCL) == 0o00000_200) -#assert(1 << uint(Open_Flags_Bits.NOCTTY) == 0o00000_400) -#assert(1 << uint(Open_Flags_Bits.TRUNC) == 0o0000_1000) -#assert(1 << uint(Open_Flags_Bits.APPEND) == 0o0000_2000) -#assert(1 << uint(Open_Flags_Bits.NONBLOCK) == 0o0000_4000) -#assert(1 << uint(Open_Flags_Bits.DSYNC) == 0o000_10000) -#assert(1 << uint(Open_Flags_Bits.ASYNC) == 0o000_20000) -#assert(1 << uint(Open_Flags_Bits.DIRECT) == 0o000_40000) -#assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000) -#assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000) -#assert(1 << uint(Open_Flags_Bits.NOFOLLOW) == 0o00_400000) -#assert(1 << uint(Open_Flags_Bits.NOATIME) == 0o0_1000000) -#assert(1 << uint(Open_Flags_Bits.CLOEXEC) == 0o0_2000000) -#assert(1 << uint(Open_Flags_Bits.PATH) == 0o_10000000) /* Bits for FD_Flags bitset @@ -519,6 +541,36 @@ Fd_Poll_Events_Bits :: enum { RDHUP = 13, } +Inotify_Init_Bits :: enum { + NONBLOCK = 11, + CLOEXEC = 19, +} + +Inotify_Event_Bits :: enum u32 { + ACCESS = 0, + MODIFY = 1, + ATTRIB = 2, + CLOSE_WRITE = 3, + CLOSE_NOWRITE = 4, + OPEN = 5, + MOVED_FROM = 6, + MOVED_TO = 7, + CREATE = 8, + DELETE = 9, + DELETE_SELF = 10, + MOVE_SELF = 11, + UNMOUNT = 13, + Q_OVERFLOW = 14, + IGNORED = 15, + ONLYDIR = 24, + DONT_FOLLOW = 25, + EXCL_UNLINK = 26, + MASK_CREATE = 28, + MASK_ADD = 29, + ISDIR = 30, + ONESHOT = 31, +} + /* Bits for Mem_Protection bitfield */ diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index 129444d0f..b3bbcafb3 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -135,6 +135,31 @@ STATX_BASIC_STATS :: Statx_Mask { .BLOCKS, } +IN_ALL_EVENTS :: Inotify_Event_Mask { + .ACCESS, + .MODIFY, + .ATTRIB, + .CLOSE_WRITE, + .CLOSE_NOWRITE, + .OPEN, + .MOVED_FROM, + .MOVED_TO, + .CREATE, + .DELETE, + .DELETE_SELF, + .MOVE_SELF, +} + +IN_CLOSE :: Inotify_Event_Mask { + .CLOSE_WRITE, + .CLOSE_NOWRITE, +} + +IN_MOVE :: Inotify_Event_Mask { + .MOVED_FROM, + .MOVED_TO, +} + /* Tell `shmget` to create a new key */ diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index c5894d78b..690902f07 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -2536,11 +2536,30 @@ waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, opt // TODO(flysand): ioprio_get -// TODO(flysand): inotify_init +inotify_init :: proc "contextless" () -> (Fd, Errno) { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + ret := syscall(SYS_inotify_init1, 0) + return errno_unwrap(ret, Fd) + } else { + ret := syscall(SYS_inotify_init) + return errno_unwrap(ret, Fd) + } +} -// TODO(flysand): inotify_add_watch +inotify_init1 :: proc "contextless" (flags: Inotify_Init_Flags) -> (Fd, Errno) { + ret := syscall(SYS_inotify_init1, transmute(i32)flags) + return errno_unwrap(ret, Fd) +} -// TODO(flysand): inotify_rm_watch +inotify_add_watch :: proc "contextless" (fd: Fd, pathname: cstring, mask: Inotify_Event_Mask) -> (Wd, Errno) { + ret := syscall(SYS_inotify_add_watch, fd, transmute(uintptr) pathname, transmute(u32) mask) + return errno_unwrap(ret, Wd) +} + +inotify_rm_watch :: proc "contextless" (fd: Fd, wd: Wd) -> (Errno) { + ret := syscall(SYS_inotify_rm_watch, fd, wd) + return Errno(-ret) +} // TODO(flysand): migrate_pages diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 07c654749..08a443bcc 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -30,6 +30,11 @@ Id :: distinct uint */ Fd :: distinct i32 +/* + Represents a watch descriptor. +*/ +Wd :: distinct i32 + /* Type for PID file descriptors. */ @@ -343,6 +348,18 @@ Poll_Fd :: struct { revents: Fd_Poll_Events, } +Inotify_Init_Flags :: bit_set[Inotify_Init_Bits; i32] + +Inotify_Event :: struct { + wd: Wd, + mask: Inotify_Event_Mask, + cookie: u32, + len: u32, + name: [0]u8, +} + +Inotify_Event_Mask :: bit_set[Inotify_Event_Bits; u32] + /* Specifies protection for memory pages. */ @@ -667,6 +684,14 @@ Address_Family :: distinct Protocol_Family */ Socket_Msg :: bit_set[Socket_Msg_Bits; i32] +/* + Struct representing a generic socket address. +*/ +Sock_Addr :: struct #packed { + sa_family: Address_Family, + sa_data: [14]u8, +} + /* Struct representing IPv4 socket address. */ @@ -674,6 +699,7 @@ Sock_Addr_In :: struct #packed { sin_family: Address_Family, sin_port: u16be, sin_addr: [4]u8, + sin_zero: [size_of(Sock_Addr) - size_of(Address_Family) - size_of(u16be) - size_of([4]u8)]u8, } /* @@ -703,6 +729,7 @@ Sock_Addr_Any :: struct #raw_union { family: Address_Family, port: u16be, }, + using generic: Sock_Addr, using ipv4: Sock_Addr_In, using ipv6: Sock_Addr_In6, using uds: Sock_Addr_Un, diff --git a/core/sys/posix/arpa_inet.odin b/core/sys/posix/arpa_inet.odin index 7e950c4be..d3592dd80 100644 --- a/core/sys/posix/arpa_inet.odin +++ b/core/sys/posix/arpa_inet.odin @@ -1,3 +1,4 @@ +#+build darwin, linux, freebsd, openbsd, netbsd package posix import "core:c" diff --git a/core/sys/posix/dirent.odin b/core/sys/posix/dirent.odin index 48830b030..bf32be8cf 100644 --- a/core/sys/posix/dirent.odin +++ b/core/sys/posix/dirent.odin @@ -1,3 +1,4 @@ +#+build darwin, linux, freebsd, openbsd, netbsd package posix import "core:c" @@ -29,12 +30,12 @@ foreign lib { panic(string(posix.strerror(posix.errno()))) } defer posix.free(list) - + entries := list[:ret] - for entry in entries { - log.info(entry) - posix.free(entry) - } + for entry in entries { + log.info(entry) + posix.free(entry) + } [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html ]] */ @@ -53,15 +54,6 @@ foreign lib { */ closedir :: proc(dirp: DIR) -> result --- - /* - Return a file descriptor referring to the same directory as the dirp argument. - - // TODO: this is a macro on NetBSD? - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]] - */ - dirfd :: proc(dirp: DIR) -> FD --- - /* Equivalent to the opendir() function except that the directory is specified by a file descriptor rather than by a name. @@ -89,16 +81,16 @@ foreign lib { Returns nil when the end is reached or an error occurred (which sets errno). Example: - posix.set_errno(.NONE) - entry := posix.readdir(dirp) - if entry == nil { - if errno := posix.errno(); errno != .NONE { - panic(string(posix.strerror(errno))) - } else { - fmt.println("end of directory stream") - } - } else { - fmt.println(entry) + posix.set_errno(.NONE) + entry := posix.readdir(dirp) + if entry == nil { + if errno := posix.errno(); errno != .NONE { + panic(string(posix.strerror(errno))) + } else { + fmt.println("end of directory stream") + } + } else { + fmt.println(entry) } [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html ]] @@ -160,11 +152,36 @@ when ODIN_OS == .NetBSD { @(private) LSCANDIR :: "__scandir30" @(private) LOPENDIR :: "__opendir30" @(private) LREADDIR :: "__readdir30" + + /* + Return a file descriptor referring to the same directory as the dirp argument. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]] + */ + dirfd :: proc "c" (dirp: DIR) -> FD { + _dirdesc :: struct { + dd_fd: FD, + + // more stuff... + } + + return (^_dirdesc)(dirp).dd_fd + } + } else { @(private) LALPHASORT :: "alphasort" + INODE_SUFFIX @(private) LSCANDIR :: "scandir" + INODE_SUFFIX @(private) LOPENDIR :: "opendir" + INODE_SUFFIX @(private) LREADDIR :: "readdir" + INODE_SUFFIX + + foreign lib { + /* + Return a file descriptor referring to the same directory as the dirp argument. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]] + */ + dirfd :: proc(dirp: DIR) -> FD --- + } } when ODIN_OS == .Darwin { @@ -210,6 +227,4 @@ when ODIN_OS == .Darwin { d_name: [256]c.char `fmt:"s,0"`, /* [PSX] entry name */ } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/dlfcn.odin b/core/sys/posix/dlfcn.odin index 6a467a2bd..e84b29d79 100644 --- a/core/sys/posix/dlfcn.odin +++ b/core/sys/posix/dlfcn.odin @@ -1,3 +1,4 @@ +#+build darwin, linux, freebsd, openbsd, netbsd package posix import "core:c" @@ -120,7 +121,5 @@ when ODIN_OS == .Darwin { _RTLD_LOCAL :: 0 RTLD_LOCAL :: RTLD_Flags{} -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/errno.odin b/core/sys/posix/errno.odin index e670ca889..9bc77f12e 100644 --- a/core/sys/posix/errno.odin +++ b/core/sys/posix/errno.odin @@ -1,3 +1,4 @@ +#+build windows, darwin, linux, freebsd, openbsd, netbsd package posix import "core:c" @@ -456,7 +457,84 @@ when ODIN_OS == .Darwin { EOWNERDEAD :: 130 ENOTRECOVERABLE :: 131 -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Windows { + E2BIG :: 7 + EACCES :: 13 + EADDRINUSE :: 100 + EADDRNOTAVAIL :: 101 + EAFNOSUPPORT :: 102 + EAGAIN :: 11 + EALREADY :: 103 + EBADF :: 9 + EBADMSG :: 104 + EBUSY :: 16 + ECANCELED :: 105 + ECHILD :: 10 + ECONNABORTED :: 106 + ECONNREFUSED :: 107 + ECONNRESET :: 108 + EDEADLK :: 36 + EDESTADDRREQ :: 109 + EDQUOT :: -1 // NOTE: not defined + EEXIST :: 17 + EFAULT :: 14 + EFBIG :: 27 + EHOSTUNREACH :: 110 + EIDRM :: 111 + EINPROGRESS :: 112 + EINTR :: 4 + EINVAL :: 22 + EIO :: 5 + EISCONN :: 113 + EISDIR :: 21 + ELOOP :: 114 + EMFILE :: 24 + EMLINK :: 31 + EMSGSIZE :: 115 + EMULTIHOP :: -1 // NOTE: not defined + ENAMETOOLONG :: 38 + ENETDOWN :: 116 + ENETRESET :: 117 + ENETUNREACH :: 118 + ENFILE :: 23 + ENOBUFS :: 119 + ENODATA :: 120 + ENODEV :: 19 + ENOENT :: 2 + ENOEXEC :: 8 + ENOLCK :: 39 + ENOLINK :: 121 + ENOMEM :: 12 + ENOMSG :: 122 + ENOPROTOOPT :: 123 + ENOSPC :: 28 + ENOSR :: 124 + ENOSTR :: 125 + ENOSYS :: 40 + ENOTCONN :: 126 + ENOTDIR :: 20 + ENOTEMPTY :: 41 + ENOTRECOVERABLE :: 127 + ENOTSOCK :: 128 + ENOTSUP :: 129 + ENOTTY :: 25 + ENXIO :: 6 + EOPNOTSUPP :: 130 + EOVERFLOW :: 132 + EOWNERDEAD :: 133 + EPERM :: 1 + EPIPE :: 32 + EPROTO :: 134 + EPROTONOSUPPORT :: 135 + EPROTOTYPE :: 136 + EROFS :: 30 + ESPIPE :: 29 + ESRCH :: 3 + ESTALE :: -1 // NOTE: not defined + ETIME :: 137 + ETIMEDOUT :: 138 + ETXTBSY :: 139 + EWOULDBLOCK :: 140 + EXDEV :: 18 } diff --git a/core/sys/posix/fcntl.odin b/core/sys/posix/fcntl.odin index 1ccc07c54..d948af600 100644 --- a/core/sys/posix/fcntl.odin +++ b/core/sys/posix/fcntl.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, openbsd, freebsd, netbsd package posix import "core:c" @@ -466,13 +467,11 @@ when ODIN_OS == .Darwin { AT_REMOVEDIR :: 0x200 flock :: struct { + l_type: Lock_Type, /* [PSX] type of lock. */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset. */ l_start: off_t, /* [PSX] relative offset in bytes. */ l_len: off_t, /* [PSX] size; if 0 then until EOF. */ l_pid: pid_t, /* [PSX] process ID of the process holding the lock. */ - l_type: Lock_Type, /* [PSX] type of lock. */ - l_whence: c.short, /* [PSX] flag (Whence) of starting offset. */ } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/fnmatch.odin b/core/sys/posix/fnmatch.odin index 1326ce9e4..2d582705c 100644 --- a/core/sys/posix/fnmatch.odin +++ b/core/sys/posix/fnmatch.odin @@ -1,3 +1,4 @@ +#+build darwin, linux, openbsd, freebsd, netbsd package posix import "core:c" @@ -61,6 +62,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS FNM_NOESCAPE :: 0x02 FNM_PERIOD :: 0x04 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/glob.odin b/core/sys/posix/glob.odin index c17f8b2c3..7c8009a59 100644 --- a/core/sys/posix/glob.odin +++ b/core/sys/posix/glob.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -203,6 +204,4 @@ when ODIN_OS == .Darwin { GLOB_ABORTED :: 2 GLOB_NOMATCH :: 3 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/grp.odin b/core/sys/posix/grp.odin index 3a78e2c4c..956ed148b 100644 --- a/core/sys/posix/grp.odin +++ b/core/sys/posix/grp.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -125,6 +126,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS gr_mem: [^]cstring, /* [PSX] group members */ } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/iconv.odin b/core/sys/posix/iconv.odin index 59248890f..f7447be9e 100644 --- a/core/sys/posix/iconv.odin +++ b/core/sys/posix/iconv.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" diff --git a/core/sys/posix/langinfo.odin b/core/sys/posix/langinfo.odin index 04b46921c..3c001aee0 100644 --- a/core/sys/posix/langinfo.odin +++ b/core/sys/posix/langinfo.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" diff --git a/core/sys/posix/libgen.odin b/core/sys/posix/libgen.odin index 99506797e..69176a557 100644 --- a/core/sys/posix/libgen.odin +++ b/core/sys/posix/libgen.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix when ODIN_OS == .Darwin { @@ -56,6 +57,7 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html ]] */ + @(link_name=LBASENAME) basename :: proc(path: cstring) -> cstring --- /* @@ -72,3 +74,9 @@ foreign lib { */ dirname :: proc(path: cstring) -> cstring --- } + +when ODIN_OS == .Linux { + @(private) LBASENAME :: "__xpg_basename" +} else { + @(private) LBASENAME :: "basename" +} diff --git a/core/sys/posix/limits.odin b/core/sys/posix/limits.odin index 58adce0a1..6680986ad 100644 --- a/core/sys/posix/limits.odin +++ b/core/sys/posix/limits.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix // limits.h - implementation-defined constants @@ -549,6 +550,4 @@ when ODIN_OS == .Darwin { NL_TEXTMAX :: 2048 // 255 on glibc, 2048 on musl NZERO :: 20 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/locale.odin b/core/sys/posix/locale.odin index fae692bcb..5b8d7c216 100644 --- a/core/sys/posix/locale.odin +++ b/core/sys/posix/locale.odin @@ -1,131 +1,11 @@ +#+build windows, linux, darwin, netbsd, openbsd, freebsd package posix -import "core:c" +import "core:c/libc" -when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" -} else { - foreign import lib "system:c" -} +localeconv :: libc.localeconv +setlocale :: libc.setlocale -// locale.h - category macros +lconv :: libc.lconv -foreign lib { - /* - Sets the components of an object with the type lconv with the values appropriate for the - formatting of numeric quantities (monetary and otherwise) according to the rules of the current - locale. - - Returns: a pointer to the lconv structure, might be invalidated by subsequent calls to localeconv() and setlocale() - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localeconv.html ]] - */ - localeconv :: proc() -> ^lconv --- - - /* - Selects the appropriate piece of the global locale, as specified by the category and locale arguments, - and can be used to change or query the entire global locale or portions thereof. - - Returns: the current locale if `locale` is `nil`, the set locale otherwise - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html ]] - */ - @(link_name=LSETLOCALE) - setlocale :: proc(category: Locale_Category, locale: cstring) -> cstring --- -} - -Locale_Category :: enum c.int { - ALL = LC_ALL, - COLLATE = LC_COLLATE, - CTYPE = LC_CTYPE, - MESSAGES = LC_MESSAGES, - MONETARY = LC_MONETARY, - NUMERIC = LC_NUMERIC, - TIME = LC_TIME, -} - -when ODIN_OS == .NetBSD { - @(private) LSETLOCALE :: "__setlocale50" -} else { - @(private) LSETLOCALE :: "setlocale" -} - -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { - - // NOTE: All of these fields are standard ([PSX]). - lconv :: struct { - decimal_point: cstring, - thousand_sep: cstring, - grouping: cstring, - int_curr_symbol: cstring, - currency_symbol: cstring, - mon_decimal_points: cstring, - mon_thousands_sep: cstring, - mon_grouping: cstring, - positive_sign: cstring, - negative_sign: cstring, - int_frac_digits: c.char, - frac_digits: c.char, - p_cs_precedes: c.char, - p_sep_by_space: c.char, - n_cs_precedes: c.char, - n_sep_by_space: c.char, - p_sign_posn: c.char, - n_sign_posn: c.char, - int_p_cs_precedes: c.char, - int_n_cs_precedes: c.char, - int_p_sep_by_space: c.char, - int_n_sep_by_space: c.char, - int_p_sign_posn: c.char, - int_n_sign_posn: c.char, - } - - LC_ALL :: 0 - LC_COLLATE :: 1 - LC_CTYPE :: 2 - LC_MESSAGES :: 6 - LC_MONETARY :: 3 - LC_NUMERIC :: 4 - LC_TIME :: 5 - -} else when ODIN_OS == .Linux { - - // NOTE: All of these fields are standard ([PSX]). - lconv :: struct { - decimal_point: cstring, - thousand_sep: cstring, - grouping: cstring, - int_curr_symbol: cstring, - currency_symbol: cstring, - mon_decimal_points: cstring, - mon_thousands_sep: cstring, - mon_grouping: cstring, - positive_sign: cstring, - negative_sign: cstring, - int_frac_digits: c.char, - frac_digits: c.char, - p_cs_precedes: c.char, - p_sep_by_space: c.char, - n_cs_precedes: c.char, - n_sep_by_space: c.char, - p_sign_posn: c.char, - n_sign_posn: c.char, - int_p_cs_precedes: c.char, - int_n_cs_precedes: c.char, - int_p_sep_by_space: c.char, - int_n_sep_by_space: c.char, - int_p_sign_posn: c.char, - int_n_sign_posn: c.char, - } - - LC_CTYPE :: 0 - LC_NUMERIC :: 1 - LC_TIME :: 2 - LC_COLLATE :: 3 - LC_MONETARY :: 4 - LC_MESSAGES :: 5 - LC_ALL :: 6 - -} else { - #panic("posix is unimplemented for the current target") -} +Locale_Category :: libc.Locale_Category diff --git a/core/sys/posix/monetary.odin b/core/sys/posix/monetary.odin index b4f0c31ee..ee342e211 100644 --- a/core/sys/posix/monetary.odin +++ b/core/sys/posix/monetary.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" diff --git a/core/sys/posix/net_if.odin b/core/sys/posix/net_if.odin index 75c1a863e..774d11b72 100644 --- a/core/sys/posix/net_if.odin +++ b/core/sys/posix/net_if.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -55,6 +56,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS IF_NAMESIZE :: 16 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/netdb.odin b/core/sys/posix/netdb.odin index f31de2c2b..79e13a140 100644 --- a/core/sys/posix/netdb.odin +++ b/core/sys/posix/netdb.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -472,6 +473,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS EAI_OVERFLOW :: 14 } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/netinet_in.odin b/core/sys/posix/netinet_in.odin index 22bfde9bc..a2cf904ce 100644 --- a/core/sys/posix/netinet_in.odin +++ b/core/sys/posix/netinet_in.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -61,6 +62,11 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS sin6_scope_id: c.uint32_t, /* [PSX] set of interfaces for a scope */ } + ipv6_mreq :: struct { + ipv6mr_multiaddr: in6_addr, /* [PSX] IPv6 multicast address */ + ipv6mr_interface: c.uint, /* [PSX] interface index */ + } + IPV6_MULTICAST_IF :: 17 IPV6_UNICAST_HOPS :: 16 IPV6_MULTICAST_HOPS :: 18 @@ -223,6 +229,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS ) } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/netinet_tcp.odin b/core/sys/posix/netinet_tcp.odin index 284351732..b1da12f5e 100644 --- a/core/sys/posix/netinet_tcp.odin +++ b/core/sys/posix/netinet_tcp.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix // netinet/tcp.h - definitions for the Internet Transmission Control Protocol (TCP) @@ -6,6 +7,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS TCP_NODELAY :: 0x01 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/poll.odin b/core/sys/posix/poll.odin index 9e38afe12..9c3b8b081 100644 --- a/core/sys/posix/poll.odin +++ b/core/sys/posix/poll.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "base:intrinsics" @@ -92,7 +93,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS POLLHUP :: 0x0010 POLLNVAL :: 0x0020 -} else { - #panic("posix is unimplemented for the current target") } - diff --git a/core/sys/posix/posix.odin b/core/sys/posix/posix.odin index 44486e424..d56217407 100644 --- a/core/sys/posix/posix.odin +++ b/core/sys/posix/posix.odin @@ -11,6 +11,9 @@ The struct fields that are cross-platform are documented with `[PSX]`. Accessing these fields on one target should be the same on others. Other fields are implementation specific. +The parts of POSIX that Windows implements are also supported here, but +other symbols are undefined on Windows targets. + Most macros have been reimplemented in Odin with inlined functions. Unimplemented headers: diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin index 76acb1a3d..490064da6 100644 --- a/core/sys/posix/pthread.odin +++ b/core/sys/posix/pthread.odin @@ -1,10 +1,11 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" when ODIN_OS == .Darwin { foreign import lib "system:System.framework" -} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Linux { foreign import lib "system:pthread" } else { foreign import lib "system:c" @@ -354,12 +355,16 @@ Thread_Scope :: enum c.int { } Cancel_State :: enum c.int { + // Cancel takes place at next cancellation point. ENABLE = PTHREAD_CANCEL_ENABLE, + // Cancel postponed. DISABLE = PTHREAD_CANCEL_DISABLE, } Cancel_Type :: enum c.int { + // Cancel waits until cancellation point. DEFERRED = PTHREAD_CANCEL_DEFERRED, + // Cancel occurs immediately. ASYNCHRONOUS = PTHREAD_CANCEL_ASYNCHRONOUS, } @@ -371,6 +376,12 @@ when ODIN_OS == .Darwin { PTHREAD_CANCEL_DISABLE :: 0x00 PTHREAD_CANCEL_ENABLE :: 0x01 + // PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + // PTHREAD_CANCEL_DEFERRED :: 0 + // + // PTHREAD_CANCEL_DISABLE :: 1 + // PTHREAD_CANCEL_ENABLE :: 0 + PTHREAD_CANCELED :: rawptr(uintptr(1)) PTHREAD_CREATE_DETACHED :: 2 @@ -398,6 +409,16 @@ when ODIN_OS == .Darwin { pthread_key_t :: distinct c.ulong + pthread_mutex_t :: struct { + __sig: c.long, + __opaque: [56]c.char, + } + + pthread_cond_t :: struct { + __sig: c.long, + __opaque: [40]c.char, + } + sched_param :: struct { sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ _: [4]c.char, @@ -423,18 +444,28 @@ when ODIN_OS == .Darwin { PTHREAD_PRIO_NONE :: 0 PTHREAD_PRIO_PROTECT :: 2 - PTHREAD_PROCESS_SHARED :: 0 - PTHREAD_PROCESS_PRIVATE :: 1 + PTHREAD_PROCESS_SHARED :: 1 + PTHREAD_PROCESS_PRIVATE :: 0 PTHREAD_SCOPE_PROCESS :: 0 PTHREAD_SCOPE_SYSTEM :: 2 pthread_t :: distinct u64 - pthread_attr_t :: distinct rawptr + pthread_attr_t :: struct #align(8) { + _: [8]byte, + } pthread_key_t :: distinct c.int + pthread_mutex_t :: struct #align(8) { + _: [8]byte, + } + + pthread_cond_t :: struct #align(8) { + _: [8]byte, + } + sched_param :: struct { sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ } @@ -475,6 +506,14 @@ when ODIN_OS == .Darwin { pthread_key_t :: distinct c.int + pthread_cond_t :: struct #align(8) { + _: [40]byte, + } + + pthread_mutex_t :: struct #align(8) { + _: [48]byte, + } + sched_param :: struct { sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ } @@ -505,9 +544,11 @@ when ODIN_OS == .Darwin { PTHREAD_SCOPE_PROCESS :: 0 PTHREAD_SCOPE_SYSTEM :: 0x2 - pthread_t :: distinct rawptr - pthread_attr_t :: distinct rawptr - pthread_key_t :: distinct c.int + pthread_t :: distinct rawptr + pthread_attr_t :: distinct rawptr + pthread_key_t :: distinct c.int + pthread_mutex_t :: distinct rawptr + pthread_cond_t :: distinct rawptr sched_param :: struct { sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ @@ -548,6 +589,16 @@ when ODIN_OS == .Darwin { pthread_key_t :: distinct c.uint + pthread_cond_t :: struct { + __size: [40]c.char, // NOTE: may be smaller depending on libc or arch, but never larger. + __align: c.long, + } + + pthread_mutex_t :: struct { + __size: [32]c.char, // NOTE: may be smaller depending on libc or arch, but never larger. + __align: c.long, + } + sched_param :: struct { sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ @@ -557,6 +608,4 @@ when ODIN_OS == .Darwin { __reserved3: c.int, } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/pwd.odin b/core/sys/posix/pwd.odin index 546d58309..33cbcd7c5 100644 --- a/core/sys/posix/pwd.odin +++ b/core/sys/posix/pwd.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -163,6 +164,16 @@ when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { pw_fields: c.int, } -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Linux { + + passwd :: struct { + pw_name: cstring, /* [PSX] user name */ + pw_passwd: cstring, /* encrypted password */ + pw_uid: uid_t, /* [PSX] user uid */ + pw_gid: gid_t, /* [PSX] user gid */ + pw_gecos: cstring, /* Real name. */ + pw_dir: cstring, /* Home directory. */ + pw_shell: cstring, /* Shell program. */ + } + } diff --git a/core/sys/posix/sched.odin b/core/sys/posix/sched.odin index 3923257aa..e91178b09 100644 --- a/core/sys/posix/sched.odin +++ b/core/sys/posix/sched.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -100,6 +101,4 @@ when ODIN_OS == .Darwin { SCHED_FIFO :: 1 SCHED_RR :: 2 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/setjmp.odin b/core/sys/posix/setjmp.odin index cb1dad184..926dbd3ad 100644 --- a/core/sys/posix/setjmp.odin +++ b/core/sys/posix/setjmp.odin @@ -1,7 +1,7 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" -import "core:c/libc" when ODIN_OS == .Darwin { foreign import lib "system:System.framework" @@ -43,12 +43,8 @@ foreign lib { sigsetjmp :: proc(env: ^sigjmp_buf, savemask: b32) -> c.int --- } -jmp_buf :: libc.jmp_buf sigjmp_buf :: distinct jmp_buf -longjmp :: libc.longjmp -setjmp :: libc.setjmp - when ODIN_OS == .NetBSD { @(private) LSIGSETJMP :: "__sigsetjmp14" @(private) LSIGLONGJMP :: "__siglongjmp14" diff --git a/core/sys/posix/setjmp_libc.odin b/core/sys/posix/setjmp_libc.odin new file mode 100644 index 000000000..a69dff09f --- /dev/null +++ b/core/sys/posix/setjmp_libc.odin @@ -0,0 +1,11 @@ +#+build windows, linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c/libc" + +// setjmp.h - stack environment declarations + +jmp_buf :: libc.jmp_buf + +longjmp :: libc.longjmp +setjmp :: libc.setjmp diff --git a/core/sys/posix/signal.odin b/core/sys/posix/signal.odin index 1e3f05104..4ba4e9943 100644 --- a/core/sys/posix/signal.odin +++ b/core/sys/posix/signal.odin @@ -1,9 +1,9 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "base:intrinsics" import "core:c" -import "core:c/libc" when ODIN_OS == .Darwin { foreign import lib "system:System.framework" @@ -14,31 +14,6 @@ when ODIN_OS == .Darwin { // signal.h - signals foreign lib { - // LIBC: - - /* - Set a signal handler. - - func can either be: - - `auto_cast posix.SIG_DFL` setting the default handler for that specific signal - - `auto_cast posix.SIG_IGN` causing the specific signal to be ignored - - a custom signal handler - - Returns: SIG_ERR (setting errno), the last value of func on success - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html ]] - */ - signal :: proc(sig: Signal, func: proc "c" (Signal)) -> proc "c" (Signal) --- - - /* - Raises a signal, calling its handler and then returning. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/raise.html ]] - */ - raise :: proc(sig: Signal) -> result --- - - // POSIX: - /* Raise a signal to the process/group specified by pid. @@ -227,72 +202,6 @@ sigval :: struct #raw_union { sigval_ptr: rawptr, /* [PSX] pointer signal value */ } -Signal :: enum c.int { - NONE, - - // LIBC: - - // Process abort signal. - SIGABRT = SIGABRT, - // Erronous arithemtic operation. - SIGFPE = SIGFPE, - // Illegal instruction. - SIGILL = SIGILL, - // Terminal interrupt signal. - SIGINT = SIGINT, - // Invalid memory reference. - SIGSEGV = SIGSEGV, - // Termination signal. - SIGTERM = SIGTERM, - - // POSIX: - - // Process abort signal. - SIGALRM = SIGALRM, - // Access to an undefined portion of a memory object. - SIGBUS = SIGBUS, - // Child process terminated, stopped, or continued. - SIGCHLD = SIGCHLD, - // Continue execution, if stopped. - SIGCONT = SIGCONT, - // Hangup. - SIGHUP = SIGHUP, - // Kill (cannot be caught or ignored). - SIGKILL = SIGKILL, - // Write on a pipe with no one to read it. - SIGPIPE = SIGPIPE, - // Terminal quit signal. - SIGQUIT = SIGQUIT, - // Stop executing (cannot be caught or ignored). - SIGSTOP = SIGSTOP, - // Terminal stop process. - SIGTSTP = SIGTSTP, - // Background process attempting read. - SIGTTIN = SIGTTIN, - // Background process attempting write. - SIGTTOU = SIGTTOU, - // User-defined signal 1. - SIGUSR1 = SIGUSR1, - // User-defined signal 2. - SIGUSR2 = SIGUSR2, - // Pollable event. - SIGPOLL = SIGPOLL, - // Profiling timer expired. - SIGPROF = SIGPROF, - // Bad system call. - SIGSYS = SIGSYS, - // Trace/breakpoint trap. - SIGTRAP = SIGTRAP, - // High bandwidth data is available at a socket. - SIGURG = SIGURG, - // Virtual timer expired. - SIGVTALRM = SIGVTALRM, - // CPU time limit exceeded. - SIGXCPU = SIGXCPU, - // File size limit exceeded. - SIGXFSZ = SIGXFSZ, -} - ILL_Code :: enum c.int { // Illegal opcode. ILLOPC = ILL_ILLOPC, @@ -434,20 +343,6 @@ Sig :: enum c.int { SETMASK = SIG_SETMASK, } -// Request for default signal handling. -SIG_DFL :: libc.SIG_DFL -// Return value from signal() in case of error. -SIG_ERR :: libc.SIG_ERR -// Request that signal be ignored. -SIG_IGN :: libc.SIG_IGN - -SIGABRT :: libc.SIGABRT -SIGFPE :: libc.SIGFPE -SIGILL :: libc.SIGILL -SIGINT :: libc.SIGINT -SIGSEGV :: libc.SIGSEGV -SIGTERM :: libc.SIGTERM - when ODIN_OS == .NetBSD { @(private) LSIGPROCMASK :: "__sigprocmask14" @(private) LSIGACTION :: "__sigaction_siginfo" @@ -1118,7 +1013,7 @@ when ODIN_OS == .Darwin { uid_t :: distinct c.uint32_t sigset_t :: struct { - [1024/(8 * size_of(c.ulong))]val, + __val: [1024/(8 * size_of(c.ulong))]c.ulong, } SIGHUP :: 1 @@ -1285,6 +1180,4 @@ when ODIN_OS == .Darwin { SI_TIMER :: -2 SI_MESGQ :: -3 SI_ASYNCIO :: -4 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/signal_libc.odin b/core/sys/posix/signal_libc.odin new file mode 100644 index 000000000..aef22da29 --- /dev/null +++ b/core/sys/posix/signal_libc.odin @@ -0,0 +1,145 @@ +#+build linux, windows, darwin, netbsd, openbsd, freebsd +package posix + +import "base:intrinsics" + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// signal.h - signals + +foreign lib { + /* + Set a signal handler. + + func can either be: + - `auto_cast posix.SIG_DFL` setting the default handler for that specific signal + - `auto_cast posix.SIG_IGN` causing the specific signal to be ignored + - a custom signal handler + + Returns: SIG_ERR (setting errno), the last value of func on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html ]] + */ + signal :: proc(sig: Signal, func: proc "c" (Signal)) -> proc "c" (Signal) --- + + /* + Raises a signal, calling its handler and then returning. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/raise.html ]] + */ + raise :: proc(sig: Signal) -> result --- +} + +Signal :: enum c.int { + NONE, + + // LIBC: + + // Process abort signal. + SIGABRT = SIGABRT, + // Erronous arithemtic operation. + SIGFPE = SIGFPE, + // Illegal instruction. + SIGILL = SIGILL, + // Terminal interrupt signal. + SIGINT = SIGINT, + // Invalid memory reference. + SIGSEGV = SIGSEGV, + // Termination signal. + SIGTERM = SIGTERM, + + // POSIX: + + // Process abort signal. + SIGALRM = SIGALRM, + // Access to an undefined portion of a memory object. + SIGBUS = SIGBUS, + // Child process terminated, stopped, or continued. + SIGCHLD = SIGCHLD, + // Continue execution, if stopped. + SIGCONT = SIGCONT, + // Hangup. + SIGHUP = SIGHUP, + // Kill (cannot be caught or ignored). + SIGKILL = SIGKILL, + // Write on a pipe with no one to read it. + SIGPIPE = SIGPIPE, + // Terminal quit signal. + SIGQUIT = SIGQUIT, + // Stop executing (cannot be caught or ignored). + SIGSTOP = SIGSTOP, + // Terminal stop process. + SIGTSTP = SIGTSTP, + // Background process attempting read. + SIGTTIN = SIGTTIN, + // Background process attempting write. + SIGTTOU = SIGTTOU, + // User-defined signal 1. + SIGUSR1 = SIGUSR1, + // User-defined signal 2. + SIGUSR2 = SIGUSR2, + // Pollable event. + SIGPOLL = SIGPOLL, + // Profiling timer expired. + SIGPROF = SIGPROF, + // Bad system call. + SIGSYS = SIGSYS, + // Trace/breakpoint trap. + SIGTRAP = SIGTRAP, + // High bandwidth data is available at a socket. + SIGURG = SIGURG, + // Virtual timer expired. + SIGVTALRM = SIGVTALRM, + // CPU time limit exceeded. + SIGXCPU = SIGXCPU, + // File size limit exceeded. + SIGXFSZ = SIGXFSZ, +} + +// Request for default signal handling. +SIG_DFL :: libc.SIG_DFL +// Return value from signal() in case of error. +SIG_ERR :: libc.SIG_ERR +// Request that signal be ignored. +SIG_IGN :: libc.SIG_IGN + +SIGABRT :: libc.SIGABRT +SIGFPE :: libc.SIGFPE +SIGILL :: libc.SIGILL +SIGINT :: libc.SIGINT +SIGSEGV :: libc.SIGSEGV +SIGTERM :: libc.SIGTERM + +when ODIN_OS == .Windows { + SIGALRM :: -1 + SIGBUS :: -1 + SIGCHLD :: -1 + SIGCONT :: -1 + SIGHUP :: -1 + SIGKILL :: -1 + SIGPIPE :: -1 + SIGQUIT :: -1 + SIGSTOP :: -1 + SIGTSTP :: -1 + SIGTTIN :: -1 + SIGTTOU :: -1 + SIGUSR1 :: -1 + SIGUSR2 :: -1 + SIGPOLL :: -1 + SIGPROF :: -1 + SIGSYS :: -1 + SIGTRAP :: -1 + SIGURG :: -1 + SIGVTALRM :: -1 + SIGXCPU :: -1 + SIGXFSZ :: -1 +} diff --git a/core/sys/posix/stdio.odin b/core/sys/posix/stdio.odin index de716f8d7..24464dfd8 100644 --- a/core/sys/posix/stdio.odin +++ b/core/sys/posix/stdio.odin @@ -1,7 +1,7 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" -import "core:c/libc" when ODIN_OS == .Darwin { foreign import lib "system:System.framework" @@ -32,16 +32,6 @@ foreign lib { */ dprintf :: proc(fildse: FD, format: cstring, #c_vararg args: ..any) -> c.int --- - /* - Equivalent to fprintf but output is written to s, it is the user's responsibility to - ensure there is enough space. - - Return: number of bytes written, negative (setting errno) on failure - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dprintf.html ]] - */ - sprintf :: proc(s: [^]byte, format: cstring, #c_vararg args: ..any) -> c.int --- - /* Associate a stream with a file descriptor. @@ -115,34 +105,6 @@ foreign lib { */ open_memstream :: proc(bufp: ^[^]byte, sizep: ^c.size_t) -> ^FILE --- - /* - Equivalent to getc but unaffected by locks. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] - */ - getc_unlocked :: proc(stream: ^FILE) -> c.int --- - - /* - Equivalent to getchar but unaffected by locks. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] - */ - getchar_unlocked :: proc() -> c.int --- - - /* - Equivalent to putc but unaffected by locks. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] - */ - putc_unlocked :: proc(ch: c.int, stream: ^FILE) -> c.int --- - - /* - Equivalent to putchar but unaffected by locks. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] - */ - putchar_unlocked :: proc(ch: c.int) -> c.int --- - /* Read a delimited record from the stream. @@ -181,60 +143,6 @@ foreign lib { */ getline :: proc(lineptr: ^cstring, n: ^c.size_t, stream: ^FILE) -> c.ssize_t --- - /* - Get a string from the stdin stream. - - It is up to the user to make sure s is big enough. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gets.html ]] - */ - gets :: proc(s: [^]byte) -> cstring --- - - /* - Create a name for a temporary file. - - Returns: an allocated cstring that needs to be freed, nil on failure - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tempnam.html ]] - */ - tempnam :: proc(dir: cstring, pfx: cstring) -> cstring --- - - /* - Executes the command specified, creating a pipe and returning a pointer to a stream that can - read or write from/to the pipe. - - Returns: nil (setting errno) on failure or a pointer to the stream - - Example: - fp := posix.popen("ls *", "r") - if fp == nil { - /* Handle error */ - } - - path: [1024]byte - for posix.fgets(raw_data(path[:]), len(path), fp) != nil { - posix.printf("%s", &path) - } - - status := posix.pclose(fp) - if status == -1 { - /* Error reported by pclose() */ - } else { - /* Use functions described under wait() to inspect `status` in order - to determine success/failure of the command executed by popen() */ - } - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/popen.html ]] - */ - popen :: proc(command: cstring, mode: cstring) -> ^FILE --- - - /* - Closes a pipe stream to or from a process. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pclose.html ]] - */ - pclose :: proc(stream: ^FILE) -> c.int --- - /* Equivalent to rename but relative directories are resolved from their respective fds. @@ -243,76 +151,6 @@ foreign lib { renameat :: proc(oldfd: FD, old: cstring, newfd: FD, new: cstring) -> result --- } -clearerr :: libc.clearerr -fclose :: libc.fclose -feof :: libc.feof -ferror :: libc.ferror -fflush :: libc.fflush -fgetc :: libc.fgetc -fgetpos :: libc.fgetpos -fgets :: libc.fgets -fopen :: libc.fopen -fprintf :: libc.fprintf -fputc :: libc.fputc -fread :: libc.fread -freopen :: libc.freopen -fscanf :: libc.fscanf -fseek :: libc.fseek -fsetpos :: libc.fsetpos -ftell :: libc.ftell -fwrite :: libc.fwrite -getc :: libc.getc -getchar :: libc.getchar -perror :: libc.perror -printf :: libc.printf -putc :: libc.puts -putchar :: libc.putchar -puts :: libc.puts -remove :: libc.remove -rename :: libc.rename -rewind :: libc.rewind -scanf :: libc.scanf -setbuf :: libc.setbuf -setvbuf :: libc.setvbuf -snprintf :: libc.snprintf -sscanf :: libc.sscanf -tmpfile :: libc.tmpfile -tmpnam :: libc.tmpnam -vfprintf :: libc.vfprintf -vfscanf :: libc.vfscanf -vprintf :: libc.vprintf -vscanf :: libc.vscanf -vsnprintf :: libc.vsnprintf -vsprintf :: libc.vsprintf -vsscanf :: libc.vsscanf -ungetc :: libc.ungetc - -to_stream :: libc.to_stream - -Whence :: libc.Whence -FILE :: libc.FILE -fpos_t :: libc.fpos_t - -BUFSIZ :: libc.BUFSIZ - -_IOFBF :: libc._IOFBF -_IOLBF :: libc._IOLBF -_IONBF :: libc._IONBF - -SEEK_CUR :: libc.SEEK_CUR -SEEK_END :: libc.SEEK_END -SEEK_SET :: libc.SEEK_SET - -FILENAME_MAX :: libc.FILENAME_MAX -FOPEN_MAX :: libc.FOPEN_MAX -TMP_MAX :: libc.TMP_MAX - -EOF :: libc.EOF - -stderr := libc.stderr -stdin := libc.stdin -stdout := libc.stdout - when ODIN_OS == .Darwin { L_ctermid :: 1024 @@ -327,6 +165,11 @@ when ODIN_OS == .Darwin { P_tmpdir :: "/tmp/" -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Linux { + + L_ctermid :: 20 // 20 on musl, 9 on glibc + L_tmpnam :: 20 + + P_tmpdir :: "/tmp/" + } diff --git a/core/sys/posix/stdio_libc.odin b/core/sys/posix/stdio_libc.odin new file mode 100644 index 000000000..fbd949b2c --- /dev/null +++ b/core/sys/posix/stdio_libc.odin @@ -0,0 +1,207 @@ +#+build linux, windows, linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib { + "system:libucrt.lib", + "system:legacy_stdio_definitions.lib", + } +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// stdio.h - standard buffered input/output + +when ODIN_OS == .Windows { + @(private) LGETC_UNLOCKED :: "_getc_nolock" + @(private) LGETCHAR_UNLOCKED :: "_getchar_nolock" + @(private) LPUTC_UNLOCKED :: "_putc_nolock" + @(private) LPUTCHAR_UNLOCKED :: "_putchar_nolock" + @(private) LTEMPNAM :: "_tempnam" + @(private) LPOPEN :: "_popen" + @(private) LPCLOSE :: "_pclose" +} else { + @(private) LGETC_UNLOCKED :: "getc_unlocked" + @(private) LGETCHAR_UNLOCKED :: "getchar_unlocked" + @(private) LPUTC_UNLOCKED :: "putc_unlocked" + @(private) LPUTCHAR_UNLOCKED :: "putchar_unlocked" + @(private) LTEMPNAM :: "tempnam" + @(private) LPOPEN :: "popen" + @(private) LPCLOSE :: "pclose" +} + +foreign lib { + /* + Equivalent to fprintf but output is written to s, it is the user's responsibility to + ensure there is enough space. + + Return: number of bytes written, negative (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dprintf.html ]] + */ + sprintf :: proc(s: [^]byte, format: cstring, #c_vararg args: ..any) -> c.int --- + + /* + Equivalent to getc but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + @(link_name=LGETC_UNLOCKED) + getc_unlocked :: proc(stream: ^FILE) -> c.int --- + + /* + Equivalent to getchar but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + @(link_name=LGETCHAR_UNLOCKED) + getchar_unlocked :: proc() -> c.int --- + + /* + Equivalent to putc but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + @(link_name=LPUTC_UNLOCKED) + putc_unlocked :: proc(ch: c.int, stream: ^FILE) -> c.int --- + + /* + Equivalent to putchar but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + @(link_name=LPUTCHAR_UNLOCKED) + putchar_unlocked :: proc(ch: c.int) -> c.int --- + + /* + Get a string from the stdin stream. + + It is up to the user to make sure s is big enough. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gets.html ]] + */ + gets :: proc(s: [^]byte) -> cstring --- + + /* + Create a name for a temporary file. + + Returns: an allocated cstring that needs to be freed, nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tempnam.html ]] + */ + @(link_name=LTEMPNAM) + tempnam :: proc(dir: cstring, pfx: cstring) -> cstring --- + + /* + Executes the command specified, creating a pipe and returning a pointer to a stream that can + read or write from/to the pipe. + + Returns: nil (setting errno) on failure or a pointer to the stream + + Example: + fp := posix.popen("ls *", "r") + if fp == nil { + /* Handle error */ + } + + path: [1024]byte + for posix.fgets(raw_data(path[:]), len(path), fp) != nil { + posix.printf("%s", &path) + } + + status := posix.pclose(fp) + if status == -1 { + /* Error reported by pclose() */ + } else { + /* Use functions described under wait() to inspect `status` in order + to determine success/failure of the command executed by popen() */ + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/popen.html ]] + */ + @(link_name=LPOPEN) + popen :: proc(command: cstring, mode: cstring) -> ^FILE --- + + /* + Closes a pipe stream to or from a process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pclose.html ]] + */ + @(link_name=LPCLOSE) + pclose :: proc(stream: ^FILE) -> c.int --- +} + +clearerr :: libc.clearerr +fclose :: libc.fclose +feof :: libc.feof +ferror :: libc.ferror +fflush :: libc.fflush +fgetc :: libc.fgetc +fgetpos :: libc.fgetpos +fgets :: libc.fgets +fopen :: libc.fopen +fprintf :: libc.fprintf +fputc :: libc.fputc +fread :: libc.fread +freopen :: libc.freopen +fscanf :: libc.fscanf +fseek :: libc.fseek +fsetpos :: libc.fsetpos +ftell :: libc.ftell +fwrite :: libc.fwrite +getc :: libc.getc +getchar :: libc.getchar +perror :: libc.perror +printf :: libc.printf +putc :: libc.puts +putchar :: libc.putchar +puts :: libc.puts +remove :: libc.remove +rename :: libc.rename +rewind :: libc.rewind +scanf :: libc.scanf +setbuf :: libc.setbuf +setvbuf :: libc.setvbuf +snprintf :: libc.snprintf +sscanf :: libc.sscanf +tmpfile :: libc.tmpfile +tmpnam :: libc.tmpnam +vfprintf :: libc.vfprintf +vfscanf :: libc.vfscanf +vprintf :: libc.vprintf +vscanf :: libc.vscanf +vsnprintf :: libc.vsnprintf +vsprintf :: libc.vsprintf +vsscanf :: libc.vsscanf +ungetc :: libc.ungetc + +to_stream :: libc.to_stream + +Whence :: libc.Whence +FILE :: libc.FILE +fpos_t :: libc.fpos_t + +BUFSIZ :: libc.BUFSIZ + +_IOFBF :: libc._IOFBF +_IOLBF :: libc._IOLBF +_IONBF :: libc._IONBF + +SEEK_CUR :: libc.SEEK_CUR +SEEK_END :: libc.SEEK_END +SEEK_SET :: libc.SEEK_SET + +FILENAME_MAX :: libc.FILENAME_MAX +FOPEN_MAX :: libc.FOPEN_MAX +TMP_MAX :: libc.TMP_MAX + +EOF :: libc.EOF + +stderr := libc.stderr +stdin := libc.stdin +stdout := libc.stdout diff --git a/core/sys/posix/stdlib.odin b/core/sys/posix/stdlib.odin index a1e2eab50..640c70b5a 100644 --- a/core/sys/posix/stdlib.odin +++ b/core/sys/posix/stdlib.odin @@ -1,9 +1,9 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "base:intrinsics" import "core:c" -import "core:c/libc" when ODIN_OS == .Darwin { foreign import lib "system:System.framework" @@ -11,56 +11,6 @@ when ODIN_OS == .Darwin { foreign import lib "system:c" } -// stdlib.h - standard library definitions - -atof :: libc.atof -atoi :: libc.atoi -atol :: libc.atol -atoll :: libc.atoll -strtod :: libc.strtod -strtof :: libc.strtof -strtol :: libc.strtol -strtoll :: libc.strtoll -strtoul :: libc.strtoul -strtoull :: libc.strtoull - -rand :: libc.rand -srand :: libc.srand - -calloc :: libc.calloc -malloc :: libc.malloc -realloc :: libc.realloc - -abort :: libc.abort -atexit :: libc.atexit -at_quick_exit :: libc.at_quick_exit -exit :: libc.exit -_Exit :: libc._Exit -getenv :: libc.getenv -quick_exit :: libc.quick_exit -system :: libc.system - -bsearch :: libc.bsearch -qsort :: libc.qsort - -abs :: libc.abs -labs :: libc.labs -llabs :: libc.llabs -div :: libc.div -ldiv :: libc.ldiv -lldiv :: libc.lldiv - -mblen :: libc.mblen -mbtowc :: libc.mbtowc -wctomb :: libc.wctomb - -mbstowcs :: libc.mbstowcs -wcstombs :: libc.wcstombs - -free :: #force_inline proc(ptr: $T) where intrinsics.type_is_pointer(T) || intrinsics.type_is_multi_pointer(T) || T == cstring { - libc.free(rawptr(ptr)) -} - foreign lib { /* Takes a pointer to a radix-64 representation, in which the first digit is the least significant, @@ -342,21 +292,6 @@ foreign lib { */ unlockpt :: proc(fildes: FD) -> result --- - /* - Uses the string argument to set environment variable values. - - Returns: 0 on success, non-zero (setting errno) on failure - - Example: - if posix.putenv("HOME=/usr/home") != 0 { - fmt.panicf("putenv failure: %v", posix.strerror(posix.errno())) - } - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/putenv.html ]] - */ - @(link_name=LPUTENV) - putenv :: proc(string: cstring) -> c.int --- - /* Updates or add a variable in the environment of the calling process. @@ -427,23 +362,11 @@ foreign lib { setkey :: proc(key: [^]byte) --- } -EXIT_FAILURE :: libc.EXIT_FAILURE -EXIT_SUCCESS :: libc.EXIT_SUCCESS - -RAND_MAX :: libc.RAND_MAX -MB_CUR_MAX :: libc.MB_CUR_MAX - -div_t :: libc.div_t -ldiv_t :: libc.ldiv_t -lldiv_t :: libc.lldiv_t - when ODIN_OS == .NetBSD { - @(private) LPUTENV :: "__putenv50" @(private) LINITSTATE :: "__initstate60" @(private) LSRANDOM :: "__srandom60" @(private) LUNSETENV :: "__unsetenv13" } else { - @(private) LPUTENV :: "putenv" @(private) LINITSTATE :: "initstate" @(private) LSRANDOM :: "srandom" @(private) LUNSETENV :: "unsetenv" diff --git a/core/sys/posix/stdlib_libc.odin b/core/sys/posix/stdlib_libc.odin new file mode 100644 index 000000000..fa4d925b2 --- /dev/null +++ b/core/sys/posix/stdlib_libc.odin @@ -0,0 +1,101 @@ +#+build linux, windows, darwin, netbsd, openbsd, freebsd +package posix + +import "base:intrinsics" + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// stdlib.h - standard library definitions + +atof :: libc.atof +atoi :: libc.atoi +atol :: libc.atol +atoll :: libc.atoll +strtod :: libc.strtod +strtof :: libc.strtof +strtol :: libc.strtol +strtoll :: libc.strtoll +strtoul :: libc.strtoul +strtoull :: libc.strtoull + +rand :: libc.rand +srand :: libc.srand + +calloc :: libc.calloc +malloc :: libc.malloc +realloc :: libc.realloc + +abort :: libc.abort +atexit :: libc.atexit +at_quick_exit :: libc.at_quick_exit +exit :: libc.exit +_Exit :: libc._Exit +getenv :: libc.getenv +quick_exit :: libc.quick_exit +system :: libc.system + +bsearch :: libc.bsearch +qsort :: libc.qsort + +abs :: libc.abs +labs :: libc.labs +llabs :: libc.llabs +div :: libc.div +ldiv :: libc.ldiv +lldiv :: libc.lldiv + +mblen :: libc.mblen +mbtowc :: libc.mbtowc +wctomb :: libc.wctomb + +mbstowcs :: libc.mbstowcs +wcstombs :: libc.wcstombs + +free :: #force_inline proc(ptr: $T) where intrinsics.type_is_pointer(T) || intrinsics.type_is_multi_pointer(T) || T == cstring { + libc.free(rawptr(ptr)) +} + +foreign lib { + + /* + Uses the string argument to set environment variable values. + + Returns: 0 on success, non-zero (setting errno) on failure + + Example: + if posix.putenv("HOME=/usr/home") != 0 { + fmt.panicf("putenv failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/putenv.html ]] + */ + @(link_name=LPUTENV) + putenv :: proc(string: cstring) -> c.int --- +} + +EXIT_FAILURE :: libc.EXIT_FAILURE +EXIT_SUCCESS :: libc.EXIT_SUCCESS + +RAND_MAX :: libc.RAND_MAX +MB_CUR_MAX :: libc.MB_CUR_MAX + +div_t :: libc.div_t +ldiv_t :: libc.ldiv_t +lldiv_t :: libc.lldiv_t + +when ODIN_OS == .Windows { + @(private) LPUTENV :: "_putenv" +} else when ODIN_OS == .NetBSD { + @(private) LPUTENV :: "__putenv50" +} else { + @(private) LPUTENV :: "putenv" +} diff --git a/core/sys/posix/string.odin b/core/sys/posix/string.odin index d22f49a96..96b6a9007 100644 --- a/core/sys/posix/string.odin +++ b/core/sys/posix/string.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -13,16 +14,6 @@ when ODIN_OS == .Darwin { // NOTE: most of the symbols in this header are not useful in Odin and have been left out. foreign lib { - /* - Map the error number to a locale-dependent error message string. - - Returns: a string that may be invalidated by subsequent calls - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror.html ]] - */ - @(link_name="strerror") - _strerror :: proc(errnum: Errno) -> cstring --- - /* Map the error number to a locale-dependent error message string and put it in the buffer. @@ -41,7 +32,3 @@ foreign lib { */ strsignal :: proc(sig: Signal) -> cstring --- } - -strerror :: #force_inline proc "contextless" (errnum: Maybe(Errno) = nil) -> cstring { - return _strerror(errnum.? or_else errno()) -} diff --git a/core/sys/posix/string_libc.odin b/core/sys/posix/string_libc.odin new file mode 100644 index 000000000..336352cbc --- /dev/null +++ b/core/sys/posix/string_libc.odin @@ -0,0 +1,30 @@ +#+build linux, windows, darwin, netbsd, openbsd, freebsd +package posix + +when ODIN_OS == .Windows { + foreign import lib "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// string.h - string operations + +// NOTE: most of the symbols in this header are not useful in Odin and have been left out. + +foreign lib { + /* + Map the error number to a locale-dependent error message string. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror.html ]] + */ + @(link_name="strerror") + _strerror :: proc(errnum: Errno) -> cstring --- +} + +strerror :: #force_inline proc "contextless" (errnum: Maybe(Errno) = nil) -> cstring { + return _strerror(errnum.? or_else errno()) +} diff --git a/core/sys/posix/sys_ipc.odin b/core/sys/posix/sys_ipc.odin index 33f8fa259..0f7ec06c5 100644 --- a/core/sys/posix/sys_ipc.odin +++ b/core/sys/posix/sys_ipc.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -110,6 +111,4 @@ when ODIN_OS == .Darwin { IPC_SET :: 1 IPC_STAT :: 2 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_mman.odin b/core/sys/posix/sys_mman.odin index 2f4eb566b..0594672ae 100644 --- a/core/sys/posix/sys_mman.odin +++ b/core/sys/posix/sys_mman.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -115,12 +116,14 @@ Prot_Flag_Bits :: enum c.int { Prot_Flags :: bit_set[Prot_Flag_Bits; c.int] Map_Flag_Bits :: enum c.int { + // Map anonymous memory. + ANONYMOUS = log2(MAP_ANONYMOUS), // Interpret addr exactly. - FIXED = log2(MAP_FIXED), + FIXED = log2(MAP_FIXED), // Changes are private. - PRIVATE = log2(MAP_PRIVATE), + PRIVATE = log2(MAP_PRIVATE), // Changes are shared. - SHARED = log2(MAP_SHARED), + SHARED = log2(MAP_SHARED), } Map_Flags :: bit_set[Map_Flag_Bits; c.int] @@ -170,9 +173,10 @@ when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS PROT_READ :: 0x01 PROT_WRITE :: 0x02 - MAP_FIXED :: 0x0010 - MAP_PRIVATE :: 0x0002 - MAP_SHARED :: 0x0001 + MAP_FIXED :: 0x0010 + MAP_PRIVATE :: 0x0002 + MAP_SHARED :: 0x0001 + MAP_ANONYMOUS :: 0x0020 when ODIN_OS == .Linux else 0x1000 when ODIN_OS == .Darwin || ODIN_OS == .Linux { MS_INVALIDATE :: 0x0002 @@ -206,9 +210,10 @@ when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS PROT_READ :: 0x01 PROT_WRITE :: 0x02 - MAP_FIXED :: 0x0010 - MAP_PRIVATE :: 0x0002 - MAP_SHARED :: 0x0001 + MAP_FIXED :: 0x0010 + MAP_PRIVATE :: 0x0002 + MAP_SHARED :: 0x0001 + MAP_ANONYMOUS :: 0x1000 MS_ASYNC :: 0x0001 MS_INVALIDATE :: 0x0002 @@ -225,6 +230,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS POSIX_MADV_SEQUENTIAL :: 2 POSIX_MADV_WILLNEED :: 3 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_msg.odin b/core/sys/posix/sys_msg.odin index 98b76c3a5..0e78777f9 100644 --- a/core/sys/posix/sys_msg.odin +++ b/core/sys/posix/sys_msg.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -150,6 +151,24 @@ when ODIN_OS == .Darwin { msg_pad4: [4]c.long, } -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Linux { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_ctime: time_t, /* [PSX] time of last change */ + msg_cbytes: c.ulong, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + __unused: [2]c.ulong, + } + } diff --git a/core/sys/posix/sys_resource.odin b/core/sys/posix/sys_resource.odin index 55789ee95..9af2a929b 100644 --- a/core/sys/posix/sys_resource.odin +++ b/core/sys/posix/sys_resource.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -154,6 +155,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS RLIMIT_AS :: 10 } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_select.odin b/core/sys/posix/sys_select.odin index c20636b21..2058ee777 100644 --- a/core/sys/posix/sys_select.odin +++ b/core/sys/posix/sys_select.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "base:intrinsics" @@ -72,7 +73,7 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS // NOTE: this seems correct for FreeBSD but they do use a set backed by the long type themselves (thus the align change). @(private) - ALIGN :: align_of(c.long) when ODIN_OS == .FreeBSD else align_of(c.int32_t) + ALIGN :: align_of(c.long) when ODIN_OS == .FreeBSD || ODIN_OS == .Linux else align_of(c.int32_t) fd_set :: struct #align(ALIGN) { fds_bits: [(FD_SETSIZE / __NFDBITS) when (FD_SETSIZE % __NFDBITS) == 0 else (FD_SETSIZE / __NFDBITS) + 1]c.int32_t, @@ -115,6 +116,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS intrinsics.mem_zero(_p, size_of(fd_set)) } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_sem.odin b/core/sys/posix/sys_sem.odin index 2d7bd1b1e..6b695e766 100644 --- a/core/sys/posix/sys_sem.odin +++ b/core/sys/posix/sys_sem.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -153,6 +154,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS sem_flg: c.short, /* [PSX] operation flags */ } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_shm.odin b/core/sys/posix/sys_shm.odin index ff56ba441..8f3c56b9c 100644 --- a/core/sys/posix/sys_shm.odin +++ b/core/sys/posix/sys_shm.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -137,6 +138,24 @@ when ODIN_OS == .Darwin { _shm_internal: rawptr, } -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Linux { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: 4096 + + shmatt_t :: distinct c.ulong + + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + _: [2]c.ulong, + } } diff --git a/core/sys/posix/sys_socket.odin b/core/sys/posix/sys_socket.odin index e613f0a10..4dd6074a3 100644 --- a/core/sys/posix/sys_socket.odin +++ b/core/sys/posix/sys_socket.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -47,6 +48,12 @@ foreign libc { addr.sun_family = .UNIX copy(addr.sun_path[:], "/somepath\x00") + /* + unlink the socket before binding in case + of previous runs not cleaning up the socket + */ + posix.unlink("/somepath") + if posix.bind(sfd, (^posix.sockaddr)(&addr), size_of(addr)) != .OK { /* Handle error */ } @@ -325,14 +332,16 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS socklen_t :: distinct c.uint - _sa_family_t :: distinct c.uint8_t - when ODIN_OS == .Linux { + _sa_family_t :: distinct c.ushort + sockaddr :: struct { sa_family: sa_family_t, /* [PSX] address family */ sa_data: [14]c.char, /* [PSX] socket address */ } } else { + _sa_family_t :: distinct c.uint8_t + sockaddr :: struct { sa_len: c.uint8_t, /* total length */ sa_family: sa_family_t, /* [PSX] address family */ @@ -560,7 +569,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS SHUT_RDWR :: 2 SHUT_WR :: 1 -} else { - #panic("posix is unimplemented for the current target") } - diff --git a/core/sys/posix/sys_stat.odin b/core/sys/posix/sys_stat.odin index dd66d7d14..61b98ef35 100644 --- a/core/sys/posix/sys_stat.odin +++ b/core/sys/posix/sys_stat.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -280,20 +281,20 @@ when ODIN_OS == .Darwin { ino_t :: distinct c.uint64_t stat_t :: struct { - st_dev: dev_t, /* [XSI] ID of device containing file */ - st_mode: mode_t, /* [XSI] mode of file */ - st_nlink: nlink_t, /* [XSI] number of hard links */ - st_ino: ino_t, /* [XSI] file serial number */ - st_uid: uid_t, /* [XSI] user ID of the file */ - st_gid: gid_t, /* [XSI] group ID of the file */ - st_rdev: dev_t, /* [XSI] device ID */ - st_atim: timespec, /* [XSI] time of last access */ - st_mtim: timespec, /* [XSI] time of last data modification */ - st_ctim: timespec, /* [XSI] time of last status change */ + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_mode: mode_t, /* [PSX] mode of file */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_ino: ino_t, /* [PSX] file serial number */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_rdev: dev_t, /* [PSX] device ID */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ st_birthtimespec: timespec, /* time of file creation(birth) */ - st_size: off_t, /* [XSI] file size, in bytes */ - st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ - st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ st_flags: c.uint32_t, /* user defined flags for file */ st_gen: c.uint32_t, /* file generation number */ st_lspare: c.int32_t, /* RESERVED */ @@ -314,47 +315,47 @@ when ODIN_OS == .Darwin { when ODIN_ARCH == .i386 { stat_t :: struct { - st_dev: dev_t, /* [XSI] ID of device containing file */ - st_ino: ino_t, /* [XSI] file serial number */ - st_nlink: nlink_t, /* [XSI] number of hard links */ - st_mode: mode_t, /* [XSI] mode of file */ + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_mode: mode_t, /* [PSX] mode of file */ st_padding0: c.int16_t, - st_uid: uid_t, /* [XSI] user ID of the file */ - st_gid: gid_t, /* [XSI] group ID of the file */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ st_padding1: c.int32_t, - st_rdev: dev_t, /* [XSI] device ID */ + st_rdev: dev_t, /* [PSX] device ID */ st_atim_ext: c.int32_t, - st_atim: timespec, /* [XSI] time of last access */ + st_atim: timespec, /* [PSX] time of last access */ st_mtim_ext: c.int32_t, - st_mtim: timespec, /* [XSI] time of last data modification */ + st_mtim: timespec, /* [PSX] time of last data modification */ st_ctim_ext: c.int32_t, - st_ctim: timespec, /* [XSI] time of last status change */ + st_ctim: timespec, /* [PSX] time of last status change */ st_birthtimespec: timespec, /* time of file creation(birth) */ - st_size: off_t, /* [XSI] file size, in bytes */ - st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ - st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ st_flags: c.uint32_t, /* user defined flags for file */ st_gen: c.uint64_t, st_spare: [10]c.uint64_t, } } else { stat_t :: struct { - st_dev: dev_t, /* [XSI] ID of device containing file */ - st_ino: ino_t, /* [XSI] file serial number */ - st_nlink: nlink_t, /* [XSI] number of hard links */ - st_mode: mode_t, /* [XSI] mode of file */ + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_mode: mode_t, /* [PSX] mode of file */ st_padding0: c.int16_t, - st_uid: uid_t, /* [XSI] user ID of the file */ - st_gid: gid_t, /* [XSI] group ID of the file */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ st_padding1: c.int32_t, - st_rdev: dev_t, /* [XSI] device ID */ - st_atim: timespec, /* [XSI] time of last access */ - st_mtim: timespec, /* [XSI] time of last data modification */ - st_ctim: timespec, /* [XSI] time of last status change */ + st_rdev: dev_t, /* [PSX] device ID */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ st_birthtimespec: timespec, /* time of file creation(birth) */ - st_size: off_t, /* [XSI] file size, in bytes */ - st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ - st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ st_flags: c.uint32_t, /* user defined flags for file */ st_gen: c.uint64_t, st_spare: [10]c.uint64_t, @@ -374,20 +375,20 @@ when ODIN_OS == .Darwin { ino_t :: distinct c.uint64_t stat_t :: struct { - st_dev: dev_t, /* [XSI] ID of device containing file */ - st_mode: mode_t, /* [XSI] mode of file */ - st_ino: ino_t, /* [XSI] file serial number */ - st_nlink: nlink_t, /* [XSI] number of hard links */ - st_uid: uid_t, /* [XSI] user ID of the file */ - st_gid: gid_t, /* [XSI] group ID of the file */ - st_rdev: dev_t, /* [XSI] device ID */ - st_atim: timespec, /* [XSI] time of last access */ - st_mtim: timespec, /* [XSI] time of last data modification */ - st_ctim: timespec, /* [XSI] time of last status change */ + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_mode: mode_t, /* [PSX] mode of file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_rdev: dev_t, /* [PSX] device ID */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ st_birthtimespec: timespec, /* time of file creation(birth) */ - st_size: off_t, /* [XSI] file size, in bytes */ - st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ - st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ st_flags: c.uint32_t, /* user defined flags for file */ st_gen: c.uint64_t, st_spare: [2]c.uint32_t, @@ -406,19 +407,19 @@ when ODIN_OS == .Darwin { ino_t :: distinct c.uint64_t stat_t :: struct { - st_mode: mode_t, /* [XSI] mode of file */ - st_dev: dev_t, /* [XSI] ID of device containing file */ - st_ino: ino_t, /* [XSI] file serial number */ - st_nlink: nlink_t, /* [XSI] number of hard links */ - st_uid: uid_t, /* [XSI] user ID of the file */ - st_gid: gid_t, /* [XSI] group ID of the file */ - st_rdev: dev_t, /* [XSI] device ID */ - st_atim: timespec, /* [XSI] time of last access */ - st_mtim: timespec, /* [XSI] time of last data modification */ - st_ctim: timespec, /* [XSI] time of last status change */ - st_size: off_t, /* [XSI] file size, in bytes */ - st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ - st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_mode: mode_t, /* [PSX] mode of file */ + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_rdev: dev_t, /* [PSX] device ID */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ st_flags: c.uint32_t, /* user defined flags for file */ st_gen: c.int32_t, st_birthtimespec: timespec, @@ -427,6 +428,58 @@ when ODIN_OS == .Darwin { UTIME_NOW :: -2 UTIME_OMIT :: -1 -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Linux { + + dev_t :: distinct u64 + _mode_t :: distinct c.uint + blkcnt_t :: distinct i64 + + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + nlink_t :: distinct c.uint + blksize_t :: distinct c.int + } else { + nlink_t :: distinct c.size_t + blksize_t :: distinct c.long + } + + ino_t :: distinct u64 + + when ODIN_ARCH == .amd64 { + stat_t :: struct { + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_mode: mode_t, /* [PSX] mode of file */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + _pad0: c.uint, + st_rdev: dev_t, /* [PSX] device ID */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ + __unused: [3]c.long, + } + } else { + stat_t :: struct { + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_mode: mode_t, /* [PSX] mode of file */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_rdev: dev_t, /* [PSX] device ID */ + __pad: c.ulonglong, + st_size: off_t, /* [PSX] file size, in bytes */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ + __pad2: c.int, + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ + __unused: [2]c.uint, + } + } } diff --git a/core/sys/posix/sys_statvfs.odin b/core/sys/posix/sys_statvfs.odin index eb6c16806..47c810135 100644 --- a/core/sys/posix/sys_statvfs.odin +++ b/core/sys/posix/sys_statvfs.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -130,6 +131,27 @@ when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { ST_RDONLY :: 0x00000001 ST_NOSUID :: 0x00000008 -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Linux { + + fsblkcnt_t :: distinct c.uint64_t + + statvfs_t :: struct { + f_bsize: c.ulong, /* [PSX] file system block size */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_fsid: c.ulong, /* [PSX] file system ID */ + _: [2*size_of(c.int)-size_of(c.long)]byte, + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + f_type: c.uint, + __reserved: [5]c.int, + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000002 } diff --git a/core/sys/posix/sys_time.odin b/core/sys/posix/sys_time.odin index fd2e58121..3036352aa 100644 --- a/core/sys/posix/sys_time.odin +++ b/core/sys/posix/sys_time.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -77,6 +78,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS ITIMER_VIRTUAL :: 1 ITIMER_PROF :: 2 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_times.odin b/core/sys/posix/sys_times.odin index d38f3efc4..113e3f963 100644 --- a/core/sys/posix/sys_times.odin +++ b/core/sys/posix/sys_times.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix when ODIN_OS == .Darwin { @@ -33,6 +34,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS tms_cstime: clock_t, /* [PSX] terminated children system CPU time */ } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_uio.odin b/core/sys/posix/sys_uio.odin index 6755fe2ae..a0ad2934e 100644 --- a/core/sys/posix/sys_uio.odin +++ b/core/sys/posix/sys_uio.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -37,6 +38,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS iov_len: c.size_t, /* [PSX] size of the region iov_base points to */ } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_un.odin b/core/sys/posix/sys_un.odin index 15eb7b5fc..ca5c4ee31 100644 --- a/core/sys/posix/sys_un.odin +++ b/core/sys/posix/sys_un.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -19,6 +20,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS sun_path: [108]c.char, /* [PSX] socket pathname */ } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/sys_utsname.odin b/core/sys/posix/sys_utsname.odin index 4786eb4fd..64930160f 100644 --- a/core/sys/posix/sys_utsname.odin +++ b/core/sys/posix/sys_utsname.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -37,15 +38,10 @@ foreign lib { uname :: proc(uname: ^utsname) -> c.int --- } -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { - when ODIN_OS == .Linux { - @(private) - _SYS_NAMELEN :: 65 - } else { - @(private) - _SYS_NAMELEN :: 256 - } + @(private) + _SYS_NAMELEN :: 256 utsname :: struct { sysname: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of OS */ @@ -55,6 +51,17 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS machine: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] hardware type */ } -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Linux { + + @(private) + _SYS_NAMELEN :: 65 + + utsname :: struct { + sysname: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of OS */ + nodename: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of this network node */ + release: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] release level */ + version: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] version level */ + machine: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] hardware type */ + __domainname: [_SYS_NAMELEN]c.char `fmt:"s,0"`, + } } diff --git a/core/sys/posix/sys_wait.odin b/core/sys/posix/sys_wait.odin index e0e2ae21b..812bd8c62 100644 --- a/core/sys/posix/sys_wait.odin +++ b/core/sys/posix/sys_wait.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -391,6 +392,54 @@ when ODIN_OS == .Darwin { return (x & _WCONTINUED) == _WCONTINUED } -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Linux { + + id_t :: distinct c.uint + + WCONTINUED :: 8 + WNOHANG :: 1 + WUNTRACED :: 2 + + WEXITED :: 4 + WNOWAIT :: 0x1000000 + WSTOPPED :: 2 + + _P_ALL :: 0 + _P_PID :: 1 + _P_PGID :: 2 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WTERMSIG(x) == nil + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return (x & 0xff00) >> 8 + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return (x & 0xffff) - 1 < 0xff + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(x & 0x7f) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return ((x & 0xffff) * 0x10001) >> 8 > 0x7f00 + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WEXITSTATUS(x)) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return x == 0xffff + } } diff --git a/core/sys/posix/termios.odin b/core/sys/posix/termios.odin index c73936d58..0c07eceb9 100644 --- a/core/sys/posix/termios.odin +++ b/core/sys/posix/termios.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -152,7 +153,7 @@ CControl_Flag_Bits :: enum tcflag_t { CControl_Flags :: bit_set[CControl_Flag_Bits; tcflag_t] // character size mask -CSIZE :: CControl_Flags{ .CS6, .CS7, .CS8 } +CSIZE :: transmute(CControl_Flags)tcflag_t(_CSIZE) COutput_Flag_Bits :: enum tcflag_t { OPOST = log2(OPOST), /* enable following output processing */ @@ -181,17 +182,17 @@ COutput_Flag_Bits :: enum tcflag_t { COutput_Flags :: bit_set[COutput_Flag_Bits; tcflag_t] // \n delay mask -NLDLY :: COutput_Flags{ .NL1, COutput_Flag_Bits(9) } +NLDLY :: transmute(COutput_Flags)tcflag_t(_NLDLY) // \r delay mask -CRDLY :: COutput_Flags{ .CR1, .CR2, .CR3 } +CRDLY :: transmute(COutput_Flags)tcflag_t(_CRDLY) // horizontal tab delay mask -TABDLY :: COutput_Flags{ .TAB1, .TAB3, COutput_Flag_Bits(2) } +TABDLY :: transmute(COutput_Flags)tcflag_t(_TABDLY) // \b delay mask -BSDLY :: COutput_Flags{ .BS1 } +BSDLY :: transmute(COutput_Flags)tcflag_t(_BSDLY) // vertical tab delay mask -VTDLY :: COutput_Flags{ .VT1 } +VTDLY :: transmute(COutput_Flags)tcflag_t(_VTDLY) // form feed delay mask -FFDLY :: COutput_Flags{ .FF1 } +FFDLY :: transmute(COutput_Flags)tcflag_t(_FFDLY) speed_t :: enum _speed_t { B0 = B0, @@ -596,6 +597,4 @@ when ODIN_OS == .Darwin { TCOOFF :: 0 TCOON :: 1 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/time.odin b/core/sys/posix/time.odin index 5c6ebcf2f..f9c51c63c 100644 --- a/core/sys/posix/time.odin +++ b/core/sys/posix/time.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -229,6 +230,16 @@ when ODIN_OS == .Darwin { getdate_err: Errno = .ENOSYS // NOTE: looks like it's not a thing on OpenBSD. -} else { - #panic("posix is unimplemented for the current target") +} else when ODIN_OS == .Linux { + + clockid_t :: distinct c.int + + CLOCK_MONOTONIC :: 1 + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 3 + + foreign lib { + getdate_err: Errno + } } diff --git a/core/sys/posix/ulimit.odin b/core/sys/posix/ulimit.odin index 782756f6e..0f87641fa 100644 --- a/core/sys/posix/ulimit.odin +++ b/core/sys/posix/ulimit.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -38,6 +39,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS // NOTE: I don't think OpenBSD implements this API. -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/unistd.odin b/core/sys/posix/unistd.odin index 5b428ef6f..0526b3235 100644 --- a/core/sys/posix/unistd.odin +++ b/core/sys/posix/unistd.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -11,19 +12,6 @@ when ODIN_OS == .Darwin { // unistd.h - standard symbolic constants and types foreign lib { - /* - Checks the file named by the pathname pointed to by the path argument for - accessibility according to the bit pattern contained in amode. - - Example: - if (posix.access("/tmp/myfile", posix.F_OK) != .OK) { - fmt.printfln("/tmp/myfile access check failed: %v", posix.strerror(posix.errno())) - } - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html ]] - */ - access :: proc(path: cstring, amode: Mode_Flags = F_OK) -> result --- - /* Equivalent to `access` but relative paths are resolved based on `fd`. @@ -42,18 +30,6 @@ foreign lib { */ alarm :: proc(seconds: c.uint) -> c.uint --- - /* - Causes the directory named by path to become the current working directory. - - Example: - if (posix.chdir("/tmp") == .OK) { - fmt.println("changed current directory to /tmp") - } - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chdir.html ]] - */ - chdir :: proc(path: cstring) -> result --- - /* Equivalent to chdir but instead of a path the fildes is resolved to a directory. @@ -204,15 +180,6 @@ foreign lib { */ dup2 :: proc(fildes, fildes2: FD) -> FD --- - /* - Exits but, shall not call functions registered with atexit() nor any registered signal handlers. - Open streams shall not be flushed. - Whether open streams are closed (without flushing) is implementation-defined. Finally, the calling process shall be terminated with the consequences described below. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_exit.html ]] - */ - _exit :: proc(status: c.int) -> ! --- - /* The exec family of functions shall replace the current process image with a new process image. The new image shall be constructed from a regular, executable file called the new process image file. @@ -392,44 +359,6 @@ foreign lib { */ ftruncate :: proc(fildes: FD, length: off_t) -> result --- - /* - Places an absolute pathname of the current working directory into buf. - - Returns: buf as a cstring on success, nil (setting errno) on failure - - Example: - size: int - path_max := posix.pathconf(".", ._PATH_MAX) - if path_max == -1 { - size = 1024 - } else if path_max > 10240 { - size = 10240 - } else { - size = int(path_max) - } - - buf: [dynamic]byte - cwd: cstring - for ; cwd == nil; size *= 2 { - if err := resize(&buf, size); err != nil { - fmt.panicf("allocation failure: %v", err) - } - - cwd = posix.getcwd(raw_data(buf), len(buf)) - if cwd == nil { - errno := posix.errno() - if errno != .ERANGE { - fmt.panicf("getcwd failure: %v", posix.strerror(errno)) - } - } - } - - fmt.println(path_max, cwd) - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html ]] - */ - getcwd :: proc(buf: [^]c.char, size: c.size_t) -> cstring --- - /* Returns the effective group ID of the calling process. @@ -829,13 +758,6 @@ foreign lib { */ readlinkat :: proc(fd: FD, path: cstring, buf: [^]byte, bufsize: c.size_t) -> c.ssize_t --- - /* - Remove an (empty) directory. - - ]] More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html ]] - */ - rmdir :: proc(path: cstring) -> result --- - /* Set the effective group ID. @@ -912,13 +834,6 @@ foreign lib { */ sleep :: proc(seconds: c.uint) -> c.uint --- - /* - Copy nbyte bytes, from src, to dest, exchanging adjecent bytes. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/swab.html ]] - */ - swab :: proc(src: [^]byte, dest: [^]byte, nbytes: c.ssize_t) --- - /* Schedule file system updates. @@ -958,13 +873,6 @@ foreign lib { */ ttyname_r :: proc(fildes: FD, name: [^]byte, namesize: c.size_t) -> Errno --- - /* - Remove a directory entry. - - [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html ]] - */ - unlink :: proc(path: cstring) -> result --- - /* Equivalent to unlink or rmdir (if flag is .REMOVEDIR) but relative paths are relative to the dir fd. @@ -973,20 +881,6 @@ foreign lib { unlinkat :: proc(fd: FD, path: cstring, flag: AT_Flags) -> result --- } -STDERR_FILENO :: 2 -STDIN_FILENO :: 0 -STDOUT_FILENO :: 1 - -Mode_Flag_Bits :: enum c.int { - X_OK = log2(X_OK), - W_OK = log2(W_OK), - R_OK = log2(R_OK), -} -Mode_Flags :: bit_set[Mode_Flag_Bits; c.int] - -#assert(_F_OK == 0) -F_OK :: Mode_Flags{} - CS :: enum c.int { _PATH = _CS_PATH, _POSIX_V6_ILP32_OFF32_CFLAGS = _CS_POSIX_V6_ILP32_OFF32_CFLAGS, @@ -2063,6 +1957,7 @@ when ODIN_OS == .Darwin { _SC_TYPED_MEMORY_OBJECTS :: 165 _SC_2_PBS :: 168 _SC_2_PBS_ACCOUNTING :: 169 + _SC_2_PBS_LOCATE :: 170 _SC_2_PBS_MESSAGE :: 171 _SC_2_PBS_TRACK :: 172 _SC_SYMLOOP_MAX :: 173 @@ -2097,7 +1992,4 @@ when ODIN_OS == .Darwin { // NOTE: Not implemented. _POSIX_VDISABLE :: 0 -} else { - #panic("posix is unimplemented for the current target") } - diff --git a/core/sys/posix/unistd_libc.odin b/core/sys/posix/unistd_libc.odin new file mode 100644 index 000000000..bbfe3d59d --- /dev/null +++ b/core/sys/posix/unistd_libc.odin @@ -0,0 +1,153 @@ +#+build linux, windows, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Windows { + foreign import lib "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// unistd.h - standard symbolic constants and types + +foreign lib { + /* + Checks the file named by the pathname pointed to by the path argument for + accessibility according to the bit pattern contained in amode. + + Example: + if (posix.access("/tmp/myfile", posix.F_OK) != .OK) { + fmt.printfln("/tmp/myfile access check failed: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html ]] + */ + @(link_name=LACCESS) + access :: proc(path: cstring, amode: Mode_Flags = F_OK) -> result --- + + /* + Causes the directory named by path to become the current working directory. + + Example: + if (posix.chdir("/tmp") == .OK) { + fmt.println("changed current directory to /tmp") + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chdir.html ]] + */ + @(link_name=LCHDIR) + chdir :: proc(path: cstring) -> result --- + + /* + Exits but, shall not call functions registered with atexit() nor any registered signal handlers. + Open streams shall not be flushed. + Whether open streams are closed (without flushing) is implementation-defined. Finally, the calling process shall be terminated with the consequences described below. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_exit.html ]] + */ + _exit :: proc(status: c.int) -> ! --- + + /* + Places an absolute pathname of the current working directory into buf. + + Returns: buf as a cstring on success, nil (setting errno) on failure + + Example: + size: int + path_max := posix.pathconf(".", ._PATH_MAX) + if path_max == -1 { + size = 1024 + } else if path_max > 10240 { + size = 10240 + } else { + size = int(path_max) + } + + buf: [dynamic]byte + cwd: cstring + for ; cwd == nil; size *= 2 { + if err := resize(&buf, size); err != nil { + fmt.panicf("allocation failure: %v", err) + } + + cwd = posix.getcwd(raw_data(buf), len(buf)) + if cwd == nil { + errno := posix.errno() + if errno != .ERANGE { + fmt.panicf("getcwd failure: %v", posix.strerror(errno)) + } + } + } + + fmt.println(path_max, cwd) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html ]] + */ + @(link_name=LGETCWD) + getcwd :: proc(buf: [^]c.char, size: c.size_t) -> cstring --- + + /* + Remove an (empty) directory. + + ]] More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html ]] + */ + @(link_name=LRMDIR) + rmdir :: proc(path: cstring) -> result --- + + /* + Copy nbyte bytes, from src, to dest, exchanging adjecent bytes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/swab.html ]] + */ + @(link_name=LSWAB) + swab :: proc(src: [^]byte, dest: [^]byte, nbytes: c.ssize_t) --- + + /* + Remove a directory entry. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html ]] + */ + @(link_name=LUNLINK) + unlink :: proc(path: cstring) -> result --- +} + +when ODIN_OS == .Windows { + @(private) LACCESS :: "_access" + @(private) LCHDIR :: "_chdir" + @(private) LGETCWD :: "_getcwd" + @(private) LRMDIR :: "_rmdir" + @(private) LSWAB :: "_swab" + @(private) LUNLINK :: "_unlink" +} else { + @(private) LACCESS :: "access" + @(private) LCHDIR :: "chdir" + @(private) LGETCWD :: "getcwd" + @(private) LRMDIR :: "rmdir" + @(private) LSWAB :: "swab" + @(private) LUNLINK :: "unlink" +} + +STDERR_FILENO :: 2 +STDIN_FILENO :: 0 +STDOUT_FILENO :: 1 + +Mode_Flag_Bits :: enum c.int { + X_OK = log2(X_OK), + W_OK = log2(W_OK), + R_OK = log2(R_OK), +} +Mode_Flags :: bit_set[Mode_Flag_Bits; c.int] + +#assert(_F_OK == 0) +F_OK :: Mode_Flags{} + +when ODIN_OS == .Windows { + _F_OK :: 0 + X_OK :: 1 + W_OK :: 2 + R_OK :: 4 + #assert(W_OK|R_OK == 6) +} diff --git a/core/sys/posix/utime.odin b/core/sys/posix/utime.odin index 1207cb402..e884eb1a3 100644 --- a/core/sys/posix/utime.odin +++ b/core/sys/posix/utime.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix when ODIN_OS == .Darwin { @@ -31,6 +32,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS modtime: time_t, /* [PSX] modification time (seconds since epoch) */ } -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/posix/wordexp.odin b/core/sys/posix/wordexp.odin index 6bb362625..a9e6f39a7 100644 --- a/core/sys/posix/wordexp.odin +++ b/core/sys/posix/wordexp.odin @@ -1,3 +1,4 @@ +#+build linux, darwin, netbsd, openbsd, freebsd package posix import "core:c" @@ -123,6 +124,4 @@ when ODIN_OS == .Darwin { WRDE_CMDSUB :: 4 WRDE_SYNTAX :: 5 -} else { - #panic("posix is unimplemented for the current target") } diff --git a/core/sys/unix/pthread_darwin.odin b/core/sys/unix/pthread_darwin.odin deleted file mode 100644 index eb2cc4c9f..000000000 --- a/core/sys/unix/pthread_darwin.odin +++ /dev/null @@ -1,96 +0,0 @@ -#+build darwin -package unix - -import "core:c" - -// NOTE(tetra): No 32-bit Macs. -// Source: _pthread_types.h on my Mac. -PTHREAD_SIZE :: 8176 -PTHREAD_ATTR_SIZE :: 56 -PTHREAD_MUTEXATTR_SIZE :: 8 -PTHREAD_MUTEX_SIZE :: 56 -PTHREAD_CONDATTR_SIZE :: 8 -PTHREAD_COND_SIZE :: 40 -PTHREAD_ONCE_SIZE :: 8 -PTHREAD_RWLOCK_SIZE :: 192 -PTHREAD_RWLOCKATTR_SIZE :: 16 - -pthread_t :: distinct u64 - -pthread_attr_t :: struct { - sig: c.long, - _: [PTHREAD_ATTR_SIZE] c.char, -} - -pthread_cond_t :: struct { - sig: c.long, - _: [PTHREAD_COND_SIZE] c.char, -} - -pthread_condattr_t :: struct { - sig: c.long, - _: [PTHREAD_CONDATTR_SIZE] c.char, -} - -pthread_mutex_t :: struct { - sig: c.long, - _: [PTHREAD_MUTEX_SIZE] c.char, -} - -pthread_mutexattr_t :: struct { - sig: c.long, - _: [PTHREAD_MUTEXATTR_SIZE] c.char, -} - -pthread_once_t :: struct { - sig: c.long, - _: [PTHREAD_ONCE_SIZE] c.char, -} - -pthread_rwlock_t :: struct { - sig: c.long, - _: [PTHREAD_RWLOCK_SIZE] c.char, -} - -pthread_rwlockattr_t :: struct { - sig: c.long, - _: [PTHREAD_RWLOCKATTR_SIZE] c.char, -} - -SCHED_OTHER :: 1 // Avoid if you are writing portable software. -SCHED_FIFO :: 4 -SCHED_RR :: 2 // Round robin. - -SCHED_PARAM_SIZE :: 4 - -sched_param :: struct { - sched_priority: c.int, - _: [SCHED_PARAM_SIZE] c.char, -} - -// Source: https://github.com/apple/darwin-libpthread/blob/03c4628c8940cca6fd6a82957f683af804f62e7f/pthread/pthread.h#L138 -PTHREAD_CREATE_JOINABLE :: 1 -PTHREAD_CREATE_DETACHED :: 2 -PTHREAD_INHERIT_SCHED :: 1 -PTHREAD_EXPLICIT_SCHED :: 2 -PTHREAD_PROCESS_SHARED :: 1 -PTHREAD_PROCESS_PRIVATE :: 2 - - -PTHREAD_MUTEX_NORMAL :: 0 -PTHREAD_MUTEX_RECURSIVE :: 1 -PTHREAD_MUTEX_ERRORCHECK :: 2 - -PTHREAD_CANCEL_ENABLE :: 0 -PTHREAD_CANCEL_DISABLE :: 1 -PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 - -foreign import pthread "system:System.framework" - -@(default_calling_convention="c") -foreign pthread { - pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- - pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- - pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} diff --git a/core/sys/unix/pthread_freebsd.odin b/core/sys/unix/pthread_freebsd.odin deleted file mode 100644 index 38fe7db55..000000000 --- a/core/sys/unix/pthread_freebsd.odin +++ /dev/null @@ -1,122 +0,0 @@ -#+build freebsd -package unix - -import "core:c" - -pthread_t :: distinct u64 -// pthread_t :: struct #align(16) { x: u64 } - -PTHREAD_COND_T_SIZE :: 8 - -PTHREAD_MUTEXATTR_T_SIZE :: 8 -PTHREAD_CONDATTR_T_SIZE :: 8 -PTHREAD_RWLOCKATTR_T_SIZE :: 8 -PTHREAD_BARRIERATTR_T_SIZE :: 8 - -// WARNING: The sizes of these things are different yet again -// on non-X86! -when size_of(int) == 8 { - PTHREAD_ATTR_T_SIZE :: 8 - PTHREAD_MUTEX_T_SIZE :: 8 - PTHREAD_RWLOCK_T_SIZE :: 8 - PTHREAD_BARRIER_T_SIZE :: 8 -} else when size_of(int) == 4 { // TODO - PTHREAD_ATTR_T_SIZE :: 32 - PTHREAD_MUTEX_T_SIZE :: 32 - PTHREAD_RWLOCK_T_SIZE :: 44 - PTHREAD_BARRIER_T_SIZE :: 20 -} - -pthread_cond_t :: struct #align(16) { - _: [PTHREAD_COND_T_SIZE] c.char, -} -pthread_mutex_t :: struct #align(16) { - _: [PTHREAD_MUTEX_T_SIZE] c.char, -} -pthread_rwlock_t :: struct #align(16) { - _: [PTHREAD_RWLOCK_T_SIZE] c.char, -} -pthread_barrier_t :: struct #align(16) { - _: [PTHREAD_BARRIER_T_SIZE] c.char, -} - -pthread_attr_t :: struct #align(16) { - _: [PTHREAD_ATTR_T_SIZE] c.char, -} -pthread_condattr_t :: struct #align(16) { - _: [PTHREAD_CONDATTR_T_SIZE] c.char, -} -pthread_mutexattr_t :: struct #align(16) { - _: [PTHREAD_MUTEXATTR_T_SIZE] c.char, -} -pthread_rwlockattr_t :: struct #align(16) { - _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char, -} -pthread_barrierattr_t :: struct #align(16) { - _: [PTHREAD_BARRIERATTR_T_SIZE] c.char, -} - -PTHREAD_MUTEX_ERRORCHECK :: 1 -PTHREAD_MUTEX_RECURSIVE :: 2 -PTHREAD_MUTEX_NORMAL :: 3 - - -PTHREAD_CREATE_JOINABLE :: 0 -PTHREAD_CREATE_DETACHED :: 1 -PTHREAD_INHERIT_SCHED :: 4 -PTHREAD_EXPLICIT_SCHED :: 0 -PTHREAD_PROCESS_PRIVATE :: 0 -PTHREAD_PROCESS_SHARED :: 1 - -SCHED_FIFO :: 1 -SCHED_OTHER :: 2 -SCHED_RR :: 3 // Round robin. - - -sched_param :: struct { - sched_priority: c.int, -} - -_usem :: struct { - _has_waiters: u32, - _count: u32, - _flags: u32, -} -_usem2 :: struct { - _count: u32, - _flags: u32, -} -sem_t :: struct { - _magic: u32, - _kern: _usem2, - _padding: u32, -} - -PTHREAD_CANCEL_ENABLE :: 0 -PTHREAD_CANCEL_DISABLE :: 1 -PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 2 - -foreign import "system:pthread" - -@(default_calling_convention="c") -foreign pthread { - // create named semaphore. - // used in process-shared semaphores. - sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t --- - - sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int --- - sem_destroy :: proc(sem: ^sem_t) -> c.int --- - sem_post :: proc(sem: ^sem_t) -> c.int --- - sem_wait :: proc(sem: ^sem_t) -> c.int --- - sem_trywait :: proc(sem: ^sem_t) -> c.int --- - // sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int --- - - // NOTE: unclear whether pthread_yield is well-supported on Linux systems, - // see https://linux.die.net/man/3/pthread_yield - pthread_yield :: proc() --- - - pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- - pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- - pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} diff --git a/core/sys/unix/pthread_haiku.odin b/core/sys/unix/pthread_haiku.odin deleted file mode 100644 index 1278f34fe..000000000 --- a/core/sys/unix/pthread_haiku.odin +++ /dev/null @@ -1,71 +0,0 @@ -package unix - -import "core:c" - -pthread_t :: distinct rawptr -pthread_attr_t :: distinct rawptr -pthread_mutex_t :: distinct rawptr -pthread_mutexattr_t :: distinct rawptr -pthread_cond_t :: distinct rawptr -pthread_condattr_t :: distinct rawptr -pthread_rwlock_t :: distinct rawptr -pthread_rwlockattr_t :: distinct rawptr -pthread_barrier_t :: distinct rawptr -pthread_barrierattr_t :: distinct rawptr -pthread_spinlock_t :: distinct rawptr - -pthread_key_t :: distinct c.int -pthread_once_t :: struct { - state: c.int, - mutex: pthread_mutex_t, -} - -PTHREAD_MUTEX_DEFAULT :: 0 -PTHREAD_MUTEX_NORMAL :: 1 -PTHREAD_MUTEX_ERRORCHECK :: 2 -PTHREAD_MUTEX_RECURSIVE :: 3 - -PTHREAD_DETACHED :: 0x1 -PTHREAD_SCOPE_SYSTEM :: 0x2 -PTHREAD_INHERIT_SCHED :: 0x4 -PTHREAD_NOFLOAT :: 0x8 - -PTHREAD_CREATE_DETACHED :: PTHREAD_DETACHED -PTHREAD_CREATE_JOINABLE :: 0 -PTHREAD_SCOPE_PROCESS :: 0 -PTHREAD_EXPLICIT_SCHED :: 0 - -SCHED_FIFO :: 1 -SCHED_RR :: 2 -SCHED_SPORADIC :: 3 -SCHED_OTHER :: 4 - -sched_param :: struct { - sched_priority: c.int, -} - -sem_t :: distinct rawptr - -PTHREAD_CANCEL_ENABLE :: 0 -PTHREAD_CANCEL_DISABLE :: 1 -PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 2 - -foreign import libc "system:c" - -@(default_calling_convention="c") -foreign libc { - sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t --- - - sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int --- - sem_destroy :: proc(sem: ^sem_t) -> c.int --- - sem_post :: proc(sem: ^sem_t) -> c.int --- - sem_wait :: proc(sem: ^sem_t) -> c.int --- - sem_trywait :: proc(sem: ^sem_t) -> c.int --- - - pthread_yield :: proc() --- - - pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- - pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- - pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} diff --git a/core/sys/unix/pthread_linux.odin b/core/sys/unix/pthread_linux.odin deleted file mode 100644 index d67add24b..000000000 --- a/core/sys/unix/pthread_linux.odin +++ /dev/null @@ -1,124 +0,0 @@ -#+build linux -package unix - -import "core:c" - -// TODO(tetra): For robustness, I'd like to mark this with align 16. -// I cannot currently do this. -// And at the time of writing there is a bug with putting it -// as the only field in a struct. -pthread_t :: distinct u64 -// pthread_t :: struct #align(16) { x: u64 }; - -// NOTE(tetra): Got all the size constants from pthreadtypes-arch.h on my -// Linux machine. - -PTHREAD_COND_T_SIZE :: 48 - -PTHREAD_MUTEXATTR_T_SIZE :: 4 -PTHREAD_CONDATTR_T_SIZE :: 4 -PTHREAD_RWLOCKATTR_T_SIZE :: 8 -PTHREAD_BARRIERATTR_T_SIZE :: 4 - -// WARNING: The sizes of these things are different yet again -// on non-X86! -when size_of(int) == 8 { - PTHREAD_ATTR_T_SIZE :: 56 - PTHREAD_MUTEX_T_SIZE :: 40 - PTHREAD_RWLOCK_T_SIZE :: 56 - PTHREAD_BARRIER_T_SIZE :: 32 -} else when size_of(int) == 4 { - PTHREAD_ATTR_T_SIZE :: 32 - PTHREAD_MUTEX_T_SIZE :: 32 - PTHREAD_RWLOCK_T_SIZE :: 44 - PTHREAD_BARRIER_T_SIZE :: 20 -} - -pthread_cond_t :: struct #align(16) { - _: [PTHREAD_COND_T_SIZE] c.char, -} -pthread_mutex_t :: struct #align(16) { - _: [PTHREAD_MUTEX_T_SIZE] c.char, -} -pthread_rwlock_t :: struct #align(16) { - _: [PTHREAD_RWLOCK_T_SIZE] c.char, -} -pthread_barrier_t :: struct #align(16) { - _: [PTHREAD_BARRIER_T_SIZE] c.char, -} - -pthread_attr_t :: struct #align(16) { - _: [PTHREAD_ATTR_T_SIZE] c.char, -} -pthread_condattr_t :: struct #align(16) { - _: [PTHREAD_CONDATTR_T_SIZE] c.char, -} -pthread_mutexattr_t :: struct #align(16) { - _: [PTHREAD_MUTEXATTR_T_SIZE] c.char, -} -pthread_rwlockattr_t :: struct #align(16) { - _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char, -} -pthread_barrierattr_t :: struct #align(16) { - _: [PTHREAD_BARRIERATTR_T_SIZE] c.char, -} - -PTHREAD_MUTEX_NORMAL :: 0 -PTHREAD_MUTEX_RECURSIVE :: 1 -PTHREAD_MUTEX_ERRORCHECK :: 2 - - -// TODO(tetra, 2019-11-01): Maybe make `enum c.int`s for these? -PTHREAD_CREATE_JOINABLE :: 0 -PTHREAD_CREATE_DETACHED :: 1 -PTHREAD_INHERIT_SCHED :: 0 -PTHREAD_EXPLICIT_SCHED :: 1 -PTHREAD_PROCESS_PRIVATE :: 0 -PTHREAD_PROCESS_SHARED :: 1 - -SCHED_OTHER :: 0 -SCHED_FIFO :: 1 -SCHED_RR :: 2 // Round robin. - -sched_param :: struct { - sched_priority: c.int, -} - -sem_t :: struct #align(16) { - _: [SEM_T_SIZE] c.char, -} - -when size_of(int) == 8 { - SEM_T_SIZE :: 32 -} else when size_of(int) == 4 { - SEM_T_SIZE :: 16 -} - -PTHREAD_CANCEL_ENABLE :: 0 -PTHREAD_CANCEL_DISABLE :: 1 -PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 - -foreign import "system:pthread" - -@(default_calling_convention="c") -foreign pthread { - // create named semaphore. - // used in process-shared semaphores. - sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t --- - - sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int --- - sem_destroy :: proc(sem: ^sem_t) -> c.int --- - sem_post :: proc(sem: ^sem_t) -> c.int --- - sem_wait :: proc(sem: ^sem_t) -> c.int --- - sem_trywait :: proc(sem: ^sem_t) -> c.int --- - // sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int ---; - - // NOTE: unclear whether pthread_yield is well-supported on Linux systems, - // see https://linux.die.net/man/3/pthread_yield - pthread_yield :: proc() -> c.int --- - - pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- - pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- - pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} diff --git a/core/sys/unix/pthread_netbsd.odin b/core/sys/unix/pthread_netbsd.odin deleted file mode 100644 index 9107f1139..000000000 --- a/core/sys/unix/pthread_netbsd.odin +++ /dev/null @@ -1,102 +0,0 @@ -package unix - -import "core:c" - -pthread_t :: distinct rawptr - -SEM_T_SIZE :: 8 - -PTHREAD_CONDATTR_T_SIZE :: 16 -PTHREAD_MUTEXATTR_T_SIZE :: 16 -PTHREAD_RWLOCKATTR_T_SIZE :: 16 -PTHREAD_BARRIERATTR_T_SIZE :: 16 - -PTHREAD_COND_T_SIZE :: 40 -PTHREAD_MUTEX_T_SIZE :: 48 -PTHREAD_RWLOCK_T_SIZE :: 64 -PTHREAD_BARRIER_T_SIZE :: 48 -PTHREAD_ATTR_T_SIZE :: 16 - -pthread_cond_t :: struct #align(8) { - _: [PTHREAD_COND_T_SIZE] c.char, -} - -pthread_mutex_t :: struct #align(8) { - _: [PTHREAD_MUTEX_T_SIZE] c.char, -} - -pthread_rwlock_t :: struct #align(8) { - _: [PTHREAD_RWLOCK_T_SIZE] c.char, -} - -pthread_barrier_t :: struct #align(8) { - _: [PTHREAD_BARRIER_T_SIZE] c.char, -} - -pthread_attr_t :: struct #align(8) { - _: [PTHREAD_ATTR_T_SIZE] c.char, -} - -pthread_condattr_t :: struct #align(8) { - _: [PTHREAD_CONDATTR_T_SIZE] c.char, -} - -pthread_mutexattr_t :: struct #align(8) { - _: [PTHREAD_MUTEXATTR_T_SIZE] c.char, -} - -pthread_rwlockattr_t :: struct #align(8) { - _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char, -} - -pthread_barrierattr_t :: struct #align(8) { - _: [PTHREAD_BARRIERATTR_T_SIZE] c.char, -} - -PTHREAD_MUTEX_NORMAL :: 0 -PTHREAD_MUTEX_ERRORCHECK :: 1 -PTHREAD_MUTEX_RECURSIVE :: 2 - -PTHREAD_CREATE_JOINABLE :: 0 -PTHREAD_CREATE_DETACHED :: 1 -PTHREAD_INHERIT_SCHED :: 0 -PTHREAD_EXPLICIT_SCHED :: 1 -PTHREAD_PROCESS_PRIVATE :: 0 -PTHREAD_PROCESS_SHARED :: 1 - -SCHED_NONE :: -1 -SCHED_OTHER :: 0 -SCHED_FIFO :: 1 -SCHED_RR :: 3 - -sched_param :: struct { - sched_priority: c.int, -} - -sem_t :: struct #align(16) { - _: [SEM_T_SIZE] c.char, -} - -PTHREAD_CANCEL_ENABLE :: 0 -PTHREAD_CANCEL_DISABLE :: 1 -PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 - -foreign import "system:pthread" - -@(default_calling_convention="c") -foreign pthread { - sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t --- - - sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int --- - sem_destroy :: proc(sem: ^sem_t) -> c.int --- - sem_post :: proc(sem: ^sem_t) -> c.int --- - sem_wait :: proc(sem: ^sem_t) -> c.int --- - sem_trywait :: proc(sem: ^sem_t) -> c.int --- - - pthread_yield :: proc() --- - - pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- - pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- - pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} diff --git a/core/sys/unix/pthread_openbsd.odin b/core/sys/unix/pthread_openbsd.odin deleted file mode 100644 index 2c6d9e598..000000000 --- a/core/sys/unix/pthread_openbsd.odin +++ /dev/null @@ -1,74 +0,0 @@ -#+build openbsd -package unix - -import "core:c" - -pthread_t :: distinct rawptr -pthread_attr_t :: distinct rawptr -pthread_mutex_t :: distinct rawptr -pthread_mutexattr_t :: distinct rawptr -pthread_cond_t :: distinct rawptr -pthread_condattr_t :: distinct rawptr -pthread_rwlock_t :: distinct rawptr -pthread_rwlockattr_t :: distinct rawptr -pthread_barrier_t :: distinct rawptr -pthread_barrierattr_t :: distinct rawptr -pthread_spinlock_t :: distinct rawptr - -pthread_key_t :: distinct c.int -pthread_once_t :: struct { - state: c.int, - mutex: pthread_mutex_t, -} - -PTHREAD_MUTEX_ERRORCHECK :: 1 -PTHREAD_MUTEX_RECURSIVE :: 2 -PTHREAD_MUTEX_NORMAL :: 3 -PTHREAD_MUTEX_STRICT_NP :: 4 - -PTHREAD_DETACHED :: 0x1 -PTHREAD_SCOPE_SYSTEM :: 0x2 -PTHREAD_INHERIT_SCHED :: 0x4 -PTHREAD_NOFLOAT :: 0x8 - -PTHREAD_CREATE_DETACHED :: PTHREAD_DETACHED -PTHREAD_CREATE_JOINABLE :: 0 -PTHREAD_SCOPE_PROCESS :: 0 -PTHREAD_EXPLICIT_SCHED :: 0 - -SCHED_FIFO :: 1 -SCHED_OTHER :: 2 -SCHED_RR :: 3 - -sched_param :: struct { - sched_priority: c.int, -} - -sem_t :: distinct rawptr - -PTHREAD_CANCEL_ENABLE :: 0 -PTHREAD_CANCEL_DISABLE :: 1 -PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 2 - -foreign import libc "system:c" - -@(default_calling_convention="c") -foreign libc { - sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t --- - - sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int --- - sem_destroy :: proc(sem: ^sem_t) -> c.int --- - sem_post :: proc(sem: ^sem_t) -> c.int --- - sem_wait :: proc(sem: ^sem_t) -> c.int --- - sem_trywait :: proc(sem: ^sem_t) -> c.int --- - //sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int --- - - // NOTE: unclear whether pthread_yield is well-supported on Linux systems, - // see https://linux.die.net/man/3/pthread_yield - pthread_yield :: proc() --- - - pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- - pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- - pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin deleted file mode 100644 index 43c4866ed..000000000 --- a/core/sys/unix/pthread_unix.odin +++ /dev/null @@ -1,127 +0,0 @@ -#+build linux, darwin, freebsd, openbsd, netbsd, haiku -package unix - -foreign import "system:pthread" - -import "core:c" - -timespec :: struct { - tv_sec: i64, - tv_nsec: i64, -} - -// -// On success, these functions return 0. -// - -@(default_calling_convention="c") -foreign pthread { - pthread_create :: proc(t: ^pthread_t, attrs: ^pthread_attr_t, routine: proc(data: rawptr) -> rawptr, arg: rawptr) -> c.int --- - - // retval is a pointer to a location to put the return value of the thread proc. - pthread_join :: proc(t: pthread_t, retval: ^rawptr) -> c.int --- - - pthread_kill :: proc(t: pthread_t, sig: c.int) -> c.int --- - - pthread_self :: proc() -> pthread_t --- - - pthread_equal :: proc(a, b: pthread_t) -> b32 --- - - pthread_detach :: proc(t: pthread_t) -> c.int --- - - sched_get_priority_min :: proc(policy: c.int) -> c.int --- - sched_get_priority_max :: proc(policy: c.int) -> c.int --- - - // NOTE: POSIX says this can fail with OOM. - pthread_attr_init :: proc(attrs: ^pthread_attr_t) -> c.int --- - - pthread_attr_destroy :: proc(attrs: ^pthread_attr_t) -> c.int --- - - pthread_attr_getschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int --- - pthread_attr_setschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int --- - - // states: PTHREAD_CREATE_DETACHED, PTHREAD_CREATE_JOINABLE - pthread_attr_setdetachstate :: proc(attrs: ^pthread_attr_t, detach_state: c.int) -> c.int --- - - // NOTE(tetra, 2019-11-06): WARNING: Different systems have different alignment requirements. - // For maximum usefulness, use the OS's page size. - // ALSO VERY MAJOR WARNING: `stack_ptr` must be the LAST byte of the stack on systems - // where the stack grows downwards, which is the common case, so far as I know. - // On systems where it grows upwards, give the FIRST byte instead. - // ALSO SLIGHTLY LESS MAJOR WARNING: Using this procedure DISABLES automatically-provided - // guard pages. If you are using this procedure, YOU must set them up manually. - // If you forget to do this, you WILL get stack corruption bugs if you do not EXTREMELY - // know what you are doing! - pthread_attr_setstack :: proc(attrs: ^pthread_attr_t, stack_ptr: rawptr, stack_size: u64) -> c.int --- - pthread_attr_getstack :: proc(attrs: ^pthread_attr_t, stack_ptr: ^rawptr, stack_size: ^u64) -> c.int --- - - pthread_sigmask :: proc(how: c.int, set: rawptr, oldset: rawptr) -> c.int --- - - sched_yield :: proc() -> c.int --- -} - -// NOTE: Unimplemented in Haiku. -when ODIN_OS != .Haiku { - foreign pthread { - // scheds: PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED - pthread_attr_setinheritsched :: proc(attrs: ^pthread_attr_t, sched: c.int) -> c.int --- - - pthread_attr_getschedpolicy :: proc(t: ^pthread_attr_t, policy: ^c.int) -> c.int --- - pthread_attr_setschedpolicy :: proc(t: ^pthread_attr_t, policy: c.int) -> c.int --- - } -} - -@(default_calling_convention="c") -foreign pthread { - // NOTE: POSIX says this can fail with OOM. - pthread_cond_init :: proc(cond: ^pthread_cond_t, attrs: ^pthread_condattr_t) -> c.int --- - - pthread_cond_destroy :: proc(cond: ^pthread_cond_t) -> c.int --- - - pthread_cond_signal :: proc(cond: ^pthread_cond_t) -> c.int --- - - // same as signal, but wakes up _all_ threads that are waiting - pthread_cond_broadcast :: proc(cond: ^pthread_cond_t) -> c.int --- - - - // assumes the mutex is pre-locked - pthread_cond_wait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t) -> c.int --- - pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- - - pthread_condattr_init :: proc(attrs: ^pthread_condattr_t) -> c.int --- - pthread_condattr_destroy :: proc(attrs: ^pthread_condattr_t) -> c.int --- - - // p-shared = "process-shared" - i.e: is this condition shared among multiple processes? - // values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED - pthread_condattr_setpshared :: proc(attrs: ^pthread_condattr_t, value: c.int) -> c.int --- - pthread_condattr_getpshared :: proc(attrs: ^pthread_condattr_t, result: ^c.int) -> c.int --- - -} - -@(default_calling_convention="c") -foreign pthread { - // NOTE: POSIX says this can fail with OOM. - pthread_mutex_init :: proc(mutex: ^pthread_mutex_t, attrs: ^pthread_mutexattr_t) -> c.int --- - - pthread_mutex_destroy :: proc(mutex: ^pthread_mutex_t) -> c.int --- - - pthread_mutex_trylock :: proc(mutex: ^pthread_mutex_t) -> c.int --- - - pthread_mutex_lock :: proc(mutex: ^pthread_mutex_t) -> c.int --- - - pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- - - pthread_mutex_unlock :: proc(mutex: ^pthread_mutex_t) -> c.int --- - - - pthread_mutexattr_init :: proc(attrs: ^pthread_mutexattr_t) -> c.int --- - pthread_mutexattr_destroy :: proc(attrs: ^pthread_mutexattr_t) -> c.int --- - pthread_mutexattr_settype :: proc(attrs: ^pthread_mutexattr_t, type: c.int) -> c.int --- - - // p-shared = "process-shared" - i.e: is this mutex shared among multiple processes? - // values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED - pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int --- - pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int --- - - pthread_testcancel :: proc () --- -} diff --git a/core/sys/unix/unix.odin b/core/sys/unix/unix.odin new file mode 100644 index 000000000..e9f58e554 --- /dev/null +++ b/core/sys/unix/unix.odin @@ -0,0 +1,8 @@ +package unix + +import "core:c" + +timespec :: struct { + secs: i64, + nsecs: c.long, +} diff --git a/core/sys/wasm/js/dom.odin b/core/sys/wasm/js/dom.odin index ffc58a9a3..902dfc941 100644 --- a/core/sys/wasm/js/dom.odin +++ b/core/sys/wasm/js/dom.odin @@ -20,6 +20,8 @@ foreign dom_lib { device_pixel_ratio :: proc() -> f64 --- window_set_scroll :: proc(x, y: f64) --- + + set_element_style :: proc(id: string, key: string, value: string) --- } get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> string { diff --git a/core/sys/wasm/js/events.odin b/core/sys/wasm/js/events.odin index 905b3eba9..ffa3a1202 100644 --- a/core/sys/wasm/js/events.odin +++ b/core/sys/wasm/js/events.odin @@ -186,8 +186,8 @@ Key_Location :: enum u8 { Numpad = 3, } -KEYBOARD_MAX_KEY_SIZE :: 16 -KEYBOARD_MAX_CODE_SIZE :: 16 +KEYBOARD_MAX_KEY_SIZE :: 32 +KEYBOARD_MAX_CODE_SIZE :: 32 GAMEPAD_MAX_ID_SIZE :: 64 GAMEPAD_MAX_MAPPING_SIZE :: 64 diff --git a/core/sys/wasm/js/odin.js b/core/sys/wasm/js/odin.js index 6f086e595..07a77952c 100644 --- a/core/sys/wasm/js/odin.js +++ b/core/sys/wasm/js/odin.js @@ -1259,13 +1259,26 @@ class WebGLInterface { }; -function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory, eventQueue, event_temp) { +function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { const MAX_INFO_CONSOLE_LINES = 512; let infoConsoleLines = new Array(); let currentLine = {}; currentLine[false] = ""; currentLine[true] = ""; let prevIsError = false; + + let event_temp = {}; + + const onEventReceived = (event_data, data, callback) => { + event_temp.data = event_data; + + const exports = wasmMemoryInterface.exports; + const odin_ctx = exports.default_context_ptr(); + + exports.odin_dom_do_event_callback(data, callback, odin_ctx); + + event_temp.data = null; + }; const writeToConsole = (line, isError) => { if (!line) { @@ -1535,8 +1548,8 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory, ev wmi.storeInt(off(W, W), e.key.length) wmi.storeInt(off(W, W), e.code.length) - wmi.storeString(off(16, 1), e.key); - wmi.storeString(off(16, 1), e.code); + wmi.storeString(off(32, 1), e.key); + wmi.storeString(off(32, 1), e.code); } else if (e.type === 'scroll') { wmi.storeF64(off(8, 8), window.scrollX); wmi.storeF64(off(8, 8), window.scrollY); @@ -1594,7 +1607,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory, ev event_data.event = e; event_data.name_code = name_code; - eventQueue.push({event_data: event_data, data: data, callback: callback}); + onEventReceived(event_data, data, callback); }; wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; element.addEventListener(name, listener, !!use_capture); @@ -1611,7 +1624,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory, ev event_data.event = e; event_data.name_code = name_code; - eventQueue.push({event_data: event_data, data: data, callback: callback}); + onEventReceived(event_data, data, callback); }; wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; element.addEventListener(name, listener, !!use_capture); @@ -1802,6 +1815,16 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory, ev } }, + set_element_style: (id_ptr, id_len, key_ptr, key_len, value_ptr, value_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let value = wasmMemoryInterface.loadString(value_ptr, value_len); + let element = getElement(id); + if (element) { + element.style[key] = value; + } + }, + get_element_key_f64: (id_ptr, id_len, key_ptr, key_len) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); let key = wasmMemoryInterface.loadString(key_ptr, key_len); @@ -1905,10 +1928,7 @@ async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemory } wasmMemoryInterface.setIntSize(intSize); - let eventQueue = new Array(); - let event_temp = {}; - - let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement, wasmMemoryInterface.memory, eventQueue, event_temp); + let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement, wasmMemoryInterface.memory); let exports = {}; if (extraForeignImports !== undefined) { @@ -1948,13 +1968,6 @@ async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemory const dt = (currTimeStamp - prevTimeStamp)*0.001; prevTimeStamp = currTimeStamp; - while (eventQueue.length > 0) { - let e = eventQueue.shift() - event_temp.data = e.event_data; - exports.odin_dom_do_event_callback(e.data, e.callback, odin_ctx); - } - event_temp.data = null; - if (!exports.step(dt, odin_ctx)) { exports._end(); return; diff --git a/core/sys/windows/icu.odin b/core/sys/windows/icu.odin new file mode 100644 index 000000000..6ed8c9b40 --- /dev/null +++ b/core/sys/windows/icu.odin @@ -0,0 +1,14 @@ +#+build windows +package sys_windows + +foreign import "system:icu.lib" + +UError :: enum i32 { + U_ZERO_ERROR = 0, +} + +@(default_calling_convention="system") +foreign icu { + ucal_getWindowsTimeZoneID :: proc(id: wstring, len: i32, winid: wstring, winidCapacity: i32, status: ^UError) -> i32 --- + ucal_getDefaultTimeZone :: proc(result: wstring, cap: i32, status: ^UError) -> i32 --- +} diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 15b24a094..a625d053d 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -4545,7 +4545,7 @@ DNS_RECORD :: struct { // aka DNS_RECORDA Flags: DWORD, dwTtl: DWORD, _: DWORD, - Data: struct #raw_union { + Data: struct #raw_union #align(4) { CNAME: DNS_PTR_DATAA, A: u32be, // Ipv4 Address AAAA: u128be, // Ipv6 Address diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index 514592e7b..da979a3e3 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -51,6 +51,7 @@ foreign user32 { IsWindowVisible :: proc(hwnd: HWND) -> BOOL --- IsWindowEnabled :: proc(hwnd: HWND) -> BOOL --- IsIconic :: proc(hwnd: HWND) -> BOOL --- + IsZoomed :: proc(hwnd: HWND) -> BOOL --- BringWindowToTop :: proc(hWnd: HWND) -> BOOL --- GetTopWindow :: proc(hWnd: HWND) -> HWND --- SetForegroundWindow :: proc(hWnd: HWND) -> BOOL --- @@ -548,7 +549,7 @@ RI_KEY_TERMSRV_SHADOW :: 0x10 MOUSE_MOVE_RELATIVE :: 0x00 MOUSE_MOVE_ABSOLUTE :: 0x01 MOUSE_VIRTUAL_DESKTOP :: 0x02 -MOUSE_ATTRIUBTTES_CHANGED :: 0x04 +MOUSE_ATTRIBUTES_CHANGED :: 0x04 MOUSE_MOVE_NOCOALESCE :: 0x08 RI_MOUSE_BUTTON_1_DOWN :: 0x0001 @@ -781,3 +782,64 @@ CF_GDIOBJLAST :: 0x03FF CF_OWNERDISPLAY :: 0x0080 CF_PRIVATEFIRST :: 0x0200 CF_PRIVATELAST :: 0x02FF + +STICKYKEYS :: struct { + cbSize: UINT, + dwFlags: DWORD, +} +LPSTICKYKEYS :: ^STICKYKEYS + +SKF_STICKYKEYSON :: 0x1 +SKF_AVAILABLE :: 0x2 +SKF_HOTKEYACTIVE :: 0x4 +SKF_CONFIRMHOTKEY :: 0x8 +SKF_HOTKEYSOUND :: 0x10 +SKF_INDICATOR :: 0x20 +SKF_AUDIBLEFEEDBACK :: 0x40 +SKF_TRISTATE :: 0x80 +SKF_TWOKEYSOFF :: 0x100 +SKF_LSHIFTLOCKED :: 0x10000 +SKF_RSHIFTLOCKED :: 0x20000 +SKF_LCTLLOCKED :: 0x40000 +SKF_RCTLLOCKED :: 0x80000 +SKF_LALTLOCKED :: 0x100000 +SKF_RALTLOCKED :: 0x200000 +SKF_LWINLOCKED :: 0x400000 +SKF_RWINLOCKED :: 0x800000 +SKF_LSHIFTLATCHED :: 0x1000000 +SKF_RSHIFTLATCHED :: 0x2000000 +SKF_LCTLLATCHED :: 0x4000000 +SKF_RCTLLATCHED :: 0x8000000 +SKF_LALTLATCHED :: 0x10000000 +SKF_RALTLATCHED :: 0x20000000 + +TOGGLEKEYS :: struct { + cbSize: UINT, + dwFlags: DWORD, +} +LPTOGGLEKEYS :: ^TOGGLEKEYS + +TKF_TOGGLEKEYSON :: 0x1 +TKF_AVAILABLE :: 0x2 +TKF_HOTKEYACTIVE :: 0x4 +TKF_CONFIRMHOTKEY :: 0x8 +TKF_HOTKEYSOUND :: 0x10 +TKF_INDICATOR :: 0x20 + +FILTERKEYS :: struct { + cbSize: UINT, + dwFlags: DWORD, + iWaitMSec: DWORD, + iDelayMSec: DWORD, + iRepeatMSec: DWORD, + iBounceMSec: DWORD, +} +LPFILTERKEYS :: ^FILTERKEYS + +FKF_FILTERKEYSON :: 0x1 +FKF_AVAILABLE :: 0x2 +FKF_HOTKEYACTIVE :: 0x4 +FKF_CONFIRMHOTKEY :: 0x8 +FKF_HOTKEYSOUND :: 0x10 +FKF_INDICATOR :: 0x20 +FKF_CLICKON :: 0x40 diff --git a/core/sys/windows/ux_theme.odin b/core/sys/windows/ux_theme.odin index 527abd62f..392cf1e18 100644 --- a/core/sys/windows/ux_theme.odin +++ b/core/sys/windows/ux_theme.odin @@ -3,7 +3,7 @@ package sys_windows foreign import uxtheme "system:UxTheme.lib" -MARGINS :: distinct [4]int +MARGINS :: distinct [4]i32 PMARGINS :: ^MARGINS @(default_calling_convention="system") diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index b3b470619..61a7d9d86 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -251,26 +251,26 @@ SEVERITY :: enum DWORD { // Generic test for success on any status value (non-negative numbers indicate success). SUCCEEDED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result >= S_OK } // and the inverse -FAILED :: #force_inline proc(#any_int result: int) -> bool { return result < S_OK } +FAILED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result < S_OK } // Generic test for error on any status value. -IS_ERROR :: #force_inline proc(#any_int status: int) -> bool { return u32(status) >> 31 == u32(SEVERITY.ERROR) } +IS_ERROR :: #force_inline proc "contextless" (#any_int status: int) -> bool { return u32(status) >> 31 == u32(SEVERITY.ERROR) } // Return the code -HRESULT_CODE :: #force_inline proc(#any_int hr: int) -> int { return int(u32(hr) & 0xFFFF) } +HRESULT_CODE :: #force_inline proc "contextless" (#any_int hr: int) -> int { return int(u32(hr) & 0xFFFF) } // Return the facility -HRESULT_FACILITY :: #force_inline proc(#any_int hr: int) -> FACILITY { return FACILITY((u32(hr) >> 16) & 0x1FFF) } +HRESULT_FACILITY :: #force_inline proc "contextless" (#any_int hr: int) -> FACILITY { return FACILITY((u32(hr) >> 16) & 0x1FFF) } // Return the severity -HRESULT_SEVERITY :: #force_inline proc(#any_int hr: int) -> SEVERITY { return SEVERITY((u32(hr) >> 31) & 0x1) } +HRESULT_SEVERITY :: #force_inline proc "contextless" (#any_int hr: int) -> SEVERITY { return SEVERITY((u32(hr) >> 31) & 0x1) } // Create an HRESULT value from component pieces -MAKE_HRESULT :: #force_inline proc(#any_int sev: int, #any_int fac: int, #any_int code: int) -> HRESULT { +MAKE_HRESULT :: #force_inline proc "contextless" (#any_int sev: int, #any_int fac: int, #any_int code: int) -> HRESULT { return HRESULT((uint(sev)<<31) | (uint(fac)<<16) | (uint(code))) } -DECODE_HRESULT :: #force_inline proc(#any_int hr: int) -> (SEVERITY, FACILITY, int) { +DECODE_HRESULT :: #force_inline proc "contextless" (#any_int hr: int) -> (SEVERITY, FACILITY, int) { return HRESULT_SEVERITY(hr), HRESULT_FACILITY(hr), HRESULT_CODE(hr) } diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 7442c100c..281fbde40 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -15,7 +15,6 @@ import "core:c/libc" import "core:encoding/ansi" import "core:sync" import "core:os" -@require import "core:sys/unix" @(private="file") stop_runner_flag: libc.sig_atomic_t @@ -45,7 +44,7 @@ stop_runner_callback :: proc "c" (sig: libc.int) { } } -@(private="file") +@(private) stop_test_callback :: proc "c" (sig: libc.int) { if !local_test_index_set { // We're a thread created by a test thread. @@ -106,17 +105,7 @@ This is a dire bug and should be reported to the Odin developers. // otherwise we may continue to generate signals. intrinsics.cpu_relax() - when ODIN_OS != .Windows { - // NOTE(Feoramund): Some UNIX-like platforms may require this. - // - // During testing, I found that NetBSD 10.0 refused to - // terminate a task thread, even when its thread had been - // properly set to PTHREAD_CANCEL_ASYNCHRONOUS. - // - // The runner would stall after returning from `pthread_cancel`. - - unix.pthread_testcancel() - } + _test_thread_cancel() } } } @@ -133,14 +122,12 @@ _setup_signal_handler :: proc() { // For tests: // Catch asserts and panics. libc.signal(libc.SIGILL, stop_test_callback) - when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin { - // Catch panics on Darwin and unhandled calls to `debug_trap`. - libc.signal(SIGTRAP, stop_test_callback) - } // Catch arithmetic errors. libc.signal(libc.SIGFPE, stop_test_callback) // Catch segmentation faults (illegal memory access). libc.signal(libc.SIGSEGV, stop_test_callback) + + __setup_signal_handler() } _setup_task_signal_handler :: proc(test_index: int) { diff --git a/core/testing/signal_handler_posix.odin b/core/testing/signal_handler_posix.odin new file mode 100644 index 000000000..1bfcc875b --- /dev/null +++ b/core/testing/signal_handler_posix.odin @@ -0,0 +1,22 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +#+private +package testing + +import "core:c/libc" +import "core:sys/posix" + +__setup_signal_handler :: proc() { + libc.signal(posix.SIGTRAP, stop_test_callback) +} + +_test_thread_cancel :: proc "contextless" () { + // NOTE(Feoramund): Some UNIX-like platforms may require this. + // + // During testing, I found that NetBSD 10.0 refused to + // terminate a task thread, even when its thread had been + // properly set to PTHREAD_CANCEL_ASYNCHRONOUS. + // + // The runner would stall after returning from `pthread_cancel`. + + posix.pthread_testcancel() +} diff --git a/core/testing/signal_handler_windows.odin b/core/testing/signal_handler_windows.odin new file mode 100644 index 000000000..74ebe2998 --- /dev/null +++ b/core/testing/signal_handler_windows.odin @@ -0,0 +1,6 @@ +#+private +package testing + +__setup_signal_handler :: proc() {} + +_test_thread_cancel :: proc "contextless" () {} diff --git a/core/text/regex/regex.odin b/core/text/regex/regex.odin index 3dc26b5c6..8f8efe252 100644 --- a/core/text/regex/regex.odin +++ b/core/text/regex/regex.odin @@ -381,6 +381,7 @@ match_with_preallocated_capture :: proc( capture.pos[n] = {a, b} n += 1 } + num_groups = n } return diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index d27c66f24..24dbcc8a4 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -2,7 +2,7 @@ // It takes a string providing the source, which then can be tokenized through // repeated calls to the scan procedure. // For compatibility with existing tooling and languages, the NUL character is not allowed. -// If an UTF-8 encoded byte order mark (BOM) is the first character in the first character in the source, it will be discarded. +// If an UTF-8 encoded byte order mark (BOM) is the first character in the source, it will be discarded. // // By default, a Scanner skips white space and Odin comments and recognizes all literals defined by the Odin programming language specification. // A Scanner may be customized to recognize only a subset of those literals and to recognize different identifiers and white space characters. diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index d9166b450..59bf90620 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -9,6 +9,7 @@ package thread import "base:intrinsics" import "core:sync" import "core:mem" +import "core:container/queue" Task_Proc :: #type proc(task: Task) @@ -40,7 +41,7 @@ Pool :: struct { threads: []^Thread, - tasks: [dynamic]Task, + tasks: queue.Queue(Task), tasks_done: [dynamic]Task, } @@ -69,13 +70,13 @@ pool_thread_runner :: proc(t: ^Thread) { } // Once initialized, the pool's memory address is not allowed to change until -// it is destroyed. +// it is destroyed. // // The thread pool requires an allocator which it either owns, or which is thread safe. pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) { context.allocator = allocator pool.allocator = allocator - pool.tasks = make([dynamic]Task) + queue.init(&pool.tasks) pool.tasks_done = make([dynamic]Task) pool.threads = make([]^Thread, max(thread_count, 1)) @@ -92,7 +93,7 @@ pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) { } pool_destroy :: proc(pool: ^Pool) { - delete(pool.tasks) + queue.destroy(&pool.tasks) delete(pool.tasks_done) for &t in pool.threads { @@ -140,11 +141,11 @@ pool_join :: proc(pool: ^Pool) { // the thread pool. You can even add tasks from inside other tasks. // // Each task also needs an allocator which it either owns, or which is thread -// safe. +// safe. pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Proc, data: rawptr, user_index: int = 0) { sync.guard(&pool.mutex) - append(&pool.tasks, Task{ + queue.push_back(&pool.tasks, Task{ procedure = procedure, data = data, user_index = user_index, @@ -288,10 +289,10 @@ pool_is_empty :: #force_inline proc(pool: ^Pool) -> bool { pool_pop_waiting :: proc(pool: ^Pool) -> (task: Task, got_task: bool) { sync.guard(&pool.mutex) - if len(pool.tasks) != 0 { + if queue.len(pool.tasks) != 0 { intrinsics.atomic_sub(&pool.num_waiting, 1) intrinsics.atomic_add(&pool.num_in_processing, 1) - task = pop_front(&pool.tasks) + task = queue.pop_front(&pool.tasks) got_task = true } diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 9576a3040..ff79cfcbc 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -4,14 +4,14 @@ package thread import "base:runtime" import "core:sync" -import "core:sys/unix" +import "core:sys/posix" _IS_SUPPORTED :: true // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. Thread_Os_Specific :: struct #align(16) { - unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux. + unix_thread: posix.pthread_t, // NOTE: very large on Darwin, small on Linux. start_ok: sync.Sema, } // @@ -23,7 +23,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { t := (^Thread)(t) // We need to give the thread a moment to start up before we enable cancellation. - can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_ENABLE, nil) == 0 + // NOTE(laytan): setting to .DISABLE on darwin, with .ENABLE pthread_cancel would deadlock + // most of the time, don't ask me why. + can_set_thread_cancel_state := posix.pthread_setcancelstate(.DISABLE when ODIN_OS == .Darwin else .ENABLE, nil) == nil t.id = sync.current_thread_id() @@ -36,9 +38,15 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { } // Enable thread's cancelability. - if can_set_thread_cancel_state { - unix.pthread_setcanceltype (unix.PTHREAD_CANCEL_ASYNCHRONOUS, nil) - unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_ENABLE, nil) + // NOTE(laytan): Darwin does not correctly/fully support all of this, not doing this does + // actually make pthread_cancel work in the capacity of my tests, while executing this would + // basically always make it deadlock. + if ODIN_OS != .Darwin && can_set_thread_cancel_state { + err := posix.pthread_setcancelstate(.ENABLE, nil) + assert_contextless(err == nil) + + err = posix.pthread_setcanceltype(.ASYNCHRONOUS, nil) + assert_contextless(err == nil) } { @@ -59,8 +67,8 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { sync.atomic_or(&t.flags, { .Done }) if .Self_Cleanup in sync.atomic_load(&t.flags) { - res := unix.pthread_detach(t.unix_thread) - assert_contextless(res == 0) + res := posix.pthread_detach(t.unix_thread) + assert_contextless(res == nil) t.unix_thread = {} // NOTE(ftphikari): It doesn't matter which context 'free' received, right? @@ -71,19 +79,19 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { return nil } - attrs: unix.pthread_attr_t - if unix.pthread_attr_init(&attrs) != 0 { + attrs: posix.pthread_attr_t + if posix.pthread_attr_init(&attrs) != nil { return nil // NOTE(tetra, 2019-11-01): POSIX OOM. } - defer unix.pthread_attr_destroy(&attrs) + defer posix.pthread_attr_destroy(&attrs) // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. - res: i32 - res = unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) - assert(res == 0) + res: posix.Errno + res = posix.pthread_attr_setdetachstate(&attrs, .CREATE_JOINABLE) + assert(res == nil) when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { - res = unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) - assert(res == 0) + res = posix.pthread_attr_setinheritsched(&attrs, .EXPLICIT_SCHED) + assert(res == nil) } thread := new(Thread) @@ -93,26 +101,26 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { thread.creation_allocator = context.allocator // Set thread priority. - policy: i32 + policy: posix.Sched_Policy when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { - res = unix.pthread_attr_getschedpolicy(&attrs, &policy) - assert(res == 0) + res = posix.pthread_attr_getschedpolicy(&attrs, &policy) + assert(res == nil) } - params: unix.sched_param - res = unix.pthread_attr_getschedparam(&attrs, ¶ms) - assert(res == 0) - low := unix.sched_get_priority_min(policy) - high := unix.sched_get_priority_max(policy) + params: posix.sched_param + res = posix.pthread_attr_getschedparam(&attrs, ¶ms) + assert(res == nil) + low := posix.sched_get_priority_min(policy) + high := posix.sched_get_priority_max(policy) switch priority { case .Normal: // Okay case .Low: params.sched_priority = low + 1 case .High: params.sched_priority = high } - res = unix.pthread_attr_setschedparam(&attrs, ¶ms) - assert(res == 0) + res = posix.pthread_attr_setschedparam(&attrs, ¶ms) + assert(res == nil) thread.procedure = procedure - if unix.pthread_create(&thread.unix_thread, &attrs, __unix_thread_entry_proc, thread) != 0 { + if posix.pthread_create(&thread.unix_thread, &attrs, __unix_thread_entry_proc, thread) != nil { free(thread, thread.creation_allocator) return nil } @@ -130,7 +138,7 @@ _is_done :: proc(t: ^Thread) -> bool { } _join :: proc(t: ^Thread) { - if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { + if posix.pthread_equal(posix.pthread_self(), t.unix_thread) { return } @@ -144,7 +152,7 @@ _join :: proc(t: ^Thread) { if .Started not_in sync.atomic_load(&t.flags) { _start(t) } - unix.pthread_join(t.unix_thread, nil) + posix.pthread_join(t.unix_thread, nil) } _join_multiple :: proc(threads: ..^Thread) { @@ -170,9 +178,9 @@ _terminate :: proc(t: ^Thread, exit_code: int) { // // This is in contrast to behavior I have seen on Linux where the thread is // just terminated. - unix.pthread_cancel(t.unix_thread) + posix.pthread_cancel(t.unix_thread) } _yield :: proc() { - unix.sched_yield() + posix.sched_yield() } diff --git a/core/time/datetime/constants.odin b/core/time/datetime/constants.odin index 5f336ef4a..e24709e49 100644 --- a/core/time/datetime/constants.odin +++ b/core/time/datetime/constants.odin @@ -77,12 +77,55 @@ Time :: struct { nano: i32, } +TZ_Record :: struct { + time: i64, + utc_offset: i64, + shortname: string, + dst: bool, +} + +TZ_Date_Kind :: enum { + No_Leap, + Leap, + Month_Week_Day, +} + +TZ_Transition_Date :: struct { + type: TZ_Date_Kind, + + month: u8, + week: u8, + day: u16, + + time: i64, +} + +TZ_RRule :: struct { + has_dst: bool, + + std_name: string, + std_offset: i64, + std_date: TZ_Transition_Date, + + dst_name: string, + dst_offset: i64, + dst_date: TZ_Transition_Date, +} + +TZ_Region :: struct { + name: string, + records: []TZ_Record, + shortnames: []string, + rrule: TZ_RRule, +} + /* A type representing datetime. */ DateTime :: struct { using date: Date, using time: Time, + tz: ^TZ_Region, } /* @@ -130,4 +173,4 @@ Weekday :: enum i8 { } @(private) -MONTH_DAYS :: [?]i8{-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} \ No newline at end of file +MONTH_DAYS :: [?]i8{-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} diff --git a/core/time/datetime/datetime.odin b/core/time/datetime/datetime.odin index fc9780e3b..2cd90b0e7 100644 --- a/core/time/datetime/datetime.odin +++ b/core/time/datetime/datetime.odin @@ -76,7 +76,7 @@ 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 + return {date, time, nil}, .None } /* @@ -88,7 +88,7 @@ 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 + return {Date(d), {}, nil}, .None } /* @@ -433,4 +433,4 @@ unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) day := i8(ordinal - unsafe_date_to_ordinal(Date{year, month, 1}) + 1) return {year, month, day} -} \ No newline at end of file +} diff --git a/core/time/time.odin b/core/time/time.odin index 98639b36a..b488f951c 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -930,7 +930,7 @@ 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}} + unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}, nil} delta, err := dt.sub(datetime, unix_epoch) if err != .None { return @@ -958,7 +958,7 @@ datetime_to_time :: proc{components_to_time, compound_to_time} Convert time into datetime. */ time_to_datetime :: proc "contextless" (t: Time) -> (dt.DateTime, bool) { - unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}} + unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}, nil} datetime, err := dt.add(unix_epoch, dt.Delta{ nanos = t._nsec }) if err != .None { diff --git a/core/time/time_linux.odin b/core/time/time_linux.odin index 649f601dc..4e557766e 100644 --- a/core/time/time_linux.odin +++ b/core/time/time_linux.odin @@ -6,8 +6,8 @@ _IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { time_spec_now, _ := linux.clock_gettime(.REALTIME) - ns := time_spec_now.time_sec * 1e9 + time_spec_now.time_nsec - return Time{_nsec=i64(ns)} + ns := i64(time_spec_now.time_sec) * 1e9 + i64(time_spec_now.time_nsec) + return Time{_nsec=ns} } _sleep :: proc "contextless" (d: Duration) { @@ -29,7 +29,7 @@ _sleep :: proc "contextless" (d: Duration) { _tick_now :: proc "contextless" () -> Tick { t, _ := linux.clock_gettime(.MONOTONIC_RAW) - return Tick{_nsec = i64(t.time_sec*1e9 + t.time_nsec)} + return Tick{_nsec = i64(t.time_sec)*1e9 + i64(t.time_nsec)} } _yield :: proc "contextless" () { diff --git a/core/time/timezone/tz_unix.odin b/core/time/timezone/tz_unix.odin new file mode 100644 index 000000000..990e78d41 --- /dev/null +++ b/core/time/timezone/tz_unix.odin @@ -0,0 +1,89 @@ +#+build darwin, linux, freebsd, openbsd, netbsd +#+private +package timezone + +import "core:os" +import "core:strings" +import "core:path/filepath" +import "core:time/datetime" + +local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { + local_str, ok := os.lookup_env("TZ", allocator) + if !ok { + orig_localtime_path := "/etc/localtime" + path, err := os.absolute_path_from_relative(orig_localtime_path, allocator) + if err != nil { + // If we can't find /etc/localtime, fallback to UTC + if err == .ENOENT { + str, err2 := strings.clone("UTC", allocator) + if err2 != nil { return } + return str, true + } + + return + } + defer delete(path, allocator) + + // FreeBSD makes me sad. + // This is a hackaround, because FreeBSD copies rather than softlinks their local timezone file, + // *sometimes* and then stores the original name of the timezone in /var/db/zoneinfo instead + if path == orig_localtime_path { + data := os.read_entire_file("/var/db/zoneinfo", allocator) or_return + return strings.trim_right_space(string(data)), true + } + + // Looking for tz path (ex fmt: "UTC", "Etc/UTC" or "America/Los_Angeles") + path_dir, path_file := filepath.split(path) + if path_dir == "" { + return + } + upper_path_dir, upper_path_chunk := filepath.split(path_dir[:len(path_dir)-1]) + if upper_path_dir == "" { + return + } + + if strings.contains(upper_path_chunk, "zoneinfo") { + region_str, err := strings.clone(path_file, allocator) + if err != nil { return } + return region_str, true + } else { + region_str, err := filepath.join({upper_path_chunk, path_file}, allocator = allocator) + if err != nil { return } + return region_str, true + } + } + + if local_str == "" { + delete(local_str, allocator) + + str, err := strings.clone("UTC", allocator) + if err != nil { return } + return str, true + } + + return local_str, true +} + +_region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, success: bool) { + reg_str := _reg_str + if reg_str == "UTC" { + return nil, true + } + + if reg_str == "local" { + local_name := local_tz_name(allocator) or_return + if local_name == "UTC" { + delete(local_name, allocator) + return nil, true + } + + reg_str = local_name + } + defer if _reg_str == "local" { delete(reg_str, allocator) } + + db_path := "/usr/share/zoneinfo" + region_path := filepath.join({db_path, reg_str}, allocator) + defer delete(region_path, allocator) + + return load_tzif_file(region_path, reg_str, allocator) +} diff --git a/core/time/timezone/tz_windows.odin b/core/time/timezone/tz_windows.odin new file mode 100644 index 000000000..238c4c933 --- /dev/null +++ b/core/time/timezone/tz_windows.odin @@ -0,0 +1,311 @@ +#+build windows +#+private +package timezone + +import "core:strings" +import "core:sys/windows" +import "core:time/datetime" + +TZ_Abbrev :: struct { + std: string, + dst: string, +} + +tz_abbrevs := map[string]TZ_Abbrev { + "Egypt Standard Time" = {"EET", "EEST"}, // Africa/Cairo + "Morocco Standard Time" = {"+00", "+01"}, // Africa/Casablanca + "South Africa Standard Time" = {"SAST", "SAST"}, // Africa/Johannesburg + "South Sudan Standard Time" = {"CAT", "CAT"}, // Africa/Juba + "Sudan Standard Time" = {"CAT", "CAT"}, // Africa/Khartoum + "W. Central Africa Standard Time" = {"WAT", "WAT"}, // Africa/Lagos + "E. Africa Standard Time" = {"EAT", "EAT"}, // Africa/Nairobi + "Sao Tome Standard Time" = {"GMT", "GMT"}, // Africa/Sao_Tome + "Libya Standard Time" = {"EET", "EET"}, // Africa/Tripoli + "Namibia Standard Time" = {"CAT", "CAT"}, // Africa/Windhoek + "Aleutian Standard Time" = {"HST", "HDT"}, // America/Adak + "Alaskan Standard Time" = {"AKST", "AKDT"}, // America/Anchorage + "Tocantins Standard Time" = {"-03", "-03"}, // America/Araguaina + "Paraguay Standard Time" = {"-04", "-03"}, // America/Asuncion + "Bahia Standard Time" = {"-03", "-03"}, // America/Bahia + "SA Pacific Standard Time" = {"-05", "-05"}, // America/Bogota + "Argentina Standard Time" = {"-03", "-03"}, // America/Buenos_Aires + "Eastern Standard Time (Mexico)" = {"EST", "EST"}, // America/Cancun + "Venezuela Standard Time" = {"-04", "-04"}, // America/Caracas + "SA Eastern Standard Time" = {"-03", "-03"}, // America/Cayenne + "Central Standard Time" = {"CST", "CDT"}, // America/Chicago + "Central Brazilian Standard Time" = {"-04", "-04"}, // America/Cuiaba + "Mountain Standard Time" = {"MST", "MDT"}, // America/Denver + "Greenland Standard Time" = {"-03", "-02"}, // America/Godthab + "Turks And Caicos Standard Time" = {"EST", "EDT"}, // America/Grand_Turk + "Central America Standard Time" = {"CST", "CST"}, // America/Guatemala + "Atlantic Standard Time" = {"AST", "ADT"}, // America/Halifax + "Cuba Standard Time" = {"CST", "CDT"}, // America/Havana + "US Eastern Standard Time" = {"EST", "EDT"}, // America/Indianapolis + "SA Western Standard Time" = {"-04", "-04"}, // America/La_Paz + "Pacific Standard Time" = {"PST", "PDT"}, // America/Los_Angeles + "Mountain Standard Time (Mexico)" = {"MST", "MST"}, // America/Mazatlan + "Central Standard Time (Mexico)" = {"CST", "CST"}, // America/Mexico_City + "Saint Pierre Standard Time" = {"-03", "-02"}, // America/Miquelon + "Montevideo Standard Time" = {"-03", "-03"}, // America/Montevideo + "Eastern Standard Time" = {"EST", "EDT"}, // America/New_York + "US Mountain Standard Time" = {"MST", "MST"}, // America/Phoenix + "Haiti Standard Time" = {"EST", "EDT"}, // America/Port-au-Prince + "Magallanes Standard Time" = {"-03", "-03"}, // America/Punta_Arenas + "Canada Central Standard Time" = {"CST", "CST"}, // America/Regina + "Pacific SA Standard Time" = {"-04", "-03"}, // America/Santiago + "E. South America Standard Time" = {"-03", "-03"}, // America/Sao_Paulo + "Newfoundland Standard Time" = {"NST", "NDT"}, // America/St_Johns + "Pacific Standard Time (Mexico)" = {"PST", "PDT"}, // America/Tijuana + "Yukon Standard Time" = {"MST", "MST"}, // America/Whitehorse + "Central Asia Standard Time" = {"+06", "+06"}, // Asia/Almaty + "Jordan Standard Time" = {"+03", "+03"}, // Asia/Amman + "Arabic Standard Time" = {"+03", "+03"}, // Asia/Baghdad + "Azerbaijan Standard Time" = {"+04", "+04"}, // Asia/Baku + "SE Asia Standard Time" = {"+07", "+07"}, // Asia/Bangkok + "Altai Standard Time" = {"+07", "+07"}, // Asia/Barnaul + "Middle East Standard Time" = {"EET", "EEST"}, // Asia/Beirut + "India Standard Time" = {"IST", "IST"}, // Asia/Calcutta + "Transbaikal Standard Time" = {"+09", "+09"}, // Asia/Chita + "Sri Lanka Standard Time" = {"+0530", "+0530"}, // Asia/Colombo + "Syria Standard Time" = {"+03", "+03"}, // Asia/Damascus + "Bangladesh Standard Time" = {"+06", "+06"}, // Asia/Dhaka + "Arabian Standard Time" = {"+04", "+04"}, // Asia/Dubai + "West Bank Standard Time" = {"EET", "EEST"}, // Asia/Hebron + "W. Mongolia Standard Time" = {"+07", "+07"}, // Asia/Hovd + "North Asia East Standard Time" = {"+08", "+08"}, // Asia/Irkutsk + "Israel Standard Time" = {"IST", "IDT"}, // Asia/Jerusalem + "Afghanistan Standard Time" = {"+0430", "+0430"}, // Asia/Kabul + "Russia Time Zone 11" = {"+12", "+12"}, // Asia/Kamchatka + "Pakistan Standard Time" = {"PKT", "PKT"}, // Asia/Karachi + "Nepal Standard Time" = {"+0545", "+0545"}, // Asia/Katmandu + "North Asia Standard Time" = {"+07", "+07"}, // Asia/Krasnoyarsk + "Magadan Standard Time" = {"+11", "+11"}, // Asia/Magadan + "N. Central Asia Standard Time" = {"+07", "+07"}, // Asia/Novosibirsk + "Omsk Standard Time" = {"+06", "+06"}, // Asia/Omsk + "North Korea Standard Time" = {"KST", "KST"}, // Asia/Pyongyang + "Qyzylorda Standard Time" = {"+05", "+05"}, // Asia/Qyzylorda + "Myanmar Standard Time" = {"+0630", "+0630"}, // Asia/Rangoon + "Arab Standard Time" = {"+03", "+03"}, // Asia/Riyadh + "Sakhalin Standard Time" = {"+11", "+11"}, // Asia/Sakhalin + "Korea Standard Time" = {"KST", "KST"}, // Asia/Seoul + "China Standard Time" = {"CST", "CST"}, // Asia/Shanghai + "Singapore Standard Time" = {"+08", "+08"}, // Asia/Singapore + "Russia Time Zone 10" = {"+11", "+11"}, // Asia/Srednekolymsk + "Taipei Standard Time" = {"CST", "CST"}, // Asia/Taipei + "West Asia Standard Time" = {"+05", "+05"}, // Asia/Tashkent + "Georgian Standard Time" = {"+04", "+04"}, // Asia/Tbilisi + "Iran Standard Time" = {"+0330", "+0330"}, // Asia/Tehran + "Tokyo Standard Time" = {"JST", "JST"}, // Asia/Tokyo + "Tomsk Standard Time" = {"+07", "+07"}, // Asia/Tomsk + "Ulaanbaatar Standard Time" = {"+08", "+08"}, // Asia/Ulaanbaatar + "Vladivostok Standard Time" = {"+10", "+10"}, // Asia/Vladivostok + "Yakutsk Standard Time" = {"+09", "+09"}, // Asia/Yakutsk + "Ekaterinburg Standard Time" = {"+05", "+05"}, // Asia/Yekaterinburg + "Caucasus Standard Time" = {"+04", "+04"}, // Asia/Yerevan + "Azores Standard Time" = {"-01", "+00"}, // Atlantic/Azores + "Cape Verde Standard Time" = {"-01", "-01"}, // Atlantic/Cape_Verde + "Greenwich Standard Time" = {"GMT", "GMT"}, // Atlantic/Reykjavik + "Cen. Australia Standard Time" = {"ACST", "ACDT"}, // Australia/Adelaide + "E. Australia Standard Time" = {"AEST", "AEST"}, // Australia/Brisbane + "AUS Central Standard Time" = {"ACST", "ACST"}, // Australia/Darwin + "Aus Central W. Standard Time" = {"+0845", "+0845"}, // Australia/Eucla + "Tasmania Standard Time" = {"AEST", "AEDT"}, // Australia/Hobart + "Lord Howe Standard Time" = {"+1030", "+11"}, // Australia/Lord_Howe + "W. Australia Standard Time" = {"AWST", "AWST"}, // Australia/Perth + "AUS Eastern Standard Time" = {"AEST", "AEDT"}, // Australia/Sydney + "UTC-11" = {"-11", "-11"}, // Etc/GMT+11 + "Dateline Standard Time" = {"-12", "-12"}, // Etc/GMT+12 + "UTC-02" = {"-02", "-02"}, // Etc/GMT+2 + "UTC-08" = {"-08", "-08"}, // Etc/GMT+8 + "UTC-09" = {"-09", "-09"}, // Etc/GMT+9 + "UTC+12" = {"+12", "+12"}, // Etc/GMT-12 + "UTC+13" = {"+13", "+13"}, // Etc/GMT-13 + "UTC" = {"UTC", "UTC"}, // Etc/UTC + "Astrakhan Standard Time" = {"+04", "+04"}, // Europe/Astrakhan + "W. Europe Standard Time" = {"CET", "CEST"}, // Europe/Berlin + "GTB Standard Time" = {"EET", "EEST"}, // Europe/Bucharest + "Central Europe Standard Time" = {"CET", "CEST"}, // Europe/Budapest + "E. Europe Standard Time" = {"EET", "EEST"}, // Europe/Chisinau + "Turkey Standard Time" = {"+03", "+03"}, // Europe/Istanbul + "Kaliningrad Standard Time" = {"EET", "EET"}, // Europe/Kaliningrad + "FLE Standard Time" = {"EET", "EEST"}, // Europe/Kiev + "GMT Standard Time" = {"GMT", "BST"}, // Europe/London + "Belarus Standard Time" = {"+03", "+03"}, // Europe/Minsk + "Russian Standard Time" = {"MSK", "MSK"}, // Europe/Moscow + "Romance Standard Time" = {"CET", "CEST"}, // Europe/Paris + "Russia Time Zone 3" = {"+04", "+04"}, // Europe/Samara + "Saratov Standard Time" = {"+04", "+04"}, // Europe/Saratov + "Volgograd Standard Time" = {"MSK", "MSK"}, // Europe/Volgograd + "Central European Standard Time" = {"CET", "CEST"}, // Europe/Warsaw + "Mauritius Standard Time" = {"+04", "+04"}, // Indian/Mauritius + "Samoa Standard Time" = {"+13", "+13"}, // Pacific/Apia + "New Zealand Standard Time" = {"NZST", "NZDT"}, // Pacific/Auckland + "Bougainville Standard Time" = {"+11", "+11"}, // Pacific/Bougainville + "Chatham Islands Standard Time" = {"+1245", "+1345"}, // Pacific/Chatham + "Easter Island Standard Time" = {"-06", "-05"}, // Pacific/Easter + "Fiji Standard Time" = {"+12", "+12"}, // Pacific/Fiji + "Central Pacific Standard Time" = {"+11", "+11"}, // Pacific/Guadalcanal + "Hawaiian Standard Time" = {"HST", "HST"}, // Pacific/Honolulu + "Line Islands Standard Time" = {"+14", "+14"}, // Pacific/Kiritimati + "Marquesas Standard Time" = {"-0930", "-0930"}, // Pacific/Marquesas + "Norfolk Standard Time" = {"+11", "+12"}, // Pacific/Norfolk + "West Pacific Standard Time" = {"+10", "+10"}, // Pacific/Port_Moresby + "Tonga Standard Time" = {"+13", "+13"}, // Pacific/Tongatapu +} + +iana_to_windows_tz :: proc(iana_name: string, allocator := context.allocator) -> (name: string, success: bool) { + wintz_name_buffer: [128]u16 + status: windows.UError + + iana_name_wstr := windows.utf8_to_wstring(iana_name, allocator) + defer free(iana_name_wstr, allocator) + + wintz_name_len := windows.ucal_getWindowsTimeZoneID(iana_name_wstr, -1, raw_data(wintz_name_buffer[:]), len(wintz_name_buffer), &status) + if status != .U_ZERO_ERROR { + return + } + + wintz_name, err := windows.utf16_to_utf8(wintz_name_buffer[:wintz_name_len], allocator) + if err != nil { + return + } + + return wintz_name, true +} + +local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { + iana_name_buffer: [128]u16 + status: windows.UError + + zone_str_len := windows.ucal_getDefaultTimeZone(raw_data(iana_name_buffer[:]), len(iana_name_buffer), &status) + if status != .U_ZERO_ERROR { + return + } + + iana_name, err := windows.utf16_to_utf8(iana_name_buffer[:zone_str_len], allocator) + if err != nil { + return + } + + return iana_name, true +} + +REG_TZI_FORMAT :: struct #packed { + bias: windows.LONG, + std_bias: windows.LONG, + dst_bias: windows.LONG, + std_date: windows.SYSTEMTIME, + dst_date: windows.SYSTEMTIME, +} + +generate_rrule_from_tzi :: proc(tzi: ^REG_TZI_FORMAT, abbrevs: TZ_Abbrev, allocator := context.allocator) -> (rrule: datetime.TZ_RRule, ok: bool) { + std_name, err := strings.clone(abbrevs.std, allocator) + if err != nil { return } + defer if err != nil { delete(std_name, allocator) } + + if (tzi.std_date.month == 0) { + return datetime.TZ_RRule{ + has_dst = false, + + std_name = std_name, + std_offset = -(i64(tzi.bias) + i64(tzi.std_bias)) * 60, + dst_date = datetime.TZ_Transition_Date{ + type = .Month_Week_Day, + month = u8(tzi.std_date.month), + week = u8(tzi.std_date.day), + day = tzi.std_date.day_of_week, + time = (i64(tzi.std_date.hour) * 60 * 60) + (i64(tzi.std_date.minute) * 60) + i64(tzi.std_date.second), + }, + }, true + } + + dst_name: string + dst_name, err = strings.clone(abbrevs.dst, allocator) + if err != nil { return } + defer if err != nil { delete(dst_name, allocator) } + + return datetime.TZ_RRule{ + has_dst = true, + + std_name = std_name, + std_offset = -(i64(tzi.bias) + i64(tzi.std_bias)) * 60, + dst_date = datetime.TZ_Transition_Date{ + type = .Month_Week_Day, + month = u8(tzi.std_date.month), + week = u8(tzi.std_date.day), + day = tzi.std_date.day_of_week, + time = (i64(tzi.std_date.hour) * 60 * 60) + (i64(tzi.std_date.minute) * 60) + i64(tzi.std_date.second), + }, + + dst_name = dst_name, + dst_offset = -(i64(tzi.bias) + i64(tzi.dst_bias)) * 60, + std_date = datetime.TZ_Transition_Date{ + type = .Month_Week_Day, + month = u8(tzi.dst_date.month), + week = u8(tzi.dst_date.day), + day = tzi.dst_date.day_of_week, + time = (i64(tzi.dst_date.hour) * 60 * 60) + (i64(tzi.dst_date.minute) * 60) + i64(tzi.dst_date.second), + }, + }, true +} + +_region_load :: proc(reg_str: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, success: bool) { + wintz_name: string + iana_name: string + + if reg_str == "local" { + ok := false + + iana_name = local_tz_name(allocator) or_return + wintz_name, ok = iana_to_windows_tz(iana_name, allocator) + if !ok { + delete(iana_name, allocator) + return + } + } else { + wintz_name = iana_to_windows_tz(reg_str, allocator) or_return + iana_name = strings.clone(reg_str, allocator) + } + defer delete(wintz_name, allocator) + defer delete(iana_name, allocator) + + abbrevs := tz_abbrevs[wintz_name] or_return + if abbrevs.std == "UTC" && abbrevs.dst == abbrevs.std { + return nil, true + } + + key_base := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones` + tz_key := strings.join({key_base, wintz_name}, "\\", allocator = allocator) + defer delete(tz_key, allocator) + + tz_key_wstr := windows.utf8_to_wstring(tz_key, allocator) + defer free(tz_key_wstr, allocator) + + key: windows.HKEY + res := windows.RegOpenKeyExW(windows.HKEY_LOCAL_MACHINE, tz_key_wstr, 0, windows.KEY_READ, &key) + if res != 0 { return } + defer windows.RegCloseKey(key) + + tzi: REG_TZI_FORMAT + size := u32(size_of(REG_TZI_FORMAT)) + + res = windows.RegGetValueW(key, nil, windows.L("TZI"), windows.RRF_RT_ANY, nil, &tzi, &size) + if res != 0 { + return + } + + rrule := generate_rrule_from_tzi(&tzi, abbrevs, allocator) or_return + + region_name, err := strings.clone(iana_name, allocator) + if err != nil { return } + defer if err != nil { delete(region_name, allocator) } + + region: ^datetime.TZ_Region + region, err = new_clone(datetime.TZ_Region{ + name = region_name, + rrule = rrule, + }, allocator) + if err != nil { return } + + return region, true +} diff --git a/core/time/timezone/tzdate.odin b/core/time/timezone/tzdate.odin new file mode 100644 index 000000000..96df44299 --- /dev/null +++ b/core/time/timezone/tzdate.odin @@ -0,0 +1,341 @@ +package timezone + +import "core:fmt" +import "core:slice" +import "core:time" +import "core:time/datetime" + +region_load :: proc(reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) { + return _region_load(reg, allocator) +} + +region_load_from_file :: proc(file_path, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) { + return load_tzif_file(file_path, reg, allocator) +} + +region_load_from_buffer :: proc(buffer: []u8, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) { + return parse_tzif(buffer, reg, allocator) +} + +rrule_destroy :: proc(rrule: datetime.TZ_RRule, allocator := context.allocator) { + delete(rrule.std_name, allocator) + delete(rrule.dst_name, allocator) +} + +region_destroy :: proc(region: ^datetime.TZ_Region, allocator := context.allocator) { + if region == nil { + return + } + + for name in region.shortnames { + delete(name, allocator) + } + delete(region.shortnames, allocator) + delete(region.records, allocator) + delete(region.name, allocator) + rrule_destroy(region.rrule, allocator) + free(region, allocator) +} + + +@private +region_get_nearest :: proc(region: ^datetime.TZ_Region, tm: time.Time) -> (out: datetime.TZ_Record, success: bool) { + if len(region.records) == 0 { + return process_rrule(region.rrule, tm) + } + + n := len(region.records) + left, right := 0, n + + tm_sec := time.to_unix_seconds(tm) + last_time := region.records[len(region.records)-1].time + if tm_sec > last_time { + return process_rrule(region.rrule, tm) + } + + for left < right { + mid := int(uint(left+right) >> 1) + if region.records[mid].time < tm_sec { + left = mid + 1 + } else { + right = mid + } + } + + idx := max(0, left-1) + return region.records[idx], true +} + +@private +month_to_seconds :: proc(month: int, is_leap: bool) -> i64 { + month_seconds := []i64{ + 0, 31 * 86_400, 59 * 86_400, 90 * 86_400, + 120 * 86_400, 151 * 86_400, 181 * 86_400, 212 * 86_400, + 243 * 86_400, 273 * 86_400, 304 * 86_400, 334 * 86_400, + } + + t := month_seconds[month] + if is_leap && month >= 2 { + t += 86_400 + } + return t +} + +@private +trans_date_to_seconds :: proc(year: i64, td: datetime.TZ_Transition_Date) -> (secs: i64, ok: bool) { + is_leap := datetime.is_leap_year(year) + DAY_SEC :: 86_400 + + year_start := datetime.DateTime{{year, 1, 1}, {0, 0, 0, 0}, nil} + year_start_time := time.datetime_to_time(year_start) or_return + + t := i64(time.to_unix_seconds(year_start_time)) + + switch td.type { + case .Month_Week_Day: + if td.month < 1 { return } + + t += month_to_seconds(int(td.month) - 1, is_leap) + + weekday := ((t + (4 * DAY_SEC)) %% (7 * DAY_SEC)) / DAY_SEC + days := i64(td.day) - weekday + if days < 0 { days += 7 } + + month_daycount, err := datetime.last_day_of_month(year, td.month) + if err != nil { return } + + week := td.week + if week == 5 && days + 28 >= i64(month_daycount) { + week = 4 + } + + t += DAY_SEC * (days + (7 * i64(week - 1))) + t += td.time + + return t, true + + // Both of these should result in 0 -> 365 days (in seconds) + case .No_Leap: + day := i64(td.day) + + // if before Feb 29th || not a leap year + if day < 60 || !is_leap { + day -= 1 + } + t += DAY_SEC * day + + return t, true + + case .Leap: + t += DAY_SEC * i64(td.day) + + return t, true + + case: + return + } + + return +} + +@private +process_rrule :: proc(rrule: datetime.TZ_RRule, tm: time.Time) -> (out: datetime.TZ_Record, success: bool) { + if !rrule.has_dst { + return datetime.TZ_Record{ + time = time.to_unix_seconds(tm), + utc_offset = rrule.std_offset, + shortname = rrule.std_name, + dst = false, + }, true + } + + y, _, _ := time.date(tm) + std_secs := trans_date_to_seconds(i64(y), rrule.std_date) or_return + dst_secs := trans_date_to_seconds(i64(y), rrule.dst_date) or_return + + records := []datetime.TZ_Record{ + { + time = std_secs, + utc_offset = rrule.std_offset, + shortname = rrule.std_name, + dst = false, + }, + { + time = dst_secs, + utc_offset = rrule.dst_offset, + shortname = rrule.dst_name, + dst = true, + }, + } + record_sort_proc :: proc(i, j: datetime.TZ_Record) -> bool { + return i.time > j.time + } + slice.sort_by(records, record_sort_proc) + + tm_sec := time.to_unix_seconds(tm) + for record in records { + if tm_sec < record.time { + return record, true + } + } + + return records[len(records)-1], true +} + +datetime_to_utc :: proc(dt: datetime.DateTime) -> (out: datetime.DateTime, success: bool) #optional_ok { + if dt.tz == nil { + return dt, true + } + + tm := time.datetime_to_time(dt) or_return + record := region_get_nearest(dt.tz, tm) or_return + + secs := time.time_to_unix(tm) + adj_time := time.unix(secs - record.utc_offset, 0) + adj_dt := time.time_to_datetime(adj_time) or_return + return adj_dt, true +} + +/* +Converts a datetime on one timezone to another timezone + +Inputs: +- dt: The input datetime +- tz: The timezone to convert to + +NOTE: tz will be referenced in the result datetime, so it must stay alive/allocated as long as it is used +Returns: +- out: The converted datetime +- success: `false` if the datetime was invalid +*/ +datetime_to_tz :: proc(dt: datetime.DateTime, tz: ^datetime.TZ_Region) -> (out: datetime.DateTime, success: bool) #optional_ok { + dt := dt + if dt.tz == tz { + return dt, true + } + if dt.tz != nil { + dt = datetime_to_utc(dt) + } + if tz == nil { + return dt, true + } + + tm := time.datetime_to_time(dt) or_return + record := region_get_nearest(tz, tm) or_return + + secs := time.time_to_unix(tm) + adj_time := time.unix(secs + record.utc_offset, 0) + adj_dt := time.time_to_datetime(adj_time) or_return + adj_dt.tz = tz + + return adj_dt, true +} + +/* +Gets the timezone abbreviation/shortname for a given date. +(ex: "PDT") + +Inputs: +- dt: The datetime containing the date, time, and timezone pointer for the lookup + +NOTE: The lifetime of name matches the timezone it was pulled from. +Returns: +- name: The timezone abbreviation +- success: returns `false` if the passed datetime is invalid +*/ +shortname :: proc(dt: datetime.DateTime) -> (name: string, success: bool) #optional_ok { + tm := time.datetime_to_time(dt) or_return + if dt.tz == nil { return "UTC", true } + + record := region_get_nearest(dt.tz, tm) or_return + return record.shortname, true +} + +/* +Gets the timezone abbreviation/shortname for a given date. +(ex: "PDT") + +WARNING: This is unsafe because it doesn't check if your datetime is valid or if your region contains a valid record. + +Inputs: +- dt: The input datetime + +NOTE: The lifetime of name matches the timezone it was pulled from. +Returns: +- name: The timezone abbreviation +*/ +shortname_unsafe :: proc(dt: datetime.DateTime) -> string { + if dt.tz == nil { return "UTC" } + + tm, _ := time.datetime_to_time(dt) + record, _ := region_get_nearest(dt.tz, tm) + return record.shortname +} + +/* +Checks DST for a given date. + +Inputs: +- dt: The input datetime + +Returns: +- is_dst: returns `true` if dt is in daylight savings time, `false` if not +- success: returns `false` if the passed datetime is invalid +*/ +dst :: proc(dt: datetime.DateTime) -> (is_dst: bool, success: bool) #optional_ok { + tm := time.datetime_to_time(dt) or_return + if dt.tz == nil { return false, true } + + record := region_get_nearest(dt.tz, tm) or_return + return record.dst, true +} + +/* +Checks DST for a given date. + +WARNING: This is unsafe because it doesn't check if your datetime is valid or if your region contains a valid record. + +Inputs: +- dt: The input datetime + +Returns: +- is_dst: returns `true` if dt is in daylight savings time, `false` if not +*/ +dst_unsafe :: proc(dt: datetime.DateTime) -> bool { + if dt.tz == nil { return false } + + tm, _ := time.datetime_to_time(dt) + record, _ := region_get_nearest(dt.tz, tm) + return record.dst +} + +datetime_to_str :: proc(dt: datetime.DateTime, allocator := context.allocator) -> string { + if dt.tz == nil { + _, ok := time.datetime_to_time(dt) + if !ok { + return "" + } + + return fmt.aprintf("%02d-%02d-%04d @ %02d:%02d:%02d UTC", dt.month, dt.day, dt.year, dt.hour, dt.minute, dt.second, allocator = allocator) + + } else { + tm, ok := time.datetime_to_time(dt) + if !ok { + return "" + } + + record, ok2 := region_get_nearest(dt.tz, tm) + if !ok2 { + return "" + } + + hour := dt.hour + am_pm_str := "AM" + if hour > 12 { + am_pm_str = "PM" + hour -= 12 + } + + return fmt.aprintf("%02d-%02d-%04d @ %02d:%02d:%02d %s %s", dt.month, dt.day, dt.year, hour, dt.minute, dt.second, am_pm_str, record.shortname, allocator = allocator) + } +} diff --git a/core/time/timezone/tzif.odin b/core/time/timezone/tzif.odin new file mode 100644 index 000000000..3fec7be53 --- /dev/null +++ b/core/time/timezone/tzif.odin @@ -0,0 +1,649 @@ +package timezone + +import "base:intrinsics" + +import "core:slice" +import "core:strings" +import "core:os" +import "core:strconv" +import "core:time/datetime" + +// Implementing RFC8536 [https://datatracker.ietf.org/doc/html/rfc8536] + +TZIF_MAGIC :: u32be(0x545A6966) // 'TZif' +TZif_Version :: enum u8 { + V1 = 0, + V2 = '2', + V3 = '3', + V4 = '4', +} +BIG_BANG_ISH :: -0x800000000000000 + +TZif_Header :: struct #packed { + magic: u32be, + version: TZif_Version, + reserved: [15]u8, + isutcnt: u32be, + isstdcnt: u32be, + leapcnt: u32be, + timecnt: u32be, + typecnt: u32be, + charcnt: u32be, +} + +Sun_Shift :: enum u8 { + Standard = 0, + DST = 1, +} + +Local_Time_Type :: struct #packed { + utoff: i32be, + dst: Sun_Shift, + idx: u8, +} + +Leapsecond_Record :: struct #packed { + occur: i64be, + corr: i32be, +} + +@private +tzif_data_block_size :: proc(hdr: ^TZif_Header, version: TZif_Version) -> (block_size: int, ok: bool) { + time_size : int + + if version == .V1 { + time_size = 4 + } else if version == .V2 || version == .V3 || version == .V4 { + time_size = 8 + } else { + return + } + + return (int(hdr.timecnt) * time_size) + + int(hdr.timecnt) + + int(hdr.typecnt * size_of(Local_Time_Type)) + + int(hdr.charcnt) + + (int(hdr.leapcnt) * (time_size + 4)) + + int(hdr.isstdcnt) + + int(hdr.isutcnt), true +} + + +load_tzif_file :: proc(filename: string, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) { + tzif_data := os.read_entire_file_from_filename(filename, allocator) or_return + defer delete(tzif_data, allocator) + return parse_tzif(tzif_data, region_name, allocator) +} + +@private +is_alphabetic :: proc(ch: u8) -> bool { + // ('A' -> 'Z') || ('a' -> 'z') + return (ch > 0x40 && ch < 0x5B) || (ch > 0x60 && ch < 0x7B) +} + +@private +is_numeric :: proc(ch: u8) -> bool { + // ('0' -> '9') + return (ch > 0x2F && ch < 0x3A) +} + +@private +is_alphanumeric :: proc(ch: u8) -> bool { + return is_alphabetic(ch) || is_numeric(ch) +} + +@private +is_valid_quoted_char :: proc(ch: u8) -> bool { + return is_alphabetic(ch) || is_numeric(ch) || ch == '+' || ch == '-' +} + +@private +parse_posix_tz_shortname :: proc(str: string) -> (out: string, idx: int, ok: bool) { + was_quoted := false + quoted := false + i := 0 + + for ; i < len(str); i += 1 { + ch := str[i] + + if !quoted && ch == '<' { + quoted = true + was_quoted = true + continue + } + + if quoted && ch == '>' { + quoted = false + break + } + + if !is_valid_quoted_char(ch) && ch != ',' { + return + } + + if !quoted && !is_alphabetic(ch) { + break + } + } + + // If we didn't see the trailing quote + if was_quoted && quoted { + return + } + + out_str: string + end_idx := i + if was_quoted { + end_idx += 1 + out_str = str[1:i] + } else { + out_str = str[:i] + } + + return out_str, end_idx, true +} + +@private +parse_posix_tz_offset :: proc(str: string) -> (out_sec: i64, idx: int, ok: bool) { + str := str + + sign : i64 = 1 + start_idx := 0 + i := 0 + if str[i] == '+' { + i += 1 + sign = 1 + start_idx = 1 + } else if str[i] == '-' { + i += 1 + sign = -1 + start_idx = 1 + } + + got_more_time := false + for ; i < len(str); i += 1 { + if is_numeric(str[i]) { + continue + } + + if str[i] == ':' { + got_more_time = true + break + } + + break + } + + ret_sec : i64 = 0 + hours := strconv.parse_int(str[start_idx:i], 10) or_return + if hours > 167 || hours < -167 { + return + } + ret_sec += i64(hours) * (60 * 60) + if !got_more_time { + return ret_sec * sign, i, true + } + + i += 1 + start_idx = i + + got_more_time = false + for ; i < len(str); i += 1 { + if is_numeric(str[i]) { + continue + } + + if str[i] == ':' { + got_more_time = true + break + } + + break + } + + mins_str := str[start_idx:i] + if len(mins_str) != 2 { + return + } + + mins := strconv.parse_int(mins_str, 10) or_return + if mins > 59 || mins < 0 { + return + } + ret_sec += i64(mins) * 60 + if !got_more_time { + return ret_sec * sign, i, true + } + + i += 1 + start_idx = i + + for ; i < len(str); i += 1 { + if !is_numeric(str[i]) { + break + } + } + secs_str := str[start_idx:i] + if len(secs_str) != 2 { + return + } + + secs := strconv.parse_int(secs_str, 10) or_return + if secs > 59 || secs < 0 { + return + } + ret_sec += i64(secs) + return ret_sec * sign, i, true +} + +@private +skim_digits :: proc(str: string) -> (out: string, idx: int, ok: bool) { + i := 0 + for ; i < len(str); i += 1 { + ch := str[i] + if ch == '.' || ch == '/' || ch == ',' { + break + } + + if !is_numeric(ch) { + return + } + } + + return str[:i], i, true +} + +TWO_AM :: 2 * 60 * 60 +parse_posix_rrule :: proc(str: string) -> (out: datetime.TZ_Transition_Date, idx: int, ok: bool) { + str := str + if len(str) < 2 { return } + + i := 0 + // No leap + if str[i] == 'J' { + i += 1 + + day_str, off := skim_digits(str[i:]) or_return + i += off + + day := strconv.parse_int(day_str, 10) or_return + if day < 1 || day > 365 { return } + + offset : i64 = TWO_AM + if len(str) != i && str[i] == '/' { + i += 1 + + offset, off = parse_posix_tz_offset(str[i:]) or_return + i += off + } + + if len(str) != i && str[i] == ',' { + i += 1 + } + + return datetime.TZ_Transition_Date{ + type = .No_Leap, + day = u16(day), + time = offset, + }, i, true + + // Leap + } else if is_numeric(str[i]) { + day_str, off := skim_digits(str[i:]) or_return + i += off + + day := strconv.parse_int(day_str, 10) or_return + if day < 0 || day > 365 { return } + + offset : i64 = TWO_AM + if len(str) != i && str[i] == '/' { + i += 1 + + offset, off = parse_posix_tz_offset(str[i:]) or_return + i += off + } + + if len(str) != i && str[i] == ',' { + i += 1 + } + + return datetime.TZ_Transition_Date{ + type = .Leap, + day = u16(day), + time = offset, + }, i, true + + } else if str[i] == 'M' { + i += 1 + + month_str, week_str, day_str: string + off := 0 + + month_str, off = skim_digits(str[i:]) or_return + i += off + 1 + + week_str, off = skim_digits(str[i:]) or_return + i += off + 1 + + day_str, off = skim_digits(str[i:]) or_return + i += off + + month := strconv.parse_int(month_str, 10) or_return + if month < 1 || month > 12 { return } + + week := strconv.parse_int(week_str, 10) or_return + if week < 1 || week > 5 { return } + + day := strconv.parse_int(day_str, 10) or_return + if day < 0 || day > 6 { return } + + offset : i64 = TWO_AM + if len(str) != i && str[i] == '/' { + i += 1 + + offset, off = parse_posix_tz_offset(str[i:]) or_return + i += off + } + + if len(str) != i && str[i] == ',' { + i += 1 + } + + return datetime.TZ_Transition_Date{ + type = .Month_Week_Day, + month = u8(month), + week = u8(week), + day = u16(day), + time = offset, + }, i, true + } + + return +} + +parse_posix_tz :: proc(posix_tz: string, allocator := context.allocator) -> (out: datetime.TZ_RRule, ok: bool) { + // TZ string contain at least 3 characters for the STD name, and 1 for the offset + if len(posix_tz) < 4 { + return + } + + str := posix_tz + + std_name, idx := parse_posix_tz_shortname(str) or_return + str = str[idx:] + + std_offset, idx2 := parse_posix_tz_offset(str) or_return + std_offset *= -1 + str = str[idx2:] + + std_name_str, err := strings.clone(std_name, allocator) + if err != nil { return } + defer if !ok { delete(std_name_str, allocator) } + + if len(str) == 0 { + return datetime.TZ_RRule{ + has_dst = false, + std_name = std_name_str, + std_offset = std_offset, + std_date = datetime.TZ_Transition_Date{ + type = .Leap, + day = 0, + time = TWO_AM, + }, + }, true + } + + dst_name: string + dst_offset := std_offset + (1 * 60 * 60) + if str[0] != ',' { + dst_name, idx = parse_posix_tz_shortname(str) or_return + str = str[idx:] + + if str[0] != ',' { + dst_offset, idx = parse_posix_tz_offset(str) or_return + dst_offset *= -1 + str = str[idx:] + } + } + if str[0] != ',' { return } + str = str[1:] + + std_td, idx3 := parse_posix_rrule(str) or_return + str = str[idx3:] + + dst_td, idx4 := parse_posix_rrule(str) or_return + str = str[idx4:] + + dst_name_str: string + dst_name_str, err = strings.clone(dst_name, allocator) + if err != nil { return } + + return datetime.TZ_RRule{ + has_dst = true, + + std_name = std_name_str, + std_offset = std_offset, + std_date = std_td, + + dst_name = dst_name_str, + dst_offset = dst_offset, + dst_date = dst_td, + }, true +} + +parse_tzif :: proc(_buffer: []u8, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) { + context.allocator = allocator + + buffer := _buffer + + // TZif is crufty. Skip the initial header. + + v1_hdr := slice.to_type(buffer, TZif_Header) or_return + if v1_hdr.magic != TZIF_MAGIC { + return + } + if v1_hdr.typecnt == 0 || v1_hdr.charcnt == 0 { + return + } + if v1_hdr.isutcnt != 0 && v1_hdr.isutcnt != v1_hdr.typecnt { + return + } + if v1_hdr.isstdcnt != 0 && v1_hdr.isstdcnt != v1_hdr.typecnt { + return + } + + // We don't bother supporting v1, it uses u32 timestamps + if v1_hdr.version == .V1 { + return + } + // We only support v2 and v3 + if v1_hdr.version != .V2 && v1_hdr.version != .V3 { + return + } + + // Skip the initial v1 block too. + first_block_size, _ := tzif_data_block_size(&v1_hdr, .V1) + if len(buffer) <= size_of(v1_hdr) + first_block_size { + return + } + buffer = buffer[size_of(v1_hdr)+first_block_size:] + + // Ok, time to parse real things + real_hdr := slice.to_type(buffer, TZif_Header) or_return + if real_hdr.magic != TZIF_MAGIC { + return + } + if real_hdr.typecnt == 0 || real_hdr.charcnt == 0 { + return + } + if real_hdr.isutcnt != 0 && real_hdr.isutcnt != real_hdr.typecnt { + return + } + if real_hdr.isstdcnt != 0 && real_hdr.isstdcnt != real_hdr.typecnt { + return + } + + // Grab the real data block + real_block_size, _ := tzif_data_block_size(&real_hdr, v1_hdr.version) + if len(buffer) <= size_of(real_hdr) + real_block_size { + return + } + buffer = buffer[size_of(real_hdr):] + + time_size := 8 + transition_times := slice.reinterpret([]i64be, buffer[:int(real_hdr.timecnt)*size_of(i64be)]) + for time in transition_times { + if time < BIG_BANG_ISH { + return + } + } + buffer = buffer[int(real_hdr.timecnt)*time_size:] + + transition_types := buffer[:int(real_hdr.timecnt)] + for type in transition_types { + if int(type) > int(real_hdr.typecnt - 1) { + return + } + } + buffer = buffer[int(real_hdr.timecnt):] + + local_time_types := slice.reinterpret([]Local_Time_Type, buffer[:int(real_hdr.typecnt)*size_of(Local_Time_Type)]) + for ltt in local_time_types { + // UT offset should be > -25 hours and < 26 hours + if int(ltt.utoff) < -89999 || int(ltt.utoff) > 93599 { + return + } + + if ltt.dst != .DST && ltt.dst != .Standard { + return + } + + if int(ltt.idx) > int(real_hdr.charcnt - 1) { + return + } + } + + buffer = buffer[int(real_hdr.typecnt) * size_of(Local_Time_Type):] + timezone_string_table := buffer[:real_hdr.charcnt] + buffer = buffer[real_hdr.charcnt:] + + leapsecond_records := slice.reinterpret([]Leapsecond_Record, buffer[:int(real_hdr.leapcnt)*size_of(Leapsecond_Record)]) + if len(leapsecond_records) > 0 { + if leapsecond_records[0].occur < 0 { + return + } + } + buffer = buffer[(int(real_hdr.leapcnt) * size_of(Leapsecond_Record)):] + + standard_wall_tags := buffer[:int(real_hdr.isstdcnt)] + for stdwall_tag, _ in standard_wall_tags { + if (stdwall_tag != 0 && stdwall_tag != 1) { + return + } + } + + buffer = buffer[int(real_hdr.isstdcnt):] + + ut_tags := buffer[:int(real_hdr.isutcnt)] + for ut_tag, _ in ut_tags { + if (ut_tag != 0 && ut_tag != 1) { + return + } + } + + buffer = buffer[int(real_hdr.isutcnt):] + + // Start of footer + if buffer[0] != '\n' { + return + } + buffer = buffer[1:] + + if buffer[0] == ':' { + return + } + + end_idx := 0 + for ch in buffer { + if ch == '\n' { + break + } + + if ch == 0 { + return + } + end_idx += 1 + } + footer_str := string(buffer[:end_idx]) + + // UTC is a special case, we don't need to alloc + if len(local_time_types) == 1 { + name := cstring(raw_data(timezone_string_table[local_time_types[0].idx:])) + if name != "UTC" { + return + } + + return nil, true + } + + ltt_names, err := make([dynamic]string, 0, len(local_time_types), allocator) + if err != nil { return } + defer if err != nil { + for name in ltt_names { + delete(name, allocator) + } + delete(ltt_names) + } + + for ltt in local_time_types { + name := cstring(raw_data(timezone_string_table[ltt.idx:])) + ltt_name: string + + ltt_name, err = strings.clone_from_cstring_bounded(name, len(timezone_string_table), allocator) + if err != nil { return } + + append(<t_names, ltt_name) + } + + records: []datetime.TZ_Record + records, err = make([]datetime.TZ_Record, len(transition_times), allocator) + if err != nil { return } + defer if err != nil { delete(records, allocator) } + + for trans_time, idx in transition_times { + trans_idx := transition_types[idx] + ltt := local_time_types[trans_idx] + + records[idx] = datetime.TZ_Record{ + time = i64(trans_time), + utc_offset = i64(ltt.utoff), + shortname = ltt_names[trans_idx], + dst = bool(ltt.dst), + } + } + + rrule, ok2 := parse_posix_tz(footer_str, allocator) + if !ok2 { return } + defer if err != nil { + delete(rrule.std_name, allocator) + delete(rrule.dst_name, allocator) + } + + region_name_out: string + region_name_out, err = strings.clone(region_name, allocator) + if err != nil { return } + defer if err != nil { delete(region_name_out, allocator) } + + region: ^datetime.TZ_Region + region, err = new_clone(datetime.TZ_Region{ + records = records, + shortnames = ltt_names[:], + name = region_name_out, + rrule = rrule, + }, allocator) + if err != nil { + return + } + + return region, true +} diff --git a/core/unicode/utf16/utf16.odin b/core/unicode/utf16/utf16.odin index 6bdd6558a..e2bcf7f68 100644 --- a/core/unicode/utf16/utf16.odin +++ b/core/unicode/utf16/utf16.odin @@ -106,6 +106,18 @@ decode :: proc(d: []rune, s: []u16) -> (n: int) { return } +rune_count :: proc(s: []u16) -> (n: int) { + for i := 0; i < len(s); i += 1 { + c := s[i] + if _surr1 <= c && c < _surr2 && i+1 < len(s) && + _surr2 <= s[i+1] && s[i+1] < _surr3 { + i += 1 + } + n += 1 + } + return +} + decode_to_utf8 :: proc(d: []byte, s: []u16) -> (n: int) { for i := 0; i < len(s); i += 1 { @@ -127,4 +139,4 @@ decode_to_utf8 :: proc(d: []byte, s: []u16) -> (n: int) { n += copy(d[n:], b[:w]) } return -} \ No newline at end of file +} diff --git a/core/unicode/utf8/utf8string/string.odin b/core/unicode/utf8/utf8string/string.odin index 431939efe..4b0fe7241 100644 --- a/core/unicode/utf8/utf8string/string.odin +++ b/core/unicode/utf8/utf8string/string.odin @@ -66,7 +66,7 @@ at :: proc(s: ^String, i: int, loc := #caller_location) -> (r: rune) { return case s.rune_count-1: - r, s.width = utf8.decode_rune_in_string(s.contents) + r, s.width = utf8.decode_last_rune(s.contents) s.rune_pos = i s.byte_pos = _len(s.contents) - s.width return diff --git a/examples/README.md b/examples/README.md index 27072a480..90076df23 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,8 +2,8 @@ The `example` directory contains two packages: -A [demo](examples/demo) illustrating the basics of Odin. +A [demo](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. +It further contains [all](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. diff --git a/examples/all/all_experimental.odin b/examples/all/all_experimental.odin index 1d4eb4bb9..f00098abd 100644 --- a/examples/all/all_experimental.odin +++ b/examples/all/all_experimental.odin @@ -1,8 +1,2 @@ #+build windows package all - -import c_tokenizer "core:c/frontend/tokenizer" -import c_preprocessor "core:c/frontend/preprocessor" - -_ :: c_tokenizer -_ :: c_preprocessor diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 5bd45fda4..4a8a198d3 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -135,6 +135,7 @@ import table "core:text/table" import thread "core:thread" import time "core:time" import datetime "core:time/datetime" +import timezone "core:time/timezone" import flags "core:flags" import orca "core:sys/orca" @@ -258,6 +259,7 @@ _ :: edit _ :: thread _ :: time _ :: datetime +_ :: timezone _ :: flags _ :: orca _ :: sysinfo diff --git a/examples/all/all_posix.odin b/examples/all/all_posix.odin index 76fac0b87..61b33a5c6 100644 --- a/examples/all/all_posix.odin +++ b/examples/all/all_posix.odin @@ -1,6 +1,8 @@ #+build darwin, openbsd, freebsd, netbsd package all -import posix "core:sys/posix" +import posix "core:sys/posix" +import kqueue "core:sys/kqueue" _ :: posix +_ :: kqueue diff --git a/examples/all/all_vendor.odin b/examples/all/all_vendor.odin index 1ab1debea..b224a3bbe 100644 --- a/examples/all/all_vendor.odin +++ b/examples/all/all_vendor.odin @@ -63,11 +63,15 @@ _ :: xlib // NOTE: needed for doc generator import NS "core:sys/darwin/Foundation" +import CF "core:sys/darwin/CoreFoundation" +import SEC "core:sys/darwin/Security" import MTL "vendor:darwin/Metal" import MTK "vendor:darwin/MetalKit" import CA "vendor:darwin/QuartzCore" _ :: NS +_ :: CF +_ :: SEC _ :: MTL _ :: MTK _ :: CA diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 38b0f1739..36d1359ca 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -2052,22 +2052,6 @@ explicit_context_definition :: proc "c" () { dummy_procedure() } -relative_data_types :: proc() { - fmt.println("\n#relative data types") - - x: int = 123 - ptr: #relative(i16) ^int - ptr = &x - fmt.println(ptr^) - - arr := [3]int{1, 2, 3} - multi_ptr: #relative(i16) [^]int - multi_ptr = &arr[0] - fmt.println(multi_ptr) - fmt.println(multi_ptr[:3]) - fmt.println(multi_ptr[1]) -} - or_else_operator :: proc() { fmt.println("\n#'or_else'") { @@ -2151,7 +2135,7 @@ or_return_operator :: proc() { return .None } foo_2 :: proc() -> (n: int, err: Error) { - // It is more common that your procedure turns multiple values + // It is more common that your procedure returns multiple values // If 'or_return' is used within a procedure multiple parameters (2+), // then all the parameters must be named so that the remaining parameters // so that a bare 'return' statement can be used @@ -2634,7 +2618,6 @@ main :: proc() { constant_literal_expressions() union_maybe() explicit_context_definition() - relative_data_types() or_else_operator() or_return_operator() or_break_and_or_continue_operators() diff --git a/misc/get-date.c b/misc/get-date.c new file mode 100644 index 000000000..bf5b32738 --- /dev/null +++ b/misc/get-date.c @@ -0,0 +1,13 @@ +/* + Prints the current date as YYYYMMDD + + e.g. 2024-12-25 +*/ +#include +#include + +int main(int arg_count, char const **arg_ptr) { + time_t t = time(NULL); + struct tm* now = localtime(&t); + printf("%04d%02d%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday); +} \ No newline at end of file diff --git a/misc/odin.rc b/misc/odin.rc index 9e605f6dc..70d395030 100644 --- a/misc/odin.rc +++ b/misc/odin.rc @@ -55,12 +55,12 @@ BEGIN BLOCK "0409FDE9" BEGIN VALUE "CompanyName", "https://odin-lang.org/" - VALUE "FileDescription", "Odin general-purpose programming language." // note this is shown in the task manager + VALUE "FileDescription", "Odin" // note this is shown in the task manager VALUE "FileVersion", QUOTE(VF) VALUE "InternalName", "odin.exe" VALUE "LegalCopyright", "Copyright (c) 2016-2024 Ginger Bill. All rights reserved." VALUE "OriginalFilename", "odin.exe" - VALUE "ProductName", "The Odin Programming Language" + VALUE "ProductName", "Odin Programming Language" VALUE "ProductVersion", QUOTE(VP) VALUE "Comments", QUOTE(git-sha: GIT_SHA) // custom values diff --git a/odin.rdi b/odin.rdi new file mode 100644 index 000000000..851ee3578 Binary files /dev/null and b/odin.rdi differ diff --git a/shell.nix b/shell.nix index 21301b9d7..040c7696e 100644 --- a/shell.nix +++ b/shell.nix @@ -3,6 +3,7 @@ pkgs.mkShell { name = "odin"; nativeBuildInputs = with pkgs; [ git + which clang_17 llvmPackages_17.llvm llvmPackages_17.bintools diff --git a/src/big_int.cpp b/src/big_int.cpp index 83235483c..8e476f090 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -62,6 +62,7 @@ gb_internal void big_int_shl (BigInt *dst, BigInt const *x, BigInt const *y); gb_internal void big_int_shr (BigInt *dst, BigInt const *x, BigInt const *y); gb_internal void big_int_mul (BigInt *dst, BigInt const *x, BigInt const *y); gb_internal void big_int_mul_u64(BigInt *dst, BigInt const *x, u64 y); +gb_internal void big_int_exp_u64(BigInt *dst, BigInt const *x, u64 y, bool *success); gb_internal void big_int_quo_rem(BigInt const *x, BigInt const *y, BigInt *q, BigInt *r); gb_internal void big_int_quo (BigInt *z, BigInt const *x, BigInt const *y); @@ -250,9 +251,7 @@ gb_internal void big_int_from_string(BigInt *dst, String const &s, bool *success exp *= 10; exp += v; } - for (u64 x = 0; x < exp; x++) { - big_int_mul_eq(dst, &b); - } + big_int_exp_u64(dst, &b, exp, success); } if (is_negative) { @@ -328,6 +327,18 @@ gb_internal void big_int_mul_u64(BigInt *dst, BigInt const *x, u64 y) { big_int_dealloc(&d); } +gb_internal void big_int_exp_u64(BigInt *dst, BigInt const *x, u64 y, bool *success) { + if (y > INT_MAX) { + *success = false; + return; + } + + // Note: The cutoff for square-multiply being faster than the naive + // for loop is when exp > 4, but it probably isn't worth adding + // a fast path. + mp_err err = mp_expt_n(x, int(y), dst); + *success = err == MP_OKAY; +} gb_internal void big_int_mul(BigInt *dst, BigInt const *x, BigInt const *y) { mp_mul(x, y, dst); diff --git a/src/bug_report.cpp b/src/bug_report.cpp index c5a8adea3..ca5d0a395 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -2,12 +2,6 @@ Gather and print platform and version info to help with reporting Odin bugs. */ -#if !defined(GB_COMPILER_MSVC) - #if defined(GB_CPU_X86) - #include - #endif -#endif - #if defined(GB_SYSTEM_LINUX) #include #include @@ -154,21 +148,6 @@ gb_internal void report_windows_product_type(DWORD ProductType) { } #endif -gb_internal void odin_cpuid(int leaf, int result[]) { - #if defined(GB_CPU_ARM) || defined(GB_CPU_RISCV) - return; - - #elif defined(GB_CPU_X86) - - #if defined(GB_COMPILER_MSVC) - __cpuid(result, leaf); - #else - __get_cpuid(leaf, (unsigned int*)&result[0], (unsigned int*)&result[1], (unsigned int*)&result[2], (unsigned int*)&result[3]); - #endif - - #endif -} - gb_internal void report_cpu_info() { gb_printf("\tCPU: "); @@ -547,387 +526,46 @@ gb_internal void report_os_info() { gb_printf(", %s %s\n", info->sysname, info->release); #elif defined(GB_SYSTEM_OSX) - struct Darwin_To_Release { - const char* build; // 21G83 - int darwin[3]; // Darwin kernel triplet - const char* os_name; // OS X, MacOS - struct { - const char* name; // Monterey, Mojave, etc. - int version[3]; // 12.4, etc. - } release; - }; - - Darwin_To_Release macos_release_map[] = { - {"8A428", { 8, 0, 0}, "macOS", {"Tiger", {10, 4, 0}}}, - {"8A432", { 8, 0, 0}, "macOS", {"Tiger", {10, 4, 0}}}, - {"8B15", { 8, 1, 0}, "macOS", {"Tiger", {10, 4, 1}}}, - {"8B17", { 8, 1, 0}, "macOS", {"Tiger", {10, 4, 1}}}, - {"8C46", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8C47", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8E102", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8E45", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8E90", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8F46", { 8, 3, 0}, "macOS", {"Tiger", {10, 4, 3}}}, - {"8G32", { 8, 4, 0}, "macOS", {"Tiger", {10, 4, 4}}}, - {"8G1165", { 8, 4, 0}, "macOS", {"Tiger", {10, 4, 4}}}, - {"8H14", { 8, 5, 0}, "macOS", {"Tiger", {10, 4, 5}}}, - {"8G1454", { 8, 5, 0}, "macOS", {"Tiger", {10, 4, 5}}}, - {"8I127", { 8, 6, 0}, "macOS", {"Tiger", {10, 4, 6}}}, - {"8I1119", { 8, 6, 0}, "macOS", {"Tiger", {10, 4, 6}}}, - {"8J135", { 8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - {"8J2135a", { 8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - {"8K1079", { 8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - {"8N5107", { 8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - {"8L127", { 8, 8, 0}, "macOS", {"Tiger", {10, 4, 8}}}, - {"8L2127", { 8, 8, 0}, "macOS", {"Tiger", {10, 4, 8}}}, - {"8P135", { 8, 9, 0}, "macOS", {"Tiger", {10, 4, 9}}}, - {"8P2137", { 8, 9, 0}, "macOS", {"Tiger", {10, 4, 9}}}, - {"8R218", { 8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - {"8R2218", { 8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - {"8R2232", { 8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - {"8S165", { 8, 11, 0}, "macOS", {"Tiger", {10, 4, 11}}}, - {"8S2167", { 8, 11, 0}, "macOS", {"Tiger", {10, 4, 11}}}, - {"9A581", { 9, 0, 0}, "macOS", {"Leopard", {10, 5, 0}}}, - {"9B18", { 9, 1, 0}, "macOS", {"Leopard", {10, 5, 1}}}, - {"9B2117", { 9, 1, 1}, "macOS", {"Leopard", {10, 5, 1}}}, - {"9C31", { 9, 2, 0}, "macOS", {"Leopard", {10, 5, 2}}}, - {"9C7010", { 9, 2, 0}, "macOS", {"Leopard", {10, 5, 2}}}, - {"9D34", { 9, 3, 0}, "macOS", {"Leopard", {10, 5, 3}}}, - {"9E17", { 9, 4, 0}, "macOS", {"Leopard", {10, 5, 4}}}, - {"9F33", { 9, 5, 0}, "macOS", {"Leopard", {10, 5, 5}}}, - {"9G55", { 9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - {"9G66", { 9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - {"9G71", { 9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - {"9J61", { 9, 7, 0}, "macOS", {"Leopard", {10, 5, 7}}}, - {"9L30", { 9, 8, 0}, "macOS", {"Leopard", {10, 5, 8}}}, - {"9L34", { 9, 8, 0}, "macOS", {"Leopard", {10, 5, 8}}}, - {"10A432", {10, 0, 0}, "macOS", {"Snow Leopard", {10, 6, 0}}}, - {"10A433", {10, 0, 0}, "macOS", {"Snow Leopard", {10, 6, 0}}}, - {"10B504", {10, 1, 0}, "macOS", {"Snow Leopard", {10, 6, 1}}}, - {"10C540", {10, 2, 0}, "macOS", {"Snow Leopard", {10, 6, 2}}}, - {"10D573", {10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - {"10D575", {10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - {"10D578", {10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - {"10F569", {10, 4, 0}, "macOS", {"Snow Leopard", {10, 6, 4}}}, - {"10H574", {10, 5, 0}, "macOS", {"Snow Leopard", {10, 6, 5}}}, - {"10J567", {10, 6, 0}, "macOS", {"Snow Leopard", {10, 6, 6}}}, - {"10J869", {10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - {"10J3250", {10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - {"10J4138", {10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - {"10K540", {10, 8, 0}, "macOS", {"Snow Leopard", {10, 6, 8}}}, - {"10K549", {10, 8, 0}, "macOS", {"Snow Leopard", {10, 6, 8}}}, - {"11A511", {11, 0, 0}, "macOS", {"Lion", {10, 7, 0}}}, - {"11A511s", {11, 0, 0}, "macOS", {"Lion", {10, 7, 0}}}, - {"11A2061", {11, 0, 2}, "macOS", {"Lion", {10, 7, 0}}}, - {"11A2063", {11, 0, 2}, "macOS", {"Lion", {10, 7, 0}}}, - {"11B26", {11, 1, 0}, "macOS", {"Lion", {10, 7, 1}}}, - {"11B2118", {11, 1, 0}, "macOS", {"Lion", {10, 7, 1}}}, - {"11C74", {11, 2, 0}, "macOS", {"Lion", {10, 7, 2}}}, - {"11D50", {11, 3, 0}, "macOS", {"Lion", {10, 7, 3}}}, - {"11E53", {11, 4, 0}, "macOS", {"Lion", {10, 7, 4}}}, - {"11G56", {11, 4, 2}, "macOS", {"Lion", {10, 7, 5}}}, - {"11G63", {11, 4, 2}, "macOS", {"Lion", {10, 7, 5}}}, - {"12A269", {12, 0, 0}, "macOS", {"Mountain Lion", {10, 8, 0}}}, - {"12B19", {12, 1, 0}, "macOS", {"Mountain Lion", {10, 8, 1}}}, - {"12C54", {12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - {"12C60", {12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - {"12C2034", {12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - {"12C3104", {12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - {"12D78", {12, 3, 0}, "macOS", {"Mountain Lion", {10, 8, 3}}}, - {"12E55", {12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - {"12E3067", {12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - {"12E4022", {12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - {"12F37", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F45", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F2501", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F2518", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F2542", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F2560", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"13A603", {13, 0, 0}, "macOS", {"Mavericks", {10, 9, 0}}}, - {"13B42", {13, 0, 0}, "macOS", {"Mavericks", {10, 9, 1}}}, - {"13C64", {13, 1, 0}, "macOS", {"Mavericks", {10, 9, 2}}}, - {"13C1021", {13, 1, 0}, "macOS", {"Mavericks", {10, 9, 2}}}, - {"13D65", {13, 2, 0}, "macOS", {"Mavericks", {10, 9, 3}}}, - {"13E28", {13, 3, 0}, "macOS", {"Mavericks", {10, 9, 4}}}, - {"13F34", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1066", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1077", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1096", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1112", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1134", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1507", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1603", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1712", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1808", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1911", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"14A389", {14, 0, 0}, "macOS", {"Yosemite", {10, 10, 0}}}, - {"14B25", {14, 0, 0}, "macOS", {"Yosemite", {10, 10, 1}}}, - {"14C109", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14C1510", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14C2043", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14C1514", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14C2513", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14D131", {14, 3, 0}, "macOS", {"Yosemite", {10, 10, 3}}}, - {"14D136", {14, 3, 0}, "macOS", {"Yosemite", {10, 10, 3}}}, - {"14E46", {14, 4, 0}, "macOS", {"Yosemite", {10, 10, 4}}}, - {"14F27", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1021", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1505", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1509", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1605", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1713", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1808", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1909", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1912", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2009", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2109", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2315", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2411", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2511", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"15A284", {15, 0, 0}, "macOS", {"El Capitan", {10, 11, 0}}}, - {"15B42", {15, 0, 0}, "macOS", {"El Capitan", {10, 11, 1}}}, - {"15C50", {15, 2, 0}, "macOS", {"El Capitan", {10, 11, 2}}}, - {"15D21", {15, 3, 0}, "macOS", {"El Capitan", {10, 11, 3}}}, - {"15E65", {15, 4, 0}, "macOS", {"El Capitan", {10, 11, 4}}}, - {"15F34", {15, 5, 0}, "macOS", {"El Capitan", {10, 11, 5}}}, - {"15G31", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1004", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1011", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1108", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1212", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1217", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1421", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1510", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1611", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G17023", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G18013", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G19009", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G20015", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G21013", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G22010", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"16A323", {16, 0, 0}, "macOS", {"Sierra", {10, 12, 0}}}, - {"16B2555", {16, 1, 0}, "macOS", {"Sierra", {10, 12, 1}}}, - {"16B2657", {16, 1, 0}, "macOS", {"Sierra", {10, 12, 1}}}, - {"16C67", {16, 3, 0}, "macOS", {"Sierra", {10, 12, 2}}}, - {"16C68", {16, 3, 0}, "macOS", {"Sierra", {10, 12, 2}}}, - {"16D32", {16, 4, 0}, "macOS", {"Sierra", {10, 12, 3}}}, - {"16E195", {16, 5, 0}, "macOS", {"Sierra", {10, 12, 4}}}, - {"16F73", {16, 6, 0}, "macOS", {"Sierra", {10, 12, 5}}}, - {"16F2073", {16, 6, 0}, "macOS", {"Sierra", {10, 12, 5}}}, - {"16G29", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1036", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1114", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1212", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1314", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1408", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1510", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1618", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1710", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1815", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1917", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1918", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G2016", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G2127", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G2128", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G2136", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"17A365", {17, 0, 0}, "macOS", {"High Sierra", {10, 13, 0}}}, - {"17A405", {17, 0, 0}, "macOS", {"High Sierra", {10, 13, 0}}}, - {"17B48", {17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - {"17B1002", {17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - {"17B1003", {17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - {"17C88", {17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - {"17C89", {17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - {"17C205", {17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - {"17C2205", {17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - {"17D47", {17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - {"17D2047", {17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - {"17D102", {17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - {"17D2102", {17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - {"17E199", {17, 5, 0}, "macOS", {"High Sierra", {10, 13, 4}}}, - {"17E202", {17, 5, 0}, "macOS", {"High Sierra", {10, 13, 4}}}, - {"17F77", {17, 6, 0}, "macOS", {"High Sierra", {10, 13, 5}}}, - {"17G65", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G2208", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G2307", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G3025", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G4015", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G5019", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G6029", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G6030", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G7024", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G8029", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G8030", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G8037", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G9016", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G10021", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G11023", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G12034", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G13033", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G13035", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G14019", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G14033", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G14042", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"18A391", {18, 0, 0}, "macOS", {"Mojave", {10, 14, 0}}}, - {"18B75", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - {"18B2107", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - {"18B3094", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - {"18C54", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 2}}}, - {"18D42", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - {"18D43", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - {"18D109", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - {"18E226", {18, 5, 0}, "macOS", {"Mojave", {10, 14, 4}}}, - {"18E227", {18, 5, 0}, "macOS", {"Mojave", {10, 14, 4}}}, - {"18F132", {18, 6, 0}, "macOS", {"Mojave", {10, 14, 5}}}, - {"18G84", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G87", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G95", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G103", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G1012", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G2022", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G3020", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G4032", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G5033", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G6020", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G6032", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G6042", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G7016", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G8012", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G8022", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G9028", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G9216", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G9323", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"19A583", {19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - {"19A602", {19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - {"19A603", {19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - {"19B88", {19, 0, 0}, "macOS", {"Catalina", {10, 15, 1}}}, - {"19C57", {19, 2, 0}, "macOS", {"Catalina", {10, 15, 2}}}, - {"19C58", {19, 2, 0}, "macOS", {"Catalina", {10, 15, 2}}}, - {"19D76", {19, 3, 0}, "macOS", {"Catalina", {10, 15, 3}}}, - {"19E266", {19, 4, 0}, "macOS", {"Catalina", {10, 15, 4}}}, - {"19E287", {19, 4, 0}, "macOS", {"Catalina", {10, 15, 4}}}, - {"19F96", {19, 5, 0}, "macOS", {"Catalina", {10, 15, 5}}}, - {"19F101", {19, 5, 0}, "macOS", {"Catalina", {10, 15, 5}}}, - {"19G73", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 6}}}, - {"19G2021", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 6}}}, - {"19H2", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H4", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H15", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H114", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H512", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H524", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1030", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1217", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1323", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1417", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1419", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1519", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1615", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1713", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1715", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1824", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1922", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H2026", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"20A2411", {20, 1, 0}, "macOS", {"Big Sur", {11, 0, 0}}}, - {"20B29", {20, 1, 0}, "macOS", {"Big Sur", {11, 0, 1}}}, - {"20B50", {20, 1, 0}, "macOS", {"Big Sur", {11, 0, 1}}}, - {"20C69", {20, 2, 0}, "macOS", {"Big Sur", {11, 1, 0}}}, - {"20D64", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 0}}}, - {"20D74", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 1}}}, - {"20D75", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 1}}}, - {"20D80", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 2}}}, - {"20D91", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 3}}}, - {"20E232", {20, 4, 0}, "macOS", {"Big Sur", {11, 3, 0}}}, - {"20E241", {20, 4, 0}, "macOS", {"Big Sur", {11, 3, 1}}}, - {"20F71", {20, 5, 0}, "macOS", {"Big Sur", {11, 4, 0}}}, - {"20G71", {20, 6, 0}, "macOS", {"Big Sur", {11, 5, 0}}}, - {"20G80", {20, 6, 0}, "macOS", {"Big Sur", {11, 5, 1}}}, - {"20G95", {20, 6, 0}, "macOS", {"Big Sur", {11, 5, 2}}}, - {"20G165", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 0}}}, - {"20G224", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 1}}}, - {"20G314", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 2}}}, - {"20G415", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 3}}}, - {"20G417", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 4}}}, - {"20G527", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 5}}}, - {"20G624", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 6}}}, - {"20G630", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 7}}}, - {"20G730", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 8}}}, - {"20G817", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 0}}}, - {"20G918", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 1}}}, - {"20G1020", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 2}}}, - {"20G1116", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 3}}}, - {"20G1120", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 4}}}, - {"20G1225", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 5}}}, - {"20G1231", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 6}}}, - {"20G1345", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 7}}}, - {"20G1351", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 8}}}, - {"20G1426", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 9}}}, - {"20G1427", {20, 6, 0}, "macOS", {"Big Sur", {11, 7, 10}}}, - {"21A344", {21, 0, 1}, "macOS", {"Monterey", {12, 0, 0}}}, - {"21A559", {21, 1, 0}, "macOS", {"Monterey", {12, 0, 1}}}, - {"21C52", {21, 2, 0}, "macOS", {"Monterey", {12, 1, 0}}}, - {"21D49", {21, 3, 0}, "macOS", {"Monterey", {12, 2, 0}}}, - {"21D62", {21, 3, 0}, "macOS", {"Monterey", {12, 2, 1}}}, - {"21E230", {21, 4, 0}, "macOS", {"Monterey", {12, 3, 0}}}, - {"21E258", {21, 4, 0}, "macOS", {"Monterey", {12, 3, 1}}}, - {"21F79", {21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - {"21F2081", {21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - {"21F2092", {21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - {"21G72", {21, 6, 0}, "macOS", {"Monterey", {12, 5, 0}}}, - {"21G83", {21, 6, 0}, "macOS", {"Monterey", {12, 5, 1}}}, - {"21G115", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 0}}}, - {"21G217", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 1}}}, - {"21G320", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 2}}}, - {"21G419", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 3}}}, - {"21G526", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 4}}}, - {"21G531", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 5}}}, - {"21G646", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 6}}}, - {"21G651", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 7}}}, - {"21G725", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 8}}}, - {"21G726", {21, 6, 0}, "macOS", {"Monterey", {12, 6, 9}}}, - {"21G816", {21, 6, 0}, "macOS", {"Monterey", {12, 7, 0}}}, - {"21G920", {21, 6, 0}, "macOS", {"Monterey", {12, 7, 1}}}, - {"21G1974", {21, 6, 0}, "macOS", {"Monterey", {12, 7, 2}}}, - {"22A380", {13, 0, 0}, "macOS", {"Ventura", {22, 1, 0}}}, - {"22A400", {13, 0, 1}, "macOS", {"Ventura", {22, 1, 0}}}, - {"22C65", {13, 1, 0}, "macOS", {"Ventura", {22, 2, 0}}}, - {"22D49", {13, 2, 0}, "macOS", {"Ventura", {22, 3, 0}}}, - {"22D68", {13, 2, 1}, "macOS", {"Ventura", {22, 3, 0}}}, - {"22E252", {13, 3, 0}, "macOS", {"Ventura", {22, 4, 0}}}, - {"22E261", {13, 3, 1}, "macOS", {"Ventura", {22, 4, 0}}}, - {"22F66", {13, 4, 0}, "macOS", {"Ventura", {22, 5, 0}}}, - {"22F82", {13, 4, 1}, "macOS", {"Ventura", {22, 5, 0}}}, - {"22E772610a", {13, 4, 1}, "macOS", {"Ventura", {22, 5, 0}}}, - {"22F770820d", {13, 4, 1}, "macOS", {"Ventura", {22, 5, 0}}}, - {"22G74", {13, 5, 0}, "macOS", {"Ventura", {22, 6, 0}}}, - {"22G90", {13, 5, 1}, "macOS", {"Ventura", {22, 6, 0}}}, - {"22G91", {13, 5, 2}, "macOS", {"Ventura", {22, 6, 0}}}, - {"22G120", {13, 6, 0}, "macOS", {"Ventura", {22, 6, 0}}}, - {"22G313", {13, 6, 1}, "macOS", {"Ventura", {22, 6, 0}}}, - {"22G320", {13, 6, 2}, "macOS", {"Ventura", {22, 6, 0}}}, - {"23A344", {23, 0, 0}, "macOS", {"Sonoma", {14, 0, 0}}}, - {"23B74", {23, 1, 0}, "macOS", {"Sonoma", {14, 1, 0}}}, - {"23B81", {23, 1, 0}, "macOS", {"Sonoma", {14, 1, 1}}}, - {"23B2082", {23, 1, 0}, "macOS", {"Sonoma", {14, 1, 1}}}, - {"23B92", {23, 1, 0}, "macOS", {"Sonoma", {14, 1, 2}}}, - {"23B2091", {23, 1, 0}, "macOS", {"Sonoma", {14, 1, 2}}}, - {"23C64", {23, 2, 0}, "macOS", {"Sonoma", {14, 2, 0}}}, - {"23C71", {23, 2, 0}, "macOS", {"Sonoma", {14, 2, 1}}}, - {"23D56", {23, 3, 0}, "macOS", {"Sonoma", {14, 3, 0}}}, - {"23D60", {23, 3, 0}, "macOS", {"Sonoma", {14, 3, 1}}}, - {"23E214", {23, 4, 0}, "macOS", {"Sonoma", {14, 4, 0}}}, - {"23E224", {23, 4, 0}, "macOS", {"Sonoma", {14, 4, 1}}}, - {"23F79", {23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}}, - {"23G80", {23, 6, 0}, "macOS", {"Sonoma", {14, 6, 0}}}, - {"23G93", {23, 6, 0}, "macOS", {"Sonoma", {14, 6, 1}}}, - {"23H124", {23, 6, 0}, "macOS", {"Sonoma", {14, 7, 0}}}, - {"24A335", {24, 0, 0}, "macOS", {"Sequoia", {15, 0, 0}}}, - {"24A348", {24, 0, 0}, "macOS", {"Sequoia", {15, 0, 1}}}, + gbString sw_vers = gb_string_make(heap_allocator(), ""); + if (!system_exec_command_line_app_output("sw_vers --productVersion", &sw_vers)) { + gb_printf("macOS Unknown\n"); + return; + } + + uint32_t major, minor, patch; + + if (sscanf(cast(const char *)sw_vers, "%u.%u.%u", &major, &minor, &patch) != 3) { + gb_printf("macOS Unknown\n"); + return; + } + + switch (major) { + case 15: gb_printf("macOS Sequoia"); break; + case 14: gb_printf("macOS Sonoma"); break; + case 13: gb_printf("macOS Ventura"); break; + case 12: gb_printf("macOS Monterey"); break; + case 11: gb_printf("macOS Big Sur"); break; + case 10: + { + switch (minor) { + case 15: gb_printf("macOS Catalina"); break; + case 14: gb_printf("macOS Mojave"); break; + case 13: gb_printf("macOS High Sierra"); break; + case 12: gb_printf("macOS Sierra"); break; + case 11: gb_printf("OS X El Capitan"); break; + case 10: gb_printf("OS X Yosemite"); break; + default: gb_printf("macOS Unknown"); + }; + break; + } + default: + gb_printf("macOS Unknown"); }; + gb_printf(" %d.%d.%d (build ", major, minor, patch); b32 build_found = 1; b32 darwin_found = 1; - uint32_t major, minor, patch; #define MACOS_VERSION_BUFFER_SIZE 100 char build_buffer[MACOS_VERSION_BUFFER_SIZE]; @@ -943,81 +581,25 @@ gb_internal void report_os_info() { int darwin_mibs[] = { CTL_KERN, KERN_OSRELEASE }; if (sysctl(darwin_mibs, 2, darwin_buffer, &darwin_buffer_size, NULL, 0) == -1) { - gb_printf("macOS Unknown\n"); - return; + darwin_found = 0; } else { if (sscanf(darwin_buffer, "%u.%u.%u", &major, &minor, &patch) != 3) { darwin_found = 0; } } - // Scan table for match on BUILD - int macos_release_count = sizeof(macos_release_map) / sizeof(macos_release_map[0]); - Darwin_To_Release build_match = {}; - Darwin_To_Release kernel_match = {}; - - for (int build = 0; build < macos_release_count; build++) { - Darwin_To_Release rel = macos_release_map[build]; - - // Do we have an exact match on the BUILD? - if (gb_strcmp(rel.build, (const char *)build_buffer) == 0) { - build_match = rel; - break; - } - - // Do we have an exact Darwin match? - if (rel.darwin[0] == major && rel.darwin[1] == minor && rel.darwin[2] == patch) { - kernel_match = rel; - } - - // Major kernel version needs to match exactly, - if (rel.darwin[0] == major) { - // No major version match yet. - if (!kernel_match.os_name) { - kernel_match = rel; - } - if (minor >= rel.darwin[1]) { - kernel_match = rel; - if (patch >= rel.darwin[2]) { - kernel_match = rel; - } - } - } - } - - Darwin_To_Release match = {}; - if(!build_match.build) { - match = kernel_match; + if (build_found) { + gb_printf("%s, kernel ", build_buffer); } else { - match = build_match; + gb_printf("Unknown, kernel "); } - if (match.os_name) { - gb_printf("%s %s %d", match.os_name, match.release.name, match.release.version[0]); - if (match.release.version[1] > 0 || match.release.version[2] > 0) { - gb_printf(".%d", match.release.version[1]); - } - if (match.release.version[2] > 0) { - gb_printf(".%d", match.release.version[2]); - } - if (build_found) { - gb_printf(" (build: %s, kernel: %d.%d.%d)\n", build_buffer, match.darwin[0], match.darwin[1], match.darwin[2]); - } else { - gb_printf(" (build: %s?, kernel: %d.%d.%d)\n", match.build, match.darwin[0], match.darwin[1], match.darwin[2]); - } - return; + if (darwin_found) { + gb_printf("%s)\n", darwin_buffer); + } else { + gb_printf("Unknown)\n"); } - if (build_found && darwin_found) { - gb_printf("macOS Unknown (build: %s, kernel: %d.%d.%d)\n", build_buffer, major, minor, patch); - return; - } else if (build_found) { - gb_printf("macOS Unknown (build: %s)\n", build_buffer); - return; - } else if (darwin_found) { - gb_printf("macOS Unknown (kernel: %d.%d.%d)\n", major, minor, patch); - return; - } #elif defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) struct utsname un; diff --git a/src/build_cpuid.cpp b/src/build_cpuid.cpp new file mode 100644 index 000000000..b7ba5dcdf --- /dev/null +++ b/src/build_cpuid.cpp @@ -0,0 +1,35 @@ +#if !defined(GB_COMPILER_MSVC) + #if defined(GB_CPU_X86) + #include + #endif +#endif + +gb_internal void odin_cpuid(int leaf, int result[]) { + #if defined(GB_CPU_ARM) || defined(GB_CPU_RISCV) + return; + + #elif defined(GB_CPU_X86) + + #if defined(GB_COMPILER_MSVC) + __cpuid(result, leaf); + #else + __get_cpuid(leaf, (unsigned int*)&result[0], (unsigned int*)&result[1], (unsigned int*)&result[2], (unsigned int*)&result[3]); + #endif + + #endif +} + +gb_internal bool should_use_march_native() { + #if !defined(GB_CPU_X86) + return false; + + #else + + int cpu[4]; + odin_cpuid(0x1, &cpu[0]); // Get feature information in ECX + EDX + + bool have_popcnt = cpu[2] & (1 << 23); // bit 23 in ECX = popcnt + return !have_popcnt; + + #endif +} \ No newline at end of file diff --git a/src/build_settings.cpp b/src/build_settings.cpp index e365d0324..4c3f4b782 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -2,6 +2,7 @@ #include #include #endif +#include "build_cpuid.cpp" // #if defined(GB_SYSTEM_WINDOWS) // #define DEFAULT_TO_THREADED_CHECKER @@ -343,6 +344,22 @@ struct BuildCacheData { bool copy_already_done; }; + +enum LinkerChoice : i32 { + Linker_Invalid = -1, + Linker_Default = 0, + Linker_lld, + Linker_radlink, + + Linker_COUNT, +}; + +String linker_choices[Linker_COUNT] = { + str_lit("default"), + str_lit("lld"), + str_lit("radlink"), +}; + // This stores the information for the specify architecture of this build struct BuildContext { // Constants @@ -418,12 +435,13 @@ struct BuildContext { bool no_rpath; bool no_entry_point; bool no_thread_local; - bool use_lld; bool cross_compiling; bool different_os; bool keep_object_files; bool disallow_do; + LinkerChoice linker_choice; + StringSet custom_attributes; bool strict_style; @@ -449,11 +467,12 @@ struct BuildContext { BuildCacheData build_cache_data; bool internal_no_inline; + bool internal_by_value; bool no_threaded_checker; bool show_debug_messages; - + bool copy_file_contents; bool no_rtti; @@ -1870,7 +1889,7 @@ gb_internal bool init_build_paths(String init_filename) { return false; } - if (!build_context.use_lld && find_result.vs_exe_path.len == 0) { + if (build_context.linker_choice == Linker_Default && find_result.vs_exe_path.len == 0) { gb_printf_err("link.exe not found.\n"); return false; } @@ -2035,7 +2054,7 @@ gb_internal bool init_build_paths(String init_filename) { // Do we have an extension? We might not if the output filename was supplied. if (bc->build_paths[BuildPath_Output].ext.len == 0) { - if (build_context.metrics.os == TargetOs_windows || build_context.build_mode != BuildMode_Executable) { + if (build_context.metrics.os == TargetOs_windows || is_arch_wasm() || build_context.build_mode != BuildMode_Executable) { bc->build_paths[BuildPath_Output].ext = copy_string(ha, output_extension); } } diff --git a/src/cached.cpp b/src/cached.cpp index 4ad65ee9e..efdadce7b 100644 --- a/src/cached.cpp +++ b/src/cached.cpp @@ -187,10 +187,7 @@ gb_internal bool try_copy_executable_from_cache(void) { extern char **environ; #endif -// returns false if different, true if it is the same -gb_internal bool try_cached_build(Checker *c, Array const &args) { - TEMPORARY_ALLOCATOR_GUARD(); - +Array cache_gather_files(Checker *c) { Parser *p = c->parser; auto files = array_make(heap_allocator()); @@ -222,29 +219,11 @@ gb_internal bool try_cached_build(Checker *c, Array const &args) { array_sort(files, string_cmp); - u64 crc = 0; - for (String const &path : files) { - crc = crc64_with_seed(path.text, path.len, crc); - } - - String base_cache_dir = build_context.build_paths[BuildPath_Output].basename; - base_cache_dir = concatenate_strings(permanent_allocator(), base_cache_dir, str_lit("/.odin-cache")); - (void)check_if_exists_directory_otherwise_create(base_cache_dir); - - gbString crc_str = gb_string_make_reserve(permanent_allocator(), 16); - crc_str = gb_string_append_fmt(crc_str, "%016llx", crc); - String cache_dir = concatenate3_strings(permanent_allocator(), base_cache_dir, str_lit("/"), make_string_c(crc_str)); - String files_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("files.manifest")); - String args_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("args.manifest")); - String env_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("env.manifest")); - - build_context.build_cache_data.cache_dir = cache_dir; - build_context.build_cache_data.files_path = files_path; - build_context.build_cache_data.args_path = args_path; - build_context.build_cache_data.env_path = env_path; + return files; +} +Array cache_gather_envs() { auto envs = array_make(heap_allocator()); - defer (array_free(&envs)); { #if defined(GB_SYSTEM_WINDOWS) wchar_t *strings = GetEnvironmentStringsW(); @@ -275,19 +254,50 @@ gb_internal bool try_cached_build(Checker *c, Array const &args) { #endif } array_sort(envs, string_cmp); + return envs; +} + +// returns false if different, true if it is the same +gb_internal bool try_cached_build(Checker *c, Array const &args) { + TEMPORARY_ALLOCATOR_GUARD(); + + auto files = cache_gather_files(c); + auto envs = cache_gather_envs(); + defer (array_free(&envs)); + + u64 crc = 0; + for (String const &path : files) { + crc = crc64_with_seed(path.text, path.len, crc); + } + + String base_cache_dir = build_context.build_paths[BuildPath_Output].basename; + base_cache_dir = concatenate_strings(permanent_allocator(), base_cache_dir, str_lit("/.odin-cache")); + (void)check_if_exists_directory_otherwise_create(base_cache_dir); + + gbString crc_str = gb_string_make_reserve(permanent_allocator(), 16); + crc_str = gb_string_append_fmt(crc_str, "%016llx", crc); + String cache_dir = concatenate3_strings(permanent_allocator(), base_cache_dir, str_lit("/"), make_string_c(crc_str)); + String files_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("files.manifest")); + String args_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("args.manifest")); + String env_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("env.manifest")); + + build_context.build_cache_data.cache_dir = cache_dir; + build_context.build_cache_data.files_path = files_path; + build_context.build_cache_data.args_path = args_path; + build_context.build_cache_data.env_path = env_path; if (check_if_exists_directory_otherwise_create(cache_dir)) { - goto write_cache; + return false; } if (check_if_exists_file_otherwise_create(files_path)) { - goto write_cache; + return false; } if (check_if_exists_file_otherwise_create(args_path)) { - goto write_cache; + return false; } if (check_if_exists_file_otherwise_create(env_path)) { - goto write_cache; + return false; } { @@ -297,7 +307,7 @@ gb_internal bool try_cached_build(Checker *c, Array const &args) { LoadedFileError file_err = load_file_32( alloc_cstring(temporary_allocator(), files_path), &loaded_file, - false + true ); if (file_err > LoadedFile_Empty) { return false; @@ -315,7 +325,7 @@ gb_internal bool try_cached_build(Checker *c, Array const &args) { } isize sep = string_index_byte(line, ' '); if (sep < 0) { - goto write_cache; + return false; } String timestamp_str = substring(line, 0, sep); @@ -325,21 +335,21 @@ gb_internal bool try_cached_build(Checker *c, Array const &args) { path_str = string_trim_whitespace(path_str); if (file_count >= files.count) { - goto write_cache; + return false; } if (files[file_count] != path_str) { - goto write_cache; + return false; } u64 timestamp = exact_value_to_u64(exact_value_integer_from_string(timestamp_str)); gbFileTime last_write_time = gb_file_last_write_time(alloc_cstring(temporary_allocator(), path_str)); if (last_write_time != timestamp) { - goto write_cache; + return false; } } if (file_count != files.count) { - goto write_cache; + return false; } } { @@ -348,7 +358,7 @@ gb_internal bool try_cached_build(Checker *c, Array const &args) { LoadedFileError file_err = load_file_32( alloc_cstring(temporary_allocator(), args_path), &loaded_file, - false + true ); if (file_err > LoadedFile_Empty) { return false; @@ -366,11 +376,11 @@ gb_internal bool try_cached_build(Checker *c, Array const &args) { break; } if (args_count >= args.count) { - goto write_cache; + return false; } if (line != args[args_count]) { - goto write_cache; + return false; } } } @@ -380,7 +390,7 @@ gb_internal bool try_cached_build(Checker *c, Array const &args) { LoadedFileError file_err = load_file_32( alloc_cstring(temporary_allocator(), env_path), &loaded_file, - false + true ); if (file_err > LoadedFile_Empty) { return false; @@ -398,20 +408,26 @@ gb_internal bool try_cached_build(Checker *c, Array const &args) { break; } if (env_count >= envs.count) { - goto write_cache; + return false; } if (line != envs[env_count]) { - goto write_cache; + return false; } } } return try_copy_executable_from_cache(); +} + +void write_cached_build(Checker *c, Array const &args) { + auto files = cache_gather_files(c); + defer (array_free(&files)); + auto envs = cache_gather_envs(); + defer (array_free(&envs)); -write_cache:; { - char const *path_c = alloc_cstring(temporary_allocator(), files_path); + char const *path_c = alloc_cstring(temporary_allocator(), build_context.build_cache_data.files_path); gb_file_remove(path_c); debugf("Cache: updating %s\n", path_c); @@ -426,7 +442,7 @@ write_cache:; } } { - char const *path_c = alloc_cstring(temporary_allocator(), args_path); + char const *path_c = alloc_cstring(temporary_allocator(), build_context.build_cache_data.args_path); gb_file_remove(path_c); debugf("Cache: updating %s\n", path_c); @@ -441,7 +457,7 @@ write_cache:; } } { - char const *path_c = alloc_cstring(temporary_allocator(), env_path); + char const *path_c = alloc_cstring(temporary_allocator(), build_context.build_cache_data.env_path); gb_file_remove(path_c); debugf("Cache: updating %s\n", path_c); @@ -454,8 +470,5 @@ write_cache:; gb_fprintf(&f, "%.*s\n", LIT(env)); } } - - - return false; } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index ab18123dd..ea902387b 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1533,6 +1533,10 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c for (FileInfo fi : list) { LoadFileCache *cache = nullptr; + if (fi.is_dir) { + continue; + } + 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 { @@ -2060,8 +2064,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As bool ok = check_builtin_simd_operation(c, operand, call, id, type_hint); if (!ok) { operand->type = t_invalid; + operand->mode = Addressing_Value; } - operand->mode = Addressing_Value; operand->value = {}; operand->expr = call; return ok; @@ -2547,6 +2551,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_swizzle: { // swizzle :: proc(v: [N]T, ..int) -> [M]T + if (!operand->type) { + return false; + } + Type *original_type = operand->type; Type *type = base_type(original_type); i64 max_count = 0; @@ -2904,6 +2912,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As // imag :: proc(x: type) -> float_type Operand *x = operand; + if (!x->type) { + return false; + } + if (is_type_untyped(x->type)) { if (x->mode == Addressing_Constant) { if (is_type_numeric(x->type)) { @@ -2964,6 +2976,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As // kmag :: proc(x: type) -> float_type Operand *x = operand; + if (!x->type) { + return false; + } + if (is_type_untyped(x->type)) { if (x->mode == Addressing_Constant) { if (is_type_numeric(x->type)) { @@ -3013,6 +3029,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_conj: { // conj :: proc(x: type) -> type Operand *x = operand; + if (!x->type) { + return false; + } + Type *t = x->type; Type *elem = core_array_type(t); @@ -3053,10 +3073,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } case BuiltinProc_expand_values: { + if (!operand->type) { + return false; + } + Type *type = base_type(operand->type); if (!is_type_struct(type) && !is_type_array(type)) { gbString type_str = type_to_string(operand->type); - error(call, "Expected a struct or array type, got '%s'", type_str); + error(call, "Expected a struct or array type to 'expand_values', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -3092,8 +3116,13 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As check_multi_expr_or_type(c, operand, ce->args[0]); + if (!operand->type) { + return false; + } + Type *original_type = operand->type; Type *type = base_type(operand->type); + if (operand->mode == Addressing_Type && is_type_enumerated_array(type)) { // Okay } else if (!is_type_ordered(type) || !(is_type_numeric(type) || is_type_string(type))) { @@ -3166,6 +3195,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + if (ce->args.count <= 1) { + error(call, "Too few arguments for 'min', two or more are required"); + return false; + } bool all_constant = operand->mode == Addressing_Constant; @@ -3260,6 +3293,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As check_multi_expr_or_type(c, operand, ce->args[0]); + if (!operand->type) { + return false; + } + Type *original_type = operand->type; Type *type = base_type(operand->type); @@ -3339,6 +3376,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As gb_string_free(type_str); return false; } + + if (ce->args.count <= 1) { + error(call, "Too few arguments for 'max', two or more are required"); + return false; + } bool all_constant = operand->mode == Addressing_Constant; @@ -3430,6 +3472,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_abs: { // abs :: proc(n: numeric) -> numeric + if (!operand->type) { + return false; + } + if (!(is_type_numeric(operand->type) && !is_type_array(operand->type))) { gbString type_str = type_to_string(operand->type); error(call, "Expected a numeric type to 'abs', got '%s'", type_str); @@ -3485,6 +3531,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_clamp: { // clamp :: proc(a, min, max: ordered) -> ordered + if (!operand->type) { + return false; + } + Type *type = operand->type; if (!is_type_ordered(type) || !(is_type_numeric(type) || is_type_string(type))) { gbString type_str = type_to_string(operand->type); diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 0cc89435d..1d792dad8 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -88,17 +88,20 @@ gb_internal Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *o e->type = t_invalid; return nullptr; } else if (is_type_polymorphic(t)) { - Entity *e = entity_of_node(operand->expr); - if (e == nullptr) { - return nullptr; - } - if (e->state.load() != EntityState_Resolved) { - gbString str = type_to_string(t); - defer (gb_string_free(str)); - error(e->token, "Invalid use of a polymorphic type '%s' in %.*s", str, LIT(context_name)); + Entity *e2 = entity_of_node(operand->expr); + if (e2 == nullptr) { e->type = t_invalid; return nullptr; } + if (e2->state.load() != EntityState_Resolved) { + e->type = t; + return nullptr; + } + gbString str = type_to_string(t); + defer (gb_string_free(str)); + error(operand->expr, "Invalid use of a non-specialized polymorphic type '%s' in %.*s", str, LIT(context_name)); + e->type = t_invalid; + return nullptr; } else if (is_type_empty_union(t)) { gbString str = type_to_string(t); defer (gb_string_free(str)); @@ -970,6 +973,43 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon } } +gb_internal void check_foreign_procedure(CheckerContext *ctx, Entity *e, DeclInfo *d) { + GB_ASSERT(e != nullptr); + GB_ASSERT(e->kind == Entity_Procedure); + String name = e->Procedure.link_name; + + mutex_lock(&ctx->info->foreign_mutex); + + auto *fp = &ctx->info->foreigns; + StringHashKey key = string_hash_string(name); + Entity **found = string_map_get(fp, key); + if (found && e != *found) { + Entity *f = *found; + TokenPos pos = f->token.pos; + Type *this_type = base_type(e->type); + Type *other_type = base_type(f->type); + if (is_type_proc(this_type) && is_type_proc(other_type)) { + if (!are_signatures_similar_enough(this_type, other_type)) { + error(d->proc_lit, + "Redeclaration of foreign procedure '%.*s' with different type signatures\n" + "\tat %s", + LIT(name), token_pos_to_string(pos)); + } + } else if (!signature_parameter_similar_enough(this_type, other_type)) { + error(d->proc_lit, + "Foreign entity '%.*s' previously declared elsewhere with a different type\n" + "\tat %s", + LIT(name), token_pos_to_string(pos)); + } + } else if (name == "main") { + error(d->proc_lit, "The link name 'main' is reserved for internal use"); + } else { + string_map_set(fp, key, e); + } + + mutex_unlock(&ctx->info->foreign_mutex); +} + gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { GB_ASSERT(e->type == nullptr); if (d->proc_lit->kind != Ast_ProcLit) { @@ -1306,57 +1346,16 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { name = e->Procedure.link_name; } Entity *foreign_library = init_entity_foreign_library(ctx, e); - - if (is_arch_wasm() && foreign_library != nullptr) { - String module_name = str_lit("env"); - GB_ASSERT (foreign_library->kind == Entity_LibraryName); - if (foreign_library->LibraryName.paths.count != 1) { - error(foreign_library->token, "'foreign import' for '%.*s' architecture may only have one path, got %td", - LIT(target_arch_names[build_context.metrics.arch]), foreign_library->LibraryName.paths.count); - } - - if (foreign_library->LibraryName.paths.count >= 1) { - module_name = foreign_library->LibraryName.paths[0]; - } - - if (!string_ends_with(module_name, str_lit(".o"))) { - name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); - } - } - e->Procedure.is_foreign = true; e->Procedure.link_name = name; + e->Procedure.foreign_library = foreign_library; - mutex_lock(&ctx->info->foreign_mutex); - - auto *fp = &ctx->info->foreigns; - StringHashKey key = string_hash_string(name); - Entity **found = string_map_get(fp, key); - if (found && e != *found) { - Entity *f = *found; - TokenPos pos = f->token.pos; - Type *this_type = base_type(e->type); - Type *other_type = base_type(f->type); - if (is_type_proc(this_type) && is_type_proc(other_type)) { - if (!are_signatures_similar_enough(this_type, other_type)) { - error(d->proc_lit, - "Redeclaration of foreign procedure '%.*s' with different type signatures\n" - "\tat %s", - LIT(name), token_pos_to_string(pos)); - } - } else if (!signature_parameter_similar_enough(this_type, other_type)) { - error(d->proc_lit, - "Foreign entity '%.*s' previously declared elsewhere with a different type\n" - "\tat %s", - LIT(name), token_pos_to_string(pos)); - } - } else if (name == "main") { - error(d->proc_lit, "The link name 'main' is reserved for internal use"); + if (is_arch_wasm() && foreign_library != nullptr) { + // NOTE(bill): this must be delayed because the foreign import paths might not be evaluated yet until much later + mpsc_enqueue(&ctx->info->foreign_decls_to_check, e); } else { - string_map_set(fp, key, e); + check_foreign_procedure(ctx, e, d); } - - mutex_unlock(&ctx->info->foreign_mutex); } else { String name = e->token.string; if (e->Procedure.link_name.len > 0) { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d3141ed84..fba9b8dad 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -897,20 +897,6 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } } - if (is_type_relative_pointer(dst)) { - i64 score = check_distance_between_types(c, operand, dst->RelativePointer.pointer_type, allow_array_programming); - if (score >= 0) { - return score+2; - } - } - - if (is_type_relative_multi_pointer(dst)) { - i64 score = check_distance_between_types(c, operand, dst->RelativeMultiPointer.pointer_type, allow_array_programming); - if (score >= 0) { - return score+2; - } - } - if (is_type_proc(dst)) { if (are_types_identical(src, dst)) { return 3; @@ -1052,12 +1038,6 @@ gb_internal AstPackage *get_package_of_type(Type *type) { case Type_DynamicArray: type = type->DynamicArray.elem; continue; - case Type_RelativePointer: - type = type->RelativePointer.pointer_type; - continue; - case Type_RelativeMultiPointer: - type = type->RelativeMultiPointer.pointer_type; - continue; } return nullptr; } @@ -3561,6 +3541,8 @@ gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type return false; } + Operand src = *o; + Type *src_t = o->type; Type *dst_t = t; Type *src_bt = base_type(src_t); @@ -3649,7 +3631,8 @@ gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type // identical casts that cannot be foreseen or otherwise // forbidden, so just skip them. if (forbid_identical && check_vet_flags(c) & VetFlag_Cast && - (c->curr_proc_sig == nullptr || !is_type_polymorphic(c->curr_proc_sig))) { + (c->curr_proc_sig == nullptr || !is_type_polymorphic(c->curr_proc_sig)) && + check_is_castable_to(c, &src, dst_t)) { if (are_types_identical(src_t, dst_t)) { gbString oper_str = expr_to_string(o->expr); gbString to_type = type_to_string(dst_t); @@ -4471,8 +4454,8 @@ gb_internal ExactValue convert_exact_value_for_type(ExactValue v, Type *type) { } gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *target_type) { - GB_ASSERT_NOT_NULL(target_type); - if (operand->mode == Addressing_Invalid || + // GB_ASSERT_NOT_NULL(target_type); + if (target_type == nullptr || operand->mode == Addressing_Invalid || operand->mode == Addressing_Type || is_type_typed(operand->type) || target_type == t_invalid) { @@ -4997,8 +4980,12 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v if (success_) *success_ = true; if (finish_) *finish_ = false; return tav.value; + } else if (is_type_proc(tav.type)) { + if (success_) *success_ = true; + if (finish_) *finish_ = false; + return tav.value; } else { - GB_ASSERT(is_type_untyped_nil(tav.type)); + GB_ASSERT_MSG(is_type_untyped_nil(tav.type), "%s", type_to_string(tav.type)); if (success_) *success_ = true; if (finish_) *finish_ = false; return tav.value; @@ -5394,22 +5381,25 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod Type *t = type_deref(operand->type); if (t == nullptr) { error(operand->expr, "Cannot use a selector expression on 0-value expression"); - } else if (is_type_dynamic_array(t)) { - init_mem_allocator(c->checker); - } - sel = lookup_field(operand->type, field_name, operand->mode == Addressing_Type); - entity = sel.entity; + } else { + if (is_type_dynamic_array(t)) { + init_mem_allocator(c->checker); + } + sel = lookup_field(operand->type, field_name, operand->mode == Addressing_Type); + entity = sel.entity; - // NOTE(bill): Add type info needed for fields like 'names' - if (entity != nullptr && (entity->flags&EntityFlag_TypeField)) { - add_type_info_type(c, operand->type); - } - if (is_type_enum(operand->type)) { - add_type_info_type(c, operand->type); + // NOTE(bill): Add type info needed for fields like 'names' + if (entity != nullptr && (entity->flags&EntityFlag_TypeField)) { + add_type_info_type(c, operand->type); + } + if (is_type_enum(operand->type)) { + add_type_info_type(c, operand->type); + } } } - if (entity == nullptr && selector->kind == Ast_Ident && (is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) { + if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr && + (is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) { String field_name = selector->Ident.token.string; if (1 < field_name.len && field_name.len <= 4) { u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'}; @@ -8008,6 +7998,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c pt = data.gen_entity->type; } } + pt = base_type(pt); if (pt->kind == Type_Proc && pt->Proc.calling_convention == ProcCC_Odin) { if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) { @@ -8226,17 +8217,6 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 } return true; - case Type_RelativeMultiPointer: - { - Type *pointer_type = base_type(t->RelativeMultiPointer.pointer_type); - GB_ASSERT(pointer_type->kind == Type_MultiPointer); - o->type = pointer_type->MultiPointer.elem; - if (o->mode != Addressing_Constant) { - o->mode = Addressing_Variable; - } - } - return true; - case Type_DynamicArray: o->type = t->DynamicArray.elem; if (o->mode != Addressing_Constant) { @@ -8475,6 +8455,15 @@ gb_internal ExprKind check_implicit_selector_expr(CheckerContext *c, Operand *o, error(node, "Undeclared name '%.*s' for type '%s'", LIT(name), typ); check_did_you_mean_type(name, bt->Enum.fields); + } else if (is_type_bit_set(th) && is_type_enum(th->BitSet.elem)) { + ERROR_BLOCK(); + + gbString typ = type_to_string(th); + gbString str = expr_to_string(node); + error(node, "Cannot convert enum value to '%s'", typ); + error_line("\tSuggestion: Did you mean '{ %s }'?\n", str); + gb_string_free(typ); + gb_string_free(str); } else { gbString typ = type_to_string(th); gbString str = expr_to_string(node); @@ -8736,6 +8725,18 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A error(node, "#caller_expression may only be used as a default argument parameter"); o->type = t_string; o->mode = Addressing_Value; + } else if (name == "branch_location") { + if (!c->in_defer) { + error(node, "#branch_location may only be used within a 'defer' statement"); + } else if (c->curr_proc_decl) { + Entity *e = c->curr_proc_decl->entity; + if (e != nullptr) { + GB_ASSERT(e->kind == Entity_Procedure); + e->Procedure.uses_branch_location = true; + } + } + o->type = t_source_code_location; + o->mode = Addressing_Value; } else { if (name == "location") { init_core_source_code_location(c->checker); @@ -10610,8 +10611,6 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, // Okay } else if (is_type_string(t)) { // Okay - } else if (is_type_relative_multi_pointer(t)) { - // Okay } else if (is_type_matrix(t)) { // Okay } else { @@ -10766,11 +10765,6 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, } break; - case Type_RelativeMultiPointer: - valid = true; - o->type = type_deref(o->type); - break; - case Type_EnumeratedArray: { gbString str = expr_to_string(o->expr); @@ -10847,16 +10841,6 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, x[i:n] -> []T */ o->type = alloc_type_slice(t->MultiPointer.elem); - } else if (t->kind == Type_RelativeMultiPointer && se->high != nullptr) { - /* - x[:] -> [^]T - x[i:] -> [^]T - x[:n] -> []T - x[i:n] -> []T - */ - Type *pointer_type = base_type(t->RelativeMultiPointer.pointer_type); - GB_ASSERT(pointer_type->kind == Type_MultiPointer); - o->type = alloc_type_slice(pointer_type->MultiPointer.elem); } @@ -11217,22 +11201,6 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast } else if (t->kind == Type_SoaPointer) { o->mode = Addressing_SoaVariable; o->type = type_deref(t); - } else if (t->kind == Type_RelativePointer) { - if (o->mode != Addressing_Variable) { - gbString str = expr_to_string(o->expr); - gbString typ = type_to_string(o->type); - error(o->expr, "Cannot dereference relative pointer '%s' of type '%s' as it does not have a variable addressing mode", str, typ); - gb_string_free(typ); - gb_string_free(str); - } - - // NOTE(bill): This is required because when dereferencing, the original type has been lost - add_type_info_type(c, o->type); - - Type *ptr_type = base_type(t->RelativePointer.pointer_type); - GB_ASSERT(ptr_type->kind == Type_Pointer); - o->mode = Addressing_Variable; - o->type = ptr_type->Pointer.elem; } else { gbString str = expr_to_string(o->expr); gbString typ = type_to_string(o->type); diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 74a9e8825..02ad72388 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2600,6 +2600,25 @@ gb_internal void check_for_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { check_expr(ctx, &o, fs->cond); if (o.mode != Addressing_Invalid && !is_type_boolean(o.type)) { error(fs->cond, "Non-boolean condition in 'for' statement"); + } else { + Ast *cond = unparen_expr(o.expr); + if (cond && cond->kind == Ast_BinaryExpr && + cond->BinaryExpr.left && cond->BinaryExpr.right && + cond->BinaryExpr.op.kind == Token_GtEq && + type_of_expr(cond->BinaryExpr.left) != nullptr && + is_type_unsigned(type_of_expr(cond->BinaryExpr.left)) && + cond->BinaryExpr.right->tav.value.kind == ExactValue_Integer && + is_exact_value_zero(cond->BinaryExpr.right->tav.value)) { + warning(cond, "Expression is always true since unsigned numbers are always >= 0"); + } else if (cond && cond->kind == Ast_BinaryExpr && + cond->BinaryExpr.left && cond->BinaryExpr.right && + cond->BinaryExpr.op.kind == Token_LtEq && + type_of_expr(cond->BinaryExpr.right) != nullptr && + is_type_unsigned(type_of_expr(cond->BinaryExpr.right)) && + cond->BinaryExpr.left->tav.value.kind == ExactValue_Integer && + is_exact_value_zero(cond->BinaryExpr.left->tav.value)) { + warning(cond, "Expression is always true since unsigned numbers are always >= 0"); + } } } if (fs->post != nullptr) { diff --git a/src/check_type.cpp b/src/check_type.cpp index bbeff9ca7..44108ccbe 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2366,8 +2366,7 @@ gb_internal Type *check_get_results(CheckerContext *ctx, Scope *scope, Ast *_res } gb_internal void check_procedure_param_polymorphic_type(CheckerContext *ctx, Type *type, Ast *type_expr) { - GB_ASSERT_NOT_NULL(type_expr); - if (type == nullptr || ctx->in_polymorphic_specialization) { return; } + if (type == nullptr || type_expr == nullptr || ctx->in_polymorphic_specialization) { return; } if (!is_type_polymorphic_record_unspecialized(type)) { return; } bool invalid_polymorpic_type_use = false; @@ -2441,8 +2440,12 @@ gb_internal bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc bool success = true; isize specialization_count = 0; Type *params = check_get_params(c, c->scope, pt->params, &variadic, &variadic_index, &success, &specialization_count, operands); - Type *results = check_get_results(c, c->scope, pt->results); + bool no_poly_return = c->disallow_polymorphic_return_types; + c->disallow_polymorphic_return_types = c->scope == c->polymorphic_scope; + // NOTE(zen3ger): if the parapoly scope is the current proc's scope, then the return types shall not declare new poly vars + Type *results = check_get_results(c, c->scope, pt->results); + c->disallow_polymorphic_return_types = no_poly_return; isize param_count = 0; isize result_count = 0; @@ -3384,6 +3387,9 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T } Type *t = alloc_type_generic(ctx->scope, 0, token.string, specific); if (ctx->allow_polymorphic_types) { + if (ctx->disallow_polymorphic_return_types) { + error(ident, "Undeclared polymorphic parameter '%.*s' in return type", LIT(token.string)); + } Scope *ps = ctx->polymorphic_scope; Scope *s = ctx->scope; Scope *entity_scope = s; @@ -3517,41 +3523,8 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T case_end; case_ast_node(rt, RelativeType, e); - GB_ASSERT(rt->tag->kind == Ast_CallExpr); - ast_node(ce, CallExpr, rt->tag); - - Type *base_integer = nullptr; - - if (ce->args.count != 1) { - error(rt->type, "#relative expected 1 type argument, got %td", ce->args.count); - } else { - base_integer = check_type(ctx, ce->args[0]); - if (!is_type_integer(base_integer)) { - error(rt->type, "#relative base types must be an integer"); - base_integer = nullptr; - } else if (type_size_of(base_integer) > 64) { - error(rt->type, "#relative base integer types be less than or equal to 64-bits"); - base_integer = nullptr; - } - } - - Type *relative_type = nullptr; - Type *base_type = check_type(ctx, rt->type); - if (!is_type_pointer(base_type) && !is_type_multi_pointer(base_type)) { - error(rt->type, "#relative types can only be a pointer or multi-pointer"); - relative_type = base_type; - } else if (base_integer == nullptr) { - relative_type = base_type; - } else { - if (is_type_pointer(base_type)) { - relative_type = alloc_type_relative_pointer(base_type, base_integer); - } else if (is_type_multi_pointer(base_type)) { - relative_type = alloc_type_relative_multi_pointer(base_type, base_integer); - } - } - GB_ASSERT(relative_type != nullptr); - - *type = relative_type; + error(e, "#relative types have been removed from the compiler. Prefer \"core:relative\"."); + *type = t_invalid; set_base_type(named_type, *type); return true; case_end; diff --git a/src/checker.cpp b/src/checker.cpp index 76f996648..7e0a64d75 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1356,6 +1356,7 @@ gb_internal void init_checker_info(CheckerInfo *i) { mpsc_init(&i->required_global_variable_queue, a); // 1<<10); mpsc_init(&i->required_foreign_imports_through_force_queue, a); // 1<<10); mpsc_init(&i->foreign_imports_to_check_fullpaths, a); // 1<<10); + mpsc_init(&i->foreign_decls_to_check, a); // 1<<10); mpsc_init(&i->intrinsics_entry_point_usage, a); // 1<<10); // just waste some memory here, even if it probably never used string_map_init(&i->load_directory_cache); @@ -1382,6 +1383,7 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { mpsc_destroy(&i->required_global_variable_queue); mpsc_destroy(&i->required_foreign_imports_through_force_queue); mpsc_destroy(&i->foreign_imports_to_check_fullpaths); + mpsc_destroy(&i->foreign_decls_to_check); map_destroy(&i->objc_msgSend_types); string_map_destroy(&i->load_file_cache); @@ -2186,16 +2188,6 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_type_internal(c, bt->SimdVector.elem); break; - case Type_RelativePointer: - add_type_info_type_internal(c, bt->RelativePointer.pointer_type); - add_type_info_type_internal(c, bt->RelativePointer.base_integer); - break; - - case Type_RelativeMultiPointer: - add_type_info_type_internal(c, bt->RelativeMultiPointer.pointer_type); - add_type_info_type_internal(c, bt->RelativeMultiPointer.base_integer); - break; - case Type_Matrix: add_type_info_type_internal(c, bt->Matrix.elem); break; @@ -2441,16 +2433,6 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { add_min_dep_type_info(c, bt->SimdVector.elem); break; - case Type_RelativePointer: - add_min_dep_type_info(c, bt->RelativePointer.pointer_type); - add_min_dep_type_info(c, bt->RelativePointer.base_integer); - break; - - case Type_RelativeMultiPointer: - add_min_dep_type_info(c, bt->RelativeMultiPointer.pointer_type); - add_min_dep_type_info(c, bt->RelativeMultiPointer.base_integer); - break; - case Type_Matrix: add_min_dep_type_info(c, bt->Matrix.elem); break; @@ -3075,8 +3057,6 @@ gb_internal void init_core_type_info(Checker *c) { t_type_info_map = find_core_type(c, str_lit("Type_Info_Map")); t_type_info_bit_set = find_core_type(c, str_lit("Type_Info_Bit_Set")); t_type_info_simd_vector = find_core_type(c, str_lit("Type_Info_Simd_Vector")); - t_type_info_relative_pointer = find_core_type(c, str_lit("Type_Info_Relative_Pointer")); - t_type_info_relative_multi_pointer = find_core_type(c, str_lit("Type_Info_Relative_Multi_Pointer")); t_type_info_matrix = find_core_type(c, str_lit("Type_Info_Matrix")); t_type_info_soa_pointer = find_core_type(c, str_lit("Type_Info_Soa_Pointer")); t_type_info_bit_field = find_core_type(c, str_lit("Type_Info_Bit_Field")); @@ -3105,8 +3085,6 @@ gb_internal void init_core_type_info(Checker *c) { t_type_info_map_ptr = alloc_type_pointer(t_type_info_map); t_type_info_bit_set_ptr = alloc_type_pointer(t_type_info_bit_set); t_type_info_simd_vector_ptr = alloc_type_pointer(t_type_info_simd_vector); - t_type_info_relative_pointer_ptr = alloc_type_pointer(t_type_info_relative_pointer); - t_type_info_relative_multi_pointer_ptr = alloc_type_pointer(t_type_info_relative_multi_pointer); t_type_info_matrix_ptr = alloc_type_pointer(t_type_info_matrix); t_type_info_soa_pointer_ptr = alloc_type_pointer(t_type_info_soa_pointer); t_type_info_bit_field_ptr = alloc_type_pointer(t_type_info_bit_field); @@ -5118,6 +5096,38 @@ gb_internal void check_foreign_import_fullpaths(Checker *c) { e->LibraryName.paths = fl->fullpaths; } + + for (Entity *e = nullptr; mpsc_dequeue(&c->info.foreign_decls_to_check, &e); /**/) { + GB_ASSERT(e != nullptr); + if (e->kind != Entity_Procedure) { + continue; + } + if (!is_arch_wasm()) { + continue; + } + Entity *foreign_library = e->Procedure.foreign_library; + GB_ASSERT(foreign_library != nullptr); + + String name = e->Procedure.link_name; + + String module_name = str_lit("env"); + GB_ASSERT (foreign_library->kind == Entity_LibraryName); + if (foreign_library->LibraryName.paths.count != 1) { + error(foreign_library->token, "'foreign import' for '%.*s' architecture may only have one path, got %td", + LIT(target_arch_names[build_context.metrics.arch]), foreign_library->LibraryName.paths.count); + } + + if (foreign_library->LibraryName.paths.count >= 1) { + module_name = foreign_library->LibraryName.paths[0]; + } + + if (!string_ends_with(module_name, str_lit(".o"))) { + name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); + } + e->Procedure.link_name = name; + + check_foreign_procedure(&ctx, e, e->decl_info); + } } gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { diff --git a/src/checker.hpp b/src/checker.hpp index 438156f18..3951fcefe 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -461,6 +461,7 @@ struct CheckerInfo { MPSCQueue required_global_variable_queue; MPSCQueue required_foreign_imports_through_force_queue; MPSCQueue foreign_imports_to_check_fullpaths; + MPSCQueue foreign_decls_to_check; MPSCQueue intrinsics_entry_point_usage; @@ -521,6 +522,7 @@ struct CheckerContext { bool in_enum_type; bool collect_delayed_decls; bool allow_polymorphic_types; + bool disallow_polymorphic_return_types; // NOTE(zen3ger): no poly type decl in return types bool no_polymorphic_errors; bool hide_polymorphic_errors; bool in_polymorphic_specialization; diff --git a/src/docs_format.cpp b/src/docs_format.cpp index ca6ecb5c2..6378971d0 100644 --- a/src/docs_format.cpp +++ b/src/docs_format.cpp @@ -79,8 +79,7 @@ enum OdinDocTypeKind : u32 { OdinDocType_SOAStructFixed = 17, OdinDocType_SOAStructSlice = 18, OdinDocType_SOAStructDynamic = 19, - OdinDocType_RelativePointer = 20, - OdinDocType_RelativeMultiPointer = 21, + OdinDocType_MultiPointer = 22, OdinDocType_Matrix = 23, OdinDocType_SoaPointer = 24, diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 835dfdff1..341b3fa6b 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -776,24 +776,6 @@ gb_internal OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { doc_type.types = odin_doc_type_as_slice(w, type->SimdVector.elem); // TODO(bill): break; - case Type_RelativePointer: - doc_type.kind = OdinDocType_RelativePointer; - { - OdinDocTypeIndex types[2] = {}; - types[0] = odin_doc_type(w, type->RelativePointer.pointer_type); - types[1] = odin_doc_type(w, type->RelativePointer.base_integer); - doc_type.types = odin_write_slice(w, types, gb_count_of(types)); - } - break; - case Type_RelativeMultiPointer: - doc_type.kind = OdinDocType_RelativeMultiPointer; - { - OdinDocTypeIndex types[2] = {}; - types[0] = odin_doc_type(w, type->RelativeMultiPointer.pointer_type); - types[1] = odin_doc_type(w, type->RelativeMultiPointer.base_integer); - doc_type.types = odin_write_slice(w, types, gb_count_of(types)); - } - break; case Type_Matrix: doc_type.kind = OdinDocType_Matrix; diff --git a/src/entity.cpp b/src/entity.cpp index 0c4a20df4..802b381f9 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -256,6 +256,7 @@ struct Entity { bool entry_point_only : 1; bool has_instrumentation : 1; bool is_memcpy_like : 1; + bool uses_branch_location : 1; } Procedure; struct { Array entities; diff --git a/src/exact_value.cpp b/src/exact_value.cpp index 1a42a82a9..ceaed84c1 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -370,7 +370,11 @@ gb_internal ExactValue exact_value_from_basic_literal(TokenKind kind, String con } case Token_Rune: { Rune r = GB_RUNE_INVALID; - utf8_decode(string.text, string.len, &r); + if (string.len == 1) { + r = cast(Rune)string.text[0]; + } else { + utf8_decode(string.text, string.len, &r); + } return exact_value_i64(r); } } @@ -687,6 +691,7 @@ gb_internal void match_exact_values(ExactValue *x, ExactValue *y) { case ExactValue_String: case ExactValue_Quaternion: case ExactValue_Pointer: + case ExactValue_Compound: case ExactValue_Procedure: case ExactValue_Typeid: return; diff --git a/src/gb/gb.h b/src/gb/gb.h index 1fef4b4f5..f74026c7d 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -2541,7 +2541,11 @@ 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) { memset(ptr, 0, size); } +gb_inline void gb_zero_size(void *ptr, isize size) { + if (size != 0) { + memset(ptr, 0, size); + } +} #if defined(_MSC_VER) && !defined(__clang__) diff --git a/src/linker.cpp b/src/linker.cpp index 500fead69..261d6e7a4 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -167,8 +167,10 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (is_windows) { String section_name = str_lit("msvc-link"); - if (build_context.use_lld) { - section_name = str_lit("lld-link"); + switch (build_context.linker_choice) { + case Linker_Default: break; + case Linker_lld: section_name = str_lit("lld-link"); break; + case Linker_radlink: section_name = str_lit("rad-link"); break; } timings_start_section(timings, section_name); @@ -304,7 +306,48 @@ gb_internal i32 linker_stage(LinkerData *gen) { String windows_sdk_bin_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Win_SDK_Bin_Path]); defer (gb_free(heap_allocator(), windows_sdk_bin_path.text)); - if (!build_context.use_lld) { // msvc + switch (build_context.linker_choice) { + case Linker_lld: + result = system_exec_command_line_app("msvc-lld-link", + "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s " + "/nologo /incremental:no /opt:ref /subsystem:%.*s " + "%.*s " + "%.*s " + "%s " + "", + LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), + link_settings, + LIT(build_context.ODIN_WINDOWS_SUBSYSTEM), + LIT(build_context.link_flags), + LIT(build_context.extra_linker_flags), + lib_str + ); + + if (result) { + return result; + } + break; + case Linker_radlink: + result = system_exec_command_line_app("msvc-rad-link", + "\"%.*s\\bin\\radlink\" %s -OUT:\"%.*s\" %s " + "/nologo /incremental:no /opt:ref /subsystem:%.*s " + "%.*s " + "%.*s " + "%s " + "", + LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), + link_settings, + LIT(build_context.ODIN_WINDOWS_SUBSYSTEM), + LIT(build_context.link_flags), + LIT(build_context.extra_linker_flags), + lib_str + ); + + if (result) { + return result; + } + break; + default: { // msvc String res_path = quote_path(heap_allocator(), build_context.build_paths[BuildPath_RES]); String rc_path = quote_path(heap_allocator(), build_context.build_paths[BuildPath_RC]); defer (gb_free(heap_allocator(), res_path.text)); @@ -365,25 +408,8 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (result) { return result; } - } else { // lld - result = system_exec_command_line_app("msvc-lld-link", - "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s " - "/nologo /incremental:no /opt:ref /subsystem:%.*s " - "%.*s " - "%.*s " - "%s " - "", - LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), - link_settings, - LIT(build_context.ODIN_WINDOWS_SUBSYSTEM), - LIT(build_context.link_flags), - LIT(build_context.extra_linker_flags), - lib_str - ); - - if (result) { - return result; - } + break; + } } } else { timings_start_section(timings, str_lit("ld-link")); @@ -605,9 +631,18 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' "); } - } else if (build_context.metrics.os != TargetOs_openbsd && build_context.metrics.os != TargetOs_haiku && build_context.metrics.arch != TargetArch_riscv64) { - // OpenBSD and Haiku default to PIE executable. do not pass -no-pie for it. - link_settings = gb_string_appendc(link_settings, "-no-pie "); + } + + if (build_context.build_mode == BuildMode_Executable && build_context.reloc_mode == RelocMode_PIC) { + // Do not disable PIE, let the linker choose. (most likely you want it enabled) + } else if (build_context.build_mode != BuildMode_DynamicLibrary) { + if (build_context.metrics.os != TargetOs_openbsd + && build_context.metrics.os != TargetOs_haiku + && build_context.metrics.arch != TargetArch_riscv64 + ) { + // OpenBSD and Haiku default to PIE executable. do not pass -no-pie for it. + link_settings = gb_string_appendc(link_settings, "-no-pie "); + } } gbString platform_lib_str = gb_string_make(heap_allocator(), ""); @@ -670,7 +705,7 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.extra_linker_flags)); link_command_line = gb_string_append_fmt(link_command_line, " %s ", link_settings); - if (build_context.use_lld) { + if (build_context.linker_choice == Linker_lld) { link_command_line = gb_string_append_fmt(link_command_line, " -fuse-ld=lld"); result = system_exec_command_line_app("lld-link", link_command_line); } else { @@ -684,7 +719,7 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (is_osx && build_context.ODIN_DEBUG) { // NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe // to the symbols in the object file - result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename)); + result = system_exec_command_line_app("dsymutil", "dsymutil \"%.*s\"", LIT(output_filename)); if (result) { return result; diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 68f95cb03..42d283a1e 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -57,6 +57,10 @@ #define LB_USE_NEW_PASS_SYSTEM 0 #endif +#if LLVM_VERSION_MAJOR >= 19 +#define LLVMDIBuilderInsertDeclareAtEnd(...) LLVMDIBuilderInsertDeclareRecordAtEnd(__VA_ARGS__) +#endif + gb_internal bool lb_use_new_pass_system(void) { return LB_USE_NEW_PASS_SYSTEM; } @@ -75,7 +79,6 @@ enum lbAddrKind { lbAddr_Context, lbAddr_SoaVariable, - lbAddr_RelativePointer, lbAddr_Swizzle, lbAddr_SwizzleLarge, @@ -103,9 +106,6 @@ struct lbAddr { lbValue index; Ast *node; } index_set; - struct { - bool deref; - } relative; struct { Type *type; u8 count; // 2, 3, or 4 components @@ -359,6 +359,10 @@ struct lbProcedure { bool in_multi_assignment; Array raw_input_parameters; + bool uses_branch_location; + TokenPos branch_location_pos; + TokenPos curr_token_pos; + Array variadic_reuses; lbAddr variadic_reuse_base_array_ptr; @@ -444,7 +448,8 @@ gb_internal lbValue lb_emit_matrix_ev(lbProcedure *p, lbValue s, isize row, isiz gb_internal lbValue lb_emit_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Type *type); gb_internal lbValue lb_emit_byte_swap(lbProcedure *p, lbValue value, Type *end_type); -gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block); +gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, TokenPos pos); +gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, Ast *node); gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t); gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue right); gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array const &args, ProcInlining inlining = ProcInlining_none); @@ -741,4 +746,6 @@ gb_global char const *llvm_linkage_strings[] = { "linker private weak linkage" }; -#define ODIN_METADATA_IS_PACKED str_lit("odin-is-packed") \ No newline at end of file +#define ODIN_METADATA_IS_PACKED str_lit("odin-is-packed") +#define ODIN_METADATA_MIN_ALIGN str_lit("odin-min-align") +#define ODIN_METADATA_MAX_ALIGN str_lit("odin-max-align") diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 5cc79dcc8..464f7065c 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -920,17 +920,6 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { elem, subscripts, gb_count_of(subscripts)); } - case Type_RelativePointer: { - LLVMMetadataRef base_integer = lb_debug_type(m, type->RelativePointer.base_integer); - gbString name = type_to_string(type, temporary_allocator()); - return LLVMDIBuilderCreateTypedef(m->debug_builder, base_integer, name, gb_string_length(name), nullptr, 0, nullptr, cast(u32)(8*type_align_of(type))); - } - case Type_RelativeMultiPointer: { - LLVMMetadataRef base_integer = lb_debug_type(m, type->RelativeMultiPointer.base_integer); - gbString name = type_to_string(type, temporary_allocator()); - return LLVMDIBuilderCreateTypedef(m->debug_builder, base_integer, name, gb_string_length(name), nullptr, 0, nullptr, cast(u32)(8*type_align_of(type))); - } - case Type_Matrix: { LLVMMetadataRef subscripts[1] = {}; subscripts[0] = LLVMDIBuilderGetOrCreateSubrange(m->debug_builder, diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 8967a4e03..3b238bcd8 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -130,7 +130,7 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, LLVMTypeRef vector_type = nullptr; if (op != Token_Not && lb_try_vector_cast(p->module, val, &vector_type)) { LLVMValueRef vp = LLVMBuildPointerCast(p->builder, val.value, LLVMPointerType(vector_type, 0), ""); - LLVMValueRef v = LLVMBuildLoad2(p->builder, vector_type, vp, ""); + LLVMValueRef v = OdinLLVMBuildLoad(p, vector_type, vp); LLVMValueRef opv = nullptr; switch (op) { @@ -324,8 +324,8 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu 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 x = OdinLLVMBuildLoad(p, vector_type, lhs_vp); + LLVMValueRef y = OdinLLVMBuildLoad(p, vector_type, rhs_vp); LLVMValueRef z = nullptr; if (is_type_float(integral_type)) { @@ -551,15 +551,14 @@ gb_internal LLVMValueRef lb_matrix_to_vector(lbProcedure *p, lbValue matrix) { Type *mt = base_type(matrix.type); GB_ASSERT(mt->kind == Type_Matrix); LLVMTypeRef elem_type = lb_type(p->module, mt->Matrix.elem); - + unsigned total_count = cast(unsigned)matrix_type_total_internal_elems(mt); LLVMTypeRef total_matrix_type = LLVMVectorType(elem_type, total_count); - + #if 1 LLVMValueRef ptr = lb_address_from_load_or_generate_local(p, matrix).value; LLVMValueRef matrix_vector_ptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(total_matrix_type, 0), ""); - LLVMValueRef matrix_vector = LLVMBuildLoad2(p->builder, total_matrix_type, matrix_vector_ptr, ""); - LLVMSetAlignment(matrix_vector, cast(unsigned)type_align_of(mt)); + LLVMValueRef matrix_vector = OdinLLVMBuildLoadAligned(p, total_matrix_type, matrix_vector_ptr, type_align_of(mt)); return matrix_vector; #else LLVMValueRef matrix_vector = LLVMBuildBitCast(p->builder, matrix.value, total_matrix_type, ""); @@ -1648,7 +1647,7 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { lb_emit_store(p, a1, id); return lb_addr_load(p, res); } else if (dst->kind == Type_Basic) { - if (src->Basic.kind == Basic_string && dst->Basic.kind == Basic_cstring) { + if (src->kind == Type_Basic && src->Basic.kind == Basic_string && dst->Basic.kind == Basic_cstring) { String str = lb_get_const_string(m, value); lbValue res = {}; res.type = t; @@ -2364,12 +2363,23 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { GB_ASSERT(src->kind == Type_Matrix); lbAddr v = lb_add_local_generated(p, t, true); - if (is_matrix_square(dst) && is_matrix_square(dst)) { + if (dst->Matrix.row_count == src->Matrix.row_count && + dst->Matrix.column_count == src->Matrix.column_count) { + for (i64 j = 0; j < dst->Matrix.column_count; j++) { + for (i64 i = 0; i < dst->Matrix.row_count; i++) { + lbValue d = lb_emit_matrix_epi(p, v.addr, i, j); + lbValue s = lb_emit_matrix_ev(p, value, i, j); + s = lb_emit_conv(p, s, dst->Matrix.elem); + lb_emit_store(p, d, s); + } + } + } else if (is_matrix_square(dst) && is_matrix_square(dst)) { for (i64 j = 0; j < dst->Matrix.column_count; j++) { for (i64 i = 0; i < dst->Matrix.row_count; i++) { if (i < src->Matrix.row_count && j < src->Matrix.column_count) { lbValue d = lb_emit_matrix_epi(p, v.addr, i, j); lbValue s = lb_emit_matrix_ev(p, value, i, j); + s = lb_emit_conv(p, s, dst->Matrix.elem); lb_emit_store(p, d, s); } else if (i == j) { lbValue d = lb_emit_matrix_epi(p, v.addr, i, j); @@ -2807,6 +2817,12 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left Type *it = bit_set_to_int(a); lbValue lhs = lb_emit_transmute(p, left, it); lbValue rhs = lb_emit_transmute(p, right, it); + if (is_type_different_to_arch_endianness(it)) { + it = integer_endian_type_to_platform_type(it); + lhs = lb_emit_byte_swap(p, lhs, it); + rhs = lb_emit_byte_swap(p, rhs, it); + } + lbValue res = lb_emit_arith(p, Token_And, lhs, rhs, it); if (op_kind == Token_Lt || op_kind == Token_LtEq) { @@ -2904,6 +2920,12 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left } } + if (is_type_different_to_arch_endianness(left.type)) { + Type *pt = integer_endian_type_to_platform_type(left.type); + lhs = lb_emit_byte_swap(p, {lhs, pt}, pt).value; + rhs = lb_emit_byte_swap(p, {rhs, pt}, pt).value; + } + res.value = LLVMBuildICmp(p->builder, pred, lhs, rhs, ""); } else if (is_type_float(a)) { LLVMRealPredicate pred = {}; @@ -2915,6 +2937,13 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left case Token_LtEq: pred = LLVMRealOLE; break; case Token_NotEq: pred = LLVMRealONE; break; } + + if (is_type_different_to_arch_endianness(left.type)) { + Type *pt = integer_endian_type_to_platform_type(left.type); + left = lb_emit_byte_swap(p, left, pt); + right = lb_emit_byte_swap(p, right, pt); + } + res.value = LLVMBuildFCmp(p->builder, pred, left.value, right.value, ""); } else if (is_type_typeid(a)) { LLVMIntPredicate pred = {}; @@ -3473,7 +3502,13 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { case_ast_node(bd, BasicDirective, expr); TokenPos pos = bd->token.pos; - GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(bd->name.string)); + String name = bd->name.string; + if (name == "branch_location") { + GB_ASSERT(p->uses_branch_location); + String proc_name = p->entity->token.string; + return lb_emit_source_code_location_as_global(p, proc_name, p->branch_location_pos); + } + GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(name)); case_end; case_ast_node(i, Implicit, expr); @@ -3639,7 +3674,7 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { lb_emit_if(p, lb_emit_try_has_value(p, rhs), then, else_); lb_start_block(p, else_); - lb_emit_defer_stmts(p, lbDeferExit_Branch, block); + lb_emit_defer_stmts(p, lbDeferExit_Branch, block, expr); lb_emit_jump(p, block); lb_start_block(p, then); @@ -4190,30 +4225,6 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { return lb_addr(v); } - case Type_RelativeMultiPointer: { - lbAddr rel_ptr_addr = {}; - if (deref) { - lbValue rel_ptr_ptr = lb_build_expr(p, ie->expr); - rel_ptr_addr = lb_addr(rel_ptr_ptr); - } else { - rel_ptr_addr = lb_build_addr(p, ie->expr); - } - lbValue rel_ptr = lb_relative_pointer_to_pointer(p, rel_ptr_addr); - - lbValue index = lb_build_expr(p, ie->index); - index = lb_emit_conv(p, index, t_int); - lbValue v = {}; - - Type *pointer_type = base_type(t->RelativeMultiPointer.pointer_type); - GB_ASSERT(pointer_type->kind == Type_MultiPointer); - Type *elem = pointer_type->MultiPointer.elem; - - LLVMValueRef indices[1] = {index.value}; - v.value = LLVMBuildGEP2(p->builder, lb_type(p->module, elem), rel_ptr.value, indices, 1, ""); - v.type = alloc_type_pointer(elem); - return lb_addr(v); - } - case Type_DynamicArray: { lbValue dynamic_array = {}; dynamic_array = lb_build_expr(p, ie->expr); @@ -4323,13 +4334,6 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { return slice; } - case Type_RelativePointer: - GB_PANIC("TODO(bill): Type_RelativePointer should be handled above already on the lb_addr_load"); - break; - case Type_RelativeMultiPointer: - GB_PANIC("TODO(bill): Type_RelativeMultiPointer should be handled above already on the lb_addr_load"); - break; - case Type_DynamicArray: { Type *elem_type = type->DynamicArray.elem; Type *slice_type = alloc_type_slice(elem_type); @@ -4595,7 +4599,11 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { auto const &f = fields[i]; LLVMValueRef mask = LLVMConstInt(lit, 1, false); - mask = LLVMConstShl(mask, LLVMConstInt(lit, f.bit_size, false)); + #if LLVM_VERSION_MAJOR >= 19 + mask = LLVMBuildShl(p->builder, mask, LLVMConstInt(lit, f.bit_size, false), ""); + #else + mask = LLVMConstShl(mask, LLVMConstInt(lit, f.bit_size, false)); + #endif mask = LLVMConstSub(mask, LLVMConstInt(lit, 1, false)); LLVMValueRef elem = values[i].value; @@ -4643,7 +4651,11 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { bits_to_set -= mask_width; LLVMValueRef mask = LLVMConstInt(vt, 1, false); - mask = LLVMConstShl(mask, LLVMConstInt(vt, mask_width, false)); + #if LLVM_VERSION_MAJOR >= 19 + mask = LLVMBuildShl(p->builder, mask, LLVMConstInt(vt, mask_width, false), ""); + #else + mask = LLVMConstShl(mask, LLVMConstInt(vt, mask_width, false)); + #endif mask = LLVMConstSub(mask, LLVMConstInt(vt, 1, false)); LLVMValueRef to_set = LLVMBuildAnd(p->builder, val, mask, ""); @@ -5333,11 +5345,7 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { case_ast_node(de, DerefExpr, expr); Type *t = type_of_expr(de->expr); - if (is_type_relative_pointer(t)) { - lbAddr addr = lb_build_addr(p, de->expr); - addr.relative.deref = true; - return addr; - } else if (is_type_soa_pointer(t)) { + if (is_type_soa_pointer(t)) { lbValue value = lb_build_expr(p, de->expr); lbValue ptr = lb_emit_struct_ev(p, value, 0); lbValue idx = lb_emit_struct_ev(p, value, 1); @@ -5491,7 +5499,7 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { lb_emit_if(p, lb_emit_try_has_value(p, rhs), then, else_); lb_start_block(p, else_); - lb_emit_defer_stmts(p, lbDeferExit_Branch, block); + lb_emit_defer_stmts(p, lbDeferExit_Branch, block, expr); lb_emit_jump(p, block); lb_start_block(p, then); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 842a1cbc8..762256258 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -409,14 +409,6 @@ gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e) { gb_internal lbAddr lb_addr(lbValue addr) { lbAddr v = {lbAddr_Default, addr}; - if (addr.type != nullptr && is_type_relative_pointer(type_deref(addr.type))) { - GB_ASSERT(is_type_pointer(addr.type)); - v.kind = lbAddr_RelativePointer; - } else if (addr.type != nullptr && is_type_relative_multi_pointer(type_deref(addr.type))) { - GB_ASSERT(is_type_pointer(addr.type) || - is_type_multi_pointer(addr.type)); - v.kind = lbAddr_RelativePointer; - } return v; } @@ -501,42 +493,6 @@ gb_internal Type *lb_addr_type(lbAddr const &addr) { return type_deref(addr.addr.type); } - -gb_internal lbValue lb_relative_pointer_to_pointer(lbProcedure *p, lbAddr const &addr) { - GB_ASSERT(addr.kind == lbAddr_RelativePointer); - - Type *t = base_type(lb_addr_type(addr)); - GB_ASSERT(is_type_relative_pointer(t) || is_type_relative_multi_pointer(t)); - - Type *pointer_type = nullptr; - Type *base_integer = nullptr; - if (t->kind == Type_RelativePointer) { - pointer_type = t->RelativePointer.pointer_type; - base_integer = t->RelativePointer.base_integer; - } else if (t->kind == Type_RelativeMultiPointer) { - pointer_type = t->RelativeMultiPointer.pointer_type; - base_integer = t->RelativeMultiPointer.base_integer; - } - - lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr); - lbValue offset = lb_emit_conv(p, ptr, alloc_type_pointer(base_integer)); - offset = lb_emit_load(p, offset); - - if (!is_type_unsigned(base_integer)) { - offset = lb_emit_conv(p, offset, t_i64); - } - offset = lb_emit_conv(p, offset, t_uintptr); - lbValue absolute_ptr = lb_emit_arith(p, Token_Add, ptr, offset, t_uintptr); - absolute_ptr = lb_emit_conv(p, absolute_ptr, pointer_type); - - lbValue cond = lb_emit_comp(p, Token_CmpEq, offset, lb_const_nil(p->module, base_integer)); - - // NOTE(bill): nil check - lbValue nil_ptr = lb_const_nil(p->module, pointer_type); - lbValue final_ptr = lb_emit_select(p, cond, nil_ptr, absolute_ptr); - return final_ptr; -} - gb_internal lbValue lb_make_soa_pointer(lbProcedure *p, Type *type, lbValue const &addr, lbValue const &index) { lbAddr v = lb_add_local_generated(p, type, false); lbValue ptr = lb_emit_struct_ep(p, v.addr, 0); @@ -557,9 +513,6 @@ gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { case lbAddr_Map: return lb_internal_dynamic_map_get_ptr(p, addr.addr, addr.map.key); - case lbAddr_RelativePointer: - return lb_relative_pointer_to_pointer(p, addr); - case lbAddr_SoaVariable: { Type *soa_ptr_type = alloc_type_soa_pointer(lb_addr_type(addr)); @@ -584,9 +537,6 @@ gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { gb_internal lbValue lb_build_addr_ptr(lbProcedure *p, Ast *expr) { lbAddr addr = lb_build_addr(p, expr); - if (addr.kind == lbAddr_RelativePointer) { - return addr.addr; - } return lb_addr_get_ptr(p, addr); } @@ -722,7 +672,10 @@ gb_internal unsigned lb_try_get_alignment(LLVMValueRef addr_ptr, unsigned defaul gb_internal bool lb_try_update_alignment(LLVMValueRef addr_ptr, unsigned alignment) { if (LLVMIsAGlobalValue(addr_ptr) || LLVMIsAAllocaInst(addr_ptr) || LLVMIsALoadInst(addr_ptr)) { if (LLVMGetAlignment(addr_ptr) < alignment) { - if (LLVMIsAAllocaInst(addr_ptr) || LLVMIsAGlobalValue(addr_ptr)) { + if (LLVMIsAAllocaInst(addr_ptr)) { + LLVMSetAlignment(addr_ptr, alignment); + } else if (LLVMIsAGlobalValue(addr_ptr) && LLVMGetLinkage(addr_ptr) != LLVMExternalLinkage) { + // NOTE(laytan): setting alignment of an external global just changes the alignment we expect it to be. LLVMSetAlignment(addr_ptr, alignment); } } @@ -755,10 +708,7 @@ gb_internal bool lb_try_vector_cast(lbModule *m, lbValue ptr, LLVMTypeRef *vecto LLVMValueRef addr_ptr = ptr.value; if (LLVMIsAAllocaInst(addr_ptr) || LLVMIsAGlobalValue(addr_ptr)) { - unsigned alignment = LLVMGetAlignment(addr_ptr); - alignment = gb_max(alignment, vector_alignment); - possible = true; - LLVMSetAlignment(addr_ptr, alignment); + possible = lb_try_update_alignment(addr_ptr, vector_alignment); } else if (LLVMIsALoadInst(addr_ptr)) { unsigned alignment = LLVMGetAlignment(addr_ptr); possible = alignment >= vector_alignment; @@ -774,6 +724,47 @@ gb_internal bool lb_try_vector_cast(lbModule *m, lbValue ptr, LLVMTypeRef *vecto return false; } +gb_internal LLVMValueRef OdinLLVMBuildLoad(lbProcedure *p, LLVMTypeRef type, LLVMValueRef value) { + LLVMValueRef result = LLVMBuildLoad2(p->builder, type, value, ""); + + // If it is not an instruction it isn't a GEP, so we don't need to track alignment in the metadata, + // which is not possible anyway (only LLVM instructions can have metadata). + if (LLVMIsAInstruction(value)) { + u64 is_packed = lb_get_metadata_custom_u64(p->module, value, ODIN_METADATA_IS_PACKED); + if (is_packed != 0) { + LLVMSetAlignment(result, 1); + } + u64 align = LLVMGetAlignment(result); + u64 align_min = lb_get_metadata_custom_u64(p->module, value, ODIN_METADATA_MIN_ALIGN); + u64 align_max = lb_get_metadata_custom_u64(p->module, value, ODIN_METADATA_MAX_ALIGN); + if (align_min != 0 && align < align_min) { + align = align_min; + } + if (align_max != 0 && align > align_max) { + align = align_max; + } + GB_ASSERT(align <= UINT_MAX); + LLVMSetAlignment(result, (unsigned int)align); + } + + return result; +} + +gb_internal LLVMValueRef OdinLLVMBuildLoadAligned(lbProcedure *p, LLVMTypeRef type, LLVMValueRef value, i64 alignment) { + LLVMValueRef result = LLVMBuildLoad2(p->builder, type, value, ""); + + LLVMSetAlignment(result, cast(unsigned)alignment); + + if (LLVMIsAInstruction(value)) { + u64 is_packed = lb_get_metadata_custom_u64(p->module, value, ODIN_METADATA_IS_PACKED); + if (is_packed != 0) { + LLVMSetAlignment(result, 1); + } + } + + return result; +} + gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { if (addr.addr.value == nullptr) { return; @@ -789,10 +780,6 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { value.value = LLVMConstNull(lb_type(p->module, t)); } - if (addr.kind == lbAddr_RelativePointer && addr.relative.deref) { - addr = lb_addr(lb_address_from_load(p, lb_addr_load(p, addr))); - } - if (addr.kind == lbAddr_BitField) { lbValue dst = addr.addr; if (is_type_endian_big(addr.bitfield.type)) { @@ -830,44 +817,6 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { lb_emit_runtime_call(p, "__write_bits", args); } return; - } else if (addr.kind == lbAddr_RelativePointer) { - Type *rel_ptr = base_type(lb_addr_type(addr)); - GB_ASSERT(rel_ptr->kind == Type_RelativePointer || - rel_ptr->kind == Type_RelativeMultiPointer); - Type *pointer_type = nullptr; - Type *base_integer = nullptr; - - if (rel_ptr->kind == Type_RelativePointer) { - pointer_type = rel_ptr->RelativePointer.pointer_type; - base_integer = rel_ptr->RelativePointer.base_integer; - } else if (rel_ptr->kind == Type_RelativeMultiPointer) { - pointer_type = rel_ptr->RelativeMultiPointer.pointer_type; - base_integer = rel_ptr->RelativeMultiPointer.base_integer; - } - - value = lb_emit_conv(p, value, pointer_type); - - GB_ASSERT(is_type_pointer(addr.addr.type)); - lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr); - lbValue val_ptr = lb_emit_conv(p, value, t_uintptr); - lbValue offset = {}; - offset.value = LLVMBuildSub(p->builder, val_ptr.value, ptr.value, ""); - offset.type = t_uintptr; - - if (!is_type_unsigned(base_integer)) { - offset = lb_emit_conv(p, offset, t_i64); - } - offset = lb_emit_conv(p, offset, base_integer); - - lbValue offset_ptr = lb_emit_conv(p, addr.addr, alloc_type_pointer(base_integer)); - offset = lb_emit_select(p, - lb_emit_comp(p, Token_CmpEq, val_ptr, lb_const_nil(p->module, t_uintptr)), - lb_const_nil(p->module, base_integer), - offset - ); - LLVMBuildStore(p->builder, offset.value, offset_ptr.value); - return; - } else if (addr.kind == lbAddr_Map) { lb_internal_dynamic_map_set(p, addr.addr, addr.map.type, addr.map.key, value, p->curr_stmt); return; @@ -1119,7 +1068,7 @@ gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue value) { Type *vt = base_type(value.type); GB_ASSERT(vt->kind == Type_MultiPointer); Type *t = vt->MultiPointer.elem; - LLVMValueRef v = LLVMBuildLoad2(p->builder, lb_type(p->module, t), value.value, ""); + LLVMValueRef v = OdinLLVMBuildLoad(p, lb_type(p->module, t), value.value); return lbValue{v, t}; } else if (is_type_soa_pointer(value.type)) { lbValue ptr = lb_emit_struct_ev(p, value, 0); @@ -1130,16 +1079,7 @@ gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue value) { 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, ""); - - // If it is not an instruction it isn't a GEP, so we don't need to track alignment in the metadata, - // which is not possible anyway (only LLVM instructions can have metadata). - if (LLVMIsAInstruction(value.value)) { - u64 is_packed = lb_get_metadata_custom_u64(p->module, value.value, ODIN_METADATA_IS_PACKED); - if (is_packed != 0) { - LLVMSetAlignment(v, 1); - } - } + LLVMValueRef v = OdinLLVMBuildLoad(p, lb_type(p->module, t), value.value); return lbValue{v, t}; } @@ -1225,46 +1165,6 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { } return r; - } else if (addr.kind == lbAddr_RelativePointer) { - Type *rel_ptr = base_type(lb_addr_type(addr)); - Type *base_integer = nullptr; - Type *pointer_type = nullptr; - GB_ASSERT(rel_ptr->kind == Type_RelativePointer || - rel_ptr->kind == Type_RelativeMultiPointer); - - if (rel_ptr->kind == Type_RelativePointer) { - base_integer = rel_ptr->RelativePointer.base_integer; - pointer_type = rel_ptr->RelativePointer.pointer_type; - } else if (rel_ptr->kind == Type_RelativeMultiPointer) { - base_integer = rel_ptr->RelativeMultiPointer.base_integer; - pointer_type = rel_ptr->RelativeMultiPointer.pointer_type; - } - - lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr); - lbValue offset = lb_emit_conv(p, ptr, alloc_type_pointer(base_integer)); - offset = lb_emit_load(p, offset); - - - if (!is_type_unsigned(base_integer)) { - offset = lb_emit_conv(p, offset, t_i64); - } - offset = lb_emit_conv(p, offset, t_uintptr); - lbValue absolute_ptr = lb_emit_arith(p, Token_Add, ptr, offset, t_uintptr); - absolute_ptr = lb_emit_conv(p, absolute_ptr, pointer_type); - - lbValue cond = lb_emit_comp(p, Token_CmpEq, offset, lb_const_nil(p->module, base_integer)); - - // NOTE(bill): nil check - lbValue nil_ptr = lb_const_nil(p->module, pointer_type); - lbValue final_ptr = {}; - final_ptr.type = absolute_ptr.type; - final_ptr.value = LLVMBuildSelect(p->builder, cond.value, nil_ptr.value, absolute_ptr.value, ""); - - if (rel_ptr->kind == Type_RelativeMultiPointer) { - return final_ptr; - } - return lb_emit_load(p, final_ptr); - } else if (addr.kind == lbAddr_Map) { Type *map_type = base_type(type_deref(addr.addr.type)); GB_ASSERT(map_type->kind == Type_Map); @@ -1413,7 +1313,7 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { LLVMTypeRef vector_type = nullptr; if (lb_try_vector_cast(p->module, addr.addr, &vector_type)) { LLVMValueRef vp = LLVMBuildPointerCast(p->builder, addr.addr.value, LLVMPointerType(vector_type, 0), ""); - LLVMValueRef v = LLVMBuildLoad2(p->builder, vector_type, vp, ""); + LLVMValueRef v = OdinLLVMBuildLoad(p, vector_type, vp); LLVMValueRef scalars[4] = {}; for (u8 i = 0; i < addr.swizzle.count; i++) { scalars[i] = LLVMConstInt(lb_type(p->module, t_u32), addr.swizzle.indices[i], false); @@ -2232,6 +2132,7 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { } i64 prev_offset = 0; + bool requires_packing = type->Struct.is_packed; for (i32 field_index : struct_fields_index_by_increasing_offset(temporary_allocator(), type)) { Entity *field = type->Struct.fields[field_index]; i64 offset = type->Struct.offsets[field_index]; @@ -2252,6 +2153,10 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { field_type = t_rawptr; } + // max_field_align might misalign items in a way that requires packing + // so check the alignment of all fields to see if packing is required. + requires_packing = requires_packing || ((offset % type_align_of(field_type)) != 0); + array_add(&fields, lb_type(m, field_type)); prev_offset = offset + type_size_of(field->type); @@ -2266,7 +2171,7 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { GB_ASSERT(fields[i] != nullptr); } - LLVMTypeRef struct_type = LLVMStructTypeInContext(ctx, fields.data, cast(unsigned)fields.count, type->Struct.is_packed); + LLVMTypeRef struct_type = LLVMStructTypeInContext(ctx, fields.data, cast(unsigned)fields.count, requires_packing); map_set(&m->struct_field_remapping, cast(void *)struct_type, field_remapping); map_set(&m->struct_field_remapping, cast(void *)type, field_remapping); #if 0 @@ -2357,13 +2262,6 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { case Type_SimdVector: return LLVMVectorType(lb_type(m, type->SimdVector.elem), cast(unsigned)type->SimdVector.count); - - case Type_RelativePointer: - return lb_type_internal(m, type->RelativePointer.base_integer); - case Type_RelativeMultiPointer: - return lb_type_internal(m, type->RelativeMultiPointer.base_integer); - - case Type_Matrix: { @@ -2710,7 +2608,7 @@ general_end:; if (LLVMIsALoadInst(val) && (src_size >= dst_size && src_align >= dst_align)) { LLVMValueRef val_ptr = LLVMGetOperand(val, 0); val_ptr = LLVMBuildPointerCast(p->builder, val_ptr, LLVMPointerType(dst_type, 0), ""); - LLVMValueRef loaded_val = LLVMBuildLoad2(p->builder, dst_type, val_ptr, ""); + LLVMValueRef loaded_val = OdinLLVMBuildLoad(p, dst_type, val_ptr); // LLVMSetAlignment(loaded_val, gb_min(src_align, dst_align)); @@ -2719,14 +2617,14 @@ general_end:; GB_ASSERT(p->decl_block != p->curr_block); i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type)); - max_align = gb_max(max_align, 4); + max_align = gb_max(max_align, 16); LLVMValueRef ptr = llvm_alloca(p, dst_type, max_align); LLVMValueRef nptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(src_type, 0), ""); LLVMBuildStore(p->builder, val, nptr); - return LLVMBuildLoad2(p->builder, dst_type, ptr, ""); + return OdinLLVMBuildLoad(p, dst_type, ptr); } } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index d84599eb0..7e44a0046 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -125,6 +125,10 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i // map_init(&p->selector_addr, 0); // map_init(&p->tuple_fix_map, 0); + if (p->entity != nullptr && p->entity->Procedure.uses_branch_location) { + p->uses_branch_location = true; + } + if (p->is_foreign) { lb_add_foreign_library_path(p->module, entity->Procedure.foreign_library); } @@ -579,6 +583,8 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { p->raw_input_parameters = array_make(permanent_allocator(), raw_input_parameters_count); LLVMGetParams(p->value, p->raw_input_parameters.data); + bool is_odin_cc = is_calling_convention_odin(ft->calling_convention); + unsigned param_index = 0; for_array(i, params->variables) { Entity *e = params->variables[i]; @@ -613,9 +619,26 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { } } else if (arg_type->kind == lbArg_Indirect) { if (e->token.string.len != 0 && !is_blank_ident(e->token.string)) { + i64 sz = type_size_of(e->type); + bool do_callee_copy = false; + + if (is_odin_cc) { + do_callee_copy = sz <= 16; + if (build_context.internal_by_value) { + do_callee_copy = true; + } + } + lbValue ptr = {}; ptr.value = LLVMGetParam(p->value, param_offset+param_index); ptr.type = alloc_type_pointer(e->type); + + if (do_callee_copy) { + lbValue new_ptr = lb_add_local_generated(p, e->type, false).addr; + lb_mem_copy_non_overlapping(p, new_ptr, ptr, lb_const_int(p->module, t_uint, sz)); + ptr = new_ptr; + } + lb_add_entity(p->module, e, ptr); lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->decl_block); } @@ -738,7 +761,7 @@ gb_internal void lb_end_procedure_body(lbProcedure *p) { if (p->type->Proc.result_count == 0) { instr = LLVMGetLastInstruction(p->curr_block->block); if (!lb_is_instr_terminating(instr)) { - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, p->body); lb_set_debug_position_to_procedure_end(p); LLVMBuildRetVoid(p->builder); } @@ -1117,10 +1140,6 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c } } - if (inlining == ProcInlining_none) { - inlining = p->inlining; - } - Type *rt = reduce_tuple_to_single_type(results); Type *original_rt = rt; if (split_returns) { @@ -2568,7 +2587,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_atomic_load_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); - LLVMValueRef instr = LLVMBuildLoad2(p->builder, lb_type(p->module, type_deref(dst.type)), dst.value, ""); + LLVMValueRef instr = OdinLLVMBuildLoad(p, lb_type(p->module, type_deref(dst.type)), dst.value); switch (id) { case BuiltinProc_non_temporal_load: { @@ -2621,8 +2640,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu if (is_type_simd_vector(t)) { lbValue res = {}; res.type = t; - res.value = LLVMBuildLoad2(p->builder, lb_type(p->module, t), src.value, ""); - LLVMSetAlignment(res.value, 1); + res.value = OdinLLVMBuildLoadAligned(p, lb_type(p->module, t), src.value, 1); return res; } else { lbAddr dst = lb_add_local_generated(p, t, false); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index df3d4bc03..a2f0d2f4a 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -201,13 +201,15 @@ gb_internal void lb_open_scope(lbProcedure *p, Scope *s) { } } + GB_ASSERT(s != nullptr); + p->curr_scope = s; p->scope_index += 1; array_add(&p->scope_stack, s); } -gb_internal void lb_close_scope(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, bool pop_stack=true) { - lb_emit_defer_stmts(p, kind, block); +gb_internal void lb_close_scope(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, Ast *node, bool pop_stack=true) { + lb_emit_defer_stmts(p, kind, block, node); GB_ASSERT(p->scope_index > 0); // NOTE(bill): Remove `context`s made in that scope @@ -221,6 +223,10 @@ gb_internal void lb_close_scope(lbProcedure *p, lbDeferExitKind kind, lbBlock *b } + if (p->curr_scope) { + p->curr_scope = p->curr_scope->parent; + } + p->scope_index -= 1; array_pop(&p->scope_stack); } @@ -715,7 +721,7 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, lb_build_stmt(p, rs->body); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, node->left); lb_pop_target_list(p); if (check != nullptr) { @@ -848,7 +854,7 @@ gb_internal void lb_build_range_tuple(lbProcedure *p, AstRangeStmt *rs, Scope *s lb_build_stmt(p, rs->body); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, rs->body); lb_pop_target_list(p); lb_emit_jump(p, loop); lb_start_block(p, done); @@ -970,7 +976,7 @@ gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs lb_build_stmt(p, rs->body); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, rs->body); lb_pop_target_list(p); lb_emit_jump(p, loop); lb_start_block(p, done); @@ -1186,7 +1192,7 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc lb_build_stmt(p, rs->body); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, rs->body); lb_pop_target_list(p); lb_emit_jump(p, loop); lb_start_block(p, done); @@ -1357,7 +1363,7 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt * } - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, rs->body); } gb_internal bool lb_switch_stmt_can_be_trivial_jump_table(AstSwitchStmt *ss, bool *default_found_) { @@ -1427,6 +1433,7 @@ gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope * ast_node(body, BlockStmt, ss->body); isize case_count = body->stmts.count; + Ast *default_clause = nullptr; Slice default_stmts = {}; lbBlock *default_fall = nullptr; lbBlock *default_block = nullptr; @@ -1476,6 +1483,7 @@ gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope * if (cc->list.count == 0) { // default case + default_clause = clause; default_stmts = cc->stmts; default_fall = fall; if (switch_instr == nullptr) { @@ -1546,7 +1554,7 @@ gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope * lb_push_target_list(p, ss->label, done, nullptr, fall); lb_open_scope(p, body->scope); lb_build_stmt_list(p, cc->stmts); - lb_close_scope(p, lbDeferExit_Default, body); + lb_close_scope(p, lbDeferExit_Default, body, clause); lb_pop_target_list(p); lb_emit_jump(p, done); @@ -1564,13 +1572,13 @@ gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope * lb_push_target_list(p, ss->label, done, nullptr, default_fall); lb_open_scope(p, default_block->scope); lb_build_stmt_list(p, default_stmts); - lb_close_scope(p, lbDeferExit_Default, default_block); + lb_close_scope(p, lbDeferExit_Default, default_block, default_clause); lb_pop_target_list(p); } lb_emit_jump(p, done); lb_start_block(p, done); - lb_close_scope(p, lbDeferExit_Default, done); + lb_close_scope(p, lbDeferExit_Default, done, ss->body); } gb_internal void lb_store_type_case_implicit(lbProcedure *p, Ast *clause, lbValue value, bool is_default_case) { @@ -1621,7 +1629,7 @@ gb_internal void lb_type_case_body(lbProcedure *p, Ast *label, Ast *clause, lbBl lb_push_target_list(p, label, done, nullptr, nullptr); lb_build_stmt_list(p, cc->stmts); - lb_close_scope(p, lbDeferExit_Default, body); + lb_close_scope(p, lbDeferExit_Default, body, clause); lb_pop_target_list(p); lb_emit_jump(p, done); @@ -1829,7 +1837,7 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss lb_emit_jump(p, done); lb_start_block(p, done); - lb_close_scope(p, lbDeferExit_Default, done); + lb_close_scope(p, lbDeferExit_Default, done, ss->body); } @@ -1953,7 +1961,7 @@ gb_internal void lb_build_assignment(lbProcedure *p, Array &lvals, Slice p->in_multi_assignment = prev_in_assignment; } -gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res) { +gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res, TokenPos pos) { lbFunctionType *ft = lb_get_function_type(p->module, p->type); bool return_by_pointer = ft->ret.kind == lbArg_Indirect; bool split_returns = ft->multiple_return_original_type != nullptr; @@ -1976,7 +1984,7 @@ gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res) { LLVMBuildStore(p->builder, LLVMConstNull(p->abi_function_type->ret.type), p->return_ptr.addr.value); } - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); // Check for terminator in the defer stmts LLVMValueRef instr = LLVMGetLastInstruction(p->curr_block->block); @@ -2001,12 +2009,12 @@ gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res) { LLVMValueRef ptr = p->temp_callee_return_struct_memory; LLVMValueRef nptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(src_type, 0), ""); LLVMBuildStore(p->builder, ret_val, nptr); - ret_val = LLVMBuildLoad2(p->builder, ret_type, ptr, ""); + ret_val = OdinLLVMBuildLoad(p, ret_type, ptr); } else { ret_val = OdinLLVMBuildTransmute(p, ret_val, ret_type); } - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); // Check for terminator in the defer stmts LLVMValueRef instr = LLVMGetLastInstruction(p->curr_block->block); @@ -2015,22 +2023,15 @@ gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res) { } } } -gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return_results) { +gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return_results, TokenPos pos) { lb_ensure_abi_function_type(p->module, p); - lbValue res = {}; - - TypeTuple *tuple = &p->type->Proc.results->Tuple; isize return_count = p->type->Proc.result_count; - isize res_count = return_results.count; - - lbFunctionType *ft = lb_get_function_type(p->module, p->type); - bool return_by_pointer = ft->ret.kind == lbArg_Indirect; if (return_count == 0) { // No return values - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); // Check for terminator in the defer stmts LLVMValueRef instr = LLVMGetLastInstruction(p->curr_block->block); @@ -2038,7 +2039,17 @@ gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return LLVMBuildRetVoid(p->builder); } return; - } else if (return_count == 1) { + } + + lbValue res = {}; + + TypeTuple *tuple = &p->type->Proc.results->Tuple; + isize res_count = return_results.count; + + lbFunctionType *ft = lb_get_function_type(p->module, p->type); + bool return_by_pointer = ft->ret.kind == lbArg_Indirect; + + if (return_count == 1) { Entity *e = tuple->variables[0]; if (res_count == 0) { rw_mutex_shared_lock(&p->module->values_mutex); @@ -2129,11 +2140,11 @@ gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return GB_ASSERT(result_values.count-1 == result_eps.count); lb_addr_store(p, p->return_ptr, result_values[result_values.count-1]); - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); LLVMBuildRetVoid(p->builder); return; } else { - return lb_build_return_stmt_internal(p, result_values[result_values.count-1]); + return lb_build_return_stmt_internal(p, result_values[result_values.count-1], pos); } } else { @@ -2160,7 +2171,7 @@ gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return } if (return_by_pointer) { - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); LLVMBuildRetVoid(p->builder); return; } @@ -2168,13 +2179,13 @@ gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return res = lb_emit_load(p, res); } } - lb_build_return_stmt_internal(p, res); + lb_build_return_stmt_internal(p, res, pos); } gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { ast_node(is, IfStmt, node); lb_open_scope(p, is->scope); // Scope #1 - defer (lb_close_scope(p, lbDeferExit_Default, nullptr)); + defer (lb_close_scope(p, lbDeferExit_Default, nullptr, node)); lbBlock *then = lb_create_block(p, "if.then"); lbBlock *done = lb_create_block(p, "if.done"); @@ -2225,7 +2236,7 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { lb_open_scope(p, scope_of_node(is->else_stmt)); lb_build_stmt(p, is->else_stmt); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, is->else_stmt); } lb_emit_jump(p, done); @@ -2242,7 +2253,7 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { lb_open_scope(p, scope_of_node(is->else_stmt)); lb_build_stmt(p, is->else_stmt); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, is->else_stmt); lb_emit_jump(p, done); } @@ -2313,7 +2324,7 @@ gb_internal void lb_build_for_stmt(lbProcedure *p, Ast *node) { } lb_start_block(p, done); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, node); } gb_internal void lb_build_assign_stmt_array(lbProcedure *p, TokenKind op, lbAddr const &lhs, lbValue const &value) { @@ -2579,7 +2590,7 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) { lb_open_scope(p, bs->scope); lb_build_stmt_list(p, bs->stmts); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, node); if (done != nullptr) { lb_emit_jump(p, done); @@ -2693,7 +2704,7 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) { case_end; case_ast_node(rs, ReturnStmt, node); - lb_build_return_stmt(p, rs->results); + lb_build_return_stmt(p, rs->results, ast_token(node).pos); case_end; case_ast_node(is, IfStmt, node); @@ -2746,7 +2757,7 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) { } } if (block != nullptr) { - lb_emit_defer_stmts(p, lbDeferExit_Branch, block); + lb_emit_defer_stmts(p, lbDeferExit_Branch, block, node); } lb_emit_jump(p, block); lb_start_block(p, lb_create_block(p, "unreachable")); @@ -2786,7 +2797,13 @@ gb_internal void lb_build_defer_stmt(lbProcedure *p, lbDefer const &d) { } } -gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block) { +gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, TokenPos pos) { + TokenPos prev_token_pos = p->branch_location_pos; + if (p->uses_branch_location) { + p->branch_location_pos = pos; + } + defer (p->branch_location_pos = prev_token_pos); + isize count = p->defer_stmts.count; isize i = count; while (i --> 0) { @@ -2813,6 +2830,21 @@ gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlo } } +gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, Ast *node) { + TokenPos pos = {}; + if (node) { + if (node->kind == Ast_BlockStmt) { + pos = ast_end_token(node).pos; + } else if (node->kind == Ast_CaseClause) { + pos = ast_end_token(node).pos; + } else { + pos = ast_token(node).pos; + } + } + return lb_emit_defer_stmts(p, kind, block, pos); +} + + gb_internal void lb_add_defer_node(lbProcedure *p, isize scope_index, Ast *stmt) { Type *pt = base_type(p->type); GB_ASSERT(pt->kind == Type_Proc); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 9d4505bb0..6c12b37be 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -43,6 +43,8 @@ gb_internal u64 lb_typeid_kind(lbModule *m, Type *type, u64 id=0) { if (flags & BasicFlag_Pointer) kind = Typeid_Pointer; if (flags & BasicFlag_String) kind = Typeid_String; if (flags & BasicFlag_Rune) kind = Typeid_Rune; + + if (bt->Basic.kind == Basic_typeid) kind = Typeid_Type_Id; } break; case Type_Pointer: kind = Typeid_Pointer; break; case Type_MultiPointer: kind = Typeid_Multi_Pointer; break; @@ -59,8 +61,6 @@ gb_internal u64 lb_typeid_kind(lbModule *m, Type *type, u64 id=0) { case Type_Proc: kind = Typeid_Procedure; break; case Type_BitSet: kind = Typeid_Bit_Set; break; case Type_SimdVector: kind = Typeid_Simd_Vector; break; - case Type_RelativePointer: kind = Typeid_Relative_Pointer; break; - case Type_RelativeMultiPointer: kind = Typeid_Relative_Multi_Pointer; break; case Type_SoaPointer: kind = Typeid_SoaPointer; break; case Type_BitField: kind = Typeid_Bit_Field; break; } @@ -948,30 +948,6 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } break; - case Type_RelativePointer: - { - tag_type = t_type_info_relative_pointer; - LLVMValueRef vals[2] = { - get_type_info_ptr(m, t->RelativePointer.pointer_type), - get_type_info_ptr(m, t->RelativePointer.base_integer), - }; - - variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); - } - break; - - case Type_RelativeMultiPointer: - { - tag_type = t_type_info_relative_multi_pointer; - LLVMValueRef vals[2] = { - get_type_info_ptr(m, t->RelativeMultiPointer.pointer_type), - get_type_info_ptr(m, t->RelativeMultiPointer.base_integer), - }; - - variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); - } - break; - case Type_Matrix: { tag_type = t_type_info_matrix; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index f63c42ab9..7b7c9d6e9 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -124,8 +124,16 @@ gb_internal void lb_mem_zero_ptr(lbProcedure *p, LLVMValueRef ptr, Type *type, u switch (kind) { case LLVMStructTypeKind: case LLVMArrayTypeKind: - // NOTE(bill): Enforce zeroing through memset to make sure padding is zeroed too - lb_mem_zero_ptr_internal(p, ptr, lb_const_int(p->module, t_int, sz).value, alignment, false); + if (is_type_tuple(type)) { + // NOTE(bill): even though this should be safe, to keep ASAN happy, do not zero the implicit padding at the end + GB_ASSERT(type->kind == Type_Tuple); + i64 n = type->Tuple.variables.count-1; + i64 end_offset = type->Tuple.offsets[n] + type_size_of(type->Tuple.variables[n]->type); + lb_mem_zero_ptr_internal(p, ptr, lb_const_int(p->module, t_int, end_offset).value, alignment, false); + } else { + // NOTE(bill): Enforce zeroing through memset to make sure padding is zeroed too + lb_mem_zero_ptr_internal(p, ptr, lb_const_int(p->module, t_int, sz).value, alignment, false); + } break; default: LLVMBuildStore(p->builder, LLVMConstNull(lb_type(p->module, type)), ptr); @@ -269,7 +277,7 @@ gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t) { if (lb_try_update_alignment(ptr, align)) { LLVMTypeRef result_type = lb_type(p->module, t); res.value = LLVMBuildPointerCast(p->builder, ptr.value, LLVMPointerType(result_type, 0), ""); - res.value = LLVMBuildLoad2(p->builder, result_type, res.value, ""); + res.value = OdinLLVMBuildLoad(p, result_type, res.value); return res; } lbAddr addr = lb_add_local_generated(p, t, false); @@ -468,8 +476,8 @@ gb_internal lbValue lb_emit_or_else(lbProcedure *p, Ast *arg, Ast *else_expr, Ty } } -gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return_results); -gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res); +gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return_results, TokenPos pos); +gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res, TokenPos pos); gb_internal lbValue lb_emit_or_return(lbProcedure *p, Ast *arg, TypeAndValue const &tv) { lbValue lhs = {}; @@ -498,10 +506,10 @@ gb_internal lbValue lb_emit_or_return(lbProcedure *p, Ast *arg, TypeAndValue con lbValue found = map_must_get(&p->module->values, end_entity); lb_emit_store(p, found, rhs); - lb_build_return_stmt(p, {}); + lb_build_return_stmt(p, {}, ast_token(arg).pos); } else { GB_ASSERT(tuple->variables.count == 1); - lb_build_return_stmt_internal(p, rhs); + lb_build_return_stmt_internal(p, rhs, ast_token(arg).pos); } } lb_start_block(p, continue_block); @@ -1123,10 +1131,6 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) { Type *t = base_type(type_deref(s.type)); Type *result_type = nullptr; - if (is_type_relative_pointer(t)) { - s = lb_addr_get_ptr(p, lb_addr(s)); - } - if (is_type_struct(t)) { result_type = get_struct_field_type(t, index); } else if (is_type_union(t)) { @@ -1196,9 +1200,22 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) { lbValue gep = lb_emit_struct_ep_internal(p, s, index, result_type); Type *bt = base_type(t); - if (bt->kind == Type_Struct && bt->Struct.is_packed) { - lb_set_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_IS_PACKED, 1); - GB_ASSERT(lb_get_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_IS_PACKED) == 1); + if (bt->kind == Type_Struct) { + if (bt->Struct.is_packed) { + lb_set_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_IS_PACKED, 1); + GB_ASSERT(lb_get_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_IS_PACKED) == 1); + } + u64 align_max = bt->Struct.custom_max_field_align; + u64 align_min = bt->Struct.custom_min_field_align; + GB_ASSERT(align_min == 0 || align_max == 0 || align_min <= align_max); + if (align_max) { + lb_set_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_MAX_ALIGN, align_max); + GB_ASSERT(lb_get_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_MAX_ALIGN) == align_max); + } + if (align_min) { + lb_set_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_MIN_ALIGN, align_min); + GB_ASSERT(lb_get_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_MIN_ALIGN) == align_min); + } } return gep; @@ -1432,8 +1449,6 @@ gb_internal lbValue lb_emit_deep_field_gep(lbProcedure *p, lbValue e, Selection e = lb_emit_array_epi(p, e, index); } else if (type->kind == Type_Map) { e = lb_emit_struct_ep(p, e, index); - } else if (type->kind == Type_RelativePointer) { - e = lb_emit_struct_ep(p, e, index); } else { GB_PANIC("un-gep-able type %s", type_to_string(type)); } diff --git a/src/main.cpp b/src/main.cpp index 04d3bdf52..0450c61ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,8 +89,8 @@ gb_global Timings global_timings = {0}; #if defined(GB_SYSTEM_OSX) #include - #if LLVM_VERSION_MAJOR < 11 || (LLVM_VERSION_MAJOR > 14 && LLVM_VERSION_MAJOR < 17) || LLVM_VERSION_MAJOR > 18 - #error LLVM Version 11..=14 or =18 is required => "brew install llvm@14" + #if LLVM_VERSION_MAJOR < 11 || (LLVM_VERSION_MAJOR > 14 && LLVM_VERSION_MAJOR < 17) || LLVM_VERSION_MAJOR > 19 + #error LLVM Version 11..=14 or 17..=19 is required => "brew install llvm@14" #endif #endif @@ -328,6 +328,8 @@ enum BuildFlagKind { BuildFlag_NoRPath, BuildFlag_NoEntryPoint, BuildFlag_UseLLD, + BuildFlag_UseRADLink, + BuildFlag_Linker, BuildFlag_UseSeparateModules, BuildFlag_NoThreadedChecker, BuildFlag_ShowDebugMessages, @@ -399,6 +401,7 @@ enum BuildFlagKind { BuildFlag_InternalModulePerFile, BuildFlag_InternalCached, BuildFlag_InternalNoInline, + BuildFlag_InternalByValue, BuildFlag_Tilde, @@ -539,6 +542,8 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_NoRPath, str_lit("no-rpath"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check &~ Command_test); add_flag(&build_flags, BuildFlag_UseLLD, str_lit("lld"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_UseRADLink, str_lit("radlink"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_Linker, str_lit("linker"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_UseSeparateModules, str_lit("use-separate-modules"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all); @@ -608,6 +613,7 @@ gb_internal bool parse_build_flags(Array args) { 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); + add_flag(&build_flags, BuildFlag_InternalByValue, str_lit("internal-by-value"), BuildFlagParam_None, Command_all); #if ALLOW_TILDE add_flag(&build_flags, BuildFlag_Tilde, str_lit("tilde"), BuildFlagParam_None, Command__does_build); @@ -1060,27 +1066,29 @@ gb_internal bool parse_build_flags(Array args) { } if (!found) { - struct DistanceAndTargetIndex { - isize distance; - isize target_index; - }; + if (str != "?") { + struct DistanceAndTargetIndex { + isize distance; + isize target_index; + }; - DistanceAndTargetIndex distances[gb_count_of(named_targets)] = {}; - for (isize i = 0; i < gb_count_of(named_targets); i++) { - distances[i].target_index = i; - distances[i].distance = levenstein_distance_case_insensitive(str, named_targets[i].name); - } - gb_sort_array(distances, gb_count_of(distances), gb_isize_cmp(gb_offset_of(DistanceAndTargetIndex, distance))); - - gb_printf_err("Unknown target '%.*s'\n", LIT(str)); - - if (distances[0].distance <= MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { - gb_printf_err("Did you mean:\n"); + DistanceAndTargetIndex distances[gb_count_of(named_targets)] = {}; for (isize i = 0; i < gb_count_of(named_targets); i++) { - if (distances[i].distance > MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { - break; + distances[i].target_index = i; + distances[i].distance = levenstein_distance_case_insensitive(str, named_targets[i].name); + } + gb_sort_array(distances, gb_count_of(distances), gb_isize_cmp(gb_offset_of(DistanceAndTargetIndex, distance))); + + gb_printf_err("Unknown target '%.*s'\n", LIT(str)); + + if (distances[0].distance <= MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { + gb_printf_err("Did you mean:\n"); + for (isize i = 0; i < gb_count_of(named_targets); i++) { + if (distances[i].distance > MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { + break; + } + gb_printf_err("\t%.*s\n", LIT(named_targets[distances[i].target_index].name)); } - gb_printf_err("\t%.*s\n", LIT(named_targets[distances[i].target_index].name)); } } gb_printf_err("All supported targets:\n"); @@ -1199,8 +1207,38 @@ gb_internal bool parse_build_flags(Array args) { build_context.no_thread_local = true; break; case BuildFlag_UseLLD: - build_context.use_lld = true; + gb_printf_err("Warning: Use of -lld has been deprecated in favour of -linker:lld\n"); + build_context.linker_choice = Linker_lld; break; + case BuildFlag_UseRADLink: + gb_printf_err("Warning: Use of -lld has been deprecated in favour of -linker:radlink\n"); + build_context.linker_choice = Linker_radlink; + break; + case BuildFlag_Linker: + { + GB_ASSERT(value.kind == ExactValue_String); + LinkerChoice linker_choice = Linker_Invalid; + + for (i32 i = 0; i < Linker_COUNT; i++) { + if (linker_choices[i] == value.value_string) { + linker_choice = cast(LinkerChoice)i; + break; + } + } + + if (linker_choice == Linker_Invalid) { + gb_printf_err("Invalid option for -linker:. Expected one of the following\n"); + for (i32 i = 0; i < Linker_COUNT; i++) { + gb_printf_err("\t%.*s\n", LIT(linker_choices[i])); + } + bad_flags = true; + } else { + build_context.linker_choice = linker_choice; + } + } + break; + + case BuildFlag_UseSeparateModules: build_context.use_separate_modules = true; break; @@ -1472,6 +1510,9 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_InternalNoInline: build_context.internal_no_inline = true; break; + case BuildFlag_InternalByValue: + build_context.internal_by_value = true; + break; case BuildFlag_Tilde: build_context.tilde_backend = true; @@ -1747,7 +1788,7 @@ gb_internal void check_defines(BuildContext *bc, Checker *c) { String name = make_string_c(entry.key); ExactValue value = entry.value; GB_ASSERT(value.kind != ExactValue_Invalid); - + bool found = false; for_array(i, c->info.defineables) { Defineable *def = &c->info.defineables[i]; @@ -1776,9 +1817,9 @@ gb_internal void temp_alloc_defineable_strings(Checker *c) { gb_internal GB_COMPARE_PROC(defineables_cmp) { Defineable *x = (Defineable *)a; Defineable *y = (Defineable *)b; - + int cmp = 0; - + String x_file = get_file_path_string(x->pos.file_id); String y_file = get_file_path_string(y->pos.file_id); cmp = string_compare(x_file, y_file); @@ -1789,8 +1830,22 @@ gb_internal GB_COMPARE_PROC(defineables_cmp) { return i32_cmp(x->pos.offset, y->pos.offset); } -gb_internal void sort_defineables(Checker *c) { +gb_internal void sort_defineables_and_remove_duplicates(Checker *c) { + if (c->info.defineables.count == 0) { + return; + } gb_sort_array(c->info.defineables.data, c->info.defineables.count, defineables_cmp); + + Defineable prev = c->info.defineables[0]; + for (isize i = 1; i < c->info.defineables.count; ) { + Defineable curr = c->info.defineables[i]; + if (prev.pos == curr.pos) { + array_ordered_remove(&c->info.defineables, i); + continue; + } + prev = curr; + i++; + } } gb_internal void export_defineables(Checker *c, String path) { @@ -2065,7 +2120,7 @@ gb_internal void export_dependencies(Checker *c) { for_array(i, files) { AstFile *file = files[i]; gb_fprintf(&f, "\t\t\"%.*s\"", LIT(file->fullpath)); - if (i+1 == files.count) { + if (i+1 < files.count) { gb_fprintf(&f, ","); } gb_fprintf(&f, "\n"); @@ -2078,7 +2133,7 @@ gb_internal void export_dependencies(Checker *c) { for_array(i, load_files) { LoadFileCache *cache = load_files[i]; gb_fprintf(&f, "\t\t\"%.*s\"", LIT(cache->path)); - if (i+1 == load_files.count) { + if (i+1 < load_files.count) { gb_fprintf(&f, ","); } gb_fprintf(&f, "\n"); @@ -2125,11 +2180,18 @@ gb_internal void remove_temp_files(lbGenerator *gen) { } -gb_internal void print_show_help(String const arg0, String const &command) { +gb_internal void print_show_help(String const arg0, String command, String optional_flag = {}) { + if (command == "help" && optional_flag.len != 0 && optional_flag[0] != '-') { + command = optional_flag; + optional_flag = {}; + } + print_usage_line(0, "%.*s is a tool for managing Odin source code.", LIT(arg0)); print_usage_line(0, "Usage:"); print_usage_line(1, "%.*s %.*s [arguments]", LIT(arg0), LIT(command)); print_usage_line(0, ""); + defer (print_usage_line(0, "")); + if (command == "build") { print_usage_line(1, "build Compiles directory of .odin files as an executable."); @@ -2175,519 +2237,560 @@ gb_internal void print_show_help(String const arg0, String const &command) { bool check_only = command == "check" || strip_semicolon; bool check = run_or_build || check_only; + if (command == "help") { + doc = true; + build = true; + run_or_build = true; + test_only = true; + strip_semicolon = true; + check_only = true; + check = true; + } + print_usage_line(0, ""); print_usage_line(1, "Flags"); print_usage_line(0, ""); - if (doc) { - print_usage_line(1, "-all-packages"); - print_usage_line(2, "Generates documentation for all packages used in the current project."); + + auto const print_flag = [&optional_flag](char const *flag) -> bool { + if (optional_flag.len != 0) { + String f = make_string_c(flag); + isize i = string_index_byte(f, ':'); + if (i >= 0) { + f.len = i; + } + if (optional_flag != f) { + return false; + } + } print_usage_line(0, ""); + print_usage_line(1, flag); + return true; + }; + + + if (doc) { + if (print_flag("-all-packages")) { + print_usage_line(2, "Generates documentation for all packages used in the current project."); + } } if (test_only) { - print_usage_line(1, "-all-packages"); - print_usage_line(2, "Tests all packages imported into the given initial package."); - print_usage_line(0, ""); + if (print_flag("-all-packages")) { + print_usage_line(2, "Tests all packages imported into the given initial package."); + } } if (build) { - print_usage_line(1, "-build-mode:"); - print_usage_line(2, "Sets the build mode."); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-build-mode:exe Builds as an executable."); - print_usage_line(3, "-build-mode:test Builds as an executable that executes tests."); - print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library."); - print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library."); - print_usage_line(3, "-build-mode:lib Builds as a statically linked library."); - print_usage_line(3, "-build-mode:static Builds as a statically linked library."); - print_usage_line(3, "-build-mode:obj Builds as an object file."); - print_usage_line(3, "-build-mode:object Builds as an object file."); - print_usage_line(3, "-build-mode:assembly Builds as an assembly file."); - print_usage_line(3, "-build-mode:assembler Builds as an assembly file."); - print_usage_line(3, "-build-mode:asm Builds as an assembly file."); - print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file."); - print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file."); - print_usage_line(0, ""); + if (print_flag("-build-mode:")) { + print_usage_line(2, "Sets the build mode."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-build-mode:exe Builds as an executable."); + print_usage_line(3, "-build-mode:test Builds as an executable that executes tests."); + print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:dynamic Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:lib Builds as a statically linked library."); + print_usage_line(3, "-build-mode:static Builds as a statically linked library."); + print_usage_line(3, "-build-mode:obj Builds as an object file."); + print_usage_line(3, "-build-mode:object Builds as an object file."); + print_usage_line(3, "-build-mode:assembly Builds as an assembly file."); + print_usage_line(3, "-build-mode:assembler Builds as an assembly file."); + print_usage_line(3, "-build-mode:asm Builds as an assembly file."); + print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file."); + print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file."); + } } if (check) { - print_usage_line(1, "-collection:="); - print_usage_line(2, "Defines a library collection used for imports."); - print_usage_line(2, "Example: -collection:shared=dir/to/shared"); - print_usage_line(2, "Usage in Code:"); - print_usage_line(3, "import \"shared:foo\""); - print_usage_line(0, ""); + if (print_flag("-collection:=")) { + print_usage_line(2, "Defines a library collection used for imports."); + print_usage_line(2, "Example: -collection:shared=dir/to/shared"); + print_usage_line(2, "Usage in Code:"); + print_usage_line(3, "import \"shared:foo\""); + } - print_usage_line(1, "-custom-attribute:"); - print_usage_line(2, "Add a custom attribute which will be ignored if it is unknown."); - print_usage_line(2, "This can be used with metaprogramming tools."); - print_usage_line(2, "Examples:"); - print_usage_line(3, "-custom-attribute:my_tag"); - print_usage_line(3, "-custom-attribute:my_tag,the_other_thing"); - print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing"); - print_usage_line(0, ""); + if (print_flag("-custom-attribute:")) { + print_usage_line(2, "Add a custom attribute which will be ignored if it is unknown."); + print_usage_line(2, "This can be used with metaprogramming tools."); + print_usage_line(2, "Examples:"); + print_usage_line(3, "-custom-attribute:my_tag"); + print_usage_line(3, "-custom-attribute:my_tag,the_other_thing"); + print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing"); + } } if (run_or_build) { - print_usage_line(1, "-debug"); - print_usage_line(2, "Enables debug information, and defines the global constant ODIN_DEBUG to be 'true'."); - print_usage_line(0, ""); + if (print_flag("-debug")) { + print_usage_line(2, "Enables debug information, and defines the global constant ODIN_DEBUG to be 'true'."); + } } if (check) { - print_usage_line(1, "-default-to-nil-allocator"); - print_usage_line(2, "Sets the default allocator to be the nil_allocator, an allocator which does nothing."); - print_usage_line(0, ""); + if (print_flag("-default-to-nil-allocator")) { + print_usage_line(2, "Sets the default allocator to be the nil_allocator, an allocator which does nothing."); + } - print_usage_line(1, "-define:="); - print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); - print_usage_line(2, "Example: -define:SPAM=123"); - print_usage_line(2, "Usage in code:"); - print_usage_line(3, "#config(SPAM, default_value)"); - print_usage_line(0, ""); + if (print_flag("-define:=")) { + print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); + print_usage_line(2, "Example: -define:SPAM=123"); + print_usage_line(2, "Usage in code:"); + print_usage_line(3, "#config(SPAM, default_value)"); + } } if (run_or_build) { - print_usage_line(1, "-disable-assert"); - print_usage_line(2, "Disables the code generation of the built-in run-time 'assert' procedure, and defines the global constant ODIN_DISABLE_ASSERT to be 'true'."); - print_usage_line(0, ""); + if (print_flag("-disable-assert")) { + print_usage_line(2, "Disables the code generation of the built-in run-time 'assert' procedure, and defines the global constant ODIN_DISABLE_ASSERT to be 'true'."); + } - print_usage_line(1, "-disable-red-zone"); - print_usage_line(2, "Disables red zone on a supported freestanding target."); - print_usage_line(0, ""); + if (print_flag("-disable-red-zone")) { + print_usage_line(2, "Disables red zone on a supported freestanding target."); + } } if (check) { - print_usage_line(1, "-disallow-do"); - print_usage_line(2, "Disallows the 'do' keyword in the project."); - print_usage_line(0, ""); + if (print_flag("-disallow-do")) { + print_usage_line(2, "Disallows the 'do' keyword in the project."); + } } if (doc) { - print_usage_line(1, "-doc-format"); - print_usage_line(2, "Generates documentation as the .odin-doc format (useful for external tooling)."); - print_usage_line(0, ""); + if (print_flag("-doc-format")) { + print_usage_line(2, "Generates documentation as the .odin-doc format (useful for external tooling)."); + } } if (run_or_build) { - print_usage_line(1, "-dynamic-map-calls"); - print_usage_line(2, "Uses dynamic map calls to minimize code generation at the cost of runtime execution."); - print_usage_line(0, ""); + if (print_flag("-dynamic-map-calls")) { + print_usage_line(2, "Uses dynamic map calls to minimize code generation at the cost of runtime execution."); + } } if (check) { - print_usage_line(1, "-error-pos-style:"); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); - print_usage_line(3, "-error-pos-style:odin file/path(45:3)"); - print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)"); - print_usage_line(0, ""); + if (print_flag("-error-pos-style:")) { + print_usage_line(2, "Available options:"); + print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); + print_usage_line(3, "-error-pos-style:odin file/path(45:3)"); + print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)"); + } - print_usage_line(1, "-export-defineables:"); - print_usage_line(2, "Exports an overview of all the #config/#defined usages in CSV format to the given file path."); - print_usage_line(2, "Example: -export-defineables:defineables.csv"); - print_usage_line(0, ""); + if (print_flag("-export-defineables:")) { + print_usage_line(2, "Exports an overview of all the #config/#defined usages in CSV format to the given file path."); + print_usage_line(2, "Example: -export-defineables:defineables.csv"); + } - print_usage_line(1, "-export-dependencies:"); - print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`."); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); - print_usage_line(3, "-export-dependencies:json Exports in JSON format"); - print_usage_line(0, ""); + if (print_flag("-export-dependencies:")) { + print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); + print_usage_line(3, "-export-dependencies:json Exports in JSON format"); + } - print_usage_line(1, "-export-dependencies-file:"); - print_usage_line(2, "Specifies the filename for `-export-dependencies`."); - print_usage_line(2, "Example: -export-dependencies-file:dependencies.d"); - print_usage_line(0, ""); + if (print_flag("-export-dependencies-file:")) { + print_usage_line(2, "Specifies the filename for `-export-dependencies`."); + print_usage_line(2, "Example: -export-dependencies-file:dependencies.d"); + } - print_usage_line(1, "-export-timings:"); - print_usage_line(2, "Exports timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`."); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-export-timings:json Exports compile time stats to JSON."); - print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV."); - print_usage_line(0, ""); + if (print_flag("-export-timings:")) { + print_usage_line(2, "Exports timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-export-timings:json Exports compile time stats to JSON."); + print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV."); + } - print_usage_line(1, "-export-timings-file:"); - print_usage_line(2, "Specifies the filename for `-export-timings`."); - print_usage_line(2, "Example: -export-timings-file:timings.json"); - print_usage_line(0, ""); + if (print_flag("-export-timings-file:")) { + print_usage_line(2, "Specifies the filename for `-export-timings`."); + print_usage_line(2, "Example: -export-timings-file:timings.json"); + } } if (run_or_build) { - print_usage_line(1, "-extra-assembler-flags:"); + if (print_flag("-extra-assembler-flags:")) { print_usage_line(2, "Adds extra assembler specific flags in a string."); - print_usage_line(0, ""); + } - print_usage_line(1, "-extra-linker-flags:"); + if (print_flag("-extra-linker-flags:")) { print_usage_line(2, "Adds extra linker specific flags in a string."); - print_usage_line(0, ""); + } } if (check) { - print_usage_line(1, "-file"); - print_usage_line(2, "Tells `%.*s %.*s` to treat the given file as a self-contained package.", LIT(arg0), LIT(command)); - print_usage_line(2, "This means that `/a.odin` won't have access to `/b.odin`'s contents."); - print_usage_line(0, ""); + if (print_flag("-file")) { + print_usage_line(2, "Tells `%.*s %.*s` to treat the given file as a self-contained package.", LIT(arg0), LIT(command)); + print_usage_line(2, "This means that `/a.odin` won't have access to `/b.odin`'s contents."); + } - print_usage_line(1, "-foreign-error-procedures"); - print_usage_line(2, "States that the error procedures used in the runtime are defined in a separate translation unit."); - print_usage_line(0, ""); + if (print_flag("-foreign-error-procedures")) { + print_usage_line(2, "States that the error procedures used in the runtime are defined in a separate translation unit."); + } - print_usage_line(1, "-ignore-unknown-attributes"); - print_usage_line(2, "Ignores unknown attributes."); - print_usage_line(2, "This can be used with metaprogramming tools."); - print_usage_line(0, ""); + if (print_flag("-ignore-unknown-attributes")) { + print_usage_line(2, "Ignores unknown attributes."); + print_usage_line(2, "This can be used with metaprogramming tools."); + } } if (run_or_build) { #if defined(GB_SYSTEM_WINDOWS) - print_usage_line(1, "-ignore-vs-search"); - print_usage_line(2, "[Windows only]"); - print_usage_line(2, "Ignores the Visual Studio search for library paths."); - print_usage_line(0, ""); + if (print_flag("-ignore-vs-search")) { + print_usage_line(2, "[Windows only]"); + print_usage_line(2, "Ignores the Visual Studio search for library paths."); + } #endif } if (check) { - print_usage_line(1, "-ignore-warnings"); - print_usage_line(2, "Ignores warning messages."); - print_usage_line(0, ""); + if (print_flag("-ignore-warnings")) { + print_usage_line(2, "Ignores warning messages."); + } - print_usage_line(1, "-json-errors"); - print_usage_line(2, "Prints the error messages as json to stderr."); - print_usage_line(0, ""); + if (print_flag("-json-errors")) { + print_usage_line(2, "Prints the error messages as json to stderr."); + } } if (run_or_build) { - print_usage_line(1, "-keep-temp-files"); - print_usage_line(2, "Keeps the temporary files generated during compilation."); - print_usage_line(0, ""); + if (print_flag("-keep-temp-files")) { + print_usage_line(2, "Keeps the temporary files generated during compilation."); + } } else if (strip_semicolon) { - print_usage_line(1, "-keep-temp-files"); - print_usage_line(2, "Keeps the temporary files generated during stripping the unneeded semicolons from files."); - print_usage_line(0, ""); + if (print_flag("-keep-temp-files")) { + print_usage_line(2, "Keeps the temporary files generated during stripping the unneeded semicolons from files."); + } } if (run_or_build) { - print_usage_line(1, "-lld"); - print_usage_line(2, "Uses the LLD linker rather than the default."); - print_usage_line(0, ""); + if (print_flag("-linker:")) { + print_usage_line(2, "Specify the linker to use."); + print_usage_line(2, "Choices:"); + for (i32 i = 0; i < Linker_COUNT; i++) { + print_usage_line(3, "%.*s", LIT(linker_choices[i])); + } + } + + if (print_flag("-lld")) { + print_usage_line(2, "Uses the LLD linker rather than the default."); + } } if (check) { - print_usage_line(1, "-max-error-count:"); - print_usage_line(2, "Sets the maximum number of errors that can be displayed before the compiler terminates."); - print_usage_line(2, "Must be an integer >0."); - print_usage_line(2, "If not set, the default max error count is %d.", DEFAULT_MAX_ERROR_COLLECTOR_COUNT); - print_usage_line(0, ""); + if (print_flag("-max-error-count:")) { + print_usage_line(2, "Sets the maximum number of errors that can be displayed before the compiler terminates."); + print_usage_line(2, "Must be an integer >0."); + print_usage_line(2, "If not set, the default max error count is %d.", DEFAULT_MAX_ERROR_COLLECTOR_COUNT); + } } if (run_or_build) { - print_usage_line(1, "-microarch:"); - print_usage_line(2, "Specifies the specific micro-architecture for the build in a string."); - print_usage_line(2, "Examples:"); - print_usage_line(3, "-microarch:sandybridge"); - print_usage_line(3, "-microarch:native"); - print_usage_line(3, "-microarch:\"?\" for a list"); - print_usage_line(0, ""); + if (print_flag("-microarch:")) { + print_usage_line(2, "Specifies the specific micro-architecture for the build in a string."); + print_usage_line(2, "Examples:"); + print_usage_line(3, "-microarch:sandybridge"); + print_usage_line(3, "-microarch:native"); + print_usage_line(3, "-microarch:\"?\" for a list"); + } } if (check) { - print_usage_line(1, "-min-link-libs"); - print_usage_line(2, "If set, the number of linked libraries will be minimized to prevent duplications."); - print_usage_line(2, "This is useful for so called \"dumb\" linkers compared to \"smart\" linkers."); - print_usage_line(0, ""); + if (print_flag("-min-link-libs")) { + print_usage_line(2, "If set, the number of linked libraries will be minimized to prevent duplications."); + print_usage_line(2, "This is useful for so called \"dumb\" linkers compared to \"smart\" linkers."); + } } if (run_or_build) { - print_usage_line(1, "-minimum-os-version:"); - print_usage_line(2, "Sets the minimum OS version targeted by the application."); - print_usage_line(2, "Default: -minimum-os-version:11.0.0"); - print_usage_line(2, "Only used when target is Darwin, if given, linking mismatched versions will emit a warning."); - print_usage_line(0, ""); + if (print_flag("-minimum-os-version:")) { + print_usage_line(2, "Sets the minimum OS version targeted by the application."); + print_usage_line(2, "Default: -minimum-os-version:11.0.0"); + print_usage_line(2, "Only used when target is Darwin, if given, linking mismatched versions will emit a warning."); + } - print_usage_line(1, "-no-bounds-check"); - print_usage_line(2, "Disables bounds checking program wide."); - print_usage_line(0, ""); + if (print_flag("-no-bounds-check")) { + print_usage_line(2, "Disables bounds checking program wide."); + } - print_usage_line(1, "-no-crt"); - print_usage_line(2, "Disables automatic linking with the C Run Time."); - print_usage_line(0, ""); + if (print_flag("-no-crt")) { + print_usage_line(2, "Disables automatic linking with the C Run Time."); + } } if (check && command != "test") { - print_usage_line(1, "-no-entry-point"); - print_usage_line(2, "Removes default requirement of an entry point (e.g. main procedure)."); - print_usage_line(0, ""); - } - - if (run_or_build) { - print_usage_line(1, "-no-rpath"); - print_usage_line(2, "Disables automatic addition of an rpath linked to the executable directory."); - print_usage_line(0, ""); - - print_usage_line(1, "-no-thread-local"); - print_usage_line(2, "Ignores @thread_local attribute, effectively treating the program as if it is single-threaded."); - print_usage_line(0, ""); - - print_usage_line(1, "-no-threaded-checker"); - print_usage_line(2, "Disables multithreading in the semantic checker stage."); - print_usage_line(0, ""); - - print_usage_line(1, "-no-type-assert"); - print_usage_line(2, "Disables type assertion checking program wide."); - print_usage_line(0, ""); - } - - if (run_or_build) { - print_usage_line(1, "-o:"); - print_usage_line(2, "Sets the optimization mode for compilation."); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-o:none"); - print_usage_line(3, "-o:minimal"); - print_usage_line(3, "-o:size"); - print_usage_line(3, "-o:speed"); - if (LB_USE_NEW_PASS_SYSTEM) { - print_usage_line(3, "-o:aggressive (use this with caution)"); + if (print_flag("-no-entry-point")) { + print_usage_line(2, "Removes default requirement of an entry point (e.g. main procedure)."); + } + } + + if (run_or_build) { + if (print_flag("-no-rpath")) { + print_usage_line(2, "Disables automatic addition of an rpath linked to the executable directory."); + } + + if (print_flag("-no-thread-local")) { + print_usage_line(2, "Ignores @thread_local attribute, effectively treating the program as if it is single-threaded."); + } + + if (print_flag("-no-threaded-checker")) { + print_usage_line(2, "Disables multithreading in the semantic checker stage."); + } + + if (print_flag("-no-type-assert")) { + print_usage_line(2, "Disables type assertion checking program wide."); + } + } + + if (run_or_build) { + if (print_flag("-o:")) { + print_usage_line(2, "Sets the optimization mode for compilation."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-o:none"); + print_usage_line(3, "-o:minimal"); + print_usage_line(3, "-o:size"); + print_usage_line(3, "-o:speed"); + if (LB_USE_NEW_PASS_SYSTEM) { + print_usage_line(3, "-o:aggressive (use this with caution)"); + } + print_usage_line(2, "The default is -o:minimal."); } - print_usage_line(2, "The default is -o:minimal."); - print_usage_line(0, ""); - print_usage_line(1, "-obfuscate-source-code-locations"); - print_usage_line(2, "Obfuscate the file and procedure strings, and line and column numbers, stored with a 'runtime.Source_Code_Location' value."); - print_usage_line(0, ""); + if (print_flag("-obfuscate-source-code-locations")) { + print_usage_line(2, "Obfuscate the file and procedure strings, and line and column numbers, stored with a 'runtime.Source_Code_Location' value."); + } - print_usage_line(1, "-out:"); - print_usage_line(2, "Sets the file name of the outputted executable."); - print_usage_line(2, "Example: -out:foo.exe"); - print_usage_line(0, ""); + if (print_flag("-out:")) { + print_usage_line(2, "Sets the file name of the outputted executable."); + print_usage_line(2, "Example: -out:foo.exe"); + } } if (doc) { - print_usage_line(1, "-out:"); - print_usage_line(2, "Sets the base name of the resultig .odin-doc file."); - print_usage_line(2, "The extension can be optionally included; the resulting file will always have an extension of '.odin-doc'."); - print_usage_line(2, "Example: -out:foo"); - print_usage_line(0, ""); + if (print_flag("-out:")) { + print_usage_line(2, "Sets the base name of the resultig .odin-doc file."); + print_usage_line(2, "The extension can be optionally included; the resulting file will always have an extension of '.odin-doc'."); + print_usage_line(2, "Example: -out:foo"); + } } if (run_or_build) { #if defined(GB_SYSTEM_WINDOWS) - print_usage_line(1, "-pdb-name:"); - print_usage_line(2, "[Windows only]"); - print_usage_line(2, "Defines the generated PDB name when -debug is enabled."); - print_usage_line(2, "Example: -pdb-name:different.pdb"); - print_usage_line(0, ""); + if (print_flag("-pdb-name:")) { + print_usage_line(2, "[Windows only]"); + print_usage_line(2, "Defines the generated PDB name when -debug is enabled."); + print_usage_line(2, "Example: -pdb-name:different.pdb"); + } #endif } if (build) { - print_usage_line(1, "-print-linker-flags"); - print_usage_line(2, "Prints the all of the flags/arguments that will be passed to the linker."); - print_usage_line(0, ""); + if (print_flag("-print-linker-flags")) { + print_usage_line(2, "Prints the all of the flags/arguments that will be passed to the linker."); + } } if (run_or_build) { - print_usage_line(1, "-reloc-mode:"); - print_usage_line(2, "Specifies the reloc mode."); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-reloc-mode:default"); - print_usage_line(3, "-reloc-mode:static"); - print_usage_line(3, "-reloc-mode:pic"); - print_usage_line(3, "-reloc-mode:dynamic-no-pic"); - print_usage_line(0, ""); + if (print_flag("-radlink")) { + print_usage_line(2, "Uses the RAD linker rather than the default."); + } + + if (print_flag("-reloc-mode:")) { + print_usage_line(2, "Specifies the reloc mode."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-reloc-mode:default"); + print_usage_line(3, "-reloc-mode:static"); + print_usage_line(3, "-reloc-mode:pic"); + print_usage_line(3, "-reloc-mode:dynamic-no-pic"); + } #if defined(GB_SYSTEM_WINDOWS) - print_usage_line(1, "-resource:"); - print_usage_line(2, "[Windows only]"); - print_usage_line(2, "Defines the resource file for the executable."); - print_usage_line(2, "Example: -resource:path/to/file.rc"); - print_usage_line(2, "or: -resource:path/to/file.res for a precompiled one."); - print_usage_line(0, ""); + if (print_flag("-resource:")) { + print_usage_line(2, "[Windows only]"); + print_usage_line(2, "Defines the resource file for the executable."); + print_usage_line(2, "Example: -resource:path/to/file.rc"); + print_usage_line(2, "or: -resource:path/to/file.res for a precompiled one."); + } #endif - print_usage_line(1, "-sanitize:"); - print_usage_line(2, "Enables sanitization analysis."); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-sanitize:address"); - print_usage_line(3, "-sanitize:memory"); - print_usage_line(3, "-sanitize:thread"); - print_usage_line(2, "NOTE: This flag can be used multiple times."); - print_usage_line(0, ""); + if (print_flag("-sanitize:")) { + print_usage_line(2, "Enables sanitization analysis."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-sanitize:address"); + print_usage_line(3, "-sanitize:memory"); + print_usage_line(3, "-sanitize:thread"); + print_usage_line(2, "NOTE: This flag can be used multiple times."); + } } if (doc) { - print_usage_line(1, "-short"); - print_usage_line(2, "Shows shortened documentation for the packages."); - print_usage_line(0, ""); + if (print_flag("-short")) { + print_usage_line(2, "Shows shortened documentation for the packages."); + } } if (check) { - print_usage_line(1, "-show-defineables"); - print_usage_line(2, "Shows an overview of all the #config/#defined usages in the project."); - print_usage_line(0, ""); + if (print_flag("-show-defineables")) { + print_usage_line(2, "Shows an overview of all the #config/#defined usages in the project."); + } - print_usage_line(1, "-show-system-calls"); - print_usage_line(2, "Prints the whole command and arguments for calls to external tools like linker and assembler."); - print_usage_line(0, ""); + if (print_flag("-show-system-calls")) { + print_usage_line(2, "Prints the whole command and arguments for calls to external tools like linker and assembler."); + } - print_usage_line(1, "-show-timings"); - print_usage_line(2, "Shows basic overview of the timings of different stages within the compiler in milliseconds."); - print_usage_line(0, ""); + if (print_flag("-show-timings")) { + print_usage_line(2, "Shows basic overview of the timings of different stages within the compiler in milliseconds."); + } - print_usage_line(1, "-show-more-timings"); - print_usage_line(2, "Shows an advanced overview of the timings of different stages within the compiler in milliseconds."); - print_usage_line(0, ""); + if (print_flag("-show-more-timings")) { + print_usage_line(2, "Shows an advanced overview of the timings of different stages within the compiler in milliseconds."); + } } if (check_only) { - print_usage_line(1, "-show-unused"); - print_usage_line(2, "Shows unused package declarations within the current project."); - print_usage_line(0, ""); - print_usage_line(1, "-show-unused-with-location"); - print_usage_line(2, "Shows unused package declarations within the current project with the declarations source location."); - print_usage_line(0, ""); + if (print_flag("-show-unused")) { + print_usage_line(2, "Shows unused package declarations within the current project."); + } + if (print_flag("-show-unused-with-location")) { + print_usage_line(2, "Shows unused package declarations within the current project with the declarations source location."); + } } if (check) { - print_usage_line(1, "-strict-style"); - print_usage_line(2, "This enforces parts of same style as the Odin compiler, prefer '-vet-style -vet-semicolon' if you do not want to match it exactly."); - print_usage_line(2, ""); - print_usage_line(2, "Errs on unneeded tokens, such as unneeded semicolons."); - print_usage_line(2, "Errs on missing trailing commas followed by a newline."); - print_usage_line(2, "Errs on deprecated syntax."); - print_usage_line(2, "Errs when the attached-brace style in not adhered to (also known as 1TBS)."); - print_usage_line(2, "Errs when 'case' labels are not in the same column as the associated 'switch' token."); - print_usage_line(0, ""); + if (print_flag("-strict-style")) { + print_usage_line(2, "This enforces parts of same style as the Odin compiler, prefer '-vet-style -vet-semicolon' if you do not want to match it exactly."); + print_usage_line(2, ""); + print_usage_line(2, "Errs on unneeded tokens, such as unneeded semicolons."); + print_usage_line(2, "Errs on missing trailing commas followed by a newline."); + print_usage_line(2, "Errs on deprecated syntax."); + print_usage_line(2, "Errs when the attached-brace style in not adhered to (also known as 1TBS)."); + print_usage_line(2, "Errs when 'case' labels are not in the same column as the associated 'switch' token."); + } } if (run_or_build) { - print_usage_line(1, "-strict-target-features"); - print_usage_line(2, "Makes @(enable_target_features=\"...\") behave the same way as @(require_target_features=\"...\")."); - print_usage_line(2, "This enforces that all generated code uses features supported by the combination of -target, -microarch, and -target-features."); - print_usage_line(0, ""); + if (print_flag("-strict-target-features")) { + print_usage_line(2, "Makes @(enable_target_features=\"...\") behave the same way as @(require_target_features=\"...\")."); + print_usage_line(2, "This enforces that all generated code uses features supported by the combination of -target, -microarch, and -target-features."); + } #if defined(GB_SYSTEM_WINDOWS) - print_usage_line(1, "-subsystem: