Browse Source

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

A1029384756 2 weeks ago
parent
commit
067fbfb48c

+ 3 - 3
.github/workflows/nightly.yml

@@ -124,13 +124,13 @@ jobs:
   build_macos:
     name: MacOS Build
     if: github.repository == 'odin-lang/Odin'
-    runs-on: macos-13
+    runs-on: macos-14 # Intel machine
     steps:
       - uses: actions/checkout@v4
       - name: Download LLVM and setup PATH
         run: |
           brew update
-          brew install llvm@20 dylibbundler lld
+          brew install llvm@20 dylibbundler lld@20
 
       - name: build odin
         # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
@@ -169,7 +169,7 @@ jobs:
       - name: Download LLVM and setup PATH
         run: |
           brew update
-          brew install llvm@20 dylibbundler lld
+          brew install llvm@20 dylibbundler lld@20
 
       - name: build odin
         # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to

+ 6 - 0
.gitignore

@@ -302,3 +302,9 @@ misc/featuregen/featuregen
 .cache/
 .clangd
 compile_commands.json
+
+# Dev cmake helpers
+build/
+cmake-build*/
+CMakeLists.txt
+sandbox/

+ 6 - 4
base/intrinsics/intrinsics.odin

@@ -374,10 +374,11 @@ objc_selector :: struct{}
 objc_class    :: struct{}
 objc_ivar     :: struct{}
 
-objc_id    :: ^objc_object
-objc_SEL   :: ^objc_selector
-objc_Class :: ^objc_class
-objc_Ivar  :: ^objc_ivar
+objc_id           :: ^objc_object
+objc_SEL          :: ^objc_selector
+objc_Class        :: ^objc_class
+objc_Ivar         :: ^objc_ivar
+objc_instancetype :: distinct objc_id
 
 objc_find_selector     :: proc($name: string) -> objc_SEL   ---
 objc_register_selector :: proc($name: string) -> objc_SEL   ---
@@ -385,6 +386,7 @@ objc_find_class        :: proc($name: string) -> objc_Class ---
 objc_register_class    :: proc($name: string) -> objc_Class ---
 objc_ivar_get          :: proc(self: ^$T) -> ^$U ---
 objc_block             :: proc(invoke: $T, ..any) -> ^Objc_Block(T) where type_is_proc(T) ---
+objc_super             :: proc(obj: ^$T) -> ^$U where type_is_subtype_of(T, objc_object) && type_is_subtype_of(U, objc_object) ---
 
 valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
 

+ 22 - 0
base/runtime/core.odin

@@ -636,6 +636,8 @@ _cleanup_runtime_contextless :: proc "contextless" () {
 /////////////////////////////
 
 
+// type_info_base returns the base-type of a `^Type_Info` stripping the `distinct`ness from the first level
+@(require_results)
 type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
 	if info == nil {
 		return nil
@@ -652,6 +654,10 @@ type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
 }
 
 
+// type_info_core returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR
+// returns the backing integer type of an enum or bit_set `^Type_Info`.
+// This is also aliased as `type_info_base_without_enum`
+@(require_results)
 type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
 	if info == nil {
 		return nil
@@ -668,6 +674,10 @@ type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
 	}
 	return base
 }
+
+// type_info_base_without_enum returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR
+// returns the backing integer type of an enum or bit_set `^Type_Info`.
+// This is also aliased as `type_info_core`
 type_info_base_without_enum :: type_info_core
 
 __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check {
@@ -684,15 +694,23 @@ __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check
 }
 
 when !ODIN_NO_RTTI {
+	// typeid_base returns the base-type of a `typeid` stripping the `distinct`ness from the first level
 	typeid_base :: proc "contextless" (id: typeid) -> typeid {
 		ti := type_info_of(id)
 		ti = type_info_base(ti)
 		return ti.id
 	}
+	// typeid_core returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR
+	// returns the backing integer type of an enum or bit_set `typeid`.
+	// This is also aliased as `typeid_base_without_enum`
 	typeid_core :: proc "contextless" (id: typeid) -> typeid {
 		ti := type_info_core(type_info_of(id))
 		return ti.id
 	}
+
+	// typeid_base_without_enum returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR
+	// returns the backing integer type of an enum or bit_set `typeid`.
+	// This is also aliased as `typeid_core`
 	typeid_base_without_enum :: typeid_core
 }
 
@@ -708,11 +726,15 @@ default_logger_proc :: proc(data: rawptr, level: Logger_Level, text: string, opt
 	// Nothing
 }
 
+// Returns the default logger used by `context.logger`
+@(require_results)
 default_logger :: proc() -> Logger {
 	return Logger{default_logger_proc, nil, Logger_Level.Debug, nil}
 }
 
 
+// Returns the default `context`
+@(require_results)
 default_context :: proc "contextless" () -> Context {
 	c: Context
 	__init_context(&c)

+ 94 - 19
base/runtime/core_builtin.odin

@@ -62,6 +62,8 @@ when !NO_DEFAULT_TEMP_ALLOCATOR {
 	}
 }
 
+// Initializes the global temporary allocator used as the default `context.temp_allocator`.
+// This is ignored when `NO_DEFAULT_TEMP_ALLOCATOR` is true.
 @(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR)
 init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) {
 	when !NO_DEFAULT_TEMP_ALLOCATOR {
@@ -564,6 +566,7 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a
 	return
 }
 
+// `append_elem` appends an element to the end of a dynamic array.
 @builtin
 append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	when size_of(E) == 0 {
@@ -575,6 +578,9 @@ append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller
 	}
 }
 
+// `non_zero_append_elem` appends an element to the end of a dynamic array, without zeroing any reserved memory
+//
+// Note: Prefer using the procedure group `non_zero_append
 @builtin
 non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	when size_of(E) == 0 {
@@ -613,6 +619,9 @@ _append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem,
 	return arg_len, err
 }
 
+// `append_elems` appends `args` to the end of a dynamic array.
+//
+// Note: Prefer using the procedure group `append`.
 @builtin
 append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	when size_of(E) == 0 {
@@ -624,6 +633,9 @@ append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #ca
 	}
 }
 
+// `non_zero_append_elems` appends `args` to the end of a dynamic array, without zeroing any reserved memory
+//
+// Note: Prefer using the procedure group `non_zero_append
 @builtin
 non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	when size_of(E) == 0 {
@@ -640,10 +652,16 @@ _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_ze
 	return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg))
 }
 
+// `append_elem_string` appends a string to the end of a dynamic array of bytes
+//
+// Note: Prefer using the procedure group `append`.
 @builtin
 append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	return _append_elem_string(array, arg, true, loc)
 }
+// `non_zero_append_elem_string` appends a string to the end of a dynamic array of bytes, without zeroing any reserved memory
+//
+// Note: Prefer using the procedure group `non_zero_append`.
 @builtin
 non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	return _append_elem_string(array, arg, false, loc)
@@ -651,6 +669,8 @@ non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, l
 
 
 // The append_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type
+//
+// Note: Prefer using the procedure group `append`.
 @builtin
 append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	n_arg: int
@@ -665,7 +685,8 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_
 }
 
 // The append built-in procedure appends elements to the end of a dynamic array
-@builtin append :: proc{
+@builtin
+append :: proc{
 	append_elem,
 	append_elems,
 	append_elem_string,
@@ -674,7 +695,8 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_
 	append_soa_elems,
 }
 
-@builtin non_zero_append :: proc{
+@builtin
+non_zero_append :: proc{
 	non_zero_append_elem,
 	non_zero_append_elems,
 	non_zero_append_elem_string,
@@ -684,6 +706,8 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_
 }
 
 
+// `append_nothing` appends an empty value to a dynamic array. It returns `1, nil` if successful, and `0, err` when it was not possible,
+// whatever `err` happens to be.
 @builtin
 append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	if array == nil {
@@ -695,6 +719,7 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
 }
 
 
+// `inject_at_elem` injects an element in a dynamic array at a specified index and moves the previous elements after that index "across"
 @builtin
 inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	when !ODIN_NO_BOUNDS_CHECK {
@@ -716,6 +741,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas
 	return
 }
 
+// `inject_at_elems` injects multiple elements in a dynamic array at a specified index and moves the previous elements after that index "across"
 @builtin
 inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	when !ODIN_NO_BOUNDS_CHECK {
@@ -742,6 +768,7 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca
 	return
 }
 
+// `inject_at_elem_string` injects a string into a dynamic array at a specified index and moves the previous elements after that index "across"
 @builtin
 inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	when !ODIN_NO_BOUNDS_CHECK {
@@ -766,10 +793,13 @@ inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, ar
 	return
 }
 
+// `inject_at` injects something into a dynamic array at a specified index and moves the previous elements after that index "across"
 @builtin inject_at :: proc{inject_at_elem, inject_at_elems, inject_at_elem_string}
 
 
 
+// `assign_at_elem` assigns a value at a given index. If the requested index is smaller than the current
+// size of the dynamic array, it will attempt to `resize` the a new length of `index+1` and then assign as `index`.
 @builtin
 assign_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	if index < len(array) {
@@ -784,6 +814,8 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc
 }
 
 
+// `assign_at_elems` assigns a values at a given index. If the requested index is smaller than the current
+// size of the dynamic array, it will attempt to `resize` the a new length of `index+len(args)` and then assign as `index`.
 @builtin
 assign_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	new_size := index + len(args)
@@ -800,7 +832,8 @@ assign_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca
 	return
 }
 
-
+// `assign_at_elem_string` assigns a string at a given index. If the requested index is smaller than the current
+// size of the dynamic array, it will attempt to `resize` the a new length of `index+len(arg)` and then assign as `index`.
 @builtin
 assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	new_size := index + len(arg)
@@ -817,7 +850,14 @@ assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, ar
 	return
 }
 
-@builtin assign_at :: proc{assign_at_elem, assign_at_elems, assign_at_elem_string}
+// `assign_at` assigns a value at a given index. If the requested index is smaller than the current
+// size of the dynamic array, it will attempt to `resize` the a new length of `index+size_needed` and then assign as `index`.
+@builtin
+assign_at :: proc{
+	assign_at_elem,
+	assign_at_elems,
+	assign_at_elem_string,
+}
 
 
 
@@ -834,6 +874,8 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) {
 
 // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
 //
+// When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`).
+//
 // Note: Prefer the procedure group `reserve`.
 _reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
 	if a == nil {
@@ -868,11 +910,21 @@ _reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_e
 	return nil
 }
 
+// `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
+//
+// When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`).
+//
+// Note: Prefer the procedure group `reserve`.
 @builtin
 reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
 	return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, true, loc)
 }
 
+// `non_zero_reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
+//
+// When a memory resize allocation is required, the memory will be asked to not be zeroed (i.e. it calls `non_zero_mem_resize`).
+//
+// Note: Prefer the procedure group `non_zero_reserve`.
 @builtin
 non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
 	return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, false, loc)
@@ -921,28 +973,33 @@ _resize_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_el
 
 // `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`).
 //
+// When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`).
+//
 // Note: Prefer the procedure group `resize`
 @builtin
 resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
 	return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc)
 }
 
+// `non_zero_resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`).
+//
+// When a memory resize allocation is required, the memory will be asked to not be zeroed (i.e. it calls `non_zero_mem_resize`).
+//
+// Note: Prefer the procedure group `non_zero_resize`
 @builtin
 non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
 	return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc)
 }
 
-/*
-	Shrinks the capacity of a dynamic array down to the current length, or the given capacity.
-
-	If `new_cap` is negative, then `len(array)` is used.
-
-	Returns false if `cap(array) < new_cap`, or the allocator report failure.
-
-	If `len(array) < new_cap`, then `len(array)` will be left unchanged.
-
-	Note: Prefer the procedure group `shrink`
-*/
+// Shrinks the capacity of a dynamic array down to the current length, or the given capacity.
+//
+// If `new_cap` is negative, then `len(array)` is used.
+//
+// Returns false if `cap(array) < new_cap`, or the allocator report failure.
+//
+// If `len(array) < new_cap`, then `len(array)` will be left unchanged.
+//
+// Note: Prefer the procedure group `shrink`
 shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
 	return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc)
 }
@@ -1023,6 +1080,7 @@ map_entry :: proc(m: ^$T/map[$K]$V, key: K, loc := #caller_location) -> (key_ptr
 }
 
 
