Merge remote-tracking branch 'upstream/master' into llvm-14-fixes

This commit is contained in:
A1029384756
2025-10-07 23:29:10 -04:00
39 changed files with 1450 additions and 504 deletions
+3 -3
View File
@@ -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
+6
View File
@@ -302,3 +302,9 @@ misc/featuregen/featuregen
.cache/
.clangd
compile_commands.json
# Dev cmake helpers
build/
cmake-build*/
CMakeLists.txt
sandbox/
+6 -4
View File
@@ -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 ---
+22
View File
@@ -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)
+94 -19
View File
@@ -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)
+11
View File
@@ -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{
@@ -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,
}
+14 -4
View File
@@ -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 ---
}
+4
View File
@@ -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
+21 -7
View File
@@ -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' {
+2
View File
@@ -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 = {}
+7 -2
View File
@@ -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
}
+5 -1
View File
@@ -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
+75 -60
View File
@@ -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 {
+6 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+51
View File
@@ -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
+8
View File
@@ -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 {
+231 -33
View File
@@ -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:
+65
View File
@@ -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
+24
View File
@@ -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))
}
+1 -78
View File
@@ -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**
+7 -2
View File
@@ -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
}
+2 -2
View File
@@ -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;
}
}
+115 -119
View File
@@ -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..<nlevels {
push_onecapture(ms, i, s, e, matches) or_return
@@ -559,6 +582,7 @@ push_captures :: proc(
// SPECIALS := "^$*+?.([%-"
// all special characters inside a small ascii array
@(rodata)
SPECIALS_TABLE := [256]bool {
'^' = true,
'$' = true,
@@ -573,6 +597,7 @@ SPECIALS_TABLE := [256]bool {
}
// helper call to quick search for special characters
@(require_results)
index_special :: proc(text: string) -> int {
for i in 0..<len(text) {
if SPECIALS_TABLE[text[i]] {
@@ -583,34 +608,34 @@ index_special :: proc(text: string) -> 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]
}
+79
View File
@@ -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, )
// 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)
}
+56 -3
View File
@@ -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<Type *> param_types) {
void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice<Type *> 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<Ast *> 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:
+83 -45
View File
@@ -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<ObjcMethodData>* 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<ObjcMethodData>(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<ObjcMethodData>* method_list = map_get(&info->objc_method_implementations, t);
if (method_list) {
array_add(method_list, method);
} else {
auto list = array_make<ObjcMethodData>(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 {
+73 -8
View File
@@ -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<Operand> 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<Type *> 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<Entity *> params = proc.params ? proc.params->Tuple.variables : Slice<Entity *>{};
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<Type *>(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<Ast *> 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;
}
+14 -1
View File
@@ -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) {
+3 -1
View File
@@ -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},
+4
View File
@@ -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<Entity *> entities;
+117 -53
View File
@@ -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<ObjcMethodData> *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<LLVMValueRef>(temporary_allocator(), 0, method_forward_arg_count+1);
auto method_call_args = array_make<lbValue>(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);
+1 -1
View File
@@ -206,7 +206,7 @@ struct lbModule {
StringMap<lbAddr> objc_classes;
StringMap<lbAddr> objc_selectors;
StringMap<lbAddr> 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<u64/*type hash*/, lbAddr> map_cell_info_map; // address of runtime.Map_Info
PtrMap<u64/*type hash*/, lbAddr> map_info_map; // address of runtime.Map_Cell_Info
+16 -6
View File
@@ -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);
}
+203 -48
View File
@@ -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<lbValue> capture_values, Slice<isize> 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<Type *>(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<lbValue>(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<LLVMValueRef>(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<lbValue> 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<lbValue>(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);
+2 -1
View File
@@ -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;
};
+11
View File
@@ -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);