diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2f32e7a1..0cbe8ad23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,11 @@ jobs: steps: - uses: actions/checkout@v1 - name: Download LLVM - run: sudo apt-get install llvm-11 clang-11 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 17 + echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH - name: build odin run: ./build_odin.sh release - name: Odin version @@ -63,10 +67,8 @@ jobs: - uses: actions/checkout@v1 - name: Download LLVM, and setup PATH run: | - brew install llvm@13 - echo "/usr/local/opt/llvm@13/bin" >> $GITHUB_PATH - TMP_PATH=$(xcrun --show-sdk-path)/user/include - echo "CPATH=$TMP_PATH" >> $GITHUB_ENV + brew install llvm@17 + echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH - name: build odin run: ./build_odin.sh release - name: Odin version @@ -104,10 +106,8 @@ jobs: - uses: actions/checkout@v1 - name: Download LLVM and setup PATH run: | - brew install llvm@13 - echo "/opt/homebrew/opt/llvm@13/bin" >> $GITHUB_PATH - TMP_PATH=$(xcrun --show-sdk-path)/user/include - echo "CPATH=$TMP_PATH" >> $GITHUB_ENV + brew install llvm@17 + echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH - name: build odin run: ./build_odin.sh release - name: Odin version @@ -128,11 +128,11 @@ jobs: - name: Odin check examples/all run: ./odin check examples/all -strict-style timeout-minutes: 10 - # - name: Core library tests - # run: | - # cd tests/core - # make - # timeout-minutes: 10 + - name: Core library tests + run: | + cd tests/core + make + timeout-minutes: 10 - name: Odin internals tests run: | cd tests/internal diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 709f968a7..ff90ab57e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -47,7 +47,11 @@ jobs: steps: - uses: actions/checkout@v1 - name: (Linux) Download LLVM - run: sudo apt-get install llvm-11 clang-11 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 17 + echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH - name: build odin run: make nightly - name: Odin run @@ -78,10 +82,8 @@ jobs: - uses: actions/checkout@v1 - name: Download LLVM and setup PATH run: | - brew install llvm@13 dylibbundler - echo "/usr/local/opt/llvm@13/bin" >> $GITHUB_PATH - TMP_PATH=$(xcrun --show-sdk-path)/user/include - echo "CPATH=$TMP_PATH" >> $GITHUB_ENV + brew install llvm@17 dylibbundler + echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH - name: build odin # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to # not link with libunwind bundled with LLVM but link with libunwind on the system. @@ -114,10 +116,8 @@ jobs: - uses: actions/checkout@v1 - name: Download LLVM and setup PATH run: | - brew install llvm@13 dylibbundler - echo "/opt/homebrew/opt/llvm@13/bin" >> $GITHUB_PATH - TMP_PATH=$(xcrun --show-sdk-path)/user/include - echo "CPATH=$TMP_PATH" >> $GITHUB_ENV + brew install llvm@17 dylibbundler + echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH - name: build odin # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to # not link with libunwind bundled with LLVM but link with libunwind on the system. diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 401dcb857..c5c419de3 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -447,12 +447,12 @@ _append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: } @builtin -append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elem(array, arg, true, loc=loc) } @builtin -non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elem(array, arg, false, loc=loc) } @@ -496,12 +496,12 @@ _append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, l } @builtin -append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elems(array, true, loc, ..args) } @builtin -non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elems(array, false, loc, ..args) } @@ -556,7 +556,7 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i @builtin -inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -574,7 +574,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle } @builtin -inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -740,6 +740,9 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, a := (^Raw_Dynamic_Array)(array) if length <= a.cap { + if should_zero && a.len < length { + intrinsics.mem_zero(([^]E)(a.data)[a.len:], (length-a.len)*size_of(E)) + } a.len = max(length, 0) return nil } diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 62bee8620..6ca61c721 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -962,9 +962,11 @@ udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { return udivmod128(a, b, rem) } -@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -udivti3 :: proc "c" (a, b: u128) -> u128 { - return udivmodti4(a, b, nil) +when !IS_WASM { + @(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) + udivti3 :: proc "c" (a, b: u128) -> u128 { + return udivmodti4(a, b, nil) + } } diff --git a/base/runtime/procs_wasm.odin b/base/runtime/procs_wasm.odin index 26dcfef77..7501df460 100644 --- a/base/runtime/procs_wasm.odin +++ b/base/runtime/procs_wasm.odin @@ -7,19 +7,25 @@ ti_int :: struct #raw_union { all: i128, } +@(private="file") +ti_uint :: struct #raw_union { + using s: struct { lo, hi: u64 }, + all: u128, +} + @(link_name="__ashlti3", linkage="strong") -__ashlti3 :: proc "contextless" (a: i128, b_: u32) -> i128 { +__ashlti3 :: proc "contextless" (la, ha: u64, b_: u32) -> i128 { bits_in_dword :: size_of(u32)*8 b := u32(b_) input, result: ti_int - input.all = a + input.lo, input.hi = la, ha if b & bits_in_dword != 0 { result.lo = 0 result.hi = input.lo << (b-bits_in_dword) } else { if b == 0 { - return a + return input.all } result.lo = input.lo<>(bits_in_dword-b)) @@ -29,12 +35,20 @@ __ashlti3 :: proc "contextless" (a: i128, b_: u32) -> i128 { @(link_name="__multi3", linkage="strong") -__multi3 :: proc "contextless" (a, b: i128) -> i128 { +__multi3 :: proc "contextless" (la, ha, lb, hb: u64) -> i128 { x, y, r: ti_int - - x.all = a - y.all = b + + x.lo, x.hi = la, ha + y.lo, y.hi = lb, hb r.all = i128(x.lo * y.lo) // TODO this is incorrect r.hi += x.hi*y.lo + x.lo*y.hi return r.all -} \ No newline at end of file +} + +@(link_name="__udivti3", linkage="strong") +udivti3 :: proc "c" (la, ha, lb, hb: u64) -> u128 { + a, b: ti_uint + a.lo, a.hi = la, ha + b.lo, b.hi = lb, hb + return udivmodti4(a.all, b.all, nil) +} diff --git a/core/bufio/writer.odin b/core/bufio/writer.odin index c3debdaaa..3c7fd30c5 100644 --- a/core/bufio/writer.odin +++ b/core/bufio/writer.odin @@ -226,7 +226,6 @@ writer_to_writer :: proc(b: ^Writer) -> (s: io.Writer) { -@(private) _writer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { b := (^Writer)(stream_data) #partial switch mode { diff --git a/core/image/common.odin b/core/image/common.odin index c7507a85f..b576a9521 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -651,7 +651,7 @@ alpha_add_if_missing :: proc(img: ^Image, alpha_key := Alpha_Key{}, allocator := // We have keyed alpha. o: GA_Pixel for p in inp { - if p == key.r { + if p.r == key.r { o = GA_Pixel{0, key.g} } else { o = GA_Pixel{p.r, 255} @@ -710,7 +710,7 @@ alpha_add_if_missing :: proc(img: ^Image, alpha_key := Alpha_Key{}, allocator := // We have keyed alpha. o: GA_Pixel_16 for p in inp { - if p == key.r { + if p.r == key.r { o = GA_Pixel_16{0, key.g} } else { o = GA_Pixel_16{p.r, 65535} @@ -842,11 +842,11 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al bg := G_Pixel{} if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { // Background is RGB 16-bit, take just the red channel's topmost byte. - bg = u8(temp_bg.r >> 8) + bg.r = u8(temp_bg.r >> 8) } for p in inp { - out[0] = bg if p == key else p + out[0] = bg if p.r == key else p out = out[1:] } @@ -865,8 +865,8 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 255.0 c := ((1.0 - a) * bg + a * f32(p.r)) - out[0] = u8(c) - out = out[1:] + out[0].r = u8(c) + out = out[1:] } } else if .alpha_premultiply in options { @@ -874,14 +874,14 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 255.0 c := f32(p.r) * a - out[0] = u8(c) - out = out[1:] + out[0].r = u8(c) + out = out[1:] } } else { // Just drop alpha on the floor. for p in inp { - out[0] = p.r - out = out[1:] + out[0].r = p.r + out = out[1:] } } @@ -951,11 +951,11 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al bg := G_Pixel_16{} if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { // Background is RGB 16-bit, take just the red channel. - bg = temp_bg.r + bg.r = temp_bg.r } for p in inp { - out[0] = bg if p == key else p + out[0] = bg if p.r == key else p out = out[1:] } @@ -974,8 +974,8 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 65535.0 c := ((1.0 - a) * bg + a * f32(p.r)) - out[0] = u16(c) - out = out[1:] + out[0].r = u16(c) + out = out[1:] } } else if .alpha_premultiply in options { @@ -983,14 +983,14 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 65535.0 c := f32(p.r) * a - out[0] = u16(c) - out = out[1:] + out[0].r = u16(c) + out = out[1:] } } else { // Just drop alpha on the floor. for p in inp { - out[0] = p.r - out = out[1:] + out[0].r = p.r + out = out[1:] } } diff --git a/core/io/multi.odin b/core/io/multi.odin index e85114a7a..e25e8133e 100644 --- a/core/io/multi.odin +++ b/core/io/multi.odin @@ -4,7 +4,6 @@ Multi_Reader :: struct { readers: [dynamic]Reader, } -@(private) _multi_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { if mode == .Query { return query_utility({.Read, .Query}) @@ -58,7 +57,6 @@ Multi_Writer :: struct { writers: [dynamic]Writer, } -@(private) _multi_writer_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { if mode == .Query { return query_utility({.Write, .Query}) diff --git a/core/math/big/prime.odin b/core/math/big/prime.odin index cb0b08dbb..b02b7cb4e 100644 --- a/core/math/big/prime.odin +++ b/core/math/big/prime.odin @@ -1112,7 +1112,7 @@ internal_int_prime_next_prime :: proc(a: ^Int, trials: int, bbs_style: bool, all Generate the restable. */ for x := 1; x < _PRIME_TAB_SIZE; x += 1 { - res_tab = internal_mod(a, _private_prime_table[x]) or_return + res_tab = cast(type_of(res_tab))(internal_mod(a, _private_prime_table[x]) or_return) } for { diff --git a/core/math/linalg/extended.odin b/core/math/linalg/extended.odin index eee339245..22c37dd0e 100644 --- a/core/math/linalg/extended.odin +++ b/core/math/linalg/extended.odin @@ -326,6 +326,7 @@ ln :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) { @(require_results) log2 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) { + INVLN2 :: 1.4426950408889634073599246810018921374266459541529859341354494069 when IS_ARRAY(T) { for i in 0.. (out: T) where IS_FLOAT(ELEM_TYPE(T)) { @(require_results) log10 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) { + INVLN10 :: 0.4342944819032518276511289189166050822943970058036665661144537831 when IS_ARRAY(T) { for i in 0.. (out: T) where IS_FLOAT(ELEM_TYPE(T)) { out[i] = math.ln(x[i]) / math.ln(cast(ELEM_TYPE(T))b[i]) } } else { - out = INVLN10 * math.ln(x) / math.ln(cast(ELEM_TYPE(T))b) + out = math.ln(x) / math.ln(cast(ELEM_TYPE(T))b) } return } diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index f6bcbab4e..3e215e0f2 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -597,6 +597,7 @@ Field_Flag :: enum { Any_Int, Subtype, By_Ptr, + No_Broadcast, Results, Tags, @@ -616,6 +617,7 @@ field_flag_strings := [Field_Flag]string{ .Any_Int = "#any_int", .Subtype = "#subtype", .By_Ptr = "#by_ptr", + .No_Broadcast ="#no_broadcast", .Results = "results", .Tags = "field tag", @@ -624,12 +626,13 @@ field_flag_strings := [Field_Flag]string{ } field_hash_flag_strings := []struct{key: string, flag: Field_Flag}{ - {"no_alias", .No_Alias}, - {"c_vararg", .C_Vararg}, - {"const", .Const}, - {"any_int", .Any_Int}, - {"subtype", .Subtype}, - {"by_ptr", .By_Ptr}, + {"no_alias", .No_Alias}, + {"c_vararg", .C_Vararg}, + {"const", .Const}, + {"any_int", .Any_Int}, + {"subtype", .Subtype}, + {"by_ptr", .By_Ptr}, + {"no_broadcast", .No_Broadcast}, } @@ -650,6 +653,7 @@ Field_Flags_Signature :: Field_Flags{ .Const, .Any_Int, .By_Ptr, + .No_Broadcast, .Default_Parameters, } diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin index 5636b1059..f8e23d53a 100644 --- a/core/odin/doc-format/doc_format.odin +++ b/core/odin/doc-format/doc_format.odin @@ -11,7 +11,7 @@ String :: distinct Array(byte) Version_Type_Major :: 0 Version_Type_Minor :: 3 -Version_Type_Patch :: 0 +Version_Type_Patch :: 1 Version_Type :: struct { major, minor, patch: u8, @@ -102,13 +102,15 @@ Entity_Flag :: enum u32le { Foreign = 0, Export = 1, - Param_Using = 2, // using - Param_Const = 3, // #const - Param_Auto_Cast = 4, // auto_cast - Param_Ellipsis = 5, // Variadic parameter - Param_CVararg = 6, // #c_vararg - Param_No_Alias = 7, // #no_alias - Param_Any_Int = 8, // #any_int + Param_Using = 2, // using + Param_Const = 3, // #const + Param_Auto_Cast = 4, // auto_cast + Param_Ellipsis = 5, // Variadic parameter + Param_CVararg = 6, // #c_vararg + Param_No_Alias = 7, // #no_alias + Param_Any_Int = 8, // #any_int + Param_By_Ptr = 9, // #by_ptr + Param_No_Broadcast = 10, // #no_broadcast Bit_Field_Field = 19, diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 1c473bd1d..55f73d106 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -163,7 +163,7 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co t := create(thread_proc, priority) t.data = rawptr(fn) t.user_index = 1 - t.user_args = data + t.user_args[0] = data if self_cleanup { t.flags += {.Self_Cleanup} } diff --git a/core/time/datetime/constants.odin b/core/time/datetime/constants.odin new file mode 100644 index 000000000..a2a02838c --- /dev/null +++ b/core/time/datetime/constants.odin @@ -0,0 +1,77 @@ +package datetime + +// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) +// | Midnight Monday, January 3, 1 A.D. (Julian) +Ordinal :: i64 +EPOCH :: Ordinal(1) + +// Minimum and maximum dates and ordinals. Chosen for safe roundtripping. +MIN_DATE :: Date{year = -25_252_734_927_766_552, month = 1, day = 1} +MAX_DATE :: Date{year = 25_252_734_927_766_552, month = 12, day = 31} +MIN_ORD :: Ordinal(-9_223_372_036_854_775_234) +MAX_ORD :: Ordinal( 9_223_372_036_854_774_869) + +Error :: enum { + None, + Invalid_Year, + Invalid_Month, + Invalid_Day, + Invalid_Hour, + Invalid_Minute, + Invalid_Second, + Invalid_Nano, + Invalid_Ordinal, + Invalid_Delta, +} + +Date :: struct { + year: i64, + month: i8, + day: i8, +} + +Time :: struct { + hour: i8, + minute: i8, + second: i8, + nano: i32, +} + +DateTime :: struct { + using date: Date, + using time: Time, +} + +Delta :: struct { + days: i64, // These are all i64 because we can also use it to add a number of seconds or nanos to a moment, + seconds: i64, // that are then normalized within their respective ranges. + nanos: i64, +} + +Month :: enum i8 { + January = 1, + February, + March, + April, + May, + June, + July, + August, + September, + October, + November, + December, +} + +Weekday :: enum i8 { + Sunday = 0, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, +} + +@(private) +MONTH_DAYS :: [?]i8{-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} \ No newline at end of file diff --git a/core/time/datetime/datetime.odin b/core/time/datetime/datetime.odin new file mode 100644 index 000000000..e15ced5a5 --- /dev/null +++ b/core/time/datetime/datetime.odin @@ -0,0 +1,272 @@ +/* + Calendrical conversions using a proleptic Gregorian calendar. + + Implemented using formulas from: Calendrical Calculations Ultimate Edition, Reingold & Dershowitz +*/ +package datetime + +import "base:intrinsics" + +// Procedures that return an Ordinal + +date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) { + validate(date) or_return + return unsafe_date_to_ordinal(date), .None +} + +components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) { + validate(year, month, day) or_return + return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None +} + +// Procedures that return a Date + +ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) { + validate(ordinal) or_return + return unsafe_ordinal_to_date(ordinal), .None +} + +components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) { + validate(year, month, day) or_return + return Date{i64(year), i8(month), i8(day)}, .None +} + +components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) { + validate(hour, minute, second, nanos) or_return + return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None +} + +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 +} + +ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) { + d := ordinal_to_date(ordinal) or_return + return {Date(d), {}}, .None +} + +day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) { + return Weekday((ordinal - EPOCH) %% 7) +} + +subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) { + ord_a := date_to_ordinal(a) or_return + ord_b := date_to_ordinal(b) or_return + + delta = Delta{days=ord_a - ord_b} + return +} + +subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) { + ord_a := date_to_ordinal(a) or_return + ord_b := date_to_ordinal(b) or_return + + validate(a.time) or_return + validate(b.time) or_return + + seconds_a := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second) + seconds_b := i64(b.hour) * 3600 + i64(b.minute) * 60 + i64(b.second) + + delta = Delta{ord_a - ord_b, seconds_a - seconds_b, i64(a.nano) - i64(b.nano)} + return +} + +subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) { + delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos} + delta = normalize_delta(delta) or_return + return +} +sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas} + +add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) { + ord := date_to_ordinal(a) or_return + ord += days + return ordinal_to_date(ord) +} + +add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) { + ord := date_to_ordinal(a) or_return + // Because the input is a Date, we add only the days from the Delta. + ord += delta.days + return ordinal_to_date(ord) +} + +add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) { + days := date_to_ordinal(a) or_return + + a_seconds := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second) + a_delta := Delta{days=days, seconds=a_seconds, nanos=i64(a.nano)} + + sum_delta := Delta{days=a_delta.days + delta.days, seconds=a_delta.seconds + delta.seconds, nanos=a_delta.nanos + delta.nanos} + sum_delta = normalize_delta(sum_delta) or_return + + datetime.date = ordinal_to_date(sum_delta.days) or_return + + hour, rem := divmod(sum_delta.seconds, 3600) + minute, second := divmod(rem, 60) + + datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return + return +} +add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime} + +day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) { + validate(date) or_return + + ord := unsafe_date_to_ordinal(date) + _, day_number = unsafe_ordinal_to_year(ord) + return +} + +days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) { + // Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year + validate(date) or_return + delta := sub(date, Date{date.year, 12, 31}) or_return + return delta.days, .None +} + +last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i64, err: Error) { + // Not using formula 2.27 from the book. This is far simpler and gives the same answer. + + validate(Date{year, month, 1}) or_return + month_days := MONTH_DAYS + + day = i64(month_days[month]) + if month == 2 && is_leap_year(year) { + day += 1 + } + return +} + +new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) { + validate(year, 1, 1) or_return + return {year, 1, 1}, .None +} + +year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) { + validate(year, 12, 31) or_return + return {year, 12, 31}, .None +} + +year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) { + is_leap := is_leap_year(year) + + days := 366 if is_leap else 365 + range = make([]Date, days, allocator) + + month_days := MONTH_DAYS + if is_leap { + month_days[2] = 29 + } + + i := 0 + for month in 1..=len(month_days) { + for day in 1..=month_days[month] { + range[i], _ = components_to_date(year, month, day) + i += 1 + } + } + return +} + +normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) { + // Distribute nanos into seconds and remainder + seconds, nanos := divmod(delta.nanos, 1e9) + + // Add original seconds to rolled over seconds. + seconds += delta.seconds + days: i64 + + // Distribute seconds into number of days and remaining seconds. + days, seconds = divmod(seconds, 24 * 3600) + + // Add original days + days += delta.days + + if days <= MIN_ORD || days >= MAX_ORD { + return {}, .Invalid_Delta + } + return Delta{days, seconds, nanos}, .None +} + +// The following procedures don't check whether their inputs are in a valid range. +// They're still exported for those who know their inputs have been validated. + +unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) { + year_minus_one := date.year - 1 + + // Day before epoch + ordinal = EPOCH - 1 + + // Add non-leap days + ordinal += 365 * year_minus_one + + // Add leap days + ordinal += floor_div(year_minus_one, 4) // Julian-rule leap days + ordinal -= floor_div(year_minus_one, 100) // Prior century years + ordinal += floor_div(year_minus_one, 400) // Prior 400-multiple years + ordinal += floor_div(367 * i64(date.month) - 362, 12) // Prior days this year + + // Apply correction + if date.month <= 2 { + ordinal += 0 + } else if is_leap_year(date.year) { + ordinal -= 1 + } else { + ordinal -= 2 + } + + // Add days + ordinal += i64(date.day) + return +} + +unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) { + // Days after epoch + d0 := ordinal - EPOCH + + // Number of 400-year cycles and remainder + n400, d1 := divmod(d0, 146097) + + // Number of 100-year cycles and remainder + n100, d2 := divmod(d1, 36524) + + // Number of 4-year cycles and remainder + n4, d3 := divmod(d2, 1461) + + // Number of remaining days + n1, d4 := divmod(d3, 365) + + year = 400 * n400 + 100 * n100 + 4 * n4 + n1 + + if n1 != 4 && n100 != 4 { + day_ordinal = d4 + 1 + } else { + day_ordinal = 366 + } + + if n100 == 4 || n1 == 4 { + return year, day_ordinal + } + return year + 1, day_ordinal +} + +unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) { + year, _ := unsafe_ordinal_to_year(ordinal) + + prior_days := ordinal - unsafe_date_to_ordinal(Date{year, 1, 1}) + correction := Ordinal(2) + + if ordinal < unsafe_date_to_ordinal(Date{year, 3, 1}) { + correction = 0 + } else if is_leap_year(year) { + correction = 1 + } + + month := i8(floor_div((12 * (prior_days + correction) + 373), 367)) + 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/datetime/internal.odin b/core/time/datetime/internal.odin new file mode 100644 index 000000000..45c2b99ab --- /dev/null +++ b/core/time/datetime/internal.odin @@ -0,0 +1,95 @@ +package datetime + +// Internal helper functions for calendrical conversions + +import "base:intrinsics" + +sign :: proc "contextless" (v: i64) -> (res: i64) { + if v == 0 { + return 0 + } else if v > 0 { + return 1 + } + return -1 +} + +// Caller has to ensure y != 0 +divmod :: proc "contextless" (x, y: $T, loc := #caller_location) -> (a: T, r: T) + where intrinsics.type_is_integer(T) { + a = x / y + r = x % y + if (r > 0 && y < 0) || (r < 0 && y > 0) { + a -= 1 + r += y + } + return a, r +} + +// Divides and floors +floor_div :: proc "contextless" (x, y: $T) -> (res: T) + where intrinsics.type_is_integer(T) { + res = x / y + r := x % y + if (r > 0 && y < 0) || (r < 0 && y > 0) { + res -= 1 + } + return res +} + +// Half open: x mod [1..b] +interval_mod :: proc "contextless" (x, a, b: i64) -> (res: i64) { + if a == b { + return x + } + return a + ((x - a) %% (b - a)) +} + +// x mod [1..b] +adjusted_remainder :: proc "contextless" (x, b: i64) -> (res: i64) { + m := x %% b + return b if m == 0 else m +} + +gcd :: proc "contextless" (x, y: i64) -> (res: i64) { + if y == 0 { + return x + } + + m := x %% y + return gcd(y, m) +} + +lcm :: proc "contextless" (x, y: i64) -> (res: i64) { + return x * y / gcd(x, y) +} + +sum :: proc "contextless" (i: i64, f: proc "contextless" (n: i64) -> i64, cond: proc "contextless" (n: i64) -> bool) -> (res: i64) { + for idx := i; cond(idx); idx += 1 { + res += f(idx) + } + return +} + +product :: proc "contextless" (i: i64, f: proc "contextless" (n: i64) -> i64, cond: proc "contextless" (n: i64) -> bool) -> (res: i64) { + res = 1 + for idx := i; cond(idx); idx += 1 { + res *= f(idx) + } + return +} + +smallest :: proc "contextless" (k: i64, cond: proc "contextless" (n: i64) -> bool) -> (d: i64) { + k := k + for !cond(k) { + k += 1 + } + return k +} + +biggest :: proc "contextless" (k: i64, cond: proc "contextless" (n: i64) -> bool) -> (d: i64) { + k := k + for !cond(k) { + k -= 1 + } + return k +} \ No newline at end of file diff --git a/core/time/datetime/validation.odin b/core/time/datetime/validation.odin new file mode 100644 index 000000000..110a7e78e --- /dev/null +++ b/core/time/datetime/validation.odin @@ -0,0 +1,72 @@ +package datetime + +// Validation helpers +is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) +} + +validate_date :: proc "contextless" (date: Date) -> (err: Error) { + return validate(date.year, date.month, date.day) +} + +validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) { + if year < MIN_DATE.year || year > MAX_DATE.year { + return .Invalid_Year + } + if month < 1 || month > 12 { + return .Invalid_Month + } + + month_days := MONTH_DAYS + days_this_month := month_days[month] + if month == 2 && is_leap_year(year) { + days_this_month = 29 + } + + if day < 1 || day > i64(days_this_month) { + return .Invalid_Day + } + return .None +} + +validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) { + if ordinal < MIN_ORD || ordinal > MAX_ORD { + return .Invalid_Ordinal + } + return +} + +validate_time :: proc "contextless" (time: Time) -> (err: Error) { + return validate(time.hour, time.minute, time.second, time.nano) +} + +validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) { + if hour < 0 || hour > 23 { + return .Invalid_Hour + } + if minute < 0 || minute > 59 { + return .Invalid_Minute + } + if second < 0 || second > 59 { + return .Invalid_Second + } + if nano < 0 || nano > 1e9 { + return .Invalid_Nano + } + return .None +} + +validate_datetime :: proc "contextless" (using datetime: DateTime) -> (err: Error) { + validate(date) or_return + validate(time) or_return + return .None +} + +validate :: proc{ + validate_date, + validate_year_month_day, + validate_ordinal, + validate_hour_minute_second, + validate_time, + validate_datetime, +} \ No newline at end of file diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin new file mode 100644 index 000000000..30c255c79 --- /dev/null +++ b/core/time/rfc3339.odin @@ -0,0 +1,122 @@ +package time +// Parsing RFC 3339 date/time strings into time.Time. +// See https://www.rfc-editor.org/rfc/rfc3339 for the definition + +import dt "core:time/datetime" + +// Parses an RFC 3339 string and returns Time in UTC, with any UTC offset applied to it. +// Only 4-digit years are accepted. +// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. +// Leap seconds are smeared into 23:59:59. +rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { + offset: int + + res, offset, consumed = rfc3339_to_time_and_offset(rfc_datetime, is_leap) + res._nsec += (i64(-offset) * i64(Minute)) + return res, consumed +} + +// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. +// e.g. 1985-04-12T23:20:50.52Z +// Note: Only 4-digit years are accepted. +// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. +// Leap seconds are smeared into 23:59:59. +rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { + moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime) + if count == 0 { + return + } + + if is_leap != nil { + is_leap^ = leap_second + } + + if _res, ok := datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, moment.nano); !ok { + return {}, 0, 0 + } else { + return _res, offset, count + } +} + +// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. +// e.g. 1985-04-12T23:20:50.52Z +// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { + moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime) + if !ok { + return + } + return moment, offset, leap_second, count +} + +// Parses an RFC 3339 string and returns datetime.DateTime. +// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +@(private) +_rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, consumed: int, is_leap: bool, ok: bool) { + // A compliant date is at minimum 20 characters long, e.g. YYYY-MM-DDThh:mm:ssZ + (len(rfc_datetime) >= 20) or_return + + // Scan and eat YYYY-MM-DD[Tt], then scan and eat HH:MM:SS, leave separator + year := scan_digits(rfc_datetime[0:], "-", 4) or_return + month := scan_digits(rfc_datetime[5:], "-", 2) or_return + day := scan_digits(rfc_datetime[8:], "Tt", 2) or_return + hour := scan_digits(rfc_datetime[11:], ":", 2) or_return + minute := scan_digits(rfc_datetime[14:], ":", 2) or_return + second := scan_digits(rfc_datetime[17:], "", 2) or_return + nanos := 0 + count := 19 + + if rfc_datetime[count] == '.' { + // Scan hundredths. The string must be at least 4 bytes long (.hhZ) + (len(rfc_datetime[count:]) >= 4) or_return + hundredths := scan_digits(rfc_datetime[count+1:], "", 2) or_return + count += 3 + nanos = 10_000_000 * hundredths + } + + // Leap second handling + if minute == 59 && second == 60 { + second = 59 + is_leap = true + } + + err: dt.Error + if res, err = dt.components_to_datetime(year, month, day, hour, minute, second, nanos); err != .None { + return {}, 0, 0, false, false + } + + // Scan UTC offset + switch rfc_datetime[count] { + case 'Z': + utc_offset = 0 + count += 1 + case '+', '-': + (len(rfc_datetime[count:]) >= 6) or_return + offset_hour := scan_digits(rfc_datetime[count+1:], ":", 2) or_return + offset_minute := scan_digits(rfc_datetime[count+4:], "", 2) or_return + + utc_offset = 60 * offset_hour + offset_minute + utc_offset *= -1 if rfc_datetime[count] == '-' else 1 + count += 6 + } + return res, utc_offset, count, is_leap, true +} + +@(private) +scan_digits :: proc(s: string, sep: string, count: int) -> (res: int, ok: bool) { + needed := count + min(1, len(sep)) + (len(s) >= needed) or_return + + #no_bounds_check for i in 0..= '0' && v <= '9' { + res = res * 10 + int(v - '0') + } else { + return 0, false + } + } + found_sep := len(sep) == 0 + #no_bounds_check for v in sep { + found_sep |= rune(s[count]) == v + } + return res, found_sep +} \ No newline at end of file diff --git a/core/time/time.odin b/core/time/time.odin index 72a09ad94..10b71ee0d 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -1,6 +1,7 @@ package time -import "base:intrinsics" +import "base:intrinsics" +import dt "core:time/datetime" Duration :: distinct i64 @@ -299,10 +300,6 @@ _time_abs :: proc "contextless" (t: Time) -> u64 { @(private) _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Month, day: int, yday: int) { - _is_leap_year :: proc "contextless" (year: int) -> bool { - return year%4 == 0 && (year%100 != 0 || year%400 == 0) - } - d := abs / SECONDS_PER_DAY // 400 year cycles @@ -335,7 +332,7 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon day = yday - if _is_leap_year(year) { + if is_leap_year(year) { switch { case day > 31+29-1: day -= 1 @@ -360,57 +357,35 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon return } -datetime_to_time :: proc "contextless" (year, month, day, hour, minute, second: int, nsec := int(0)) -> (t: Time, ok: bool) { - divmod :: proc "contextless" (year: int, divisor: int) -> (div: int, mod: int) { - if divisor <= 0 { - intrinsics.debug_trap() - } - div = int(year / divisor) - mod = year % divisor +components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) { + this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec) + if err != .None { return } - _is_leap_year :: proc "contextless" (year: int) -> bool { - return year%4 == 0 && (year%100 != 0 || year%400 == 0) + return compound_to_time(this_date) +} + +compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) { + unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}} + delta, err := dt.sub(datetime, unix_epoch) + ok = err == .None + + seconds := delta.days * 86_400 + delta.seconds + nanoseconds := i128(seconds) * 1e9 + i128(delta.nanos) + + // Can this moment be represented in i64 worth of nanoseconds? + // min(Time): 1677-09-21 00:12:44.145224192 +0000 UTC + // max(Time): 2262-04-11 23:47:16.854775807 +0000 UTC + if nanoseconds < i128(min(i64)) || nanoseconds > i128(max(i64)) { + return {}, false } + return Time{_nsec=i64(nanoseconds)}, true +} +datetime_to_time :: proc{components_to_time, compound_to_time} - ok = true - - _y := year - 1970 - _m := month - 1 - _d := day - 1 - - if month < 1 || month > 12 { - _m %= 12; ok = false - } - if day < 1 || day > 31 { - _d %= 31; ok = false - } - - s := i64(0) - div, mod := divmod(_y, 400) - days := div * DAYS_PER_400_YEARS - - div, mod = divmod(mod, 100) - days += div * DAYS_PER_100_YEARS - - div, mod = divmod(mod, 4) - days += (div * DAYS_PER_4_YEARS) + (mod * 365) - - days += int(days_before[_m]) + _d - - if _is_leap_year(year) && _m >= 2 { - days += 1 - } - - s += i64(days) * SECONDS_PER_DAY - s += i64(hour) * SECONDS_PER_HOUR - s += i64(minute) * SECONDS_PER_MINUTE - s += i64(second) - - t._nsec = (s * 1e9) + i64(nsec) - - return +is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } days_before := [?]i32{ diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index c89b93e3b..bc1aff607 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -117,6 +117,7 @@ import table "core:text/table" import edit "core:text/edit" import thread "core:thread" import time "core:time" +import datetime "core:time/datetime" import sysinfo "core:sys/info" @@ -225,6 +226,7 @@ _ :: table _ :: edit _ :: thread _ :: time +_ :: datetime _ :: sysinfo _ :: unicode _ :: utf8 diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 4be189cf1..19006ab2c 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -510,7 +510,7 @@ gb_global TargetMetrics target_darwin_amd64 = { TargetOs_darwin, TargetArch_amd64, 8, 8, 8, 16, - str_lit("x86_64-apple-darwin"), + str_lit("x86_64-apple-macosx"), // NOTE: Changes during initialization based on build flags. str_lit("e-m:o-i64:64-f80:128-n8:16:32:64-S128"), }; @@ -518,7 +518,7 @@ gb_global TargetMetrics target_darwin_arm64 = { TargetOs_darwin, TargetArch_arm64, 8, 8, 8, 16, - str_lit("arm64-apple-macosx11.0.0"), + str_lit("arm64-apple-macosx"), // NOTE: Changes during initialization based on build flags. str_lit("e-m:o-i64:64-i128:128-n32:64-S128"), }; @@ -1418,19 +1418,25 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta } bc->metrics = *metrics; - switch (subtarget) { - case Subtarget_Default: - break; - case Subtarget_iOS: - GB_ASSERT(metrics->os == TargetOs_darwin); - if (metrics->arch == TargetArch_arm64) { - bc->metrics.target_triplet = str_lit("arm64-apple-ios"); - } else if (metrics->arch == TargetArch_amd64) { - bc->metrics.target_triplet = str_lit("x86_64-apple-ios"); - } else { - GB_PANIC("Unknown architecture for darwin"); + if (metrics->os == TargetOs_darwin) { + if (bc->minimum_os_version_string.len == 0) { + bc->minimum_os_version_string = str_lit("11.0.0"); + } + + switch (subtarget) { + case Subtarget_Default: + bc->metrics.target_triplet = concatenate_strings(permanent_allocator(), bc->metrics.target_triplet, bc->minimum_os_version_string); + break; + case Subtarget_iOS: + if (metrics->arch == TargetArch_arm64) { + bc->metrics.target_triplet = str_lit("arm64-apple-ios"); + } else if (metrics->arch == TargetArch_amd64) { + bc->metrics.target_triplet = str_lit("x86_64-apple-ios"); + } else { + GB_PANIC("Unknown architecture for darwin"); + } + break; } - break; } bc->ODIN_OS = target_os_names[metrics->os]; diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index d3158961e..f4aa9567d 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -89,6 +89,7 @@ gb_internal void check_or_else_split_types(CheckerContext *c, Operand *x, String gb_internal void check_or_else_expr_no_value_error(CheckerContext *c, String const &name, Operand const &x, Type *type_hint) { + ERROR_BLOCK(); gbString t = type_to_string(x.type); error(x.expr, "'%.*s' does not return a value, value is of type %s", LIT(name), t); if (is_type_union(type_deref(x.type))) { @@ -1565,6 +1566,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o } if (!operand->value.value_bool) { + ERROR_BLOCK(); gbString arg1 = expr_to_string(ce->args[0]); gbString arg2 = {}; @@ -1590,6 +1592,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_untyped_bool; operand->mode = Addressing_Constant; } else if (name == "panic") { + ERROR_BLOCK(); if (ce->args.count != 1) { error(call, "'#panic' expects 1 argument, got %td", ce->args.count); return false; @@ -3390,6 +3393,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As elem->Struct.tags = gb_alloc_array(permanent_allocator(), String, fields.count); elem->Struct.node = dummy_node_struct; type_set_offsets(elem); + wait_signal_set(&elem->Struct.fields_wait_signal); } Type *soa_type = make_soa_struct_slice(c, dummy_node_soa, nullptr, elem); @@ -3763,6 +3767,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As soa_struct->Struct.tags[i] = old_struct->Struct.tags[i]; } } + wait_signal_set(&soa_struct->Struct.fields_wait_signal); Token token = {}; token.string = str_lit("Base_Type"); diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 2c0f7a7b8..952a877a4 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1630,6 +1630,7 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de Entity *uvar = entry.uvar; Entity *prev = scope_insert_no_mutex(ctx->scope, uvar); if (prev != nullptr) { + ERROR_BLOCK(); error(e->token, "Namespace collision while 'using' procedure argument '%.*s' of: %.*s", LIT(e->token.string), LIT(prev->token.string)); error_line("%.*s != %.*s\n", LIT(uvar->token.string), LIT(prev->token.string)); break; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8fb2cf36b..a34b425c2 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -79,7 +79,6 @@ gb_internal Type * check_type_expr (CheckerContext *c, Ast *exp gb_internal Type * make_optional_ok_type (Type *value, bool typed=true); gb_internal Entity * check_selector (CheckerContext *c, Operand *operand, Ast *node, Type *type_hint); gb_internal Entity * check_ident (CheckerContext *c, Operand *o, Ast *n, Type *named_type, Type *type_hint, bool allow_import_name); -gb_internal Entity * find_polymorphic_record_entity (CheckerContext *c, Type *original_type, isize param_count, Array const &ordered_operands, bool *failure); gb_internal void check_not_tuple (CheckerContext *c, Operand *operand); gb_internal void convert_to_typed (CheckerContext *c, Operand *operand, Type *target_type); gb_internal gbString expr_to_string (Ast *expression); @@ -121,6 +120,8 @@ gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize * gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr); +gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array const &ordered_operands); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -623,7 +624,7 @@ gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type); #define MAXIMUM_TYPE_DISTANCE 10 -gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type) { +gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type, bool allow_array_programming) { if (c == nullptr) { GB_ASSERT(operand->mode == Addressing_Value); GB_ASSERT(is_type_typed(operand->type)); @@ -832,7 +833,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand if (dst->Union.variants.count == 1) { Type *vt = dst->Union.variants[0]; - i64 score = check_distance_between_types(c, operand, vt); + i64 score = check_distance_between_types(c, operand, vt, allow_array_programming); if (score >= 0) { return score+2; } @@ -840,7 +841,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand i64 prev_lowest_score = -1; i64 lowest_score = -1; for (Type *vt : dst->Union.variants) { - i64 score = check_distance_between_types(c, operand, vt); + i64 score = check_distance_between_types(c, operand, vt, allow_array_programming); if (score >= 0) { if (lowest_score < 0) { lowest_score = score; @@ -863,14 +864,14 @@ 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); + 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); + i64 score = check_distance_between_types(c, operand, dst->RelativeMultiPointer.pointer_type, allow_array_programming); if (score >= 0) { return score+2; } @@ -896,19 +897,21 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } } - if (is_type_array(dst)) { - Type *elem = base_array_type(dst); - i64 distance = check_distance_between_types(c, operand, elem); - if (distance >= 0) { - return distance + 6; + if (allow_array_programming) { + if (is_type_array(dst)) { + Type *elem = base_array_type(dst); + i64 distance = check_distance_between_types(c, operand, elem, allow_array_programming); + if (distance >= 0) { + return distance + 6; + } } - } - if (is_type_simd_vector(dst)) { - Type *dst_elem = base_array_type(dst); - i64 distance = check_distance_between_types(c, operand, dst_elem); - if (distance >= 0) { - return distance + 6; + if (is_type_simd_vector(dst)) { + Type *dst_elem = base_array_type(dst); + i64 distance = check_distance_between_types(c, operand, dst_elem, allow_array_programming); + if (distance >= 0) { + return distance + 6; + } } } @@ -918,7 +921,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } if (dst->Matrix.row_count == dst->Matrix.column_count) { Type *dst_elem = base_array_type(dst); - i64 distance = check_distance_between_types(c, operand, dst_elem); + i64 distance = check_distance_between_types(c, operand, dst_elem, allow_array_programming); if (distance >= 0) { return distance + 7; } @@ -966,9 +969,9 @@ gb_internal i64 assign_score_function(i64 distance, bool is_variadic=false) { } -gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false) { +gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true) { i64 score = 0; - i64 distance = check_distance_between_types(c, operand, type); + i64 distance = check_distance_between_types(c, operand, type, allow_array_programming); bool ok = distance >= 0; if (ok) { score = assign_score_function(distance, is_variadic); @@ -978,9 +981,9 @@ gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *o } -gb_internal bool check_is_assignable_to(CheckerContext *c, Operand *operand, Type *type) { +gb_internal bool check_is_assignable_to(CheckerContext *c, Operand *operand, Type *type, bool allow_array_programming=true) { i64 score = 0; - return check_is_assignable_to_with_score(c, operand, type, &score); + return check_is_assignable_to_with_score(c, operand, type, &score, /*is_variadic*/false, allow_array_programming); } gb_internal bool internal_check_is_assignable_to(Type *src, Type *dst) { @@ -1172,6 +1175,16 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ type_str, type_extra, LIT(context_name)); check_assignment_error_suggestion(c, operand, type); + + if (context_name == "procedure argument") { + Type *src = base_type(operand->type); + Type *dst = base_type(type); + if (is_type_slice(src) && are_types_identical(src->Slice.elem, dst)) { + gbString a = expr_to_string(operand->expr); + error_line("\tSuggestion: Did you mean to pass the slice into the variadic parameter with ..%s?\n\n", a); + gb_string_free(a); + } + } } break; } @@ -1597,7 +1610,7 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam for (CIdentSuggestion const &suggestion : c_ident_suggestions) { if (name == suggestion.name) { - error_line("\tSuggestion: Did you mean %s\n", LIT(suggestion.msg)); + error_line("\tSuggestion: Did you mean %.*s\n", LIT(suggestion.msg)); } } } @@ -1725,7 +1738,7 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam if (check_cycle(c, e, true)) { o->type = t_invalid; } - if (o->type != nullptr && type->kind == Type_Named && o->type->Named.type_name->TypeName.is_type_alias) { + if (o->type != nullptr && o->type->kind == Type_Named && o->type->Named.type_name->TypeName.is_type_alias) { o->type = base_type(o->type); } @@ -1798,6 +1811,21 @@ gb_internal bool check_unary_op(CheckerContext *c, Operand *o, Token op) { } break; + case Token_Mul: + { + ERROR_BLOCK(); + error(op, "Operator '%.*s' is not a valid unary operator in Odin", LIT(op.string)); + if (is_type_pointer(o->type)) { + str = expr_to_string(o->expr); + error_line("\tSuggestion: Did you mean '%s^'?\n", str); + o->type = type_deref(o->type); + } else if (is_type_multi_pointer(o->type)) { + str = expr_to_string(o->expr); + error_line("\tSuggestion: The value is a multi-pointer, did you mean '%s[0]'?\n", str); + o->type = type_deref(o->type, true); + } + } + break; default: error(op, "Unknown operator '%.*s'", LIT(op.string)); return false; @@ -3142,6 +3170,14 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type return true; } + + if (is_type_array(dst)) { + Type *elem = base_array_type(dst); + if (check_is_castable_to(c, operand, elem)) { + return true; + } + } + if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { if (src->SimdVector.count != dst->SimdVector.count) { return false; @@ -4875,10 +4911,18 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod Selection sel = {}; // NOTE(bill): Not used if it's an import name if (!c->allow_arrow_right_selector_expr && se->token.kind == Token_ArrowRight) { + ERROR_BLOCK(); error(node, "Illegal use of -> selector shorthand outside of a call"); - operand->mode = Addressing_Invalid; - operand->expr = node; - return nullptr; + gbString x = expr_to_string(se->expr); + gbString y = expr_to_string(se->selector); + error_line("\tSuggestion: Did you mean '%s.%s'?\n", x, y); + gb_string_free(y); + gb_string_free(x); + + // TODO(bill): Should this terminate here or propagate onwards? + // operand->mode = Addressing_Invalid; + // operand->expr = node; + // return nullptr; } operand->expr = node; @@ -5787,10 +5831,14 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A Operand *variadic_operand = &ordered_operands[pt->variadic_index]; if (vari_expand) { - GB_ASSERT(variadic_operands.count != 0); - *variadic_operand = variadic_operands[0]; - variadic_operand->type = default_type(variadic_operand->type); - actually_variadic = true; + if (variadic_operands.count == 0) { + error(call, "'..' in the wrong position"); + } else { + GB_ASSERT(variadic_operands.count != 0); + *variadic_operand = variadic_operands[0]; + variadic_operand->type = default_type(variadic_operand->type); + actually_variadic = true; + } } else { AstFile *f = call->file(); @@ -5854,31 +5902,28 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } auto eval_param_and_score = [](CheckerContext *c, Operand *o, Type *param_type, CallArgumentError &err, bool param_is_variadic, Entity *e, bool show_error) -> i64 { + bool allow_array_programming = !(e && (e->flags & EntityFlag_NoBroadcast)); i64 s = 0; - if (!check_is_assignable_to_with_score(c, o, param_type, &s, param_is_variadic)) { + if (!check_is_assignable_to_with_score(c, o, param_type, &s, param_is_variadic, allow_array_programming)) { bool ok = false; - if (e && e->flags & EntityFlag_AnyInt) { + if (e && (e->flags & EntityFlag_AnyInt)) { if (is_type_integer(param_type)) { ok = check_is_castable_to(c, o, param_type); } } + if (!allow_array_programming && check_is_assignable_to_with_score(c, o, param_type, nullptr, param_is_variadic, !allow_array_programming)) { + if (show_error) { + error(o->expr, "'#no_broadcast' disallows automatic broadcasting a value across all elements of an array-like type in a procedure argument"); + } + } if (ok) { s = assign_score_function(MAXIMUM_TYPE_DISTANCE); } else { if (show_error) { check_assignment(c, o, param_type, str_lit("procedure argument")); - - Type *src = base_type(o->type); - Type *dst = base_type(param_type); - if (is_type_slice(src) && are_types_identical(src->Slice.elem, dst)) { - gbString a = expr_to_string(o->expr); - error_line("\tSuggestion: Did you mean to pass the slice into the variadic parameter with ..%s?\n\n", a); - gb_string_free(a); - } } err = CallArgumentError_WrongTypes; } - } else if (show_error) { check_assignment(c, o, param_type, str_lit("procedure argument")); } @@ -5963,12 +6008,13 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A if (param_is_variadic) { continue; } - score += eval_param_and_score(c, o, e->type, err, param_is_variadic, e, show_error); + score += eval_param_and_score(c, o, e->type, err, false, e, show_error); } } if (variadic) { - Type *slice = pt->params->Tuple.variables[pt->variadic_index]->type; + Entity *var_entity = pt->params->Tuple.variables[pt->variadic_index]; + Type *slice = var_entity->type; GB_ASSERT(is_type_slice(slice)); Type *elem = base_type(slice)->Slice.elem; Type *t = elem; @@ -5994,7 +6040,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A return CallArgumentError_MultipleVariadicExpand; } } - score += eval_param_and_score(c, o, t, err, true, nullptr, show_error); + score += eval_param_and_score(c, o, t, err, true, var_entity, show_error); } } @@ -6122,7 +6168,10 @@ gb_internal bool evaluate_where_clauses(CheckerContext *ctx, Ast *call_expr, Sco } } - if (call_expr) error(call_expr, "at caller location"); + if (call_expr) { + TokenPos pos = ast_token(call_expr).pos; + error_line("%s at caller location\n", token_pos_to_string(pos)); + } } return false; } @@ -7123,8 +7172,12 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O } { - bool failure = false; - Entity *found_entity = find_polymorphic_record_entity(c, original_type, param_count, ordered_operands, &failure); + GenTypesData *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(c, original_type); + + mutex_lock(&found_gen_types->mutex); + defer (mutex_unlock(&found_gen_types->mutex)); + Entity *found_entity = find_polymorphic_record_entity(found_gen_types, param_count, ordered_operands); + if (found_entity) { operand->mode = Addressing_Type; operand->type = found_entity->type; @@ -8417,6 +8470,7 @@ gb_internal ExprKind check_or_return_expr(CheckerContext *c, Operand *o, Ast *no // NOTE(bill): allow implicit conversion between boolean types // within 'or_return' to improve the experience using third-party code } else if (!check_is_assignable_to(c, &rhs, end_type)) { + ERROR_BLOCK(); // TODO(bill): better error message gbString a = type_to_string(right_type); gbString b = type_to_string(end_type); @@ -8824,6 +8878,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * break; } + wait_signal_until_available(&t->Struct.fields_wait_signal); isize field_count = t->Struct.fields.count; isize min_field_count = t->Struct.fields.count; for (isize i = min_field_count-1; i >= 0; i--) { @@ -9988,6 +10043,7 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, bool ok = check_index_value(c, t, false, ie->index, max_count, &index, index_type_hint); if (is_const) { if (index < 0) { + ERROR_BLOCK(); gbString str = expr_to_string(o->expr); error(o->expr, "Cannot index a constant '%s'", str); if (!build_context.terse_errors) { @@ -10004,6 +10060,7 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, bool finish = false; o->value = get_constant_field_single(c, value, cast(i32)index, &success, &finish); if (!success) { + ERROR_BLOCK(); gbString str = expr_to_string(o->expr); error(o->expr, "Cannot index a constant '%s' with index %lld", str, cast(long long)index); if (!build_context.terse_errors) { @@ -10194,6 +10251,7 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, } } if (!all_constant) { + ERROR_BLOCK(); gbString str = expr_to_string(o->expr); error(o->expr, "Cannot slice '%s' with non-constant indices", str); if (!build_context.terse_errors) { @@ -11148,6 +11206,9 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan if (f->flags&FieldFlag_any_int) { str = gb_string_appendc(str, "#any_int "); } + if (f->flags&FieldFlag_no_broadcast) { + str = gb_string_appendc(str, "#no_broadcast "); + } if (f->flags&FieldFlag_const) { str = gb_string_appendc(str, "#const "); } @@ -11214,6 +11275,18 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan if (field->flags&FieldFlag_c_vararg) { str = gb_string_appendc(str, "#c_vararg "); } + if (field->flags&FieldFlag_any_int) { + str = gb_string_appendc(str, "#any_int "); + } + if (field->flags&FieldFlag_no_broadcast) { + str = gb_string_appendc(str, "#no_broadcast "); + } + if (field->flags&FieldFlag_const) { + str = gb_string_appendc(str, "#const "); + } + if (field->flags&FieldFlag_subtype) { + str = gb_string_appendc(str, "#subtype "); + } str = write_expr_to_string(str, field->type, shorthand); } diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 7cccab226..1d7e7d4e9 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -883,6 +883,7 @@ gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod } if (ctx->inline_for_depth >= MAX_INLINE_FOR_DEPTH && prev_inline_for_depth < MAX_INLINE_FOR_DEPTH) { + ERROR_BLOCK(); if (prev_inline_for_depth > 0) { error(node, "Nested '#unroll for' loop cannot be inlined as it exceeds the maximum '#unroll for' depth (%lld levels >= %lld maximum levels)", v, MAX_INLINE_FOR_DEPTH); } else { @@ -1365,6 +1366,8 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ } if (unhandled.count > 0) { + ERROR_BLOCK(); + if (unhandled.count == 1) { gbString s = type_to_string(unhandled[0]); error_no_newline(node, "Unhandled switch case: %s", s); @@ -1463,25 +1466,6 @@ gb_internal bool check_stmt_internal_builtin_proc_id(Ast *expr, BuiltinProcId *i return id != BuiltinProc_Invalid; } -gb_internal bool check_expr_is_stack_variable(Ast *expr) { - /* - expr = unparen_expr(expr); - Entity *e = entity_of_node(expr); - if (e && e->kind == Entity_Variable) { - if (e->flags & (EntityFlag_Static|EntityFlag_Using|EntityFlag_ImplicitReference|EntityFlag_ForValue)) { - // okay - } else if (e->Variable.thread_local_model.len != 0) { - // okay - } else if (e->scope) { - if ((e->scope->flags & (ScopeFlag_Global|ScopeFlag_File|ScopeFlag_Type)) == 0) { - return true; - } - } - } - */ - return false; -} - gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { ast_node(rs, RangeStmt, node); @@ -1544,10 +1528,13 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } } else if (operand.mode != Addressing_Invalid) { if (operand.mode == Addressing_OptionalOk || operand.mode == Addressing_OptionalOkPtr) { - Type *end_type = nullptr; - check_promote_optional_ok(ctx, &operand, nullptr, &end_type, false); - if (is_type_boolean(end_type)) { - check_promote_optional_ok(ctx, &operand, nullptr, &end_type, true); + Ast *expr = unparen_expr(operand.expr); + if (expr->kind != Ast_TypeAssertion) { // Only for procedure calls + Type *end_type = nullptr; + check_promote_optional_ok(ctx, &operand, nullptr, &end_type, false); + if (is_type_boolean(end_type)) { + check_promote_optional_ok(ctx, &operand, nullptr, &end_type, true); + } } } bool is_ptr = is_type_pointer(operand.type); @@ -1606,6 +1593,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { isize count = t->Tuple.variables.count; if (count < 1 || count > 3) { + ERROR_BLOCK(); check_not_tuple(ctx, &operand); error_line("\tMultiple return valued parameters in a range statement are limited to a maximum of 2 usable values with a trailing boolean for the conditional\n"); break; @@ -2099,6 +2087,9 @@ gb_internal void check_expr_stmt(CheckerContext *ctx, Ast *node) { } return; } + + ERROR_BLOCK(); + gbString expr_str = expr_to_string(operand.expr); error(node, "Expression is not used: '%s'", expr_str); gb_string_free(expr_str); @@ -2292,29 +2283,6 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { if (is_type_untyped(o->type)) { update_untyped_expr_type(ctx, o->expr, e->type, true); } - - - // NOTE(bill): This is very basic escape analysis - // This needs to be improved tremendously, and a lot of it done during the - // middle-end (or LLVM side) to improve checks and error messages - Ast *expr = unparen_expr(o->expr); - if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) { - Ast *x = unparen_expr(expr->UnaryExpr.expr); - if (x->kind == Ast_CompoundLit) { - error(expr, "Cannot return the address to a stack value from a procedure"); - } else if (x->kind == Ast_IndexExpr) { - Ast *array = x->IndexExpr.expr; - if (is_type_array_like(type_of_expr(array)) && check_expr_is_stack_variable(array)) { - gbString t = type_to_string(type_of_expr(array)); - error(expr, "Cannot return the address to an element of stack variable from a procedure, of type %s", t); - gb_string_free(t); - } - } else { - if (check_expr_is_stack_variable(x)) { - error(expr, "Cannot return the address to a stack variable from a procedure"); - } - } - } } } @@ -2322,16 +2290,51 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { if (o.expr == nullptr) { continue; } - if (o.expr->kind != Ast_CompoundLit || !is_type_slice(o.type)) { - continue; + Ast *expr = unparen_expr(o.expr); + + auto unsafe_return_error = [](Operand const &o, char const *msg, Type *extra_type=nullptr) { + gbString s = expr_to_string(o.expr); + if (extra_type) { + gbString t = type_to_string(extra_type); + error(o.expr, "It is unsafe to return %s ('%s') of type ('%s') from a procedure, as it uses the current stack frame's memory", msg, s, t); + gb_string_free(t); + } else { + error(o.expr, "It is unsafe to return %s ('%s') from a procedure, as it uses the current stack frame's memory", msg, s); + } + gb_string_free(s); + }; + + + // NOTE(bill): This is very basic escape analysis + // This needs to be improved tremendously, and a lot of it done during the + // middle-end (or LLVM side) to improve checks and error messages + if (expr->kind == Ast_CompoundLit && is_type_slice(o.type)) { + ast_node(cl, CompoundLit, expr); + if (cl->elems.count == 0) { + continue; + } + unsafe_return_error(o, "a compound literal of a slice"); + } else if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) { + Ast *x = unparen_expr(expr->UnaryExpr.expr); + Entity *e = entity_of_node(x); + if (is_entity_local_variable(e)) { + unsafe_return_error(o, "the address of a local variable"); + } else if(x->kind == Ast_CompoundLit) { + unsafe_return_error(o, "the address of a compound literal"); + } else if (x->kind == Ast_IndexExpr) { + Entity *f = entity_of_node(x->IndexExpr.expr); + if (is_type_array_like(f->type) || is_type_matrix(f->type)) { + if (is_entity_local_variable(f)) { + unsafe_return_error(o, "the address of an indexed variable", f->type); + } + } + } else if (x->kind == Ast_MatrixIndexExpr) { + Entity *f = entity_of_node(x->MatrixIndexExpr.expr); + if (is_type_matrix(f->type) && is_entity_local_variable(f)) { + unsafe_return_error(o, "the address of an indexed variable", f->type); + } + } } - ast_node(cl, CompoundLit, o.expr); - if (cl->elems.count == 0) { - continue; - } - gbString s = type_to_string(o.type); - error(o.expr, "It is unsafe to return a compound literal of a slice ('%s') with elements from a procedure, as the contents of the slice uses the current stack frame's memory", s); - gb_string_free(s); } } diff --git a/src/check_type.cpp b/src/check_type.cpp index 3fe633892..0f63d8bcb 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -260,84 +260,21 @@ gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_, } -gb_internal Entity *find_polymorphic_record_entity(CheckerContext *ctx, Type *original_type, isize param_count, Array const &ordered_operands, bool *failure) { - rw_mutex_shared_lock(&ctx->info->gen_types_mutex); // @@global +gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type) { + mutex_lock(&ctx->info->gen_types_mutex); // @@global - auto *found_gen_types = map_get(&ctx->info->gen_types, original_type); - if (found_gen_types == nullptr) { - rw_mutex_shared_unlock(&ctx->info->gen_types_mutex); // @@global - return nullptr; + GenTypesData *found_gen_types = nullptr; + auto *found_gen_types_ptr = map_get(&ctx->info->gen_types, original_type); + if (found_gen_types_ptr == nullptr) { + GenTypesData *gen_types = gb_alloc_item(permanent_allocator(), GenTypesData); + gen_types->types = array_make(heap_allocator()); + map_set(&ctx->info->gen_types, original_type, gen_types); + found_gen_types_ptr = map_get(&ctx->info->gen_types, original_type); } - - rw_mutex_shared_lock(&found_gen_types->mutex); // @@local - defer (rw_mutex_shared_unlock(&found_gen_types->mutex)); // @@local - - rw_mutex_shared_unlock(&ctx->info->gen_types_mutex); // @@global - - for (Entity *e : found_gen_types->types) { - Type *t = base_type(e->type); - TypeTuple *tuple = nullptr; - switch (t->kind) { - case Type_Struct: - if (t->Struct.polymorphic_params) { - tuple = &t->Struct.polymorphic_params->Tuple; - } - break; - case Type_Union: - if (t->Union.polymorphic_params) { - tuple = &t->Union.polymorphic_params->Tuple; - } - break; - } - GB_ASSERT_MSG(tuple != nullptr, "%s :: %s", type_to_string(e->type), type_to_string(t)); - GB_ASSERT(param_count == tuple->variables.count); - - bool skip = false; - - for (isize j = 0; j < param_count; j++) { - Entity *p = tuple->variables[j]; - Operand o = {}; - if (j < ordered_operands.count) { - o = ordered_operands[j]; - } - if (o.expr == nullptr) { - continue; - } - Entity *oe = entity_of_node(o.expr); - if (p == oe) { - // NOTE(bill): This is the same type, make sure that it will be be same thing and use that - // Saves on a lot of checking too below - continue; - } - - if (p->kind == Entity_TypeName) { - if (is_type_polymorphic(o.type)) { - // NOTE(bill): Do not add polymorphic version to the gen_types - skip = true; - break; - } - if (!are_types_identical(o.type, p->type)) { - skip = true; - break; - } - } else if (p->kind == Entity_Constant) { - if (!compare_exact_values(Token_CmpEq, o.value, p->Constant.value)) { - skip = true; - break; - } - if (!are_types_identical(o.type, p->type)) { - skip = true; - break; - } - } else { - GB_PANIC("Unknown entity kind"); - } - } - if (!skip) { - return e; - } - } - return nullptr; + found_gen_types = *found_gen_types_ptr; + GB_ASSERT(found_gen_types != nullptr); + mutex_unlock(&ctx->info->gen_types_mutex); // @@global + return found_gen_types; } @@ -367,19 +304,16 @@ gb_internal void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, T // TODO(bill): Is this even correct? Or should the metadata be copied? e->TypeName.objc_metadata = original_type->Named.type_name->TypeName.objc_metadata; - rw_mutex_lock(&ctx->info->gen_types_mutex); - auto *found_gen_types = map_get(&ctx->info->gen_types, original_type); - if (found_gen_types) { - rw_mutex_lock(&found_gen_types->mutex); - array_add(&found_gen_types->types, e); - rw_mutex_unlock(&found_gen_types->mutex); - } else { - GenTypesData gen_types = {}; - gen_types.types = array_make(heap_allocator()); - array_add(&gen_types.types, e); - map_set(&ctx->info->gen_types, original_type, gen_types); + auto *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(ctx, original_type); + mutex_lock(&found_gen_types->mutex); + defer (mutex_unlock(&found_gen_types->mutex)); + + for (Entity *prev : found_gen_types->types) { + if (prev == e) { + return; + } } - rw_mutex_unlock(&ctx->info->gen_types_mutex); + array_add(&found_gen_types->types, e); } @@ -612,6 +546,73 @@ gb_internal bool check_record_poly_operand_specialization(CheckerContext *ctx, T return true; } +gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array const &ordered_operands) { + for (Entity *e : found_gen_types->types) { + Type *t = base_type(e->type); + TypeTuple *tuple = nullptr; + switch (t->kind) { + case Type_Struct: + if (t->Struct.polymorphic_params) { + tuple = &t->Struct.polymorphic_params->Tuple; + } + break; + case Type_Union: + if (t->Union.polymorphic_params) { + tuple = &t->Union.polymorphic_params->Tuple; + } + break; + } + GB_ASSERT_MSG(tuple != nullptr, "%s :: %s", type_to_string(e->type), type_to_string(t)); + GB_ASSERT(param_count == tuple->variables.count); + + bool skip = false; + + for (isize j = 0; j < param_count; j++) { + Entity *p = tuple->variables[j]; + Operand o = {}; + if (j < ordered_operands.count) { + o = ordered_operands[j]; + } + if (o.expr == nullptr) { + continue; + } + Entity *oe = entity_of_node(o.expr); + if (p == oe) { + // NOTE(bill): This is the same type, make sure that it will be be same thing and use that + // Saves on a lot of checking too below + continue; + } + + if (p->kind == Entity_TypeName) { + if (is_type_polymorphic(o.type)) { + // NOTE(bill): Do not add polymorphic version to the gen_types + skip = true; + break; + } + if (!are_types_identical(o.type, p->type)) { + skip = true; + break; + } + } else if (p->kind == Entity_Constant) { + if (!compare_exact_values(Token_CmpEq, o.value, p->Constant.value)) { + skip = true; + break; + } + if (!are_types_identical(o.type, p->type)) { + skip = true; + break; + } + } else { + GB_PANIC("Unknown entity kind"); + } + } + if (!skip) { + return e; + } + } + return nullptr; +}; + gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *node, Array *poly_operands, Type *named_type, Type *original_type_for_poly) { GB_ASSERT(is_type_struct(struct_type)); @@ -1869,6 +1870,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#any_int' can only be applied to variable fields"); p->flags &= ~FieldFlag_any_int; } + if (p->flags&FieldFlag_no_broadcast) { + error(name, "'#no_broadcast' can only be applied to variable fields"); + p->flags &= ~FieldFlag_no_broadcast; + } if (p->flags&FieldFlag_by_ptr) { error(name, "'#by_ptr' can only be applied to variable fields"); p->flags &= ~FieldFlag_by_ptr; @@ -1926,7 +1931,13 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para } } } - if (type != t_invalid && !check_is_assignable_to(ctx, &op, type)) { + + bool allow_array_programming = true; + if (p->flags&FieldFlag_no_broadcast) { + allow_array_programming = false; + } + + if (type != t_invalid && !check_is_assignable_to(ctx, &op, type, allow_array_programming)) { bool ok = true; if (p->flags&FieldFlag_any_int) { if ((!is_type_integer(op.type) && !is_type_enum(op.type)) || (!is_type_integer(type) && !is_type_enum(type))) { @@ -2002,6 +2013,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (p->flags&FieldFlag_no_alias) { param->flags |= EntityFlag_NoAlias; } + if (p->flags&FieldFlag_no_broadcast) { + param->flags |= EntityFlag_NoBroadcast; + } + if (p->flags&FieldFlag_any_int) { if (!is_type_integer(param->type) && !is_type_enum(param->type)) { gbString str = type_to_string(param->type); @@ -2382,6 +2397,8 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) { return 0; } + ERROR_BLOCK(); + gbString s = expr_to_string(o->expr); error(e, "Array count must be a constant integer, got %s", s); gb_string_free(s); @@ -2474,6 +2491,7 @@ gb_internal Type *get_map_cell_type(Type *type) { s->Struct.fields[0] = alloc_entity_field(scope, make_token_ident("v"), alloc_type_array(type, len), false, 0, EntityState_Resolved); s->Struct.fields[1] = alloc_entity_field(scope, make_token_ident("_"), alloc_type_array(t_u8, padding), false, 1, EntityState_Resolved); s->Struct.scope = scope; + wait_signal_set(&s->Struct.fields_wait_signal); gb_unused(type_size_of(s)); return s; @@ -2504,6 +2522,7 @@ gb_internal void init_map_internal_types(Type *type) { metadata_type->Struct.fields[4] = alloc_entity_field(metadata_scope, make_token_ident("value_cell"), value_cell, false, 4, EntityState_Resolved); metadata_type->Struct.scope = metadata_scope; metadata_type->Struct.node = nullptr; + wait_signal_set(&metadata_type->Struct.fields_wait_signal); gb_unused(type_size_of(metadata_type)); @@ -2521,6 +2540,7 @@ gb_internal void init_map_internal_types(Type *type) { debug_type->Struct.fields[3] = alloc_entity_field(scope, make_token_ident("__metadata"), metadata_type, false, 3, EntityState_Resolved); debug_type->Struct.scope = scope; debug_type->Struct.node = nullptr; + wait_signal_set(&debug_type->Struct.fields_wait_signal); gb_unused(type_size_of(debug_type)); @@ -2816,6 +2836,7 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e add_entity(ctx, scope, nullptr, base_type_entity); add_type_info_type(ctx, soa_struct); + wait_signal_set(&soa_struct->Struct.fields_wait_signal); return soa_struct; } @@ -2864,6 +2885,8 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t } if (!is_sparse && t->EnumeratedArray.count > bt->Enum.fields.count) { + ERROR_BLOCK(); + error(e, "Non-contiguous enumeration used as an index in an enumerated array"); long long ea_count = cast(long long)t->EnumeratedArray.count; long long enum_count = cast(long long)bt->Enum.fields.count; @@ -3094,20 +3117,22 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T Type *elem = t_invalid; Operand o = {}; + check_expr_or_type(&c, &o, pt->type); if (o.mode != Addressing_Invalid && o.mode != Addressing_Type) { - // NOTE(bill): call check_type_expr again to get a consistent error message - ERROR_BLOCK(); - elem = check_type_expr(&c, pt->type, nullptr); if (o.mode == Addressing_Variable) { gbString s = expr_to_string(pt->type); - error_line("\tSuggestion: ^ is used for pointer types, did you mean '&%s'?\n", s); + error(e, "^ is used for pointer types, did you mean '&%s'?", s); gb_string_free(s); + } else { + // NOTE(bill): call check_type_expr again to get a consistent error message + elem = check_type_expr(&c, pt->type, nullptr); } } else { elem = o.type; } + if (pt->tag != nullptr) { GB_ASSERT(pt->tag->kind == Ast_BasicDirective); String name = pt->tag->BasicDirective.name.string; @@ -3355,7 +3380,9 @@ gb_internal Type *check_type_expr(CheckerContext *ctx, Ast *e, Type *named_type) type = t_invalid; - // NOTE(bill): Check for common mistakes from C programmers e.g. T[] and T[N] + // NOTE(bill): Check for common mistakes from C programmers + // e.g. T[] and T[N] + // e.g. *T Ast *node = unparen_expr(e); if (node && node->kind == Ast_IndexExpr) { gbString index_str = nullptr; @@ -3367,7 +3394,7 @@ gb_internal Type *check_type_expr(CheckerContext *ctx, Ast *e, Type *named_type) gbString type_str = expr_to_string(node->IndexExpr.expr); defer (gb_string_free(type_str)); - error_line("\tSuggestion: Did you mean '[%s]%s'?", index_str ? index_str : "", type_str); + error_line("\tSuggestion: Did you mean '[%s]%s'?\n", index_str ? index_str : "", type_str); end_error_block(); // NOTE(bill): Minimize error propagation of bad array syntax by treating this like a type @@ -3375,6 +3402,18 @@ gb_internal Type *check_type_expr(CheckerContext *ctx, Ast *e, Type *named_type) Ast *pseudo_array_expr = ast_array_type(e->file(), ast_token(node->IndexExpr.expr), node->IndexExpr.index, node->IndexExpr.expr); check_array_type_internal(ctx, pseudo_array_expr, &type, nullptr); } + } else if (node && node->kind == Ast_UnaryExpr && node->UnaryExpr.op.kind == Token_Mul) { + gbString type_str = expr_to_string(node->UnaryExpr.expr); + defer (gb_string_free(type_str)); + + error_line("\tSuggestion: Did you mean '^%s'?\n", type_str); + end_error_block(); + + // NOTE(bill): Minimize error propagation of bad array syntax by treating this like a type + if (node->UnaryExpr.expr != nullptr) { + Ast *pseudo_pointer_expr = ast_pointer_type(e->file(), ast_token(node->UnaryExpr.expr), node->UnaryExpr.expr); + return check_type_expr(ctx, pseudo_pointer_expr, named_type); + } } else { end_error_block(); } diff --git a/src/checker.cpp b/src/checker.cpp index 0efe61fba..e7d0ad9cb 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1115,7 +1115,16 @@ gb_internal void init_universal(void) { add_global_constant("ODIN_COMPILE_TIMESTAMP", t_untyped_integer, exact_value_i64(odin_compile_timestamp())); - add_global_bool_constant("__ODIN_LLVM_F16_SUPPORTED", lb_use_new_pass_system() && !is_arch_wasm()); + { + bool f16_supported = lb_use_new_pass_system(); + if (is_arch_wasm()) { + f16_supported = false; + } else if (build_context.metrics.os == TargetOs_darwin && build_context.metrics.arch == TargetArch_amd64) { + // NOTE(laytan): See #3222 for my ramblings on this. + f16_supported = false; + } + add_global_bool_constant("__ODIN_LLVM_F16_SUPPORTED", f16_supported); + } { GlobalEnumValue values[3] = { @@ -3180,6 +3189,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { linkage == "link_once") { ac->linkage = linkage; } else { + ERROR_BLOCK(); error(elem, "Invalid linkage '%.*s'. Valid kinds:", LIT(linkage)); error_line("\tinternal\n"); error_line("\tstrong\n"); @@ -3428,6 +3438,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } else if (mode == "speed") { ac->optimization_mode = ProcedureOptimizationMode_Speed; } else { + ERROR_BLOCK(); error(elem, "Invalid optimization_mode for '%.*s'. Valid modes:", LIT(name)); error_line("\tnone\n"); error_line("\tminimal\n"); @@ -3558,6 +3569,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { model == "localexec") { ac->thread_local_model = model; } else { + ERROR_BLOCK(); error(elem, "Invalid thread local model '%.*s'. Valid models:", LIT(model)); error_line("\tdefault\n"); error_line("\tlocaldynamic\n"); @@ -3608,6 +3620,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { linkage == "link_once") { ac->linkage = linkage; } else { + ERROR_BLOCK(); error(elem, "Invalid linkage '%.*s'. Valid kinds:", LIT(linkage)); error_line("\tinternal\n"); error_line("\tstrong\n"); @@ -3662,6 +3675,15 @@ gb_internal DECL_ATTRIBUTE_PROC(const_decl_attribute) { } else if (name == "private") { // NOTE(bill): Handled elsewhere `check_collect_value_decl` return true; + } else if (name == "static" || + name == "thread_local" || + name == "require" || + name == "linkage" || + name == "link_name" || + name == "link_prefix" || + false) { + error(elem, "@(%.*s) is not supported for compile time constant value declarations", LIT(name)); + return true; } return false; } @@ -3762,6 +3784,7 @@ gb_internal void check_decl_attributes(CheckerContext *c, Array const &at if (!proc(c, elem, name, value, ac)) { if (!build_context.ignore_unknown_attributes) { + ERROR_BLOCK(); error(elem, "Unknown attribute element name '%.*s'", LIT(name)); error_line("\tDid you forget to use build flag '-ignore-unknown-attributes'?\n"); } @@ -3831,6 +3854,8 @@ gb_internal bool check_arity_match(CheckerContext *c, AstValueDecl *vd, bool is_ gb_string_free(str); return false; } else if (is_global) { + ERROR_BLOCK(); + Ast *n = vd->values[rhs-1]; error(n, "Expected %td expressions on the right hand side, got %td", lhs, rhs); error_line("Note: Global declarations do not allow for multi-valued expressions"); @@ -4050,6 +4075,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { Entity *e = alloc_entity_variable(c->scope, name->Ident.token, nullptr); e->identifier = name; e->file = c->file; + e->Variable.is_global = true; if (entity_visibility_kind != EntityVisiblity_Public) { e->flags |= EntityFlag_NotExported; @@ -5448,7 +5474,10 @@ gb_internal void check_procedure_later_from_entity(Checker *c, Entity *e, char c return; } Type *type = base_type(e->type); - GB_ASSERT(type->kind == Type_Proc); + if (type == t_invalid) { + return; + } + GB_ASSERT_MSG(type->kind == Type_Proc, "%s", type_to_string(e->type)); if (is_type_polymorphic(type) && !type->Proc.is_poly_specialized) { return; @@ -6051,11 +6080,14 @@ gb_internal void check_unique_package_names(Checker *c) { continue; } + + begin_error_block(); error(curr, "Duplicate declaration of 'package %.*s'", LIT(name)); error_line("\tA package name must be unique\n" "\tThere is no relation between a package name and the directory that contains it, so they can be completely different\n" "\tA package name is required for link name prefixing to have a consistent ABI\n"); - error(prev, "found at previous location"); + error_line("%s found at previous location\n", token_pos_to_string(ast_token(prev).pos)); + end_error_block(); } } @@ -6320,6 +6352,8 @@ gb_internal void check_parsed_files(Checker *c) { error(token, "Undefined entry point procedure 'main'"); } + } else if (build_context.build_mode == BuildMode_DynamicLibrary && build_context.no_entry_point) { + c->info.entry_point = nullptr; } thread_pool_wait(); diff --git a/src/checker.hpp b/src/checker.hpp index eea99578e..e0dc54a87 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -360,7 +360,7 @@ struct GenProcsData { struct GenTypesData { Array types; - RwMutex mutex; + RecursiveMutex mutex; }; // CheckerInfo stores all the symbol information for a type-checked program @@ -400,8 +400,8 @@ struct CheckerInfo { RecursiveMutex lazy_mutex; // Mutex required for lazy type checking of specific files - RwMutex gen_types_mutex; - PtrMap gen_types; + BlockingMutex gen_types_mutex; + PtrMap gen_types; BlockingMutex type_info_mutex; // NOT recursive Array type_info_types; @@ -560,3 +560,6 @@ gb_internal void init_core_context(Checker *c); gb_internal void init_mem_allocator(Checker *c); gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap *untyped); + + +gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type); \ No newline at end of file diff --git a/src/docs_format.cpp b/src/docs_format.cpp index 779190e62..ca6ecb5c2 100644 --- a/src/docs_format.cpp +++ b/src/docs_format.cpp @@ -15,7 +15,7 @@ struct OdinDocVersionType { #define OdinDocVersionType_Major 0 #define OdinDocVersionType_Minor 3 -#define OdinDocVersionType_Patch 0 +#define OdinDocVersionType_Patch 1 struct OdinDocHeaderBase { u8 magic[8]; @@ -163,13 +163,15 @@ enum OdinDocEntityFlag : u64 { OdinDocEntityFlag_Foreign = 1ull<<0, OdinDocEntityFlag_Export = 1ull<<1, - OdinDocEntityFlag_Param_Using = 1ull<<2, - OdinDocEntityFlag_Param_Const = 1ull<<3, - OdinDocEntityFlag_Param_AutoCast = 1ull<<4, - OdinDocEntityFlag_Param_Ellipsis = 1ull<<5, - OdinDocEntityFlag_Param_CVararg = 1ull<<6, - OdinDocEntityFlag_Param_NoAlias = 1ull<<7, - OdinDocEntityFlag_Param_AnyInt = 1ull<<8, + OdinDocEntityFlag_Param_Using = 1ull<<2, + OdinDocEntityFlag_Param_Const = 1ull<<3, + OdinDocEntityFlag_Param_AutoCast = 1ull<<4, + OdinDocEntityFlag_Param_Ellipsis = 1ull<<5, + OdinDocEntityFlag_Param_CVararg = 1ull<<6, + OdinDocEntityFlag_Param_NoAlias = 1ull<<7, + OdinDocEntityFlag_Param_AnyInt = 1ull<<8, + OdinDocEntityFlag_Param_ByPtr = 1ull<<9, + OdinDocEntityFlag_Param_NoBroadcast = 1ull<<10, OdinDocEntityFlag_BitField_Field = 1ull<<19, diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 824445ed5..9ced78d33 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -925,11 +925,13 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) break; } - if (e->flags & EntityFlag_Using) { flags |= OdinDocEntityFlag_Param_Using; } - if (e->flags & EntityFlag_ConstInput) { flags |= OdinDocEntityFlag_Param_Const; } - if (e->flags & EntityFlag_Ellipsis) { flags |= OdinDocEntityFlag_Param_Ellipsis; } - if (e->flags & EntityFlag_NoAlias) { flags |= OdinDocEntityFlag_Param_NoAlias; } - if (e->flags & EntityFlag_AnyInt) { flags |= OdinDocEntityFlag_Param_AnyInt; } + if (e->flags & EntityFlag_Using) { flags |= OdinDocEntityFlag_Param_Using; } + if (e->flags & EntityFlag_ConstInput) { flags |= OdinDocEntityFlag_Param_Const; } + if (e->flags & EntityFlag_Ellipsis) { flags |= OdinDocEntityFlag_Param_Ellipsis; } + if (e->flags & EntityFlag_NoAlias) { flags |= OdinDocEntityFlag_Param_NoAlias; } + if (e->flags & EntityFlag_AnyInt) { flags |= OdinDocEntityFlag_Param_AnyInt; } + if (e->flags & EntityFlag_ByPtr) { flags |= OdinDocEntityFlag_Param_ByPtr; } + if (e->flags & EntityFlag_NoBroadcast) { flags |= OdinDocEntityFlag_Param_NoBroadcast; } if (e->scope && (e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) && !is_entity_exported(e)) { flags |= OdinDocEntityFlag_Private; diff --git a/src/entity.cpp b/src/entity.cpp index 916c2b2bd..6cea0930f 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -60,7 +60,7 @@ enum EntityFlag : u64 { EntityFlag_ProcBodyChecked = 1ull<<21, EntityFlag_CVarArg = 1ull<<22, - + EntityFlag_NoBroadcast = 1ull<<23, EntityFlag_AnyInt = 1ull<<24, EntityFlag_Disabled = 1ull<<25, @@ -229,6 +229,7 @@ struct Entity { CommentGroup *comment; bool is_foreign; bool is_export; + bool is_global; } Variable; struct { Type * type_parameter_specialization; @@ -480,3 +481,25 @@ gb_internal Entity *strip_entity_wrapping(Ast *expr) { Entity *e = entity_from_expr(expr); return strip_entity_wrapping(e); } + + +gb_internal bool is_entity_local_variable(Entity *e) { + if (e == nullptr) { + return false; + } + if (e->kind != Entity_Variable) { + return false; + } + if (e->Variable.is_global) { + return false; + } + if (e->scope == nullptr) { + return true; + } + if (e->flags & (EntityFlag_ForValue|EntityFlag_SwitchValue)) { + return false; + } + + return ((e->scope->flags &~ ScopeFlag_ContextDefined) == 0) || + (e->scope->flags & ScopeFlag_Proc) != 0; +} \ No newline at end of file diff --git a/src/error.cpp b/src/error.cpp index 2458061c2..b18df79b7 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -28,7 +28,7 @@ gb_global ErrorCollector global_error_collector; gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = ErrorValue_Error) { - GB_ASSERT(global_error_collector.curr_error_value_set.load() == false); + GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == false, "Possible race condition in error handling system, please report this with an issue"); ErrorValue ev = {kind, pos}; ev.msgs.allocator = heap_allocator(); @@ -53,7 +53,7 @@ gb_internal void try_pop_error_value(void) { } gb_internal ErrorValue *get_error_value(void) { - GB_ASSERT(global_error_collector.curr_error_value_set.load() == true); + GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == true, "Possible race condition in error handling system, please report this with an issue"); return &global_error_collector.curr_error_value; } @@ -600,7 +600,9 @@ gb_internal void syntax_error_with_verbose(TokenPos pos, TokenPos end, char cons gb_internal void compiler_error(char const *fmt, ...) { - print_all_errors(); + if (any_errors()) { + print_all_errors(); + } va_list va; @@ -614,7 +616,9 @@ gb_internal void compiler_error(char const *fmt, ...) { gb_internal void exit_with_errors(void) { - print_all_errors(); + if (any_errors()) { + print_all_errors(); + } gb_exit(1); } diff --git a/src/linker.cpp b/src/linker.cpp index 9d7f16f1d..6699c9cb8 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -502,7 +502,6 @@ gb_internal i32 linker_stage(LinkerData *gen) { platform_lib_str = gb_string_appendc(platform_lib_str, "-L/opt/local/lib "); } - // This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit. if (build_context.minimum_os_version_string.len) { link_settings = gb_string_append_fmt(link_settings, "-mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string)); } @@ -513,7 +512,9 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (!build_context.no_crt) { platform_lib_str = gb_string_appendc(platform_lib_str, "-lm "); if (build_context.metrics.os == TargetOs_darwin) { - platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem "); + // NOTE: adding this causes a warning about duplicate libraries, I think it is + // automatically assumed/added by clang when you don't do `-nostdlib`. + // platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem "); } else { platform_lib_str = gb_string_appendc(platform_lib_str, "-lc "); } diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 375235752..f976d99b3 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1146,11 +1146,26 @@ namespace lbAbiArm64 { if (size <= 16) { LLVMTypeRef cast_type = nullptr; + GB_ASSERT(size > 0); if (size <= 8) { cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); } else { unsigned count = cast(unsigned)((size+7)/8); - cast_type = llvm_array_type(LLVMInt64TypeInContext(c), count); + + LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64); + LLVMTypeRef *types = gb_alloc_array(temporary_allocator(), LLVMTypeRef, count); + + i64 size_copy = size; + for (unsigned i = 0; i < count; i++) { + if (size_copy >= 8) { + types[i] = llvm_i64; + } else { + types[i] = LLVMIntTypeInContext(c, 8*cast(unsigned)size_copy); + } + size_copy -= 8; + } + GB_ASSERT(size_copy <= 0); + cast_type = LLVMStructTypeInContext(c, types, count, true); } return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr); } else { @@ -1183,7 +1198,9 @@ namespace lbAbiArm64 { i64 size = lb_sizeof(type); if (size <= 16) { LLVMTypeRef cast_type = nullptr; - if (size <= 8) { + if (size == 0) { + cast_type = LLVMStructTypeInContext(c, nullptr, 0, false); + } else if (size <= 8) { cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); } else { unsigned count = cast(unsigned)((size+7)/8); diff --git a/src/main.cpp b/src/main.cpp index ab721a143..9c353653f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1925,7 +1925,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { 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, "Example: -minimum-os-version:12.0.0"); + print_usage_line(2, "Default: -minimum-os-version:11.0.0"); print_usage_line(2, "(Only used when target is Darwin.)"); print_usage_line(0, ""); diff --git a/src/parser.cpp b/src/parser.cpp index 6a7be8a7c..d46079964 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2351,9 +2351,6 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { return ast_bad_expr(f, token, name); } switch (expr->kind) { - case Ast_ArrayType: - syntax_error(expr, "#partial has been replaced with #sparse for non-contiguous enumerated array types"); - break; case Ast_CompoundLit: expr->CompoundLit.tag = tag; break; @@ -2541,6 +2538,9 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { return ast_pointer_type(f, token, elem); } break; + case Token_Mul: + return parse_unary_expr(f, true); + case Token_OpenBracket: { Token token = expect_token(f, Token_OpenBracket); Ast *count_expr = nullptr; @@ -3274,7 +3274,9 @@ gb_internal Ast *parse_unary_expr(AstFile *f, bool lhs) { case Token_Sub: case Token_Xor: case Token_And: - case Token_Not: { + case Token_Not: + case Token_Mul: // Used for error handling when people do C-like things + { Token token = advance_token(f); Ast *expr = parse_unary_expr(f, lhs); return ast_unary_expr(f, token, expr); @@ -3661,6 +3663,7 @@ gb_internal Ast *parse_simple_stmt(AstFile *f, u32 flags) { expect_token_after(f, Token_Colon, "identifier list"); if ((flags&StmtAllowFlag_Label) && lhs.count == 1) { bool is_partial = false; + bool is_reverse = false; Token partial_token = {}; if (f->curr_token.kind == Token_Hash) { // NOTE(bill): This is purely for error messages @@ -3670,6 +3673,11 @@ gb_internal Ast *parse_simple_stmt(AstFile *f, u32 flags) { partial_token = expect_token(f, Token_Hash); expect_token(f, Token_Ident); is_partial = true; + } else if (name.kind == Token_Ident && name.string == "reverse" && + peek_token_n(f, 1).kind == Token_for) { + partial_token = expect_token(f, Token_Hash); + expect_token(f, Token_Ident); + is_reverse = true; } } switch (f->curr_token.kind) { @@ -3704,6 +3712,18 @@ gb_internal Ast *parse_simple_stmt(AstFile *f, u32 flags) { break; } syntax_error(partial_token, "Incorrect use of directive, use '#partial %.*s: switch'", LIT(ast_token(name).string)); + } else if (is_reverse) { + switch (stmt->kind) { + case Ast_RangeStmt: + if (stmt->RangeStmt.reverse) { + syntax_error(token, "#reverse already applied to a 'for in' statement"); + } + stmt->RangeStmt.reverse = true; + break; + default: + syntax_error(token, "#reverse can only be applied to a 'for in' statement"); + break; + } } return stmt; @@ -3898,14 +3918,15 @@ struct ParseFieldPrefixMapping { FieldFlag flag; }; -gb_global ParseFieldPrefixMapping parse_field_prefix_mappings[] = { - {str_lit("using"), Token_using, FieldFlag_using}, - {str_lit("no_alias"), Token_Hash, FieldFlag_no_alias}, - {str_lit("c_vararg"), Token_Hash, FieldFlag_c_vararg}, - {str_lit("const"), Token_Hash, FieldFlag_const}, - {str_lit("any_int"), Token_Hash, FieldFlag_any_int}, - {str_lit("subtype"), Token_Hash, FieldFlag_subtype}, - {str_lit("by_ptr"), Token_Hash, FieldFlag_by_ptr}, +gb_global ParseFieldPrefixMapping const parse_field_prefix_mappings[] = { + {str_lit("using"), Token_using, FieldFlag_using}, + {str_lit("no_alias"), Token_Hash, FieldFlag_no_alias}, + {str_lit("c_vararg"), Token_Hash, FieldFlag_c_vararg}, + {str_lit("const"), Token_Hash, FieldFlag_const}, + {str_lit("any_int"), Token_Hash, FieldFlag_any_int}, + {str_lit("subtype"), Token_Hash, FieldFlag_subtype}, + {str_lit("by_ptr"), Token_Hash, FieldFlag_by_ptr}, + {str_lit("no_broadcast"), Token_Hash, FieldFlag_no_broadcast}, }; @@ -6289,7 +6310,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if (!path_is_directory(init_fullpath)) { String const ext = str_lit(".odin"); if (!string_ends_with(init_fullpath, ext)) { - error_line("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); + error({}, "Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); return ParseFile_WrongExtension; } } else if (init_fullpath.len != 0) { @@ -6302,7 +6323,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { String short_path = filename_from_path(path); char *cpath = alloc_cstring(temporary_allocator(), short_path); if (gb_file_exists(cpath)) { - error_line("Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); + error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); return ParseFile_DirectoryAlreadyExists; } } @@ -6338,7 +6359,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if (!path_is_directory(fullpath)) { String const ext = str_lit(".odin"); if (!string_ends_with(fullpath, ext)) { - error_line("Expected either a directory or a .odin file, got '%.*s'\n", LIT(fullpath)); + error({}, "Expected either a directory or a .odin file, got '%.*s'\n", LIT(fullpath)); return ParseFile_WrongExtension; } } diff --git a/src/parser.hpp b/src/parser.hpp index ff3c5eb34..5820275c8 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -326,6 +326,7 @@ enum FieldFlag : u32 { FieldFlag_any_int = 1<<6, FieldFlag_subtype = 1<<7, FieldFlag_by_ptr = 1<<8, + FieldFlag_no_broadcast = 1<<9, // disallow array programming // Internal use by the parser only FieldFlag_Tags = 1<<10, @@ -336,7 +337,7 @@ enum FieldFlag : u32 { FieldFlag_Invalid = 1u<<31, // Parameter List Restrictions - FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr, + FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast, FieldFlag_Struct = FieldFlag_using|FieldFlag_subtype|FieldFlag_Tags, }; diff --git a/src/threading.cpp b/src/threading.cpp index a469435d2..ab19718cc 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -113,7 +113,7 @@ struct Wait_Signal { gb_internal void wait_signal_until_available(Wait_Signal *ws) { if (ws->futex.load() == 0) { - futex_wait(&ws->futex, 1); + futex_wait(&ws->futex, 0); } } @@ -122,8 +122,6 @@ gb_internal void wait_signal_set(Wait_Signal *ws) { futex_broadcast(&ws->futex); } - - struct MutexGuard { MutexGuard() = delete; MutexGuard(MutexGuard const &) = delete; diff --git a/tests/core/Makefile b/tests/core/Makefile index ecb05d002..dcb3c9906 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -24,7 +24,8 @@ all: c_libc_test \ slice_test \ strings_test \ thread_test \ - runtime_test + runtime_test \ + time_test download_test_assets: $(PYTHON) download_assets.py @@ -94,3 +95,6 @@ thread_test: runtime_test: $(ODIN) run runtime $(COMMON) -out:test_core_runtime + +time_test: + $(ODIN) run time $(COMMON) -out:test_core_time diff --git a/tests/core/build.bat b/tests/core/build.bat index 210760d00..ac7137fa1 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -100,3 +100,8 @@ echo --- echo Running core:runtime tests echo --- %PATH_TO_ODIN% run runtime %COMMON% %COLLECTION% -out:test_core_runtime.exe || exit /b + +echo --- +echo Running core:time tests +echo --- +%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b \ No newline at end of file diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin new file mode 100644 index 000000000..2cea47680 --- /dev/null +++ b/tests/core/time/test_core_time.odin @@ -0,0 +1,178 @@ +package test_core_time + +import "core:fmt" +import "core:mem" +import "core:os" +import "core:testing" +import "core:time" +import dt "core:time/datetime" + +is_leap_year :: time.is_leap_year + +TEST_count := 0 +TEST_fail := 0 + +when ODIN_TEST { + expect :: testing.expect + expect_value :: testing.expect_value + log :: testing.log +} else { + expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { + TEST_count += 1 + if !condition { + TEST_fail += 1 + fmt.printf("[%v] %v\n", loc, message) + return + } + } + log :: proc(t: ^testing.T, v: any, loc := #caller_location) { + fmt.printf("[%v] ", loc) + fmt.printf("log: %v\n", v) + } +} + +main :: proc() { + t := testing.T{} + + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + defer mem.tracking_allocator_destroy(&track) + context.allocator = mem.tracking_allocator(&track) + + test_ordinal_date_roundtrip(&t) + test_component_to_time_roundtrip(&t) + test_parse_rfc3339_string(&t) + + for _, leak in track.allocation_map { + expect(&t, false, fmt.tprintf("%v leaked %m\n", leak.location, leak.size)) + } + for bad_free in track.bad_free_array { + expect(&t, false, fmt.tprintf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)) + } + + fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) + if TEST_fail > 0 { + os.exit(1) + } +} + +@test +test_ordinal_date_roundtrip :: proc(t: ^testing.T) { + expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.") + expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MIN_ORD)) == dt.MIN_ORD, "Roundtripping MIN_ORD failed.") + expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MAX_DATE)) == dt.MAX_DATE, "Roundtripping MAX_DATE failed.") + expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MAX_ORD)) == dt.MAX_ORD, "Roundtripping MAX_ORD failed.") +} + +/* + 1990-12-31T23:59:60Z + +This represents the leap second inserted at the end of 1990. + + 1990-12-31T15:59:60-08:00 + +This represents the same leap second in Pacific Standard Time, 8 hours behind UTC. + + 1937-01-01T12:00:27.87+00:20 + +This represents the same instant of time as noon, January 1, 1937, Netherlands time. +Standard time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by law from 1909-05-01 through 1937-06-30. +This time zone cannot be represented exactly using the HH:MM format, and this timestamp uses the closest representable UTC offset. +*/ +RFC3339_Test :: struct{ + rfc_3339: string, + datetime: time.Time, + apply_offset: bool, + utc_offset: int, + consumed: int, + is_leap: bool, +} + +// These are based on RFC 3339's examples, see https://www.rfc-editor.org/rfc/rfc3339#page-10 +rfc3339_tests :: []RFC3339_Test{ + // This represents 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC. + {"1985-04-12T23:20:50.52Z", {482196050520000000}, true, 0, 23, false}, + + // This represents 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with an offset of -08:00 from UTC (Pacific Standard Time). + // Note that this is equivalent to 1996-12-20T00:39:57Z in UTC. + {"1996-12-19T16:39:57-08:00", {851013597000000000}, false, -480, 25, false}, + {"1996-12-19T16:39:57-08:00", {851042397000000000}, true, 0, 25, false}, + {"1996-12-20T00:39:57Z", {851042397000000000}, false, 0, 20, false}, + + // This represents the leap second inserted at the end of 1990. + // It'll be represented as 1990-12-31 23:59:59 UTC after parsing, and `is_leap` will be set to `true`. + {"1990-12-31T23:59:60Z", {662687999000000000}, true, 0, 20, true}, + + // This represents the same leap second in Pacific Standard Time, 8 hours behind UTC. + {"1990-12-31T15:59:60-08:00", {662687999000000000}, true, 0, 25, true}, + + // This represents the same instant of time as noon, January 1, 1937, Netherlands time. + // Standard time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by law + // from 1909-05-01 through 1937-06-30. This time zone cannot be represented exactly using the + // HH:MM format, and this timestamp uses the closest representable UTC offset. + {"1937-01-01T12:00:27.87+00:20", {-1041335972130000000}, false, 20, 28, false}, + {"1937-01-01T12:00:27.87+00:20", {-1041337172130000000}, true, 0, 28, false}, +} + +@test +test_parse_rfc3339_string :: proc(t: ^testing.T) { + for test in rfc3339_tests { + is_leap := false + if test.apply_offset { + res, consumed := time.rfc3339_to_time_utc(test.rfc_3339, &is_leap) + msg := fmt.tprintf("[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, test.consumed, consumed) + expect(t, test.consumed == consumed, msg) + + if test.consumed == consumed { + expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) + expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + } + } else { + res, offset, consumed := time.rfc3339_to_time_and_offset(test.rfc_3339) + msg := fmt.tprintf("Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, offset, test.consumed, consumed) + expect(t, test.consumed == consumed, msg) + + if test.consumed == consumed { + expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) + expect(t, test.utc_offset == offset, fmt.tprintf("UTC offset didn't match. Expected %v, got %v", test.utc_offset, offset)) + expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + } + } + } +} + +MONTH_DAYS := []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +YEAR_START :: 1900 +YEAR_END :: 2024 + +@test +test_component_to_time_roundtrip :: proc(t: ^testing.T) { + // Roundtrip a datetime through `datetime_to_time` to `Time` and back to its components. + for year in YEAR_START..=YEAR_END { + for month in 1..=12 { + days := MONTH_DAYS[month - 1] + if month == 2 && is_leap_year(year) { + days += 1 + } + for day in 1..=days { + d, _ := dt.components_to_datetime(year, month, day, 0, 0, 0, 0) + date_component_roundtrip_test(t, d) + } + } + } +} + +date_component_roundtrip_test :: proc(t: ^testing.T, moment: dt.DateTime) { + res, ok := time.datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second) + expect(t, ok, "Couldn't convert date components into date") + + YYYY, MM, DD := time.date(res) + hh, mm, ss := time.clock(res) + + expected := fmt.tprintf("Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", + moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss) + + ok = moment.year == i64(YYYY) && moment.month == i8(MM) && moment.day == i8(DD) + ok &= moment.hour == i8(hh) && moment.minute == i8(mm) && moment.second == i8(ss) + expect(t, ok, expected) +} \ No newline at end of file diff --git a/tests/vendor/glfw/test_vendor_glfw.odin b/tests/vendor/glfw/test_vendor_glfw.odin index baf46aa7e..ce55ad7ef 100644 --- a/tests/vendor/glfw/test_vendor_glfw.odin +++ b/tests/vendor/glfw/test_vendor_glfw.odin @@ -6,8 +6,8 @@ import "vendor:glfw" import "core:os" GLFW_MAJOR :: 3 -GLFW_MINOR :: 3 -GLFW_PATCH :: 4 +GLFW_MINOR :: 4 +GLFW_PATCH :: 0 TEST_count := 0 TEST_fail := 0 @@ -46,4 +46,4 @@ main :: proc() { test_glfw :: proc(t: ^testing.T) { major, minor, patch := glfw.GetVersion() expect(t, major == GLFW_MAJOR && minor == GLFW_MINOR, fmt.tprintf("Expected GLFW.GetVersion: %v.%v.%v, got %v.%v.%v instead", GLFW_MAJOR, GLFW_MINOR, GLFW_PATCH, major, minor, patch)) -} \ No newline at end of file +} diff --git a/vendor/glfw/bindings/bindings.odin b/vendor/glfw/bindings/bindings.odin index bb50b8973..164a8ea2d 100644 --- a/vendor/glfw/bindings/bindings.odin +++ b/vendor/glfw/bindings/bindings.odin @@ -21,14 +21,21 @@ when ODIN_OS == .Windows { "system:shell32.lib", } } -} else when ODIN_OS == .Linux { - foreign import glfw "system:glfw" } else when ODIN_OS == .Darwin { - foreign import glfw { - "../lib/darwin/libglfw3.a", - "system:Cocoa.framework", - "system:IOKit.framework", - "system:OpenGL.framework", + when GLFW_SHARED { + foreign import glfw { + "system:glfw", + "system:Cocoa.framework", + "system:IOKit.framework", + "system:OpenGL.framework", + } + } else { + foreign import glfw { + "../lib/darwin/libglfw3.a", + "system:Cocoa.framework", + "system:IOKit.framework", + "system:OpenGL.framework", + } } } else { foreign import glfw "system:glfw" @@ -44,6 +51,10 @@ foreign glfw { InitHint :: proc(hint, value: c.int) --- + InitAllocator :: proc(#by_ptr allocator: Allocator) --- + + InitVulkanLoader :: proc(loader: vk.ProcGetInstanceProcAddr) --- + GetVersion :: proc(major, minor, rev: ^c.int) --- GetError :: proc(description: ^cstring) -> c.int --- @@ -94,6 +105,7 @@ foreign glfw { GetKey :: proc(window: WindowHandle, key: c.int) -> c.int --- GetKeyName :: proc(key, scancode: c.int) -> cstring --- SetWindowShouldClose :: proc(window: WindowHandle, value: b32) --- + GetWindowTitle :: proc(window: WindowHandle) -> cstring --- JoystickPresent :: proc(joy: c.int) -> b32 --- GetJoystickName :: proc(joy: c.int) -> cstring --- GetKeyScancode :: proc(key: c.int) -> c.int --- @@ -184,5 +196,8 @@ foreign glfw { SetJoystickCallback :: proc(cbfun: JoystickProc) -> JoystickProc --- SetErrorCallback :: proc(cbfun: ErrorProc) -> ErrorProc --- + + GetPlatform :: proc() -> c.int --- + PlatformSupported :: proc(platform: c.int) -> b32 --- } diff --git a/vendor/glfw/bindings/types.odin b/vendor/glfw/bindings/types.odin index a3bee3fae..5bdbf9cb9 100644 --- a/vendor/glfw/bindings/types.odin +++ b/vendor/glfw/bindings/types.odin @@ -30,6 +30,13 @@ GamepadState :: struct { axes: [6]f32, } +Allocator :: struct { + allocate: AllocateProc, + reallocate: ReallocateProc, + deallocate: DeallocateProc, + user: rawptr, +} + /*** Procedure type declarations ***/ WindowIconifyProc :: #type proc "c" (window: WindowHandle, iconified: c.int) WindowRefreshProc :: #type proc "c" (window: WindowHandle) @@ -53,3 +60,7 @@ CursorEnterProc :: #type proc "c" (window: WindowHandle, entered: c.int) JoystickProc :: #type proc "c" (joy, event: c.int) ErrorProc :: #type proc "c" (error: c.int, description: cstring) + +AllocateProc :: #type proc "c" (size: c.size_t, user: rawptr) -> rawptr +ReallocateProc :: #type proc "c" (block: rawptr, size: c.size_t, user: rawptr) -> rawptr +DeallocateProc :: #type proc "c" (block: rawptr, user: rawptr) diff --git a/vendor/glfw/constants.odin b/vendor/glfw/constants.odin index 90c46f4bc..ce4f80584 100644 --- a/vendor/glfw/constants.odin +++ b/vendor/glfw/constants.odin @@ -6,8 +6,8 @@ GLFW_SHARED :: #config(GLFW_SHARED, false) /*** Constants ***/ /* Versions */ VERSION_MAJOR :: 3 -VERSION_MINOR :: 3 -VERSION_REVISION :: 8 +VERSION_MINOR :: 4 +VERSION_REVISION :: 0 /* Booleans */ TRUE :: true @@ -251,17 +251,21 @@ GAMEPAD_AXIS_RIGHT_TRIGGER :: 5 GAMEPAD_AXIS_LAST :: GAMEPAD_AXIS_RIGHT_TRIGGER /* Error constants */ -NO_ERROR :: 0x00000000 -NOT_INITIALIZED :: 0x00010001 -NO_CURRENT_CONTEXT :: 0x00010002 -INVALID_ENUM :: 0x00010003 -INVALID_VALUE :: 0x00010004 -OUT_OF_MEMORY :: 0x00010005 -API_UNAVAILABLE :: 0x00010006 -VERSION_UNAVAILABLE :: 0x00010007 -PLATFORM_ERROR :: 0x00010008 -FORMAT_UNAVAILABLE :: 0x00010009 -NO_WINDOW_CONTEXT :: 0x0001000A +NO_ERROR :: 0x00000000 +NOT_INITIALIZED :: 0x00010001 +NO_CURRENT_CONTEXT :: 0x00010002 +INVALID_ENUM :: 0x00010003 +INVALID_VALUE :: 0x00010004 +OUT_OF_MEMORY :: 0x00010005 +API_UNAVAILABLE :: 0x00010006 +VERSION_UNAVAILABLE :: 0x00010007 +PLATFORM_ERROR :: 0x00010008 +FORMAT_UNAVAILABLE :: 0x00010009 +NO_WINDOW_CONTEXT :: 0x0001000A +CURSOR_UNAVAILABLE :: 0x0001000B +FEATURE_UNAVAILABLE :: 0x0001000C +FEATURE_UNIMPLEMENTED :: 0x0001000D +PLATFORM_UNAVAILABLE :: 0x0001000E /* Window attributes */ FOCUSED :: 0x00020001 @@ -276,6 +280,9 @@ CENTER_CURSOR :: 0x00020009 TRANSPARENT_FRAMEBUFFER :: 0x0002000A HOVERED :: 0x0002000B FOCUS_ON_SHOW :: 0x0002000C +MOUSE_PASSTHROUGH :: 0x0002000D +POSITION_X :: 0x0002000E +POSITION_Y :: 0x0002000F /* Pixel window attributes */ RED_BITS :: 0x00021001 @@ -302,12 +309,14 @@ CONTEXT_VERSION_MINOR :: 0x00022003 CONTEXT_REVISION :: 0x00022004 CONTEXT_ROBUSTNESS :: 0x00022005 OPENGL_FORWARD_COMPAT :: 0x00022006 -OPENGL_DEBUG_CONTEXT :: 0x00022007 +CONTEXT_DEBUG :: 0x00022007 +OPENGL_DEBUG_CONTEXT :: CONTEXT_DEBUG // Backwards compatibility OPENGL_PROFILE :: 0x00022008 CONTEXT_RELEASE_BEHAVIOR :: 0x00022009 CONTEXT_NO_ERROR :: 0x0002200A CONTEXT_CREATION_API :: 0x0002200B SCALE_TO_MONITOR :: 0x0002200C +SCALE_FRAMEBUFFER :: 0x0002200D /* Cross platform attributes */ COCOA_RETINA_FRAMEBUFFER :: 0x00023001 @@ -315,6 +324,9 @@ COCOA_FRAME_NAME :: 0x00023002 COCOA_GRAPHICS_SWITCHING :: 0x00023003 X11_CLASS_NAME :: 0x00024001 X11_INSTANCE_NAME :: 0x00024002 +WIN32_KEYBOARD_MENU :: 0x00025001 +WIN32_SHOWDEFAULT :: 0x00025002 +WAYLAND_APP_ID :: 0x00026001 /* APIs */ NO_API :: 0 @@ -341,6 +353,7 @@ LOCK_KEY_MODS :: 0x00033004 CURSOR_NORMAL :: 0x00034001 CURSOR_HIDDEN :: 0x00034002 CURSOR_DISABLED :: 0x00034003 +CURSOR_CAPTURED :: 0x00034004 /* Mouse motion */ RAW_MOUSE_MOTION :: 0x00033005 @@ -355,24 +368,56 @@ NATIVE_CONTEXT_API :: 0x00036001 EGL_CONTEXT_API :: 0x00036002 OSMESA_CONTEXT_API :: 0x00036003 +ANGLE_PLATFORM_TYPE_NONE :: 0x00037001 +ANGLE_PLATFORM_TYPE_OPENGL :: 0x00037002 +ANGLE_PLATFORM_TYPE_OPENGLES :: 0x00037003 +ANGLE_PLATFORM_TYPE_D3D9 :: 0x00037004 +ANGLE_PLATFORM_TYPE_D3D11 :: 0x00037005 +ANGLE_PLATFORM_TYPE_VULKAN :: 0x00037007 +ANGLE_PLATFORM_TYPE_METAL :: 0x00037008 + +WAYLAND_PREFER_LIBDECOR :: 0x00038001 +WAYLAND_DISABLE_LIBDECOR :: 0x00038002 + +ANY_POSITION :: 0x80000000 + /* Types of cursors */ -ARROW_CURSOR :: 0x00036001 -IBEAM_CURSOR :: 0x00036002 -CROSSHAIR_CURSOR :: 0x00036003 -HAND_CURSOR :: 0x00036004 -HRESIZE_CURSOR :: 0x00036005 -VRESIZE_CURSOR :: 0x00036006 -RESIZE_NWSE_CURSOR :: 0x00036007 -RESIZE_NESW_CURSOR :: 0x00036008 +ARROW_CURSOR :: 0x00036001 +IBEAM_CURSOR :: 0x00036002 +CROSSHAIR_CURSOR :: 0x00036003 +POINTING_HAND_CURSOR :: 0x00036004 +RESIZE_EW_CURSOR :: 0x00036005 +RESIZE_NS_CURSOR :: 0x00036006 +RESIZE_NWSE_CURSOR :: 0x00036007 +RESIZE_NESW_CURSOR :: 0x00036008 +RESIZE_ALL_CURSOR :: 0x00036009 +NOT_ALLOWED_CURSOR :: 0x0003600A + +/* Backwards compatibility cursors. */ +HRESIZE_CURSOR :: RESIZE_EW_CURSOR +VRESIZE_CURSOR :: RESIZE_NS_CURSOR +HAND_CURSOR :: POINTING_HAND_CURSOR /* Joystick? */ CONNECTED :: 0x00040001 DISCONNECTED :: 0x00040002 -/* macOS specific init hint. */ -JOYSTICK_HAT_BUTTONS :: 0x00050001 -COCOA_CHDIR_RESOURCES :: 0x00051001 -COCOA_MENUBAR :: 0x00051002 +JOYSTICK_HAT_BUTTONS :: 0x00050001 +ANGLE_PLATFORM_TYPE :: 0x00050002 +PLATFORM :: 0x00050003 + +/* Platform specific init hints. */ +COCOA_CHDIR_RESOURCES :: 0x00051001 +COCOA_MENUBAR :: 0x00051002 +X11_XCB_VULKAN_SURFACE :: 0x00052001 +WAYLAND_LIBDECOR :: 0x00053001 + +ANY_PLATFORM :: 0x00060000 +PLATFORM_WIN32 :: 0x00060001 +PLATFORM_COCOA :: 0x00060002 +PLATFORM_WAYLAND :: 0x00060003 +PLATFORM_X11 :: 0x00060004 +PLATFORM_NULL :: 0x00060005 /* */ DONT_CARE :: -1 diff --git a/vendor/glfw/lib/darwin/libglfw3.a b/vendor/glfw/lib/darwin/libglfw3.a index 77506567f..08fb5935c 100644 Binary files a/vendor/glfw/lib/darwin/libglfw3.a and b/vendor/glfw/lib/darwin/libglfw3.a differ diff --git a/vendor/glfw/lib/glfw3.dll b/vendor/glfw/lib/glfw3.dll index 733404cf4..0511a9a8c 100644 Binary files a/vendor/glfw/lib/glfw3.dll and b/vendor/glfw/lib/glfw3.dll differ diff --git a/vendor/glfw/lib/glfw3.lib b/vendor/glfw/lib/glfw3.lib index 2a52eb4eb..92de42955 100644 Binary files a/vendor/glfw/lib/glfw3.lib and b/vendor/glfw/lib/glfw3.lib differ diff --git a/vendor/glfw/lib/glfw3_mt.lib b/vendor/glfw/lib/glfw3_mt.lib index 30ba4adab..eee805592 100644 Binary files a/vendor/glfw/lib/glfw3_mt.lib and b/vendor/glfw/lib/glfw3_mt.lib differ diff --git a/vendor/glfw/lib/glfw3dll.lib b/vendor/glfw/lib/glfw3dll.lib index e1b3f3efc..40d5189d9 100644 Binary files a/vendor/glfw/lib/glfw3dll.lib and b/vendor/glfw/lib/glfw3dll.lib differ diff --git a/vendor/glfw/native_darwin.odin b/vendor/glfw/native_darwin.odin index 181a53e24..999de218b 100644 --- a/vendor/glfw/native_darwin.odin +++ b/vendor/glfw/native_darwin.odin @@ -4,18 +4,10 @@ package glfw import NS "vendor:darwin/Foundation" -when GLFW_SHARED { - #panic("Dynamic linking for glfw is not supported for darwin yet") - foreign import glfw {"_"} -} else { - foreign import glfw { - "lib/darwin/libglfw3.a", - } -} - @(default_calling_convention="c", link_prefix="glfw") -foreign glfw { - GetCocoaWindow :: proc(window: WindowHandle) -> ^NS.Window --- +foreign { + GetCocoaWindow :: proc(window: WindowHandle) -> ^NS.Window --- + GetCocoaView :: proc(window: WindowHandle) -> ^NS.View --- } // TODO: diff --git a/vendor/glfw/native_windows.odin b/vendor/glfw/native_windows.odin index 9903ff298..ce0dbf66f 100644 --- a/vendor/glfw/native_windows.odin +++ b/vendor/glfw/native_windows.odin @@ -4,24 +4,8 @@ package glfw import win32 "core:sys/windows" -when GLFW_SHARED { - foreign import glfw { - "lib/glfw3dll.lib", - "system:user32.lib", - "system:gdi32.lib", - "system:shell32.lib", - } -} else { - foreign import glfw { - "lib/glfw3_mt.lib", - "system:user32.lib", - "system:gdi32.lib", - "system:shell32.lib", - } -} - @(default_calling_convention="c", link_prefix="glfw") -foreign glfw { +foreign { GetWin32Adapter :: proc(monitor: MonitorHandle) -> cstring --- GetWin32Monitor :: proc(monitor: MonitorHandle) -> cstring --- GetWin32Window :: proc(window: WindowHandle) -> win32.HWND --- diff --git a/vendor/glfw/types.odin b/vendor/glfw/types.odin index f537df7a2..cfe810fe5 100644 --- a/vendor/glfw/types.odin +++ b/vendor/glfw/types.odin @@ -11,6 +11,8 @@ GammaRamp :: glfw.GammaRamp Image :: glfw.Image GamepadState :: glfw.GamepadState +Allocator :: glfw.Allocator + /*** Procedure type declarations ***/ WindowIconifyProc :: glfw.WindowIconifyProc WindowRefreshProc :: glfw.WindowRefreshProc @@ -34,3 +36,7 @@ CursorEnterProc :: glfw.CursorEnterProc JoystickProc :: glfw.JoystickProc ErrorProc :: glfw.ErrorProc + +AllocateProc :: glfw.AllocateProc +ReallocateProc :: glfw.ReallocateProc +DeallocateProc :: glfw.DeallocateProc diff --git a/vendor/glfw/wrapper.odin b/vendor/glfw/wrapper.odin index 6ef46c183..fa9329aa7 100644 --- a/vendor/glfw/wrapper.odin +++ b/vendor/glfw/wrapper.odin @@ -8,6 +8,10 @@ Terminate :: glfw.Terminate InitHint :: glfw.InitHint +InitAllocator :: glfw.InitAllocator + +InitVulkanLoader :: glfw.InitVulkanLoader + GetVersion :: proc "c" () -> (major, minor, rev: c.int) { glfw.GetVersion(&major, &minor, &rev) return @@ -121,6 +125,7 @@ GetKeyName :: proc "c" (key, scancode: c.int) -> string { return string(glfw.GetKeyName(key, scancode)) } SetWindowShouldClose :: glfw.SetWindowShouldClose +GetWindowTitle :: glfw.GetWindowTitle JoystickPresent :: glfw.JoystickPresent GetJoystickName :: proc "c" (joy: c.int) -> string { return string(glfw.GetJoystickName(joy)) @@ -237,6 +242,8 @@ SetJoystickCallback :: glfw.SetJoystickCallback SetErrorCallback :: glfw.SetErrorCallback +GetPlatform :: glfw.GetPlatform +PlatformSupported :: glfw.PlatformSupported // Used by vendor:OpenGL gl_set_proc_address :: proc(p: rawptr, name: cstring) { diff --git a/vendor/raylib/raymath.odin b/vendor/raylib/raymath.odin index beeda7989..9682ffe4f 100644 --- a/vendor/raylib/raymath.odin +++ b/vendor/raylib/raymath.odin @@ -159,7 +159,7 @@ Vector2Transform :: proc "c" (v: Vector2, m: Matrix) -> Vector2 { // Calculate linear interpolation between two vectors @(require_results, deprecated="Prefer = linalg.lerp(v1, v2, amount)") Vector2Lerp :: proc "c" (v1, v2: Vector2, amount: f32) -> Vector2 { - return linalg.lerp(v1, v2, amount) + return linalg.lerp(v1, v2, Vector2(amount)) } // Calculate reflected vector to normal @(require_results, deprecated="Prefer = linalg.reflect(v, normal)") @@ -405,7 +405,7 @@ Vector3Transform :: proc "c" (v: Vector3, m: Matrix) -> Vector3 { // Calculate linear interpolation between two vectors @(require_results, deprecated="Prefer = linalg.lerp(v1, v2, amount)") Vector3Lerp :: proc "c" (v1, v2: Vector3, amount: f32) -> Vector3 { - return linalg.lerp(v1, v2, amount) + return linalg.lerp(v1, v2, Vector3(amount)) } // Calculate reflected vector to normal @(require_results, deprecated="Prefer = linalg.reflect(v, normal)")