+// `card` returns the number of bits that are set in a bit_set—its cardinality
 @builtin
 card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int {
 	return int(intrinsics.count_ones(transmute(intrinsics.type_bit_set_underlying_type(S))s))
@@ -1030,6 +1088,10 @@ card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int {
 
 
 
+// Evaluates the condition and panics the program iff the condition is false.
+// This uses the `context.assertion_failure_procedure` to assert.
+//
+// This routine will be ignored when `ODIN_DISABLE_ASSERT` is true.
 @builtin
 @(disabled=ODIN_DISABLE_ASSERT)
 assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
@@ -1050,9 +1112,9 @@ assert :: proc(condition: bool, message := #caller_expression(condition), loc :=
 	}
 }
 
-// Evaluates the condition and aborts the program iff the condition is
-// false.  This routine ignores `ODIN_DISABLE_ASSERT`, and will always
-// execute.
+// Evaluates the condition and panics the program iff the condition is false.
+// This uses the `context.assertion_failure_procedure` to assert.
+// This routine ignores `ODIN_DISABLE_ASSERT`, and will always execute.
 @builtin
 ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
 	if !condition {
@@ -1068,6 +1130,8 @@ ensure :: proc(condition: bool, message := #caller_expression(condition), loc :=
 	}
 }
 
+// Panics the program with a message.
+// This uses the `context.assertion_failure_procedure` to panic.
 @builtin
 panic :: proc(message: string, loc := #caller_location) -> ! {
 	p := context.assertion_failure_proc
@@ -1077,6 +1141,8 @@ panic :: proc(message: string, loc := #caller_location) -> ! {
 	p("panic", message, loc)
 }
 
+// Panics the program with a message to indicate something has yet to be implemented.
+// This uses the `context.assertion_failure_procedure` to assert.
 @builtin
 unimplemented :: proc(message := "", loc := #caller_location) -> ! {
 	p := context.assertion_failure_proc
@@ -1086,7 +1152,10 @@ unimplemented :: proc(message := "", loc := #caller_location) -> ! {
 	p("not yet implemented", message, loc)
 }
 
-
+// Evaluates the condition and panics the program iff the condition is false.
+// This uses the `default_assertion_contextless_failure_proc` to assert.
+//
+// This routine will be ignored when `ODIN_DISABLE_ASSERT` is true.
 @builtin
 @(disabled=ODIN_DISABLE_ASSERT)
 assert_contextless :: proc "contextless" (condition: bool, message := #caller_expression(condition), loc := #caller_location) {
@@ -1103,6 +1172,8 @@ assert_contextless :: proc "contextless" (condition: bool, message := #caller_ex
 	}
 }
 
+// Evaluates the condition and panics the program iff the condition is false.
+// This uses the `default_assertion_contextless_failure_proc` to assert.
 @builtin
 ensure_contextless :: proc "contextless" (condition: bool, message := #caller_expression(condition), loc := #caller_location) {
 	if !condition {
@@ -1114,11 +1185,15 @@ ensure_contextless :: proc "contextless" (condition: bool, message := #caller_ex
 	}
 }
 
+// Panics the program with a message to indicate something has yet to be implemented.
+// This uses the `default_assertion_contextless_failure_proc` to assert.
 @builtin
 panic_contextless :: proc "contextless" (message: string, loc := #caller_location) -> ! {
 	default_assertion_contextless_failure_proc("panic", message, loc)
 }
 
+// Panics the program with a message.
+// This uses the `default_assertion_contextless_failure_proc` to assert.
 @builtin
 unimplemented_contextless :: proc "contextless" (message := "", loc := #caller_location) -> ! {
 	default_assertion_contextless_failure_proc("not yet implemented", message, loc)

+ 11 - 0
base/runtime/default_allocators_nil.odin

@@ -23,6 +23,14 @@ nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 	return nil, .None
 }
 
+// nil_allocator returns an allocator which will return `nil` for any result.
+// * `.Alloc`, `.Alloc_Non_Zero`, `.Resize`, `.Resize_Non_Zeroed` will return `nil, .Out_Of_Memory`
+// * `.Free` will return `nil, .None`
+// * `.Free_All` will return `nil, .Mode_Not_Implemented`
+// * `.Query_Features`, `.Query_Info` will return `nil, .Mode_Not_Implemented`
+//
+// This is extremely useful for creating a dynamic array from a buffer which does not nothing
+// on a resize/reserve beyond the originally allocated memory.
 @(require_results)
 nil_allocator :: proc "contextless" () -> Allocator {
 	return Allocator{
@@ -73,6 +81,9 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 	return nil, nil
 }
 
+// panic_allocator returns an allocator which will panic for any non-zero-sized allocation or `query_info`
+//
+// This is extremely useful for to check when something does a memory operation when it should not, and thus panic.
 @(require_results)
 panic_allocator :: proc() -> Allocator {
 	return Allocator{

+ 6 - 0
base/runtime/default_temporary_allocator.odin

@@ -4,6 +4,7 @@ DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKIN
 NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_DEFAULT_TO_NIL_ALLOCATOR
 
 when NO_DEFAULT_TEMP_ALLOCATOR {
+	// `Default_Temp_Allocator` is a `nil_allocator` when `NO_DEFAULT_TEMP_ALLOCATOR` is `true`.
 	Default_Temp_Allocator :: struct {}
 	
 	default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {}
@@ -20,6 +21,11 @@ when NO_DEFAULT_TEMP_ALLOCATOR {
 	default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
 	}
 } else {
+	// `Default_Temp_Allocator` is an `Arena` based type of allocator. See `runtime.Arena` for its implementation.
+	// The default `context.temp_allocator` is typically called with `free_all(context.temp_allocator)` once per "frame-loop"
+	// to prevent it from "leaking" memory.
+	//
+	// Note: `Default_Temp_Allocator` is a `nil_allocator` when `NO_DEFAULT_TEMP_ALLOCATOR` is `true`.
 	Default_Temp_Allocator :: struct {
 		arena: Arena,
 	}

+ 14 - 4
base/runtime/procs_darwin.odin

@@ -15,16 +15,25 @@ objc_SEL   :: ^intrinsics.objc_selector
 objc_Ivar  :: ^intrinsics.objc_ivar
 objc_BOOL  :: bool
 
+objc_super :: struct {
+	receiver: 	 objc_id,
+	super_class: objc_Class,
+}
 
 objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id
 
 foreign ObjC {
 	sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
 
-	objc_msgSend        :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
-	objc_msgSend_fpret  :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
-	objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
-	objc_msgSend_stret  :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
+	objc_msgSend             :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
+	objc_msgSend_fpret       :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
+	objc_msgSend_fp2ret      :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
+	objc_msgSend_stret       :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
+
+	// See: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-abi.h#L111
+	objc_msgSendSuper2       :: proc "c" (super: rawptr, op: objc_SEL, #c_vararg args: ..any) -> objc_id ---
+	objc_msgSendSuper2_stret :: proc "c" (super: ^objc_super, op: objc_SEL, #c_vararg args: ..any) ---
+
 
 	objc_lookUpClass          :: proc "c" (name: cstring) -> objc_Class ---
 	objc_allocateClassPair    :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
@@ -33,6 +42,7 @@ foreign ObjC {
 	class_addIvar             :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL ---
 	class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar ---
 	class_getInstanceSize     :: proc "c" (cls : objc_Class) -> uint ---
+	class_getSuperclass       :: proc "c" (cls : objc_Class) -> objc_Class ---
 	ivar_getOffset            :: proc "c" (v: objc_Ivar) -> uintptr ---
 	object_getClass           :: proc "c" (obj: objc_id) -> objc_Class ---
 }

+ 4 - 0
core/bufio/reader.odin

@@ -29,6 +29,7 @@ MIN_READ_BUFFER_SIZE :: 16
 @(private)
 DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
 
+// reader_init initializes using an `allocator`
 reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) {
 	size := size
 	size = max(size, MIN_READ_BUFFER_SIZE)
@@ -37,6 +38,7 @@ reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, all
 	b.buf = make([]byte, size, allocator, loc)
 }
 
+// reader_init initializes using a user provided bytes buffer `buf`
 reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {
 	reader_reset(b, rd)
 	b.buf_allocator = {}
@@ -49,10 +51,12 @@ reader_destroy :: proc(b: ^Reader) {
 	b^ = {}
 }
 
+// reader_size returns the number of bytes in the backing buffer
 reader_size :: proc(b: ^Reader) -> int {
 	return len(b.buf)
 }
 
+// reader_reset resets the read and write positions, and the error values
 reader_reset :: proc(b: ^Reader, r: io.Reader) {
 	b.rd = r
 	b.r, b.w = 0, 0

+ 21 - 7
core/bufio/scanner.odin

@@ -46,6 +46,7 @@ DEFAULT_MAX_SCAN_TOKEN_SIZE :: 1<<16
 @(private)
 _INIT_BUF_SIZE :: 4096
 
+// Initializes a Scanner buffer an allocator `buf_allocator`
 scanner_init :: proc(s: ^Scanner, r: io.Reader, buf_allocator := context.allocator) -> ^Scanner {
 	s.r = r
 	s.split = scan_lines
@@ -53,6 +54,8 @@ scanner_init :: proc(s: ^Scanner, r: io.Reader, buf_allocator := context.allocat
 	s.buf.allocator = buf_allocator
 	return s
 }
+
+// Initializes a Scanner buffer a user provided bytes buffer `buf`
 scanner_init_with_buffer :: proc(s: ^Scanner, r: io.Reader, buf: []byte) -> ^Scanner {
 	s.r = r
 	s.split = scan_lines
@@ -75,24 +78,27 @@ scanner_error :: proc(s: ^Scanner) -> Scanner_Error {
 	return s._err
 }
 
-// Returns the most recent token created by scanner_scan.
+// Returns the most recent token created by 'scan'.
 // The underlying array may point to data that may be overwritten
-// by another call to scanner_scan.
+// by another call to 'scan'.
 // Treat the returned value as if it is immutable.
 scanner_bytes :: proc(s: ^Scanner) -> []byte {
 	return s.token
 }
 
-// Returns the most recent token created by scanner_scan.
+// Returns the most recent token created by 'scan'.
 // The underlying array may point to data that may be overwritten
-// by another call to scanner_scan.
+// by another call to 'scan'.
 // Treat the returned value as if it is immutable.
 scanner_text :: proc(s: ^Scanner) -> string {
 	return string(s.token)
 }
 
-// scanner_scan advances the scanner
-scanner_scan :: proc(s: ^Scanner) -> bool {
+// scanner_scan is an alias of scan
+scanner_scan :: scan
+
+// scan advances the Scanner
+scan :: proc(s: ^Scanner) -> bool {
 	set_err :: proc(s: ^Scanner, err: Scanner_Error) {
 		switch s._err {
 		case nil, .EOF:
@@ -229,6 +235,7 @@ scanner_scan :: proc(s: ^Scanner) -> bool {
 	}
 }
 
+// scan_bytes is a splitting procedure that returns each byte as a token
 scan_bytes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
 	if at_eof && len(data) == 0 {
 		return
@@ -236,6 +243,10 @@ scan_bytes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte,
 	return 1, data[0:1], nil, false
 }
 
+// scan_runes is a splitting procedure that returns each UTF-8 encoded rune as a token.
+// The lsit of runes return is equivalent to that of iterating over a string in a 'for in' loop, meaning any
+// erroneous UTF-8 encodings will be returned as U+FFFD. Unfortunately this means it is impossible for the "client"
+// to know whether a U+FFFD is an expected replacement rune or an encoding of an error.
 scan_runes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
 	if at_eof && len(data) == 0 {
 		return
@@ -264,7 +275,8 @@ scan_runes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte,
 	token = ERROR_RUNE
 	return
 }
-
+// scan_words is a splitting procedure that returns each Unicode-space-separated word of text, excluding the surrounded spaces.
+// It will never return return an empty string.
 scan_words :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
 	is_space :: proc "contextless" (r:  rune) -> bool {
 		switch r {
@@ -312,6 +324,8 @@ scan_words :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte,
 	return
 }
 
+// scan_lines is a splitting procedure that returns each line of text stripping of any trailing newline and an optional preceding carriage return (\r?\n).
+// A new line is allowed to be empty.
 scan_lines :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
 	trim_carriage_return :: proc "contextless" (data: []byte) -> []byte {
 		if len(data) > 0 && data[len(data)-1] == '\r' {

+ 2 - 0
core/bufio/writer.odin

@@ -19,6 +19,7 @@ Writer :: struct {
 
 }
 
+// Initialized a Writer with an `allocator`
 writer_init :: proc(b: ^Writer, wr: io.Writer, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
 	size := size
 	size = max(size, MIN_READ_BUFFER_SIZE)
@@ -27,6 +28,7 @@ writer_init :: proc(b: ^Writer, wr: io.Writer, size: int = DEFAULT_BUF_SIZE, all
 	b.buf = make([]byte, size, allocator)
 }
 
+// Initialized a Writer with a user provided buffer `buf`
 writer_init_with_buf :: proc(b: ^Writer, wr: io.Writer, buf: []byte) {
 	writer_reset(b, wr)
 	b.buf_allocator = {}

+ 7 - 2
core/bytes/bytes.odin

@@ -134,8 +134,13 @@ equal_fold :: proc(u, v: []byte) -> bool {
 			return false
 		}
 
-		// TODO(bill): Unicode folding
-
+		r := unicode.simple_fold(sr)
+		for r != sr && r < tr {
+			r = unicode.simple_fold(sr)
+		}
+		if r == tr {
+			continue loop
+		}
 		return false
 	}
 

+ 5 - 1
core/encoding/json/marshal.odin

@@ -176,7 +176,11 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 		return .Unsupported_Type
 
 	case runtime.Type_Info_Pointer:
-		return .Unsupported_Type
+		if v.id == typeid_of(Null) {
+			io.write_string(w, "null") or_return
+		} else {
+			return .Unsupported_Type
+		}
 
 	case runtime.Type_Info_Multi_Pointer:
 		return .Unsupported_Type

+ 75 - 60
core/math/linalg/specific.odin

@@ -164,25 +164,25 @@ orthogonal :: proc{vector2_orthogonal, vector3_orthogonal}
 
 @(require_results)
 vector4_srgb_to_linear_f16 :: proc "contextless" (col: Vector4f16) -> Vector4f16 {
-	r := math.pow(col.x, 2.2)
-	g := math.pow(col.y, 2.2)
-	b := math.pow(col.z, 2.2)
+	r := math.pow((col.x + 0.055) / 1.055, 2.4) if col.x > 0.04045 else col.x / 12.92
+	g := math.pow((col.y + 0.055) / 1.055, 2.4) if col.y > 0.04045 else col.y / 12.92
+	b := math.pow((col.z + 0.055) / 1.055, 2.4) if col.z > 0.04045 else col.z / 12.92
 	a := col.w
 	return {r, g, b, a}
 }
 @(require_results)
 vector4_srgb_to_linear_f32 :: proc "contextless" (col: Vector4f32) -> Vector4f32 {
-	r := math.pow(col.x, 2.2)
-	g := math.pow(col.y, 2.2)
-	b := math.pow(col.z, 2.2)
+	r := math.pow((col.x + 0.055) / 1.055, 2.4) if col.x > 0.04045 else col.x / 12.92
+	g := math.pow((col.y + 0.055) / 1.055, 2.4) if col.y > 0.04045 else col.y / 12.92
+	b := math.pow((col.z + 0.055) / 1.055, 2.4) if col.z > 0.04045 else col.z / 12.92
 	a := col.w
 	return {r, g, b, a}
 }
 @(require_results)
 vector4_srgb_to_linear_f64 :: proc "contextless" (col: Vector4f64) -> Vector4f64 {
-	r := math.pow(col.x, 2.2)
-	g := math.pow(col.y, 2.2)
-	b := math.pow(col.z, 2.2)
+	r := math.pow((col.x + 0.055) / 1.055, 2.4) if col.x > 0.04045 else col.x / 12.92
+	g := math.pow((col.y + 0.055) / 1.055, 2.4) if col.y > 0.04045 else col.y / 12.92
+	b := math.pow((col.z + 0.055) / 1.055, 2.4) if col.z > 0.04045 else col.z / 12.92
 	a := col.w
 	return {r, g, b, a}
 }
@@ -192,70 +192,55 @@ vector4_srgb_to_linear :: proc{
 	vector4_srgb_to_linear_f64,
 }
 
-
 @(require_results)
-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
+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,
+}
 
-	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)
+@(require_results)
+vector4_linear_to_srgb_f16 :: proc "contextless" (col: Vector4f16) -> Vector4f16 {
+	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
core/math/math.odin

@@ -402,7 +402,12 @@ remap :: proc "contextless" (old_value, old_min, old_max, new_min, new_max: $T)
 	if old_range == 0 {
 		return new_range / 2
 	}
-	return ((old_value - old_min) / old_range) * new_range + new_min
+
+	when intrinsics.type_is_integer(T) {
+		return (((old_value - old_min)) * new_range) / old_range + new_min
+	} else {
+		return ((old_value - old_min) / old_range) * new_range + new_min
+	}
 }
 
 @(require_results)

+ 1 - 1
core/os/os2/path_linux.odin

@@ -199,7 +199,7 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath:
 	buf: [32]u8
 	copy(buf[:], PROC_FD_PATH)
 
-	strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
+	strconv.write_int(buf[len(PROC_FD_PATH):], i64(fd), 10)
 
 	if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
 		delete(fullpath, allocator)

+ 1 - 1
core/os/os_linux.odin

@@ -908,7 +908,7 @@ _dup :: proc(fd: Handle) -> (Handle, Error) {
 @(require_results)
 absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
 	buf : [256]byte
-	fd_str := strconv.itoa( buf[:], cast(int)fd )
+	fd_str := strconv.write_int( buf[:], cast(i64)fd, 10 )
 
 	procfs_path := strings.concatenate( []string{ "/proc/self/fd/", fd_str } )
 	defer delete(procfs_path)

+ 51 - 0
core/reflect/doc.odin

@@ -0,0 +1,51 @@
+// Package reflect provides utility procedures and types to perform runtime type introspection/reflection (RTTI).
+//
+// WARNING! THIS IS ADVANCED BEHAVIOUR FOR ODIN! THIS SHOULD NOT BE USED BY BEGINNERS TO ODIN!
+//
+// This package is only to be used by individuals who know exactly how the RTTI works as well as EXACTLY how `any` works.
+// Especially since `any` can be unintuitive in its use to many, it can be dangerous to use. It is highly recommend that you **do not**
+// use `any` unless you know exactly what you are doing.
+//
+// RTTI is an extremely powerful tool which should only be used when absolutely necessary (such runtime-type-safe formatted printing).
+//
+// ## The Type System of Odin
+//
+// It is important to understand how the type systems works in Odin before using any RTTI. A good example of this is Odin's `distinct` type system.
+// In Odin, `distinct` types are represented by `Type_Info_Named`. A named struct is a `Type_Info_Named` which then points to a `Type_Info_Struct`.
+// This means you must use something like `type_info_base` to restrict the `Type_Info_Named` aspect and get the base-type directly. Doing a type-assertion
+// on the variant will not work as (incorrectly) expected without doing this.
+//
+// ## Advanced Information of How `any` Works
+//
+// An overview of how `any` works:
+//
+// An `any` type can reference any data type. It is functionally equivalent to `struct {data: rawptr, id: typeid}` with extra semantics on
+// how assignment and type assertion works.
+//
+// This is commonly used to construct runtime-type-safe printing, such as in `core:fmt`.
+// The use of `any` outside of this is heavily discourage and should be only used by people who FULLY understand its semantics.
+//
+// The `any` value is only valid as long as the underlying data is still valid. Passing a literal to an `any` will allocate the literal in
+// the current stack frame.
+//
+// Example:
+// 	x: int = 123
+// 	a: any = x
+// 	// equivalent to
+// 	a: any
+// 	a.data = &x
+// 	a.id   = typeid_of(type_of(x))
+// 	// With literals
+// 	v: any = 123
+// 	// equivalent to
+// 	v: any
+// 	_tmp: int = 123
+// 	v.data = &_tmp
+// 	v.id   = typeid_of(type_of(_tmp))
+//
+//
+// `any` is a topologically-dual to a `union` in terms of its usage. Both support assignments of differing types
+// (`any` being open to any type, `union` being closed to a specific set of types), type assertions (`x.(T)`), and `switch in`.
+// The main internal difference is how the memory is stored. `any` being open is a pointer+typeid, a `union`
+// is a blob+tag. A `union` does not need to store a `typeid` because it is a closed ABI-consistent set of variant types.
+package reflect

+ 8 - 0
core/reflect/iterator.odin

@@ -2,6 +2,10 @@ package reflect
 
 import "base:runtime"
 
+// An iterator to dynamically iterate across something that is array-like (or pointer-to-array-like)
+// Example:
+// 	it: int // used as a tracking value
+// 	for elem, idx in iterate_array(any_array_val, &it) { ... }
 @(require_results)
 iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) {
 	if val == nil || it == nil {
@@ -45,6 +49,10 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) {
 	return
 }
 
+// An iterator to dynamically iterate across map (or pointer-to-map)
+// Example:
+// 	it: int // used as a tracking value
+// 	for key, val in iterate_map(any_map_val, &it) { ... }
 @(require_results)
 iterate_map :: proc(val: any, it: ^int) -> (key, value: any, ok: bool) {
 	if val == nil || it == nil {

+ 231 - 33
core/reflect/reflect.odin

@@ -70,6 +70,7 @@ Type_Kind :: enum {
 }
 
 
+// type_kind returns a enum `Type_Kind` to state what kind of type a typeid is
 @(require_results)
 type_kind :: proc(T: typeid) -> Type_Kind {
 	ti := type_info_of(T)
@@ -108,31 +109,51 @@ type_kind :: proc(T: typeid) -> Type_Kind {
 	return .Invalid
 }
 
-// TODO(bill): Better name
+// Returns the `Type_Kind` of the base-type of a typeid.
 @(require_results)
 underlying_type_kind :: proc(T: typeid) -> Type_Kind {
 	return type_kind(runtime.typeid_base(T))
 }
 
-// TODO(bill): Better name
+// Returns the `Type_Kind` of the core-type of a typeid. See
 @(require_results)
 backing_type_kind :: proc(T: typeid) -> Type_Kind {
 	return type_kind(runtime.typeid_core(T))
 }
 
 
+// type_info_base returns the base-type of a `^Type_Info` stripping the `distinct`ness from the first level
 type_info_base :: runtime.type_info_base
+
+// type_info_core returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR
+// returns the backing integer type of an enum or bit_set `^Type_Info`.
+// This is also aliased as `type_info_base_without_enum`
 type_info_core :: runtime.type_info_core 
+
+
+// type_info_base_without_enum returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR
+// returns the backing integer type of an enum or bit_set `^Type_Info`.
+// This is also aliased as `type_info_core`
 type_info_base_without_enum :: type_info_core
 
 
 when !ODIN_NO_RTTI {
+	// typeid_base returns the base-type of a `typeid` stripping the `distinct`ness from the first level
 	typeid_base :: runtime.typeid_base
+
+	// typeid_core returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR
+	// returns the backing integer type of an enum or bit_set `typeid`.
+	// This is also aliased as `typeid_base_without_enum`
 	typeid_core :: runtime.typeid_core
+
+	// typeid_base_without_enum returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR
+	// returns the backing integer type of an enum or bit_set `typeid`.
+	// This is also aliased as `typeid_core`
 	typeid_base_without_enum :: typeid_core
 }
 
 
+// any_base returns an `any` where the `typeid` has been replaced with the `base-type` equivalent
 @(require_results)
 any_base :: proc(v: any) -> any {
 	v := v
@@ -141,6 +162,8 @@ any_base :: proc(v: any) -> any {
 	}
 	return v
 }
+
+// any_core returns an `any` where the `typeid` has been replaced with the `core-type` equivalent
 @(require_results)
 any_core :: proc(v: any) -> any {
 	v := v
@@ -150,6 +173,20 @@ any_core :: proc(v: any) -> any {
 	return v
 }
 
+// typeid_elem returns a `typeid` of the element-type of a type if possible, otherwise it returns itself
+//     complex32  -> f16
+//     complex64  -> f32
+//     complex128 -> f64
+//     quaternion64  -> f16
+//     quaternion128 -> f32
+//     quaternion256 -> f64
+//     ^T         -> T
+//     [^]T       -> T
+//     #soa^T     -> T
+//     [N]T       -> T
+//     []T        -> T
+//     [dynamic]T -> T
+//     #simd[N]T  -> T
 @(require_results)
 typeid_elem :: proc(id: typeid) -> typeid {
 	ti := type_info_of(id)
@@ -160,11 +197,13 @@ typeid_elem :: proc(id: typeid) -> typeid {
 	#partial switch v in ti.variant {
 	case Type_Info_Complex:
 		switch bits {
+		case 32:  return f16
 		case 64:  return f32
 		case 128: return f64
 		}
 	case Type_Info_Quaternion:
 		switch bits {
+		case  64: return f16
 		case 128: return f32
 		case 256: return f64
 		}
@@ -181,6 +220,7 @@ typeid_elem :: proc(id: typeid) -> typeid {
 }
 
 
+// returns the size of the type that the passed typeid represents
 @(require_results)
 size_of_typeid :: proc(T: typeid) -> int {
 	if ti := type_info_of(T); ti != nil {
@@ -189,6 +229,7 @@ size_of_typeid :: proc(T: typeid) -> int {
 	return 0
 }
 
+// returns the alignment of the type that the passed typeid represents
 @(require_results)
 align_of_typeid :: proc(T: typeid) -> int {
 	if ti := type_info_of(T); ti != nil {
@@ -197,6 +238,7 @@ align_of_typeid :: proc(T: typeid) -> int {
 	return 1
 }
 
+// Reinterprets the data stored at `v` as a slice of bytes
 @(require_results)
 as_bytes :: proc(v: any) -> []byte {
 	if v != nil {
@@ -206,11 +248,13 @@ as_bytes :: proc(v: any) -> []byte {
 	return nil
 }
 
+// Splits the data stored in `any` into its two components: `data` and `id`
 @(require_results)
 any_data :: #force_inline proc(v: any) -> (data: rawptr, id: typeid) {
 	return v.data, v.id
 }
 
+// Returns true if the `any` value is either `nil` or the data stored at the address is all zeroed
 @(require_results)
 is_nil :: proc(v: any) -> bool {
 	if v == nil {
@@ -228,6 +272,16 @@ is_nil :: proc(v: any) -> bool {
 	return true
 }
 
+
+// Returns the length of the type that represents the `any` value, or returns 0 if not possible
+//     len(^T)   -> len(T)
+//     len([N]T) -> N
+//     len(#simd[N]T) -> N
+//     len([]T)
+//     len([dynamic]T)
+//     len(map[K]V)
+//     len(string) or len(cstring)
+//     len(string16) or len(cstring16)
 @(require_results)
 length :: proc(val: any) -> int {
 	if val == nil { return 0 }
@@ -255,10 +309,19 @@ length :: proc(val: any) -> int {
 		return runtime.map_len((^runtime.Raw_Map)(val.data)^)
 
 	case Type_Info_String:
-		if a.is_cstring {
-			return len((^cstring)(val.data)^)
-		} else {
-			return (^runtime.Raw_String)(val.data).len
+		switch a.encoding {
+		case .UTF_8:
+			if a.is_cstring {
+				return len((^cstring)(val.data)^)
+			} else {
+				return (^runtime.Raw_String)(val.data).len
+			}
+		case .UTF_16:
+			if a.is_cstring {
+				return len((^cstring16)(val.data)^)
+			} else {
+				return (^runtime.Raw_String16)(val.data).len
+			}
 		}
 
 	case Type_Info_Simd_Vector:
@@ -268,6 +331,12 @@ length :: proc(val: any) -> int {
 	return 0
 }
 
+// Returns the capacity of the type that represents the `any` value, or returns 0 if not possible
+//     cap(^T)        -> cap(T)
+//     cap([N]T)      -> N
+//     cap(#simd[N]T) -> N
+//     cap([dynamic]T)
+//     cap(map[K]V)
 @(require_results)
 capacity :: proc(val: any) -> int {
 	if val == nil { return 0 }
@@ -299,6 +368,7 @@ capacity :: proc(val: any) -> int {
 }
 
 
+// Dynamically indexes `any` as an indexable-type if possible. Returns `nil` if not possible
 @(require_results)
 index :: proc(val: any, i: int, loc := #caller_location) -> any {
 	if val == nil { return nil }
@@ -350,15 +420,25 @@ index :: proc(val: any, i: int, loc := #caller_location) -> any {
 	case Type_Info_String:
 		if a.is_cstring { return nil }
 
-		raw := (^runtime.Raw_String)(val.data)
-		runtime.bounds_check_error_loc(loc, i, raw.len)
-		offset := uintptr(size_of(u8) * i)
-		data := rawptr(uintptr(raw.data) + offset)
-		return any{data, typeid_of(u8)}
+		switch a.encoding {
+		case .UTF_8:
+			raw := (^runtime.Raw_String)(val.data)
+			runtime.bounds_check_error_loc(loc, i, raw.len)
+			offset := uintptr(size_of(u8) * i)
+			data := rawptr(uintptr(raw.data) + offset)
+			return any{data, typeid_of(u8)}
+		case .UTF_16:
+			raw := (^runtime.Raw_String16)(val.data)
+			runtime.bounds_check_error_loc(loc, i, raw.len)
+			offset := uintptr(size_of(u16) * i)
+			data := rawptr(uintptr(raw.data) + offset)
+			return any{data, typeid_of(u16)}
+		}
 	}
 	return nil
 }
 
+// Dereferences `any` if it represents a pointer-based value (`^T -> T`)
 @(require_results)
 deref :: proc(val: any) -> any {
 	if val != nil {
@@ -375,20 +455,22 @@ deref :: proc(val: any) -> any {
 
 
 
-// Struct_Tag represents the type of the string of a struct field
+// `Struct_Tag` represents the type of the `string` of a struct field
 //
-// Through convention, tags are the concatenation of optionally space separationed key:"value" pairs.
+// Through convention, tags are the concatenation of optionally space-separated key:"value" pairs.
 // Each key is a non-empty string which contains no control characters other than space, quotes, and colon.
 Struct_Tag :: distinct string
 
+// `Struct_Field` represents a information of a field of a struct
 Struct_Field :: struct {
 	name:     string,
 	type:     ^Type_Info,
 	tag:      Struct_Tag,
-	offset:   uintptr,
+	offset:   uintptr, // in bytes
 	is_using: bool,
 }
 
+// Returns a `Struct_Field` containing the information for a struct field of a typeid `T` at index `i`
 @(require_results)
 struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -404,6 +486,7 @@ struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) {
 	return
 }
 
+// Returns a `Struct_Field` containing the information for a struct field by `name` of a typeid `T`
 @(require_results)
 struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -422,6 +505,10 @@ struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) {
 	return
 }
 
+// Returns an `any` of a struct field specified by name
+// Example:
+// 	v := struct_field_value_by_name(the_struct, "field_name")
+// 	nested_value_through_using := struct_field_value_by_name(the_struct, "field_name", allow_using=true)
 @(require_results)
 struct_field_value_by_name :: proc(a: any, field: string, allow_using := false) -> any {
 	if a == nil { return nil }
@@ -452,6 +539,10 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false)
 	return nil
 }
 
+// Returns an `any` of a struct field specified by a `Struct_Field`
+// Example:
+// 	field := struct_field_value_by_name(the_struct, "field_name")
+// 	value_by_field := struct_field_value(the_struct, field)
 @(require_results)
 struct_field_value :: proc(a: any, field: Struct_Field) -> any {
 	if a == nil { return nil }
@@ -461,6 +552,7 @@ struct_field_value :: proc(a: any, field: Struct_Field) -> any {
 	}
 }
 
+// Returns a `[]string` of the names of the struct fields of type `T`
 @(require_results)
 struct_field_names :: proc(T: typeid) -> []string {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -470,6 +562,7 @@ struct_field_names :: proc(T: typeid) -> []string {
 	return nil
 }
 
+// Returns a `[]^Type_Info` of the types of the struct fields of type `T`
 @(require_results)
 struct_field_types :: proc(T: typeid) -> []^Type_Info {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -480,6 +573,7 @@ struct_field_types :: proc(T: typeid) -> []^Type_Info {
 }
 
 
+// Returns a `[]Struct_Tag` of the tags of the struct fields of type `T`
 @(require_results)
 struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -489,6 +583,7 @@ struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
 	return nil
 }
 
+// Returns a `[]uintptr` of the offsets in bytes of the struct fields of type `T`
 @(require_results)
 struct_field_offsets :: proc(T: typeid) -> []uintptr {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -498,6 +593,7 @@ struct_field_offsets :: proc(T: typeid) -> []uintptr {
 	return nil
 }
 
+// Struct_Field_Count_Method is the count method used by `struct_field_count` in order to find the number of fields
 Struct_Field_Count_Method :: enum {
 	Top_Level,
 	Using,
@@ -556,6 +652,10 @@ struct_field_count :: proc(T: typeid, method := Struct_Field_Count_Method.Top_Le
 	return
 }
 
+// Returns the fields of a struct type `T` as an `#soa` slice.
+// This is useful to iterate over.
+// Example:
+// 	for field, i in reflect.struct_fields_zipped(Foo) { ... }
 @(require_results)
 struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -572,13 +672,26 @@ struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) {
 }
 
 
-
+// struct_tag_get returns the value associated with a key in the tag string.
+// If the key is present in the tag, the value (which might be empty) is returned. Otherwise an empty string is returned.
+// This is just a wrapper around `struct_tag_lookup` with the `ok` value being ignored.
+//
+// The convention for struct tags is usually of the form:
+//
+// 	`key:"value" another:"set" and:"whatever"`
 @(require_results)
 struct_tag_get :: proc(tag: Struct_Tag, key: string) -> (value: string) {
 	v, _ := struct_tag_lookup(tag, key)
-	return string(v)
+	return v
 }
 
+// struct_tag_lookup returns the value associated with a key in the tag string.
+// If the key is present in the tag, the value (which might be empty) is return. Otherwise an empty string is returned.
+// The `ok` value returns whether the value was explicit set in the tag string.
+//
+// The convention for struct tags is usually of the form:
+//
+// 	`key:"value" another:"set" and:"whatever"`
 @(require_results)
 struct_tag_lookup :: proc(tag: Struct_Tag, key: string) -> (value: string, ok: bool) {
 	for t := tag; t != ""; /**/ {
@@ -638,6 +751,7 @@ struct_tag_lookup :: proc(tag: Struct_Tag, key: string) -> (value: string, ok: b
 }
 
 
+// Returns the string representation of an enum value. It will panic if the value passed is not an enum.
 @(require_results)
 enum_string :: proc(a: any) -> string {
 	if a == nil { return "" }
@@ -656,7 +770,7 @@ enum_string :: proc(a: any) -> string {
 	return ""
 }
 
-// Given a enum type and a value name, get the enum value.
+// Given an enum type and a value name, get the enum value.
 @(require_results)
 enum_from_name :: proc($Enum_Type: typeid, name: string) -> (value: Enum_Type, ok: bool) {
 	ti := type_info_base(type_info_of(Enum_Type))
@@ -674,6 +788,7 @@ enum_from_name :: proc($Enum_Type: typeid, name: string) -> (value: Enum_Type, o
 	return
 }
 
+// enum_from_name_any returns the value of an enum field's name if found, returns `0, false` otherwise.
 @(require_results)
 enum_from_name_any :: proc(Enum_Type: typeid, name: string) -> (value: Type_Info_Enum_Value, ok: bool) {
 	ti := runtime.type_info_base(type_info_of(Enum_Type))
@@ -690,6 +805,7 @@ enum_from_name_any :: proc(Enum_Type: typeid, name: string) -> (value: Type_Info
 	return
 }
 
+// enum_name_from_value returns the name of enum field if a valid name using parametric polymorphism, otherwise returns `"", false`
 @(require_results)
 enum_name_from_value :: proc(value: $Enum_Type) -> (name: string, ok: bool) where intrinsics.type_is_enum(Enum_Type) {
 	ti := type_info_base(type_info_of(Enum_Type))
@@ -706,6 +822,7 @@ enum_name_from_value :: proc(value: $Enum_Type) -> (name: string, ok: bool) wher
 	return
 }
 
+// enum_name_from_value_any returns the name of enum field if a valid name using reflection, otherwise returns `"", false`
 @(require_results)
 enum_name_from_value_any :: proc(value: any) -> (name: string, ok: bool) {
 	if value.id == nil {
@@ -725,9 +842,7 @@ enum_name_from_value_any :: proc(value: any) -> (name: string, ok: bool) {
 	return
 }
 
-/*
-Returns whether the value given has a defined name in the enum type.
-*/
+// Returns whether the value given has a defined name in the enum type.
 @(require_results)
 enum_value_has_name :: proc(value: $T) -> bool where intrinsics.type_is_enum(T) {
 	when len(T) == cap(T) {
@@ -749,6 +864,7 @@ enum_value_has_name :: proc(value: $T) -> bool where intrinsics.type_is_enum(T)
 
 
 
+// enum_field_names returns `[]string` of the names of the fields of type `Enum_Type`
 @(require_results)
 enum_field_names :: proc(Enum_Type: typeid) -> []string {
 	ti := runtime.type_info_base(type_info_of(Enum_Type))
@@ -757,6 +873,7 @@ enum_field_names :: proc(Enum_Type: typeid) -> []string {
 	}
 	return nil
 }
+// enum_field_values returns `[]Type_Info_Enum_Value` of the values of the fields of type `Enum_Type`
 @(require_results)
 enum_field_values :: proc(Enum_Type: typeid) -> []Type_Info_Enum_Value {
 	ti := runtime.type_info_base(type_info_of(Enum_Type))
@@ -766,11 +883,16 @@ enum_field_values :: proc(Enum_Type: typeid) -> []Type_Info_Enum_Value {
 	return nil
 }
 
+// Represents an `Enum_Field` storing the `name` and `value`
 Enum_Field :: struct {
 	name:  string,
 	value: Type_Info_Enum_Value,
 }
 
+// Returns a #soa slice of the enum field information of type `Enum_Type`
+// This is useful to iterate over.
+// Example:
+// 	for field, i in reflect.enum_fields_zipped(Foo) { ... }
 @(require_results)
 enum_fields_zipped :: proc(Enum_Type: typeid) -> (fields: #soa[]Enum_Field) {
 	ti := runtime.type_info_base(type_info_of(Enum_Type))
@@ -782,17 +904,20 @@ enum_fields_zipped :: proc(Enum_Type: typeid) -> (fields: #soa[]Enum_Field) {
 
 
 
+// Returns `^Type_Info` of a any-encoded union type. Panics if a union was not passed.
 @(require_results)
 union_variant_type_info :: proc(a: any) -> ^Type_Info {
 	id := union_variant_typeid(a)
 	return type_info_of(id)
 }
 
+// Returns whether the `Type_Info_Union` store no tag (called a "pure maybe").
 @(require_results)
 type_info_union_is_pure_maybe :: proc(info: runtime.Type_Info_Union) -> bool {
 	return len(info.variants) == 1 && is_pointer_internally(info.variants[0])
 }
 
+// UNSAFE: Returns `typeid` of a any-encoded union type. Panics if a union was not passed.
 @(require_results)
 union_variant_typeid :: proc(a: any) -> typeid {
 	if a == nil { return nil }
@@ -833,6 +958,7 @@ union_variant_typeid :: proc(a: any) -> typeid {
 	panic("expected a union to reflect.union_variant_typeid")
 }
 
+// UNSAFE: Returns the underlying tag value of a union. Panics if a union was not passed.
 @(require_results)
 get_union_variant_raw_tag :: proc(a: any) -> i64 {
 	if a == nil { return -1 }
@@ -864,6 +990,7 @@ get_union_variant_raw_tag :: proc(a: any) -> i64 {
 	panic("expected a union to reflect.get_union_variant_raw_tag")
 }
 
+// Returns the underlying variant value of a union. Panics if a union was not passed.
 @(require_results)
 get_union_variant :: proc(a: any) -> any {
 	if a == nil {
@@ -876,6 +1003,14 @@ get_union_variant :: proc(a: any) -> any {
 	return any{a.data, id}
 }
 
+// Converts a pointer to a union, to a union containing the pointers to the variant types, and stores a pointer of the variant value in the new union
+//
+// Example:
+// 	val: union{i32, f32, string}
+//	val = "123"
+// 	ptr: union{^i32, ^f32, ^string} = get_union_as_ptr_variants(&val)
+// 	sp := ptr.(^string)
+// 	assert(sp^ == "123")
 @(require_results)
 get_union_as_ptr_variants :: proc(val: ^$T) -> (res: intrinsics.type_convert_variants_to_pointers(T)) where intrinsics.type_is_union(T) {
 	ptr := rawptr(val)
@@ -886,7 +1021,7 @@ get_union_as_ptr_variants :: proc(val: ^$T) -> (res: intrinsics.type_convert_var
 }
 
 
-
+// UNSAFE: Manually set the tag value of a union using an integer. Panics if a union was not passed.
 set_union_variant_raw_tag :: proc(a: any, tag: i64) {
 	if a == nil { return }
 
@@ -917,6 +1052,7 @@ set_union_variant_raw_tag :: proc(a: any, tag: i64) {
 	panic("expected a union to reflect.set_union_variant_raw_tag")
 }
 
+// UNSAFE: Manually set the tag value of a union using a `typeid`. Panics if a union was not passed.
 set_union_variant_typeid :: proc(a: any, id: typeid) {
 	if a == nil { return }
 
@@ -947,6 +1083,7 @@ set_union_variant_typeid :: proc(a: any, id: typeid) {
 	panic("expected a union to reflect.set_union_variant_typeid")
 }
 
+// UNSAFE: Manually set the tag value of a union using a `^Type_Info`. Panics if a union was not passed.
 set_union_variant_type_info :: proc(a: any, tag_ti: ^Type_Info) {
 	if a == nil { return }
 
@@ -977,6 +1114,7 @@ set_union_variant_type_info :: proc(a: any, tag_ti: ^Type_Info) {
 	panic("expected a union to reflect.set_union_variant_type_info")
 }
 
+// UNSAFE: Manually set the variant value of a union using an `any`. Panics if a union was not passed.
 set_union_value :: proc(dst: any, value: any) -> bool {
 	if dst == nil { return false }
 
@@ -1015,6 +1153,7 @@ set_union_value :: proc(dst: any, value: any) -> bool {
 	panic("expected a union to reflect.set_union_variant_typeid")
 }
 
+// UNSAFE: Checks to see if the data stored is a `bit_set` and is big endian. Panics if a `bit_set` was not passed.
 @(require_results)
 bit_set_is_big_endian :: proc(value: any, loc := #caller_location) -> bool {
 	if value == nil { return ODIN_ENDIAN == .Big }
@@ -1046,6 +1185,10 @@ Bit_Field :: struct {
 	tag:    Struct_Tag,
 }
 
+// Returns the fields of a `bit_field` type `T` as an `#soa` slice.
+// This is useful to iterate over.
+// Example:
+// 	for field, i in reflect.bit_fields_zipped(Foo_Bit_Field) { ... }
 @(require_results)
 bit_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Bit_Field) {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -1061,6 +1204,7 @@ bit_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Bit_Field) {
 	return nil
 }
 
+// bit_field_names returns a `[]string` of the field names of a `bit_field` type `T`
 @(require_results)
 bit_field_names :: proc(T: typeid) -> []string {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -1070,6 +1214,7 @@ bit_field_names :: proc(T: typeid) -> []string {
 	return nil
 }
 
+// bit_field_types returns a `[]^Type_Info` of the field representation types of a `bit_field` type `T`, not the backing integer-bit-width types
 @(require_results)
 bit_field_types :: proc(T: typeid) -> []^Type_Info {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -1079,6 +1224,7 @@ bit_field_types :: proc(T: typeid) -> []^Type_Info {
 	return nil
 }
 
+// bit_field_types returns a `[]uintptr` of the field bit-width-sizes of a `bit_field` type `T`
 @(require_results)
 bit_field_sizes :: proc(T: typeid) -> []uintptr {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -1088,6 +1234,7 @@ bit_field_sizes :: proc(T: typeid) -> []uintptr {
 	return nil
 }
 
+// bit_field_types returns a `[]uintptr` of the field offsets in bits of a `bit_field` type `T`
 @(require_results)
 bit_field_offsets :: proc(T: typeid) -> []uintptr {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -1097,6 +1244,7 @@ bit_field_offsets :: proc(T: typeid) -> []uintptr {
 	return nil
 }
 
+// bit_field_types returns a `[]Struct_Tag` of the field tags of a `bit_field` type `T`
 @(require_results)
 bit_field_tags :: proc(T: typeid) -> []Struct_Tag {
 	ti := runtime.type_info_base(type_info_of(T))
@@ -1106,6 +1254,7 @@ bit_field_tags :: proc(T: typeid) -> []Struct_Tag {
 	return nil
 }
 
+// as_bool attempts to convert an `any` to a `bool`.
 @(require_results)
 as_bool :: proc(a: any) -> (value: bool, valid: bool) {
 	if a == nil { return }
@@ -1129,6 +1278,7 @@ as_bool :: proc(a: any) -> (value: bool, valid: bool) {
 	return
 }
 
+// as_int attempts to convert an `any` to a `int`.
 @(require_results)
 as_int :: proc(a: any) -> (value: int, valid: bool) {
 	v: i64
@@ -1137,6 +1287,7 @@ as_int :: proc(a: any) -> (value: int, valid: bool) {
 	return
 }
 
+// as_uint attempts to convert an `any` to a `uint`.
 @(require_results)
 as_uint :: proc(a: any) -> (value: uint, valid: bool) {
 	v: u64
@@ -1145,6 +1296,7 @@ as_uint :: proc(a: any) -> (value: uint, valid: bool) {
 	return
 }
 
+// as_i64 attempts to convert an `any` to a `i64`.
 @(require_results)
 as_i64 :: proc(a: any) -> (value: i64, valid: bool) {
 	if a == nil { return }
@@ -1253,6 +1405,7 @@ as_i64 :: proc(a: any) -> (value: i64, valid: bool) {
 	return
 }
 
+// as_u64 attempts to convert an `any` to a `u64`.
 @(require_results)
 as_u64 :: proc(a: any) -> (value: u64, valid: bool) {
 	if a == nil { return }
@@ -1363,6 +1516,7 @@ as_u64 :: proc(a: any) -> (value: u64, valid: bool) {
 }
 
 
+// as_f64 attempts to convert an `any` to a `f64`.
 @(require_results)
 as_f64 :: proc(a: any) -> (value: f64, valid: bool) {
 	if a == nil { return }
@@ -1480,6 +1634,7 @@ as_f64 :: proc(a: any) -> (value: f64, valid: bool) {
 }
 
 
+// as_string attempts to convert an `any` to a `string`.
 @(require_results)
 as_string :: proc(a: any) -> (value: string, valid: bool) {
 	if a == nil { return }
@@ -1500,6 +1655,27 @@ as_string :: proc(a: any) -> (value: string, valid: bool) {
 	return
 }
 
+// as_string16 attempts to convert an `any` to a `string16`.
+@(require_results)
+as_string16 :: proc(a: any) -> (value: string16, valid: bool) {
+	if a == nil { return }
+	a := a
+	ti := runtime.type_info_core(type_info_of(a.id))
+	a.id = ti.id
+
+	#partial switch info in ti.variant {
+	case Type_Info_String:
+		valid = true
+		switch v in a {
+		case string16:  value = v
+		case cstring16: value = string16(v)
+		case: valid = false
+		}
+	}
+
+	return
+}
+
 @(require_results)
 relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) -> rawptr {
 	_handle :: proc(ptr: ^$T) -> rawptr where intrinsics.type_is_integer(T) {
@@ -1543,6 +1719,8 @@ relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid)
 
 
 
+// as_pointer attempts to convert an `any` to a `rawptr`.
+// This only works for `^T`, `[^]T`, `cstring`, `cstring16` based types
 @(require_results)
 as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) {
 	if a == nil { return }
@@ -1551,14 +1729,15 @@ as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) {
 	a.id = ti.id
 
 	#partial switch info in ti.variant {
-	case Type_Info_Pointer:
+	case Type_Info_Pointer, Type_Info_Multi_Pointer:
 		valid = true
 		value = (^rawptr)(a.data)^
 
 	case Type_Info_String:
 		valid = true
 		switch v in a {
-		case cstring: value = rawptr(v)
+		case cstring:   value = rawptr(v)
+		case cstring16: value = rawptr(v)
 		case: valid = false
 		}
 	}
@@ -1567,6 +1746,7 @@ as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) {
 }
 
 
+// Returns the equivalent of doing `raw_data(v)` where `v` is a non-any value
 @(require_results)
 as_raw_data :: proc(a: any) -> (value: rawptr, valid: bool) {
 	if a == nil { return }
@@ -1578,8 +1758,10 @@ as_raw_data :: proc(a: any) -> (value: rawptr, valid: bool) {
 	case Type_Info_String:
 		valid = true
 		switch v in a {
-		case string:  value = raw_data(v)
-		case cstring: value = rawptr(v) // just in case
+		case string:    value = raw_data(v)
+		case cstring:   value = rawptr(v) // just in case
+		case string16:  value = raw_data(v)
+		case cstring16: value = rawptr(v) // just in case
 		case: valid = false
 		}
 
@@ -1604,10 +1786,13 @@ ne :: not_equal
 
 DEFAULT_EQUAL_MAX_RECURSION_LEVEL :: 32
 
+// Checks to see if two `any` values are not semantically equivalent
 @(require_results)
 not_equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_level := 0) -> bool {
 	return !equal(a, b, including_indirect_array_recursion, recursion_level)
 }
+
+// Checks to see if two `any` values are semantically equivalent
 @(require_results)
 equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_level := 0) -> bool {
 	if a == nil && b == nil {
@@ -1702,14 +1887,27 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 		return runtime.memory_compare(a.data, b.data, t.size) == 0
 		
 	case Type_Info_String:
-		if v.is_cstring {
-			x := string((^cstring)(a.data)^)
-			y := string((^cstring)(b.data)^)
-			return x == y
-		} else {
-			x := (^string)(a.data)^
-			y := (^string)(b.data)^
-			return x == y
+		switch v.encoding {
+		case .UTF_8:
+			if v.is_cstring {
+				x := string((^cstring)(a.data)^)
+				y := string((^cstring)(b.data)^)
+				return x == y
+			} else {
+				x := (^string)(a.data)^
+				y := (^string)(b.data)^
+				return x == y
+			}
+		case .UTF_16:
+			if v.is_cstring {
+				x := string16((^cstring16)(a.data)^)
+				y := string16((^cstring16)(b.data)^)
+				return x == y
+			} else {
+				x := (^string16)(a.data)^
+				y := (^string16)(b.data)^
+				return x == y
+			}
 		}
 		return true
 	case Type_Info_Array:

+ 65 - 0
core/reflect/types.odin

@@ -3,6 +3,10 @@ package reflect
 import "core:io"
 import "core:strings"
 
+
+// Returns true when the `^Type_Info`s are semantically equivalent types
+// Note: The pointers being identical should be enough to check but this is done to make sure in certain cases where it is non-trivial
+// and each value wants to be checked directly.
 @(require_results)
 are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 	if a == b {
@@ -187,6 +191,7 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 	return false
 }
 
+// Returns true if the base-type is a signed integer or just a float, false otherwise.
 @(require_results)
 is_signed :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
@@ -196,6 +201,7 @@ is_signed :: proc(info: ^Type_Info) -> bool {
 	}
 	return false
 }
+// Returns true if the base-type is an usigned integer, false otherwise.
 @(require_results)
 is_unsigned :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
@@ -206,6 +212,7 @@ is_unsigned :: proc(info: ^Type_Info) -> bool {
 	return false
 }
 
+// Returns true when it is a 1-byte wide integer type, false otherwise.
 @(require_results)
 is_byte :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
@@ -216,78 +223,108 @@ is_byte :: proc(info: ^Type_Info) -> bool {
 }
 
 
+// Returns true the base-type is an integer of any kind, false otherwise.
 @(require_results)
 is_integer :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Integer)
 	return ok
 }
+// Returns true the base-type is a rune, false otherwise.
 @(require_results)
 is_rune :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Rune)
 	return ok
 }
+// Returns true the base-type is a float of any kind, false otherwise.
 @(require_results)
 is_float :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Float)
 	return ok
 }
+// Returns true the base-type is a complex-type of any kind, false otherwise.
 @(require_results)
 is_complex :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Complex)
 	return ok
 }
+// Returns true the base-type is a quaternions any kind, false otherwise.
 @(require_results)
 is_quaternion :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Quaternion)
 	return ok
 }
+// Returns true the base-type is an `any`, false otherwise.
 @(require_results)
 is_any :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Any)
 	return ok
 }
+
+// Returns true the base-type is a string of any kind (string, cstring, string16, cstring16), false otherwise.
 @(require_results)
 is_string :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_String)
 	return ok
 }
+// Returns true the base-type is a cstring of any kind (cstring, cstring16), false otherwise.
 @(require_results)
 is_cstring :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	v, ok := type_info_base(info).variant.(Type_Info_String)
 	return ok && v.is_cstring
 }
+
+// Returns true the base-type is a string of any kind (string16, cstring16), false otherwise.
+@(require_results)
+is_string16 :: proc(info: ^Type_Info) -> bool {
+	if info == nil { return false }
+	v, ok := type_info_base(info).variant.(Type_Info_String)
+	return ok && v.encoding == .UTF_16
+}
+// Returns true the base-type is a cstring of any kind (cstring16), false otherwise.
+@(require_results)
+is_cstring16 :: proc(info: ^Type_Info) -> bool {
+	if info == nil { return false }
+	v, ok := type_info_base(info).variant.(Type_Info_String)
+	return ok && v.is_cstring && v.encoding == .UTF_16
+}
+
+// Returns true the base-type is a boolean of any kind, false otherwise.
 @(require_results)
 is_boolean :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Boolean)
 	return ok
 }
+// Returns true the base-type is a pointer-type of any kind (^T or rawptr), false otherwise.
 @(require_results)
 is_pointer :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Pointer)
 	return ok
 }
+// Returns true the base-type is a pointer-type of any kind ([^]T), false otherwise.
 @(require_results)
 is_multi_pointer :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Multi_Pointer)
 	return ok
 }
+// Returns true the base-type is a pointer-type of any kind (#soa^T), false otherwise.
 @(require_results)
 is_soa_pointer :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Soa_Pointer)
 	return ok
 }
+// Returns true when the type is a pointer-like type, false otherwise.
 @(require_results)
 is_pointer_internally :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
@@ -300,78 +337,91 @@ is_pointer_internally :: proc(info: ^Type_Info) -> bool {
 	}
 	return false
 }
+// Returns true when the type is a procedure type, false otherwise.
 @(require_results)
 is_procedure :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Procedure)
 	return ok
 }
+// Returns true when the type is a fixed-array type ([N]T), false otherwise.
 @(require_results)
 is_array :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Array)
 	return ok
 }
+// Returns true when the type is an enumerated-array type ([Enum]T), false otherwise.
 @(require_results)
 is_enumerated_array :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Enumerated_Array)
 	return ok
 }
+// Returns true when the type is a dynamic-array type ([dynamic]T), false otherwise.
 @(require_results)
 is_dynamic_array :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Dynamic_Array)
 	return ok
 }
+// Returns true when the type is a map type (map[K]V), false otherwise.
 @(require_results)
 is_dynamic_map :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Map)
 	return ok
 }
+// Returns true when the type is a bit_set type, false otherwise.
 @(require_results)
 is_bit_set :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Bit_Set)
 	return ok
 }
+// Returns true when the type is a slice type ([]T), false otherwise.
 @(require_results)
 is_slice :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Slice)
 	return ok
 }
+// Returns true when the type represents a set of parameters for a procedure (inputs or outputs), false otherwise.
 @(require_results)
 is_parameters :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Parameters)
 	return ok
 }
+// Returns true when the type is a struct type, `#raw_union` will be false. All other types will be false otherwise.
 @(require_results)
 is_struct :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	s, ok := type_info_base(info).variant.(Type_Info_Struct)
 	return ok && .raw_union not_in s.flags
 }
+// Returns true when the type is a struct type with `#raw_union` applied, when `#raw_union` is not applied, the value will be false. All other types will be false otherwise.
 @(require_results)
 is_raw_union :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	s, ok := type_info_base(info).variant.(Type_Info_Struct)
 	return ok && .raw_union in s.flags
 }
+// Returns true when the type is a union type (not `#raw_union`), false otherwise.
 @(require_results)
 is_union :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Union)
 	return ok
 }
+// Returns true when the type is an enum type, false otherwise.
 @(require_results)
 is_enum :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	_, ok := type_info_base(info).variant.(Type_Info_Enum)
 	return ok
 }
+// Returns true when the type is a #simd-array type (#simd[N]T), false otherwise.
 @(require_results)
 is_simd_vector :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
@@ -380,6 +430,9 @@ is_simd_vector :: proc(info: ^Type_Info) -> bool {
 }
 
 
+// Returns true when the core-type is represented with a platform-native endian type, and returns false otherwise.
+// This will also return false when the type is not an integer, pointer, or bit_set.
+// If the type is the same as the platform-native endian type (e.g. `u32le` on a little-endian system), this will return false.
 @(require_results)
 is_endian_platform :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false}
@@ -399,6 +452,9 @@ is_endian_platform :: proc(info: ^Type_Info) -> bool {
 	return false
 }
 
+// Returns true when the core-type is represented with a platform-native endian type or the same endianness as the system.
+// This will also return false when the type is not an integer, pointer, or bit_set.
+// If the type is the same as the platform-native endian type (e.g. `u32le` on a little-endian system), this will return true.
 @(require_results)
 is_endian_little :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false}
@@ -421,6 +477,9 @@ is_endian_little :: proc(info: ^Type_Info) -> bool {
 	return ODIN_ENDIAN == .Little
 }
 
+// Returns true when the core-type is represented with a platform-native endian type or the same endianness as the system.
+// This will also return false when the type is not an integer, pointer, or bit_set.
+// If the type is the same as the platform-native endian type (e.g. `u32be` on a big-endian system), this will return true.
 @(require_results)
 is_endian_big :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false}
@@ -446,27 +505,33 @@ is_endian_big :: proc(info: ^Type_Info) -> bool {
 
 
 
+// Writes a typeid in standard (non-canonical) form to a `strings.Builder`
 write_typeid_builder :: proc(buf: ^strings.Builder, id: typeid, n_written: ^int = nil) -> (n: int, err: io.Error) {
 	return write_type_writer(strings.to_writer(buf), type_info_of(id))
 }
+// Writes a typeid in standard (non-canonical) form to an `io.Writer`
 write_typeid_writer :: proc(writer: io.Writer, id: typeid, n_written: ^int = nil) -> (n: int, err: io.Error) {
 	return write_type_writer(writer, type_info_of(id), n_written)
 }
 
+// Writes a typeid in standard (non-canonical) form
 write_typeid :: proc{
 	write_typeid_builder,
 	write_typeid_writer,
 }
 
+// Writes a `^Type_Info` in standard (non-canonical) form
 write_type :: proc{
 	write_type_builder,
 	write_type_writer,
 }
 
+// Writes a `^Type_Info` in standard (non-canonical) form to a `strings.Builder`
 write_type_builder :: proc(buf: ^strings.Builder, ti: ^Type_Info) -> int {
 	n, _ := write_type_writer(strings.to_writer(buf), ti)
 	return n
 }
+// Writes a `^Type_Info` in standard (non-canonical) form to an `io.Writer`
 write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) {
 	defer if n_written != nil {
 		n_written^ += n

+ 24 - 0
core/strconv/deprecated.odin

@@ -36,3 +36,27 @@ append_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
 append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
 	return write_float(buf, f, fmt, prec, bit_size)
 }
+
+// 2025-10-03 Deprecated C short names and implementations
+
+@(deprecated="Use strconv.write_int() instead")
+itoa :: proc(buf: []byte, i: int) -> string {
+	return write_int(buf, i64(i), 10)
+}
+
+@(deprecated="Use strconv.parse_int() instead")
+atoi :: proc(s: string) -> int {
+	v, _ := parse_int(s)
+	return v
+}
+
+@(deprecated="Use strconv.parse_f64() instead")
+atof :: proc(s: string) -> f64 {
+	v, _  := parse_f64(s)
+	return v
+}
+
+@(deprecated="Use strconv.write_float() instead")
+ftoa :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
+	return string(generic_ftoa(buf, f, fmt, prec, bit_size))
+}

+ 1 - 78
core/strconv/strconv.odin

@@ -1547,85 +1547,8 @@ write_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
 }
 
 /*
-Converts an integer value to a string and stores it in the given buffer
+`ftoa` C name deprecated, use `write_float` instead (same procedure)
 
-**Inputs**
-- buf: The buffer to store the resulting string
-- i: The integer value to be converted
-
-Example:
-
-	import "core:fmt"
-	import "core:strconv"
-	itoa_example :: proc() {
-		buf: [4]byte
-		result := strconv.itoa(buf[:], 42)
-		fmt.println(result, buf) // "42"
-	}
-
-Output:
-
-	42 [52, 50, 0, 0]
-
-**Returns**
-- The resulting string after converting the integer value
-*/
-itoa :: proc(buf: []byte, i: int) -> string {
-	return write_int(buf, i64(i), 10)
-}
-/*
-Converts a string to an integer value
-
-**Inputs**
-- s: The string to be converted
-
-Example:
-
-	import "core:fmt"
-	import "core:strconv"
-	atoi_example :: proc() {
-		fmt.println(strconv.atoi("42"))
-	}
-
-Output:
-
-	42
-
-**Returns**
-- The resulting integer value
-*/
-atoi :: proc(s: string) -> int {
-	v, _ := parse_int(s)
-	return v
-}
-/*
-Converts a string to a float64 value
-
-**Inputs**
-- s: The string to be converted
-
-Example:
-
-	import "core:fmt"
-	import "core:strconv"
-	atof_example :: proc() {
-		fmt.printfln("%.3f", strconv.atof("3.14"))
-	}
-
-Output:
-
-	3.140
-
-**Returns**
-- The resulting float64 value after converting the string
-*/
-atof :: proc(s: string) -> f64 {
-	v, _  := parse_f64(s)
-	return v
-}
-// Alias to `write_float`
-ftoa :: write_float
-/*
 Writes a float64 value as a string to the given buffer with the specified format and precision
 
 **Inputs**

+ 7 - 2
core/strings/strings.odin

@@ -436,8 +436,13 @@ equal_fold :: proc(u, v: string) -> (res: bool) {
 			return false
 		}
 
-		// TODO(bill): Unicode folding
-
+		r := unicode.simple_fold(sr)
+		for r != sr && r < tr {
+			r = unicode.simple_fold(sr)
+		}
+		if r == tr {
+			continue loop
+		}
 		return false
 	}
 

+ 2 - 2
core/sys/wasm/js/odin.js

@@ -1937,7 +1937,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) {
 					if (buf_len > 0 && buf_ptr) {
 						let n = Math.min(buf_len, str.length);
 						str = str.substring(0, n);
-						this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str))
+						wasmMemoryInterface.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str))
 						return n;
 					}
 				}
@@ -2001,7 +2001,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) {
 					if (buf_len > 0 && buf_ptr) {
 						let n = Math.min(buf_len, str.length);
 						str = str.substring(0, n);
-						this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str))
+						wasmMemoryInterface.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str))
 						return n;
 					}
 				}

+ 118 - 122
core/text/match/strlib.odin

@@ -9,11 +9,12 @@ MAX_CAPTURES :: 32
 
 Capture :: struct {
 	init: int,
-	len: int,
+	len:  int,
 }
 
 Match :: struct {
-	byte_start, byte_end: int,
+	byte_start: int,
+	byte_end:   int,
 }
 
 Error :: enum {
@@ -27,18 +28,19 @@ Error :: enum {
 	Match_Invalid,
 }
 
-L_ESC :: '%'
-CAP_POSITION :: -2
+L_ESC          :: '%'
+CAP_POSITION   :: -2
 CAP_UNFINISHED :: -1
-INVALID :: -1
+INVALID        :: -1
 
 Match_State :: struct {
-	src: string,
+	src:     string,
 	pattern: string,
-	level: int,
+	level:   int,
 	capture: [MAX_CAPTURES]Capture,
 }
 
+@(require_results)
 match_class :: proc(c: rune, cl: rune) -> (res: bool) {
 	switch unicode.to_lower(cl) {
 	case 'a': res = is_alpha(c)
@@ -65,19 +67,23 @@ is_punct :: unicode.is_punct
 is_space :: unicode.is_space
 is_cntrl :: unicode.is_control
 
+@(require_results)
 is_alnum :: proc(c: rune) -> bool {
 	return unicode.is_alpha(c) || unicode.is_digit(c)
 }
 
+@(require_results)
 is_graph :: proc(c: rune) -> bool {
 	return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || unicode.is_digit(c)
 }
 
+@(require_results)
 is_xdigit :: proc(c: rune) -> bool {
 	return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || unicode.is_digit(c)
 }
 
 // find the first utf8 charater and its size, return an error if the character is an error
+@(require_results)
 utf8_peek :: proc(bytes: string) -> (c: rune, size: int, err: Error) {
 	c, size = utf8.decode_rune_in_string(bytes)
 
@@ -90,6 +96,7 @@ utf8_peek :: proc(bytes: string) -> (c: rune, size: int, err: Error) {
 
 // find the first utf8 charater and its size and advance the index
 // return an error if the character is an error
+@(require_results)
 utf8_advance :: proc(bytes: string, index: ^int) -> (c: rune, err: Error) {
 	size: int
 	c, size = utf8.decode_rune_in_string(bytes[index^:])
@@ -103,10 +110,12 @@ utf8_advance :: proc(bytes: string, index: ^int) -> (c: rune, err: Error) {
 }
 
 // continuation byte?
+@(require_results)
 is_cont :: proc(b: byte) -> bool {
 	return b & 0xc0 == 0x80
 }
 
+@(require_results)
 utf8_prev :: proc(bytes: string, a, b: int) -> int {
 	b := b
 
@@ -117,6 +126,7 @@ utf8_prev :: proc(bytes: string, a, b: int) -> int {
 	return a < b ? b - 1 : a
 }
 
+@(require_results)
 utf8_next :: proc(bytes: string, a: int) -> int {
 	a := a
 	b := len(bytes)
@@ -128,6 +138,7 @@ utf8_next :: proc(bytes: string, a: int) -> int {
 	return a < b ? a + 1 : b
 }
 
+@(require_results)
 check_capture :: proc(ms: ^Match_State, l: rune) -> (int, Error) {
 	l := int(l - '1')
 	
@@ -138,6 +149,7 @@ check_capture :: proc(ms: ^Match_State, l: rune) -> (int, Error) {
 	return l, .OK
 }
 
+@(require_results)
 capture_to_close :: proc(ms: ^Match_State) -> (int, Error) {
 	level := ms.level - 1
 
@@ -152,6 +164,7 @@ capture_to_close :: proc(ms: ^Match_State) -> (int, Error) {
 	return 0, .Invalid_Pattern_Capture
 }
 
+@(require_results)
 class_end :: proc(ms: ^Match_State, p: int) -> (step: int, err: Error) {
 	step = p
 	ch := utf8_advance(ms.pattern, &step) or_return
@@ -163,7 +176,7 @@ class_end :: proc(ms: ^Match_State, p: int) -> (step: int, err: Error) {
 			return
 		}
 
-		utf8_advance(ms.pattern, &step) or_return
+		_ = utf8_advance(ms.pattern, &step) or_return
 
 	case '[': 
 		// fine with step by 1
@@ -198,6 +211,7 @@ class_end :: proc(ms: ^Match_State, p: int) -> (step: int, err: Error) {
 	return
 }
 
+@(require_results)
 match_bracket_class :: proc(ms: ^Match_State, c: rune, p, ec: int) -> (sig: bool, err: Error) {
 	sig = true
 	p := p
@@ -240,6 +254,7 @@ match_bracket_class :: proc(ms: ^Match_State, c: rune, p, ec: int) -> (sig: bool
 	return
 }
 
+@(require_results)
 single_match :: proc(ms: ^Match_State, s, p, ep: int) -> (matched: bool, schar_size: int, err: Error) {
 	if s >= len(ms.src) {
 		return
@@ -254,13 +269,16 @@ single_match :: proc(ms: ^Match_State, s, p, ep: int) -> (matched: bool, schar_s
 	case L_ESC: 
 		pchar_next, _ := utf8_peek(ms.pattern[p + psize:]) or_return
 		matched = match_class(schar, pchar_next)
-	case '[': matched = match_bracket_class(ms, schar, p, ep - 1) or_return
-	case: matched = schar == pchar
+	case '[':
+		matched = match_bracket_class(ms, schar, p, ep - 1) or_return
+	case:
+		matched = schar == pchar
 	}
 
 	return
 }
 
+@(require_results)
 match_balance :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) {
 	if p >= len(ms.pattern) - 1 {
 		return INVALID, .Invalid_Pattern_Capture
@@ -300,13 +318,13 @@ match_balance :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error)
 	return INVALID, .OK
 }
 
+@(require_results)
 max_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) {
 	m := s
 
 	// count up matches
 	for {
 		matched, size := single_match(ms, m, p, ep) or_return
-		
 		if !matched {
 			break
 		}
@@ -316,7 +334,6 @@ max_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) {
 
 	for s <= m {
 		result := match(ms, m, ep + 1) or_return
-
 		if result != INVALID {
 			return result, .OK
 		}
@@ -331,6 +348,7 @@ max_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) {
 	return INVALID, .OK
 }
 
+@(require_results)
 min_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) {
 	s := s
 
@@ -339,19 +357,19 @@ min_expand :: proc(ms: ^Match_State, s, p, ep: int) -> (res: int, err: Error) {
 
 		if result != INVALID {
 			return result, .OK
-		} else {
-			// TODO receive next step maybe?
-			matched, rune_size := single_match(ms, s, p, ep) or_return
+		}
+		// TODO receive next step maybe?
+		matched, rune_size := single_match(ms, s, p, ep) or_return
 
-			if matched {
-				s += rune_size
-			} else {
-				return INVALID, .OK
-			}
+		if matched {
+			s += rune_size
+		} else {
+			return INVALID, .OK
 		}
 	}
 }
 
+@(require_results)
 start_capture :: proc(ms: ^Match_State, s, p, what: int) -> (res: int, err: Error) {
 	level := ms.level
 
@@ -366,6 +384,7 @@ start_capture :: proc(ms: ^Match_State, s, p, what: int) -> (res: int, err: Erro
 	return
 }
 
+@(require_results)
 end_capture :: proc(ms: ^Match_State, s, p: int) -> (res: int, err: Error) {
 	l := capture_to_close(ms) or_return
 	
@@ -379,6 +398,7 @@ end_capture :: proc(ms: ^Match_State, s, p: int) -> (res: int, err: Error) {
 	return
 }
 
+@(require_results)
 match_capture :: proc(ms: ^Match_State, s: int, char: rune) -> (res: int, err: Error) {
 	index := check_capture(ms, char) or_return
 	length := ms.capture[index].len
@@ -390,6 +410,7 @@ match_capture :: proc(ms: ^Match_State, s: int, char: rune) -> (res: int, err: E
 	return INVALID, .OK
 }
 
+@(require_results)
 match :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) {
 	s := s
 	p := p
@@ -431,7 +452,6 @@ match :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) {
 		// balanced string
 		case 'b': 
 			s = match_balance(ms, s, p + 2) or_return
-
 			if s != INVALID {
 				// eg after %b()
 				return match(ms, s, p + 4)
@@ -460,7 +480,7 @@ match :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) {
 			}
 
 			m1 := match_bracket_class(ms, previous, p, ep - 1) or_return
-			m2 := match_bracket_class(ms, current, p, ep - 1) or_return
+			m2 := match_bracket_class(ms, current,  p, ep - 1) or_return
 
 			if !m1 && m2 {
 				return match(ms, s, ep)
@@ -486,6 +506,7 @@ match :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) {
 	return s, .OK
 }
 
+@(require_results)
 match_default :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error) {
 	s := s
 	ep := class_end(ms, p) or_return
@@ -495,8 +516,10 @@ match_default :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error)
 		epc := ep < len(ms.pattern) ? ms.pattern[ep] : 0
 
 		switch epc {
-		case '*', '?', '-': return match(ms, s, ep + 1)
-		case: s = INVALID
+		case '*', '?', '-':
+			return match(ms, s, ep + 1)
+		case:
+			s = INVALID
 		}
 	} else {
 		epc := ep < len(ms.pattern) ? ms.pattern[ep] : 0
@@ -505,22 +528,23 @@ match_default :: proc(ms: ^Match_State, s, p: int) -> (unused: int, err: Error)
 		case '?':
 			result := match(ms, s + ssize, ep + 1) or_return
 			
-			if result != INVALID {
-				s = result
-			} else {
+			if result == INVALID {
 				return match(ms, s, ep + 1)
 			}
+			s = result
 
 		case '+': s = max_expand(ms, s + ssize, p, ep) or_return
-		case '*': s = max_expand(ms, s, p, ep) or_return
-		case '-': s = min_expand(ms, s, p, ep) or_return
-		case: return match(ms, s + ssize, ep)
+		case '*': s = max_expand(ms, s,         p, ep) or_return
+		case '-': s = min_expand(ms, s,         p, ep) or_return
+		case:
+			return match(ms, s + ssize, ep)
 		}
 	}
 
 	return s, .OK
 }
 
+@(require_results)
 push_onecapture :: proc(ms: ^Match_State,  i: int,  s: int, e: int, matches: []Match) -> (err: Error) {
 	if i >= ms.level {
 		if i == 0 {
@@ -533,22 +557,21 @@ push_onecapture :: proc(ms: ^Match_State,  i: int,  s: int, e: int, matches: []M
 		length := ms.capture[i].len
 
 		switch length {
-		case CAP_UNFINISHED: err = .Unfinished_Capture
-		case CAP_POSITION: matches[i] = { init, init + 1 }
-		case: matches[i] = { init, init + length }
+		case CAP_UNFINISHED:
+			err = .Unfinished_Capture
+		case CAP_POSITION:
+			matches[i] = { init, init + 1 }
+		case:
+			matches[i] = { init, init + length }
 		}
 	}
 
 	return
 }
 
-push_captures :: proc(
-	ms: ^Match_State,
-	s: int,
-	e: int,
-	matches: []Match,
-) -> (nlevels: int, err: Error) {
-	nlevels = 1 if ms.level == 0 && s != -1 else ms.level
+@(require_results)
+push_captures :: proc(ms: ^Match_State, s, e: int, matches: []Match) -> (nlevels: int, err: Error) {
+	nlevels = 1 if ms.level == 0 && s >= 0 else ms.level
 
 	for i in 0..<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
-
-			if s1[init - 1:end] == s2 {
-				return init - 1
-			} else {
-				next := strings.index_byte(s1[init:], s2[0])
-
-				if next == -1 {
-					return -1
-				} else {
-					init = init + next
-					end = init + l2
-				}
-			}
+	}
+
+	init := strings.index_byte(s1, s2[0])
+	end := init + l2
+
+	for end <= l1 && init >= 0 {
+		init += 1
+
+		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 - 0
core/unicode/fold.odin

@@ -0,0 +1,79 @@
+package unicode
+
+// simple_fold iterates over the Unicode code points equivalent under the Unicode defined simple case folding.
+// simple_fold returns the smallest rune > r if one exists, or the smallest rune >= 0.
+// If no valid Unicode code point exists, r is returned.
+//
+// Example:
+// 	simple_fold('A')      == 'a'
+// 	simple_fold('a')      == 'A'
+// 	simple_fold('Z')      == 'z'
+// 	simple_fold('z')      == 'Z'
+// 	simple_fold('7')      == '7'
+// 	simple_fold('k')      == '\u212a' (Kelvin symbol, K)
+// 	simple_fold('\u212a') == 'k'
+// 	simple_fold(-3)       == -3
+@(require_results)
+simple_fold :: proc(r: rune) -> rune {
+	Fold_Pair :: struct {
+		from: u16,
+		to:   u16,
+	}
+
+	@(static, rodata)
+	ASCII_FOLD := [MAX_ASCII + 1]u16{
+		0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+		0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+		0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+		0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+		0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+		0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+		0x0060, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x212a, 0x004c, 0x004d, 0x004e, 0x004f,
+		0x0050, 0x0051, 0x0052, 0x017f, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+	}
+
+	@(static, rodata)
+	CASE_ORBIT := [?]Fold_Pair{
+		{0x004B, 0x006B}, {0x0053, 0x0073}, {0x006B, 0x212A}, {0x0073, 0x017F}, {0x00B5, 0x039C}, {0x00C5, 0x00E5}, {0x00DF, 0x1E9E},
+		{0x00E5, 0x212B}, {0x0130, 0x0130}, {0x0131, 0x0131}, {0x017F, 0x0053}, {0x01C4, 0x01C5}, {0x01C5, 0x01C6}, {0x01C6, 0x01C4},
+		{0x01C7, 0x01C8}, {0x01C8, 0x01C9}, {0x01C9, 0x01C7}, {0x01CA, 0x01CB}, {0x01CB, 0x01CC}, {0x01CC, 0x01CA}, {0x01F1, 0x01F2},
+		{0x01F2, 0x01F3}, {0x01F3, 0x01F1}, {0x0345, 0x0399}, {0x0392, 0x03B2}, {0x0395, 0x03B5}, {0x0398, 0x03B8}, {0x0399, 0x03B9},
+		{0x039A, 0x03BA}, {0x039C, 0x03BC}, {0x03A0, 0x03C0}, {0x03A1, 0x03C1}, {0x03A3, 0x03C2}, {0x03A6, 0x03C6}, {0x03A9, 0x03C9},
+		{0x03B2, 0x03D0}, {0x03B5, 0x03F5}, {0x03B8, 0x03D1}, {0x03B9, 0x1FBE}, {0x03BA, 0x03F0}, {0x03BC, 0x00B5}, {0x03C0, 0x03D6},
+		{0x03C1, 0x03F1}, {0x03C2, 0x03C3}, {0x03C3, 0x03A3}, {0x03C6, 0x03D5}, {0x03C9, 0x2126}, {0x03D0, 0x0392}, {0x03D1, 0x03F4},
+		{0x03D5, 0x03A6}, {0x03D6, 0x03A0}, {0x03F0, 0x039A}, {0x03F1, 0x03A1}, {0x03F4, 0x0398}, {0x03F5, 0x0395}, {0x0412, 0x0432},
+		{0x0414, 0x0434}, {0x041E, 0x043E}, {0x0421, 0x0441}, {0x0422, 0x0442}, {0x042A, 0x044A}, {0x0432, 0x1C80}, {0x0434, 0x1C81},
+		{0x043E, 0x1C82}, {0x0441, 0x1C83}, {0x0442, 0x1C84}, {0x044A, 0x1C86}, {0x0462, 0x0463}, {0x0463, 0x1C87}, {0x1C80, 0x0412},
+		{0x1C81, 0x0414}, {0x1C82, 0x041E}, {0x1C83, 0x0421}, {0x1C84, 0x1C85}, {0x1C85, 0x0422}, {0x1C86, 0x042A}, {0x1C87, 0x0462},
+		{0x1C88, 0xA64A}, {0x1E60, 0x1E61}, {0x1E61, 0x1E9B}, {0x1E9B, 0x1E60}, {0x1E9E, 0x00DF}, {0x1FBE, 0x0345}, {0x2126, 0x03A9},
+		{0x212A, 0x004B}, {0x212B, 0x00C5}, {0xA64A, 0xA64B}, {0xA64B, 0x1C88},
+	}
+
+	if r < 0 || r > MAX_RUNE {
+		return r
+	}
+	if int(r) < len(ASCII_FOLD) {
+		return rune(ASCII_FOLD[r])
+	}
+
+	lo, hi := 0, len(CASE_ORBIT)
+	for lo < hi {
+		m := int(uint(lo+hi) >> 1)
+		if rune(CASE_ORBIT[m].from) < r {
+			lo = m + 1
+		} else {
+			hi = m
+		}
+	}
+
+	if lo < len(CASE_ORBIT) && rune(CASE_ORBIT[lo].from) == r {
+		return rune(CASE_ORBIT[lo].to)
+	}
+
+
+	l := to_lower(r)
+	if l != r {
+		return l
+	}
+	return to_upper(r)
+}

+ 56 - 3
src/check_builtin.cpp

@@ -210,7 +210,7 @@ gb_internal ObjcMsgKind get_objc_proc_kind(Type *return_type) {
 	return ObjcMsg_normal;
 }
 
-gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice<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:

+ 80 - 42
src/check_decl.cpp

@@ -587,9 +587,7 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
 					super = named_type->Named.type_name->TypeName.objc_superclass;
 				}
 			} else {
-				if (ac.objc_superclass != nullptr) {
-					error(e->token, "@(objc_superclass) may only be applied when the @(obj_implement) attribute is also applied");
-				} else if (ac.objc_ivar != nullptr) {
+				if (ac.objc_ivar != nullptr) {
 					error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied");
 				} else if (ac.objc_context_provider != nullptr) {
 					error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied");
@@ -1084,61 +1082,100 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon
 		// Enable implementation by default if the class is an implementer too and
 		// @objc_implement was not set to false explicitly in this proc.
 		bool implement = tn->TypeName.objc_is_implementation;
+		if( ac.objc_is_implementation && !tn->TypeName.objc_is_implementation ) {
+			error(e->token, "Cannot apply @(objc_is_implement) to a procedure whose type does not also have @(objc_is_implement) set");
+		}
+
 		if (ac.objc_is_disabled_implement) {
 			implement = false;
 		}
 
-		if (implement) {
-			GB_ASSERT(e->kind == Entity_Procedure);
+		String objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name;
+
+		if (e->kind == Entity_Procedure) {
+			bool has_body = e->decl_info->proc_lit->ProcLit.body != nullptr;
+			e->Procedure.is_objc_impl_or_import = implement || !has_body;
+			e->Procedure.is_objc_class_method   = ac.objc_is_class_method;
+			e->Procedure.objc_selector_name     = objc_selector;
+			e->Procedure.objc_class             = tn;
 
 			auto &proc = e->type->Proc;
 			Type *first_param = proc.param_count > 0 ? proc.params->Tuple.variables[0]->type : t_untyped_nil;
 
-			if (!tn->TypeName.objc_is_implementation) {
-				error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied");
-			} else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) {
-				error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)");
-			} else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) {
-				error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set");
-			} else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) {
-				error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention");
-			} else if (proc.result_count > 1) {
-				error(e->token, "Objective-C method implementations may return at most 1 value");
-			} else {
-				// Always export unconditionally
-				// NOTE(harold): This means check_objc_methods() MUST be called before
-				//				 e->Procedure.is_export is set in check_proc_decl()!
-				if (ac.is_export) {
-					error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly");
-				}
-				if (ac.link_name != "") {
-					error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly");
-				}
+			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 {
+					// 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");
+					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;
+					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));
+					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;
+					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);
+						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
src/check_expr.cpp

@@ -1296,11 +1296,6 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ
 						error_line("\t      Got:      %s\n", s_got);
 						gb_string_free(s_got);
 						gb_string_free(s_expected);
-
-						Type *tx = x->Proc.params->Tuple.variables[0]->type;
-						Type *ty = y->Proc.params->Tuple.variables[0]->type;
-						gb_printf_err("%s kind:%.*s e:%p ot:%p\n", type_to_string(tx), LIT(type_strings[tx->kind]), tx->Named.type_name, tx->Named.type_name->TypeName.original_type_for_parapoly);
-						gb_printf_err("%s kind:%.*s e:%p ot:%p\n", type_to_string(ty), LIT(type_strings[ty->kind]), ty->Named.type_name, ty->Named.type_name->TypeName.original_type_for_parapoly);
 					} else {
 						gbString s_expected = type_to_string(y);
 						gbString s_got = type_to_string(x);
@@ -6216,7 +6211,6 @@ gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize *
 					continue;
 				}
 			}
-			break;
 		}
 	}
 
@@ -7488,8 +7482,6 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 		Entity *e = proc_entities[valids[0].index];
 		GB_ASSERT(e != nullptr);
 
-		Array<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
src/checker.cpp

@@ -1416,6 +1416,8 @@ gb_internal void init_universal(void) {
 		t_objc_SEL      = alloc_type_pointer(t_objc_selector);
 		t_objc_Class    = alloc_type_pointer(t_objc_class);
 		t_objc_Ivar     = alloc_type_pointer(t_objc_ivar);
+
+		t_objc_instancetype = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_instancetype"), t_objc_id);
 	}
 }
 
@@ -1499,9 +1501,12 @@ gb_internal void destroy_checker_info(CheckerInfo *i) {
 
 	map_destroy(&i->objc_msgSend_types);
 	string_set_destroy(&i->obcj_class_name_set);
-	mpsc_destroy(&i->objc_class_implementations);
 	map_destroy(&i->objc_method_implementations);
 
+	// NOTE(harold): Disabling this: It can cause the 'count == 0' assert to trigger
+	//               when there's checker errors and the queue is still full as it did not reach the generation stage.
+	// mpsc_destroy(&i->objc_class_implementations);
+
 	string_map_destroy(&i->load_file_cache);
 	string_map_destroy(&i->load_directory_cache);
 	map_destroy(&i->load_directory_map);
@@ -3386,12 +3391,20 @@ gb_internal void init_core_map_type(Checker *c) {
 	t_raw_map_ptr       = alloc_type_pointer(t_raw_map);
 }
 
+gb_internal void init_core_objc_c(Checker *c) {
+	if (build_context.metrics.os == TargetOs_darwin) {
+		t_objc_super     = find_core_type(c, str_lit("objc_super"));
+		t_objc_super_ptr = alloc_type_pointer(t_objc_super);
+	}
+}
+
 gb_internal void init_preload(Checker *c) {
 	init_core_type_info(c);
 	init_mem_allocator(c);
 	init_core_context(c);
 	init_core_source_code_location(c);
 	init_core_map_type(c);
+	init_core_objc_c(c);
 }
 
 gb_internal ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) {

+ 3 - 1
src/checker_builtin_procs.hpp

@@ -354,6 +354,7 @@ BuiltinProc__type_end,
 	BuiltinProc_objc_register_class,
 	BuiltinProc_objc_ivar_get,
 	BuiltinProc_objc_block,
+	BuiltinProc_objc_super,
 
 	BuiltinProc_constant_utf16_cstring,
 
@@ -715,7 +716,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
 	{STR_LIT("objc_register_class"),    1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
 	{STR_LIT("objc_ivar_get"),          1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
-	{STR_LIT("objc_block"),             1, true,  Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
+	{STR_LIT("objc_block"),             1, true,  Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("objc_super"),             1, true,  Expr_Expr, BuiltinProcPkg_intrinsics},
 
 	{STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 

+ 4 - 0
src/entity.cpp

@@ -251,6 +251,8 @@ struct Entity {
 			String  link_name;
 			String  link_prefix;
 			String  link_suffix;
+			String  objc_selector_name;
+			Entity *objc_class;
 			DeferredProcedure deferred_procedure;
 
 			struct GenProcsData *gen_procs;
@@ -266,6 +268,8 @@ struct Entity {
 			bool    is_anonymous               : 1;
 			bool    no_sanitize_address        : 1;
 			bool    no_sanitize_memory         : 1;
+			bool    is_objc_impl_or_import     : 1;
+			bool    is_objc_class_method       : 1;
 		} Procedure;
 		struct {
 			Array<Entity *> entities;

+ 117 - 53
src/llvm_backend.cpp

@@ -1417,8 +1417,21 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) {
 		return str_lit("?");
 	case Type_Proc:
 		return str_lit("?");
-	case Type_BitSet:
-		return lb_get_objc_type_encoding(t->BitSet.underlying, pointer_depth);
+	case Type_BitSet: {
+		Type *bitset_integer_type = t->BitSet.underlying;
+		if (!bitset_integer_type) {
+			switch (t->cached_size) {
+				case 1:  bitset_integer_type = t_u8;   break;
+				case 2:  bitset_integer_type = t_u16;  break;
+				case 4:  bitset_integer_type = t_u32;  break;
+				case 8:  bitset_integer_type = t_u64;  break;
+				case 16: bitset_integer_type = t_u128; break;
+			}
+		}
+		GB_ASSERT_MSG(bitset_integer_type, "Could not determine bit_set integer size for objc_type_encoding");
+
+		return lb_get_objc_type_encoding(bitset_integer_type, pointer_depth);
+	}
 
 	case Type_SimdVector: {
 		String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth);
@@ -1452,7 +1465,10 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) {
 
 struct lbObjCGlobalClass {
 	lbObjCGlobal g;
-	lbValue      class_value;    // Local registered class value
+	union {
+		lbValue      class_value;    // Local registered class value
+		lbAddr       class_global;   // Global class pointer. Placeholder for class implementations which are registered in order of definition.
+	};
 };
 
 gb_internal void lb_register_objc_thing(
@@ -1482,44 +1498,43 @@ gb_internal void lb_register_objc_thing(
 		LLVMSetInitializer(v.value, LLVMConstNull(t));
 	}
 
-	lbValue class_ptr  = {};
-	lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name));
-
 	// If this class requires an implementation, save it for registration below.
 	if (g.class_impl_type != nullptr) {
 
 		// Make sure the superclass has been initialized before us
-		lbValue superclass_value = lb_const_nil(m, t_objc_Class);
-
 		auto &tn = g.class_impl_type->Named.type_name->TypeName;
 		Type *superclass = tn.objc_superclass;
 		if (superclass != nullptr) {
 			auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name);
 			lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call);
-			GB_ASSERT(superclass_global.class_value.value);
-
-			superclass_value = superclass_global.class_value;
+			GB_ASSERT(superclass_global.class_global.addr.value);
 		}
 
-		args.count = 3;
-		args[0] = superclass_value;
-		args[1] = class_name;
-		args[2] = lb_const_int(m, t_uint, 0);
-		class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args);
+		lbObjCGlobalClass impl_global = {};
+		impl_global.g            = g;
+		impl_global.class_global = addr;
 
-		array_add(&class_impls, lbObjCGlobalClass{g, class_ptr});
+		array_add(&class_impls, impl_global);
+
+		lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
+		if (class_global != nullptr) {
+			class_global->class_global = addr;
+		}
 	}
 	else {
+		lbValue class_ptr  = {};
+		lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name));
+
 		args.count = 1;
 		args[0] = class_name;
 		class_ptr = lb_emit_runtime_call(p, call, args);
-	}
 
-	lb_addr_store(p, addr, class_ptr);
+		lb_addr_store(p, addr, class_ptr);
 
-	lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
-	if (class_global != nullptr) {
-		class_global->class_value = class_ptr;
+		lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
+		if (class_global != nullptr) {
+			class_global->class_value = class_ptr;
+		}
 	}
 }
 
@@ -1582,7 +1597,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
 	string_map_init(&global_class_map, (usize)gen->objc_classes.count);
 	defer (string_map_destroy(&global_class_map));
 
-	for (lbObjCGlobal g :referenced_classes) {
+	for (lbObjCGlobal g : referenced_classes) {
 		string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g});
 	}
 
@@ -1629,9 +1644,36 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
 
 	for (const auto &cd : class_impls) {
 		auto &g = cd.g;
-		Type *class_type = g.class_impl_type;
+
+		Type *class_type     = g.class_impl_type;
 		Type *class_ptr_type = alloc_type_pointer(class_type);
-		lbValue class_value = cd.class_value;
+
+		// Begin class registration: create class pair and update global reference
+		lbValue class_value = {};
+
+		{
+			lbValue superclass_value = lb_const_nil(m, t_objc_Class);
+
+			auto& tn = class_type->Named.type_name->TypeName;
+			Type *superclass = tn.objc_superclass;
+
+			if (superclass != nullptr) {
+				auto& superclass_global = string_map_must_get(&global_class_map, superclass->Named.type_name->TypeName.objc_class_name);
+				superclass_value = superclass_global.class_value;
+			}
+
+			args.count = 3;
+			args[0] = superclass_value;
+			args[1] = lb_const_value(m, t_cstring, exact_value_string(g.name));
+			args[2] = lb_const_int(m, t_uint, 0);
+			class_value = lb_emit_runtime_call(p, "objc_allocateClassPair", args);
+
+			lbObjCGlobalClass &mapped_global = string_map_must_get(&global_class_map, tn.objc_class_name);
+			lb_addr_store(p, mapped_global.class_global, class_value);
+
+			mapped_global.class_value = class_value;
+		}
+
 
 		Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar;
 
@@ -1651,7 +1693,6 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
 			is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type);
 		}
 
-
 		Array<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
src/llvm_backend.hpp

@@ -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
src/llvm_backend_proc.cpp

@@ -3753,6 +3753,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 	case BuiltinProc_objc_register_class:    return lb_handle_objc_register_class(p, expr);
 	case BuiltinProc_objc_ivar_get:          return lb_handle_objc_ivar_get(p, expr);
 	case BuiltinProc_objc_block:             return lb_handle_objc_block(p, expr);
+	case BuiltinProc_objc_super:             return lb_handle_objc_super(p, expr);
 
 
 	case BuiltinProc_constant_utf16_cstring:
@@ -4122,21 +4123,23 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) {
 	}
 
 	Ast *proc_expr = unparen_expr(ce->proc);
+	Entity *proc_entity = entity_of_node(proc_expr);
+
 	if (proc_mode == Addressing_Builtin) {
-		Entity *e = entity_of_node(proc_expr);
 		BuiltinProcId id = BuiltinProc_Invalid;
-		if (e != nullptr) {
-			id = cast(BuiltinProcId)e->Builtin.id;
+		if (proc_entity != nullptr) {
+			id = cast(BuiltinProcId)proc_entity->Builtin.id;
 		} else {
 			id = BuiltinProc_DIRECTIVE;
 		}
 		return lb_build_builtin_proc(p, expr, tv, id);
 	}
 
+	bool is_objc_call = proc_entity && proc_entity->Procedure.is_objc_impl_or_import;
+
 	// NOTE(bill): Regular call
 	lbValue value = {};
 
-	Entity *proc_entity = entity_of_node(proc_expr);
 	if (proc_entity != nullptr) {
 		if (proc_entity->flags & EntityFlag_Disabled) {
 			GB_ASSERT(tv.type == nullptr);
@@ -4170,11 +4173,13 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) {
 		}
 	}
 
-	if (value.value == nullptr) {
+	if (is_objc_call) {
+		value.type = proc_tv.type;
+	} else if (value.value == nullptr) {
 		value = lb_build_expr(p, proc_expr);
 	}
 
-	GB_ASSERT(value.value != nullptr);
+	GB_ASSERT(value.value != nullptr || is_objc_call);
 	Type *proc_type_ = base_type(value.type);
 	GB_ASSERT(proc_type_->kind == Type_Proc);
 	TypeProc *pt = &proc_type_->Proc;
@@ -4402,6 +4407,11 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) {
 
 	isize final_count = is_c_vararg ? args.count : pt->param_count;
 	auto call_args = array_slice(args, 0, final_count);
+
+	if (is_objc_call) {
+		return lb_handle_objc_auto_send(p, expr, slice(call_args, 0, call_args.count));
+	}
+
 	return lb_emit_call(p, value, call_args, ce->inlining);
 }
 

+ 200 - 45
src/llvm_backend_utility.cpp

@@ -286,7 +286,14 @@ gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t) {
 		}
 	}
 
+	bool is_simd_vector_bitcastable = false;
 	if (is_type_simd_vector(src) && is_type_simd_vector(dst)) {
+		if (!is_type_internally_pointer_like(src->SimdVector.elem) && !is_type_internally_pointer_like(dst->SimdVector.elem)) {
+			is_simd_vector_bitcastable = true;
+		}
+	}
+
+	if (is_simd_vector_bitcastable) {
 		res.value = LLVMBuildBitCast(p->builder, value.value, lb_type(p->module, t), "");
 		return res;
 	} else if (is_type_array_like(src) && (is_type_simd_vector(dst) || is_type_integer_128bit(dst))) {
@@ -2264,12 +2271,12 @@ gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) {
 }
 
 gb_internal void lb_create_objc_block_helper_procs(
-	lbModule *m, LLVMTypeRef block_lit_type, isize capture_field_offset,
+	lbModule *m, LLVMTypeRef block_lit_type, isize capture_field_offset, isize block_id,
 	Slice<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;
 
-		LLVMValueRef block_literal = invoker_proc->raw_input_parameters[0];
+		lbFunctionType* user_proc_ft = lb_get_function_type(m, user_proc_value.type);
 
-		// 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;
+		lbArgKind return_kind = {};
 
-			lb_push_context_onto_stack(invoker_proc, lb_addr(ctx_val));
+		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;
+			}
+		}
+
+		// 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), "");
 
-			lbValue cap_arg = {};
-			cap_arg.value = cap_value;
-			cap_arg.type  = alloc_type_pointer(captured_values[i].type);
+			// 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;
 
-			lbValue arg = lb_emit_load(invoker_proc, cap_arg);
-			call_args[block_forward_args+i] = arg;
+			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);
 		}
 
-		lbValue result = lb_emit_call(invoker_proc, user_proc_value, call_args, proc_lit->ProcLit.inlining);
+		// 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");
+			array_add(&call_args, p_context);
+		}
 
-		GB_ASSERT(user_proc.result_count <= 1);
-		if (user_proc.result_count > 0) {
-			GB_ASSERT(result.value != nullptr);
-			LLVMBuildRet(p->builder, result.value);
+		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, "");
+
+		if (user_proc.result_count > 0 && return_kind != lbArg_Indirect) {
+			LLVMBuildRet(invoker_proc->builder, ret_val);
+		}
+		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
src/parser.hpp

@@ -48,7 +48,8 @@ gb_global String const addressing_mode_strings[] = {
 struct TypeAndValue {
 	Type *         type;
 	AddressingMode mode;
-	bool           is_lhs; // Debug info
+	bool           is_lhs;            // Debug info
+	Type *         objc_super_target; // Original type of the Obj-C object before being converted to the superclass' type by the objc_super() intrinsic.
 	ExactValue     value;
 };
 

+ 11 - 0
src/types.cpp

@@ -752,11 +752,14 @@ gb_global Type *t_objc_object   = nullptr;
 gb_global Type *t_objc_selector = nullptr;
 gb_global Type *t_objc_class    = nullptr;
 gb_global Type *t_objc_ivar     = nullptr;
+gb_global Type *t_objc_super    = nullptr;     // Struct used in lieu of the 'self' instance when calling objc_msgSendSuper.
+gb_global Type *t_objc_super_ptr = nullptr;
 
 gb_global Type *t_objc_id    = nullptr;
 gb_global Type *t_objc_SEL   = nullptr;
 gb_global Type *t_objc_Class = nullptr;
 gb_global Type *t_objc_Ivar  = nullptr;
+gb_global Type *t_objc_instancetype = nullptr; // Special distinct variant of t_objc_id used mimic auto-typing of instancetype* in Objective-C
 
 enum OdinAtomicMemoryOrder : i32 {
 	OdinAtomicMemoryOrder_relaxed = 0, // unordered
@@ -4735,6 +4738,14 @@ gb_internal bool is_type_objc_object(Type *t) {
 	return internal_check_is_assignable_to(t, t_objc_object);
 }
 
+gb_internal bool is_type_objc_ptr_to_object(Type *t) {
+	// NOTE (harold): is_type_objc_object() returns true if it's a pointer to an object or the object itself.
+	//                This returns true ONLY if Type is a shallow pointer to an Objective-C object.
+
+	Type *elem = type_deref(t);
+	return elem != t && elem->kind == Type_Named && is_type_objc_object(elem);
+}
+
 gb_internal Type *get_struct_field_type(Type *t, isize index) {
 	t = base_type(type_deref(t));
 	GB_ASSERT(t->kind == Type_Struct);