diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 2c9dc30ae..d3f14ca9e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -124,13 +124,13 @@ jobs: build_macos: name: MacOS Build if: github.repository == 'odin-lang/Odin' - runs-on: macos-13 + runs-on: macos-14 # Intel machine steps: - uses: actions/checkout@v4 - name: Download LLVM and setup PATH run: | brew update - brew install llvm@20 dylibbundler lld + brew install llvm@20 dylibbundler lld@20 - name: build odin # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to @@ -169,7 +169,7 @@ jobs: - name: Download LLVM and setup PATH run: | brew update - brew install llvm@20 dylibbundler lld + brew install llvm@20 dylibbundler lld@20 - name: build odin # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to diff --git a/.gitignore b/.gitignore index be3e78954..4efb5d73e 100644 --- a/.gitignore +++ b/.gitignore @@ -302,3 +302,9 @@ misc/featuregen/featuregen .cache/ .clangd compile_commands.json + +# Dev cmake helpers +build/ +cmake-build*/ +CMakeLists.txt +sandbox/ diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 2d940cf67..dd508180d 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -374,10 +374,11 @@ objc_selector :: struct{} objc_class :: struct{} objc_ivar :: struct{} -objc_id :: ^objc_object -objc_SEL :: ^objc_selector -objc_Class :: ^objc_class -objc_Ivar :: ^objc_ivar +objc_id :: ^objc_object +objc_SEL :: ^objc_selector +objc_Class :: ^objc_class +objc_Ivar :: ^objc_ivar +objc_instancetype :: distinct objc_id objc_find_selector :: proc($name: string) -> objc_SEL --- objc_register_selector :: proc($name: string) -> objc_SEL --- @@ -385,6 +386,7 @@ objc_find_class :: proc($name: string) -> objc_Class --- objc_register_class :: proc($name: string) -> objc_Class --- objc_ivar_get :: proc(self: ^$T) -> ^$U --- objc_block :: proc(invoke: $T, ..any) -> ^Objc_Block(T) where type_is_proc(T) --- +objc_super :: proc(obj: ^$T) -> ^$U where type_is_subtype_of(T, objc_object) && type_is_subtype_of(U, objc_object) --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 478a3d307..71e560700 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -636,6 +636,8 @@ _cleanup_runtime_contextless :: proc "contextless" () { ///////////////////////////// +// type_info_base returns the base-type of a `^Type_Info` stripping the `distinct`ness from the first level +@(require_results) type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { if info == nil { return nil @@ -652,6 +654,10 @@ type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { } +// type_info_core returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR +// returns the backing integer type of an enum or bit_set `^Type_Info`. +// This is also aliased as `type_info_base_without_enum` +@(require_results) type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { if info == nil { return nil @@ -668,6 +674,10 @@ type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { } return base } + +// type_info_base_without_enum returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR +// returns the backing integer type of an enum or bit_set `^Type_Info`. +// This is also aliased as `type_info_core` type_info_base_without_enum :: type_info_core __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check { @@ -684,15 +694,23 @@ __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check } when !ODIN_NO_RTTI { + // typeid_base returns the base-type of a `typeid` stripping the `distinct`ness from the first level typeid_base :: proc "contextless" (id: typeid) -> typeid { ti := type_info_of(id) ti = type_info_base(ti) return ti.id } + // typeid_core returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR + // returns the backing integer type of an enum or bit_set `typeid`. + // This is also aliased as `typeid_base_without_enum` typeid_core :: proc "contextless" (id: typeid) -> typeid { ti := type_info_core(type_info_of(id)) return ti.id } + + // typeid_base_without_enum returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR + // returns the backing integer type of an enum or bit_set `typeid`. + // This is also aliased as `typeid_core` typeid_base_without_enum :: typeid_core } @@ -708,11 +726,15 @@ default_logger_proc :: proc(data: rawptr, level: Logger_Level, text: string, opt // Nothing } +// Returns the default logger used by `context.logger` +@(require_results) default_logger :: proc() -> Logger { return Logger{default_logger_proc, nil, Logger_Level.Debug, nil} } +// Returns the default `context` +@(require_results) default_context :: proc "contextless" () -> Context { c: Context __init_context(&c) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index d63d9e72a..9f3fcbf9a 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -62,6 +62,8 @@ when !NO_DEFAULT_TEMP_ALLOCATOR { } } +// Initializes the global temporary allocator used as the default `context.temp_allocator`. +// This is ignored when `NO_DEFAULT_TEMP_ALLOCATOR` is true. @(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR) init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) { when !NO_DEFAULT_TEMP_ALLOCATOR { @@ -564,6 +566,7 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a return } +// `append_elem` appends an element to the end of a dynamic array. @builtin append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { @@ -575,6 +578,9 @@ append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller } } +// `non_zero_append_elem` appends an element to the end of a dynamic array, without zeroing any reserved memory +// +// Note: Prefer using the procedure group `non_zero_append @builtin non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { @@ -613,6 +619,9 @@ _append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, return arg_len, err } +// `append_elems` appends `args` to the end of a dynamic array. +// +// Note: Prefer using the procedure group `append`. @builtin append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { @@ -624,6 +633,9 @@ append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #ca } } +// `non_zero_append_elems` appends `args` to the end of a dynamic array, without zeroing any reserved memory +// +// Note: Prefer using the procedure group `non_zero_append @builtin non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { @@ -640,10 +652,16 @@ _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_ze return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg)) } +// `append_elem_string` appends a string to the end of a dynamic array of bytes +// +// Note: Prefer using the procedure group `append`. @builtin append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elem_string(array, arg, true, loc) } +// `non_zero_append_elem_string` appends a string to the end of a dynamic array of bytes, without zeroing any reserved memory +// +// Note: Prefer using the procedure group `non_zero_append`. @builtin non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elem_string(array, arg, false, loc) @@ -651,6 +669,8 @@ non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, l // The append_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type +// +// Note: Prefer using the procedure group `append`. @builtin append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { n_arg: int @@ -665,7 +685,8 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_ } // The append built-in procedure appends elements to the end of a dynamic array -@builtin append :: proc{ +@builtin +append :: proc{ append_elem, append_elems, append_elem_string, @@ -674,7 +695,8 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_ append_soa_elems, } -@builtin non_zero_append :: proc{ +@builtin +non_zero_append :: proc{ non_zero_append_elem, non_zero_append_elems, non_zero_append_elem_string, @@ -684,6 +706,8 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_ } +// `append_nothing` appends an empty value to a dynamic array. It returns `1, nil` if successful, and `0, err` when it was not possible, +// whatever `err` happens to be. @builtin append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { @@ -695,6 +719,7 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i } +// `inject_at_elem` injects an element in a dynamic array at a specified index and moves the previous elements after that index "across" @builtin inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { when !ODIN_NO_BOUNDS_CHECK { @@ -716,6 +741,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas return } +// `inject_at_elems` injects multiple elements in a dynamic array at a specified index and moves the previous elements after that index "across" @builtin inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { when !ODIN_NO_BOUNDS_CHECK { @@ -742,6 +768,7 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca return } +// `inject_at_elem_string` injects a string into a dynamic array at a specified index and moves the previous elements after that index "across" @builtin inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { when !ODIN_NO_BOUNDS_CHECK { @@ -766,10 +793,13 @@ inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, ar return } +// `inject_at` injects something into a dynamic array at a specified index and moves the previous elements after that index "across" @builtin inject_at :: proc{inject_at_elem, inject_at_elems, inject_at_elem_string} +// `assign_at_elem` assigns a value at a given index. If the requested index is smaller than the current +// size of the dynamic array, it will attempt to `resize` the a new length of `index+1` and then assign as `index`. @builtin assign_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if index < len(array) { @@ -784,6 +814,8 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc } +// `assign_at_elems` assigns a values at a given index. If the requested index is smaller than the current +// size of the dynamic array, it will attempt to `resize` the a new length of `index+len(args)` and then assign as `index`. @builtin assign_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { new_size := index + len(args) @@ -800,7 +832,8 @@ assign_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca return } - +// `assign_at_elem_string` assigns a string at a given index. If the requested index is smaller than the current +// size of the dynamic array, it will attempt to `resize` the a new length of `index+len(arg)` and then assign as `index`. @builtin assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { new_size := index + len(arg) @@ -817,7 +850,14 @@ assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, ar return } -@builtin assign_at :: proc{assign_at_elem, assign_at_elems, assign_at_elem_string} +// `assign_at` assigns a value at a given index. If the requested index is smaller than the current +// size of the dynamic array, it will attempt to `resize` the a new length of `index+size_needed` and then assign as `index`. +@builtin +assign_at :: proc{ + assign_at_elem, + assign_at_elems, + assign_at_elem_string, +} @@ -834,6 +874,8 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). // +// When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`). +// // Note: Prefer the procedure group `reserve`. _reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { if a == nil { @@ -868,11 +910,21 @@ _reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_e return nil } +// `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). +// +// When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`). +// +// Note: Prefer the procedure group `reserve`. @builtin reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, true, loc) } +// `non_zero_reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). +// +// When a memory resize allocation is required, the memory will be asked to not be zeroed (i.e. it calls `non_zero_mem_resize`). +// +// Note: Prefer the procedure group `non_zero_reserve`. @builtin non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, false, loc) @@ -921,28 +973,33 @@ _resize_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_el // `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). // +// When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`). +// // Note: Prefer the procedure group `resize` @builtin resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc) } +// `non_zero_resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). +// +// When a memory resize allocation is required, the memory will be asked to not be zeroed (i.e. it calls `non_zero_mem_resize`). +// +// Note: Prefer the procedure group `non_zero_resize` @builtin non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc) } -/* - Shrinks the capacity of a dynamic array down to the current length, or the given capacity. - - If `new_cap` is negative, then `len(array)` is used. - - Returns false if `cap(array) < new_cap`, or the allocator report failure. - - If `len(array) < new_cap`, then `len(array)` will be left unchanged. - - Note: Prefer the procedure group `shrink` -*/ +// Shrinks the capacity of a dynamic array down to the current length, or the given capacity. +// +// If `new_cap` is negative, then `len(array)` is used. +// +// Returns false if `cap(array) < new_cap`, or the allocator report failure. +// +// If `len(array) < new_cap`, then `len(array)` will be left unchanged. +// +// Note: Prefer the procedure group `shrink` shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc) } @@ -1023,6 +1080,7 @@ map_entry :: proc(m: ^$T/map[$K]$V, key: K, loc := #caller_location) -> (key_ptr } +// `card` returns the number of bits that are set in a bit_set—its cardinality @builtin card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int { return int(intrinsics.count_ones(transmute(intrinsics.type_bit_set_underlying_type(S))s)) @@ -1030,6 +1088,10 @@ card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int { +// Evaluates the condition and panics the program iff the condition is false. +// This uses the `context.assertion_failure_procedure` to assert. +// +// This routine will be ignored when `ODIN_DISABLE_ASSERT` is true. @builtin @(disabled=ODIN_DISABLE_ASSERT) assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { @@ -1050,9 +1112,9 @@ assert :: proc(condition: bool, message := #caller_expression(condition), loc := } } -// Evaluates the condition and aborts the program iff the condition is -// false. This routine ignores `ODIN_DISABLE_ASSERT`, and will always -// execute. +// Evaluates the condition and panics the program iff the condition is false. +// This uses the `context.assertion_failure_procedure` to assert. +// This routine ignores `ODIN_DISABLE_ASSERT`, and will always execute. @builtin ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { if !condition { @@ -1068,6 +1130,8 @@ ensure :: proc(condition: bool, message := #caller_expression(condition), loc := } } +// Panics the program with a message. +// This uses the `context.assertion_failure_procedure` to panic. @builtin panic :: proc(message: string, loc := #caller_location) -> ! { p := context.assertion_failure_proc @@ -1077,6 +1141,8 @@ panic :: proc(message: string, loc := #caller_location) -> ! { p("panic", message, loc) } +// Panics the program with a message to indicate something has yet to be implemented. +// This uses the `context.assertion_failure_procedure` to assert. @builtin unimplemented :: proc(message := "", loc := #caller_location) -> ! { p := context.assertion_failure_proc @@ -1086,7 +1152,10 @@ unimplemented :: proc(message := "", loc := #caller_location) -> ! { p("not yet implemented", message, loc) } - +// Evaluates the condition and panics the program iff the condition is false. +// This uses the `default_assertion_contextless_failure_proc` to assert. +// +// This routine will be ignored when `ODIN_DISABLE_ASSERT` is true. @builtin @(disabled=ODIN_DISABLE_ASSERT) assert_contextless :: proc "contextless" (condition: bool, message := #caller_expression(condition), loc := #caller_location) { @@ -1103,6 +1172,8 @@ assert_contextless :: proc "contextless" (condition: bool, message := #caller_ex } } +// Evaluates the condition and panics the program iff the condition is false. +// This uses the `default_assertion_contextless_failure_proc` to assert. @builtin ensure_contextless :: proc "contextless" (condition: bool, message := #caller_expression(condition), loc := #caller_location) { if !condition { @@ -1114,11 +1185,15 @@ ensure_contextless :: proc "contextless" (condition: bool, message := #caller_ex } } +// Panics the program with a message to indicate something has yet to be implemented. +// This uses the `default_assertion_contextless_failure_proc` to assert. @builtin panic_contextless :: proc "contextless" (message: string, loc := #caller_location) -> ! { default_assertion_contextless_failure_proc("panic", message, loc) } +// Panics the program with a message. +// This uses the `default_assertion_contextless_failure_proc` to assert. @builtin unimplemented_contextless :: proc "contextless" (message := "", loc := #caller_location) -> ! { default_assertion_contextless_failure_proc("not yet implemented", message, loc) diff --git a/base/runtime/default_allocators_nil.odin b/base/runtime/default_allocators_nil.odin index f5ebad28f..2f2edb40a 100644 --- a/base/runtime/default_allocators_nil.odin +++ b/base/runtime/default_allocators_nil.odin @@ -23,6 +23,14 @@ nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, return nil, .None } +// nil_allocator returns an allocator which will return `nil` for any result. +// * `.Alloc`, `.Alloc_Non_Zero`, `.Resize`, `.Resize_Non_Zeroed` will return `nil, .Out_Of_Memory` +// * `.Free` will return `nil, .None` +// * `.Free_All` will return `nil, .Mode_Not_Implemented` +// * `.Query_Features`, `.Query_Info` will return `nil, .Mode_Not_Implemented` +// +// This is extremely useful for creating a dynamic array from a buffer which does not nothing +// on a resize/reserve beyond the originally allocated memory. @(require_results) nil_allocator :: proc "contextless" () -> Allocator { return Allocator{ @@ -73,6 +81,9 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, return nil, nil } +// panic_allocator returns an allocator which will panic for any non-zero-sized allocation or `query_info` +// +// This is extremely useful for to check when something does a memory operation when it should not, and thus panic. @(require_results) panic_allocator :: proc() -> Allocator { return Allocator{ diff --git a/base/runtime/default_temporary_allocator.odin b/base/runtime/default_temporary_allocator.odin index ec3c6ad8c..2017570bb 100644 --- a/base/runtime/default_temporary_allocator.odin +++ b/base/runtime/default_temporary_allocator.odin @@ -4,6 +4,7 @@ DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKIN NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_DEFAULT_TO_NIL_ALLOCATOR when NO_DEFAULT_TEMP_ALLOCATOR { + // `Default_Temp_Allocator` is a `nil_allocator` when `NO_DEFAULT_TEMP_ALLOCATOR` is `true`. Default_Temp_Allocator :: struct {} default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {} @@ -20,6 +21,11 @@ when NO_DEFAULT_TEMP_ALLOCATOR { default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { } } else { + // `Default_Temp_Allocator` is an `Arena` based type of allocator. See `runtime.Arena` for its implementation. + // The default `context.temp_allocator` is typically called with `free_all(context.temp_allocator)` once per "frame-loop" + // to prevent it from "leaking" memory. + // + // Note: `Default_Temp_Allocator` is a `nil_allocator` when `NO_DEFAULT_TEMP_ALLOCATOR` is `true`. Default_Temp_Allocator :: struct { arena: Arena, } diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin index d176f0f63..0ffe68e49 100644 --- a/base/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -15,16 +15,25 @@ objc_SEL :: ^intrinsics.objc_selector objc_Ivar :: ^intrinsics.objc_ivar objc_BOOL :: bool +objc_super :: struct { + receiver: objc_id, + super_class: objc_Class, +} objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id foreign ObjC { sel_registerName :: proc "c" (name: cstring) -> objc_SEL --- - objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- - objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- - objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- - objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- + objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- + objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- + objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- + objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- + + // See: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-abi.h#L111 + objc_msgSendSuper2 :: proc "c" (super: rawptr, op: objc_SEL, #c_vararg args: ..any) -> objc_id --- + objc_msgSendSuper2_stret :: proc "c" (super: ^objc_super, op: objc_SEL, #c_vararg args: ..any) --- + objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- @@ -33,6 +42,7 @@ foreign ObjC { class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL --- class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar --- class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint --- + class_getSuperclass :: proc "c" (cls : objc_Class) -> objc_Class --- ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr --- object_getClass :: proc "c" (obj: objc_id) -> objc_Class --- } diff --git a/core/bufio/reader.odin b/core/bufio/reader.odin index b78cac6e1..4aa1e6c5d 100644 --- a/core/bufio/reader.odin +++ b/core/bufio/reader.odin @@ -29,6 +29,7 @@ MIN_READ_BUFFER_SIZE :: 16 @(private) DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128 +// reader_init initializes using an `allocator` reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) { size := size size = max(size, MIN_READ_BUFFER_SIZE) @@ -37,6 +38,7 @@ reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, all b.buf = make([]byte, size, allocator, loc) } +// reader_init initializes using a user provided bytes buffer `buf` reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) { reader_reset(b, rd) b.buf_allocator = {} @@ -49,10 +51,12 @@ reader_destroy :: proc(b: ^Reader) { b^ = {} } +// reader_size returns the number of bytes in the backing buffer reader_size :: proc(b: ^Reader) -> int { return len(b.buf) } +// reader_reset resets the read and write positions, and the error values reader_reset :: proc(b: ^Reader, r: io.Reader) { b.rd = r b.r, b.w = 0, 0 diff --git a/core/bufio/scanner.odin b/core/bufio/scanner.odin index ee2d5d1f6..27d29c685 100644 --- a/core/bufio/scanner.odin +++ b/core/bufio/scanner.odin @@ -46,6 +46,7 @@ DEFAULT_MAX_SCAN_TOKEN_SIZE :: 1<<16 @(private) _INIT_BUF_SIZE :: 4096 +// Initializes a Scanner buffer an allocator `buf_allocator` scanner_init :: proc(s: ^Scanner, r: io.Reader, buf_allocator := context.allocator) -> ^Scanner { s.r = r s.split = scan_lines @@ -53,6 +54,8 @@ scanner_init :: proc(s: ^Scanner, r: io.Reader, buf_allocator := context.allocat s.buf.allocator = buf_allocator return s } + +// Initializes a Scanner buffer a user provided bytes buffer `buf` scanner_init_with_buffer :: proc(s: ^Scanner, r: io.Reader, buf: []byte) -> ^Scanner { s.r = r s.split = scan_lines @@ -75,24 +78,27 @@ scanner_error :: proc(s: ^Scanner) -> Scanner_Error { return s._err } -// Returns the most recent token created by scanner_scan. +// Returns the most recent token created by 'scan'. // The underlying array may point to data that may be overwritten -// by another call to scanner_scan. +// by another call to 'scan'. // Treat the returned value as if it is immutable. scanner_bytes :: proc(s: ^Scanner) -> []byte { return s.token } -// Returns the most recent token created by scanner_scan. +// Returns the most recent token created by 'scan'. // The underlying array may point to data that may be overwritten -// by another call to scanner_scan. +// by another call to 'scan'. // Treat the returned value as if it is immutable. scanner_text :: proc(s: ^Scanner) -> string { return string(s.token) } -// scanner_scan advances the scanner -scanner_scan :: proc(s: ^Scanner) -> bool { +// scanner_scan is an alias of scan +scanner_scan :: scan + +// scan advances the Scanner +scan :: proc(s: ^Scanner) -> bool { set_err :: proc(s: ^Scanner, err: Scanner_Error) { switch s._err { case nil, .EOF: @@ -229,6 +235,7 @@ scanner_scan :: proc(s: ^Scanner) -> bool { } } +// scan_bytes is a splitting procedure that returns each byte as a token scan_bytes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) { if at_eof && len(data) == 0 { return @@ -236,6 +243,10 @@ scan_bytes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, return 1, data[0:1], nil, false } +// scan_runes is a splitting procedure that returns each UTF-8 encoded rune as a token. +// The lsit of runes return is equivalent to that of iterating over a string in a 'for in' loop, meaning any +// erroneous UTF-8 encodings will be returned as U+FFFD. Unfortunately this means it is impossible for the "client" +// to know whether a U+FFFD is an expected replacement rune or an encoding of an error. scan_runes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) { if at_eof && len(data) == 0 { return @@ -264,7 +275,8 @@ scan_runes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, token = ERROR_RUNE return } - +// scan_words is a splitting procedure that returns each Unicode-space-separated word of text, excluding the surrounded spaces. +// It will never return return an empty string. scan_words :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) { is_space :: proc "contextless" (r: rune) -> bool { switch r { @@ -312,6 +324,8 @@ scan_words :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, return } +// scan_lines is a splitting procedure that returns each line of text stripping of any trailing newline and an optional preceding carriage return (\r?\n). +// A new line is allowed to be empty. scan_lines :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) { trim_carriage_return :: proc "contextless" (data: []byte) -> []byte { if len(data) > 0 && data[len(data)-1] == '\r' { diff --git a/core/bufio/writer.odin b/core/bufio/writer.odin index 5edd3dd6b..9c73baf87 100644 --- a/core/bufio/writer.odin +++ b/core/bufio/writer.odin @@ -19,6 +19,7 @@ Writer :: struct { } +// Initialized a Writer with an `allocator` writer_init :: proc(b: ^Writer, wr: io.Writer, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) { size := size size = max(size, MIN_READ_BUFFER_SIZE) @@ -27,6 +28,7 @@ writer_init :: proc(b: ^Writer, wr: io.Writer, size: int = DEFAULT_BUF_SIZE, all b.buf = make([]byte, size, allocator) } +// Initialized a Writer with a user provided buffer `buf` writer_init_with_buf :: proc(b: ^Writer, wr: io.Writer, buf: []byte) { writer_reset(b, wr) b.buf_allocator = {} diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 71b6ef70c..6e872e757 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -134,8 +134,13 @@ equal_fold :: proc(u, v: []byte) -> bool { return false } - // TODO(bill): Unicode folding - + r := unicode.simple_fold(sr) + for r != sr && r < tr { + r = unicode.simple_fold(sr) + } + if r == tr { + continue loop + } return false } diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index cdb00a354..e563c326a 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -176,7 +176,11 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: return .Unsupported_Type case runtime.Type_Info_Pointer: - return .Unsupported_Type + if v.id == typeid_of(Null) { + io.write_string(w, "null") or_return + } else { + return .Unsupported_Type + } case runtime.Type_Info_Multi_Pointer: return .Unsupported_Type diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index c23feddce..3c37d4d25 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -164,25 +164,25 @@ orthogonal :: proc{vector2_orthogonal, vector3_orthogonal} @(require_results) vector4_srgb_to_linear_f16 :: proc "contextless" (col: Vector4f16) -> Vector4f16 { - r := math.pow(col.x, 2.2) - g := math.pow(col.y, 2.2) - b := math.pow(col.z, 2.2) + r := math.pow((col.x + 0.055) / 1.055, 2.4) if col.x > 0.04045 else col.x / 12.92 + g := math.pow((col.y + 0.055) / 1.055, 2.4) if col.y > 0.04045 else col.y / 12.92 + b := math.pow((col.z + 0.055) / 1.055, 2.4) if col.z > 0.04045 else col.z / 12.92 a := col.w return {r, g, b, a} } @(require_results) vector4_srgb_to_linear_f32 :: proc "contextless" (col: Vector4f32) -> Vector4f32 { - r := math.pow(col.x, 2.2) - g := math.pow(col.y, 2.2) - b := math.pow(col.z, 2.2) + r := math.pow((col.x + 0.055) / 1.055, 2.4) if col.x > 0.04045 else col.x / 12.92 + g := math.pow((col.y + 0.055) / 1.055, 2.4) if col.y > 0.04045 else col.y / 12.92 + b := math.pow((col.z + 0.055) / 1.055, 2.4) if col.z > 0.04045 else col.z / 12.92 a := col.w return {r, g, b, a} } @(require_results) vector4_srgb_to_linear_f64 :: proc "contextless" (col: Vector4f64) -> Vector4f64 { - r := math.pow(col.x, 2.2) - g := math.pow(col.y, 2.2) - b := math.pow(col.z, 2.2) + r := math.pow((col.x + 0.055) / 1.055, 2.4) if col.x > 0.04045 else col.x / 12.92 + g := math.pow((col.y + 0.055) / 1.055, 2.4) if col.y > 0.04045 else col.y / 12.92 + b := math.pow((col.z + 0.055) / 1.055, 2.4) if col.z > 0.04045 else col.z / 12.92 a := col.w return {r, g, b, a} } @@ -192,70 +192,55 @@ vector4_srgb_to_linear :: proc{ vector4_srgb_to_linear_f64, } +@(require_results) +vector3_srgb_to_linear_f16 :: proc "contextless" (col: Vector3f16) -> Vector3f16 { + r := math.pow((col.x + 0.055) / 1.055, 2.4) if col.x > 0.04045 else col.x / 12.92 + g := math.pow((col.y + 0.055) / 1.055, 2.4) if col.y > 0.04045 else col.y / 12.92 + b := math.pow((col.z + 0.055) / 1.055, 2.4) if col.z > 0.04045 else col.z / 12.92 + return {r, g, b} +} +@(require_results) +vector3_srgb_to_linear_f32 :: proc "contextless" (col: Vector3f32) -> Vector3f32 { + r := math.pow((col.x + 0.055) / 1.055, 2.4) if col.x > 0.04045 else col.x / 12.92 + g := math.pow((col.y + 0.055) / 1.055, 2.4) if col.y > 0.04045 else col.y / 12.92 + b := math.pow((col.z + 0.055) / 1.055, 2.4) if col.z > 0.04045 else col.z / 12.92 + return {r, g, b} +} +@(require_results) +vector3_srgb_to_linear_f64 :: proc "contextless" (col: Vector3f64) -> Vector3f64 { + r := math.pow((col.x + 0.055) / 1.055, 2.4) if col.x > 0.04045 else col.x / 12.92 + g := math.pow((col.y + 0.055) / 1.055, 2.4) if col.y > 0.04045 else col.y / 12.92 + b := math.pow((col.z + 0.055) / 1.055, 2.4) if col.z > 0.04045 else col.z / 12.92 + return {r, g, b} +} +vector3_srgb_to_linear :: proc{ + vector3_srgb_to_linear_f16, + vector3_srgb_to_linear_f32, + vector3_srgb_to_linear_f64, +} + @(require_results) vector4_linear_to_srgb_f16 :: proc "contextless" (col: Vector4f16) -> Vector4f16 { - a :: 2.51 - b :: 0.03 - c :: 2.43 - d :: 0.59 - e :: 0.14 - - x := col.x - y := col.y - z := col.z - - x = (x * (a * x + b)) / (x * (c * x + d) + e) - y = (y * (a * y + b)) / (y * (c * y + d) + e) - z = (z * (a * z + b)) / (z * (c * z + d) + e) - - x = math.pow(clamp(x, 0, 1), 1.0 / 2.2) - y = math.pow(clamp(y, 0, 1), 1.0 / 2.2) - z = math.pow(clamp(z, 0, 1), 1.0 / 2.2) + x := 1.055 * math.pow(col.x, 1.0 / 2.4) - 0.055 if col.x > 0.0031308 else 12.92 * col.x + y := 1.055 * math.pow(col.y, 1.0 / 2.4) - 0.055 if col.y > 0.0031308 else 12.92 * col.y + z := 1.055 * math.pow(col.z, 1.0 / 2.4) - 0.055 if col.z > 0.0031308 else 12.92 * col.z return {x, y, z, col.w} } @(require_results) vector4_linear_to_srgb_f32 :: proc "contextless" (col: Vector4f32) -> Vector4f32 { - a :: 2.51 - b :: 0.03 - c :: 2.43 - d :: 0.59 - e :: 0.14 - - x := col.x - y := col.y - z := col.z - - x = (x * (a * x + b)) / (x * (c * x + d) + e) - y = (y * (a * y + b)) / (y * (c * y + d) + e) - z = (z * (a * z + b)) / (z * (c * z + d) + e) - - x = math.pow(clamp(x, 0, 1), 1.0 / 2.2) - y = math.pow(clamp(y, 0, 1), 1.0 / 2.2) - z = math.pow(clamp(z, 0, 1), 1.0 / 2.2) + x := 1.055 * math.pow(col.x, 1.0 / 2.4) - 0.055 if col.x > 0.0031308 else 12.92 * col.x + y := 1.055 * math.pow(col.y, 1.0 / 2.4) - 0.055 if col.y > 0.0031308 else 12.92 * col.y + z := 1.055 * math.pow(col.z, 1.0 / 2.4) - 0.055 if col.z > 0.0031308 else 12.92 * col.z return {x, y, z, col.w} } @(require_results) vector4_linear_to_srgb_f64 :: proc "contextless" (col: Vector4f64) -> Vector4f64 { - a :: 2.51 - b :: 0.03 - c :: 2.43 - d :: 0.59 - e :: 0.14 - - x := col.x - y := col.y - z := col.z - - x = (x * (a * x + b)) / (x * (c * x + d) + e) - y = (y * (a * y + b)) / (y * (c * y + d) + e) - z = (z * (a * z + b)) / (z * (c * z + d) + e) - - x = math.pow(clamp(x, 0, 1), 1.0 / 2.2) - y = math.pow(clamp(y, 0, 1), 1.0 / 2.2) - z = math.pow(clamp(z, 0, 1), 1.0 / 2.2) + x := 1.055 * math.pow(col.x, 1.0 / 2.4) - 0.055 if col.x > 0.0031308 else 12.92 * col.x + y := 1.055 * math.pow(col.y, 1.0 / 2.4) - 0.055 if col.y > 0.0031308 else 12.92 * col.y + z := 1.055 * math.pow(col.z, 1.0 / 2.4) - 0.055 if col.z > 0.0031308 else 12.92 * col.z return {x, y, z, col.w} } @@ -265,6 +250,36 @@ vector4_linear_to_srgb :: proc{ vector4_linear_to_srgb_f64, } +@(require_results) +vector3_linear_to_srgb_f16 :: proc "contextless" (col: Vector3f16) -> Vector3f16 { + x := 1.055 * math.pow(col.x, 1.0 / 2.4) - 0.055 if col.x > 0.0031308 else 12.92 * col.x + y := 1.055 * math.pow(col.y, 1.0 / 2.4) - 0.055 if col.y > 0.0031308 else 12.92 * col.y + z := 1.055 * math.pow(col.z, 1.0 / 2.4) - 0.055 if col.z > 0.0031308 else 12.92 * col.z + + return {x, y, z} +} +@(require_results) +vector3_linear_to_srgb_f32 :: proc "contextless" (col: Vector3f32) -> Vector3f32 { + x := 1.055 * math.pow(col.x, 1.0 / 2.4) - 0.055 if col.x > 0.0031308 else 12.92 * col.x + y := 1.055 * math.pow(col.y, 1.0 / 2.4) - 0.055 if col.y > 0.0031308 else 12.92 * col.y + z := 1.055 * math.pow(col.z, 1.0 / 2.4) - 0.055 if col.z > 0.0031308 else 12.92 * col.z + + return {x, y, z} +} +@(require_results) +vector3_linear_to_srgb_f64 :: proc "contextless" (col: Vector3f64) -> Vector3f64 { + x := 1.055 * math.pow(col.x, 1.0 / 2.4) - 0.055 if col.x > 0.0031308 else 12.92 * col.x + y := 1.055 * math.pow(col.y, 1.0 / 2.4) - 0.055 if col.y > 0.0031308 else 12.92 * col.y + z := 1.055 * math.pow(col.z, 1.0 / 2.4) - 0.055 if col.z > 0.0031308 else 12.92 * col.z + + return {x, y, z} +} +vector3_linear_to_srgb :: proc{ + vector3_linear_to_srgb_f16, + vector3_linear_to_srgb_f32, + vector3_linear_to_srgb_f64, +} + @(require_results) vector4_hsl_to_rgb_f16 :: proc "contextless" (h, s, l: f16, a: f16 = 1) -> Vector4f16 { diff --git a/core/math/math.odin b/core/math/math.odin index b99a97bc1..b399b4dac 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -402,7 +402,12 @@ remap :: proc "contextless" (old_value, old_min, old_max, new_min, new_max: $T) if old_range == 0 { return new_range / 2 } - return ((old_value - old_min) / old_range) * new_range + new_min + + when intrinsics.type_is_integer(T) { + return (((old_value - old_min)) * new_range) / old_range + new_min + } else { + return ((old_value - old_min) / old_range) * new_range + new_min + } } @(require_results) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 8b185f419..1c9927843 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -199,7 +199,7 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: buf: [32]u8 copy(buf[:], PROC_FD_PATH) - strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) + strconv.write_int(buf[len(PROC_FD_PATH):], i64(fd), 10) if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' { delete(fullpath, allocator) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 15d230820..f0f9b401c 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -908,7 +908,7 @@ _dup :: proc(fd: Handle) -> (Handle, Error) { @(require_results) absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { buf : [256]byte - fd_str := strconv.itoa( buf[:], cast(int)fd ) + fd_str := strconv.write_int( buf[:], cast(i64)fd, 10 ) procfs_path := strings.concatenate( []string{ "/proc/self/fd/", fd_str } ) defer delete(procfs_path) diff --git a/core/reflect/doc.odin b/core/reflect/doc.odin new file mode 100644 index 000000000..1c94f3dbc --- /dev/null +++ b/core/reflect/doc.odin @@ -0,0 +1,51 @@ +// Package reflect provides utility procedures and types to perform runtime type introspection/reflection (RTTI). +// +// WARNING! THIS IS ADVANCED BEHAVIOUR FOR ODIN! THIS SHOULD NOT BE USED BY BEGINNERS TO ODIN! +// +// This package is only to be used by individuals who know exactly how the RTTI works as well as EXACTLY how `any` works. +// Especially since `any` can be unintuitive in its use to many, it can be dangerous to use. It is highly recommend that you **do not** +// use `any` unless you know exactly what you are doing. +// +// RTTI is an extremely powerful tool which should only be used when absolutely necessary (such runtime-type-safe formatted printing). +// +// ## The Type System of Odin +// +// It is important to understand how the type systems works in Odin before using any RTTI. A good example of this is Odin's `distinct` type system. +// In Odin, `distinct` types are represented by `Type_Info_Named`. A named struct is a `Type_Info_Named` which then points to a `Type_Info_Struct`. +// This means you must use something like `type_info_base` to restrict the `Type_Info_Named` aspect and get the base-type directly. Doing a type-assertion +// on the variant will not work as (incorrectly) expected without doing this. +// +// ## Advanced Information of How `any` Works +// +// An overview of how `any` works: +// +// An `any` type can reference any data type. It is functionally equivalent to `struct {data: rawptr, id: typeid}` with extra semantics on +// how assignment and type assertion works. +// +// This is commonly used to construct runtime-type-safe printing, such as in `core:fmt`. +// The use of `any` outside of this is heavily discourage and should be only used by people who FULLY understand its semantics. +// +// The `any` value is only valid as long as the underlying data is still valid. Passing a literal to an `any` will allocate the literal in +// the current stack frame. +// +// Example: +// x: int = 123 +// a: any = x +// // equivalent to +// a: any +// a.data = &x +// a.id = typeid_of(type_of(x)) +// // With literals +// v: any = 123 +// // equivalent to +// v: any +// _tmp: int = 123 +// v.data = &_tmp +// v.id = typeid_of(type_of(_tmp)) +// +// +// `any` is a topologically-dual to a `union` in terms of its usage. Both support assignments of differing types +// (`any` being open to any type, `union` being closed to a specific set of types), type assertions (`x.(T)`), and `switch in`. +// The main internal difference is how the memory is stored. `any` being open is a pointer+typeid, a `union` +// is a blob+tag. A `union` does not need to store a `typeid` because it is a closed ABI-consistent set of variant types. +package reflect \ No newline at end of file diff --git a/core/reflect/iterator.odin b/core/reflect/iterator.odin index 090fe04cc..e96019f68 100644 --- a/core/reflect/iterator.odin +++ b/core/reflect/iterator.odin @@ -2,6 +2,10 @@ package reflect import "base:runtime" +// An iterator to dynamically iterate across something that is array-like (or pointer-to-array-like) +// Example: +// it: int // used as a tracking value +// for elem, idx in iterate_array(any_array_val, &it) { ... } @(require_results) iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { if val == nil || it == nil { @@ -45,6 +49,10 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { return } +// An iterator to dynamically iterate across map (or pointer-to-map) +// Example: +// it: int // used as a tracking value +// for key, val in iterate_map(any_map_val, &it) { ... } @(require_results) iterate_map :: proc(val: any, it: ^int) -> (key, value: any, ok: bool) { if val == nil || it == nil { diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index b3315a0c3..5fbff08c2 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -70,6 +70,7 @@ Type_Kind :: enum { } +// type_kind returns a enum `Type_Kind` to state what kind of type a typeid is @(require_results) type_kind :: proc(T: typeid) -> Type_Kind { ti := type_info_of(T) @@ -108,31 +109,51 @@ type_kind :: proc(T: typeid) -> Type_Kind { return .Invalid } -// TODO(bill): Better name +// Returns the `Type_Kind` of the base-type of a typeid. @(require_results) underlying_type_kind :: proc(T: typeid) -> Type_Kind { return type_kind(runtime.typeid_base(T)) } -// TODO(bill): Better name +// Returns the `Type_Kind` of the core-type of a typeid. See @(require_results) backing_type_kind :: proc(T: typeid) -> Type_Kind { return type_kind(runtime.typeid_core(T)) } +// type_info_base returns the base-type of a `^Type_Info` stripping the `distinct`ness from the first level type_info_base :: runtime.type_info_base + +// type_info_core returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR +// returns the backing integer type of an enum or bit_set `^Type_Info`. +// This is also aliased as `type_info_base_without_enum` type_info_core :: runtime.type_info_core + + +// type_info_base_without_enum returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR +// returns the backing integer type of an enum or bit_set `^Type_Info`. +// This is also aliased as `type_info_core` type_info_base_without_enum :: type_info_core when !ODIN_NO_RTTI { + // typeid_base returns the base-type of a `typeid` stripping the `distinct`ness from the first level typeid_base :: runtime.typeid_base + + // typeid_core returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR + // returns the backing integer type of an enum or bit_set `typeid`. + // This is also aliased as `typeid_base_without_enum` typeid_core :: runtime.typeid_core + + // typeid_base_without_enum returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR + // returns the backing integer type of an enum or bit_set `typeid`. + // This is also aliased as `typeid_core` typeid_base_without_enum :: typeid_core } +// any_base returns an `any` where the `typeid` has been replaced with the `base-type` equivalent @(require_results) any_base :: proc(v: any) -> any { v := v @@ -141,6 +162,8 @@ any_base :: proc(v: any) -> any { } return v } + +// any_core returns an `any` where the `typeid` has been replaced with the `core-type` equivalent @(require_results) any_core :: proc(v: any) -> any { v := v @@ -150,6 +173,20 @@ any_core :: proc(v: any) -> any { return v } +// typeid_elem returns a `typeid` of the element-type of a type if possible, otherwise it returns itself +// complex32 -> f16 +// complex64 -> f32 +// complex128 -> f64 +// quaternion64 -> f16 +// quaternion128 -> f32 +// quaternion256 -> f64 +// ^T -> T +// [^]T -> T +// #soa^T -> T +// [N]T -> T +// []T -> T +// [dynamic]T -> T +// #simd[N]T -> T @(require_results) typeid_elem :: proc(id: typeid) -> typeid { ti := type_info_of(id) @@ -160,11 +197,13 @@ typeid_elem :: proc(id: typeid) -> typeid { #partial switch v in ti.variant { case Type_Info_Complex: switch bits { + case 32: return f16 case 64: return f32 case 128: return f64 } case Type_Info_Quaternion: switch bits { + case 64: return f16 case 128: return f32 case 256: return f64 } @@ -181,6 +220,7 @@ typeid_elem :: proc(id: typeid) -> typeid { } +// returns the size of the type that the passed typeid represents @(require_results) size_of_typeid :: proc(T: typeid) -> int { if ti := type_info_of(T); ti != nil { @@ -189,6 +229,7 @@ size_of_typeid :: proc(T: typeid) -> int { return 0 } +// returns the alignment of the type that the passed typeid represents @(require_results) align_of_typeid :: proc(T: typeid) -> int { if ti := type_info_of(T); ti != nil { @@ -197,6 +238,7 @@ align_of_typeid :: proc(T: typeid) -> int { return 1 } +// Reinterprets the data stored at `v` as a slice of bytes @(require_results) as_bytes :: proc(v: any) -> []byte { if v != nil { @@ -206,11 +248,13 @@ as_bytes :: proc(v: any) -> []byte { return nil } +// Splits the data stored in `any` into its two components: `data` and `id` @(require_results) any_data :: #force_inline proc(v: any) -> (data: rawptr, id: typeid) { return v.data, v.id } +// Returns true if the `any` value is either `nil` or the data stored at the address is all zeroed @(require_results) is_nil :: proc(v: any) -> bool { if v == nil { @@ -228,6 +272,16 @@ is_nil :: proc(v: any) -> bool { return true } + +// Returns the length of the type that represents the `any` value, or returns 0 if not possible +// len(^T) -> len(T) +// len([N]T) -> N +// len(#simd[N]T) -> N +// len([]T) +// len([dynamic]T) +// len(map[K]V) +// len(string) or len(cstring) +// len(string16) or len(cstring16) @(require_results) length :: proc(val: any) -> int { if val == nil { return 0 } @@ -255,10 +309,19 @@ length :: proc(val: any) -> int { return runtime.map_len((^runtime.Raw_Map)(val.data)^) case Type_Info_String: - if a.is_cstring { - return len((^cstring)(val.data)^) - } else { - return (^runtime.Raw_String)(val.data).len + switch a.encoding { + case .UTF_8: + if a.is_cstring { + return len((^cstring)(val.data)^) + } else { + return (^runtime.Raw_String)(val.data).len + } + case .UTF_16: + if a.is_cstring { + return len((^cstring16)(val.data)^) + } else { + return (^runtime.Raw_String16)(val.data).len + } } case Type_Info_Simd_Vector: @@ -268,6 +331,12 @@ length :: proc(val: any) -> int { return 0 } +// Returns the capacity of the type that represents the `any` value, or returns 0 if not possible +// cap(^T) -> cap(T) +// cap([N]T) -> N +// cap(#simd[N]T) -> N +// cap([dynamic]T) +// cap(map[K]V) @(require_results) capacity :: proc(val: any) -> int { if val == nil { return 0 } @@ -299,6 +368,7 @@ capacity :: proc(val: any) -> int { } +// Dynamically indexes `any` as an indexable-type if possible. Returns `nil` if not possible @(require_results) index :: proc(val: any, i: int, loc := #caller_location) -> any { if val == nil { return nil } @@ -350,15 +420,25 @@ index :: proc(val: any, i: int, loc := #caller_location) -> any { case Type_Info_String: if a.is_cstring { return nil } - raw := (^runtime.Raw_String)(val.data) - runtime.bounds_check_error_loc(loc, i, raw.len) - offset := uintptr(size_of(u8) * i) - data := rawptr(uintptr(raw.data) + offset) - return any{data, typeid_of(u8)} + switch a.encoding { + case .UTF_8: + raw := (^runtime.Raw_String)(val.data) + runtime.bounds_check_error_loc(loc, i, raw.len) + offset := uintptr(size_of(u8) * i) + data := rawptr(uintptr(raw.data) + offset) + return any{data, typeid_of(u8)} + case .UTF_16: + raw := (^runtime.Raw_String16)(val.data) + runtime.bounds_check_error_loc(loc, i, raw.len) + offset := uintptr(size_of(u16) * i) + data := rawptr(uintptr(raw.data) + offset) + return any{data, typeid_of(u16)} + } } return nil } +// Dereferences `any` if it represents a pointer-based value (`^T -> T`) @(require_results) deref :: proc(val: any) -> any { if val != nil { @@ -375,20 +455,22 @@ deref :: proc(val: any) -> any { -// Struct_Tag represents the type of the string of a struct field +// `Struct_Tag` represents the type of the `string` of a struct field // -// Through convention, tags are the concatenation of optionally space separationed key:"value" pairs. +// Through convention, tags are the concatenation of optionally space-separated key:"value" pairs. // Each key is a non-empty string which contains no control characters other than space, quotes, and colon. Struct_Tag :: distinct string +// `Struct_Field` represents a information of a field of a struct Struct_Field :: struct { name: string, type: ^Type_Info, tag: Struct_Tag, - offset: uintptr, + offset: uintptr, // in bytes is_using: bool, } +// Returns a `Struct_Field` containing the information for a struct field of a typeid `T` at index `i` @(require_results) struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) @@ -404,6 +486,7 @@ struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { return } +// Returns a `Struct_Field` containing the information for a struct field by `name` of a typeid `T` @(require_results) struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) @@ -422,6 +505,10 @@ struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) { return } +// Returns an `any` of a struct field specified by name +// Example: +// v := struct_field_value_by_name(the_struct, "field_name") +// nested_value_through_using := struct_field_value_by_name(the_struct, "field_name", allow_using=true) @(require_results) struct_field_value_by_name :: proc(a: any, field: string, allow_using := false) -> any { if a == nil { return nil } @@ -452,6 +539,10 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false) return nil } +// Returns an `any` of a struct field specified by a `Struct_Field` +// Example: +// field := struct_field_value_by_name(the_struct, "field_name") +// value_by_field := struct_field_value(the_struct, field) @(require_results) struct_field_value :: proc(a: any, field: Struct_Field) -> any { if a == nil { return nil } @@ -461,6 +552,7 @@ struct_field_value :: proc(a: any, field: Struct_Field) -> any { } } +// Returns a `[]string` of the names of the struct fields of type `T` @(require_results) struct_field_names :: proc(T: typeid) -> []string { ti := runtime.type_info_base(type_info_of(T)) @@ -470,6 +562,7 @@ struct_field_names :: proc(T: typeid) -> []string { return nil } +// Returns a `[]^Type_Info` of the types of the struct fields of type `T` @(require_results) struct_field_types :: proc(T: typeid) -> []^Type_Info { ti := runtime.type_info_base(type_info_of(T)) @@ -480,6 +573,7 @@ struct_field_types :: proc(T: typeid) -> []^Type_Info { } +// Returns a `[]Struct_Tag` of the tags of the struct fields of type `T` @(require_results) struct_field_tags :: proc(T: typeid) -> []Struct_Tag { ti := runtime.type_info_base(type_info_of(T)) @@ -489,6 +583,7 @@ struct_field_tags :: proc(T: typeid) -> []Struct_Tag { return nil } +// Returns a `[]uintptr` of the offsets in bytes of the struct fields of type `T` @(require_results) struct_field_offsets :: proc(T: typeid) -> []uintptr { ti := runtime.type_info_base(type_info_of(T)) @@ -498,6 +593,7 @@ struct_field_offsets :: proc(T: typeid) -> []uintptr { return nil } +// Struct_Field_Count_Method is the count method used by `struct_field_count` in order to find the number of fields Struct_Field_Count_Method :: enum { Top_Level, Using, @@ -556,6 +652,10 @@ struct_field_count :: proc(T: typeid, method := Struct_Field_Count_Method.Top_Le return } +// Returns the fields of a struct type `T` as an `#soa` slice. +// This is useful to iterate over. +// Example: +// for field, i in reflect.struct_fields_zipped(Foo) { ... } @(require_results) struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) @@ -572,13 +672,26 @@ struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) { } - +// struct_tag_get returns the value associated with a key in the tag string. +// If the key is present in the tag, the value (which might be empty) is returned. Otherwise an empty string is returned. +// This is just a wrapper around `struct_tag_lookup` with the `ok` value being ignored. +// +// The convention for struct tags is usually of the form: +// +// `key:"value" another:"set" and:"whatever"` @(require_results) struct_tag_get :: proc(tag: Struct_Tag, key: string) -> (value: string) { v, _ := struct_tag_lookup(tag, key) - return string(v) + return v } +// struct_tag_lookup returns the value associated with a key in the tag string. +// If the key is present in the tag, the value (which might be empty) is return. Otherwise an empty string is returned. +// The `ok` value returns whether the value was explicit set in the tag string. +// +// The convention for struct tags is usually of the form: +// +// `key:"value" another:"set" and:"whatever"` @(require_results) struct_tag_lookup :: proc(tag: Struct_Tag, key: string) -> (value: string, ok: bool) { for t := tag; t != ""; /**/ { @@ -638,6 +751,7 @@ struct_tag_lookup :: proc(tag: Struct_Tag, key: string) -> (value: string, ok: b } +// Returns the string representation of an enum value. It will panic if the value passed is not an enum. @(require_results) enum_string :: proc(a: any) -> string { if a == nil { return "" } @@ -656,7 +770,7 @@ enum_string :: proc(a: any) -> string { return "" } -// Given a enum type and a value name, get the enum value. +// Given an enum type and a value name, get the enum value. @(require_results) enum_from_name :: proc($Enum_Type: typeid, name: string) -> (value: Enum_Type, ok: bool) { ti := type_info_base(type_info_of(Enum_Type)) @@ -674,6 +788,7 @@ enum_from_name :: proc($Enum_Type: typeid, name: string) -> (value: Enum_Type, o return } +// enum_from_name_any returns the value of an enum field's name if found, returns `0, false` otherwise. @(require_results) enum_from_name_any :: proc(Enum_Type: typeid, name: string) -> (value: Type_Info_Enum_Value, ok: bool) { ti := runtime.type_info_base(type_info_of(Enum_Type)) @@ -690,6 +805,7 @@ enum_from_name_any :: proc(Enum_Type: typeid, name: string) -> (value: Type_Info return } +// enum_name_from_value returns the name of enum field if a valid name using parametric polymorphism, otherwise returns `"", false` @(require_results) enum_name_from_value :: proc(value: $Enum_Type) -> (name: string, ok: bool) where intrinsics.type_is_enum(Enum_Type) { ti := type_info_base(type_info_of(Enum_Type)) @@ -706,6 +822,7 @@ enum_name_from_value :: proc(value: $Enum_Type) -> (name: string, ok: bool) wher return } +// enum_name_from_value_any returns the name of enum field if a valid name using reflection, otherwise returns `"", false` @(require_results) enum_name_from_value_any :: proc(value: any) -> (name: string, ok: bool) { if value.id == nil { @@ -725,9 +842,7 @@ enum_name_from_value_any :: proc(value: any) -> (name: string, ok: bool) { return } -/* -Returns whether the value given has a defined name in the enum type. -*/ +// Returns whether the value given has a defined name in the enum type. @(require_results) enum_value_has_name :: proc(value: $T) -> bool where intrinsics.type_is_enum(T) { when len(T) == cap(T) { @@ -749,6 +864,7 @@ enum_value_has_name :: proc(value: $T) -> bool where intrinsics.type_is_enum(T) +// enum_field_names returns `[]string` of the names of the fields of type `Enum_Type` @(require_results) enum_field_names :: proc(Enum_Type: typeid) -> []string { ti := runtime.type_info_base(type_info_of(Enum_Type)) @@ -757,6 +873,7 @@ enum_field_names :: proc(Enum_Type: typeid) -> []string { } return nil } +// enum_field_values returns `[]Type_Info_Enum_Value` of the values of the fields of type `Enum_Type` @(require_results) enum_field_values :: proc(Enum_Type: typeid) -> []Type_Info_Enum_Value { ti := runtime.type_info_base(type_info_of(Enum_Type)) @@ -766,11 +883,16 @@ enum_field_values :: proc(Enum_Type: typeid) -> []Type_Info_Enum_Value { return nil } +// Represents an `Enum_Field` storing the `name` and `value` Enum_Field :: struct { name: string, value: Type_Info_Enum_Value, } +// Returns a #soa slice of the enum field information of type `Enum_Type` +// This is useful to iterate over. +// Example: +// for field, i in reflect.enum_fields_zipped(Foo) { ... } @(require_results) enum_fields_zipped :: proc(Enum_Type: typeid) -> (fields: #soa[]Enum_Field) { ti := runtime.type_info_base(type_info_of(Enum_Type)) @@ -782,17 +904,20 @@ enum_fields_zipped :: proc(Enum_Type: typeid) -> (fields: #soa[]Enum_Field) { +// Returns `^Type_Info` of a any-encoded union type. Panics if a union was not passed. @(require_results) union_variant_type_info :: proc(a: any) -> ^Type_Info { id := union_variant_typeid(a) return type_info_of(id) } +// Returns whether the `Type_Info_Union` store no tag (called a "pure maybe"). @(require_results) type_info_union_is_pure_maybe :: proc(info: runtime.Type_Info_Union) -> bool { return len(info.variants) == 1 && is_pointer_internally(info.variants[0]) } +// UNSAFE: Returns `typeid` of a any-encoded union type. Panics if a union was not passed. @(require_results) union_variant_typeid :: proc(a: any) -> typeid { if a == nil { return nil } @@ -833,6 +958,7 @@ union_variant_typeid :: proc(a: any) -> typeid { panic("expected a union to reflect.union_variant_typeid") } +// UNSAFE: Returns the underlying tag value of a union. Panics if a union was not passed. @(require_results) get_union_variant_raw_tag :: proc(a: any) -> i64 { if a == nil { return -1 } @@ -864,6 +990,7 @@ get_union_variant_raw_tag :: proc(a: any) -> i64 { panic("expected a union to reflect.get_union_variant_raw_tag") } +// Returns the underlying variant value of a union. Panics if a union was not passed. @(require_results) get_union_variant :: proc(a: any) -> any { if a == nil { @@ -876,6 +1003,14 @@ get_union_variant :: proc(a: any) -> any { return any{a.data, id} } +// Converts a pointer to a union, to a union containing the pointers to the variant types, and stores a pointer of the variant value in the new union +// +// Example: +// val: union{i32, f32, string} +// val = "123" +// ptr: union{^i32, ^f32, ^string} = get_union_as_ptr_variants(&val) +// sp := ptr.(^string) +// assert(sp^ == "123") @(require_results) get_union_as_ptr_variants :: proc(val: ^$T) -> (res: intrinsics.type_convert_variants_to_pointers(T)) where intrinsics.type_is_union(T) { ptr := rawptr(val) @@ -886,7 +1021,7 @@ get_union_as_ptr_variants :: proc(val: ^$T) -> (res: intrinsics.type_convert_var } - +// UNSAFE: Manually set the tag value of a union using an integer. Panics if a union was not passed. set_union_variant_raw_tag :: proc(a: any, tag: i64) { if a == nil { return } @@ -917,6 +1052,7 @@ set_union_variant_raw_tag :: proc(a: any, tag: i64) { panic("expected a union to reflect.set_union_variant_raw_tag") } +// UNSAFE: Manually set the tag value of a union using a `typeid`. Panics if a union was not passed. set_union_variant_typeid :: proc(a: any, id: typeid) { if a == nil { return } @@ -947,6 +1083,7 @@ set_union_variant_typeid :: proc(a: any, id: typeid) { panic("expected a union to reflect.set_union_variant_typeid") } +// UNSAFE: Manually set the tag value of a union using a `^Type_Info`. Panics if a union was not passed. set_union_variant_type_info :: proc(a: any, tag_ti: ^Type_Info) { if a == nil { return } @@ -977,6 +1114,7 @@ set_union_variant_type_info :: proc(a: any, tag_ti: ^Type_Info) { panic("expected a union to reflect.set_union_variant_type_info") } +// UNSAFE: Manually set the variant value of a union using an `any`. Panics if a union was not passed. set_union_value :: proc(dst: any, value: any) -> bool { if dst == nil { return false } @@ -1015,6 +1153,7 @@ set_union_value :: proc(dst: any, value: any) -> bool { panic("expected a union to reflect.set_union_variant_typeid") } +// UNSAFE: Checks to see if the data stored is a `bit_set` and is big endian. Panics if a `bit_set` was not passed. @(require_results) bit_set_is_big_endian :: proc(value: any, loc := #caller_location) -> bool { if value == nil { return ODIN_ENDIAN == .Big } @@ -1046,6 +1185,10 @@ Bit_Field :: struct { tag: Struct_Tag, } +// Returns the fields of a `bit_field` type `T` as an `#soa` slice. +// This is useful to iterate over. +// Example: +// for field, i in reflect.bit_fields_zipped(Foo_Bit_Field) { ... } @(require_results) bit_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Bit_Field) { ti := runtime.type_info_base(type_info_of(T)) @@ -1061,6 +1204,7 @@ bit_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Bit_Field) { return nil } +// bit_field_names returns a `[]string` of the field names of a `bit_field` type `T` @(require_results) bit_field_names :: proc(T: typeid) -> []string { ti := runtime.type_info_base(type_info_of(T)) @@ -1070,6 +1214,7 @@ bit_field_names :: proc(T: typeid) -> []string { return nil } +// bit_field_types returns a `[]^Type_Info` of the field representation types of a `bit_field` type `T`, not the backing integer-bit-width types @(require_results) bit_field_types :: proc(T: typeid) -> []^Type_Info { ti := runtime.type_info_base(type_info_of(T)) @@ -1079,6 +1224,7 @@ bit_field_types :: proc(T: typeid) -> []^Type_Info { return nil } +// bit_field_types returns a `[]uintptr` of the field bit-width-sizes of a `bit_field` type `T` @(require_results) bit_field_sizes :: proc(T: typeid) -> []uintptr { ti := runtime.type_info_base(type_info_of(T)) @@ -1088,6 +1234,7 @@ bit_field_sizes :: proc(T: typeid) -> []uintptr { return nil } +// bit_field_types returns a `[]uintptr` of the field offsets in bits of a `bit_field` type `T` @(require_results) bit_field_offsets :: proc(T: typeid) -> []uintptr { ti := runtime.type_info_base(type_info_of(T)) @@ -1097,6 +1244,7 @@ bit_field_offsets :: proc(T: typeid) -> []uintptr { return nil } +// bit_field_types returns a `[]Struct_Tag` of the field tags of a `bit_field` type `T` @(require_results) bit_field_tags :: proc(T: typeid) -> []Struct_Tag { ti := runtime.type_info_base(type_info_of(T)) @@ -1106,6 +1254,7 @@ bit_field_tags :: proc(T: typeid) -> []Struct_Tag { return nil } +// as_bool attempts to convert an `any` to a `bool`. @(require_results) as_bool :: proc(a: any) -> (value: bool, valid: bool) { if a == nil { return } @@ -1129,6 +1278,7 @@ as_bool :: proc(a: any) -> (value: bool, valid: bool) { return } +// as_int attempts to convert an `any` to a `int`. @(require_results) as_int :: proc(a: any) -> (value: int, valid: bool) { v: i64 @@ -1137,6 +1287,7 @@ as_int :: proc(a: any) -> (value: int, valid: bool) { return } +// as_uint attempts to convert an `any` to a `uint`. @(require_results) as_uint :: proc(a: any) -> (value: uint, valid: bool) { v: u64 @@ -1145,6 +1296,7 @@ as_uint :: proc(a: any) -> (value: uint, valid: bool) { return } +// as_i64 attempts to convert an `any` to a `i64`. @(require_results) as_i64 :: proc(a: any) -> (value: i64, valid: bool) { if a == nil { return } @@ -1253,6 +1405,7 @@ as_i64 :: proc(a: any) -> (value: i64, valid: bool) { return } +// as_u64 attempts to convert an `any` to a `u64`. @(require_results) as_u64 :: proc(a: any) -> (value: u64, valid: bool) { if a == nil { return } @@ -1363,6 +1516,7 @@ as_u64 :: proc(a: any) -> (value: u64, valid: bool) { } +// as_f64 attempts to convert an `any` to a `f64`. @(require_results) as_f64 :: proc(a: any) -> (value: f64, valid: bool) { if a == nil { return } @@ -1480,6 +1634,7 @@ as_f64 :: proc(a: any) -> (value: f64, valid: bool) { } +// as_string attempts to convert an `any` to a `string`. @(require_results) as_string :: proc(a: any) -> (value: string, valid: bool) { if a == nil { return } @@ -1500,6 +1655,27 @@ as_string :: proc(a: any) -> (value: string, valid: bool) { return } +// as_string16 attempts to convert an `any` to a `string16`. +@(require_results) +as_string16 :: proc(a: any) -> (value: string16, valid: bool) { + if a == nil { return } + a := a + ti := runtime.type_info_core(type_info_of(a.id)) + a.id = ti.id + + #partial switch info in ti.variant { + case Type_Info_String: + valid = true + switch v in a { + case string16: value = v + case cstring16: value = string16(v) + case: valid = false + } + } + + return +} + @(require_results) relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) -> rawptr { _handle :: proc(ptr: ^$T) -> rawptr where intrinsics.type_is_integer(T) { @@ -1543,6 +1719,8 @@ relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) +// as_pointer attempts to convert an `any` to a `rawptr`. +// This only works for `^T`, `[^]T`, `cstring`, `cstring16` based types @(require_results) as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) { if a == nil { return } @@ -1551,14 +1729,15 @@ as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) { a.id = ti.id #partial switch info in ti.variant { - case Type_Info_Pointer: + case Type_Info_Pointer, Type_Info_Multi_Pointer: valid = true value = (^rawptr)(a.data)^ case Type_Info_String: valid = true switch v in a { - case cstring: value = rawptr(v) + case cstring: value = rawptr(v) + case cstring16: value = rawptr(v) case: valid = false } } @@ -1567,6 +1746,7 @@ as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) { } +// Returns the equivalent of doing `raw_data(v)` where `v` is a non-any value @(require_results) as_raw_data :: proc(a: any) -> (value: rawptr, valid: bool) { if a == nil { return } @@ -1578,8 +1758,10 @@ as_raw_data :: proc(a: any) -> (value: rawptr, valid: bool) { case Type_Info_String: valid = true switch v in a { - case string: value = raw_data(v) - case cstring: value = rawptr(v) // just in case + case string: value = raw_data(v) + case cstring: value = rawptr(v) // just in case + case string16: value = raw_data(v) + case cstring16: value = rawptr(v) // just in case case: valid = false } @@ -1604,10 +1786,13 @@ ne :: not_equal DEFAULT_EQUAL_MAX_RECURSION_LEVEL :: 32 +// Checks to see if two `any` values are not semantically equivalent @(require_results) not_equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_level := 0) -> bool { return !equal(a, b, including_indirect_array_recursion, recursion_level) } + +// Checks to see if two `any` values are semantically equivalent @(require_results) equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_level := 0) -> bool { if a == nil && b == nil { @@ -1702,14 +1887,27 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ return runtime.memory_compare(a.data, b.data, t.size) == 0 case Type_Info_String: - if v.is_cstring { - x := string((^cstring)(a.data)^) - y := string((^cstring)(b.data)^) - return x == y - } else { - x := (^string)(a.data)^ - y := (^string)(b.data)^ - return x == y + switch v.encoding { + case .UTF_8: + if v.is_cstring { + x := string((^cstring)(a.data)^) + y := string((^cstring)(b.data)^) + return x == y + } else { + x := (^string)(a.data)^ + y := (^string)(b.data)^ + return x == y + } + case .UTF_16: + if v.is_cstring { + x := string16((^cstring16)(a.data)^) + y := string16((^cstring16)(b.data)^) + return x == y + } else { + x := (^string16)(a.data)^ + y := (^string16)(b.data)^ + return x == y + } } return true case Type_Info_Array: diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 98b7b368f..2e82e29b1 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -3,6 +3,10 @@ package reflect import "core:io" import "core:strings" + +// Returns true when the `^Type_Info`s are semantically equivalent types +// Note: The pointers being identical should be enough to check but this is done to make sure in certain cases where it is non-trivial +// and each value wants to be checked directly. @(require_results) are_types_identical :: proc(a, b: ^Type_Info) -> bool { if a == b { @@ -187,6 +191,7 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { return false } +// Returns true if the base-type is a signed integer or just a float, false otherwise. @(require_results) is_signed :: proc(info: ^Type_Info) -> bool { if info == nil { return false } @@ -196,6 +201,7 @@ is_signed :: proc(info: ^Type_Info) -> bool { } return false } +// Returns true if the base-type is an usigned integer, false otherwise. @(require_results) is_unsigned :: proc(info: ^Type_Info) -> bool { if info == nil { return false } @@ -206,6 +212,7 @@ is_unsigned :: proc(info: ^Type_Info) -> bool { return false } +// Returns true when it is a 1-byte wide integer type, false otherwise. @(require_results) is_byte :: proc(info: ^Type_Info) -> bool { if info == nil { return false } @@ -216,78 +223,108 @@ is_byte :: proc(info: ^Type_Info) -> bool { } +// Returns true the base-type is an integer of any kind, false otherwise. @(require_results) is_integer :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Integer) return ok } +// Returns true the base-type is a rune, false otherwise. @(require_results) is_rune :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Rune) return ok } +// Returns true the base-type is a float of any kind, false otherwise. @(require_results) is_float :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Float) return ok } +// Returns true the base-type is a complex-type of any kind, false otherwise. @(require_results) is_complex :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Complex) return ok } +// Returns true the base-type is a quaternions any kind, false otherwise. @(require_results) is_quaternion :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Quaternion) return ok } +// Returns true the base-type is an `any`, false otherwise. @(require_results) is_any :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Any) return ok } + +// Returns true the base-type is a string of any kind (string, cstring, string16, cstring16), false otherwise. @(require_results) is_string :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_String) return ok } +// Returns true the base-type is a cstring of any kind (cstring, cstring16), false otherwise. @(require_results) is_cstring :: proc(info: ^Type_Info) -> bool { if info == nil { return false } v, ok := type_info_base(info).variant.(Type_Info_String) return ok && v.is_cstring } + +// Returns true the base-type is a string of any kind (string16, cstring16), false otherwise. +@(require_results) +is_string16 :: proc(info: ^Type_Info) -> bool { + if info == nil { return false } + v, ok := type_info_base(info).variant.(Type_Info_String) + return ok && v.encoding == .UTF_16 +} +// Returns true the base-type is a cstring of any kind (cstring16), false otherwise. +@(require_results) +is_cstring16 :: proc(info: ^Type_Info) -> bool { + if info == nil { return false } + v, ok := type_info_base(info).variant.(Type_Info_String) + return ok && v.is_cstring && v.encoding == .UTF_16 +} + +// Returns true the base-type is a boolean of any kind, false otherwise. @(require_results) is_boolean :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Boolean) return ok } +// Returns true the base-type is a pointer-type of any kind (^T or rawptr), false otherwise. @(require_results) is_pointer :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Pointer) return ok } +// Returns true the base-type is a pointer-type of any kind ([^]T), false otherwise. @(require_results) is_multi_pointer :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Multi_Pointer) return ok } +// Returns true the base-type is a pointer-type of any kind (#soa^T), false otherwise. @(require_results) is_soa_pointer :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Soa_Pointer) return ok } +// Returns true when the type is a pointer-like type, false otherwise. @(require_results) is_pointer_internally :: proc(info: ^Type_Info) -> bool { if info == nil { return false } @@ -300,78 +337,91 @@ is_pointer_internally :: proc(info: ^Type_Info) -> bool { } return false } +// Returns true when the type is a procedure type, false otherwise. @(require_results) is_procedure :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Procedure) return ok } +// Returns true when the type is a fixed-array type ([N]T), false otherwise. @(require_results) is_array :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Array) return ok } +// Returns true when the type is an enumerated-array type ([Enum]T), false otherwise. @(require_results) is_enumerated_array :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Enumerated_Array) return ok } +// Returns true when the type is a dynamic-array type ([dynamic]T), false otherwise. @(require_results) is_dynamic_array :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Dynamic_Array) return ok } +// Returns true when the type is a map type (map[K]V), false otherwise. @(require_results) is_dynamic_map :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Map) return ok } +// Returns true when the type is a bit_set type, false otherwise. @(require_results) is_bit_set :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Bit_Set) return ok } +// Returns true when the type is a slice type ([]T), false otherwise. @(require_results) is_slice :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Slice) return ok } +// Returns true when the type represents a set of parameters for a procedure (inputs or outputs), false otherwise. @(require_results) is_parameters :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Parameters) return ok } +// Returns true when the type is a struct type, `#raw_union` will be false. All other types will be false otherwise. @(require_results) is_struct :: proc(info: ^Type_Info) -> bool { if info == nil { return false } s, ok := type_info_base(info).variant.(Type_Info_Struct) return ok && .raw_union not_in s.flags } +// Returns true when the type is a struct type with `#raw_union` applied, when `#raw_union` is not applied, the value will be false. All other types will be false otherwise. @(require_results) is_raw_union :: proc(info: ^Type_Info) -> bool { if info == nil { return false } s, ok := type_info_base(info).variant.(Type_Info_Struct) return ok && .raw_union in s.flags } +// Returns true when the type is a union type (not `#raw_union`), false otherwise. @(require_results) is_union :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Union) return ok } +// Returns true when the type is an enum type, false otherwise. @(require_results) is_enum :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Enum) return ok } +// Returns true when the type is a #simd-array type (#simd[N]T), false otherwise. @(require_results) is_simd_vector :: proc(info: ^Type_Info) -> bool { if info == nil { return false } @@ -380,6 +430,9 @@ is_simd_vector :: proc(info: ^Type_Info) -> bool { } +// Returns true when the core-type is represented with a platform-native endian type, and returns false otherwise. +// This will also return false when the type is not an integer, pointer, or bit_set. +// If the type is the same as the platform-native endian type (e.g. `u32le` on a little-endian system), this will return false. @(require_results) is_endian_platform :: proc(info: ^Type_Info) -> bool { if info == nil { return false} @@ -399,6 +452,9 @@ is_endian_platform :: proc(info: ^Type_Info) -> bool { return false } +// Returns true when the core-type is represented with a platform-native endian type or the same endianness as the system. +// This will also return false when the type is not an integer, pointer, or bit_set. +// If the type is the same as the platform-native endian type (e.g. `u32le` on a little-endian system), this will return true. @(require_results) is_endian_little :: proc(info: ^Type_Info) -> bool { if info == nil { return false} @@ -421,6 +477,9 @@ is_endian_little :: proc(info: ^Type_Info) -> bool { return ODIN_ENDIAN == .Little } +// Returns true when the core-type is represented with a platform-native endian type or the same endianness as the system. +// This will also return false when the type is not an integer, pointer, or bit_set. +// If the type is the same as the platform-native endian type (e.g. `u32be` on a big-endian system), this will return true. @(require_results) is_endian_big :: proc(info: ^Type_Info) -> bool { if info == nil { return false} @@ -446,27 +505,33 @@ is_endian_big :: proc(info: ^Type_Info) -> bool { +// Writes a typeid in standard (non-canonical) form to a `strings.Builder` write_typeid_builder :: proc(buf: ^strings.Builder, id: typeid, n_written: ^int = nil) -> (n: int, err: io.Error) { return write_type_writer(strings.to_writer(buf), type_info_of(id)) } +// Writes a typeid in standard (non-canonical) form to an `io.Writer` write_typeid_writer :: proc(writer: io.Writer, id: typeid, n_written: ^int = nil) -> (n: int, err: io.Error) { return write_type_writer(writer, type_info_of(id), n_written) } +// Writes a typeid in standard (non-canonical) form write_typeid :: proc{ write_typeid_builder, write_typeid_writer, } +// Writes a `^Type_Info` in standard (non-canonical) form write_type :: proc{ write_type_builder, write_type_writer, } +// Writes a `^Type_Info` in standard (non-canonical) form to a `strings.Builder` write_type_builder :: proc(buf: ^strings.Builder, ti: ^Type_Info) -> int { n, _ := write_type_writer(strings.to_writer(buf), ti) return n } +// Writes a `^Type_Info` in standard (non-canonical) form to an `io.Writer` write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) { defer if n_written != nil { n_written^ += n diff --git a/core/strconv/deprecated.odin b/core/strconv/deprecated.odin index 883822e4b..c644d331e 100644 --- a/core/strconv/deprecated.odin +++ b/core/strconv/deprecated.odin @@ -36,3 +36,27 @@ append_u128 :: proc(buf: []byte, u: u128, base: int) -> string { append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string { return write_float(buf, f, fmt, prec, bit_size) } + +// 2025-10-03 Deprecated C short names and implementations + +@(deprecated="Use strconv.write_int() instead") +itoa :: proc(buf: []byte, i: int) -> string { + return write_int(buf, i64(i), 10) +} + +@(deprecated="Use strconv.parse_int() instead") +atoi :: proc(s: string) -> int { + v, _ := parse_int(s) + return v +} + +@(deprecated="Use strconv.parse_f64() instead") +atof :: proc(s: string) -> f64 { + v, _ := parse_f64(s) + return v +} + +@(deprecated="Use strconv.write_float() instead") +ftoa :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string { + return string(generic_ftoa(buf, f, fmt, prec, bit_size)) +} \ No newline at end of file diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 652da1adb..7fcb578f7 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -1547,85 +1547,8 @@ write_u128 :: proc(buf: []byte, u: u128, base: int) -> string { } /* -Converts an integer value to a string and stores it in the given buffer +`ftoa` C name deprecated, use `write_float` instead (same procedure) -**Inputs** -- buf: The buffer to store the resulting string -- i: The integer value to be converted - -Example: - - import "core:fmt" - import "core:strconv" - itoa_example :: proc() { - buf: [4]byte - result := strconv.itoa(buf[:], 42) - fmt.println(result, buf) // "42" - } - -Output: - - 42 [52, 50, 0, 0] - -**Returns** -- The resulting string after converting the integer value -*/ -itoa :: proc(buf: []byte, i: int) -> string { - return write_int(buf, i64(i), 10) -} -/* -Converts a string to an integer value - -**Inputs** -- s: The string to be converted - -Example: - - import "core:fmt" - import "core:strconv" - atoi_example :: proc() { - fmt.println(strconv.atoi("42")) - } - -Output: - - 42 - -**Returns** -- The resulting integer value -*/ -atoi :: proc(s: string) -> int { - v, _ := parse_int(s) - return v -} -/* -Converts a string to a float64 value - -**Inputs** -- s: The string to be converted - -Example: - - import "core:fmt" - import "core:strconv" - atof_example :: proc() { - fmt.printfln("%.3f", strconv.atof("3.14")) - } - -Output: - - 3.140 - -**Returns** -- The resulting float64 value after converting the string -*/ -atof :: proc(s: string) -> f64 { - v, _ := parse_f64(s) - return v -} -// Alias to `write_float` -ftoa :: write_float -/* Writes a float64 value as a string to the given buffer with the specified format and precision **Inputs** diff --git a/core/strings/strings.odin b/core/strings/strings.odin index ffa11f219..79a8cec29 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -436,8 +436,13 @@ equal_fold :: proc(u, v: string) -> (res: bool) { return false } - // TODO(bill): Unicode folding - + r := unicode.simple_fold(sr) + for r != sr && r < tr { + r = unicode.simple_fold(sr) + } + if r == tr { + continue loop + } return false } diff --git a/core/sys/wasm/js/odin.js b/core/sys/wasm/js/odin.js index 2a8ccdd5e..f4f73a42a 100644 --- a/core/sys/wasm/js/odin.js +++ b/core/sys/wasm/js/odin.js @@ -1937,7 +1937,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { if (buf_len > 0 && buf_ptr) { let n = Math.min(buf_len, str.length); str = str.substring(0, n); - this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) + wasmMemoryInterface.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) return n; } } @@ -2001,7 +2001,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { if (buf_len > 0 && buf_ptr) { let n = Math.min(buf_len, str.length); str = str.substring(0, n); - this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) + wasmMemoryInterface.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) return n; } } diff --git a/core/text/match/strlib.odin b/core/text/match/strlib.odin index 819f464c5..05f907bb6 100644 --- a/core/text/match/strlib.odin +++ b/core/text/match/strlib.odin @@ -9,11 +9,12 @@ MAX_CAPTURES :: 32 Capture :: struct { init: int, - len: int, + len: int, } Match :: struct { - byte_start, byte_end: int, + byte_start: int, + byte_end: int, } Error :: enum { @@ -27,18 +28,19 @@ Error :: enum { Match_Invalid, } -L_ESC :: '%' -CAP_POSITION :: -2 +L_ESC :: '%' +CAP_POSITION :: -2 CAP_UNFINISHED :: -1 -INVALID :: -1 +INVALID :: -1 Match_State :: struct { - src: string, + src: string, pattern: string, - level: int, + level: int, capture: [MAX_CAPTURES]Capture, } +@(require_results) match_class :: proc(c: rune, cl: rune) -> (res: bool) { switch unicode.to_lower(cl) { case 'a': res = is_alpha(c) @@ -65,19 +67,23 @@ is_punct :: unicode.is_punct is_space :: unicode.is_space is_cntrl :: unicode.is_control +@(require_results) is_alnum :: proc(c: rune) -> bool { return unicode.is_alpha(c) || unicode.is_digit(c) } +@(require_results) is_graph :: proc(c: rune) -> bool { return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || unicode.is_digit(c) } +@(require_results) is_xdigit :: proc(c: rune) -> bool { return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || unicode.is_digit(c) } // find the first utf8 charater and its size, return an error if the character is an error +@(require_results) utf8_peek :: proc(bytes: string) -> (c: rune, size: int, err: Error) { c, size = utf8.decode_rune_in_string(bytes) @@ -90,6 +96,7 @@ utf8_peek :: proc(bytes: string) -> (c: rune, size: int, err: Error) { // find the first utf8 charater and its size and advance the index // return an error if the character is an error +@(require_results) utf8_advance :: proc(bytes: string, index: ^int) -> (c: rune, err: Error) { size: int c, size = utf8.decode_rune_in_string(bytes[index^:]) @@ -103,10 +110,12 @@ utf8_advance :: proc(bytes: string, index: ^int) -> (c: rune, err: Error) { } // continuation byte? +@(require_results) is_cont :: proc(b: byte) -> bool { return b & 0xc0 == 0x80 } +@(require_results) utf8_prev :: proc(bytes: string, a, b: int) -> int { b := b @@ -117,6 +126,7 @@ utf8_prev :: proc(bytes: string, a, b: int) -> int { return a < b ? b - 1 : a } +@(require_results) utf8_next :: proc(bytes: string, a: int) -> int { a := a b := len(bytes) @@ -128,6 +138,7 @@ utf8_next :: proc(bytes: string, a: int) -> int { return a < b ? a + 1 : b } +@(require_results) check_capture :: proc(ms: ^Match_State, l: rune) -> (int, Error) { l := int(l - '1') @@ -138,6 +149,7 @@ check_capture :: proc(ms: ^Match_State, l: rune) -> (int, Error) { return l, .OK } +@(require_results) capture_to_close :: proc(ms: ^Match_State) -> (int, Error) { level := ms.level - 1 @@ -152,6 +164,7 @@ capture_to_close :: proc(ms: ^Match_State) -> (int, Error) { return 0, .Invalid_Pattern_Capture } +@(require_results) class_end :: proc(ms: ^Match_State, p: int) -> (step: int, err: Error) { step = p ch := utf8_advance(ms.pattern, &step) or_return @@ -163,7 +176,7 @@ class_end :: proc(ms: ^Match_State, p: int) -> (step: int, err: Error) { return } - utf8_advance(ms.pattern, &step) or_return + _ = utf8_advance(ms.pattern, &step) or_return case '[': // fine with step by 1 @@ -198,6 +211,7 @@ class_end :: proc(ms: ^Match_State, p: int) -> (step: int, err: Error) { return } +@(require_results) match_bracket_class :: proc(ms: ^Match_State, c: rune, p, ec: int) -> (sig: bool, err: Error) { sig = true p := p @@ -240,6 +254,7 @@ match_bracket_class :: proc(ms: ^Match_State, c: rune, p, ec: int) -> (sig: bool return } +@(require_results) single_match :: proc(ms: ^Match_State, s, p, ep: int) -> (matched: bool, schar_size: int, err: Error) { if s >= len(ms.src) { return @@ -254,13 +269,16 @@ single_match :: proc(ms: ^Match_State, s, p, ep: int) -> (matched: bool, schar_s case L_ESC: pchar_next, _ := utf8_peek(ms.pattern[p + psize:]) or_return matched = match_class(schar, pchar_next) - case '[': matched = match_bracket_class(ms, schar, p, ep - 1) or_return - case: matched = schar == pchar + case '[': + matched = match_bracket_class(ms, schar, p, ep - 1) or_return + case: + matched = schar == pchar } return } +@(require_results) match_balance :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) { if p >= len(ms.pattern) - 1 { return INVALID, .Invalid_Pattern_Capture @@ -300,13 +318,13 @@ match_balance :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) return INVALID, .OK } +@(require_results) max_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) { m := s // count up matches for { matched, size := single_match(ms, m, p, ep) or_return - if !matched { break } @@ -316,7 +334,6 @@ max_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) { for s <= m { result := match(ms, m, ep + 1) or_return - if result != INVALID { return result, .OK } @@ -331,6 +348,7 @@ max_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) { return INVALID, .OK } +@(require_results) min_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) { s := s @@ -339,19 +357,19 @@ min_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) { if result != INVALID { return result, .OK - } else { - // TODO receive next step maybe? - matched, rune_size := single_match(ms, s, p, ep) or_return + } + // TODO receive next step maybe? + matched, rune_size := single_match(ms, s, p, ep) or_return - if matched { - s += rune_size - } else { - return INVALID, .OK - } + if matched { + s += rune_size + } else { + return INVALID, .OK } } } +@(require_results) start_capture :: proc(ms: ^Match_State, s, p, what: int) -> (res: int, err: Error) { level := ms.level @@ -366,6 +384,7 @@ start_capture :: proc(ms: ^Match_State, s, p, what: int) -> (res: int, err: Erro return } +@(require_results) end_capture :: proc(ms: ^Match_State, s, p: int) -> (res: int, err: Error) { l := capture_to_close(ms) or_return @@ -379,6 +398,7 @@ end_capture :: proc(ms: ^Match_State, s, p: int) -> (res: int, err: Error) { return } +@(require_results) match_capture :: proc(ms: ^Match_State, s: int, char: rune) -> (res: int, err: Error) { index := check_capture(ms, char) or_return length := ms.capture[index].len @@ -390,6 +410,7 @@ match_capture :: proc(ms: ^Match_State, s: int, char: rune) -> (res: int, err: E return INVALID, .OK } +@(require_results) match :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) { s := s p := p @@ -431,7 +452,6 @@ match :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) { // balanced string case 'b': s = match_balance(ms, s, p + 2) or_return - if s != INVALID { // eg after %b() return match(ms, s, p + 4) @@ -460,7 +480,7 @@ match :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) { } m1 := match_bracket_class(ms, previous, p, ep - 1) or_return - m2 := match_bracket_class(ms, current, p, ep - 1) or_return + m2 := match_bracket_class(ms, current, p, ep - 1) or_return if !m1 && m2 { return match(ms, s, ep) @@ -486,6 +506,7 @@ match :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) { return s, .OK } +@(require_results) match_default :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) { s := s ep := class_end(ms, p) or_return @@ -495,8 +516,10 @@ match_default :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) epc := ep < len(ms.pattern) ? ms.pattern[ep] : 0 switch epc { - case '*', '?', '-': return match(ms, s, ep + 1) - case: s = INVALID + case '*', '?', '-': + return match(ms, s, ep + 1) + case: + s = INVALID } } else { epc := ep < len(ms.pattern) ? ms.pattern[ep] : 0 @@ -505,22 +528,23 @@ match_default :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) case '?': result := match(ms, s + ssize, ep + 1) or_return - if result != INVALID { - s = result - } else { + if result == INVALID { return match(ms, s, ep + 1) } + s = result case '+': s = max_expand(ms, s + ssize, p, ep) or_return - case '*': s = max_expand(ms, s, p, ep) or_return - case '-': s = min_expand(ms, s, p, ep) or_return - case: return match(ms, s + ssize, ep) + case '*': s = max_expand(ms, s, p, ep) or_return + case '-': s = min_expand(ms, s, p, ep) or_return + case: + return match(ms, s + ssize, ep) } } return s, .OK } +@(require_results) push_onecapture :: proc(ms: ^Match_State, i: int, s: int, e: int, matches: []Match) -> (err: Error) { if i >= ms.level { if i == 0 { @@ -533,22 +557,21 @@ push_onecapture :: proc(ms: ^Match_State, i: int, s: int, e: int, matches: []M length := ms.capture[i].len switch length { - case CAP_UNFINISHED: err = .Unfinished_Capture - case CAP_POSITION: matches[i] = { init, init + 1 } - case: matches[i] = { init, init + length } + case CAP_UNFINISHED: + err = .Unfinished_Capture + case CAP_POSITION: + matches[i] = { init, init + 1 } + case: + matches[i] = { init, init + length } } } return } -push_captures :: proc( - ms: ^Match_State, - s: int, - e: int, - matches: []Match, -) -> (nlevels: int, err: Error) { - nlevels = 1 if ms.level == 0 && s != -1 else ms.level +@(require_results) +push_captures :: proc(ms: ^Match_State, s, e: int, matches: []Match) -> (nlevels: int, err: Error) { + nlevels = 1 if ms.level == 0 && s >= 0 else ms.level for i in 0.. int { for i in 0.. int { return -1 } +@(require_results) lmem_find :: proc(s1, s2: string) -> int { l1 := len(s1) l2 := len(s2) if l2 == 0 { return 0 - } else if l2 > l1 { + } + if l2 > l1 { return -1 - } else { - init := strings.index_byte(s1, s2[0]) - end := init + l2 + } - for end <= l1 && init != -1 { - init += 1 + init := strings.index_byte(s1, s2[0]) + end := init + l2 - if s1[init - 1:end] == s2 { - return init - 1 - } else { - next := strings.index_byte(s1[init:], s2[0]) + for end <= l1 && init >= 0 { + init += 1 - if next == -1 { - return -1 - } else { - init = init + next - end = init + l2 - } - } + if s1[init - 1:end] == s2 { + return init - 1 } + next := strings.index_byte(s1[init:], s2[0]) + + if next == -1 { + return -1 + } + init = init + next + end = init + l2 } return -1 @@ -618,36 +643,28 @@ lmem_find :: proc(s1, s2: string) -> int { // find a pattern with in a haystack with an offset // allow_memfind will speed up simple searches -find_aux :: proc( - haystack: string, - pattern: string, - offset: int, - allow_memfind: bool, - matches: ^[MAX_CAPTURES]Match, -) -> (captures: int, err: Error) { +find_aux :: proc(haystack, pattern: string, offset: int, allow_memfind: bool, matches: ^[MAX_CAPTURES]Match) -> (captures: int, err: Error) { s := offset p := 0 specials_idx := index_special(pattern) if allow_memfind && specials_idx == -1 { - if index := lmem_find(haystack[s:], pattern); index != -1 { + if index := lmem_find(haystack[s:], pattern); index >= 0 { matches[0] = { index + s, index + s + len(pattern) } captures = 1 - return - } else { - return } + return } pattern := pattern anchor: bool if len(pattern) > 0 && pattern[0] == '^' { - anchor = true + anchor = true pattern = pattern[1:] } ms := Match_State { - src = haystack, + src = haystack, pattern = pattern, } @@ -684,11 +701,8 @@ find_aux :: proc( // rest has to be used from captures // assumes captures is zeroed on first iteration // resets captures to zero on last iteration -gmatch :: proc( - haystack: ^string, - pattern: string, - captures: ^[MAX_CAPTURES]Match, -) -> (res: string, ok: bool) { +@(require_results) +gmatch :: proc(haystack: ^string, pattern: string, captures: ^[MAX_CAPTURES]Match) -> (res: string, ok: bool) { haystack^ = haystack[captures[0].byte_end:] if len(haystack) > 0 { length, err := find_aux(haystack^, pattern, 0, false, captures) @@ -707,24 +721,17 @@ gmatch :: proc( } // gsub with builder, replace patterns found with the replace content -gsub_builder :: proc( - builder: ^strings.Builder, - haystack: string, - pattern: string, - replace: string, -) -> string { +@(require_results) +gsub_builder :: proc(builder: ^strings.Builder, haystack, pattern, replace: string) -> string { // find matches captures: [MAX_CAPTURES]Match haystack := haystack for { length, err := find_aux(haystack, pattern, 0, false, &captures) - - // done - if length == 0 { + if length == 0 { // done break } - if err != .OK { return {} } @@ -746,21 +753,17 @@ gsub_builder :: proc( } // uses temp builder to build initial string - then allocates the result -gsub_allocator :: proc( - haystack: string, - pattern: string, - replace: string, - allocator := context.allocator, -) -> string { +@(require_results) +gsub_allocator :: proc(haystack, pattern, replace: string, allocator := context.allocator) -> string { builder := strings.builder_make(0, 256, context.temp_allocator) return gsub_builder(&builder, haystack, pattern, replace) } Gsub_Proc :: proc( // optional passed data - data: rawptr, + data: rawptr, // word match found - word: string, + word: string, // current haystack for found captures haystack: string, // found captures - empty for no captures @@ -768,20 +771,14 @@ Gsub_Proc :: proc( ) // call a procedure on every match in the haystack -gsub_with :: proc( - haystack: string, - pattern: string, - data: rawptr, - call: Gsub_Proc, -) { +gsub_with :: proc(haystack, pattern: string, data: rawptr, call: Gsub_Proc) { // find matches captures: [MAX_CAPTURES]Match haystack := haystack for { length := find_aux(haystack, pattern, 0, false, &captures) or_break - // done - if length == 0 { + if length == 0 { // done break } @@ -800,11 +797,8 @@ gsub :: proc { gsub_builder, gsub_allocator } // iterative find with zeroth capture only // assumes captures is zeroed on first iteration // resets captures to zero on last iteration -gfind :: proc( - haystack: ^string, - pattern: string, - captures: ^[MAX_CAPTURES]Match, -) -> (res: string, ok: bool) { +@(require_results) +gfind :: proc(haystack: ^string, pattern: string, captures: ^[MAX_CAPTURES]Match) -> (res: string, ok: bool) { haystack^ = haystack[captures[0].byte_end:] if len(haystack) > 0 { length, err := find_aux(haystack^, pattern, 0, true, captures) @@ -822,10 +816,8 @@ gfind :: proc( } // rebuilds a pattern into a case insensitive pattern -pattern_case_insensitive_builder :: proc( - builder: ^strings.Builder, - pattern: string, -) -> (res: string) { +@(require_results) +pattern_case_insensitive_builder :: proc(builder: ^strings.Builder, pattern: string) -> string { p := pattern last_percent: bool @@ -849,11 +841,8 @@ pattern_case_insensitive_builder :: proc( return strings.to_string(builder^) } -pattern_case_insensitive_allocator :: proc( - pattern: string, - cap: int = 256, - allocator := context.allocator, -) -> (res: string) { +@(require_results) +pattern_case_insensitive_allocator :: proc(pattern: string, cap: int = 256, allocator := context.allocator) -> string { builder := strings.builder_make(0, cap, context.temp_allocator) return pattern_case_insensitive_builder(&builder, pattern) } @@ -877,6 +866,7 @@ Matcher :: struct { } // init using haystack & pattern and an optional byte offset +@(require_results) matcher_init :: proc(haystack, pattern: string, offset: int = 0) -> (res: Matcher) { res.haystack = haystack res.pattern = pattern @@ -886,13 +876,14 @@ matcher_init :: proc(haystack, pattern: string, offset: int = 0) -> (res: Matche } // find the first match and return the byte start / end position in the string, true on success +@(require_results) matcher_find :: proc(matcher: ^Matcher) -> (start, end: int, ok: bool) #no_bounds_check { matcher.captures_length, matcher.err = find_aux( matcher.haystack, matcher.pattern, matcher.offset, - true, - &matcher.captures, + allow_memfind=true, + matches=&matcher.captures, ) ok = matcher.captures_length > 0 && matcher.err == .OK match := matcher.captures[0] @@ -902,13 +893,14 @@ matcher_find :: proc(matcher: ^Matcher) -> (start, end: int, ok: bool) #no_bound } // find the first match and return the matched word, true on success +@(require_results) matcher_match :: proc(matcher: ^Matcher) -> (word: string, ok: bool) #no_bounds_check { matcher.captures_length, matcher.err = find_aux( matcher.haystack, matcher.pattern, matcher.offset, - false, - &matcher.captures, + allow_memfind=false, + matches=&matcher.captures, ) ok = matcher.captures_length > 0 && matcher.err == .OK match := matcher.captures[0] @@ -917,6 +909,7 @@ matcher_match :: proc(matcher: ^Matcher) -> (word: string, ok: bool) #no_bounds_ } // get the capture at the "correct" spot, as spot 0 is reserved for the first match +@(require_results) matcher_capture :: proc(matcher: ^Matcher, index: int, loc := #caller_location) -> string #no_bounds_check { runtime.bounds_check_error_loc(loc, index + 1, MAX_CAPTURES - 1) cap := matcher.captures[index + 1] @@ -924,6 +917,7 @@ matcher_capture :: proc(matcher: ^Matcher, index: int, loc := #caller_location) } // get the raw match out of the captures, skipping spot 0 +@(require_results) matcher_capture_raw :: proc(matcher: ^Matcher, index: int, loc := #caller_location) -> Match #no_bounds_check { runtime.bounds_check_error_loc(loc, index + 1, MAX_CAPTURES - 1) return matcher.captures[index + 1] @@ -933,6 +927,7 @@ matcher_capture_raw :: proc(matcher: ^Matcher, index: int, loc := #caller_locati matcher_gmatch :: matcher_match_iter // iteratively match the haystack till it cant find any matches +@(require_results) matcher_match_iter :: proc(matcher: ^Matcher) -> (res: string, index: int, ok: bool) { if len(matcher.iter) > 0 { matcher.captures_length, matcher.err = find_aux( @@ -962,6 +957,7 @@ matcher_match_iter :: proc(matcher: ^Matcher) -> (res: string, index: int, ok: b } // get a slice of all valid captures above the first match +@(require_results) matcher_captures_slice :: proc(matcher: ^Matcher) -> []Match { return matcher.captures[1:matcher.captures_length] } diff --git a/core/unicode/fold.odin b/core/unicode/fold.odin new file mode 100644 index 000000000..edd8a3156 --- /dev/null +++ b/core/unicode/fold.odin @@ -0,0 +1,79 @@ +package unicode + +// simple_fold iterates over the Unicode code points equivalent under the Unicode defined simple case folding. +// simple_fold returns the smallest rune > r if one exists, or the smallest rune >= 0. +// If no valid Unicode code point exists, r is returned. +// +// Example: +// simple_fold('A') == 'a' +// simple_fold('a') == 'A' +// simple_fold('Z') == 'z' +// simple_fold('z') == 'Z' +// simple_fold('7') == '7' +// simple_fold('k') == '\u212a' (Kelvin symbol, K) +// simple_fold('\u212a') == 'k' +// simple_fold(-3) == -3 +@(require_results) +simple_fold :: proc(r: rune) -> rune { + Fold_Pair :: struct { + from: u16, + to: u16, + } + + @(static, rodata) + ASCII_FOLD := [MAX_ASCII + 1]u16{ + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x212a, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x017f, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + } + + @(static, rodata) + CASE_ORBIT := [?]Fold_Pair{ + {0x004B, 0x006B}, {0x0053, 0x0073}, {0x006B, 0x212A}, {0x0073, 0x017F}, {0x00B5, 0x039C}, {0x00C5, 0x00E5}, {0x00DF, 0x1E9E}, + {0x00E5, 0x212B}, {0x0130, 0x0130}, {0x0131, 0x0131}, {0x017F, 0x0053}, {0x01C4, 0x01C5}, {0x01C5, 0x01C6}, {0x01C6, 0x01C4}, + {0x01C7, 0x01C8}, {0x01C8, 0x01C9}, {0x01C9, 0x01C7}, {0x01CA, 0x01CB}, {0x01CB, 0x01CC}, {0x01CC, 0x01CA}, {0x01F1, 0x01F2}, + {0x01F2, 0x01F3}, {0x01F3, 0x01F1}, {0x0345, 0x0399}, {0x0392, 0x03B2}, {0x0395, 0x03B5}, {0x0398, 0x03B8}, {0x0399, 0x03B9}, + {0x039A, 0x03BA}, {0x039C, 0x03BC}, {0x03A0, 0x03C0}, {0x03A1, 0x03C1}, {0x03A3, 0x03C2}, {0x03A6, 0x03C6}, {0x03A9, 0x03C9}, + {0x03B2, 0x03D0}, {0x03B5, 0x03F5}, {0x03B8, 0x03D1}, {0x03B9, 0x1FBE}, {0x03BA, 0x03F0}, {0x03BC, 0x00B5}, {0x03C0, 0x03D6}, + {0x03C1, 0x03F1}, {0x03C2, 0x03C3}, {0x03C3, 0x03A3}, {0x03C6, 0x03D5}, {0x03C9, 0x2126}, {0x03D0, 0x0392}, {0x03D1, 0x03F4}, + {0x03D5, 0x03A6}, {0x03D6, 0x03A0}, {0x03F0, 0x039A}, {0x03F1, 0x03A1}, {0x03F4, 0x0398}, {0x03F5, 0x0395}, {0x0412, 0x0432}, + {0x0414, 0x0434}, {0x041E, 0x043E}, {0x0421, 0x0441}, {0x0422, 0x0442}, {0x042A, 0x044A}, {0x0432, 0x1C80}, {0x0434, 0x1C81}, + {0x043E, 0x1C82}, {0x0441, 0x1C83}, {0x0442, 0x1C84}, {0x044A, 0x1C86}, {0x0462, 0x0463}, {0x0463, 0x1C87}, {0x1C80, 0x0412}, + {0x1C81, 0x0414}, {0x1C82, 0x041E}, {0x1C83, 0x0421}, {0x1C84, 0x1C85}, {0x1C85, 0x0422}, {0x1C86, 0x042A}, {0x1C87, 0x0462}, + {0x1C88, 0xA64A}, {0x1E60, 0x1E61}, {0x1E61, 0x1E9B}, {0x1E9B, 0x1E60}, {0x1E9E, 0x00DF}, {0x1FBE, 0x0345}, {0x2126, 0x03A9}, + {0x212A, 0x004B}, {0x212B, 0x00C5}, {0xA64A, 0xA64B}, {0xA64B, 0x1C88}, + } + + if r < 0 || r > MAX_RUNE { + return r + } + if int(r) < len(ASCII_FOLD) { + return rune(ASCII_FOLD[r]) + } + + lo, hi := 0, len(CASE_ORBIT) + for lo < hi { + m := int(uint(lo+hi) >> 1) + if rune(CASE_ORBIT[m].from) < r { + lo = m + 1 + } else { + hi = m + } + } + + if lo < len(CASE_ORBIT) && rune(CASE_ORBIT[lo].from) == r { + return rune(CASE_ORBIT[lo].to) + } + + + l := to_lower(r) + if l != r { + return l + } + return to_upper(r) +} diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 7e1567750..f142f04b7 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -210,7 +210,7 @@ gb_internal ObjcMsgKind get_objc_proc_kind(Type *return_type) { return ObjcMsg_normal; } -gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice param_types) { +void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice param_types) { ObjcMsgKind kind = get_objc_proc_kind(return_type); Scope *scope = create_scope(c->info, nullptr); @@ -248,6 +248,12 @@ gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_t try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret"); try_to_add_package_dependency(c, "runtime", "objc_msgSend_fp2ret"); try_to_add_package_dependency(c, "runtime", "objc_msgSend_stret"); + + Slice args = call->CallExpr.args; + if (args.count > 0 && args[0]->tav.objc_super_target) { + try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2"); + try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2_stret"); + } } gb_internal bool is_constant_string(CheckerContext *c, String const &builtin_name, Ast *expr, String *name_) { @@ -466,8 +472,8 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan isize capture_arg_count = ce->args.count - 1; - // NOTE(harold): The first parameter is already checked at check_builtin_procedure(). - // Checking again would invalidate the Entity -> Value map for direct parameters if it's the handler proc. + // NOTE(harold): The first argument is already checked at check_builtin_procedure(). + // Checking again would invalidate the Entity -> Value map for direct arguments if it's the handler proc. param_operands[0] = *operand; for (isize i = 0; i < ce->args.count-1; i++) { @@ -680,6 +686,52 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan operand->mode = Addressing_Value; return true; } break; + + case BuiltinProc_objc_super: + { + // Must be a pointer to an Objective-C object. + Type *objc_obj = operand->type; + if (!is_type_objc_ptr_to_object(objc_obj)) { + gbString e = expr_to_string(operand->expr); + gbString t = type_to_string(objc_obj); + error(operand->expr, "'%.*s' expected a pointer to an Objective-C object, but got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (operand->mode != Addressing_Value && operand->mode != Addressing_Variable) { + gbString e = expr_to_string(operand->expr); + gbString t = type_to_string(operand->type); + error(operand->expr, "'%.*s' expression '%s', of type %s, must be a value or variable.", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + Type *obj_type = type_deref(objc_obj); + GB_ASSERT(obj_type->kind == Type_Named); + + // NOTE(harold) Track original type before transforming it to the superclass. + // This is needed because objc_msgSendSuper2 must start its search on the subclass, not the superclass. + call->tav.objc_super_target = obj_type; + + // The superclass type must be known at compile time. We require this so that the selector method expressions + // methods are resolved to the superclass's methods instead of the subclass's. + Type *superclass = obj_type->Named.type_name->TypeName.objc_superclass; + if (superclass == nullptr) { + gbString t = type_to_string(obj_type); + error(operand->expr, "'%.*s' target object '%.*s' does not have an Objective-C superclass. One must be set via the @(objc_superclass) attribute", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + GB_ASSERT(superclass->Named.type_name->TypeName.objc_class_name.len > 0); + + operand->type = alloc_type_pointer(superclass); + return true; + + } break; } } @@ -2515,6 +2567,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_objc_register_class: case BuiltinProc_objc_ivar_get: case BuiltinProc_objc_block: + case BuiltinProc_objc_super: return check_builtin_objc_procedure(c, operand, call, id, type_hint); case BuiltinProc___entry_point: diff --git a/src/check_decl.cpp b/src/check_decl.cpp index b61c23fa7..fa4eade0f 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -587,9 +587,7 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, super = named_type->Named.type_name->TypeName.objc_superclass; } } else { - if (ac.objc_superclass != nullptr) { - error(e->token, "@(objc_superclass) may only be applied when the @(obj_implement) attribute is also applied"); - } else if (ac.objc_ivar != nullptr) { + if (ac.objc_ivar != nullptr) { error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied"); } else if (ac.objc_context_provider != nullptr) { error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied"); @@ -1084,61 +1082,100 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon // Enable implementation by default if the class is an implementer too and // @objc_implement was not set to false explicitly in this proc. bool implement = tn->TypeName.objc_is_implementation; + if( ac.objc_is_implementation && !tn->TypeName.objc_is_implementation ) { + error(e->token, "Cannot apply @(objc_is_implement) to a procedure whose type does not also have @(objc_is_implement) set"); + } + if (ac.objc_is_disabled_implement) { implement = false; } - if (implement) { - GB_ASSERT(e->kind == Entity_Procedure); + String objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name; + + if (e->kind == Entity_Procedure) { + bool has_body = e->decl_info->proc_lit->ProcLit.body != nullptr; + e->Procedure.is_objc_impl_or_import = implement || !has_body; + e->Procedure.is_objc_class_method = ac.objc_is_class_method; + e->Procedure.objc_selector_name = objc_selector; + e->Procedure.objc_class = tn; auto &proc = e->type->Proc; Type *first_param = proc.param_count > 0 ? proc.params->Tuple.variables[0]->type : t_untyped_nil; - if (!tn->TypeName.objc_is_implementation) { - error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied"); - } else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) { - error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)"); - } else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) { - error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set"); - } else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) { - error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention"); - } else if (proc.result_count > 1) { - error(e->token, "Objective-C method implementations may return at most 1 value"); - } else { - // Always export unconditionally - // NOTE(harold): This means check_objc_methods() MUST be called before - // e->Procedure.is_export is set in check_proc_decl()! - if (ac.is_export) { - error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly"); - } - if (ac.link_name != "") { - error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly"); - } - - ac.is_export = true; - ac.linkage = STR_LIT("strong"); - - auto method = ObjcMethodData{ ac, e }; - method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name; - - CheckerInfo *info = ctx->info; - mutex_lock(&info->objc_method_mutex); - defer (mutex_unlock(&info->objc_method_mutex)); - - Array* method_list = map_get(&info->objc_method_implementations, t); - if (method_list) { - array_add(method_list, method); + if (implement) { + if( !has_body ) { + error(e->token, "Procedures with @(objc_is_implement) must have a body"); + } else if (!tn->TypeName.objc_is_implementation) { + error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied"); + } else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) { + error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)"); + } else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) { + error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set"); + } else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) { + error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention"); + } else if (proc.result_count > 1) { + error(e->token, "Objective-C method implementations may return at most 1 value"); } else { - auto list = array_make(permanent_allocator(), 1, 8); - list[0] = method; + // Always export unconditionally + // NOTE(harold): This means check_objc_methods() MUST be called before + // e->Procedure.is_export is set in check_proc_decl()! + if (ac.is_export) { + error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly"); + } + if (ac.link_name != "") { + error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly"); + } - map_set(&info->objc_method_implementations, t, list); + ac.is_export = true; + ac.linkage = STR_LIT("strong"); + + auto method = ObjcMethodData{ ac, e }; + method.ac.objc_selector = objc_selector; + + CheckerInfo *info = ctx->info; + mutex_lock(&info->objc_method_mutex); + defer (mutex_unlock(&info->objc_method_mutex)); + + Array* method_list = map_get(&info->objc_method_implementations, t); + if (method_list) { + array_add(method_list, method); + } else { + auto list = array_make(permanent_allocator(), 1, 8); + list[0] = method; + + map_set(&info->objc_method_implementations, t, list); + } + } + } else if (!has_body) { + if (ac.objc_selector == "The @(objc_selector) attribute is required for imported Objective-C methods.") { + return; + } else if (proc.calling_convention != ProcCC_CDecl) { + error(e->token, "Imported Objective-C methods must use the \"c\" calling convention"); + return; + } else if (tn->TypeName.objc_context_provider) { + error(e->token, "Imported Objective-C class '%.*s' must not declare context providers.", tn->type->Named.name); + return; + } else if (tn->TypeName.objc_is_implementation) { + error(e->token, "Imported Objective-C methods used in a class with @(objc_implement) is not allowed."); + return; + } else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) { + error(e->token, "Objective-C instance methods require the first parameter to be a pointer to the class type set by @(objc_type)"); + return; } } - } else if (ac.objc_selector != "") { - error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C implementations."); + else if(ac.objc_selector != "") { + error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C method implementations or are imported."); + return; + } + } else { + GB_ASSERT(e->kind == Entity_ProcGroup); + if (tn->TypeName.objc_is_implementation) { + error(e->token, "Objective-C procedure groups cannot use the @(objc_implement) attribute."); + return; + } } + mutex_lock(&global_type_name_objc_metadata_mutex); defer (mutex_unlock(&global_type_name_objc_metadata_mutex)); @@ -1523,7 +1560,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { if (!pt->is_polymorphic) { check_procedure_later(ctx->checker, ctx->file, e->token, d, proc_type, pl->body, pl->tags); } - } else if (!is_foreign) { + } else if (!is_foreign && !e->Procedure.is_objc_impl_or_import) { if (e->Procedure.is_export) { error(e->token, "Foreign export procedures must have a body"); } else { @@ -1571,6 +1608,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { // NOTE(bill): this must be delayed because the foreign import paths might not be evaluated yet until much later mpsc_enqueue(&ctx->info->foreign_decls_to_check, e); } else { + // TODO(harold): Check if it's an objective-C foreign, if so, I don't think we need to check it. check_foreign_procedure(ctx, e, d); } } else { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index c1861652a..8834091d1 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1296,11 +1296,6 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ error_line("\t Got: %s\n", s_got); gb_string_free(s_got); gb_string_free(s_expected); - - Type *tx = x->Proc.params->Tuple.variables[0]->type; - Type *ty = y->Proc.params->Tuple.variables[0]->type; - gb_printf_err("%s kind:%.*s e:%p ot:%p\n", type_to_string(tx), LIT(type_strings[tx->kind]), tx->Named.type_name, tx->Named.type_name->TypeName.original_type_for_parapoly); - gb_printf_err("%s kind:%.*s e:%p ot:%p\n", type_to_string(ty), LIT(type_strings[ty->kind]), ty->Named.type_name, ty->Named.type_name->TypeName.original_type_for_parapoly); } else { gbString s_expected = type_to_string(y); gbString s_got = type_to_string(x); @@ -6216,7 +6211,6 @@ gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize * continue; } } - break; } } @@ -7488,8 +7482,6 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, Entity *e = proc_entities[valids[0].index]; GB_ASSERT(e != nullptr); - Array named_operands = {}; - check_call_arguments_single(c, call, operand, e, e->type, positional_operands, named_operands, @@ -8151,6 +8143,73 @@ gb_internal ExprKind check_call_expr_as_type_cast(CheckerContext *c, Operand *op } +void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice param_types); + +gb_internal void check_objc_call_expr(CheckerContext *c, Operand *operand, Ast *call, Entity *proc_entity, Type *proc_type) { + auto &proc = proc_type->Proc; + Slice params = proc.params ? proc.params->Tuple.variables : Slice{}; + + Type *self_type = nullptr; + isize params_start = 1; + + ast_node(ce, CallExpr, call); + + Type *return_type = proc.result_count == 0 ? nullptr : proc.results->Tuple.variables[0]->type; + bool is_return_instancetype = return_type != nullptr && return_type == t_objc_instancetype; + + if (params.count == 0 || !is_type_objc_ptr_to_object(params[0]->type)) { + if (!proc_entity->Procedure.is_objc_class_method) { + // Not a class method, invalid call + error(call, "Invalid Objective-C call: The Objective-C method is not a class method but this first parameter is not an Objective-C object pointer."); + return; + } + + if (is_return_instancetype) { + if (ce->proc->kind == Ast_SelectorExpr) { + ast_node(se, SelectorExpr, ce->proc); + + // NOTE(harold): These should have already been checked, right? + GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named); + + return_type = alloc_type_pointer(se->expr->tav.type); + } else { + return_type = proc_entity->Procedure.objc_class->type; + } + } + + self_type = t_objc_Class; + params_start = 0; + } else if (ce->args.count > 0) { + GB_ASSERT(is_type_objc_ptr_to_object(params[0]->type)); + + if (ce->args[0]->tav.objc_super_target) { + self_type = t_objc_super_ptr; + } else { + self_type = ce->args[0]->tav.type; + } + + if (is_return_instancetype) { + // NOTE(harold): These should have already been checked, right? + GB_ASSERT(ce->args[0]->tav.type && ce->args[0]->tav.type->kind == Type_Pointer && ce->args[0]->tav.type->Pointer.elem->kind == Type_Named); + + return_type = ce->args[0]->tav.type; + } + } + + auto param_types = slice_make(permanent_allocator(), proc.param_count + 2 - params_start); + param_types[0] = self_type; + param_types[1] = t_objc_SEL; + + for (isize i = params_start; i < params.count; i++) { + param_types[i+2-params_start] = params[i]->type; + } + + if (is_return_instancetype) { + operand->type = return_type; + } + + add_objc_proc_type(c, call, return_type, param_types); +} gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice const &args, ProcInlining inlining, Type *type_hint) { if (proc != nullptr && @@ -8414,6 +8473,12 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } + Entity *proc_entity = entity_from_expr(call->CallExpr.proc); + bool is_objc_call = proc_entity && proc_entity->kind == Entity_Procedure && proc_entity->Procedure.is_objc_impl_or_import; + if (is_objc_call) { + check_objc_call_expr(c, operand, call, proc_entity, pt); + } + return Expr_Expr; } diff --git a/src/checker.cpp b/src/checker.cpp index 32bda2e43..2a82203ef 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1416,6 +1416,8 @@ gb_internal void init_universal(void) { t_objc_SEL = alloc_type_pointer(t_objc_selector); t_objc_Class = alloc_type_pointer(t_objc_class); t_objc_Ivar = alloc_type_pointer(t_objc_ivar); + + t_objc_instancetype = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_instancetype"), t_objc_id); } } @@ -1499,9 +1501,12 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { map_destroy(&i->objc_msgSend_types); string_set_destroy(&i->obcj_class_name_set); - mpsc_destroy(&i->objc_class_implementations); map_destroy(&i->objc_method_implementations); + // NOTE(harold): Disabling this: It can cause the 'count == 0' assert to trigger + // when there's checker errors and the queue is still full as it did not reach the generation stage. + // mpsc_destroy(&i->objc_class_implementations); + string_map_destroy(&i->load_file_cache); string_map_destroy(&i->load_directory_cache); map_destroy(&i->load_directory_map); @@ -3386,12 +3391,20 @@ gb_internal void init_core_map_type(Checker *c) { t_raw_map_ptr = alloc_type_pointer(t_raw_map); } +gb_internal void init_core_objc_c(Checker *c) { + if (build_context.metrics.os == TargetOs_darwin) { + t_objc_super = find_core_type(c, str_lit("objc_super")); + t_objc_super_ptr = alloc_type_pointer(t_objc_super); + } +} + gb_internal void init_preload(Checker *c) { init_core_type_info(c); init_mem_allocator(c); init_core_context(c); init_core_source_code_location(c); init_core_map_type(c); + init_core_objc_c(c); } gb_internal ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index c6071bf98..c2255a6ba 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -354,6 +354,7 @@ BuiltinProc__type_end, BuiltinProc_objc_register_class, BuiltinProc_objc_ivar_get, BuiltinProc_objc_block, + BuiltinProc_objc_super, BuiltinProc_constant_utf16_cstring, @@ -715,7 +716,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_ivar_get"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, - {STR_LIT("objc_block"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("objc_block"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("objc_super"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/entity.cpp b/src/entity.cpp index d6d8f58de..2b21fdcac 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -251,6 +251,8 @@ struct Entity { String link_name; String link_prefix; String link_suffix; + String objc_selector_name; + Entity *objc_class; DeferredProcedure deferred_procedure; struct GenProcsData *gen_procs; @@ -266,6 +268,8 @@ struct Entity { bool is_anonymous : 1; bool no_sanitize_address : 1; bool no_sanitize_memory : 1; + bool is_objc_impl_or_import : 1; + bool is_objc_class_method : 1; } Procedure; struct { Array entities; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 1e8280a2e..c2decf091 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1417,8 +1417,21 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) { return str_lit("?"); case Type_Proc: return str_lit("?"); - case Type_BitSet: - return lb_get_objc_type_encoding(t->BitSet.underlying, pointer_depth); + case Type_BitSet: { + Type *bitset_integer_type = t->BitSet.underlying; + if (!bitset_integer_type) { + switch (t->cached_size) { + case 1: bitset_integer_type = t_u8; break; + case 2: bitset_integer_type = t_u16; break; + case 4: bitset_integer_type = t_u32; break; + case 8: bitset_integer_type = t_u64; break; + case 16: bitset_integer_type = t_u128; break; + } + } + GB_ASSERT_MSG(bitset_integer_type, "Could not determine bit_set integer size for objc_type_encoding"); + + return lb_get_objc_type_encoding(bitset_integer_type, pointer_depth); + } case Type_SimdVector: { String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth); @@ -1452,7 +1465,10 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) { struct lbObjCGlobalClass { lbObjCGlobal g; - lbValue class_value; // Local registered class value + union { + lbValue class_value; // Local registered class value + lbAddr class_global; // Global class pointer. Placeholder for class implementations which are registered in order of definition. + }; }; gb_internal void lb_register_objc_thing( @@ -1482,44 +1498,43 @@ gb_internal void lb_register_objc_thing( LLVMSetInitializer(v.value, LLVMConstNull(t)); } - lbValue class_ptr = {}; - lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name)); - // If this class requires an implementation, save it for registration below. if (g.class_impl_type != nullptr) { // Make sure the superclass has been initialized before us - lbValue superclass_value = lb_const_nil(m, t_objc_Class); - auto &tn = g.class_impl_type->Named.type_name->TypeName; Type *superclass = tn.objc_superclass; if (superclass != nullptr) { auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name); lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call); - GB_ASSERT(superclass_global.class_value.value); - - superclass_value = superclass_global.class_value; + GB_ASSERT(superclass_global.class_global.addr.value); } - args.count = 3; - args[0] = superclass_value; - args[1] = class_name; - args[2] = lb_const_int(m, t_uint, 0); - class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args); + lbObjCGlobalClass impl_global = {}; + impl_global.g = g; + impl_global.class_global = addr; - array_add(&class_impls, lbObjCGlobalClass{g, class_ptr}); + array_add(&class_impls, impl_global); + + lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); + if (class_global != nullptr) { + class_global->class_global = addr; + } } else { + lbValue class_ptr = {}; + lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name)); + args.count = 1; args[0] = class_name; class_ptr = lb_emit_runtime_call(p, call, args); - } - lb_addr_store(p, addr, class_ptr); + lb_addr_store(p, addr, class_ptr); - lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); - if (class_global != nullptr) { - class_global->class_value = class_ptr; + lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); + if (class_global != nullptr) { + class_global->class_value = class_ptr; + } } } @@ -1582,7 +1597,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { string_map_init(&global_class_map, (usize)gen->objc_classes.count); defer (string_map_destroy(&global_class_map)); - for (lbObjCGlobal g :referenced_classes) { + for (lbObjCGlobal g : referenced_classes) { string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g}); } @@ -1629,9 +1644,36 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { for (const auto &cd : class_impls) { auto &g = cd.g; - Type *class_type = g.class_impl_type; + + Type *class_type = g.class_impl_type; Type *class_ptr_type = alloc_type_pointer(class_type); - lbValue class_value = cd.class_value; + + // Begin class registration: create class pair and update global reference + lbValue class_value = {}; + + { + lbValue superclass_value = lb_const_nil(m, t_objc_Class); + + auto& tn = class_type->Named.type_name->TypeName; + Type *superclass = tn.objc_superclass; + + if (superclass != nullptr) { + auto& superclass_global = string_map_must_get(&global_class_map, superclass->Named.type_name->TypeName.objc_class_name); + superclass_value = superclass_global.class_value; + } + + args.count = 3; + args[0] = superclass_value; + args[1] = lb_const_value(m, t_cstring, exact_value_string(g.name)); + args[2] = lb_const_int(m, t_uint, 0); + class_value = lb_emit_runtime_call(p, "objc_allocateClassPair", args); + + lbObjCGlobalClass &mapped_global = string_map_must_get(&global_class_map, tn.objc_class_name); + lb_addr_store(p, mapped_global.class_global, class_value); + + mapped_global.class_value = class_value; + } + Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar; @@ -1651,7 +1693,6 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type); } - Array *methods = map_get(&m->info->objc_method_implementations, class_type); if (!methods) { continue; @@ -1710,17 +1751,21 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl); lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type); - lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind"); + + lb_add_function_type_attributes(wrapper_proc->value, lb_get_function_type(m, wrapper_proc_type), ProcCC_CDecl); // Emit the wrapper - LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage); + // LLVMSetLinkage(wrapper_proc->value, LLVMInternalLinkage); + LLVMSetDLLStorageClass(wrapper_proc->value, LLVMDLLExportStorageClass); + lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind"); + lb_begin_procedure_body(wrapper_proc); { + LLVMValueRef context_addr = nullptr; if (method_type->Proc.calling_convention == ProcCC_Odin) { GB_ASSERT(context_provider); // Emit the get odin context call - get_context_args[0] = lbValue { wrapper_proc->raw_input_parameters[0], contex_provider_self_ptr_type, @@ -1736,44 +1781,58 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { get_context_args[0] = lb_handle_objc_ivar_for_objc_object_pointer(wrapper_proc, real_self); } - lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args); - lbAddr context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context)); - lb_push_context_onto_stack(wrapper_proc, context_addr); + lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args); + context_addr = lb_address_from_load(wrapper_proc, context).value;//lb_address_from_load_or_generate_local(wrapper_proc, context)); + // context_addr = LLVMGetOperand(context.value, 0); } + isize method_forward_arg_count = method_param_count + method_param_offset; + isize method_forward_return_arg_offset = 0; + auto raw_method_args = array_make(temporary_allocator(), 0, method_forward_arg_count+1); - auto method_call_args = array_make(temporary_allocator(), method_param_count + method_param_offset); + lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity); + lbFunctionType* ft = lb_get_function_type(m, method_type); + bool has_return = false; + lbArgKind return_kind = {}; + + if (wrapper_results_tuple != nullptr) { + has_return = true; + return_kind = ft->ret.kind; + + if (return_kind == lbArg_Indirect) { + method_forward_return_arg_offset = 1; + array_add(&raw_method_args, wrapper_proc->return_ptr.addr.value); + } + } if (!md.ac.objc_is_class_method) { - method_call_args[0] = lbValue { - wrapper_proc->raw_input_parameters[0], - class_ptr_type, - }; + array_add(&raw_method_args, wrapper_proc->raw_input_parameters[method_forward_return_arg_offset]); } for (isize i = 0; i < method_param_count; i++) { - method_call_args[i+method_param_offset] = lbValue { - wrapper_proc->raw_input_parameters[i+2], - method_type->Proc.params->Tuple.variables[i+method_param_offset]->type, - }; + array_add(&raw_method_args, wrapper_proc->raw_input_parameters[i+2+method_forward_return_arg_offset]); + } + + if (method_type->Proc.calling_convention == ProcCC_Odin) { + array_add(&raw_method_args, context_addr); } - lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity); // Call real procedure for method from here, passing the parameters expected, if any. - lbValue return_value = lb_emit_call(wrapper_proc, method_proc_value, method_call_args); + LLVMTypeRef fnp = lb_type_internal_for_procedures_raw(m, method_type); + LLVMValueRef ret_val_raw = LLVMBuildCall2(wrapper_proc->builder, fnp, method_proc_value.value, raw_method_args.data, (unsigned)raw_method_args.count, ""); - if (wrapper_results_tuple != nullptr) { - auto &result_var = method_type->Proc.results->Tuple.variables[0]; - return_value = lb_emit_conv(wrapper_proc, return_value, result_var->type); - lb_build_return_stmt_internal(wrapper_proc, return_value, result_var->token.pos); + if (has_return && return_kind != lbArg_Indirect) { + LLVMBuildRet(wrapper_proc->builder, ret_val_raw); + } + else { + LLVMBuildRetVoid(wrapper_proc->builder); } } lb_end_procedure_body(wrapper_proc); - // Add the method to the class String method_encoding = str_lit("v"); - // TODO (harold): Checker must ensure that objc_methods have a single return value or none! + GB_ASSERT(method_type->Proc.result_count <= 1); if (method_type->Proc.result_count != 0) { method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type); @@ -1785,8 +1844,8 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:")); } - for (isize i = method_param_offset; i < method_param_count; i++) { - Type *param_type = method_type->Proc.params->Tuple.variables[i]->type; + for (isize i = 0; i < method_param_count; i++) { + Type *param_type = method_type->Proc.params->Tuple.variables[i + method_param_offset]->type; String param_encoding = lb_get_objc_type_encoding(param_type); method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding); @@ -1805,7 +1864,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { args[2] = lbValue { wrapper_proc->value, wrapper_proc->type }; args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding)); - // TODO(harold): Emit check BOOL result and panic if false. + // TODO(harold): Emit check BOOL result and panic if false? lb_emit_runtime_call(p, "class_addMethod", args); } // End methods @@ -1853,7 +1912,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { // Defined in an external package, define it now in the main package LLVMTypeRef t = lb_type(m, t_int); - lbValue global{}; + lbValue global = {}; global.value = LLVMAddGlobal(m->mod, t, g.global_name); global.type = t_int_ptr; @@ -2193,6 +2252,11 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker GB_ASSERT(m != nullptr); if (e->kind == Entity_Procedure) { + if (e->Procedure.is_foreign && e->Procedure.is_objc_impl_or_import) { + // Do not generate declarations for foreign Objective-C methods. These are called indirectly through the Objective-C runtime. + continue; + } + array_add(&m->global_procedures_to_create, e); } else if (e->kind == Entity_TypeName) { array_add(&m->global_types_to_create, e); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 6870f6259..9969aaa06 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -206,7 +206,7 @@ struct lbModule { StringMap objc_classes; StringMap objc_selectors; StringMap objc_ivars; - isize objc_next_block_id; // Used to name objective-c blocks, per module + isize objc_next_block_id; // Used to name objective-c blocks. Tracked per module. PtrMap map_cell_info_map; // address of runtime.Map_Info PtrMap map_info_map; // address of runtime.Map_Cell_Info diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 7680c5e76..08ef42f01 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3753,6 +3753,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_objc_register_class: return lb_handle_objc_register_class(p, expr); case BuiltinProc_objc_ivar_get: return lb_handle_objc_ivar_get(p, expr); case BuiltinProc_objc_block: return lb_handle_objc_block(p, expr); + case BuiltinProc_objc_super: return lb_handle_objc_super(p, expr); case BuiltinProc_constant_utf16_cstring: @@ -4122,21 +4123,23 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } Ast *proc_expr = unparen_expr(ce->proc); + Entity *proc_entity = entity_of_node(proc_expr); + if (proc_mode == Addressing_Builtin) { - Entity *e = entity_of_node(proc_expr); BuiltinProcId id = BuiltinProc_Invalid; - if (e != nullptr) { - id = cast(BuiltinProcId)e->Builtin.id; + if (proc_entity != nullptr) { + id = cast(BuiltinProcId)proc_entity->Builtin.id; } else { id = BuiltinProc_DIRECTIVE; } return lb_build_builtin_proc(p, expr, tv, id); } + bool is_objc_call = proc_entity && proc_entity->Procedure.is_objc_impl_or_import; + // NOTE(bill): Regular call lbValue value = {}; - Entity *proc_entity = entity_of_node(proc_expr); if (proc_entity != nullptr) { if (proc_entity->flags & EntityFlag_Disabled) { GB_ASSERT(tv.type == nullptr); @@ -4170,11 +4173,13 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } } - if (value.value == nullptr) { + if (is_objc_call) { + value.type = proc_tv.type; + } else if (value.value == nullptr) { value = lb_build_expr(p, proc_expr); } - GB_ASSERT(value.value != nullptr); + GB_ASSERT(value.value != nullptr || is_objc_call); Type *proc_type_ = base_type(value.type); GB_ASSERT(proc_type_->kind == Type_Proc); TypeProc *pt = &proc_type_->Proc; @@ -4402,6 +4407,11 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { isize final_count = is_c_vararg ? args.count : pt->param_count; auto call_args = array_slice(args, 0, final_count); + + if (is_objc_call) { + return lb_handle_objc_auto_send(p, expr, slice(call_args, 0, call_args.count)); + } + return lb_emit_call(p, value, call_args, ce->inlining); } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 155c55675..2cddd23d9 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -286,7 +286,14 @@ gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t) { } } + bool is_simd_vector_bitcastable = false; if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { + if (!is_type_internally_pointer_like(src->SimdVector.elem) && !is_type_internally_pointer_like(dst->SimdVector.elem)) { + is_simd_vector_bitcastable = true; + } + } + + if (is_simd_vector_bitcastable) { res.value = LLVMBuildBitCast(p->builder, value.value, lb_type(p->module, t), ""); return res; } else if (is_type_array_like(src) && (is_type_simd_vector(dst) || is_type_integer_128bit(dst))) { @@ -2264,12 +2271,12 @@ gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) { } gb_internal void lb_create_objc_block_helper_procs( - lbModule *m, LLVMTypeRef block_lit_type, isize capture_field_offset, + lbModule *m, LLVMTypeRef block_lit_type, isize capture_field_offset, isize block_id, Slice capture_values, Slice objc_object_indices, lbProcedure *&out_copy_helper, lbProcedure *&out_dispose_helper ) { - gbString copy_helper_name = gb_string_append_fmt(gb_string_make(temporary_allocator(), ""), "__$objc_block_copy_helper_%lld", m->objc_next_block_id); - gbString dispose_helper_name = gb_string_append_fmt(gb_string_make(temporary_allocator(), ""), "__$objc_block_dispose_helper_%lld", m->objc_next_block_id); + gbString copy_helper_name = gb_string_append_fmt(gb_string_make(temporary_allocator(), ""), "__$%s::objc_block_copy_helper_%lld", m->module_name, block_id); + gbString dispose_helper_name = gb_string_append_fmt(gb_string_make(temporary_allocator(), ""), "__$%s::objc_block_dispose_helper_%lld", m->module_name, block_id); // copy: Block_Literal *dst, Block_Literal *src, i32 field_apropos // dispose: Block_Literal *src, i32 field_apropos @@ -2373,14 +2380,12 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { /// https://www.newosxbook.com/src.php?tree=xnu&file=/libkern/libkern/Block_private.h /// https://github.com/llvm/llvm-project/blob/21f1f9558df3830ffa637def364e3c0cb0dbb3c0/compiler-rt/lib/BlocksRuntime/Block_private.h /// https://github.com/apple-oss-distributions/libclosure/blob/3668b0837f47be3cc1c404fb5e360f4ff178ca13/runtime.cpp - ast_node(ce, CallExpr, expr); GB_ASSERT(ce->args.count > 0); lbModule *m = p->module; - m->objc_next_block_id += 1; - + const isize block_id = m->objc_next_block_id++; const isize capture_arg_count = ce->args.count - 1; Type *block_result_type = type_of_expr(expr); @@ -2425,7 +2430,7 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { // Create proc with the block signature // (takes a block literal pointer as the first parameter, followed by any expected ones from the user's proc) - gbString block_invoker_name = gb_string_append_fmt(gb_string_make(permanent_allocator(), ""), "__$objc_block_invoker_%lld", m->objc_next_block_id); + gbString block_invoker_name = gb_string_append_fmt(gb_string_make(permanent_allocator(), ""), "__$%s::objc_block_invoker_%lld", m->module_name, block_id); // Add + 1 because the first parameter received is the block literal pointer itself auto invoker_args = array_make(temporary_allocator(), block_forward_args + 1, block_forward_args + 1); @@ -2452,14 +2457,16 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { lbProcedure *invoker_proc = lb_create_dummy_procedure(m, make_string((u8*)block_invoker_name, gb_string_length(block_invoker_name)), invoker_proc_type); + LLVMSetLinkage(invoker_proc->value, LLVMPrivateLinkage); + lb_add_function_type_attributes(invoker_proc->value, lb_get_function_type(m, invoker_proc_type), ProcCC_CDecl); // Create the block descriptor and block literal - gbString block_lit_type_name = gb_string_make(temporary_allocator(), "__$ObjC_Block_Literal_"); - block_lit_type_name = gb_string_append_fmt(block_lit_type_name, "%lld", m->objc_next_block_id); + gbString block_lit_type_name = gb_string_make(temporary_allocator(), ""); + block_lit_type_name = gb_string_append_fmt(block_lit_type_name, "__$%s::ObjC_Block_Literal_%lld", m->module_name, block_id); - gbString block_desc_type_name = gb_string_make(temporary_allocator(), "__$ObjC_Block_Descriptor_"); - block_desc_type_name = gb_string_append_fmt(block_desc_type_name, "%lld", m->objc_next_block_id); + gbString block_desc_type_name = gb_string_make(temporary_allocator(), ""); + block_desc_type_name = gb_string_append_fmt(block_desc_type_name, "__$%s::ObjC_Block_Descriptor_%lld", m->module_name,block_id); LLVMTypeRef block_lit_type = {}; LLVMTypeRef block_desc_type = {}; @@ -2503,7 +2510,7 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { // Generate copy and dispose helper functions for captured params that are Objective-C objects (or a Block) if (has_objc_fields) { - lb_create_objc_block_helper_procs(m, block_lit_type, capture_fields_offset, + lb_create_objc_block_helper_procs(m, block_lit_type, capture_fields_offset, block_id, slice(captured_values, 0, captured_values.count), slice(objc_captures, 0, objc_captures.count), copy_helper, dispose_helper); @@ -2521,8 +2528,8 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { } // Create global block descriptor - gbString desc_global_name = gb_string_make(temporary_allocator(), "__$objc_block_desc_"); - desc_global_name = gb_string_append_fmt(desc_global_name, "%lld", m->objc_next_block_id); + gbString desc_global_name = gb_string_make(temporary_allocator(), ""); + desc_global_name = gb_string_append_fmt(desc_global_name, "__$%s::objc_block_desc_%lld", m->module_name, block_id); LLVMValueRef p_descriptor = LLVMAddGlobal(m->mod, block_desc_type, desc_global_name); LLVMSetInitializer(p_descriptor, block_desc_initializer); @@ -2531,45 +2538,66 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { /// Invoker body lb_begin_procedure_body(invoker_proc); { - auto call_args = array_make(temporary_allocator(), user_proc.param_count, user_proc.param_count); + // Reserve 2 extra arguments for: Indirect return values and context. + auto call_args = array_make(temporary_allocator(), 0, user_proc.param_count + 2); - for (isize i = 1; i < invoker_proc->raw_input_parameters.count; i++) { - lbValue arg = {}; - arg.type = invoker_args[i]; - arg.value = invoker_proc->raw_input_parameters[i], - call_args[i-1] = arg; + isize block_literal_arg_index = 0; + + lbFunctionType* user_proc_ft = lb_get_function_type(m, user_proc_value.type); + + lbArgKind return_kind = {}; + + GB_ASSERT(user_proc.result_count <= 1); + if (user_proc.result_count > 0) { + return_kind = user_proc_ft->ret.kind; + + if (return_kind == lbArg_Indirect) { + // Forward indirect return value + array_add(&call_args, invoker_proc->raw_input_parameters[0]); + block_literal_arg_index = 1; + } } - LLVMValueRef block_literal = invoker_proc->raw_input_parameters[0]; + // Forward raw arguments + for (isize i = block_literal_arg_index+1; i < invoker_proc->raw_input_parameters.count; i++) { + array_add(&call_args, invoker_proc->raw_input_parameters[i]); + } + + LLVMValueRef block_literal = invoker_proc->raw_input_parameters[block_literal_arg_index]; + + // Copy capture parameters from the block literal + isize capture_arg_in_user_proc_start_index = user_proc_ft->args.count - capture_arg_count; + if (user_proc.calling_convention == ProcCC_Odin) { + capture_arg_in_user_proc_start_index -= 1; + } + + for (isize i = 0; i < capture_arg_count; i++) { + LLVMValueRef cap_value = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, unsigned(capture_fields_offset + i), ""); + + // Don't emit load if indirect. Pass the pointer as-is + isize cap_arg_index_in_user_proc = capture_arg_in_user_proc_start_index + i; + + if (user_proc_ft->args[cap_arg_index_in_user_proc].kind != lbArg_Indirect) { + cap_value = OdinLLVMBuildLoad(invoker_proc, lb_type(invoker_proc->module, captured_values[i].type), cap_value); + } + + array_add(&call_args, cap_value); + } // Push context, if needed if (user_proc.calling_convention == ProcCC_Odin) { LLVMValueRef p_context = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, 5, "context"); - lbValue ctx_val = {}; - ctx_val.type = t_context_ptr; - ctx_val.value = p_context; - - lb_push_context_onto_stack(invoker_proc, lb_addr(ctx_val)); + array_add(&call_args, p_context); } - // Copy capture parameters from the block literal - for (isize i = 0; i < capture_arg_count; i++) { - LLVMValueRef cap_value = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, unsigned(capture_fields_offset + i), ""); + LLVMTypeRef fnp = lb_type_internal_for_procedures_raw(m, user_proc_value.type); + LLVMValueRef ret_val = LLVMBuildCall2(invoker_proc->builder, fnp, user_proc_value.value, call_args.data, (unsigned)call_args.count, ""); - lbValue cap_arg = {}; - cap_arg.value = cap_value; - cap_arg.type = alloc_type_pointer(captured_values[i].type); - - lbValue arg = lb_emit_load(invoker_proc, cap_arg); - call_args[block_forward_args+i] = arg; + if (user_proc.result_count > 0 && return_kind != lbArg_Indirect) { + LLVMBuildRet(invoker_proc->builder, ret_val); } - - lbValue result = lb_emit_call(invoker_proc, user_proc_value, call_args, proc_lit->ProcLit.inlining); - - GB_ASSERT(user_proc.result_count <= 1); - if (user_proc.result_count > 0) { - GB_ASSERT(result.value != nullptr); - LLVMBuildRet(p->builder, result.value); + else { + LLVMBuildRetVoid(invoker_proc->builder); } } lb_end_procedure_body(invoker_proc); @@ -2585,10 +2613,10 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { } gbString block_var_name = gb_string_make(temporary_allocator(), "__$objc_block_literal_"); - block_var_name = gb_string_append_fmt(block_var_name, "%lld", m->objc_next_block_id); + block_var_name = gb_string_append_fmt(block_var_name, "%lld", block_id); - lbValue result = {}; - result.type = block_result_type; + lbValue block_result = {}; + block_result.type = block_result_type; lbValue isa_val = lb_find_runtime_value(m, is_global ? str_lit("_NSConcreteGlobalBlock") : str_lit("_NSConcreteStackBlock")); lbValue flags_val = lb_const_int(m, t_i32, (u64)raw_flags); @@ -2596,7 +2624,7 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { if (is_global) { LLVMValueRef p_block_lit = LLVMAddGlobal(m->mod, block_lit_type, block_var_name); - result.value = p_block_lit; + block_result.value = p_block_lit; LLVMValueRef fields_values[5] = { isa_val.value, // isa @@ -2611,7 +2639,7 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { } else { LLVMValueRef p_block_lit = llvm_alloca(p, block_lit_type, lb_alignof(block_lit_type), block_var_name); - result.value = p_block_lit; + block_result.value = p_block_lit; // Initialize it LLVMValueRef f_isa = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 0, "isa"); @@ -2651,7 +2679,20 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { } } - return result; + return block_result; +} + +gb_internal lbValue lb_handle_objc_block_invoke(lbProcedure *p, Ast *expr) { + return {}; +} + +gb_internal lbValue lb_handle_objc_super(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + GB_ASSERT(ce->args.count == 1); + + // NOTE(harold): This doesn't actually do anything by itself, + // it has an effect when used on the left side of a selector call expression. + return lb_build_expr(p, ce->args[0]); } gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { @@ -2767,6 +2808,120 @@ gb_internal lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) { return lb_emit_call(p, the_proc, args); } +gb_internal lbValue lb_handle_objc_auto_send(lbProcedure *p, Ast *expr, Slice const arg_values) { + ast_node(ce, CallExpr, expr); + + lbModule *m = p->module; + CheckerInfo *info = m->info; + ObjcMsgData data = map_must_get(&info->objc_msgSend_types, expr); + + Type *proc_type = data.proc_type; + GB_ASSERT(proc_type != nullptr); + + Entity *objc_method_ent = entity_of_node(ce->proc); + GB_ASSERT(objc_method_ent != nullptr); + GB_ASSERT(objc_method_ent->kind == Entity_Procedure); + GB_ASSERT(objc_method_ent->Procedure.objc_selector_name.len > 0); + + auto &proc = proc_type->Proc; + GB_ASSERT(proc.param_count >= 2); + + Type *objc_super_orig_type = nullptr; + if (ce->args.count > 0) { + objc_super_orig_type = unparen_expr(ce->args[0])->tav.objc_super_target; + } + + isize arg_offset = 1; + lbValue id = {}; + if (!objc_method_ent->Procedure.is_objc_class_method) { + GB_ASSERT(ce->args.count > 0); + id = arg_values[0]; + + if (objc_super_orig_type) { + GB_ASSERT(objc_super_orig_type->kind == Type_Named); + + auto& tn = objc_super_orig_type->Named.type_name->TypeName; + lbAddr p_supercls = lb_handle_objc_find_or_register_class(p, tn.objc_class_name, tn.objc_is_implementation ? objc_super_orig_type : nullptr); + + lbValue supercls = lb_addr_load(p, p_supercls); + lbAddr p_objc_super = lb_add_local_generated(p, t_objc_super, false); + + lbValue f_id = lb_emit_struct_ep(p, p_objc_super.addr, 0); + lbValue f_superclass = lb_emit_struct_ep(p, p_objc_super.addr, 1); + + id = lb_emit_conv(p, id, t_objc_id); + lb_emit_store(p, f_id, id); + lb_emit_store(p, f_superclass, supercls); + + id = p_objc_super.addr; + } + } else { + Entity *objc_class = objc_method_ent->Procedure.objc_class; + if (ce->proc->kind == Ast_SelectorExpr) { + // NOTE (harold): If called via a selector expression (ex: Foo.alloc()), then we should use + // the lhs-side to determine the class. This allows for class methods to be called + // with the correct class as the target, even when the method is defined in a superclass. + ast_node(se, SelectorExpr, ce->proc); + GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named); + + objc_class = entity_from_expr(se->expr); + + GB_ASSERT(objc_class); + GB_ASSERT(objc_class->kind == Entity_TypeName); + GB_ASSERT(objc_class->TypeName.objc_class_name != ""); + } + + Type *class_impl_type = objc_class->TypeName.objc_is_implementation ? objc_class->type : nullptr; + + id = lb_addr_load(p, lb_handle_objc_find_or_register_class(p, objc_class->TypeName.objc_class_name, class_impl_type)); + arg_offset = 0; + } + + lbValue sel = lb_addr_load(p, lb_handle_objc_find_or_register_selector(p, objc_method_ent->Procedure.objc_selector_name)); + + auto args = array_make(permanent_allocator(), 0, arg_values.count + 2 - arg_offset); + + array_add(&args, id); + array_add(&args, sel); + + for (isize i = arg_offset; i < ce->args.count; i++) { + array_add(&args, arg_values[i]); + } + + lbValue the_proc = {}; + + if (!objc_super_orig_type) { + switch (data.kind) { + default: + GB_PANIC("unhandled ObjcMsgKind %u", data.kind); + break; + case ObjcMsg_normal: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend")); break; + case ObjcMsg_fpret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fpret")); break; + case ObjcMsg_fp2ret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fp2ret")); break; + case ObjcMsg_stret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_stret")); break; + } + } else { + switch (data.kind) { + default: + GB_PANIC("unhandled ObjcMsgKind %u", data.kind); + break; + case ObjcMsg_normal: + case ObjcMsg_fpret: + case ObjcMsg_fp2ret: + the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2")); + break; + case ObjcMsg_stret: + the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2_stret")); + break; + } + } + + the_proc = lb_emit_conv(p, the_proc, data.proc_type); + + return lb_emit_call(p, the_proc, args); +} + + gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) { GB_ASSERT(value.kind == ExactValue_Integer); i64 v = exact_value_to_i64(value); diff --git a/src/parser.hpp b/src/parser.hpp index 979b44618..6127468d4 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -48,7 +48,8 @@ gb_global String const addressing_mode_strings[] = { struct TypeAndValue { Type * type; AddressingMode mode; - bool is_lhs; // Debug info + bool is_lhs; // Debug info + Type * objc_super_target; // Original type of the Obj-C object before being converted to the superclass' type by the objc_super() intrinsic. ExactValue value; }; diff --git a/src/types.cpp b/src/types.cpp index effa8ef64..372c2e991 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -752,11 +752,14 @@ gb_global Type *t_objc_object = nullptr; gb_global Type *t_objc_selector = nullptr; gb_global Type *t_objc_class = nullptr; gb_global Type *t_objc_ivar = nullptr; +gb_global Type *t_objc_super = nullptr; // Struct used in lieu of the 'self' instance when calling objc_msgSendSuper. +gb_global Type *t_objc_super_ptr = nullptr; gb_global Type *t_objc_id = nullptr; gb_global Type *t_objc_SEL = nullptr; gb_global Type *t_objc_Class = nullptr; gb_global Type *t_objc_Ivar = nullptr; +gb_global Type *t_objc_instancetype = nullptr; // Special distinct variant of t_objc_id used mimic auto-typing of instancetype* in Objective-C enum OdinAtomicMemoryOrder : i32 { OdinAtomicMemoryOrder_relaxed = 0, // unordered @@ -4735,6 +4738,14 @@ gb_internal bool is_type_objc_object(Type *t) { return internal_check_is_assignable_to(t, t_objc_object); } +gb_internal bool is_type_objc_ptr_to_object(Type *t) { + // NOTE (harold): is_type_objc_object() returns true if it's a pointer to an object or the object itself. + // This returns true ONLY if Type is a shallow pointer to an Objective-C object. + + Type *elem = type_deref(t); + return elem != t && elem->kind == Type_Named && is_type_objc_object(elem); +} + gb_internal Type *get_struct_field_type(Type *t, isize index) { t = base_type(type_deref(t)); GB_ASSERT(t->kind == Type_Struct);