From d3b1aaad18e7a1445992aea76dfb6fa00e5a9d80 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 12 Mar 2025 12:53:26 +0000 Subject: Use `::` rather than `$` to try and fix mac SEL stuff --- src/llvm_backend_utility.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index c876169f3..c21e88792 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2112,11 +2112,10 @@ gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, Stri } if (!entity) { - gbString global_name = gb_string_make(temporary_allocator(), "__$objc_SEL$"); + gbString global_name = gb_string_make(temporary_allocator(), "__$objc_SEL::"); global_name = gb_string_append_length(global_name, name.text, name.len); - lbAddr default_addr = lb_add_global_generated_with_name( - default_module, t_objc_SEL, {}, + lbAddr default_addr = lb_add_global_generated_with_name(default_module, t_objc_SEL, {}, make_string(cast(u8 const *)global_name, gb_string_length(global_name)), &entity); string_map_set(&default_module->objc_selectors, name, lbObjcRef{entity, default_addr}); @@ -2175,7 +2174,7 @@ gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String } if (!entity) { - gbString global_name = gb_string_make(temporary_allocator(), "__$objc_Class$"); + gbString global_name = gb_string_make(temporary_allocator(), "__$objc_Class::"); global_name = gb_string_append_length(global_name, name.text, name.len); lbAddr default_addr = lb_add_global_generated_with_name(default_module, t_objc_Class, {}, -- cgit v1.2.3 From f85db012b80f0ddbd33833c0aa617c8d5a6892cb Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 21 Mar 2025 22:53:00 +0100 Subject: fix off by one temp cstring and put objc names on permanent allocator to be safe Fixes #4922 --- src/llvm_backend_general.cpp | 6 +----- src/llvm_backend_utility.cpp | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 71d368ec9..b7f70893f 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -2812,15 +2812,11 @@ gb_internal lbAddr lb_add_global_generated_with_name(lbModule *m, Type *type, lb GB_ASSERT(type != nullptr); type = default_type(type); - u8 *str = cast(u8 *)gb_alloc_array(temporary_allocator(), u8, name.len); - memcpy(str, name.text, name.len); - str[name.len] = 0; - Scope *scope = nullptr; Entity *e = alloc_entity_variable(scope, make_token_ident(name), type); lbValue g = {}; g.type = alloc_type_pointer(type); - g.value = LLVMAddGlobal(m->mod, lb_type(m, type), cast(char const *)str); + g.value = LLVMAddGlobal(m->mod, lb_type(m, type), alloc_cstring(temporary_allocator(), name)); if (value.value != nullptr) { GB_ASSERT_MSG(LLVMIsConstant(value.value), LLVMPrintValueToString(value.value)); LLVMSetInitializer(g.value, value.value); diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index c21e88792..e06369be3 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2112,7 +2112,7 @@ gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, Stri } if (!entity) { - gbString global_name = gb_string_make(temporary_allocator(), "__$objc_SEL::"); + gbString global_name = gb_string_make(permanent_allocator(), "__$objc_SEL::"); global_name = gb_string_append_length(global_name, name.text, name.len); lbAddr default_addr = lb_add_global_generated_with_name(default_module, t_objc_SEL, {}, @@ -2174,7 +2174,7 @@ gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String } if (!entity) { - gbString global_name = gb_string_make(temporary_allocator(), "__$objc_Class::"); + gbString global_name = gb_string_make(permanent_allocator(), "__$objc_Class::"); global_name = gb_string_append_length(global_name, name.text, name.len); lbAddr default_addr = lb_add_global_generated_with_name(default_module, t_objc_Class, {}, -- cgit v1.2.3 From d0dd1b2d80ab32083f97a4753200758731155ade Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 2 Apr 2025 18:23:08 +0100 Subject: Add mutex for `lb_handle_objc_find_or_register_selector` --- src/llvm_backend.hpp | 1 + src/llvm_backend_utility.cpp | 7 +++++++ 2 files changed, 8 insertions(+) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 4fd1b8d1a..610fb72e4 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -165,6 +165,7 @@ struct lbModule { PtrMap func_raw_types; // mutex: func_raw_types_mutex RecursiveMutex types_mutex; RecursiveMutex func_raw_types_mutex; + RecursiveMutex objc_selectors_mutex; i32 internal_type_level; RwMutex values_mutex; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index e06369be3..a21039360 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2096,7 +2096,11 @@ gb_internal void lb_set_wasm_export_attributes(LLVMValueRef value, String export gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { + mutex_lock(&p->module->objc_selectors_mutex); + defer (mutex_unlock(&p->module->objc_selectors_mutex)); + lbObjcRef *found = string_map_get(&p->module->objc_selectors, name); + if (found) { return found->local_module_addr; } @@ -2118,7 +2122,10 @@ gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, Stri lbAddr default_addr = lb_add_global_generated_with_name(default_module, t_objc_SEL, {}, make_string(cast(u8 const *)global_name, gb_string_length(global_name)), &entity); + + mutex_lock(&default_module->objc_selectors_mutex); string_map_set(&default_module->objc_selectors, name, lbObjcRef{entity, default_addr}); + mutex_unlock(&default_module->objc_selectors_mutex); } lbValue ptr = lb_find_value_from_entity(p->module, entity); -- cgit v1.2.3 From 5eaff20f4a672760b4d90d596dd46be132634f41 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 2 Apr 2025 18:26:40 +0100 Subject: Add mutex for `lb_handle_objc_find_or_register_class` --- src/llvm_backend.hpp | 5 ++++- src/llvm_backend_utility.cpp | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 610fb72e4..f9f96a906 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -165,7 +165,6 @@ struct lbModule { PtrMap func_raw_types; // mutex: func_raw_types_mutex RecursiveMutex types_mutex; RecursiveMutex func_raw_types_mutex; - RecursiveMutex objc_selectors_mutex; i32 internal_type_level; RwMutex values_mutex; @@ -199,6 +198,10 @@ struct lbModule { RecursiveMutex debug_values_mutex; PtrMap debug_values; + + RecursiveMutex objc_classes_mutex; + RecursiveMutex objc_selectors_mutex; + StringMap objc_classes; StringMap objc_selectors; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index a21039360..efe196e58 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2165,6 +2165,9 @@ gb_internal lbValue lb_handle_objc_register_selector(lbProcedure *p, Ast *expr) } gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { + mutex_lock(&p->module->objc_classes_mutex); + defer (mutex_unlock(&p->module->objc_classes_mutex)); + lbObjcRef *found = string_map_get(&p->module->objc_classes, name); if (found) { return found->local_module_addr; @@ -2187,7 +2190,10 @@ gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String lbAddr default_addr = lb_add_global_generated_with_name(default_module, t_objc_Class, {}, make_string(cast(u8 const *)global_name, gb_string_length(global_name)), &entity); + + mutex_lock(&default_module->objc_classes_mutex); string_map_set(&default_module->objc_classes, name, lbObjcRef{entity, default_addr}); + mutex_unlock(&default_module->objc_classes_mutex); } lbValue ptr = lb_find_value_from_entity(p->module, entity); -- cgit v1.2.3 From cd5bef4f610ec3ee32957cbca354ccbfef310921 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 3 Apr 2025 09:33:14 +0100 Subject: Rewrite objc SEL/Class register handling code --- src/llvm_backend.cpp | 45 ++++++++++++----- src/llvm_backend.hpp | 21 ++++---- src/llvm_backend_general.cpp | 2 + src/llvm_backend_utility.cpp | 118 ++++++++++++++++++------------------------- 4 files changed, 96 insertions(+), 90 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 6f3abc607..396b94f98 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1126,30 +1126,51 @@ gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) { return p; } -gb_internal void lb_finalize_objc_names(lbProcedure *p) { +gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { if (p == nullptr) { return; } lbModule *m = p->module; + GB_ASSERT(m == &p->module->gen->default_module); TEMPORARY_ALLOCATOR_GUARD(); + StringSet handled = {}; + string_set_init(&handled); + defer (string_set_destroy(&handled)); + auto args = array_make(temporary_allocator(), 1); LLVMSetLinkage(p->value, LLVMInternalLinkage); lb_begin_procedure_body(p); - for (auto const &entry : m->objc_classes) { - String name = entry.key; - args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); - lbValue ptr = lb_emit_runtime_call(p, "objc_lookUpClass", args); - lb_addr_store(p, entry.value.local_module_addr, ptr); + + auto register_thing = [&handled, &m, &args](lbProcedure *p, lbObjCGlobal const &g, char const *call) { + if (!string_set_update(&handled, g.name)) { + lbAddr addr = {}; + lbValue *found = string_map_get(&m->members, g.global_name); + if (found) { + addr = lb_addr(*found); + } else { + lbValue v = {}; + LLVMTypeRef t = lb_type(m, g.type); + v.value = LLVMAddGlobal(m->mod, t, g.global_name); + v.type = alloc_type_pointer(g.type); + addr = lb_addr(v); + LLVMSetInitializer(v.value, LLVMConstNull(t)); + } + + args[0] = lb_const_value(m, t_cstring, exact_value_string(g.name)); + lbValue ptr = lb_emit_runtime_call(p, call, args); + lb_addr_store(p, addr, ptr); + } + }; + + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { + register_thing(p, g, "objc_lookUpClass"); } - for (auto const &entry : m->objc_selectors) { - String name = entry.key; - args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); - lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args); - lb_addr_store(p, entry.value.local_module_addr, ptr); + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) { + register_thing(p, g, "sel_registerName"); } lb_end_procedure_body(p); @@ -2637,7 +2658,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { if (gen->objc_names) { TIME_SECTION("Finalize objc names"); - lb_finalize_objc_names(gen->objc_names); + lb_finalize_objc_names(gen, gen->objc_names); } if (build_context.ODIN_DEBUG) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index f9f96a906..3e01ada5f 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -143,11 +143,6 @@ struct lbPadType { LLVMTypeRef type; }; -struct lbObjcRef { - Entity * entity; - lbAddr local_module_addr; -}; - struct lbModule { LLVMModuleRef mod; LLVMContextRef ctx; @@ -199,11 +194,8 @@ struct lbModule { PtrMap debug_values; - RecursiveMutex objc_classes_mutex; - RecursiveMutex objc_selectors_mutex; - - StringMap objc_classes; - StringMap objc_selectors; + StringMap objc_classes; + StringMap objc_selectors; PtrMap map_cell_info_map; // address of runtime.Map_Info PtrMap map_info_map; // address of runtime.Map_Cell_Info @@ -222,6 +214,13 @@ struct lbEntityCorrection { char const *cname; }; +struct lbObjCGlobal { + lbModule *module; + gbString global_name; + String name; + Type * type; +}; + struct lbGenerator : LinkerData { CheckerInfo *info; @@ -239,6 +238,8 @@ struct lbGenerator : LinkerData { lbProcedure *objc_names; MPSCQueue entities_to_correct_linkage; + MPSCQueue objc_selectors; + MPSCQueue objc_classes; }; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index b7f70893f..ce2c70661 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -171,6 +171,8 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { } mpsc_init(&gen->entities_to_correct_linkage, heap_allocator()); + mpsc_init(&gen->objc_selectors, heap_allocator()); + mpsc_init(&gen->objc_classes, heap_allocator()); return true; } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index efe196e58..0decbcdb8 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2094,48 +2094,71 @@ gb_internal void lb_set_wasm_export_attributes(LLVMValueRef value, String export } - gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { - mutex_lock(&p->module->objc_selectors_mutex); - defer (mutex_unlock(&p->module->objc_selectors_mutex)); - - lbObjcRef *found = string_map_get(&p->module->objc_selectors, name); - + lbModule *m = p->module; + lbAddr *found = string_map_get(&m->objc_selectors, name); if (found) { - return found->local_module_addr; + return *found; } lbModule *default_module = &p->module->gen->default_module; - Entity *entity = {}; - if (default_module != p->module) { - found = string_map_get(&default_module->objc_selectors, name); - if (found) { - entity = found->entity; - } + + gbString global_name = gb_string_make(permanent_allocator(), "__$objc_SEL::"); + global_name = gb_string_append_length(global_name, name.text, name.len); + + LLVMTypeRef t = lb_type(m, t_objc_SEL); + lbValue g = {}; + g.value = LLVMAddGlobal(m->mod, t, global_name); + g.type = alloc_type_pointer(t_objc_SEL); + + if (default_module == m) { + LLVMSetInitializer(g.value, LLVMConstNull(t)); + lb_add_member(m, make_string_c(global_name), g); + } else { + LLVMSetLinkage(g.value, LLVMExternalLinkage); } - if (!entity) { - gbString global_name = gb_string_make(permanent_allocator(), "__$objc_SEL::"); - global_name = gb_string_append_length(global_name, name.text, name.len); + mpsc_enqueue(&m->gen->objc_selectors, lbObjCGlobal{m, global_name, name, t_objc_SEL}); + + lbAddr addr = lb_addr(g); - lbAddr default_addr = lb_add_global_generated_with_name(default_module, t_objc_SEL, {}, - make_string(cast(u8 const *)global_name, gb_string_length(global_name)), - &entity); + string_map_set(&m->objc_selectors, name, addr); - mutex_lock(&default_module->objc_selectors_mutex); - string_map_set(&default_module->objc_selectors, name, lbObjcRef{entity, default_addr}); - mutex_unlock(&default_module->objc_selectors_mutex); + return addr; +} + +gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { + lbModule *m = p->module; + lbAddr *found = string_map_get(&m->objc_classes, name); + if (found) { + return *found; } - lbValue ptr = lb_find_value_from_entity(p->module, entity); - lbAddr local_addr = lb_addr(ptr); + lbModule *default_module = &p->module->gen->default_module; + + + gbString global_name = gb_string_make(permanent_allocator(), "__$objc_Class::"); + global_name = gb_string_append_length(global_name, name.text, name.len); - if (default_module != p->module) { - string_map_set(&p->module->objc_selectors, name, lbObjcRef{entity, local_addr}); + LLVMTypeRef t = lb_type(m, t_objc_Class); + lbValue g = {}; + g.value = LLVMAddGlobal(m->mod, t, global_name); + g.type = alloc_type_pointer(t_objc_Class); + + if (default_module == m) { + LLVMSetInitializer(g.value, LLVMConstNull(t)); + lb_add_member(m, make_string_c(global_name), g); + } else { + LLVMSetLinkage(g.value, LLVMExternalLinkage); } + mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class}); - return local_addr; + lbAddr addr = lb_addr(g); + + string_map_set(&m->objc_classes, name, addr); + + return addr; } gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { @@ -2164,47 +2187,6 @@ gb_internal lbValue lb_handle_objc_register_selector(lbProcedure *p, Ast *expr) return lb_addr_load(p, dst); } -gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { - mutex_lock(&p->module->objc_classes_mutex); - defer (mutex_unlock(&p->module->objc_classes_mutex)); - - lbObjcRef *found = string_map_get(&p->module->objc_classes, name); - if (found) { - return found->local_module_addr; - } - - lbModule *default_module = &p->module->gen->default_module; - Entity *entity = {}; - - if (default_module != p->module) { - found = string_map_get(&default_module->objc_classes, name); - if (found) { - entity = found->entity; - } - } - - if (!entity) { - gbString global_name = gb_string_make(permanent_allocator(), "__$objc_Class::"); - global_name = gb_string_append_length(global_name, name.text, name.len); - - lbAddr default_addr = lb_add_global_generated_with_name(default_module, t_objc_Class, {}, - make_string(cast(u8 const *)global_name, gb_string_length(global_name)), - &entity); - - mutex_lock(&default_module->objc_classes_mutex); - string_map_set(&default_module->objc_classes, name, lbObjcRef{entity, default_addr}); - mutex_unlock(&default_module->objc_classes_mutex); - } - - lbValue ptr = lb_find_value_from_entity(p->module, entity); - lbAddr local_addr = lb_addr(ptr); - - if (default_module != p->module) { - string_map_set(&p->module->objc_classes, name, lbObjcRef{entity, local_addr}); - } - - return local_addr; -} gb_internal lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) { ast_node(ce, CallExpr, expr); -- cgit v1.2.3 From e412d3b4c15d24c644723ea89960f3507a0b6618 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 3 Apr 2025 09:48:30 +0100 Subject: Minor clean up to `lb_handle_objc_find_or_register_internal` --- src/llvm_backend_utility.cpp | 49 +++++++++++--------------------------------- 1 file changed, 12 insertions(+), 37 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 0decbcdb8..4064b3134 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2094,9 +2094,9 @@ gb_internal void lb_set_wasm_export_attributes(LLVMValueRef value, String export } -gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { +gb_internal lbAddr lb_handle_objc_find_or_register_internal(lbProcedure *p, StringMap *objc_map, String const &name, char const *prefix, Type *type) { lbModule *m = p->module; - lbAddr *found = string_map_get(&m->objc_selectors, name); + lbAddr *found = string_map_get(objc_map, name); if (found) { return *found; } @@ -2104,13 +2104,13 @@ gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, Stri lbModule *default_module = &p->module->gen->default_module; - gbString global_name = gb_string_make(permanent_allocator(), "__$objc_SEL::"); + gbString global_name = gb_string_make(permanent_allocator(), prefix); global_name = gb_string_append_length(global_name, name.text, name.len); - LLVMTypeRef t = lb_type(m, t_objc_SEL); + LLVMTypeRef t = lb_type(m, type); lbValue g = {}; g.value = LLVMAddGlobal(m->mod, t, global_name); - g.type = alloc_type_pointer(t_objc_SEL); + g.type = alloc_type_pointer(type); if (default_module == m) { LLVMSetInitializer(g.value, LLVMConstNull(t)); @@ -2119,46 +2119,21 @@ gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, Stri LLVMSetLinkage(g.value, LLVMExternalLinkage); } - mpsc_enqueue(&m->gen->objc_selectors, lbObjCGlobal{m, global_name, name, t_objc_SEL}); + mpsc_enqueue(&m->gen->objc_selectors, lbObjCGlobal{m, global_name, name, type}); lbAddr addr = lb_addr(g); - string_map_set(&m->objc_selectors, name, addr); + string_map_set(objc_map, name, addr); return addr; } -gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { - lbModule *m = p->module; - lbAddr *found = string_map_get(&m->objc_classes, name); - if (found) { - return *found; - } - - lbModule *default_module = &p->module->gen->default_module; - - - gbString global_name = gb_string_make(permanent_allocator(), "__$objc_Class::"); - global_name = gb_string_append_length(global_name, name.text, name.len); - - LLVMTypeRef t = lb_type(m, t_objc_Class); - lbValue g = {}; - g.value = LLVMAddGlobal(m->mod, t, global_name); - g.type = alloc_type_pointer(t_objc_Class); - - if (default_module == m) { - LLVMSetInitializer(g.value, LLVMConstNull(t)); - lb_add_member(m, make_string_c(global_name), g); - } else { - LLVMSetLinkage(g.value, LLVMExternalLinkage); - } - mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class}); - - lbAddr addr = lb_addr(g); - - string_map_set(&m->objc_classes, name, addr); +gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { + return lb_handle_objc_find_or_register_internal(p, &p->module->objc_selectors, name, "__$objc_SEL::", t_objc_SEL); +} - return addr; +gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { + return lb_handle_objc_find_or_register_internal(p, &p->module->objc_classes, name, "__$objc_Class::", t_objc_Class); } gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { -- cgit v1.2.3 From 4ebaed1fddd21a42623bfeff32558496e424326c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 3 Apr 2025 09:54:44 +0100 Subject: Remove refactor --- src/llvm_backend_utility.cpp | 49 ++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 15 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 4064b3134..bfeebfcbe 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2094,23 +2094,22 @@ gb_internal void lb_set_wasm_export_attributes(LLVMValueRef value, String export } -gb_internal lbAddr lb_handle_objc_find_or_register_internal(lbProcedure *p, StringMap *objc_map, String const &name, char const *prefix, Type *type) { +gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { lbModule *m = p->module; - lbAddr *found = string_map_get(objc_map, name); + lbAddr *found = string_map_get(&m->objc_selectors, name); if (found) { return *found; } lbModule *default_module = &p->module->gen->default_module; - - gbString global_name = gb_string_make(permanent_allocator(), prefix); + gbString global_name = gb_string_make(permanent_allocator(), "__$objc_SEL::"); global_name = gb_string_append_length(global_name, name.text, name.len); - LLVMTypeRef t = lb_type(m, type); + LLVMTypeRef t = lb_type(m, t_objc_SEL); lbValue g = {}; g.value = LLVMAddGlobal(m->mod, t, global_name); - g.type = alloc_type_pointer(type); + g.type = alloc_type_pointer(t_objc_SEL); if (default_module == m) { LLVMSetInitializer(g.value, LLVMConstNull(t)); @@ -2119,21 +2118,41 @@ gb_internal lbAddr lb_handle_objc_find_or_register_internal(lbProcedure *p, Stri LLVMSetLinkage(g.value, LLVMExternalLinkage); } - mpsc_enqueue(&m->gen->objc_selectors, lbObjCGlobal{m, global_name, name, type}); + mpsc_enqueue(&m->gen->objc_selectors, lbObjCGlobal{m, global_name, name, t_objc_SEL}); lbAddr addr = lb_addr(g); - - string_map_set(objc_map, name, addr); - + string_map_set(&m->objc_selectors, name, addr); return addr; } -gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { - return lb_handle_objc_find_or_register_internal(p, &p->module->objc_selectors, name, "__$objc_SEL::", t_objc_SEL); -} - gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { - return lb_handle_objc_find_or_register_internal(p, &p->module->objc_classes, name, "__$objc_Class::", t_objc_Class); + lbModule *m = p->module; + lbAddr *found = string_map_get(&m->objc_classes, name); + if (found) { + return *found; + } + + lbModule *default_module = &p->module->gen->default_module; + + gbString global_name = gb_string_make(permanent_allocator(), "__$objc_Class::"); + global_name = gb_string_append_length(global_name, name.text, name.len); + + LLVMTypeRef t = lb_type(m, t_objc_Class); + lbValue g = {}; + g.value = LLVMAddGlobal(m->mod, t, global_name); + g.type = alloc_type_pointer(t_objc_Class); + + if (default_module == m) { + LLVMSetInitializer(g.value, LLVMConstNull(t)); + lb_add_member(m, make_string_c(global_name), g); + } else { + LLVMSetLinkage(g.value, LLVMExternalLinkage); + } + mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class}); + + lbAddr addr = lb_addr(g); + string_map_set(&m->objc_classes, name, addr); + return addr; } gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { -- cgit v1.2.3 From a3de9c8de4e539905a85f3cc060f95529b402f18 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Sat, 19 Apr 2025 08:04:23 -0400 Subject: Add initial support for Objective-C class implementation --- base/intrinsics/intrinsics.odin | 5 +- base/runtime/procs_darwin.odin | 25 +- src/check_builtin.cpp | 77 +++++- src/check_decl.cpp | 73 +++++ src/checker.cpp | 77 +++++- src/checker.hpp | 17 +- src/checker_builtin_procs.hpp | 2 + src/entity.cpp | 3 + src/llvm_backend.cpp | 591 +++++++++++++++++++++++++++++++++++++--- src/llvm_backend.hpp | 3 + src/llvm_backend_general.cpp | 2 + src/llvm_backend_proc.cpp | 1 + src/llvm_backend_utility.cpp | 74 ++++- src/types.cpp | 2 + 14 files changed, 900 insertions(+), 52 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index bec452007..515e8d48a 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -353,15 +353,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- objc_object :: struct{} 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_find_selector :: proc($name: string) -> objc_SEL --- objc_register_selector :: proc($name: string) -> objc_SEL --- objc_find_class :: proc($name: string) -> objc_Class --- objc_register_class :: proc($name: string) -> objc_Class --- - +ivar_get :: proc(self: ^$T, $U: typeid) -> ^U --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin index c3fc46af1..0aec57e80 100644 --- a/base/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -2,21 +2,34 @@ package runtime @(priority_index=-1e6) -foreign import "system:Foundation.framework" +foreign import ObjC "system:objc" import "base:intrinsics" -objc_id :: ^intrinsics.objc_object +objc_id :: ^intrinsics.objc_object objc_Class :: ^intrinsics.objc_class -objc_SEL :: ^intrinsics.objc_selector +objc_SEL :: ^intrinsics.objc_selector +objc_Ivar :: ^intrinsics.objc_ivar +objc_BOOL :: bool -foreign Foundation { - objc_lookUpClass :: proc "c" (name: cstring) -> 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_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- 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_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- + objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- + objc_registerClassPair :: proc "c" (cls : objc_Class) --- + class_addMethod :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL --- + 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 --- + ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr --- } + diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index f66a8605c..c44d1c123 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -387,6 +387,80 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan try_to_add_package_dependency(c, "runtime", "objc_allocateClassPair"); return true; } break; + + case BuiltinProc_objc_ivar_get: + { + Type *self_type = nullptr; + Type *ivar_type = nullptr; + + Operand self {}; + check_expr_or_type(c, &self, ce->args[0]); + + if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) { + gbString e = expr_to_string(self.expr); + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + else if (!is_type_pointer(self.type)) { + gbString e = expr_to_string(self.expr); + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a pointer of a value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + self_type = type_deref(self.type); + + if (!(self_type->kind == Type_Named && + self_type->Named.type_name != nullptr && + self_type->Named.type_name->TypeName.objc_class_name != "")) { + gbString t = type_to_string(self_type); + error(self.expr, "'%.*s' expected a named type with the attribute @(obj_class=) , got type %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + if (self_type->Named.type_name->TypeName.objc_ivar == nullptr) { + gbString t = type_to_string(self_type); + error(self.expr, "'%.*s' requires that type %s have the attribute @(obj_ivar=).", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + Operand ivar {}; + check_expr_or_type(c, &ivar, ce->args[1]); + if (ivar.mode == Addressing_Type) { + ivar_type = ivar.type; + } else { + return false; + } + + if (self_type->Named.type_name->TypeName.objc_ivar != ivar_type) { + gbString name_self = type_to_string(self_type); + gbString name_expected = type_to_string(self_type->Named.type_name->TypeName.objc_ivar); + gbString name_given = type_to_string(ivar_type); + error(self.expr, "'%.*s' ivar type %s does not match @obj_ivar type %s on Objective-C class %s.", + LIT(builtin_name), name_given, name_expected, name_self); + gb_string_free(name_self); + gb_string_free(name_expected); + gb_string_free(name_given); + return false; + } + + if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) { + operand->type = type_hint; + } else { + operand->type = alloc_type_pointer(ivar_type); + } + + operand->mode = Addressing_Value; + + return true; + } break; } } @@ -2132,7 +2206,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_objc_find_selector: case BuiltinProc_objc_find_class: case BuiltinProc_objc_register_selector: - case BuiltinProc_objc_register_class: + case BuiltinProc_objc_register_class: + case BuiltinProc_objc_ivar_get: return check_builtin_objc_procedure(c, operand, call, id, type_hint); case BuiltinProc___entry_point: diff --git a/src/check_decl.cpp b/src/check_decl.cpp index ba6445ea4..dffe0b48e 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -526,6 +526,54 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac); if (e->kind == Entity_TypeName && ac.objc_class != "") { e->TypeName.objc_class_name = ac.objc_class; + e->TypeName.objc_superclass = ac.objc_superclass; + e->TypeName.objc_ivar = ac.objc_ivar; + + if (ac.objc_is_implementation) { + e->TypeName.objc_is_implementation = true; + mpsc_enqueue(&ctx->info->objc_class_implementations, e); // TODO(harold): Don't need this for anything. Remove. + + GB_ASSERT(e->TypeName.objc_ivar == nullptr || e->TypeName.objc_ivar->kind == Type_Named); + + // Ensure superclass hierarchy are all Objective-C classes and does not cycle + Type *super = ac.objc_superclass; + if (super != nullptr) { + TypeSet super_set{}; + type_set_init(&super_set, 8); + defer (type_set_destroy(&super_set)); + + type_set_update(&super_set, e->type); + + for (;;) { + if (type_set_update(&super_set, super)) { + error(e->token, "@(objc_superclass) Superclass hierarchy cycle encountered"); + break; + } + + if (super->kind != Type_Named) { + error(e->token, "@(objc_superclass) References type must be a named struct."); + break; + } + + Type* named_type = base_type(super->Named.type_name->type); + if (!is_type_objc_object(named_type)) { + error(e->token, "@(objc_superclass) Superclass must be an Objective-C class."); + break; + } + + super = super->Named.type_name->TypeName.objc_superclass; + if (super == nullptr) { + break; + } + + // TODO(harold): Is this the right way to do this??? The referenced entity must be already resolved + // so that we can access its objc_superclass attribute + check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info); + } + } + } else if (e->TypeName.objc_superclass != nullptr) { + error(e->token, "@(objc_superclass) can only be applied when the obj_implement attribute is also applied"); + } if (type_size_of(e->type) > 0) { error(e->token, "@(objc_class) marked type must be of zero size"); @@ -942,6 +990,31 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon if (tn->scope != e->scope) { error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope"); } else { + + if (ac.objc_is_implementation) { + GB_ASSERT(e->kind == Entity_Procedure); + + CheckerInfo *info = ctx->info; + mutex_lock(&info->objc_method_mutex); + defer (mutex_unlock(&info->objc_method_mutex)); + + auto method = ObjcMethodData{ ac, e }; + + if (ac.objc_selector == "") { + method.ac.objc_selector = ac.objc_name; + } + + Array* method_list = map_get(&info->objc_method_implementations, t); + if (method_list) { + array_add(method_list, method); + } else { + auto list = array_make(permanent_allocator(), 1, 8); + list[0] = method; + + map_set(&info->objc_method_implementations, t, list); + } + } + mutex_lock(&global_type_name_objc_metadata_mutex); defer (mutex_unlock(&global_type_name_objc_metadata_mutex)); diff --git a/src/checker.cpp b/src/checker.cpp index 5a5ec9706..29ef7d2b3 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1351,10 +1351,12 @@ gb_internal void init_universal(void) { t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete()); t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete()); t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete()); + t_objc_ivar = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_ivar"), alloc_type_struct_complete()); t_objc_id = alloc_type_pointer(t_objc_object); 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); } } @@ -1387,6 +1389,9 @@ gb_internal void init_checker_info(CheckerInfo *i) { array_init(&i->defineables, a); map_init(&i->objc_msgSend_types); + mpsc_init(&i->objc_class_implementations, a); + map_init(&i->objc_method_implementations); + string_map_init(&i->load_file_cache); array_init(&i->all_procedures, heap_allocator()); @@ -3345,6 +3350,11 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { ac->test = true; return true; } else if (name == "export") { + if (ac->objc_is_implementation) { + error(value, "Setting @(export) explicitly is not allowed when @(objc_implement) is set. It is exported implicitly."); + return false; + } + ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_Invalid) { ac->is_export = true; @@ -3356,6 +3366,12 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } return true; } else if (name == "linkage") { + + if (ac->objc_is_implementation) { + error(value, "Explicit linkage not allowed when @(objc_implement) is set. It is set implicitly"); + return false; + } + ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind != ExactValue_String) { error(value, "Expected either a string 'linkage'"); @@ -3662,6 +3678,35 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } } return true; + } else if (name == "objc_implement") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Bool) { + ac->objc_is_implementation = ev.value_bool; + } else if (ev.kind == ExactValue_Invalid) { + ac->objc_is_implementation = true; + } else { + error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name)); + } + + // This implies exported, strongly linked + if (ac->objc_is_implementation) { + ac->is_export = true; + ac->linkage = str_lit("strong"); + } + + return true; + } else if (name == "objc_selector") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_String) { + if (string_is_valid_identifier(ev.value_string)) { + ac->objc_selector = ev.value_string; + } else { + error(elem, "Invalid identifier for '%.*s', got '%.*s'", LIT(name), LIT(ev.value_string)); + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; } else if (name == "require_target_feature") { ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_String) { @@ -3901,8 +3946,36 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) { ac->objc_class = ev.value_string; } return true; - } - return false; + } else if (name == "objc_implement") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Bool) { + ac->objc_is_implementation = ev.value_bool; + } else if (ev.kind == ExactValue_Invalid) { + ac->objc_is_implementation = true; + } else { + error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name)); + } + return true; + } else if (name == "objc_superclass") { + Type *objc_superclass = check_type(c, value); + + if (objc_superclass != nullptr) { + ac->objc_superclass = objc_superclass; + } else { + error(value, "'%.*s' expected a named type", LIT(name)); + } + return true; + } else if (name == "objc_ivar") { + Type *objc_ivar = check_type(c, value); + + if (objc_ivar != nullptr) { + ac->objc_ivar = objc_ivar; + } else { + error(value, "'%.*s' expected a named type", LIT(name)); + } + return true; + } + return false; } diff --git a/src/checker.hpp b/src/checker.hpp index d3b2d7d89..9910ed17b 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -148,8 +148,12 @@ struct AttributeContext { String objc_class; String objc_name; - bool objc_is_class_method; + String objc_selector; Type * objc_type; + Type * objc_superclass; + Type * objc_ivar; + bool objc_is_class_method : 1; + bool objc_is_implementation : 1; // This struct or proc provides a class/method implementation, not a binding to an existing type. String require_target_feature; // required by the target micro-architecture String enable_target_feature; // will be enabled for the procedure only @@ -365,6 +369,11 @@ struct ObjcMsgData { Type *proc_type; }; +struct ObjcMethodData { + AttributeContext ac; + Entity *proc_entity; +}; + enum LoadFileTier { LoadFileTier_Invalid, LoadFileTier_Exists, @@ -479,6 +488,12 @@ struct CheckerInfo { BlockingMutex objc_types_mutex; PtrMap objc_msgSend_types; + MPSCQueue objc_class_implementations; + + BlockingMutex objc_method_mutex; + PtrMap> objc_method_implementations; + + BlockingMutex load_file_mutex; StringMap load_file_cache; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 40dde8240..cb2ce3915 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -331,6 +331,7 @@ BuiltinProc__type_end, BuiltinProc_objc_find_class, BuiltinProc_objc_register_selector, BuiltinProc_objc_register_class, + BuiltinProc_objc_ivar_get, BuiltinProc_constant_utf16_cstring, @@ -673,6 +674,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {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("ivar_get"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/entity.cpp b/src/entity.cpp index b2148aa7b..9a5996e3d 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -235,6 +235,9 @@ struct Entity { Type * type_parameter_specialization; String ir_mangled_name; bool is_type_alias; + bool objc_is_implementation; + Type* objc_superclass; + Type* objc_ivar; String objc_class_name; TypeNameObjCMetadata *objc_metadata; } TypeName; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 083a1d90e..23ad81847 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1173,6 +1173,332 @@ gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) { return p; } +// TODO(harold): Move this out of here and into a more suitable place. +// TODO(harold): Should not take an allocator, but always use temp, as we return string literals as well. +String lb_get_objc_type_encoding(Type *t, gbAllocator allocator, isize pointer_depth = 0) { + // NOTE(harold): See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 + + // NOTE(harold): Darwin targets are always 64-bit. Should we drop this and assume "q" always? + #define INT_SIZE_ENCODING (build_context.metrics.ptr_size == 4 ? "i" : "q") + switch (t->kind) { + case Type_Basic: { + switch (t->Basic.kind) { + case Basic_Invalid: + return str_lit("?"); + + case Basic_llvm_bool: + case Basic_bool: + case Basic_b8: + return str_lit("B"); + + case Basic_b16: + return str_lit("C"); + case Basic_b32: + return str_lit("I"); + case Basic_b64: + return str_lit("q"); + case Basic_i8: + return str_lit("c"); + case Basic_u8: + return str_lit("C"); + case Basic_i16: + case Basic_i16le: + case Basic_i16be: + return str_lit("s"); + case Basic_u16: + case Basic_u16le: + case Basic_u16be: + return str_lit("S"); + case Basic_i32: + case Basic_i32le: + case Basic_i32be: + return str_lit("i"); + case Basic_u32le: + case Basic_u32: + case Basic_u32be: + return str_lit("I"); + case Basic_i64: + case Basic_i64le: + case Basic_i64be: + return str_lit("q"); + case Basic_u64: + case Basic_u64le: + case Basic_u64be: + return str_lit("Q"); + case Basic_i128: + case Basic_i128le: + case Basic_i128be: + return str_lit("t"); + case Basic_u128: + case Basic_u128le: + case Basic_u128be: + return str_lit("T"); + case Basic_rune: + return str_lit("I"); + case Basic_f16: + case Basic_f16le: + case Basic_f16be: + return str_lit("s"); // @harold: Closest we've got? + case Basic_f32: + case Basic_f32le: + case Basic_f32be: + return str_lit("f"); + case Basic_f64: + case Basic_f64le: + case Basic_f64be: + return str_lit("d"); + + // TODO(harold) These: + case Basic_complex32: + case Basic_complex64: + case Basic_complex128: + case Basic_quaternion64: + case Basic_quaternion128: + case Basic_quaternion256: + return str_lit("?"); + + case Basic_int: + return str_lit(INT_SIZE_ENCODING); + case Basic_uint: + return build_context.metrics.ptr_size == 4 ? str_lit("I") : str_lit("Q"); + case Basic_uintptr: + case Basic_rawptr: + return str_lit("^v"); + + case Basic_string: + return build_context.metrics.ptr_size == 4 ? str_lit("{string=*i}") : str_lit("{string=*q}"); + + case Basic_cstring: return str_lit("*"); + case Basic_any: return str_lit("{any=^v^v"); // rawptr + ^Type_Info + + case Basic_typeid: + GB_ASSERT(t->Basic.size == 8); + return str_lit("q"); + + // Untyped types + case Basic_UntypedBool: + case Basic_UntypedInteger: + case Basic_UntypedFloat: + case Basic_UntypedComplex: + case Basic_UntypedQuaternion: + case Basic_UntypedString: + case Basic_UntypedRune: + case Basic_UntypedNil: + case Basic_UntypedUninit: + GB_PANIC("Untyped types cannot be @encoded()"); + return str_lit("?"); + } + break; + } + + case Type_Named: + case Type_Struct: + case Type_Union: { + Type* base = t; + if (base->kind == Type_Named) { + base = base_type(base); + if(base->kind != Type_Struct && base->kind != Type_Union) { + return lb_get_objc_type_encoding(base, allocator, pointer_depth); + } + } + + const bool is_union = base->kind == Type_Union; + if (!is_union) { + // Check for objc_SEL + if (internal_check_is_assignable_to(base, t_objc_SEL)) { + return str_lit(":"); + } + + // Check for objc_Class + if (internal_check_is_assignable_to(base, t_objc_SEL)) { + return str_lit("#"); + } + + // Treat struct as an Objective-C Class? + if (has_type_got_objc_class_attribute(base) && pointer_depth == 0) { + return str_lit("#"); + } + } + + if (is_type_objc_object(base)) { + return str_lit("@"); + } + + + gbString s = gb_string_make_reserve(allocator, 16); + s = gb_string_append_length(s, is_union ? "(" :"{", 1); + if (t->kind == Type_Named) { + s = gb_string_append_length(s, t->Named.name.text, t->Named.name.len); + } + + // Write fields + if (pointer_depth < 2) { + s = gb_string_append_length(s, "=", 1); + + if (!is_union) { + for( auto& f : t->Struct.fields ) { + String field_type = lb_get_objc_type_encoding(f->type, allocator, pointer_depth); + s = gb_string_append_length(s, field_type.text, field_type.len); + } + } else { + // #TODO(harold): Encode fields + } + } + + s = gb_string_append_length(s, is_union ? ")" :"}", 1); + + return make_string_c(s); + } + + case Type_Generic: + GB_PANIC("Generic types cannot be @encoded()"); + return str_lit("?"); + + case Type_Pointer: { + String pointee = lb_get_objc_type_encoding(t->Pointer.elem, allocator, pointer_depth +1); + // Special case for Objective-C Objects + if (pointer_depth == 0 && pointee == "@") { + return pointee; + } + + return concatenate_strings(allocator, str_lit("^"), pointee); + } + + case Type_MultiPointer: + return concatenate_strings(allocator, str_lit("^"), lb_get_objc_type_encoding(t->Pointer.elem, allocator, pointer_depth +1)); + + case Type_Array: { + String type_str = lb_get_objc_type_encoding(t->Array.elem, allocator, pointer_depth); + + gbString s = gb_string_make_reserve(allocator, type_str.len + 8); + s = gb_string_append_fmt(s, "[%lld%s]", t->Array.count, type_str.text); + return make_string_c(s); + } + + case Type_EnumeratedArray: { + String type_str = lb_get_objc_type_encoding(t->EnumeratedArray.elem, allocator, pointer_depth); + + gbString s = gb_string_make_reserve(allocator, type_str.len + 8); + s = gb_string_append_fmt(s, "[%lld%s]", t->EnumeratedArray.count, type_str.text); + return make_string_c(s); + } + + case Type_Slice: { + String type_str = lb_get_objc_type_encoding(t->Slice.elem, allocator, pointer_depth); + gbString s = gb_string_make_reserve(allocator, type_str.len + 8); + s = gb_string_append_fmt(s, "{slice=^%s%s}", type_str, INT_SIZE_ENCODING); + return make_string_c(s); + } + + case Type_DynamicArray: { + String type_str = lb_get_objc_type_encoding(t->DynamicArray.elem, allocator, pointer_depth); + gbString s = gb_string_make_reserve(allocator, type_str.len + 8); + s = gb_string_append_fmt(s, "{dynamic=^%s%s%sAllocator={?^v}}", type_str, INT_SIZE_ENCODING, INT_SIZE_ENCODING); + return make_string_c(s); + } + + case Type_Map: + return str_lit("{^v^v{Allocator=?^v}}"); + case Type_Enum: + return lb_get_objc_type_encoding(t->Enum.base_type, allocator, pointer_depth); + case Type_Tuple: + // NOTE(harold): Is this allowed here? + return str_lit("?"); + case Type_Proc: + return str_lit("?"); + case Type_BitSet: + return lb_get_objc_type_encoding(t->BitSet.underlying, allocator, pointer_depth); + case Type_SimdVector: + break; + case Type_Matrix: + break; + case Type_BitField: + return lb_get_objc_type_encoding(t->BitField.backing_type, allocator, pointer_depth); + case Type_SoaPointer: { + gbString s = gb_string_make_reserve(allocator, 8); + s = gb_string_append_fmt(s, "{=^v%s}", INT_SIZE_ENCODING); + return make_string_c(s); + } + + } // End switch t->kind + #undef INT_SIZE_ENCODING + + GB_PANIC("Unreachable"); +} + +struct lbObjCGlobalClass { + lbObjCGlobal g; + lbValue class_value; // Local registered class value +}; + +gb_internal void lb_register_objc_thing( + StringSet &handled, + lbModule *m, + Array &args, + Array &class_impls, + StringMap &class_map, + lbProcedure *p, + lbObjCGlobal const &g, + char const *call +) { + if (string_set_update(&handled, g.name)) { + return; + } + + lbAddr addr = {}; + lbValue *found = string_map_get(&m->members, g.global_name); + if (found) { + addr = lb_addr(*found); + } else { + lbValue v = {}; + LLVMTypeRef t = lb_type(m, g.type); + v.value = LLVMAddGlobal(m->mod, t, g.global_name); + v.type = alloc_type_pointer(g.type); + addr = lb_addr(v); + 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{}; + + 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; + } + + args.count = 3; + args[0] = superclass == nullptr ? lb_const_nil(m, t_objc_Class) : 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); + + array_add(&class_impls, lbObjCGlobalClass{g, class_ptr}); + } + else { + args.count = 1; + args[0] = class_name; + class_ptr = lb_emit_runtime_call(p, call, args); + } + + 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; + } +} + gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { if (p == nullptr) { return; @@ -1186,39 +1512,238 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { string_set_init(&handled); defer (string_set_destroy(&handled)); - auto args = array_make(temporary_allocator(), 1); - - LLVMSetLinkage(p->value, LLVMInternalLinkage); - lb_begin_procedure_body(p); - - auto register_thing = [&handled, &m, &args](lbProcedure *p, lbObjCGlobal const &g, char const *call) { - if (!string_set_update(&handled, g.name)) { - lbAddr addr = {}; - lbValue *found = string_map_get(&m->members, g.global_name); - if (found) { - addr = lb_addr(*found); - } else { - lbValue v = {}; - LLVMTypeRef t = lb_type(m, g.type); - v.value = LLVMAddGlobal(m->mod, t, g.global_name); - v.type = alloc_type_pointer(g.type); - addr = lb_addr(v); - LLVMSetInitializer(v.value, LLVMConstNull(t)); - } - - args[0] = lb_const_value(m, t_cstring, exact_value_string(g.name)); - lbValue ptr = lb_emit_runtime_call(p, call, args); - lb_addr_store(p, addr, ptr); - } - }; - - for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { - register_thing(p, g, "objc_lookUpClass"); - } - - for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) { - register_thing(p, g, "sel_registerName"); - } + auto args = array_make(temporary_allocator(), 3, 8); + auto class_impls = array_make(temporary_allocator(), 0, 16); + + // Ensure classes that have been implicitly referenced through + // the objc_superclass attribute have a global variable available for them. + TypeSet class_set{}; + type_set_init(&class_set, gen->objc_classes.count+16); + defer (type_set_destroy(&class_set)); + + auto referenced_classes = array_make(temporary_allocator()); + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { + array_add( &referenced_classes, g); + + Type *cls = g.class_impl_type; + while (cls) { + if (type_set_update(&class_set, cls)) { + break; + } + GB_ASSERT(cls->kind == Type_Named); + + cls = cls->Named.type_name->TypeName.objc_superclass; + } + } + + for (auto pair : class_set) { + auto& tn = pair.type->Named.type_name->TypeName; + Type *class_impl = !tn.objc_is_implementation ? nullptr : pair.type; + lb_handle_objc_find_or_register_class(p, tn.objc_class_name, class_impl); + } + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { + array_add( &referenced_classes, g ); + } + + // Add all class globals to a map so that we can look them up dynamically + // in order to resolve out-of-order because classes that are being implemented + // need their superclasses to have been registered before them. + StringMap global_class_map{}; + string_map_init(&global_class_map, (usize)gen->objc_classes.count); + defer (string_map_destroy(&global_class_map)); + + for (lbObjCGlobal g :referenced_classes) { + string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g}); + } + + LLVMSetLinkage(p->value, LLVMInternalLinkage); + lb_begin_procedure_body(p); + + // Register class globals, gathering classes that must be implemented + for (auto& kv : global_class_map) { + lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, kv.value.g, "objc_lookUpClass"); + } + + // Prefetch selectors for implemented methods so that they can also be registered. + for (const auto& cd : class_impls) { + auto& g = cd.g; + Type *class_type = g.class_impl_type; + + Array* methods = map_get(&m->info->objc_method_implementations, class_type); + if (!methods) { + continue; + } + + for (const ObjcMethodData& md : *methods) { + lb_handle_objc_find_or_register_selector(p, md.ac.objc_selector); + } + } + + // Now we can register all referenced selectors + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) { + lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, g, "sel_registerName"); + } + + + // Emit method wrapper implementations and registration + auto wrapper_args = array_make(temporary_allocator(), 2, 8); + + for (const auto& cd : class_impls) { + auto& g = cd.g; + Type *class_type = g.class_impl_type; + + Array* methods = map_get(&m->info->objc_method_implementations, class_type); + if (!methods) { + continue; + } + + Type *class_ptr_type = alloc_type_pointer(class_type); + lbValue class_value = cd.class_value; + + for (const ObjcMethodData& md : *methods) { + GB_ASSERT( md.proc_entity->kind == Entity_Procedure); + Type *method_type = md.proc_entity->type; + + String proc_name = make_string_c("__$objc_method::"); + proc_name = concatenate_strings(temporary_allocator(), proc_name, g.name); + proc_name = concatenate_strings(temporary_allocator(), proc_name, str_lit("::")); + proc_name = concatenate_strings( permanent_allocator(), proc_name, md.ac.objc_name); + + wrapper_args.count = 2; + wrapper_args[0] = md.ac.objc_is_class_method ? t_objc_Class : class_ptr_type; + wrapper_args[1] = t_objc_SEL; + + auto method_param_count = (isize)method_type->Proc.param_count; + i32 method_param_offset = 0; + + // TODO(harold): Need to make sure (at checker stage) that the non-class method has the self parameter already. + // (Maybe this is already accounted for?.) + if (!md.ac.objc_is_class_method) { + GB_ASSERT(method_param_count >= 1); + method_param_count -= 1; + method_param_offset = 1; + } + + for (i32 i = 0; i < method_param_count; i++) { + array_add(&wrapper_args, method_type->Proc.params->Tuple.variables[method_param_offset+i]->type); + } + + Type *wrapper_args_tuple = alloc_type_tuple_from_field_types(wrapper_args.data, wrapper_args.count, false, true); + Type *wrapper_proc_type = alloc_type_proc(nullptr, wrapper_args_tuple, (isize)wrapper_args_tuple->Tuple.variables.count, nullptr, 0, 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"); + + // Emit the wrapper + LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage); + lb_begin_procedure_body(wrapper_proc); + { + auto method_call_args = array_make(temporary_allocator(), method_param_count + (isize)method_param_offset); + + if (!md.ac.objc_is_class_method) { + method_call_args[0] = lbValue { + wrapper_proc->raw_input_parameters[0], + class_ptr_type, + }; + } + + 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, + }; + } + 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. + lb_emit_call(wrapper_proc, method_proc_value, method_call_args); + } + 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, temporary_allocator()); + } + + if (!md.ac.objc_is_class_method) { + method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("@:")); + } else { + method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:")); + } + + for (i32 i = method_param_offset; i < method_param_count; i++) { + Type *param_type = method_type->Proc.params->Tuple.variables[i]->type; + String param_encoding = lb_get_objc_type_encoding(param_type, temporary_allocator()); + + method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding); + } + + // Emit method registration + lbAddr* sel_address = string_map_get(&m->objc_selectors, md.ac.objc_selector); + GB_ASSERT(sel_address); + lbValue selector_value = lb_addr_load(p, *sel_address); + + args.count = 4; + args[0] = class_value; // Class + args[1] = selector_value; // SEL + 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. + lb_emit_runtime_call(p, "class_addMethod", args); + + } // End methods + + // Add ivar if we have one + Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar; + if (ivar_type != nullptr) { + // Register a single ivar for this class + Type *ivar_base = ivar_type->Named.base; + // TODO(harold): No idea if I can use this, but I assume so? + const i64 size = ivar_base->cached_size; + const i64 alignment = ivar_base->cached_align; + // TODO(harold): Checker: Alignment must be compatible with ivar rules. Or we should increase the alignment if needed. + + String ivar_name = str_lit("__$ivar"); + String ivar_types = str_lit("{= }"); + args.count = 5; + args[0] = class_value; + args[1] = lb_const_value(m, t_cstring, exact_value_string(ivar_name)); + args[2] = lb_const_value(m, t_uint, exact_value_u64((u64)size)); + args[3] = lb_const_value(m, t_u8, exact_value_u64((u64)alignment)); + args[4] = lb_const_value(m, t_cstring, exact_value_string(ivar_types)); + lb_emit_runtime_call(p, "class_addIvar", args); + } + + // Complete the class registration + args.count = 1; + args[0] = class_value; + lb_emit_runtime_call(p, "objc_registerClassPair", args); + + // If we have an ivar, store its offset globally for an intrinsic + // TODO(harold): Only do this for types that had ivar_get calls registered! + if (ivar_type != nullptr) { + args.count = 2; + args[0] = class_value; + args[1] = lb_const_value(m, t_cstring, exact_value_string(str_lit("__$ivar"))); + lbValue ivar = lb_emit_runtime_call(p, "class_getInstanceVariable", args); + + args.count = 1; + args[0] = ivar; + lbValue ivar_offset = lb_emit_runtime_call(p, "ivar_getOffset", args); + lbValue ivar_offset_u32 = lb_emit_conv(p, ivar_offset, t_u32); + + String class_name = class_type->Named.type_name->TypeName.objc_class_name; + // TODO(harold): Oops! This is wrong, that map is there to prevent re-entry. + // Simply emit from referred ivars. For now use a single module only. + lbAddr ivar_addr = string_map_must_get(&m->objc_ivars, class_name); + lb_addr_store(p, ivar_addr, ivar_offset_u32); + } + } lb_end_procedure_body(p); } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 6177fcf6e..7694c65c3 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -196,6 +196,7 @@ struct lbModule { StringMap objc_classes; StringMap objc_selectors; + StringMap objc_ivars; PtrMap map_cell_info_map; // address of runtime.Map_Info PtrMap map_info_map; // address of runtime.Map_Cell_Info @@ -219,6 +220,7 @@ struct lbObjCGlobal { gbString global_name; String name; Type * type; + Type * class_impl_type; // This is set when the class has the objc_implement attribute set to true. }; struct lbGenerator : LinkerData { @@ -240,6 +242,7 @@ struct lbGenerator : LinkerData { MPSCQueue entities_to_correct_linkage; MPSCQueue objc_selectors; MPSCQueue objc_classes; + MPSCQueue objc_ivars; }; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 421720c4c..7f012e006 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -101,6 +101,7 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { string_map_init(&m->objc_classes); string_map_init(&m->objc_selectors); + string_map_init(&m->objc_ivars); map_init(&m->map_info_map, 0); map_init(&m->map_cell_info_map, 0); @@ -173,6 +174,7 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { mpsc_init(&gen->entities_to_correct_linkage, heap_allocator()); mpsc_init(&gen->objc_selectors, heap_allocator()); mpsc_init(&gen->objc_classes, heap_allocator()); + mpsc_init(&gen->objc_ivars, heap_allocator()); return true; } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3212abd9a..bf4ebf377 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3290,6 +3290,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_objc_find_class: return lb_handle_objc_find_class(p, expr); case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr); 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_constant_utf16_cstring: diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index bfeebfcbe..897b71b5b 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2125,7 +2125,7 @@ gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, Stri return addr; } -gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { +gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name, Type *class_impl_type) { lbModule *m = p->module; lbAddr *found = string_map_get(&m->objc_classes, name); if (found) { @@ -2148,13 +2148,72 @@ gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String } else { LLVMSetLinkage(g.value, LLVMExternalLinkage); } - mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class}); + mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class, class_impl_type}); lbAddr addr = lb_addr(g); string_map_set(&m->objc_classes, name, addr); return addr; } +gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_type) { + + String name = self_type->Named.type_name->TypeName.objc_class_name; + GB_ASSERT(name != ""); + + lbAddr *found = string_map_get(&m->objc_ivars, name); + if (found) { + return *found; + } + + + lbModule *default_module = &m->gen->default_module; + + gbString global_name = gb_string_make(permanent_allocator(), "__$objc_ivar::"); + global_name = gb_string_append_length(global_name, name.text, name.len); + + // Create a global variable to store offset of the ivar in an instance of an object + Type *p_ivar_offset = alloc_type_pointer(t_u32); + + LLVMTypeRef t = lb_type(m, p_ivar_offset); + lbValue g = {}; + g.value = LLVMAddGlobal(m->mod, t, global_name); + g.type = p_ivar_offset; + + if (default_module == m) { + LLVMSetInitializer(g.value, LLVMConstNull(t)); + lb_add_member(m, make_string_c(global_name), g); + } else { + LLVMSetLinkage(g.value, LLVMExternalLinkage); + } + + mpsc_enqueue(&m->gen->objc_ivars, lbObjCGlobal{m, global_name, name, self_type}); + + lbAddr addr = lb_addr(g); + string_map_set(&m->objc_ivars, name, addr); + return addr; +} + +gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + lbModule *m = p->module; + + GB_ASSERT(ce->args[0]->tav.type->kind == Type_Pointer); + Type *self_type = ce->args[0]->tav.type->Pointer.elem; + Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar; + + Type* p_ivar = alloc_type_pointer(ivar_type); + + lbValue ivar_offset = lb_addr_load(p, lb_handle_objc_find_or_register_ivar(m, self_type)); + lbValue ivar_offset_uptr = lb_emit_conv(p, ivar_offset, t_uintptr); + + lbValue self = lb_build_expr(p, ce->args[0]); + lbValue self_uptr = lb_emit_conv(p, self, t_uintptr); + + lbValue ivar_uptr = lb_emit_arith(p, Token_Add, self_uptr, ivar_offset_uptr, t_uintptr); + + return lb_emit_conv(p, ivar_uptr, p_ivar); +} + gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { ast_node(ce, CallExpr, expr); @@ -2188,7 +2247,7 @@ gb_internal lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) { auto tav = ce->args[0]->tav; GB_ASSERT(tav.value.kind == ExactValue_String); String name = tav.value.value_string; - return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name)); + return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name, nullptr)); } gb_internal lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) { @@ -2198,7 +2257,7 @@ gb_internal lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) { auto tav = ce->args[0]->tav; GB_ASSERT(tav.value.kind == ExactValue_String); String name = tav.value.value_string; - lbAddr dst = lb_handle_objc_find_or_register_class(p, name); + lbAddr dst = lb_handle_objc_find_or_register_class(p, name, nullptr); auto args = array_make(permanent_allocator(), 3); args[0] = lb_const_nil(m, t_objc_Class); @@ -2220,7 +2279,9 @@ gb_internal lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) { GB_ASSERT(e->kind == Entity_TypeName); String name = e->TypeName.objc_class_name; - return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name)); + Type *class_impl_type = e->TypeName.objc_is_implementation ? type : nullptr; + + return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name, class_impl_type)); } return lb_build_expr(p, expr); @@ -2266,9 +2327,6 @@ gb_internal lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) { return lb_emit_call(p, the_proc, args); } - - - gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) { GB_ASSERT(value.kind == ExactValue_Integer); i64 v = exact_value_to_i64(value); diff --git a/src/types.cpp b/src/types.cpp index 9c9472a28..1b2545279 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -729,10 +729,12 @@ gb_global Type *t_map_set_proc = nullptr; 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_id = nullptr; gb_global Type *t_objc_SEL = nullptr; gb_global Type *t_objc_Class = nullptr; +gb_global Type *t_objc_Ivar = nullptr; enum OdinAtomicMemoryOrder : i32 { OdinAtomicMemoryOrder_relaxed = 0, // unordered -- cgit v1.2.3 From b3b4d501ca6c1af70c0ceef7610f6c5d83a2d296 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Mon, 21 Apr 2025 02:42:02 -0400 Subject: Fix ivar in multi-module mode. --- src/llvm_backend.cpp | 69 ++++++++++++++++++++++++++++++-------------- src/llvm_backend_utility.cpp | 14 ++++----- 2 files changed, 54 insertions(+), 29 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 23ad81847..ef975b8c1 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1588,6 +1588,13 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { // Emit method wrapper implementations and registration auto wrapper_args = array_make(temporary_allocator(), 2, 8); + PtrMap ivar_map{}; + map_init(&ivar_map, gen->objc_ivars.count); + + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_ivars, &g); /**/) { + map_set(&ivar_map, g.class_impl_type, g); + } + for (const auto& cd : class_impls) { auto& g = cd.g; Type *class_type = g.class_impl_type; @@ -1700,7 +1707,9 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { // Add ivar if we have one Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar; - if (ivar_type != nullptr) { + lbObjCGlobal *g_ivar = map_get(&ivar_map, class_type); + + if (ivar_type != nullptr && g_ivar != nullptr) { // Register a single ivar for this class Type *ivar_base = ivar_type->Named.base; // TODO(harold): No idea if I can use this, but I assume so? @@ -1723,28 +1732,46 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { args.count = 1; args[0] = class_value; lb_emit_runtime_call(p, "objc_registerClassPair", args); - - // If we have an ivar, store its offset globally for an intrinsic - // TODO(harold): Only do this for types that had ivar_get calls registered! - if (ivar_type != nullptr) { - args.count = 2; - args[0] = class_value; - args[1] = lb_const_value(m, t_cstring, exact_value_string(str_lit("__$ivar"))); - lbValue ivar = lb_emit_runtime_call(p, "class_getInstanceVariable", args); - - args.count = 1; - args[0] = ivar; - lbValue ivar_offset = lb_emit_runtime_call(p, "ivar_getOffset", args); - lbValue ivar_offset_u32 = lb_emit_conv(p, ivar_offset, t_u32); - - String class_name = class_type->Named.type_name->TypeName.objc_class_name; - // TODO(harold): Oops! This is wrong, that map is there to prevent re-entry. - // Simply emit from referred ivars. For now use a single module only. - lbAddr ivar_addr = string_map_must_get(&m->objc_ivars, class_name); - lb_addr_store(p, ivar_addr, ivar_offset_u32); - } } + // Register ivars + Type *ptr_u32 = alloc_type_pointer(t_u32); + for (auto const& kv : ivar_map) { + lbObjCGlobal const& g = kv.value; + lbAddr ivar_addr = {}; + lbValue *found = string_map_get(&m->members, g.global_name); + + if (found) { + ivar_addr = lb_addr(*found); + } else { + // Defined in an external package, must define now + LLVMTypeRef t = lb_type(m, t_u32); + + lbValue global{}; + global.value = LLVMAddGlobal(m->mod, t, g.global_name); + global.type = ptr_u32; + + LLVMSetInitializer(global.value, LLVMConstInt(t, 0, true)); + + ivar_addr = lb_addr(global); + } + + String class_name = g.class_impl_type->Named.type_name->TypeName.objc_class_name; + lbValue class_value = string_map_must_get(&global_class_map, class_name).class_value; + + args.count = 2; + args[0] = class_value; + args[1] = lb_const_value(m, t_cstring, exact_value_string(str_lit("__$ivar"))); + lbValue ivar = lb_emit_runtime_call(p, "class_getInstanceVariable", args); + + args.count = 1; + args[0] = ivar; + lbValue ivar_offset = lb_emit_runtime_call(p, "ivar_getOffset", args); + lbValue ivar_offset_u32 = lb_emit_conv(p, ivar_offset, t_u32); + + lb_addr_store(p, ivar_addr, ivar_offset_u32); + } + lb_end_procedure_body(p); } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 897b71b5b..ae7842ce6 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2165,28 +2165,26 @@ gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_ return *found; } - lbModule *default_module = &m->gen->default_module; gbString global_name = gb_string_make(permanent_allocator(), "__$objc_ivar::"); global_name = gb_string_append_length(global_name, name.text, name.len); // Create a global variable to store offset of the ivar in an instance of an object - Type *p_ivar_offset = alloc_type_pointer(t_u32); + LLVMTypeRef t = lb_type(m, t_u32); - LLVMTypeRef t = lb_type(m, p_ivar_offset); lbValue g = {}; g.value = LLVMAddGlobal(m->mod, t, global_name); - g.type = p_ivar_offset; + g.type = alloc_type_pointer(t_u32); if (default_module == m) { - LLVMSetInitializer(g.value, LLVMConstNull(t)); + LLVMSetInitializer(g.value, LLVMConstInt(t, 0, true)); lb_add_member(m, make_string_c(global_name), g); } else { LLVMSetLinkage(g.value, LLVMExternalLinkage); } - mpsc_enqueue(&m->gen->objc_ivars, lbObjCGlobal{m, global_name, name, self_type}); + mpsc_enqueue(&m->gen->objc_ivars, lbObjCGlobal{m, global_name, name, t_u32, self_type}); lbAddr addr = lb_addr(g); string_map_set(&m->objc_ivars, name, addr); @@ -2206,8 +2204,8 @@ gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) { lbValue ivar_offset = lb_addr_load(p, lb_handle_objc_find_or_register_ivar(m, self_type)); lbValue ivar_offset_uptr = lb_emit_conv(p, ivar_offset, t_uintptr); - lbValue self = lb_build_expr(p, ce->args[0]); - lbValue self_uptr = lb_emit_conv(p, self, t_uintptr); + lbValue self = lb_build_expr(p, ce->args[0]); + lbValue self_uptr = lb_emit_conv(p, self, t_uintptr); lbValue ivar_uptr = lb_emit_arith(p, Token_Add, self_uptr, ivar_offset_uptr, t_uintptr); -- cgit v1.2.3 From bca02f81cd5affa288bac0cc0ed08fe730072aec Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Tue, 22 Apr 2025 19:16:29 -0400 Subject: Include the ivar in the Objective-C class unconditionally of it being used or not. Allow pseudo-fields for ivar access. --- src/llvm_backend.cpp | 5 ++--- src/llvm_backend_expr.cpp | 32 ++++++++++++++++++++++++-------- src/llvm_backend_utility.cpp | 31 ++++++++++++++++++------------- src/types.cpp | 9 +++++++++ 4 files changed, 53 insertions(+), 24 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index ef975b8c1..257625849 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1707,9 +1707,8 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { // Add ivar if we have one Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar; - lbObjCGlobal *g_ivar = map_get(&ivar_map, class_type); - if (ivar_type != nullptr && g_ivar != nullptr) { + if (ivar_type != nullptr) { // Register a single ivar for this class Type *ivar_base = ivar_type->Named.base; // TODO(harold): No idea if I can use this, but I assume so? @@ -1734,7 +1733,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { lb_emit_runtime_call(p, "objc_registerClassPair", args); } - // Register ivars + // Register ivar offsets for any `objc_ivar_get` expressions emitted. Type *ptr_u32 = alloc_type_pointer(t_u32); for (auto const& kv : ivar_map) { lbObjCGlobal const& g = kv.value; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index c5ea0ddac..b9c01ad03 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -5138,8 +5138,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { return lb_build_addr(p, unparen_expr(se->selector)); } - - Type *type = base_type(tav.type); if (tav.mode == Addressing_Type) { // Addressing_Type Selection sel = lookup_field(tav.type, selector, true); if (sel.pseudo_field) { @@ -5174,18 +5172,37 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { return lb_addr_swizzle(a, type, swizzle_count, swizzle_indices); } - Selection sel = lookup_field(type, selector, false); + Selection sel = lookup_field(tav.type, selector, false); GB_ASSERT(sel.entity != nullptr); - if (sel.pseudo_field) { - GB_ASSERT(sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup); + if (sel.pseudo_field && (sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup)) { + // GB_ASSERT(sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup); Entity *e = entity_of_node(sel_node); GB_ASSERT(e->kind == Entity_Procedure); return lb_addr(lb_find_value_from_entity(p->module, e)); } - if (sel.is_bit_field) { - lbAddr addr = lb_build_addr(p, se->expr); + lbAddr addr = lb_build_addr(p, se->expr); + + // TODO(harold): Ensure objc_ivar is always null when objc_implement is not set! + Type *d_type = type_deref(tav.type); //base_type(tav.type); + if (d_type->kind == Type_Named && d_type->Named.type_name->TypeName.objc_ivar) { + // NOTE(harold): We need to load the ivar from the current address and + // replace addr with the loaded ivar addr to apply the selector load properly. + + // If it's a deep pointer, dereference it first + // TODO(harold): Ensure this is save to do here. lb_emit_deep_field_gep() has several derefs, once per index. + // Not sure what multiple indices represent... + Type* type = tav.type; + if (is_type_pointer(type)) { + type = type_deref(type); + addr = lb_addr(lb_emit_load(p, addr.addr)); + } + lbValue ivar_ptr = lb_handle_objc_ivar_for_objc_object_pointer(p, addr.addr); + addr = lb_addr(ivar_ptr); + } + + if (sel.is_bit_field) { Selection sub_sel = sel; sub_sel.index.count -= 1; @@ -5211,7 +5228,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { } { - lbAddr addr = lb_build_addr(p, se->expr); if (addr.kind == lbAddr_Map) { lbValue v = lb_addr_load(p, addr); lbValue a = lb_address_from_load_or_generate_local(p, v); diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index ae7842ce6..33211395a 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2191,25 +2191,30 @@ gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_ return addr; } -gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) { - ast_node(ce, CallExpr, expr); - lbModule *m = p->module; +gb_internal lbValue lb_handle_objc_ivar_for_objc_object_pointer(lbProcedure *p, lbValue self) { + GB_ASSERT(self.type->kind == Type_Pointer && self.type->Pointer.elem->kind == Type_Named); - GB_ASSERT(ce->args[0]->tav.type->kind == Type_Pointer); - Type *self_type = ce->args[0]->tav.type->Pointer.elem; - Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar; + Type *self_type = self.type->Pointer.elem; + + lbValue self_uptr = lb_emit_conv(p, self, t_uintptr); + + lbValue ivar_offset = lb_addr_load(p, lb_handle_objc_find_or_register_ivar(p->module, self_type)); + lbValue ivar_offset_uptr = lb_emit_conv(p, ivar_offset, t_uintptr); - Type* p_ivar = alloc_type_pointer(ivar_type); - lbValue ivar_offset = lb_addr_load(p, lb_handle_objc_find_or_register_ivar(m, self_type)); - lbValue ivar_offset_uptr = lb_emit_conv(p, ivar_offset, t_uintptr); + lbValue ivar_uptr = lb_emit_arith(p, Token_Add, self_uptr, ivar_offset_uptr, t_uintptr); - lbValue self = lb_build_expr(p, ce->args[0]); - lbValue self_uptr = lb_emit_conv(p, self, t_uintptr); + Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar; + return lb_emit_conv(p, ivar_uptr, alloc_type_pointer(ivar_type)); +} - lbValue ivar_uptr = lb_emit_arith(p, Token_Add, self_uptr, ivar_offset_uptr, t_uintptr); +gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + + GB_ASSERT(ce->args[0]->tav.type->kind == Type_Pointer); + lbValue self = lb_build_expr(p, ce->args[0]); - return lb_emit_conv(p, ivar_uptr, p_ivar); + return lb_handle_objc_ivar_for_objc_object_pointer(p, self); } gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { diff --git a/src/types.cpp b/src/types.cpp index 1b2545279..96c17f49a 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -3329,6 +3329,15 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name } } } + + Type *objc_ivar_type = e->TypeName.objc_ivar; + if (objc_ivar_type != nullptr) { + sel = lookup_field_with_selection(objc_ivar_type, field_name, false, sel, allow_blank_ident); + if (sel.entity != nullptr) { + sel.pseudo_field = true; + return sel; + } + } } if (is_type_polymorphic(type)) { -- cgit v1.2.3 From f3923ed66640ea9fd342ca851fdd2bd794405e0c Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Sun, 27 Apr 2025 22:48:16 -0400 Subject: Fix indentations Fix Objective-C wrapper procs not forwarding return value --- src/check_builtin.cpp | 144 +++--- src/check_decl.cpp | 174 +++---- src/checker.cpp | 150 +++--- src/checker.hpp | 18 +- src/checker_builtin_procs.hpp | 4 +- src/entity.cpp | 6 +- src/llvm_backend.cpp | 1108 +++++++++++++++++++++-------------------- src/llvm_backend.hpp | 6 +- src/llvm_backend_general.cpp | 4 +- src/llvm_backend_proc.cpp | 2 +- src/llvm_backend_utility.cpp | 14 +- 11 files changed, 822 insertions(+), 808 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 92942b4db..099f99045 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -389,77 +389,77 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan } break; case BuiltinProc_objc_ivar_get: - { - Type *self_type = nullptr; - Type *ivar_type = nullptr; - - Operand self = {}; - check_expr_or_type(c, &self, ce->args[0]); - - if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) { - gbString e = expr_to_string(self.expr); - gbString t = type_to_string(self.type); - error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); - gb_string_free(t); - gb_string_free(e); - return false; - } else if (!is_type_pointer(self.type)) { - gbString e = expr_to_string(self.expr); - gbString t = type_to_string(self.type); - error(self.expr, "'%.*s' expected a pointer of a value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); - gb_string_free(t); - gb_string_free(e); - return false; - } - - self_type = type_deref(self.type); - - if (!(self_type->kind == Type_Named && - self_type->Named.type_name != nullptr && - self_type->Named.type_name->TypeName.objc_class_name != "")) { - gbString t = type_to_string(self_type); - error(self.expr, "'%.*s' expected a named type with the attribute @(obj_class=) , got type %s", LIT(builtin_name), t); - gb_string_free(t); - return false; - } - - if (self_type->Named.type_name->TypeName.objc_ivar == nullptr) { - gbString t = type_to_string(self_type); - error(self.expr, "'%.*s' requires that type %s have the attribute @(obj_ivar=).", LIT(builtin_name), t); - gb_string_free(t); - return false; - } - - Operand ivar = {}; - check_expr_or_type(c, &ivar, ce->args[1]); - if (ivar.mode == Addressing_Type) { - ivar_type = ivar.type; - } else { - return false; - } - - if (self_type->Named.type_name->TypeName.objc_ivar != ivar_type) { - gbString name_self = type_to_string(self_type); - gbString name_expected = type_to_string(self_type->Named.type_name->TypeName.objc_ivar); - gbString name_given = type_to_string(ivar_type); - error(self.expr, "'%.*s' ivar type %s does not match @obj_ivar type %s on Objective-C class %s.", - LIT(builtin_name), name_given, name_expected, name_self); - gb_string_free(name_self); - gb_string_free(name_expected); - gb_string_free(name_given); - return false; - } - - if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) { - operand->type = type_hint; - } else { - operand->type = alloc_type_pointer(ivar_type); - } - - operand->mode = Addressing_Value; - - return true; - } break; + { + Type *self_type = nullptr; + Type *ivar_type = nullptr; + + Operand self = {}; + check_expr_or_type(c, &self, ce->args[0]); + + if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) { + gbString e = expr_to_string(self.expr); + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } else if (!is_type_pointer(self.type)) { + gbString e = expr_to_string(self.expr); + gbString t = type_to_string(self.type); + error(self.expr, "'%.*s' expected a pointer of a value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + self_type = type_deref(self.type); + + if (!(self_type->kind == Type_Named && + self_type->Named.type_name != nullptr && + self_type->Named.type_name->TypeName.objc_class_name != "")) { + gbString t = type_to_string(self_type); + error(self.expr, "'%.*s' expected a named type with the attribute @(obj_class=) , got type %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + if (self_type->Named.type_name->TypeName.objc_ivar == nullptr) { + gbString t = type_to_string(self_type); + error(self.expr, "'%.*s' requires that type %s have the attribute @(obj_ivar=).", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + Operand ivar = {}; + check_expr_or_type(c, &ivar, ce->args[1]); + if (ivar.mode == Addressing_Type) { + ivar_type = ivar.type; + } else { + return false; + } + + if (self_type->Named.type_name->TypeName.objc_ivar != ivar_type) { + gbString name_self = type_to_string(self_type); + gbString name_expected = type_to_string(self_type->Named.type_name->TypeName.objc_ivar); + gbString name_given = type_to_string(ivar_type); + error(self.expr, "'%.*s' ivar type %s does not match @obj_ivar type %s on Objective-C class %s.", + LIT(builtin_name), name_given, name_expected, name_self); + gb_string_free(name_self); + gb_string_free(name_expected); + gb_string_free(name_given); + return false; + } + + if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) { + operand->type = type_hint; + } else { + operand->type = alloc_type_pointer(ivar_type); + } + + operand->mode = Addressing_Value; + + return true; + } break; } } @@ -2206,7 +2206,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_objc_find_class: case BuiltinProc_objc_register_selector: case BuiltinProc_objc_register_class: - case BuiltinProc_objc_ivar_get: + case BuiltinProc_objc_ivar_get: return check_builtin_objc_procedure(c, operand, call, id, type_hint); case BuiltinProc___entry_point: diff --git a/src/check_decl.cpp b/src/check_decl.cpp index e67241b31..48e5172d6 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -526,68 +526,68 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac); if (e->kind == Entity_TypeName && ac.objc_class != "") { e->TypeName.objc_class_name = ac.objc_class; - e->TypeName.objc_superclass = ac.objc_superclass; - e->TypeName.objc_ivar = ac.objc_ivar; + e->TypeName.objc_superclass = ac.objc_superclass; + e->TypeName.objc_ivar = ac.objc_ivar; e->TypeName.objc_context_provider = ac.objc_context_provider; - if (ac.objc_is_implementation) { - e->TypeName.objc_is_implementation = true; - mpsc_enqueue(&ctx->info->objc_class_implementations, e); // TODO(harold): Don't need this for anything? See if needed when using explicit @export - - GB_ASSERT(e->TypeName.objc_ivar == nullptr || e->TypeName.objc_ivar->kind == Type_Named); - - // Enqueue the proc to be checked when resolved - if (e->TypeName.objc_context_provider != nullptr) { - mpsc_enqueue(&ctx->checker->procs_with_objc_context_provider_to_check, e); - } - - // @TODO(harold): I think there's a Check elsewhere in the checker for checking cycles. - // See about moving this to the right location. - // Ensure superclass hierarchy are all Objective-C classes and does not cycle - Type *super = ac.objc_superclass; - if (super != nullptr) { - TypeSet super_set{}; - type_set_init(&super_set, 8); - defer (type_set_destroy(&super_set)); - - type_set_update(&super_set, e->type); - - for (;;) { - if (type_set_update(&super_set, super)) { - error(e->token, "@(objc_superclass) Superclass hierarchy cycle encountered"); - break; - } - - if (super->kind != Type_Named) { - error(e->token, "@(objc_superclass) References type must be a named struct."); - break; - } - - Type* named_type = base_type(super->Named.type_name->type); - if (!is_type_objc_object(named_type)) { - error(e->token, "@(objc_superclass) Superclass must be an Objective-C class."); - break; - } - - super = super->Named.type_name->TypeName.objc_superclass; - if (super == nullptr) { - break; - } - - // TODO(harold): Is this the right way to do this??? The referenced entity must be already resolved - // so that we can access its objc_superclass attribute - check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info); - } - } - } else { - if (e->TypeName.objc_superclass != nullptr) { - error(e->token, "@(objc_superclass) can only be applied when the @(obj_implement) attribute is also applied"); - } else if (e->TypeName.objc_ivar != nullptr) { - error(e->token, "@(objc_ivar) can only be applied when the @(obj_implement) attribute is also applied"); - } else if (e->TypeName.objc_context_provider != nullptr) { - error(e->token, "@(objc_context_provider) can only be applied when the @(obj_implement) attribute is also applied"); - } - } + if (ac.objc_is_implementation) { + e->TypeName.objc_is_implementation = true; + mpsc_enqueue(&ctx->info->objc_class_implementations, e); // TODO(harold): Don't need this for anything? See if needed when using explicit @export + + GB_ASSERT(e->TypeName.objc_ivar == nullptr || e->TypeName.objc_ivar->kind == Type_Named); + + // Enqueue the proc to be checked when resolved + if (e->TypeName.objc_context_provider != nullptr) { + mpsc_enqueue(&ctx->checker->procs_with_objc_context_provider_to_check, e); + } + + // @TODO(harold): I think there's a Check elsewhere in the checker for checking cycles. + // See about moving this to the right location. + // Ensure superclass hierarchy are all Objective-C classes and does not cycle + Type *super = ac.objc_superclass; + if (super != nullptr) { + TypeSet super_set{}; + type_set_init(&super_set, 8); + defer (type_set_destroy(&super_set)); + + type_set_update(&super_set, e->type); + + for (;;) { + if (type_set_update(&super_set, super)) { + error(e->token, "@(objc_superclass) Superclass hierarchy cycle encountered"); + break; + } + + if (super->kind != Type_Named) { + error(e->token, "@(objc_superclass) References type must be a named struct."); + break; + } + + Type* named_type = base_type(super->Named.type_name->type); + if (!is_type_objc_object(named_type)) { + error(e->token, "@(objc_superclass) Superclass must be an Objective-C class."); + break; + } + + super = super->Named.type_name->TypeName.objc_superclass; + if (super == nullptr) { + break; + } + + // TODO(harold): Is this the right way to do this??? The referenced entity must be already resolved + // so that we can access its objc_superclass attribute + check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info); + } + } + } else { + if (e->TypeName.objc_superclass != nullptr) { + error(e->token, "@(objc_superclass) can only be applied when the @(obj_implement) attribute is also applied"); + } else if (e->TypeName.objc_ivar != nullptr) { + error(e->token, "@(objc_ivar) can only be applied when the @(obj_implement) attribute is also applied"); + } else if (e->TypeName.objc_context_provider != nullptr) { + error(e->token, "@(objc_context_provider) can only be applied when the @(obj_implement) attribute is also applied"); + } + } if (type_size_of(e->type) > 0) { error(e->token, "@(objc_class) marked type must be of zero size"); @@ -1005,37 +1005,37 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope"); } else { - if (ac.objc_is_implementation) { - GB_ASSERT(e->kind == Entity_Procedure); + if (ac.objc_is_implementation) { + GB_ASSERT(e->kind == Entity_Procedure); - Type *proc_type = e->type; + Type *proc_type = e->type; - 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 (proc_type->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_type->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 (!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 (proc_type->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_type->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 { auto method = ObjcMethodData{ ac, e }; - method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name; - - CheckerInfo *info = ctx->info; - mutex_lock(&info->objc_method_mutex); - defer (mutex_unlock(&info->objc_method_mutex)); - - Array* method_list = map_get(&info->objc_method_implementations, t); - if (method_list) { - array_add(method_list, method); - } else { - auto list = array_make(permanent_allocator(), 1, 8); - list[0] = method; - - map_set(&info->objc_method_implementations, t, list); - } - } - } + method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name; + + CheckerInfo *info = ctx->info; + mutex_lock(&info->objc_method_mutex); + defer (mutex_unlock(&info->objc_method_mutex)); + + Array* method_list = map_get(&info->objc_method_implementations, t); + if (method_list) { + array_add(method_list, method); + } else { + auto list = array_make(permanent_allocator(), 1, 8); + list[0] = method; + + map_set(&info->objc_method_implementations, t, list); + } + } + } mutex_lock(&global_type_name_objc_metadata_mutex); defer (mutex_unlock(&global_type_name_objc_metadata_mutex)); diff --git a/src/checker.cpp b/src/checker.cpp index 79c773a3c..6563b1c58 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1351,12 +1351,12 @@ gb_internal void init_universal(void) { t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete()); t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete()); t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete()); - t_objc_ivar = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_ivar"), alloc_type_struct_complete()); + t_objc_ivar = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_ivar"), alloc_type_struct_complete()); t_objc_id = alloc_type_pointer(t_objc_object); 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_Ivar = alloc_type_pointer(t_objc_ivar); } } @@ -1389,8 +1389,8 @@ gb_internal void init_checker_info(CheckerInfo *i) { array_init(&i->defineables, a); map_init(&i->objc_msgSend_types); - mpsc_init(&i->objc_class_implementations, a); - map_init(&i->objc_method_implementations); + mpsc_init(&i->objc_class_implementations, a); + map_init(&i->objc_method_implementations); string_map_init(&i->load_file_cache); array_init(&i->all_procedures, heap_allocator()); @@ -3352,10 +3352,10 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { ac->test = true; return true; } else if (name == "export") { - if (ac->objc_is_implementation) { - error(value, "Setting @(export) explicitly is not allowed when @(objc_implement) is set. It is exported implicitly."); - return false; - } + if (ac->objc_is_implementation) { + error(value, "Setting @(export) explicitly is not allowed when @(objc_implement) is set. It is exported implicitly."); + return false; + } ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_Invalid) { @@ -3369,10 +3369,10 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { return true; } else if (name == "linkage") { - if (ac->objc_is_implementation) { - error(value, "Explicit linkage not allowed when @(objc_implement) is set. It is set implicitly"); - return false; - } + if (ac->objc_is_implementation) { + error(value, "Explicit linkage not allowed when @(objc_implement) is set. It is set implicitly"); + return false; + } ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind != ExactValue_String) { @@ -3681,23 +3681,23 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } return true; } else if (name == "objc_implement") { - ExactValue ev = check_decl_attribute_value(c, value); - if (ev.kind == ExactValue_Bool) { - ac->objc_is_implementation = ev.value_bool; - } else if (ev.kind == ExactValue_Invalid) { - ac->objc_is_implementation = true; - } else { - error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name)); - } - - // This implies exported, strongly linked - if (ac->objc_is_implementation) { - ac->is_export = true; - ac->linkage = str_lit("strong"); - } - - return true; - } else if (name == "objc_selector") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Bool) { + ac->objc_is_implementation = ev.value_bool; + } else if (ev.kind == ExactValue_Invalid) { + ac->objc_is_implementation = true; + } else { + error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name)); + } + + // This implies exported, strongly linked + if (ac->objc_is_implementation) { + ac->is_export = true; + ac->linkage = str_lit("strong"); + } + + return true; + } else if (name == "objc_selector") { ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_String) { if (string_is_valid_identifier(ev.value_string)) { @@ -3949,52 +3949,52 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) { } return true; } else if (name == "objc_implement") { - ExactValue ev = check_decl_attribute_value(c, value); - if (ev.kind == ExactValue_Bool) { - ac->objc_is_implementation = ev.value_bool; - } else if (ev.kind == ExactValue_Invalid) { - ac->objc_is_implementation = true; - } else { - error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name)); - } - return true; - } else if (name == "objc_superclass") { - Type *objc_superclass = check_type(c, value); - - if (objc_superclass != nullptr) { - ac->objc_superclass = objc_superclass; - } else { - error(value, "'%.*s' expected a named type", LIT(name)); - } - return true; - } else if (name == "objc_ivar") { - Type *objc_ivar = check_type(c, value); - - if (objc_ivar != nullptr) { - ac->objc_ivar = objc_ivar; - } else { - error(value, "'%.*s' expected a named type", LIT(name)); - } - return true; - } else if (name == "objc_context_provider") { - Operand o = {}; - check_expr(c, &o, value); - Entity *e = entity_of_node(o.expr); - - if (e != nullptr) { - if (ac->objc_context_provider != nullptr) { - error(elem, "Previous usage of a 'objc_context_provider' attribute"); - } - if (e->kind != Entity_Procedure) { - error(elem, "'objc_context_provider' must refer to a procedure"); - } else { - ac->objc_context_provider = e; - } - - return true; - } - } - return false; + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Bool) { + ac->objc_is_implementation = ev.value_bool; + } else if (ev.kind == ExactValue_Invalid) { + ac->objc_is_implementation = true; + } else { + error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name)); + } + return true; + } else if (name == "objc_superclass") { + Type *objc_superclass = check_type(c, value); + + if (objc_superclass != nullptr) { + ac->objc_superclass = objc_superclass; + } else { + error(value, "'%.*s' expected a named type", LIT(name)); + } + return true; + } else if (name == "objc_ivar") { + Type *objc_ivar = check_type(c, value); + + if (objc_ivar != nullptr) { + ac->objc_ivar = objc_ivar; + } else { + error(value, "'%.*s' expected a named type", LIT(name)); + } + return true; + } else if (name == "objc_context_provider") { + Operand o = {}; + check_expr(c, &o, value); + Entity *e = entity_of_node(o.expr); + + if (e != nullptr) { + if (ac->objc_context_provider != nullptr) { + error(elem, "Previous usage of a 'objc_context_provider' attribute"); + } + if (e->kind != Entity_Procedure) { + error(elem, "'objc_context_provider' must refer to a procedure"); + } else { + ac->objc_context_provider = e; + } + + return true; + } + } + return false; } diff --git a/src/checker.hpp b/src/checker.hpp index 574c71c7f..336f09a7e 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -148,13 +148,13 @@ struct AttributeContext { String objc_class; String objc_name; - String objc_selector; + String objc_selector; Type * objc_type; - Type * objc_superclass; - Type * objc_ivar; + Type * objc_superclass; + Type * objc_ivar; Entity *objc_context_provider; bool objc_is_class_method : 1; - bool objc_is_implementation : 1; // This struct or proc provides a class/method implementation, not a binding to an existing type. + bool objc_is_implementation : 1; // This struct or proc provides a class/method implementation, not a binding to an existing type. String require_target_feature; // required by the target micro-architecture String enable_target_feature; // will be enabled for the procedure only @@ -371,8 +371,8 @@ struct ObjcMsgData { }; struct ObjcMethodData { - AttributeContext ac; - Entity *proc_entity; + AttributeContext ac; + Entity *proc_entity; }; enum LoadFileTier { @@ -489,10 +489,10 @@ struct CheckerInfo { BlockingMutex objc_types_mutex; PtrMap objc_msgSend_types; - MPSCQueue objc_class_implementations; + MPSCQueue objc_class_implementations; - BlockingMutex objc_method_mutex; - PtrMap> objc_method_implementations; + BlockingMutex objc_method_mutex; + PtrMap> objc_method_implementations; BlockingMutex load_file_mutex; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index cb2ce3915..ce7d8349b 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -331,7 +331,7 @@ BuiltinProc__type_end, BuiltinProc_objc_find_class, BuiltinProc_objc_register_selector, BuiltinProc_objc_register_class, - BuiltinProc_objc_ivar_get, + BuiltinProc_objc_ivar_get, BuiltinProc_constant_utf16_cstring, @@ -674,7 +674,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {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("ivar_get"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("ivar_get"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/entity.cpp b/src/entity.cpp index a5443cf27..cc41b5e59 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -235,9 +235,9 @@ struct Entity { Type * type_parameter_specialization; String ir_mangled_name; bool is_type_alias; - bool objc_is_implementation; - Type* objc_superclass; - Type* objc_ivar; + bool objc_is_implementation; + Type* objc_superclass; + Type* objc_ivar; Entity*objc_context_provider; String objc_class_name; TypeNameObjCMetadata *objc_metadata; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index fad542d4a..7ffd4ea30 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1176,327 +1176,327 @@ gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) { // TODO(harold): Move this out of here and into a more suitable place. // TODO(harold): Should not take an allocator, but always use temp, as we return string literals as well. String lb_get_objc_type_encoding(Type *t, gbAllocator allocator, isize pointer_depth = 0) { - // NOTE(harold): See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 - - // NOTE(harold): Darwin targets are always 64-bit. Should we drop this and assume "q" always? - #define INT_SIZE_ENCODING (build_context.metrics.ptr_size == 4 ? "i" : "q") - switch (t->kind) { - case Type_Basic: { - switch (t->Basic.kind) { - case Basic_Invalid: - return str_lit("?"); - - case Basic_llvm_bool: - case Basic_bool: - case Basic_b8: - return str_lit("B"); - - case Basic_b16: - return str_lit("C"); - case Basic_b32: - return str_lit("I"); - case Basic_b64: - return str_lit("q"); - case Basic_i8: - return str_lit("c"); - case Basic_u8: - return str_lit("C"); - case Basic_i16: - case Basic_i16le: - case Basic_i16be: - return str_lit("s"); - case Basic_u16: - case Basic_u16le: - case Basic_u16be: - return str_lit("S"); - case Basic_i32: - case Basic_i32le: - case Basic_i32be: - return str_lit("i"); - case Basic_u32le: - case Basic_u32: - case Basic_u32be: - return str_lit("I"); - case Basic_i64: - case Basic_i64le: - case Basic_i64be: - return str_lit("q"); - case Basic_u64: - case Basic_u64le: - case Basic_u64be: - return str_lit("Q"); - case Basic_i128: - case Basic_i128le: - case Basic_i128be: - return str_lit("t"); - case Basic_u128: - case Basic_u128le: - case Basic_u128be: - return str_lit("T"); - case Basic_rune: - return str_lit("I"); - case Basic_f16: - case Basic_f16le: - case Basic_f16be: - return str_lit("s"); // @harold: Closest we've got? - case Basic_f32: - case Basic_f32le: - case Basic_f32be: - return str_lit("f"); - case Basic_f64: - case Basic_f64le: - case Basic_f64be: - return str_lit("d"); - - // TODO(harold) These: - case Basic_complex32: - case Basic_complex64: - case Basic_complex128: - case Basic_quaternion64: - case Basic_quaternion128: - case Basic_quaternion256: - return str_lit("?"); - - case Basic_int: - return str_lit(INT_SIZE_ENCODING); - case Basic_uint: - return build_context.metrics.ptr_size == 4 ? str_lit("I") : str_lit("Q"); - case Basic_uintptr: - case Basic_rawptr: - return str_lit("^v"); - - case Basic_string: - return build_context.metrics.ptr_size == 4 ? str_lit("{string=*i}") : str_lit("{string=*q}"); - - case Basic_cstring: return str_lit("*"); - case Basic_any: return str_lit("{any=^v^v"); // rawptr + ^Type_Info - - case Basic_typeid: - GB_ASSERT(t->Basic.size == 8); - return str_lit("q"); - - // Untyped types - case Basic_UntypedBool: - case Basic_UntypedInteger: - case Basic_UntypedFloat: - case Basic_UntypedComplex: - case Basic_UntypedQuaternion: - case Basic_UntypedString: - case Basic_UntypedRune: - case Basic_UntypedNil: - case Basic_UntypedUninit: - GB_PANIC("Untyped types cannot be @encoded()"); - return str_lit("?"); - } - break; - } - - case Type_Named: - case Type_Struct: - case Type_Union: { - Type* base = t; - if (base->kind == Type_Named) { - base = base_type(base); - if(base->kind != Type_Struct && base->kind != Type_Union) { - return lb_get_objc_type_encoding(base, allocator, pointer_depth); - } - } - - const bool is_union = base->kind == Type_Union; - if (!is_union) { - // Check for objc_SEL - if (internal_check_is_assignable_to(base, t_objc_SEL)) { - return str_lit(":"); - } - - // Check for objc_Class - if (internal_check_is_assignable_to(base, t_objc_SEL)) { - return str_lit("#"); - } - - // Treat struct as an Objective-C Class? - if (has_type_got_objc_class_attribute(base) && pointer_depth == 0) { - return str_lit("#"); - } - } - - if (is_type_objc_object(base)) { - return str_lit("@"); - } - - - gbString s = gb_string_make_reserve(allocator, 16); - s = gb_string_append_length(s, is_union ? "(" :"{", 1); - if (t->kind == Type_Named) { - s = gb_string_append_length(s, t->Named.name.text, t->Named.name.len); - } - - // Write fields - if (pointer_depth < 2) { - s = gb_string_append_length(s, "=", 1); - - if (!is_union) { - for( auto& f : base->Struct.fields ) { - String field_type = lb_get_objc_type_encoding(f->type, allocator, pointer_depth); - s = gb_string_append_length(s, field_type.text, field_type.len); - } - } else { - // #TODO(harold): Encode fields - } - } - - s = gb_string_append_length(s, is_union ? ")" :"}", 1); - - return make_string_c(s); - } - - case Type_Generic: - GB_PANIC("Generic types cannot be @encoded()"); - return str_lit("?"); - - case Type_Pointer: { - String pointee = lb_get_objc_type_encoding(t->Pointer.elem, allocator, pointer_depth +1); - // Special case for Objective-C Objects - if (pointer_depth == 0 && pointee == "@") { - return pointee; - } - - return concatenate_strings(allocator, str_lit("^"), pointee); - } - - case Type_MultiPointer: - return concatenate_strings(allocator, str_lit("^"), lb_get_objc_type_encoding(t->Pointer.elem, allocator, pointer_depth +1)); - - case Type_Array: { - String type_str = lb_get_objc_type_encoding(t->Array.elem, allocator, pointer_depth); - - gbString s = gb_string_make_reserve(allocator, type_str.len + 8); - s = gb_string_append_fmt(s, "[%lld%s]", t->Array.count, type_str.text); - return make_string_c(s); - } - - case Type_EnumeratedArray: { - String type_str = lb_get_objc_type_encoding(t->EnumeratedArray.elem, allocator, pointer_depth); - - gbString s = gb_string_make_reserve(allocator, type_str.len + 8); - s = gb_string_append_fmt(s, "[%lld%s]", t->EnumeratedArray.count, type_str.text); - return make_string_c(s); - } - - case Type_Slice: { - String type_str = lb_get_objc_type_encoding(t->Slice.elem, allocator, pointer_depth); - gbString s = gb_string_make_reserve(allocator, type_str.len + 8); - s = gb_string_append_fmt(s, "{slice=^%s%s}", type_str, INT_SIZE_ENCODING); - return make_string_c(s); - } - - case Type_DynamicArray: { - String type_str = lb_get_objc_type_encoding(t->DynamicArray.elem, allocator, pointer_depth); - gbString s = gb_string_make_reserve(allocator, type_str.len + 8); - s = gb_string_append_fmt(s, "{dynamic=^%s%s%sAllocator={?^v}}", type_str, INT_SIZE_ENCODING, INT_SIZE_ENCODING); - return make_string_c(s); - } - - case Type_Map: - return str_lit("{^v^v{Allocator=?^v}}"); - case Type_Enum: - return lb_get_objc_type_encoding(t->Enum.base_type, allocator, pointer_depth); - case Type_Tuple: - // NOTE(harold): Is this allowed here? - return str_lit("?"); - case Type_Proc: - return str_lit("?"); - case Type_BitSet: - return lb_get_objc_type_encoding(t->BitSet.underlying, allocator, pointer_depth); - case Type_SimdVector: - break; - case Type_Matrix: - break; - case Type_BitField: - return lb_get_objc_type_encoding(t->BitField.backing_type, allocator, pointer_depth); - case Type_SoaPointer: { - gbString s = gb_string_make_reserve(allocator, 8); - s = gb_string_append_fmt(s, "{=^v%s}", INT_SIZE_ENCODING); - return make_string_c(s); - } - - } // End switch t->kind - #undef INT_SIZE_ENCODING - - GB_PANIC("Unreachable"); + // NOTE(harold): See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 + + // NOTE(harold): Darwin targets are always 64-bit. Should we drop this and assume "q" always? + #define INT_SIZE_ENCODING (build_context.metrics.ptr_size == 4 ? "i" : "q") + switch (t->kind) { + case Type_Basic: { + switch (t->Basic.kind) { + case Basic_Invalid: + return str_lit("?"); + + case Basic_llvm_bool: + case Basic_bool: + case Basic_b8: + return str_lit("B"); + + case Basic_b16: + return str_lit("C"); + case Basic_b32: + return str_lit("I"); + case Basic_b64: + return str_lit("q"); + case Basic_i8: + return str_lit("c"); + case Basic_u8: + return str_lit("C"); + case Basic_i16: + case Basic_i16le: + case Basic_i16be: + return str_lit("s"); + case Basic_u16: + case Basic_u16le: + case Basic_u16be: + return str_lit("S"); + case Basic_i32: + case Basic_i32le: + case Basic_i32be: + return str_lit("i"); + case Basic_u32le: + case Basic_u32: + case Basic_u32be: + return str_lit("I"); + case Basic_i64: + case Basic_i64le: + case Basic_i64be: + return str_lit("q"); + case Basic_u64: + case Basic_u64le: + case Basic_u64be: + return str_lit("Q"); + case Basic_i128: + case Basic_i128le: + case Basic_i128be: + return str_lit("t"); + case Basic_u128: + case Basic_u128le: + case Basic_u128be: + return str_lit("T"); + case Basic_rune: + return str_lit("I"); + case Basic_f16: + case Basic_f16le: + case Basic_f16be: + return str_lit("s"); // @harold: Closest we've got? + case Basic_f32: + case Basic_f32le: + case Basic_f32be: + return str_lit("f"); + case Basic_f64: + case Basic_f64le: + case Basic_f64be: + return str_lit("d"); + + // TODO(harold) These: + case Basic_complex32: + case Basic_complex64: + case Basic_complex128: + case Basic_quaternion64: + case Basic_quaternion128: + case Basic_quaternion256: + return str_lit("?"); + + case Basic_int: + return str_lit(INT_SIZE_ENCODING); + case Basic_uint: + return build_context.metrics.ptr_size == 4 ? str_lit("I") : str_lit("Q"); + case Basic_uintptr: + case Basic_rawptr: + return str_lit("^v"); + + case Basic_string: + return build_context.metrics.ptr_size == 4 ? str_lit("{string=*i}") : str_lit("{string=*q}"); + + case Basic_cstring: return str_lit("*"); + case Basic_any: return str_lit("{any=^v^v"); // rawptr + ^Type_Info + + case Basic_typeid: + GB_ASSERT(t->Basic.size == 8); + return str_lit("q"); + + // Untyped types + case Basic_UntypedBool: + case Basic_UntypedInteger: + case Basic_UntypedFloat: + case Basic_UntypedComplex: + case Basic_UntypedQuaternion: + case Basic_UntypedString: + case Basic_UntypedRune: + case Basic_UntypedNil: + case Basic_UntypedUninit: + GB_PANIC("Untyped types cannot be @encoded()"); + return str_lit("?"); + } + break; + } + + case Type_Named: + case Type_Struct: + case Type_Union: { + Type* base = t; + if (base->kind == Type_Named) { + base = base_type(base); + if(base->kind != Type_Struct && base->kind != Type_Union) { + return lb_get_objc_type_encoding(base, allocator, pointer_depth); + } + } + + const bool is_union = base->kind == Type_Union; + if (!is_union) { + // Check for objc_SEL + if (internal_check_is_assignable_to(base, t_objc_SEL)) { + return str_lit(":"); + } + + // Check for objc_Class + if (internal_check_is_assignable_to(base, t_objc_SEL)) { + return str_lit("#"); + } + + // Treat struct as an Objective-C Class? + if (has_type_got_objc_class_attribute(base) && pointer_depth == 0) { + return str_lit("#"); + } + } + + if (is_type_objc_object(base)) { + return str_lit("@"); + } + + + gbString s = gb_string_make_reserve(allocator, 16); + s = gb_string_append_length(s, is_union ? "(" :"{", 1); + if (t->kind == Type_Named) { + s = gb_string_append_length(s, t->Named.name.text, t->Named.name.len); + } + + // Write fields + if (pointer_depth < 2) { + s = gb_string_append_length(s, "=", 1); + + if (!is_union) { + for( auto& f : base->Struct.fields ) { + String field_type = lb_get_objc_type_encoding(f->type, allocator, pointer_depth); + s = gb_string_append_length(s, field_type.text, field_type.len); + } + } else { + // #TODO(harold): Encode fields + } + } + + s = gb_string_append_length(s, is_union ? ")" :"}", 1); + + return make_string_c(s); + } + + case Type_Generic: + GB_PANIC("Generic types cannot be @encoded()"); + return str_lit("?"); + + case Type_Pointer: { + String pointee = lb_get_objc_type_encoding(t->Pointer.elem, allocator, pointer_depth +1); + // Special case for Objective-C Objects + if (pointer_depth == 0 && pointee == "@") { + return pointee; + } + + return concatenate_strings(allocator, str_lit("^"), pointee); + } + + case Type_MultiPointer: + return concatenate_strings(allocator, str_lit("^"), lb_get_objc_type_encoding(t->Pointer.elem, allocator, pointer_depth +1)); + + case Type_Array: { + String type_str = lb_get_objc_type_encoding(t->Array.elem, allocator, pointer_depth); + + gbString s = gb_string_make_reserve(allocator, type_str.len + 8); + s = gb_string_append_fmt(s, "[%lld%s]", t->Array.count, type_str.text); + return make_string_c(s); + } + + case Type_EnumeratedArray: { + String type_str = lb_get_objc_type_encoding(t->EnumeratedArray.elem, allocator, pointer_depth); + + gbString s = gb_string_make_reserve(allocator, type_str.len + 8); + s = gb_string_append_fmt(s, "[%lld%s]", t->EnumeratedArray.count, type_str.text); + return make_string_c(s); + } + + case Type_Slice: { + String type_str = lb_get_objc_type_encoding(t->Slice.elem, allocator, pointer_depth); + gbString s = gb_string_make_reserve(allocator, type_str.len + 8); + s = gb_string_append_fmt(s, "{slice=^%s%s}", type_str, INT_SIZE_ENCODING); + return make_string_c(s); + } + + case Type_DynamicArray: { + String type_str = lb_get_objc_type_encoding(t->DynamicArray.elem, allocator, pointer_depth); + gbString s = gb_string_make_reserve(allocator, type_str.len + 8); + s = gb_string_append_fmt(s, "{dynamic=^%s%s%sAllocator={?^v}}", type_str, INT_SIZE_ENCODING, INT_SIZE_ENCODING); + return make_string_c(s); + } + + case Type_Map: + return str_lit("{^v^v{Allocator=?^v}}"); + case Type_Enum: + return lb_get_objc_type_encoding(t->Enum.base_type, allocator, pointer_depth); + case Type_Tuple: + // NOTE(harold): Is this allowed here? + return str_lit("?"); + case Type_Proc: + return str_lit("?"); + case Type_BitSet: + return lb_get_objc_type_encoding(t->BitSet.underlying, allocator, pointer_depth); + case Type_SimdVector: + break; + case Type_Matrix: + break; + case Type_BitField: + return lb_get_objc_type_encoding(t->BitField.backing_type, allocator, pointer_depth); + case Type_SoaPointer: { + gbString s = gb_string_make_reserve(allocator, 8); + s = gb_string_append_fmt(s, "{=^v%s}", INT_SIZE_ENCODING); + return make_string_c(s); + } + + } // End switch t->kind + #undef INT_SIZE_ENCODING + + GB_PANIC("Unreachable"); } struct lbObjCGlobalClass { - lbObjCGlobal g; - lbValue class_value; // Local registered class value + lbObjCGlobal g; + lbValue class_value; // Local registered class value }; gb_internal void lb_register_objc_thing( - StringSet &handled, - lbModule *m, - Array &args, - Array &class_impls, - StringMap &class_map, - lbProcedure *p, - lbObjCGlobal const &g, - char const *call + StringSet &handled, + lbModule *m, + Array &args, + Array &class_impls, + StringMap &class_map, + lbProcedure *p, + lbObjCGlobal const &g, + char const *call ) { - if (string_set_update(&handled, g.name)) { - return; - } - - lbAddr addr = {}; - lbValue *found = string_map_get(&m->members, g.global_name); - if (found) { - addr = lb_addr(*found); - } else { - lbValue v = {}; - LLVMTypeRef t = lb_type(m, g.type); - v.value = LLVMAddGlobal(m->mod, t, g.global_name); - v.type = alloc_type_pointer(g.type); - addr = lb_addr(v); - 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{}; - - 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; - } - - args.count = 3; - args[0] = superclass == nullptr ? lb_const_nil(m, t_objc_Class) : 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); - - array_add(&class_impls, lbObjCGlobalClass{g, class_ptr}); - } - else { - args.count = 1; - args[0] = class_name; - class_ptr = lb_emit_runtime_call(p, call, args); - } - - 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; - } + if (string_set_update(&handled, g.name)) { + return; + } + + lbAddr addr = {}; + lbValue *found = string_map_get(&m->members, g.global_name); + if (found) { + addr = lb_addr(*found); + } else { + lbValue v = {}; + LLVMTypeRef t = lb_type(m, g.type); + v.value = LLVMAddGlobal(m->mod, t, g.global_name); + v.type = alloc_type_pointer(g.type); + addr = lb_addr(v); + 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{}; + + 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; + } + + args.count = 3; + args[0] = superclass == nullptr ? lb_const_nil(m, t_objc_Class) : 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); + + array_add(&class_impls, lbObjCGlobalClass{g, class_ptr}); + } + else { + args.count = 1; + args[0] = class_name; + class_ptr = lb_emit_runtime_call(p, call, args); + } + + 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; + } } gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { @@ -1513,80 +1513,80 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { defer (string_set_destroy(&handled)); auto args = array_make(temporary_allocator(), 3, 8); - auto class_impls = array_make(temporary_allocator(), 0, 16); - - // Ensure classes that have been implicitly referenced through - // the objc_superclass attribute have a global variable available for them. - TypeSet class_set{}; - type_set_init(&class_set, gen->objc_classes.count+16); - defer (type_set_destroy(&class_set)); - - auto referenced_classes = array_make(temporary_allocator()); - for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { - array_add( &referenced_classes, g); - - Type *cls = g.class_impl_type; - while (cls) { - if (type_set_update(&class_set, cls)) { - break; - } - GB_ASSERT(cls->kind == Type_Named); - - cls = cls->Named.type_name->TypeName.objc_superclass; - } - } - - for (auto pair : class_set) { - auto& tn = pair.type->Named.type_name->TypeName; - Type *class_impl = !tn.objc_is_implementation ? nullptr : pair.type; - lb_handle_objc_find_or_register_class(p, tn.objc_class_name, class_impl); - } - for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { - array_add( &referenced_classes, g ); - } - - // Add all class globals to a map so that we can look them up dynamically - // in order to resolve out-of-order because classes that are being implemented - // need their superclasses to have been registered before them. - StringMap global_class_map{}; - string_map_init(&global_class_map, (usize)gen->objc_classes.count); - defer (string_map_destroy(&global_class_map)); - - for (lbObjCGlobal g :referenced_classes) { - string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g}); - } - - LLVMSetLinkage(p->value, LLVMInternalLinkage); - lb_begin_procedure_body(p); - - // Register class globals, gathering classes that must be implemented - for (auto& kv : global_class_map) { - lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, kv.value.g, "objc_lookUpClass"); - } - - // Prefetch selectors for implemented methods so that they can also be registered. - for (const auto& cd : class_impls) { - auto& g = cd.g; - Type *class_type = g.class_impl_type; - - Array* methods = map_get(&m->info->objc_method_implementations, class_type); - if (!methods) { - continue; - } - - for (const ObjcMethodData& md : *methods) { - lb_handle_objc_find_or_register_selector(p, md.ac.objc_selector); - } - } - - // Now we can register all referenced selectors - for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) { - lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, g, "sel_registerName"); - } - - - // Emit method wrapper implementations and registration - auto wrapper_args = array_make(temporary_allocator(), 2, 8); + auto class_impls = array_make(temporary_allocator(), 0, 16); + + // Ensure classes that have been implicitly referenced through + // the objc_superclass attribute have a global variable available for them. + TypeSet class_set{}; + type_set_init(&class_set, gen->objc_classes.count+16); + defer (type_set_destroy(&class_set)); + + auto referenced_classes = array_make(temporary_allocator()); + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { + array_add( &referenced_classes, g); + + Type *cls = g.class_impl_type; + while (cls) { + if (type_set_update(&class_set, cls)) { + break; + } + GB_ASSERT(cls->kind == Type_Named); + + cls = cls->Named.type_name->TypeName.objc_superclass; + } + } + + for (auto pair : class_set) { + auto& tn = pair.type->Named.type_name->TypeName; + Type *class_impl = !tn.objc_is_implementation ? nullptr : pair.type; + lb_handle_objc_find_or_register_class(p, tn.objc_class_name, class_impl); + } + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) { + array_add( &referenced_classes, g ); + } + + // Add all class globals to a map so that we can look them up dynamically + // in order to resolve out-of-order because classes that are being implemented + // need their superclasses to have been registered before them. + StringMap global_class_map{}; + string_map_init(&global_class_map, (usize)gen->objc_classes.count); + defer (string_map_destroy(&global_class_map)); + + for (lbObjCGlobal g :referenced_classes) { + string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g}); + } + + LLVMSetLinkage(p->value, LLVMInternalLinkage); + lb_begin_procedure_body(p); + + // Register class globals, gathering classes that must be implemented + for (auto& kv : global_class_map) { + lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, kv.value.g, "objc_lookUpClass"); + } + + // Prefetch selectors for implemented methods so that they can also be registered. + for (const auto& cd : class_impls) { + auto& g = cd.g; + Type *class_type = g.class_impl_type; + + Array* methods = map_get(&m->info->objc_method_implementations, class_type); + if (!methods) { + continue; + } + + for (const ObjcMethodData& md : *methods) { + lb_handle_objc_find_or_register_selector(p, md.ac.objc_selector); + } + } + + // Now we can register all referenced selectors + for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) { + lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, g, "sel_registerName"); + } + + + // Emit method wrapper implementations and registration + auto wrapper_args = array_make(temporary_allocator(), 2, 8); auto get_context_args = array_make(temporary_allocator(), 1); @@ -1597,186 +1597,200 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { map_set(&ivar_map, g.class_impl_type, g); } - for (const auto& cd : class_impls) { - auto& g = cd.g; - Type *class_type = g.class_impl_type; - Type *class_ptr_type = alloc_type_pointer(class_type); - lbValue class_value = cd.class_value; + for (const auto& cd : class_impls) { + auto& g = cd.g; + Type *class_type = g.class_impl_type; + Type *class_ptr_type = alloc_type_pointer(class_type); + lbValue class_value = cd.class_value; - Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar; + Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar; - Entity *context_provider = class_type->Named.type_name->TypeName.objc_context_provider; - Type *contex_provider_self_ptr_type = nullptr; - Type *contex_provider_self_named_type = nullptr; - bool is_context_provider_ivar = false; - lbValue context_provider_proc_value{}; + Entity *context_provider = class_type->Named.type_name->TypeName.objc_context_provider; + Type *contex_provider_self_ptr_type = nullptr; + Type *contex_provider_self_named_type = nullptr; + bool is_context_provider_ivar = false; + lbValue context_provider_proc_value{}; - if (context_provider) { - context_provider_proc_value = lb_find_procedure_value_from_entity(m, context_provider); + if (context_provider) { + context_provider_proc_value = lb_find_procedure_value_from_entity(m, context_provider); - contex_provider_self_ptr_type = base_type(context_provider->type->Proc.params->Tuple.variables[0]->type); - GB_ASSERT(contex_provider_self_ptr_type->kind == Type_Pointer); - contex_provider_self_named_type = base_named_type(type_deref(contex_provider_self_ptr_type)); + contex_provider_self_ptr_type = base_type(context_provider->type->Proc.params->Tuple.variables[0]->type); + GB_ASSERT(contex_provider_self_ptr_type->kind == Type_Pointer); + contex_provider_self_named_type = base_named_type(type_deref(contex_provider_self_ptr_type)); - is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type); - } + is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type); + } - Array* methods = map_get(&m->info->objc_method_implementations, class_type); - if (!methods) { - continue; - } + Array* methods = map_get(&m->info->objc_method_implementations, class_type); + if (!methods) { + continue; + } - for (const ObjcMethodData& md : *methods) { - GB_ASSERT( md.proc_entity->kind == Entity_Procedure); - Type *method_type = md.proc_entity->type; + for (const ObjcMethodData& md : *methods) { + GB_ASSERT( md.proc_entity->kind == Entity_Procedure); + Type *method_type = md.proc_entity->type; - String proc_name = make_string_c("__$objc_method::"); - proc_name = concatenate_strings(temporary_allocator(), proc_name, g.name); - proc_name = concatenate_strings(temporary_allocator(), proc_name, str_lit("::")); - proc_name = concatenate_strings( permanent_allocator(), proc_name, md.ac.objc_name); + String proc_name = make_string_c("__$objc_method::"); + proc_name = concatenate_strings(temporary_allocator(), proc_name, g.name); + proc_name = concatenate_strings(temporary_allocator(), proc_name, str_lit("::")); + proc_name = concatenate_strings( permanent_allocator(), proc_name, md.ac.objc_name); - wrapper_args.count = 2; - wrapper_args[0] = md.ac.objc_is_class_method ? t_objc_Class : class_ptr_type; - wrapper_args[1] = t_objc_SEL; + wrapper_args.count = 2; + wrapper_args[0] = md.ac.objc_is_class_method ? t_objc_Class : class_ptr_type; + wrapper_args[1] = t_objc_SEL; - auto method_param_count = (isize)method_type->Proc.param_count; - i32 method_param_offset = 0; + auto method_param_count = (isize)method_type->Proc.param_count; + i32 method_param_offset = 0; - // TODO(harold): Need to make sure (at checker stage) that the non-class method has the self parameter already. - // (Maybe this is already accounted for?.) - if (!md.ac.objc_is_class_method) { - GB_ASSERT(method_param_count >= 1); - method_param_count -= 1; - method_param_offset = 1; - } + // TODO(harold): Need to make sure (at checker stage) that the non-class method has the self parameter already. + // (Maybe this is already accounted for?.) + if (!md.ac.objc_is_class_method) { + GB_ASSERT(method_param_count >= 1); + method_param_count -= 1; + method_param_offset = 1; + } - for (i32 i = 0; i < method_param_count; i++) { - array_add(&wrapper_args, method_type->Proc.params->Tuple.variables[method_param_offset+i]->type); - } + for (i32 i = 0; i < method_param_count; i++) { + array_add(&wrapper_args, method_type->Proc.params->Tuple.variables[method_param_offset+i]->type); + } - Type *wrapper_args_tuple = alloc_type_tuple_from_field_types(wrapper_args.data, wrapper_args.count, false, true); - Type *wrapper_proc_type = alloc_type_proc(nullptr, wrapper_args_tuple, (isize)wrapper_args_tuple->Tuple.variables.count, nullptr, 0, false, ProcCC_CDecl); + Type *wrapper_args_tuple = alloc_type_tuple_from_field_types(wrapper_args.data, wrapper_args.count, false, true); + Type *wrapper_results_tuple = nullptr; - 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"); + if (method_type->Proc.result_count > 0) { + GB_ASSERT(method_type->Proc.result_count == 1); + wrapper_results_tuple = alloc_type_tuple_from_field_types(&method_type->Proc.results->Tuple.variables[0]->type, 1, false, true); + } + + Type *wrapper_proc_type = alloc_type_proc(nullptr, wrapper_args_tuple, wrapper_args_tuple->Tuple.variables.count, + wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl); - // Emit the wrapper - LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage); - lb_begin_procedure_body(wrapper_proc); - { - if (method_type->Proc.calling_convention == ProcCC_Odin) { - GB_ASSERT(context_provider); + 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"); - // Emit the get odin context call + // Emit the wrapper + LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage); + lb_begin_procedure_body(wrapper_proc); + { + if (method_type->Proc.calling_convention == ProcCC_Odin) { + GB_ASSERT(context_provider); - get_context_args[0] = lbValue { - wrapper_proc->raw_input_parameters[0], + // Emit the get odin context call + + get_context_args[0] = lbValue { + wrapper_proc->raw_input_parameters[0], contex_provider_self_ptr_type, }; - if (is_context_provider_ivar) { - // The context provider takes the ivar's type. - // Emit an obj_ivar_get call and use that pointer for 'self' instead. - lbValue real_self { - wrapper_proc->raw_input_parameters[0], - class_ptr_type - }; - 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); - } - - - auto method_call_args = array_make(temporary_allocator(), method_param_count + (isize)method_param_offset); - - if (!md.ac.objc_is_class_method) { - method_call_args[0] = lbValue { - wrapper_proc->raw_input_parameters[0], - class_ptr_type, - }; - } - - 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, - }; - } - 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. - lb_emit_call(wrapper_proc, method_proc_value, method_call_args); - } - 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, temporary_allocator()); - } - - if (!md.ac.objc_is_class_method) { - method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("@:")); - } else { - method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:")); - } - - for (i32 i = method_param_offset; i < method_param_count; i++) { - Type *param_type = method_type->Proc.params->Tuple.variables[i]->type; - String param_encoding = lb_get_objc_type_encoding(param_type, temporary_allocator()); - - method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding); - } - - // Emit method registration - lbAddr* sel_address = string_map_get(&m->objc_selectors, md.ac.objc_selector); - GB_ASSERT(sel_address); - lbValue selector_value = lb_addr_load(p, *sel_address); - - args.count = 4; - args[0] = class_value; // Class - args[1] = selector_value; // SEL - 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. - lb_emit_runtime_call(p, "class_addMethod", args); - - } // End methods - - // Add ivar if we have one - if (ivar_type != nullptr) { - // Register a single ivar for this class - Type *ivar_base = ivar_type->Named.base; - - const i64 size = type_size_of(ivar_base); - const i64 alignment = type_align_of(ivar_base); - // TODO(harold): Checker: Alignment must be compatible with ivar rules. Or we should increase the alignment if needed. - - // TODO(harold): Should we pass the actual type encoding? Might not be ideal for obfuscation. - String ivar_name = str_lit("__$ivar"); - String ivar_types = str_lit("{= }"); //lb_get_objc_type_encoding(ivar_type, temporary_allocator());// str_lit("{= }"); - args.count = 5; - args[0] = class_value; - args[1] = lb_const_value(m, t_cstring, exact_value_string(ivar_name)); - args[2] = lb_const_value(m, t_uint, exact_value_u64((u64)size)); - args[3] = lb_const_value(m, t_u8, exact_value_u64((u64)alignment)); - args[4] = lb_const_value(m, t_cstring, exact_value_string(ivar_types)); - lb_emit_runtime_call(p, "class_addIvar", args); - } - - // Complete the class registration - args.count = 1; - args[0] = class_value; - lb_emit_runtime_call(p, "objc_registerClassPair", args); - } + if (is_context_provider_ivar) { + // The context provider takes the ivar's type. + // Emit an obj_ivar_get call and use that pointer for 'self' instead. + lbValue real_self { + wrapper_proc->raw_input_parameters[0], + class_ptr_type + }; + 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); + } + + + auto method_call_args = array_make(temporary_allocator(), method_param_count + (isize)method_param_offset); + + if (!md.ac.objc_is_class_method) { + method_call_args[0] = lbValue { + wrapper_proc->raw_input_parameters[0], + class_ptr_type, + }; + } + + 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, + }; + } + 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); + + 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); + } + } + 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, temporary_allocator()); + } + + if (!md.ac.objc_is_class_method) { + method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("@:")); + } else { + method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:")); + } + + for (i32 i = method_param_offset; i < method_param_count; i++) { + Type *param_type = method_type->Proc.params->Tuple.variables[i]->type; + String param_encoding = lb_get_objc_type_encoding(param_type, temporary_allocator()); + + method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding); + } + + // Emit method registration + lbAddr* sel_address = string_map_get(&m->objc_selectors, md.ac.objc_selector); + GB_ASSERT(sel_address); + lbValue selector_value = lb_addr_load(p, *sel_address); + + args.count = 4; + args[0] = class_value; // Class + args[1] = selector_value; // SEL + 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. + lb_emit_runtime_call(p, "class_addMethod", args); + + } // End methods + + // Add ivar if we have one + if (ivar_type != nullptr) { + // Register a single ivar for this class + Type *ivar_base = ivar_type->Named.base; + + const i64 size = type_size_of(ivar_base); + const i64 alignment = type_align_of(ivar_base); + // TODO(harold): Checker: Alignment must be compatible with ivar rules. Or we should increase the alignment if needed. + + // TODO(harold): Should we pass the actual type encoding? Might not be ideal for obfuscation. + String ivar_name = str_lit("__$ivar"); + String ivar_types = str_lit("{= }"); //lb_get_objc_type_encoding(ivar_type, temporary_allocator());// str_lit("{= }"); + args.count = 5; + args[0] = class_value; + args[1] = lb_const_value(m, t_cstring, exact_value_string(ivar_name)); + args[2] = lb_const_value(m, t_uint, exact_value_u64((u64)size)); + args[3] = lb_const_value(m, t_u8, exact_value_u64((u64)alignment)); + args[4] = lb_const_value(m, t_cstring, exact_value_string(ivar_types)); + lb_emit_runtime_call(p, "class_addIvar", args); + } + + // Complete the class registration + args.count = 1; + args[0] = class_value; + lb_emit_runtime_call(p, "objc_registerClassPair", args); + } // Register ivar offsets for any `objc_ivar_get` expressions emitted. Type *ptr_u32 = alloc_type_pointer(t_u32); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 7694c65c3..99ee2b2ff 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -196,7 +196,7 @@ struct lbModule { StringMap objc_classes; StringMap objc_selectors; - StringMap objc_ivars; + StringMap objc_ivars; PtrMap map_cell_info_map; // address of runtime.Map_Info PtrMap map_info_map; // address of runtime.Map_Cell_Info @@ -220,7 +220,7 @@ struct lbObjCGlobal { gbString global_name; String name; Type * type; - Type * class_impl_type; // This is set when the class has the objc_implement attribute set to true. + Type * class_impl_type; // This is set when the class has the objc_implement attribute set to true. }; struct lbGenerator : LinkerData { @@ -242,7 +242,7 @@ struct lbGenerator : LinkerData { MPSCQueue entities_to_correct_linkage; MPSCQueue objc_selectors; MPSCQueue objc_classes; - MPSCQueue objc_ivars; + MPSCQueue objc_ivars; }; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 7f012e006..bb683465b 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -101,7 +101,7 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { string_map_init(&m->objc_classes); string_map_init(&m->objc_selectors); - string_map_init(&m->objc_ivars); + string_map_init(&m->objc_ivars); map_init(&m->map_info_map, 0); map_init(&m->map_cell_info_map, 0); @@ -174,7 +174,7 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { mpsc_init(&gen->entities_to_correct_linkage, heap_allocator()); mpsc_init(&gen->objc_selectors, heap_allocator()); mpsc_init(&gen->objc_classes, heap_allocator()); - mpsc_init(&gen->objc_ivars, heap_allocator()); + mpsc_init(&gen->objc_ivars, heap_allocator()); return true; } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index bf4ebf377..ba375283e 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3290,7 +3290,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_objc_find_class: return lb_handle_objc_find_class(p, expr); case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr); 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_ivar_get: return lb_handle_objc_ivar_get(p, expr); case BuiltinProc_constant_utf16_cstring: diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 33211395a..264364162 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2157,8 +2157,8 @@ gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_type) { - String name = self_type->Named.type_name->TypeName.objc_class_name; - GB_ASSERT(name != ""); + String name = self_type->Named.type_name->TypeName.objc_class_name; + GB_ASSERT(name != ""); lbAddr *found = string_map_get(&m->objc_ivars, name); if (found) { @@ -2170,7 +2170,7 @@ gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_ gbString global_name = gb_string_make(permanent_allocator(), "__$objc_ivar::"); global_name = gb_string_append_length(global_name, name.text, name.len); - // Create a global variable to store offset of the ivar in an instance of an object + // Create a global variable to store offset of the ivar in an instance of an object LLVMTypeRef t = lb_type(m, t_u32); lbValue g = {}; @@ -2209,10 +2209,10 @@ gb_internal lbValue lb_handle_objc_ivar_for_objc_object_pointer(lbProcedure *p, } gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) { - ast_node(ce, CallExpr, expr); + ast_node(ce, CallExpr, expr); - GB_ASSERT(ce->args[0]->tav.type->kind == Type_Pointer); - lbValue self = lb_build_expr(p, ce->args[0]); + GB_ASSERT(ce->args[0]->tav.type->kind == Type_Pointer); + lbValue self = lb_build_expr(p, ce->args[0]); return lb_handle_objc_ivar_for_objc_object_pointer(p, self); } @@ -2282,7 +2282,7 @@ gb_internal lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) { GB_ASSERT(e->kind == Entity_TypeName); String name = e->TypeName.objc_class_name; - Type *class_impl_type = e->TypeName.objc_is_implementation ? type : nullptr; + Type *class_impl_type = e->TypeName.objc_is_implementation ? type : nullptr; return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name, class_impl_type)); } -- cgit v1.2.3 From 1505edef011949e10b31335577ea344e5da2b623 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Wed, 30 Apr 2025 22:09:03 -0400 Subject: Change ivar offsets from u32 to (odin base type) int/native pointer size. --- src/llvm_backend.cpp | 12 ++++++------ src/llvm_backend_utility.cpp | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index c8626c195..6e6b9e39a 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1793,7 +1793,6 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { } // Register ivar offsets for any `objc_ivar_get` expressions emitted. - Type *ptr_u32 = alloc_type_pointer(t_u32); for (auto const& kv : ivar_map) { lbObjCGlobal const& g = kv.value; lbAddr ivar_addr = {}; @@ -1801,13 +1800,14 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { if (found) { ivar_addr = lb_addr(*found); + GB_ASSERT(ivar_addr.addr.type == t_int_ptr); } else { - // Defined in an external package, must define now - LLVMTypeRef t = lb_type(m, t_u32); + // Defined in an external package, define it now in the main package + LLVMTypeRef t = lb_type(m, t_int); lbValue global{}; global.value = LLVMAddGlobal(m->mod, t, g.global_name); - global.type = ptr_u32; + global.type = t_int_ptr; LLVMSetInitializer(global.value, LLVMConstInt(t, 0, true)); @@ -1825,9 +1825,9 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { args.count = 1; args[0] = ivar; lbValue ivar_offset = lb_emit_runtime_call(p, "ivar_getOffset", args); - lbValue ivar_offset_u32 = lb_emit_conv(p, ivar_offset, t_u32); + lbValue ivar_offset_int = lb_emit_conv(p, ivar_offset, t_int); - lb_addr_store(p, ivar_addr, ivar_offset_u32); + lb_addr_store(p, ivar_addr, ivar_offset_int); } lb_end_procedure_body(p); diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 264364162..521553147 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2171,11 +2171,11 @@ gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_ global_name = gb_string_append_length(global_name, name.text, name.len); // Create a global variable to store offset of the ivar in an instance of an object - LLVMTypeRef t = lb_type(m, t_u32); + LLVMTypeRef t = lb_type(m, t_int); lbValue g = {}; g.value = LLVMAddGlobal(m->mod, t, global_name); - g.type = alloc_type_pointer(t_u32); + g.type = t_int_ptr; if (default_module == m) { LLVMSetInitializer(g.value, LLVMConstInt(t, 0, true)); @@ -2184,7 +2184,7 @@ gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_ LLVMSetLinkage(g.value, LLVMExternalLinkage); } - mpsc_enqueue(&m->gen->objc_ivars, lbObjCGlobal{m, global_name, name, t_u32, self_type}); + mpsc_enqueue(&m->gen->objc_ivars, lbObjCGlobal{m, global_name, name, t_int, self_type}); lbAddr addr = lb_addr(g); string_map_set(&m->objc_ivars, name, addr); -- cgit v1.2.3 From 2561427dd396a69cd49eb02c0814c4e8e8b3a08f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 2 Aug 2025 11:00:15 +0100 Subject: Add `string16` and `cstring16` (UTF-16 based strings) --- base/runtime/core.odin | 13 +++- base/runtime/core_builtin.odin | 28 ++++++- base/runtime/internal.odin | 87 +++++++++++++++++++++ core/fmt/fmt.odin | 76 ++++++++++++++++++ core/io/io.odin | 24 ++++++ core/io/util.odin | 27 +++++++ core/unicode/utf16/utf16.odin | 20 +++++ src/build_settings.cpp | 8 +- src/cached.cpp | 2 +- src/check_builtin.cpp | 9 ++- src/check_decl.cpp | 6 ++ src/check_expr.cpp | 88 +++++++++++++++++++++ src/checker.cpp | 16 ++-- src/common.cpp | 2 +- src/exact_value.cpp | 52 ++++++++++++- src/llvm_backend_expr.cpp | 72 +++++++++++++++++ src/llvm_backend_general.cpp | 31 ++++++++ src/llvm_backend_proc.cpp | 9 +++ src/llvm_backend_type.cpp | 28 ++++++- src/llvm_backend_utility.cpp | 12 +++ src/main.cpp | 8 +- src/microsoft_craziness.h | 4 +- src/path.cpp | 8 +- src/string.cpp | 172 +++++++++++++++++++++++++++++++++++++---- src/types.cpp | 133 +++++++++++++++++++++++++++---- 25 files changed, 873 insertions(+), 62 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/base/runtime/core.odin b/base/runtime/core.odin index baecb4146..fe40427ff 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -73,7 +73,7 @@ Type_Info_Rune :: struct {} Type_Info_Float :: struct {endianness: Platform_Endianness} Type_Info_Complex :: struct {} Type_Info_Quaternion :: struct {} -Type_Info_String :: struct {is_cstring: bool} +Type_Info_String :: struct {is_cstring: bool, is_utf16: bool} Type_Info_Boolean :: struct {} Type_Info_Any :: struct {} Type_Info_Type_Id :: struct {} @@ -397,6 +397,11 @@ Raw_String :: struct { len: int, } +Raw_String16 :: struct { + data: [^]u16, + len: int, +} + Raw_Slice :: struct { data: rawptr, len: int, @@ -450,6 +455,12 @@ Raw_Cstring :: struct { } #assert(size_of(Raw_Cstring) == size_of(cstring)) +Raw_Cstring16 :: struct { + data: [^]u16, +} +#assert(size_of(Raw_Cstring16) == size_of(cstring16)) + + Raw_Soa_Pointer :: struct { data: rawptr, index: int, diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index e2ba14f3a..09118998c 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -86,11 +86,26 @@ copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int } return n } + +// `copy_from_string16` is a built-in procedure that copies elements from a source string `src` to a destination slice `dst`. +// The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum +// of len(src) and len(dst). +// +// Prefer the procedure group `copy`. +@builtin +copy_from_string16 :: proc "contextless" (dst: $T/[]$E/u16, src: $S/string16) -> int { + n := min(len(dst), len(src)) + if n > 0 { + intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(u16)) + } + return n +} + // `copy` is a built-in procedure that copies elements from a source slice/string `src` to a destination slice `dst`. // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum // of len(src) and len(dst). @builtin -copy :: proc{copy_slice, copy_from_string} +copy :: proc{copy_slice, copy_from_string, copy_from_string16} @@ -285,6 +300,15 @@ delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error } +@builtin +delete_string16 :: proc(str: string16, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + return mem_free_with_size(raw_data(str), len(str)*size_of(u16), allocator, loc) +} +@builtin +delete_cstring16 :: proc(str: cstring16, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + return mem_free((^u16)(str), allocator, loc) +} + // `delete` will try to free the underlying data of the passed built-in data structure (string, cstring, dynamic array, slice, or map), with the given `allocator` if the allocator supports this operation. // // Note: Prefer `delete` over the specific `delete_*` procedures where possible. @@ -297,6 +321,8 @@ delete :: proc{ delete_map, delete_soa_slice, delete_soa_dynamic_array, + delete_string16, + delete_cstring16, } diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 907b187f1..660af58ab 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -493,12 +493,40 @@ string_cmp :: proc "contextless" (a, b: string) -> int { return ret } + +string16_eq :: proc "contextless" (lhs, rhs: string16) -> bool { + x := transmute(Raw_String16)lhs + y := transmute(Raw_String16)rhs + if x.len != y.len { + return false + } + return #force_inline memory_equal(x.data, y.data, x.len*size_of(u16)) +} + +string16_cmp :: proc "contextless" (a, b: string16) -> int { + x := transmute(Raw_String16)a + y := transmute(Raw_String16)b + + ret := memory_compare(x.data, y.data, min(x.len, y.len)*size_of(u16)) + if ret == 0 && x.len != y.len { + return -1 if x.len < y.len else +1 + } + return ret +} + string_ne :: #force_inline proc "contextless" (a, b: string) -> bool { return !string_eq(a, b) } string_lt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) < 0 } string_gt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) > 0 } string_le :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) <= 0 } string_ge :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) >= 0 } +string16_ne :: #force_inline proc "contextless" (a, b: string16) -> bool { return !string16_eq(a, b) } +string16_lt :: #force_inline proc "contextless" (a, b: string16) -> bool { return string16_cmp(a, b) < 0 } +string16_gt :: #force_inline proc "contextless" (a, b: string16) -> bool { return string16_cmp(a, b) > 0 } +string16_le :: #force_inline proc "contextless" (a, b: string16) -> bool { return string16_cmp(a, b) <= 0 } +string16_ge :: #force_inline proc "contextless" (a, b: string16) -> bool { return string16_cmp(a, b) >= 0 } + + cstring_len :: proc "contextless" (s: cstring) -> int { p0 := uintptr((^byte)(s)) p := p0 @@ -508,6 +536,16 @@ cstring_len :: proc "contextless" (s: cstring) -> int { return int(p - p0) } +cstring16_len :: proc "contextless" (s: cstring16) -> int { + p := ([^]u16)(s) + n := 0 + for p != nil && p[0] != 0 { + p = p[1:] + n += 1 + } + return n +} + cstring_to_string :: proc "contextless" (s: cstring) -> string { if s == nil { return "" @@ -517,6 +555,15 @@ cstring_to_string :: proc "contextless" (s: cstring) -> string { return transmute(string)Raw_String{ptr, n} } +cstring16_to_string16 :: proc "contextless" (s: cstring16) -> string16 { + if s == nil { + return "" + } + ptr := (^u16)(s) + n := cstring16_len(s) + return transmute(string16)Raw_String16{ptr, n} +} + cstring_eq :: proc "contextless" (lhs, rhs: cstring) -> bool { x := ([^]byte)(lhs) @@ -559,6 +606,46 @@ cstring_gt :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_le :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) <= 0 } cstring_ge :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) >= 0 } +cstring16_eq :: proc "contextless" (lhs, rhs: cstring16) -> bool { + x := ([^]u16)(lhs) + y := ([^]u16)(rhs) + if x == y { + return true + } + if (x == nil) ~ (y == nil) { + return false + } + xn := cstring16_len(lhs) + yn := cstring16_len(rhs) + if xn != yn { + return false + } + return #force_inline memory_equal(x, y, xn*size_of(u16)) +} + +cstring16_cmp :: proc "contextless" (lhs, rhs: cstring16) -> int { + x := ([^]u16)(lhs) + y := ([^]u16)(rhs) + if x == y { + return 0 + } + if (x == nil) ~ (y == nil) { + return -1 if x == nil else +1 + } + xn := cstring16_len(lhs) + yn := cstring16_len(rhs) + ret := memory_compare(x, y, min(xn, yn)*size_of(u16)) + if ret == 0 && xn != yn { + return -1 if xn < yn else +1 + } + return ret +} + +cstring16_ne :: #force_inline proc "contextless" (a, b: cstring16) -> bool { return !cstring16_eq(a, b) } +cstring16_lt :: #force_inline proc "contextless" (a, b: cstring16) -> bool { return cstring16_cmp(a, b) < 0 } +cstring16_gt :: #force_inline proc "contextless" (a, b: cstring16) -> bool { return cstring16_cmp(a, b) > 0 } +cstring16_le :: #force_inline proc "contextless" (a, b: cstring16) -> bool { return cstring16_cmp(a, b) <= 0 } +cstring16_ge :: #force_inline proc "contextless" (a, b: cstring16) -> bool { return cstring16_cmp(a, b) >= 0 } complex32_eq :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) == real(b) && imag(a) == imag(b) } complex32_ne :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) != real(b) || imag(a) != imag(b) } diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 0f6470cca..7fe6287d4 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1551,6 +1551,79 @@ fmt_string :: proc(fi: ^Info, s: string, verb: rune) { fmt_cstring :: proc(fi: ^Info, s: cstring, verb: rune) { fmt_string(fi, string(s), verb) } + +// Formats a string UTF-16 with a specific format. +// +// Inputs: +// - fi: Pointer to the Info struct containing format settings. +// - s: The string to format. +// - verb: The format specifier character (e.g. 's', 'v', 'q', 'x', 'X'). +// +fmt_string16 :: proc(fi: ^Info, s: string16, verb: rune) { + s, verb := s, verb + if ol, ok := fi.optional_len.?; ok { + s = s[:clamp(ol, 0, len(s))] + } + if !fi.in_bad && fi.record_level > 0 && verb == 'v' { + verb = 'q' + } + + switch verb { + case 's', 'v': + if fi.width_set { + if fi.width > len(s) { + if fi.minus { + io.write_string16(fi.writer, s, &fi.n) + } + + for _ in 0.. 0 && space { + io.write_byte(fi.writer, ' ', &fi.n) + } + char_set := __DIGITS_UPPER + if verb == 'x' { + char_set = __DIGITS_LOWER + } + _fmt_int(fi, u64(s[i]), 16, false, bit_size=16, digits=char_set) + } + + case: + fmt_bad_verb(fi, verb) + } +} +// Formats a C-style UTF-16 string with a specific format. +// +// Inputs: +// - fi: Pointer to the Info struct containing format settings. +// - s: The C-style string to format. +// - verb: The format specifier character (Ref fmt_string). +// +fmt_cstring16 :: proc(fi: ^Info, s: cstring16, verb: rune) { + fmt_string16(fi, string16(s), verb) +} + // Formats a raw pointer with a specific format. // // Inputs: @@ -3210,6 +3283,9 @@ fmt_arg :: proc(fi: ^Info, arg: any, verb: rune) { case string: fmt_string(fi, a, verb) case cstring: fmt_cstring(fi, a, verb) + case string16: fmt_string16(fi, a, verb) + case cstring16: fmt_cstring16(fi, a, verb) + case typeid: reflect.write_typeid(fi.writer, a, &fi.n) case i16le: fmt_int(fi, u64(a), true, 16, verb) diff --git a/core/io/io.odin b/core/io/io.odin index c2b44cbdb..5431519bf 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -5,6 +5,7 @@ package io import "base:intrinsics" import "core:unicode/utf8" +import "core:unicode/utf16" // Seek whence values Seek_From :: enum { @@ -314,6 +315,29 @@ write_string :: proc(s: Writer, str: string, n_written: ^int = nil) -> (n: int, return write(s, transmute([]byte)str, n_written) } +// write_string16 writes the contents of the string16 s to w reencoded as utf-8 +write_string16 :: proc(s: Writer, str: string16, n_written: ^int = nil) -> (n: int, err: Error) { + for i := 0; i < len(str); i += 1 { + r := rune(utf16.REPLACEMENT_CHAR) + + switch c := str[i]; { + case c < utf16._surr1, utf16._surr3 <= c: + r = rune(c) + case utf16._surr1 <= c && c < utf16._surr2 && i+1 < len(str) && + utf16._surr2 <= str[i+1] && str[i+1] < utf16._surr3: + r = utf16.decode_surrogate_pair(rune(c), rune(str[i+1])) + i += 1 + } + + w, err := write_rune(s, r, n_written) + n += w + if err != nil { + return + } + } + return +} + // write_rune writes a UTF-8 encoded rune to w. write_rune :: proc(s: Writer, r: rune, n_written: ^int = nil) -> (size: int, err: Error) { defer if err == nil && n_written != nil { diff --git a/core/io/util.odin b/core/io/util.odin index fa98e007b..72983523a 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -264,6 +264,33 @@ write_quoted_string :: proc(w: Writer, str: string, quote: byte = '"', n_written return } +write_quoted_string16 :: proc(w: Writer, str: string16, quote: byte = '"', n_written: ^int = nil, for_json := false) -> (n: int, err: Error) { + defer if n_written != nil { + n_written^ += n + } + write_byte(w, quote, &n) or_return + for width, s := 0, str; len(s) > 0; s = s[width:] { + r := rune(s[0]) + width = 1 + if r >= utf8.RUNE_SELF { + r, width = utf16.decode_rune_in_string(s) + } + if width == 1 && r == utf8.RUNE_ERROR { + write_byte(w, '\\', &n) or_return + write_byte(w, 'x', &n) or_return + write_byte(w, DIGITS_LOWER[s[0]>>4], &n) or_return + write_byte(w, DIGITS_LOWER[s[0]&0xf], &n) or_return + continue + } + + n_wrapper(write_escaped_rune(w, r, quote, false, nil, for_json), &n) or_return + + } + write_byte(w, quote, &n) or_return + return +} + + // writer append a quoted rune into the byte buffer, return the written size write_quoted_rune :: proc(w: Writer, r: rune) -> (n: int) { _write_byte :: #force_inline proc(w: Writer, c: byte) -> int { diff --git a/core/unicode/utf16/utf16.odin b/core/unicode/utf16/utf16.odin index e2bcf7f68..9a8cfe438 100644 --- a/core/unicode/utf16/utf16.odin +++ b/core/unicode/utf16/utf16.odin @@ -106,6 +106,26 @@ decode :: proc(d: []rune, s: []u16) -> (n: int) { return } +decode_rune_in_string :: proc(s: string16) -> (r: rune, width: int) { + r = rune(REPLACEMENT_CHAR) + n := len(s) + if n < 1 { + return + } + width = 1 + + + switch c := s[0]; { + case c < _surr1, _surr3 <= c: + r = rune(c) + case _surr1 <= c && c < _surr2 && 1 < len(s) && + _surr2 <= s[1] && s[1] < _surr3: + r = decode_surrogate_pair(rune(c), rune(s[1])) + width += 1 + } + return +} + rune_count :: proc(s: []u16) -> (n: int) { for i := 0; i < len(s); i += 1 { c := s[i] diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 46a4f9ae5..40bbe41e5 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1089,7 +1089,7 @@ gb_internal String internal_odin_root_dir(void) { text = gb_alloc_array(permanent_allocator(), wchar_t, len+1); GetModuleFileNameW(nullptr, text, cast(int)len); - path = string16_to_string(heap_allocator(), make_string16(text, len)); + path = string16_to_string(heap_allocator(), make_string16(cast(u16 *)text, len)); for (i = path.len-1; i >= 0; i--) { u8 c = path[i]; @@ -1387,14 +1387,14 @@ gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { mutex_lock(&fullpath_mutex); - len = GetFullPathNameW(&string16[0], 0, nullptr, nullptr); + len = GetFullPathNameW(cast(wchar_t *)&string16[0], 0, nullptr, nullptr); if (len != 0) { wchar_t *text = gb_alloc_array(permanent_allocator(), wchar_t, len+1); - GetFullPathNameW(&string16[0], len, text, nullptr); + GetFullPathNameW(cast(wchar_t *)&string16[0], len, text, nullptr); mutex_unlock(&fullpath_mutex); text[len] = 0; - result = string16_to_string(a, make_string16(text, len)); + result = string16_to_string(a, make_string16(cast(u16 *)text, len)); result = string_trim_whitespace(result); // Replace Windows style separators diff --git a/src/cached.cpp b/src/cached.cpp index efdadce7b..61b5d01b4 100644 --- a/src/cached.cpp +++ b/src/cached.cpp @@ -231,7 +231,7 @@ Array cache_gather_envs() { wchar_t *curr_string = strings; while (curr_string && *curr_string) { - String16 wstr = make_string16_c(curr_string); + String16 wstr = make_string16_c(cast(u16 *)curr_string); curr_string += wstr.len+1; String str = string16_to_string(temporary_allocator(), wstr); if (string_starts_with(str, str_lit("CURR_DATE_TIME="))) { diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 974224ed2..d36cf4520 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2327,6 +2327,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (is_type_string(op_type) && id == BuiltinProc_len) { if (operand->mode == Addressing_Constant) { mode = Addressing_Constant; + + GB_ASSERT_MSG(!is_type_string16(op_type), "TODO(bill): constant utf-16 string len"); + String str = operand->value.value_string; value = exact_value_i64(str.len); type = t_untyped_integer; @@ -2334,6 +2337,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As mode = Addressing_Value; if (is_type_cstring(op_type)) { add_package_dependency(c, "runtime", "cstring_len"); + } else if (is_type_cstring16(op_type)) { + add_package_dependency(c, "runtime", "cstring16_len"); } } } else if (is_type_array(op_type)) { @@ -4683,7 +4688,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; case Type_Basic: if (t->Basic.kind == Basic_string) { - operand->type = alloc_type_multi_pointer(t_u8); + operand->type = t_u8_multi_ptr; + } else if (t->Basic.kind == Basic_string16) { + operand->type = t_u16_multi_ptr; } break; case Type_Pointer: diff --git a/src/check_decl.cpp b/src/check_decl.cpp index dd4c09e85..af46ee40e 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -815,6 +815,12 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { if (sig_compare(is_type_cstring, is_type_u8_multi_ptr, x, y)) { return true; } + if (sig_compare(is_type_cstring16, is_type_u16_ptr, x, y)) { + return true; + } + if (sig_compare(is_type_cstring16, is_type_u16_multi_ptr, x, y)) { + return true; + } if (sig_compare(is_type_uintptr, is_type_rawptr, x, y)) { return true; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 6723a7580..57073e22f 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2862,6 +2862,14 @@ gb_internal void add_comparison_procedures_for_fields(CheckerContext *c, Type *t add_package_dependency(c, "runtime", "string_eq"); add_package_dependency(c, "runtime", "string_ne"); break; + case Basic_cstring16: + add_package_dependency(c, "runtime", "cstring16_eq"); + add_package_dependency(c, "runtime", "cstring16_ne"); + break; + case Basic_string16: + add_package_dependency(c, "runtime", "string16_eq"); + add_package_dependency(c, "runtime", "string16_ne"); + break; } break; case Type_Struct: @@ -3035,6 +3043,24 @@ gb_internal void check_comparison(CheckerContext *c, Ast *node, Operand *x, Oper case Token_LtEq: add_package_dependency(c, "runtime", "cstring_le"); break; case Token_GtEq: add_package_dependency(c, "runtime", "cstring_gt"); break; } + } else if (is_type_cstring16(x->type) && is_type_cstring16(y->type)) { + switch (op) { + case Token_CmpEq: add_package_dependency(c, "runtime", "cstring16_eq"); break; + case Token_NotEq: add_package_dependency(c, "runtime", "cstring16_ne"); break; + case Token_Lt: add_package_dependency(c, "runtime", "cstring16_lt"); break; + case Token_Gt: add_package_dependency(c, "runtime", "cstring16_gt"); break; + case Token_LtEq: add_package_dependency(c, "runtime", "cstring16_le"); break; + case Token_GtEq: add_package_dependency(c, "runtime", "cstring16_gt"); break; + } + } else if (is_type_string16(x->type) || is_type_string16(y->type)) { + switch (op) { + case Token_CmpEq: add_package_dependency(c, "runtime", "string16_eq"); break; + case Token_NotEq: add_package_dependency(c, "runtime", "string16_ne"); break; + case Token_Lt: add_package_dependency(c, "runtime", "string16_lt"); break; + case Token_Gt: add_package_dependency(c, "runtime", "string16_gt"); break; + case Token_LtEq: add_package_dependency(c, "runtime", "string16_le"); break; + case Token_GtEq: add_package_dependency(c, "runtime", "string16_gt"); break; + } } else if (is_type_string(x->type) || is_type_string(y->type)) { switch (op) { case Token_CmpEq: add_package_dependency(c, "runtime", "string_eq"); break; @@ -3340,6 +3366,11 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type return true; } + // []u16 <-> string16 (not cstring16) + if (is_type_u16_slice(src) && (is_type_string16(dst) && !is_type_cstring16(dst))) { + return true; + } + // cstring -> string if (are_types_identical(src, t_cstring) && are_types_identical(dst, t_string)) { if (operand->mode != Addressing_Constant) { @@ -3347,6 +3378,14 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type } return true; } + // cstring16 -> string16 + if (are_types_identical(src, t_cstring16) && are_types_identical(dst, t_string16)) { + if (operand->mode != Addressing_Constant) { + add_package_dependency(c, "runtime", "cstring16_to_string16"); + } + return true; + } + // cstring -> ^u8 if (are_types_identical(src, t_cstring) && is_type_u8_ptr(dst)) { return !is_constant; @@ -3372,6 +3411,34 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type if (is_type_rawptr(src) && are_types_identical(dst, t_cstring)) { return !is_constant; } + + // cstring -> ^u16 + if (are_types_identical(src, t_cstring16) && is_type_u16_ptr(dst)) { + return !is_constant; + } + // cstring -> [^]u16 + if (are_types_identical(src, t_cstring16) && is_type_u16_multi_ptr(dst)) { + return !is_constant; + } + // cstring -> rawptr + if (are_types_identical(src, t_cstring16) && is_type_rawptr(dst)) { + return !is_constant; + } + + + // ^u16 -> cstring16 + if (is_type_u16_ptr(src) && are_types_identical(dst, t_cstring16)) { + return !is_constant; + } + // [^]u16 -> cstring + if (is_type_u16_multi_ptr(src) && are_types_identical(dst, t_cstring16)) { + return !is_constant; + } + // rawptr -> cstring16 + if (is_type_rawptr(src) && are_types_identical(dst, t_cstring16)) { + return !is_constant; + } + // proc <-> proc if (is_type_proc(src) && is_type_proc(dst)) { if (is_type_polymorphic(dst)) { @@ -4558,6 +4625,8 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar // target_type = t_untyped_nil; } else if (is_type_cstring(target_type)) { // target_type = t_untyped_nil; + } else if (is_type_cstring16(target_type)) { + // target_type = t_untyped_nil; } else if (!type_has_nil(target_type)) { operand->mode = Addressing_Invalid; convert_untyped_error(c, operand, target_type); @@ -8226,6 +8295,7 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 case Type_Basic: if (t->Basic.kind == Basic_string) { if (o->mode == Addressing_Constant) { + GB_ASSERT(o->value.kind == ExactValue_String); *max_count = o->value.value_string.len; } if (o->mode != Addressing_Constant) { @@ -8233,6 +8303,16 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 } o->type = t_u8; return true; + } else if (t->Basic.kind == Basic_string16) { + if (o->mode == Addressing_Constant) { + GB_ASSERT(o->value.kind == ExactValue_String16); + *max_count = o->value.value_string16.len; + } + if (o->mode != Addressing_Constant) { + o->mode = Addressing_Value; + } + o->type = t_u16; + return true; } else if (t->Basic.kind == Basic_UntypedString) { if (o->mode == Addressing_Constant) { *max_count = o->value.value_string.len; @@ -10879,9 +10959,17 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, if (t->Basic.kind == Basic_string || t->Basic.kind == Basic_UntypedString) { valid = true; if (o->mode == Addressing_Constant) { + GB_ASSERT(o->value.kind == ExactValue_String); max_count = o->value.value_string.len; } o->type = type_deref(o->type); + } else if (t->Basic.kind == Basic_string16) { + valid = true; + if (o->mode == Addressing_Constant) { + GB_ASSERT(o->value.kind == ExactValue_String16); + max_count = o->value.value_string16.len; + } + o->type = type_deref(o->type); } break; diff --git a/src/checker.cpp b/src/checker.cpp index a1d8f98d7..20da5b19b 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1363,13 +1363,15 @@ gb_internal void init_universal(void) { } - t_u8_ptr = alloc_type_pointer(t_u8); - t_u8_multi_ptr = alloc_type_multi_pointer(t_u8); - t_int_ptr = alloc_type_pointer(t_int); - t_i64_ptr = alloc_type_pointer(t_i64); - t_f64_ptr = alloc_type_pointer(t_f64); - t_u8_slice = alloc_type_slice(t_u8); - t_string_slice = alloc_type_slice(t_string); + t_u8_ptr = alloc_type_pointer(t_u8); + t_u8_multi_ptr = alloc_type_multi_pointer(t_u8); + t_u16_ptr = alloc_type_pointer(t_u16); + t_u16_multi_ptr = alloc_type_multi_pointer(t_u16); + t_int_ptr = alloc_type_pointer(t_int); + t_i64_ptr = alloc_type_pointer(t_i64); + t_f64_ptr = alloc_type_pointer(t_f64); + t_u8_slice = alloc_type_slice(t_u8); + t_string_slice = alloc_type_slice(t_string); // intrinsics types for objective-c stuff { diff --git a/src/common.cpp b/src/common.cpp index ad1e5a851..b3761fc36 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -669,7 +669,7 @@ gb_internal gb_inline f64 gb_sqrt(f64 x) { gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { u32 i, j; - u32 len = cast(u32)string16_len(cmd_line); + u32 len = cast(u32)string16_len(cast(u16 *)cmd_line); i = ((len+2)/2)*gb_size_of(void *) + gb_size_of(void *); wchar_t **argv = cast(wchar_t **)GlobalAlloc(GMEM_FIXED, i + (len+2)*gb_size_of(wchar_t)); diff --git a/src/exact_value.cpp b/src/exact_value.cpp index 37751c8f1..f2aed84c2 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -29,6 +29,7 @@ enum ExactValueKind { ExactValue_Compound = 8, ExactValue_Procedure = 9, ExactValue_Typeid = 10, + ExactValue_String16 = 11, ExactValue_Count, }; @@ -46,6 +47,7 @@ struct ExactValue { Ast * value_compound; Ast * value_procedure; Type * value_typeid; + String16 value_string16; }; }; @@ -66,6 +68,9 @@ gb_internal uintptr hash_exact_value(ExactValue v) { case ExactValue_String: res = gb_fnv32a(v.value_string.text, v.value_string.len); break; + case ExactValue_String16: + res = gb_fnv32a(v.value_string.text, v.value_string.len*gb_size_of(u16)); + break; case ExactValue_Integer: { u32 key = gb_fnv32a(v.value_integer.dp, gb_size_of(*v.value_integer.dp) * v.value_integer.used); @@ -118,6 +123,11 @@ gb_internal ExactValue exact_value_string(String string) { result.value_string = string; return result; } +gb_internal ExactValue exact_value_string16(String16 string) { + ExactValue result = {ExactValue_String16}; + result.value_string16 = string; + return result; +} gb_internal ExactValue exact_value_i64(i64 i) { ExactValue result = {ExactValue_Integer}; @@ -656,6 +666,7 @@ gb_internal i32 exact_value_order(ExactValue const &v) { return 0; case ExactValue_Bool: case ExactValue_String: + case ExactValue_String16: return 1; case ExactValue_Integer: return 2; @@ -689,6 +700,7 @@ gb_internal void match_exact_values(ExactValue *x, ExactValue *y) { case ExactValue_Bool: case ExactValue_String: + case ExactValue_String16: case ExactValue_Quaternion: case ExactValue_Pointer: case ExactValue_Compound: @@ -891,7 +903,18 @@ gb_internal ExactValue exact_binary_operator_value(TokenKind op, ExactValue x, E gb_memmove(data, sx.text, sx.len); gb_memmove(data+sx.len, sy.text, sy.len); return exact_value_string(make_string(data, len)); - break; + } + case ExactValue_String16: { + if (op != Token_Add) goto error; + + // NOTE(bill): How do you minimize this over allocation? + String sx = x.value_string; + String sy = y.value_string; + isize len = sx.len+sy.len; + u16 *data = gb_alloc_array(permanent_allocator(), u16, len); + gb_memmove(data, sx.text, sx.len*gb_size_of(u16)); + gb_memmove(data+sx.len, sy.text, sy.len*gb_size_of(u16)); + return exact_value_string16(make_string16(data, len)); } } @@ -994,6 +1017,19 @@ gb_internal bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y) } break; } + case ExactValue_String16: { + String16 a = x.value_string16; + String16 b = y.value_string16; + switch (op) { + case Token_CmpEq: return a == b; + case Token_NotEq: return a != b; + case Token_Lt: return a < b; + case Token_LtEq: return a <= b; + case Token_Gt: return a > b; + case Token_GtEq: return a >= b; + } + break; + } case ExactValue_Pointer: { switch (op) { @@ -1050,6 +1086,20 @@ gb_internal gbString write_exact_value_to_string(gbString str, ExactValue const gb_free(heap_allocator(), s.text); return str; } + case ExactValue_String16: { + String s = quote_to_ascii(heap_allocator(), v.value_string16); + string_limit = gb_max(string_limit, 36); + if (s.len <= string_limit) { + str = gb_string_append_length(str, s.text, s.len); + } else { + isize n = string_limit/5; + str = gb_string_append_length(str, s.text, n); + str = gb_string_append_fmt(str, "\"..%lld chars..\"", s.len-(2*n)); + str = gb_string_append_length(str, s.text+s.len-n, n); + } + gb_free(heap_allocator(), s.text); + return str; + } case ExactValue_Integer: { String s = big_int_to_string(heap_allocator(), &v.value_integer); str = gb_string_append_length(str, s.text, s.len); diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 74aea82f1..fbf0dea11 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1656,6 +1656,8 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { res.type = t; res.value = llvm_cstring(m, str); return res; + } else if (src->kind == Type_Basic && src->Basic.kind == Basic_string16 && dst->Basic.kind == Basic_cstring16) { + GB_PANIC("TODO(bill): UTF-16 string"); } // if (is_type_float(dst)) { // return value; @@ -1795,6 +1797,38 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } + + if (is_type_cstring16(src) && is_type_u16_ptr(dst)) { + return lb_emit_transmute(p, value, dst); + } + if (is_type_u16_ptr(src) && is_type_cstring16(dst)) { + return lb_emit_transmute(p, value, dst); + } + if (is_type_cstring16(src) && is_type_u16_multi_ptr(dst)) { + return lb_emit_transmute(p, value, dst); + } + if (is_type_u8_multi_ptr(src) && is_type_cstring16(dst)) { + return lb_emit_transmute(p, value, dst); + } + if (is_type_cstring16(src) && is_type_rawptr(dst)) { + return lb_emit_transmute(p, value, dst); + } + if (is_type_rawptr(src) && is_type_cstring16(dst)) { + return lb_emit_transmute(p, value, dst); + } + + if (are_types_identical(src, t_cstring16) && are_types_identical(dst, t_string16)) { + TEMPORARY_ALLOCATOR_GUARD(); + + lbValue c = lb_emit_conv(p, value, t_cstring16); + auto args = array_make(temporary_allocator(), 1); + args[0] = c; + lbValue s = lb_emit_runtime_call(p, "cstring16_to_string16", args); + return lb_emit_conv(p, s, dst); + } + + + // integer -> boolean if (is_type_integer(src) && is_type_boolean(dst)) { lbValue res = {}; @@ -2296,6 +2330,14 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { return res; } + // []u16 <-> string16 + if (is_type_u16_slice(src) && is_type_string16(dst)) { + return lb_emit_transmute(p, value, t); + } + if (is_type_string16(src) && is_type_u16_slice(dst)) { + return lb_emit_transmute(p, value, t); + } + // []byte/[]u8 <-> string if (is_type_u8_slice(src) && is_type_string(dst)) { return lb_emit_transmute(p, value, t); @@ -2304,6 +2346,7 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { return lb_emit_transmute(p, value, t); } + if (is_type_array_like(dst)) { Type *elem = base_array_type(dst); isize index_count = cast(isize)get_array_type_count(dst); @@ -2483,6 +2526,12 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { if (is_type_untyped(src)) { + if (is_type_string(src) && is_type_string16(dst)) { + GB_PANIC("TODO(bill): UTF-16 string"); + lbAddr result = lb_add_local_generated(p, t, false); + lb_addr_store(p, result, value); + return lb_addr_load(p, result); + } if (is_type_string(src) && is_type_string(dst)) { lbAddr result = lb_add_local_generated(p, t, false); lb_addr_store(p, result, value); @@ -3056,6 +3105,13 @@ gb_internal lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind, res.value = LLVMBuildIsNotNull(p->builder, x.value, ""); } return res; + case Basic_cstring16: + if (op_kind == Token_CmpEq) { + res.value = LLVMBuildIsNull(p->builder, x.value, ""); + } else if (op_kind == Token_NotEq) { + res.value = LLVMBuildIsNotNull(p->builder, x.value, ""); + } + return res; case Basic_any: { // TODO(bill): is this correct behaviour for nil comparison for any? @@ -4432,6 +4488,22 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { } case Type_Basic: { + if (is_type_string16(type)) { + GB_ASSERT_MSG(are_types_identical(type, t_string16), "got %s", type_to_string(type)); + lbValue len = lb_string_len(p, base); + if (high.value == nullptr) high = len; + + if (!no_indices) { + lb_emit_slice_bounds_check(p, se->open, low, high, len, se->low != nullptr); + } + + lbValue elem = lb_emit_ptr_offset(p, lb_string_elem(p, base), low); + lbValue new_len = lb_emit_arith(p, Token_Sub, high, low, t_int); + + lbAddr str = lb_add_local_generated(p, t_string16, false); + lb_fill_string(p, str, elem, new_len); + return str; + } GB_ASSERT_MSG(are_types_identical(type, t_string), "got %s", type_to_string(type)); lbValue len = lb_string_len(p, base); if (high.value == nullptr) high = len; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 3ce0c725f..d9771a75b 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1812,6 +1812,37 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { return type; } case Basic_cstring: return LLVMPointerType(LLVMInt8TypeInContext(ctx), 0); + + + case Basic_string16: + { + char const *name = "..string16"; + LLVMTypeRef type = LLVMGetTypeByName(m->mod, name); + if (type != nullptr) { + return type; + } + type = LLVMStructCreateNamed(ctx, name); + + if (build_context.metrics.ptr_size < build_context.metrics.int_size) { + GB_ASSERT(build_context.metrics.ptr_size == 4); + GB_ASSERT(build_context.metrics.int_size == 8); + LLVMTypeRef fields[3] = { + LLVMPointerType(lb_type(m, t_u16), 0), + lb_type(m, t_i32), + lb_type(m, t_int), + }; + LLVMStructSetBody(type, fields, 3, false); + } else { + LLVMTypeRef fields[2] = { + LLVMPointerType(lb_type(m, t_u16), 0), + lb_type(m, t_int), + }; + LLVMStructSetBody(type, fields, 2, false); + } + return type; + } + case Basic_cstring16: return LLVMPointerType(LLVMInt16TypeInContext(ctx), 0); + case Basic_any: { char const *name = "..any"; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index e63c92f6f..8f306b771 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2289,6 +2289,10 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu } if (is_type_cstring(t)) { return lb_cstring_len(p, v); + } else if (is_type_cstring16(t)) { + return lb_cstring16_len(p, v); + } else if (is_type_string16(t)) { + return lb_string_len(p, v); } else if (is_type_string(t)) { return lb_string_len(p, v); } else if (is_type_array(t)) { @@ -2728,6 +2732,11 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu res = lb_emit_conv(p, res, tv.type); } else if (t->Basic.kind == Basic_cstring) { res = lb_emit_conv(p, x, tv.type); + } else if (t->Basic.kind == Basic_string16) { + res = lb_string_elem(p, x); + res = lb_emit_conv(p, res, tv.type); + } else if (t->Basic.kind == Basic_cstring16) { + res = lb_emit_conv(p, x, tv.type); } break; case Type_Pointer: diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 43c5f0b40..a91d77fe5 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -531,7 +531,33 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ case Basic_cstring: { tag_type = t_type_info_string; - LLVMValueRef vals[1] = { + LLVMValueRef vals[2] = { + lb_const_bool(m, t_bool, true).value, + lb_const_bool(m, t_bool, false).value, + }; + + variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); + } + break; + + case Basic_string16: + { + tag_type = t_type_info_string; + LLVMValueRef vals[2] = { + lb_const_bool(m, t_bool, false).value, + lb_const_bool(m, t_bool, true).value, + }; + + variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); + } + break; + + + case Basic_cstring16: + { + tag_type = t_type_info_string; + LLVMValueRef vals[2] = { + lb_const_bool(m, t_bool, true).value, lb_const_bool(m, t_bool, true).value, }; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 521553147..d4117b7ff 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -1626,11 +1626,17 @@ gb_internal void lb_fill_string(lbProcedure *p, lbAddr const &string, lbValue ba gb_internal lbValue lb_string_elem(lbProcedure *p, lbValue string) { Type *t = base_type(string.type); + if (t->kind == Type_Basic && t->Basic.kind == Basic_string16) { + return lb_emit_struct_ev(p, string, 0); + } GB_ASSERT(t->kind == Type_Basic && t->Basic.kind == Basic_string); return lb_emit_struct_ev(p, string, 0); } gb_internal lbValue lb_string_len(lbProcedure *p, lbValue string) { Type *t = base_type(string.type); + if (t->kind == Type_Basic && t->Basic.kind == Basic_string16) { + return lb_emit_struct_ev(p, string, 1); + } GB_ASSERT_MSG(t->kind == Type_Basic && t->Basic.kind == Basic_string, "%s", type_to_string(t)); return lb_emit_struct_ev(p, string, 1); } @@ -1641,6 +1647,12 @@ gb_internal lbValue lb_cstring_len(lbProcedure *p, lbValue value) { args[0] = lb_emit_conv(p, value, t_cstring); return lb_emit_runtime_call(p, "cstring_len", args); } +gb_internal lbValue lb_cstring16_len(lbProcedure *p, lbValue value) { + GB_ASSERT(is_type_cstring16(value.type)); + auto args = array_make(permanent_allocator(), 1); + args[0] = lb_emit_conv(p, value, t_cstring16); + return lb_emit_runtime_call(p, "cstring16_len", args); +} gb_internal lbValue lb_array_elem(lbProcedure *p, lbValue array_ptr) { diff --git a/src/main.cpp b/src/main.cpp index 112d1208a..5a43e3c02 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -142,9 +142,9 @@ gb_internal i32 system_exec_command_line_app_internal(bool exit_on_err, char con } wcmd = string_to_string16(permanent_allocator(), make_string(cast(u8 *)cmd_line, cmd_len-1)); - if (CreateProcessW(nullptr, wcmd.text, - nullptr, nullptr, true, 0, nullptr, nullptr, - &start_info, &pi)) { + if (CreateProcessW(nullptr, cast(wchar_t *)wcmd.text, + nullptr, nullptr, true, 0, nullptr, nullptr, + &start_info, &pi)) { WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess, cast(DWORD *)&exit_code); @@ -232,7 +232,7 @@ gb_internal Array setup_args(int argc, char const **argv) { wchar_t **wargv = command_line_to_wargv(GetCommandLineW(), &wargc); auto args = array_make(a, 0, wargc); for (isize i = 0; i < wargc; i++) { - wchar_t *warg = wargv[i]; + u16 *warg = cast(u16 *)wargv[i]; isize wlen = string16_len(warg); String16 wstr = make_string16(warg, wlen); String arg = string16_to_string(a, wstr); diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index b0fd22a23..933607a2a 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -59,7 +59,7 @@ struct Find_Result { }; gb_internal String mc_wstring_to_string(wchar_t const *str) { - return string16_to_string(mc_allocator, make_string16_c(str)); + return string16_to_string(mc_allocator, make_string16_c(cast(u16 *)str)); } gb_internal String16 mc_string_to_wstring(String str) { @@ -103,7 +103,7 @@ gb_internal HANDLE mc_find_first(String wildcard, MC_Find_Data *find_data) { String16 wildcard_wide = mc_string_to_wstring(wildcard); defer (mc_free(wildcard_wide)); - HANDLE handle = FindFirstFileW(wildcard_wide.text, &_find_data); + HANDLE handle = FindFirstFileW(cast(wchar_t *)wildcard_wide.text, &_find_data); if (handle == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE; find_data->file_attributes = _find_data.dwFileAttributes; diff --git a/src/path.cpp b/src/path.cpp index d5e982088..2b97a04df 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -130,7 +130,7 @@ gb_internal String directory_from_path(String const &s) { String16 wstr = string_to_string16(a, path); defer (gb_free(a, wstr.text)); - i32 attribs = GetFileAttributesW(wstr.text); + i32 attribs = GetFileAttributesW(cast(wchar_t *)wstr.text); if (attribs < 0) return false; return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0; @@ -360,7 +360,7 @@ gb_internal ReadDirectoryError read_directory(String path, Array *fi) defer (gb_free(a, wstr.text)); WIN32_FIND_DATAW file_data = {}; - HANDLE find_file = FindFirstFileW(wstr.text, &file_data); + HANDLE find_file = FindFirstFileW(cast(wchar_t *)wstr.text, &file_data); if (find_file == INVALID_HANDLE_VALUE) { return ReadDirectory_Unknown; } @@ -372,7 +372,7 @@ gb_internal ReadDirectoryError read_directory(String path, Array *fi) wchar_t *filename_w = file_data.cFileName; u64 size = cast(u64)file_data.nFileSizeLow; size |= (cast(u64)file_data.nFileSizeHigh) << 32; - String name = string16_to_string(a, make_string16_c(filename_w)); + String name = string16_to_string(a, make_string16_c(cast(u16 *)filename_w)); if (name == "." || name == "..") { gb_free(a, name.text); continue; @@ -494,7 +494,7 @@ gb_internal bool write_directory(String path) { #else gb_internal bool write_directory(String path) { String16 wstr = string_to_string16(heap_allocator(), path); - LPCWSTR wdirectory_name = wstr.text; + LPCWSTR wdirectory_name = cast(wchar_t *)wstr.text; HANDLE directory = CreateFileW(wdirectory_name, GENERIC_WRITE, diff --git a/src/string.cpp b/src/string.cpp index ae8d066b1..8405938f4 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -26,15 +26,14 @@ struct String_Iterator { // NOTE(bill): String16 is only used for Windows due to its file directories struct String16 { - wchar_t *text; - isize len; - wchar_t const &operator[](isize i) const { + u16 * text; + isize len; + u16 const &operator[](isize i) const { GB_ASSERT_MSG(0 <= i && i < len, "[%td]", i); return text[i]; } }; - gb_internal gb_inline String make_string(u8 const *text, isize len) { String s; s.text = cast(u8 *)text; @@ -45,19 +44,19 @@ gb_internal gb_inline String make_string(u8 const *text, isize len) { return s; } - -gb_internal gb_inline String16 make_string16(wchar_t const *text, isize len) { +gb_internal gb_inline String16 make_string16(u16 const *text, isize len) { String16 s; - s.text = cast(wchar_t *)text; + s.text = cast(u16 *)text; s.len = len; return s; } -gb_internal isize string16_len(wchar_t const *s) { + +gb_internal isize string16_len(u16 const *s) { if (s == nullptr) { return 0; } - wchar_t const *p = s; + u16 const *p = s; while (*p) { p++; } @@ -69,7 +68,7 @@ gb_internal gb_inline String make_string_c(char const *text) { return make_string(cast(u8 *)cast(void *)text, gb_strlen(text)); } -gb_internal gb_inline String16 make_string16_c(wchar_t const *text) { +gb_internal gb_inline String16 make_string16_c(u16 const *text) { return make_string16(text, string16_len(text)); } @@ -145,6 +144,27 @@ gb_internal int string_compare(String const &a, String const &b) { return res; } + +gb_internal int string16_compare(String16 const &a, String16 const &b) { + if (a.text == b.text) { + return cast(int)(a.len - b.len); + } + if (a.text == nullptr) { + return -1; + } + if (b.text == nullptr) { + return +1; + } + + uintptr n = gb_min(a.len, b.len); + int res = memcmp(a.text, b.text, n*gb_size_of(u16)); + if (res == 0) { + res = cast(int)(a.len - b.len); + } + return res; +} + + gb_internal isize string_index_byte(String const &s, u8 x) { for (isize i = 0; i < s.len; i++) { if (s.text[i] == x) { @@ -182,6 +202,26 @@ template gb_internal bool operator >= (String const &a, char const (&b template <> bool operator == (String const &a, char const (&b)[1]) { return a.len == 0; } template <> bool operator != (String const &a, char const (&b)[1]) { return a.len != 0; } + +gb_internal gb_inline bool str_eq(String16 const &a, String16 const &b) { + if (a.len != b.len) return false; + if (a.len == 0) return true; + return memcmp(a.text, b.text, a.len) == 0; +} +gb_internal gb_inline bool str_ne(String16 const &a, String16 const &b) { return !str_eq(a, b); } +gb_internal gb_inline bool str_lt(String16 const &a, String16 const &b) { return string16_compare(a, b) < 0; } +gb_internal gb_inline bool str_gt(String16 const &a, String16 const &b) { return string16_compare(a, b) > 0; } +gb_internal gb_inline bool str_le(String16 const &a, String16 const &b) { return string16_compare(a, b) <= 0; } +gb_internal gb_inline bool str_ge(String16 const &a, String16 const &b) { return string16_compare(a, b) >= 0; } + +gb_internal gb_inline bool operator == (String16 const &a, String16 const &b) { return str_eq(a, b); } +gb_internal gb_inline bool operator != (String16 const &a, String16 const &b) { return str_ne(a, b); } +gb_internal gb_inline bool operator < (String16 const &a, String16 const &b) { return str_lt(a, b); } +gb_internal gb_inline bool operator > (String16 const &a, String16 const &b) { return str_gt(a, b); } +gb_internal gb_inline bool operator <= (String16 const &a, String16 const &b) { return str_le(a, b); } +gb_internal gb_inline bool operator >= (String16 const &a, String16 const &b) { return str_ge(a, b); } + + gb_internal gb_inline bool string_starts_with(String const &s, String const &prefix) { if (prefix.len > s.len) { return false; @@ -614,7 +654,7 @@ gb_internal String normalize_path(gbAllocator a, String const &path, String cons // TODO(bill): Make this non-windows specific gb_internal String16 string_to_string16(gbAllocator a, String s) { int len, len1; - wchar_t *text; + u16 *text; if (s.len < 1) { return make_string16(nullptr, 0); @@ -625,9 +665,9 @@ gb_internal String16 string_to_string16(gbAllocator a, String s) { return make_string16(nullptr, 0); } - text = gb_alloc_array(a, wchar_t, len+1); + text = gb_alloc_array(a, u16, len+1); - len1 = convert_multibyte_to_widechar(cast(char *)s.text, cast(int)s.len, text, cast(int)len); + len1 = convert_multibyte_to_widechar(cast(char *)s.text, cast(int)s.len, cast(wchar_t *)text, cast(int)len); if (len1 == 0) { gb_free(a, text); return make_string16(nullptr, 0); @@ -646,7 +686,7 @@ gb_internal String string16_to_string(gbAllocator a, String16 s) { return make_string(nullptr, 0); } - len = convert_widechar_to_multibyte(s.text, cast(int)s.len, nullptr, 0); + len = convert_widechar_to_multibyte(cast(wchar_t *)s.text, cast(int)s.len, nullptr, 0); if (len == 0) { return make_string(nullptr, 0); } @@ -654,7 +694,7 @@ gb_internal String string16_to_string(gbAllocator a, String16 s) { text = gb_alloc_array(a, u8, len+1); - len1 = convert_widechar_to_multibyte(s.text, cast(int)s.len, cast(char *)text, cast(int)len); + len1 = convert_widechar_to_multibyte(cast(wchar_t *)s.text, cast(int)s.len, cast(char *)text, cast(int)len); if (len1 == 0) { gb_free(a, text); return make_string(nullptr, 0); @@ -674,9 +714,9 @@ gb_internal String temporary_directory(gbAllocator allocator) { return String{0}; } DWORD len = gb_max(MAX_PATH, n); - wchar_t *b = gb_alloc_array(heap_allocator(), wchar_t, len+1); + u16 *b = gb_alloc_array(heap_allocator(), u16, len+1); defer (gb_free(heap_allocator(), b)); - n = GetTempPathW(len, b); + n = GetTempPathW(len, cast(wchar_t *)b); if (n == 3 && b[1] == ':' && b[2] == '\\') { } else if (n > 0 && b[n-1] == '\\') { @@ -791,6 +831,104 @@ gb_internal String quote_to_ascii(gbAllocator a, String str, u8 quote='"') { return res; } +gb_internal Rune decode_surrogate_pair(u16 r1, u16 r2) { + static Rune const _surr1 = 0xd800; + static Rune const _surr2 = 0xdc00; + static Rune const _surr3 = 0xe000; + static Rune const _surr_self = 0x10000; + + if (_surr1 <= r1 && r1 < _surr2 && _surr2 <= r2 && r2 < _surr3) { + return (((r1-_surr1)<<10) | (r2 - _surr2)) + _surr_self; + } + return GB_RUNE_INVALID; +} + +gb_internal String quote_to_ascii(gbAllocator a, String16 str, u8 quote='"') { + static Rune const _surr1 = 0xd800; + static Rune const _surr2 = 0xdc00; + static Rune const _surr3 = 0xe000; + static Rune const _surr_self = 0x10000; + + u16 *s = cast(u16 *)str.text; + isize n = str.len; + auto buf = array_make(a, 0, n*2); + array_add(&buf, quote); + for (isize width = 0; n > 0; s += width, n -= width) { + Rune r = cast(Rune)s[0]; + width = 1; + if (r < _surr1 || _surr3 <= r) { + r = cast(Rune)r; + } else if (_surr1 <= r && r < _surr2) { + if (n>1) { + r = decode_surrogate_pair(s[0], s[1]); + if (r != GB_RUNE_INVALID) { + width = 2; + } + } else { + r = GB_RUNE_INVALID; + } + } + if (width == 1 && r == GB_RUNE_INVALID) { + array_add(&buf, cast(u8)'\\'); + array_add(&buf, cast(u8)'x'); + array_add(&buf, cast(u8)lower_hex[s[0]>>4]); + array_add(&buf, cast(u8)lower_hex[s[0]&0xf]); + continue; + } + + if (r == quote || r == '\\') { + array_add(&buf, cast(u8)'\\'); + array_add(&buf, u8(r)); + continue; + } + if (r < 0x80 && is_printable(r)) { + array_add(&buf, u8(r)); + continue; + } + switch (r) { + case '\a': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + default: + if (r < ' ') { + u8 b = cast(u8)r; + array_add(&buf, cast(u8)'\\'); + array_add(&buf, cast(u8)'x'); + array_add(&buf, cast(u8)lower_hex[b>>4]); + array_add(&buf, cast(u8)lower_hex[b&0xf]); + } + if (r > GB_RUNE_MAX) { + r = 0XFFFD; + } + if (r < 0x10000) { + array_add(&buf, cast(u8)'\\'); + array_add(&buf, cast(u8)'u'); + for (isize i = 12; i >= 0; i -= 4) { + array_add(&buf, cast(u8)lower_hex[(r>>i)&0xf]); + } + } else { + array_add(&buf, cast(u8)'\\'); + array_add(&buf, cast(u8)'U'); + for (isize i = 28; i >= 0; i -= 4) { + array_add(&buf, cast(u8)lower_hex[(r>>i)&0xf]); + } + } + } + } + + + + array_add(&buf, quote); + String res = {}; + res.text = buf.data; + res.len = buf.count; + return res; +} + diff --git a/src/types.cpp b/src/types.cpp index 2e696810d..ad576c8af 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -41,8 +41,13 @@ enum BasicKind { Basic_uint, Basic_uintptr, Basic_rawptr, - Basic_string, // ^u8 + int - Basic_cstring, // ^u8 + + Basic_string, // [^]u8 + int + Basic_cstring, // [^]u8 + + Basic_string16, // [^]u16 + int + Basic_cstring16, // [^]u16 + int + Basic_any, // rawptr + ^Type_Info Basic_typeid, @@ -500,8 +505,14 @@ gb_global Type basic_types[] = { {Type_Basic, {Basic_uintptr, BasicFlag_Integer | BasicFlag_Unsigned, -1, STR_LIT("uintptr")}}, {Type_Basic, {Basic_rawptr, BasicFlag_Pointer, -1, STR_LIT("rawptr")}}, + {Type_Basic, {Basic_string, BasicFlag_String, -1, STR_LIT("string")}}, {Type_Basic, {Basic_cstring, BasicFlag_String, -1, STR_LIT("cstring")}}, + + {Type_Basic, {Basic_string16, BasicFlag_String, -1, STR_LIT("string16")}}, + {Type_Basic, {Basic_cstring16, BasicFlag_String, -1, STR_LIT("cstring16")}}, + + {Type_Basic, {Basic_any, 0, 16, STR_LIT("any")}}, {Type_Basic, {Basic_typeid, 0, 8, STR_LIT("typeid")}}, @@ -591,8 +602,12 @@ gb_global Type *t_uint = &basic_types[Basic_uint]; gb_global Type *t_uintptr = &basic_types[Basic_uintptr]; gb_global Type *t_rawptr = &basic_types[Basic_rawptr]; + gb_global Type *t_string = &basic_types[Basic_string]; gb_global Type *t_cstring = &basic_types[Basic_cstring]; +gb_global Type *t_string16 = &basic_types[Basic_string16]; +gb_global Type *t_cstring16 = &basic_types[Basic_cstring16]; + gb_global Type *t_any = &basic_types[Basic_any]; gb_global Type *t_typeid = &basic_types[Basic_typeid]; @@ -630,6 +645,8 @@ gb_global Type *t_untyped_uninit = &basic_types[Basic_UntypedUninit]; gb_global Type *t_u8_ptr = nullptr; gb_global Type *t_u8_multi_ptr = nullptr; +gb_global Type *t_u16_ptr = nullptr; +gb_global Type *t_u16_multi_ptr = nullptr; gb_global Type *t_int_ptr = nullptr; gb_global Type *t_i64_ptr = nullptr; gb_global Type *t_f64_ptr = nullptr; @@ -1292,6 +1309,14 @@ gb_internal bool is_type_string(Type *t) { } return false; } +gb_internal bool is_type_string16(Type *t) { + t = base_type(t); + if (t == nullptr) { return false; } + if (t->kind == Type_Basic) { + return t->Basic.kind == Basic_string16; + } + return false; +} gb_internal bool is_type_cstring(Type *t) { t = base_type(t); if (t == nullptr) { return false; } @@ -1300,6 +1325,14 @@ gb_internal bool is_type_cstring(Type *t) { } return false; } +gb_internal bool is_type_cstring16(Type *t) { + t = base_type(t); + if (t == nullptr) { return false; } + if (t->kind == Type_Basic) { + return t->Basic.kind == Basic_cstring16; + } + return false; +} gb_internal bool is_type_typed(Type *t) { t = base_type(t); if (t == nullptr) { return false; } @@ -1429,6 +1462,12 @@ gb_internal bool is_type_u8(Type *t) { } return false; } +gb_internal bool is_type_u16(Type *t) { + if (t->kind == Type_Basic) { + return t->Basic.kind == Basic_u16; + } + return false; +} gb_internal bool is_type_array(Type *t) { t = base_type(t); if (t == nullptr) { return false; } @@ -1690,6 +1729,39 @@ gb_internal bool is_type_rune_array(Type *t) { return false; } +gb_internal bool is_type_u16_slice(Type *t) { + t = base_type(t); + if (t == nullptr) { return false; } + if (t->kind == Type_Slice) { + return is_type_u16(t->Slice.elem); + } + return false; +} +gb_internal bool is_type_u16_array(Type *t) { + t = base_type(t); + if (t == nullptr) { return false; } + if (t->kind == Type_Array) { + return is_type_u16(t->Array.elem); + } + return false; +} +gb_internal bool is_type_u16_ptr(Type *t) { + t = base_type(t); + if (t == nullptr) { return false; } + if (t->kind == Type_Pointer) { + return is_type_u16(t->Slice.elem); + } + return false; +} +gb_internal bool is_type_u16_multi_ptr(Type *t) { + t = base_type(t); + if (t == nullptr) { return false; } + if (t->kind == Type_MultiPointer) { + return is_type_u16(t->Slice.elem); + } + return false; +} + gb_internal bool is_type_array_like(Type *t) { return is_type_array(t) || is_type_enumerated_array(t); @@ -2109,7 +2181,7 @@ gb_internal bool is_type_indexable(Type *t) { Type *bt = base_type(t); switch (bt->kind) { case Type_Basic: - return bt->Basic.kind == Basic_string; + return bt->Basic.kind == Basic_string || bt->Basic.kind == Basic_string16; case Type_Array: case Type_Slice: case Type_DynamicArray: @@ -2129,7 +2201,7 @@ gb_internal bool is_type_sliceable(Type *t) { Type *bt = base_type(t); switch (bt->kind) { case Type_Basic: - return bt->Basic.kind == Basic_string; + return bt->Basic.kind == Basic_string || bt->Basic.kind == Basic_string16; case Type_Array: case Type_Slice: case Type_DynamicArray: @@ -2376,6 +2448,7 @@ gb_internal bool type_has_nil(Type *t) { case Basic_any: return true; case Basic_cstring: + case Basic_cstring16: return true; case Basic_typeid: return true; @@ -2443,8 +2516,9 @@ gb_internal bool is_type_comparable(Type *t) { case Basic_rune: return true; case Basic_string: - return true; case Basic_cstring: + case Basic_string16: + case Basic_cstring16: return true; case Basic_typeid: return true; @@ -3774,10 +3848,12 @@ gb_internal i64 type_size_of(Type *t) { if (t->kind == Type_Basic) { GB_ASSERT_MSG(is_type_typed(t), "%s", type_to_string(t)); switch (t->Basic.kind) { - case Basic_string: size = 2*build_context.int_size; break; - case Basic_cstring: size = build_context.ptr_size; break; - case Basic_any: size = 16; break; - case Basic_typeid: size = 8; break; + case Basic_string: size = 2*build_context.int_size; break; + case Basic_cstring: size = build_context.ptr_size; break; + case Basic_string16: size = 2*build_context.int_size; break; + case Basic_cstring16: size = build_context.ptr_size; break; + case Basic_any: size = 16; break; + case Basic_typeid: size = 8; break; case Basic_int: case Basic_uint: size = build_context.int_size; @@ -3837,10 +3913,12 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { case Type_Basic: { GB_ASSERT(is_type_typed(t)); switch (t->Basic.kind) { - case Basic_string: return build_context.int_size; - case Basic_cstring: return build_context.ptr_size; - case Basic_any: return 8; - case Basic_typeid: return 8; + case Basic_string: return build_context.int_size; + case Basic_cstring: return build_context.ptr_size; + case Basic_string16: return build_context.int_size; + case Basic_cstring16: return build_context.ptr_size; + case Basic_any: return 8; + case Basic_typeid: return 8; case Basic_int: case Basic_uint: return build_context.int_size; @@ -4088,10 +4166,12 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) { return size; } switch (kind) { - case Basic_string: return 2*build_context.int_size; - case Basic_cstring: return build_context.ptr_size; - case Basic_any: return 16; - case Basic_typeid: return 8; + case Basic_string: return 2*build_context.int_size; + case Basic_cstring: return build_context.ptr_size; + case Basic_string16: return 2*build_context.int_size; + case Basic_cstring16: return build_context.ptr_size; + case Basic_any: return 16; + case Basic_typeid: return 8; case Basic_int: case Basic_uint: return build_context.int_size; @@ -4320,6 +4400,15 @@ gb_internal i64 type_offset_of(Type *t, i64 index, Type **field_type_) { if (field_type_) *field_type_ = t_int; return build_context.int_size; // len } + } else if (t->Basic.kind == Basic_string16) { + switch (index) { + case 0: + if (field_type_) *field_type_ = t_u16_ptr; + return 0; // data + case 1: + if (field_type_) *field_type_ = t_int; + return build_context.int_size; // len + } } else if (t->Basic.kind == Basic_any) { switch (index) { case 0: @@ -4396,6 +4485,11 @@ gb_internal i64 type_offset_of_from_selection(Type *type, Selection sel) { case 0: t = t_rawptr; break; case 1: t = t_int; break; } + } else if (t->Basic.kind == Basic_string16) { + switch (index) { + case 0: t = t_rawptr; break; + case 1: t = t_int; break; + } } else if (t->Basic.kind == Basic_any) { switch (index) { case 0: t = t_rawptr; break; @@ -4637,6 +4731,11 @@ gb_internal Type *type_internal_index(Type *t, isize index) { GB_ASSERT(index == 0 || index == 1); return index == 0 ? t_u8_ptr : t_int; } + case Basic_string16: + { + GB_ASSERT(index == 0 || index == 1); + return index == 0 ? t_u16_ptr : t_int; + } case Basic_any: { GB_ASSERT(index == 0 || index == 1); -- cgit v1.2.3 From ae02d3d02d2eb5132fa7c6573ed7db20d7e18f3e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 2 Aug 2025 11:55:16 +0100 Subject: Begin supporting `string16` across the core library --- base/intrinsics/intrinsics.odin | 1 + base/runtime/print.odin | 6 +++ core/encoding/cbor/tags.odin | 2 +- core/encoding/cbor/unmarshal.odin | 2 + core/encoding/json/marshal.odin | 8 ++-- core/encoding/json/unmarshal.odin | 4 +- core/flags/internal_rtti.odin | 2 + core/fmt/fmt.odin | 16 ++++---- core/io/io.odin | 4 +- core/reflect/types.odin | 8 ++-- src/check_builtin.cpp | 2 + src/check_expr.cpp | 42 +++++++++++++++++---- src/checker_builtin_procs.hpp | 2 + src/llvm_backend.cpp | 6 +++ src/llvm_backend_const.cpp | 77 +++++++++++++++++++++++++++++++++++++-- src/llvm_backend_debug.cpp | 14 +++++++ src/llvm_backend_expr.cpp | 3 +- src/llvm_backend_general.cpp | 37 +++++++++++++++++++ src/llvm_backend_utility.cpp | 19 +++++++++- src/string.cpp | 7 ++++ 20 files changed, 230 insertions(+), 32 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index be75739fe..d45d24f48 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -141,6 +141,7 @@ type_is_quaternion :: proc($T: typeid) -> bool --- type_is_string :: proc($T: typeid) -> bool --- type_is_typeid :: proc($T: typeid) -> bool --- type_is_any :: proc($T: typeid) -> bool --- +type_is_string16 :: proc($T: typeid) -> bool --- type_is_endian_platform :: proc($T: typeid) -> bool --- type_is_endian_little :: proc($T: typeid) -> bool --- diff --git a/base/runtime/print.odin b/base/runtime/print.odin index 145f002d1..85ed49445 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -293,7 +293,13 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { print_string("quaternion") print_u64(u64(8*ti.size)) case Type_Info_String: + if info.is_cstring { + print_byte('c') + } print_string("string") + if info.is_utf16 { + print_string("16") + } case Type_Info_Boolean: switch ti.id { case bool: print_string("bool") diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index 17420af46..e0e69cbf5 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -298,7 +298,7 @@ tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, #partial switch t in ti.variant { case reflect.Type_Info_String: - + assert(!t.is_utf16) if t.is_cstring { length := base64.decoded_len(bytes) builder := strings.builder_make(0, length+1) diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 365ac5d6f..2840429f5 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -335,6 +335,8 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.a _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: + assert(!t.is_utf16) + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return if t.is_cstring { diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index ebb9a639c..cdb00a354 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -353,10 +353,10 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: #partial switch info in ti.variant { case runtime.Type_Info_String: switch x in v { - case string: - return x == "" - case cstring: - return x == nil || x == "" + case string: return x == "" + case cstring: return x == nil || x == "" + case string16: return x == "" + case cstring16: return x == nil || x == "" } case runtime.Type_Info_Any: return v.(any) == nil diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index b9ed1476f..51e7e3b81 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -570,7 +570,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm key_ptr: rawptr #partial switch tk in t.key.variant { - case runtime.Type_Info_String: + case runtime.Type_Info_String: + assert(!tk.is_utf16) + key_ptr = rawptr(&key) key_cstr: cstring if reflect.is_cstring(t.key) { diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin index 1c559ca55..58224cc87 100644 --- a/core/flags/internal_rtti.odin +++ b/core/flags/internal_rtti.odin @@ -127,6 +127,8 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: } case runtime.Type_Info_String: + assert(!specific_type_info.is_utf16) + if specific_type_info.is_cstring { cstr_ptr := (^cstring)(ptr) if cstr_ptr != nil { diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 7fe6287d4..9c245de94 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2346,14 +2346,14 @@ fmt_array :: proc(fi: ^Info, data: rawptr, n: int, elem_size: int, elem: ^reflec } switch reflect.type_info_base(elem).id { - case byte: fmt_string(fi, string(([^]byte)(data)[:n]), verb); return - case u16: print_utf16(fi, ([^]u16)(data)[:n]); return - case u16le: print_utf16(fi, ([^]u16le)(data)[:n]); return - case u16be: print_utf16(fi, ([^]u16be)(data)[:n]); return - case u32: print_utf32(fi, ([^]u32)(data)[:n]); return - case u32le: print_utf32(fi, ([^]u32le)(data)[:n]); return - case u32be: print_utf32(fi, ([^]u32be)(data)[:n]); return - case rune: print_utf32(fi, ([^]rune)(data)[:n]); return + case byte: fmt_string(fi, string (([^]byte)(data)[:n]), verb); return + case u16: fmt_string16(fi, string16(([^]u16) (data)[:n]), verb); return + case u16le: print_utf16(fi, ([^]u16le)(data)[:n]); return + case u16be: print_utf16(fi, ([^]u16be)(data)[:n]); return + case u32: print_utf32(fi, ([^]u32)(data)[:n]); return + case u32le: print_utf32(fi, ([^]u32le)(data)[:n]); return + case u32be: print_utf32(fi, ([^]u32be)(data)[:n]); return + case rune: print_utf32(fi, ([^]rune)(data)[:n]); return } } if verb == 'p' { diff --git a/core/io/io.odin b/core/io/io.odin index 5431519bf..c4eb6a073 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -319,7 +319,6 @@ write_string :: proc(s: Writer, str: string, n_written: ^int = nil) -> (n: int, write_string16 :: proc(s: Writer, str: string16, n_written: ^int = nil) -> (n: int, err: Error) { for i := 0; i < len(str); i += 1 { r := rune(utf16.REPLACEMENT_CHAR) - switch c := str[i]; { case c < utf16._surr1, utf16._surr3 <= c: r = rune(c) @@ -329,7 +328,8 @@ write_string16 :: proc(s: Writer, str: string16, n_written: ^int = nil) -> (n: i i += 1 } - w, err := write_rune(s, r, n_written) + w: int + w, err = write_rune(s, r, n_written) n += w if err != nil { return diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 511c5c9bd..2351408cc 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -511,9 +511,11 @@ write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_writt io.write_i64(w, i64(8*ti.size), 10, &n) or_return case Type_Info_String: if info.is_cstring { - io.write_string(w, "cstring", &n) or_return - } else { - io.write_string(w, "string", &n) or_return + io.write_byte(w, 'c', &n) or_return + } + io.write_string(w, "string", &n) or_return + if info.is_utf16 { + io.write_string(w, "16", &n) or_return } case Type_Info_Boolean: switch ti.id { diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index d36cf4520..4abace637 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -19,6 +19,7 @@ gb_global BuiltinTypeIsProc *builtin_type_is_procs[BuiltinProc__type_simple_bool is_type_complex, is_type_quaternion, is_type_string, + is_type_string16, is_type_typeid, is_type_any, is_type_endian_platform, @@ -6139,6 +6140,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_type_is_complex: case BuiltinProc_type_is_quaternion: case BuiltinProc_type_is_string: + case BuiltinProc_type_is_string16: case BuiltinProc_type_is_typeid: case BuiltinProc_type_is_any: case BuiltinProc_type_is_endian_platform: diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 57073e22f..8d2e4d637 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2106,6 +2106,9 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i } else if (is_type_boolean(type)) { return in_value.kind == ExactValue_Bool; } else if (is_type_string(type)) { + if (in_value.kind == ExactValue_String16) { + return is_type_string16(type) || is_type_cstring16(type); + } return in_value.kind == ExactValue_String; } else if (is_type_integer(type) || is_type_rune(type)) { if (in_value.kind == ExactValue_Bool) { @@ -2320,6 +2323,9 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i if (in_value.kind == ExactValue_String) { return false; } + if (in_value.kind == ExactValue_String16) { + return false; + } if (out_value) *out_value = in_value; } else if (is_type_bit_set(type)) { if (in_value.kind == ExactValue_Integer) { @@ -4654,6 +4660,13 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar break; } } + } else if (operand->value.kind == ExactValue_String16) { + String16 s = operand->value.value_string16; + if (is_type_u16_array(t)) { + if (s.len == t->Array.count) { + break; + } + } } operand->mode = Addressing_Invalid; convert_untyped_error(c, operand, target_type); @@ -4983,6 +4996,12 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v if (success_) *success_ = true; if (finish_) *finish_ = true; return exact_value_u64(val); + } else if (value.kind == ExactValue_String16) { + GB_ASSERT(0 <= index && index < value.value_string.len); + u16 val = value.value_string16[index]; + if (success_) *success_ = true; + if (finish_) *finish_ = true; + return exact_value_u64(val); } if (value.kind != ExactValue_Compound) { if (success_) *success_ = true; @@ -11124,15 +11143,21 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, o->expr = node; return kind; } - - String s = {}; - if (o->value.kind == ExactValue_String) { - s = o->value.value_string; - } - o->mode = Addressing_Constant; o->type = t; - o->value = exact_value_string(substring(s, cast(isize)indices[0], cast(isize)indices[1])); + + if (o->value.kind == ExactValue_String16) { + String16 s = o->value.value_string16; + + o->value = exact_value_string16(substring(s, cast(isize)indices[0], cast(isize)indices[1])); + } else { + String s = {}; + if (o->value.kind == ExactValue_String) { + s = o->value.value_string; + } + + o->value = exact_value_string(substring(s, cast(isize)indices[0], cast(isize)indices[1])); + } } return kind; } @@ -11221,6 +11246,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast Type *t = t_invalid; switch (node->tav.value.kind) { case ExactValue_String: t = t_untyped_string; break; + case ExactValue_String16: t = t_string16; break; // TODO(bill): determine this correctly case ExactValue_Float: t = t_untyped_float; break; case ExactValue_Complex: t = t_untyped_complex; break; case ExactValue_Quaternion: t = t_untyped_quaternion; break; @@ -11657,6 +11683,8 @@ gb_internal bool is_exact_value_zero(ExactValue const &v) { return !v.value_bool; case ExactValue_String: return v.value_string.len == 0; + case ExactValue_String16: + return v.value_string16.len == 0; case ExactValue_Integer: return big_int_is_zero(&v.value_integer); case ExactValue_Float: diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 8e135ab10..bff887d9e 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -250,6 +250,7 @@ BuiltinProc__type_simple_boolean_begin, BuiltinProc_type_is_complex, BuiltinProc_type_is_quaternion, BuiltinProc_type_is_string, + BuiltinProc_type_is_string16, BuiltinProc_type_is_typeid, BuiltinProc_type_is_any, @@ -607,6 +608,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_is_complex"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_quaternion"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_string"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_is_string16"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_typeid"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_any"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 13a1d8cf3..f37415cc1 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1264,7 +1264,13 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) { case Basic_string: return build_context.metrics.int_size == 4 ? str_lit("{string=*i}") : str_lit("{string=*q}"); + case Basic_string16: + return build_context.metrics.int_size == 4 ? str_lit("{string16=*i}") : str_lit("{string16=*q}"); + case Basic_cstring: return str_lit("*"); + case Basic_cstring16: return str_lit("*"); + + case Basic_any: return str_lit("{any=^v^v}"); // rawptr + ^Type_Info case Basic_typeid: diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index c3112934e..8c05ed4a2 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -122,6 +122,25 @@ gb_internal lbValue lb_const_ptr_cast(lbModule *m, lbValue value, Type *t) { gb_internal LLVMValueRef llvm_const_string_internal(lbModule *m, Type *t, LLVMValueRef data, LLVMValueRef len) { + GB_ASSERT(!is_type_string16(t)); + if (build_context.metrics.ptr_size < build_context.metrics.int_size) { + LLVMValueRef values[3] = { + data, + LLVMConstNull(lb_type(m, t_i32)), + len, + }; + return llvm_const_named_struct_internal(lb_type(m, t), values, 3); + } else { + LLVMValueRef values[2] = { + data, + len, + }; + return llvm_const_named_struct_internal(lb_type(m, t), values, 2); + } +} + +gb_internal LLVMValueRef llvm_const_string16_internal(lbModule *m, Type *t, LLVMValueRef data, LLVMValueRef len) { + GB_ASSERT(is_type_string16(t)); if (build_context.metrics.ptr_size < build_context.metrics.int_size) { LLVMValueRef values[3] = { data, @@ -238,6 +257,10 @@ gb_internal lbValue lb_const_string(lbModule *m, String const &value) { return lb_const_value(m, t_string, exact_value_string(value)); } +gb_internal lbValue lb_const_string(lbModule *m, String16 const &value) { + return lb_const_value(m, t_string16, exact_value_string16(value)); +} + gb_internal lbValue lb_const_bool(lbModule *m, Type *type, bool value) { lbValue res = {}; @@ -569,7 +592,11 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb GB_ASSERT(is_type_slice(type)); res.value = lb_find_or_add_entity_string_byte_slice_with_type(m, value.value_string, original_type).value; return res; - } else { + } else if (value.kind == ExactValue_String16) { + GB_ASSERT(is_type_slice(type)); + GB_PANIC("TODO(bill): UTF-16 String"); + return res; + }else { ast_node(cl, CompoundLit, value.value_compound); isize count = cl->elems.count; @@ -751,15 +778,23 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb { bool custom_link_section = cc.link_section.len > 0; - LLVMValueRef ptr = lb_find_or_add_entity_string_ptr(m, value.value_string, custom_link_section); + LLVMValueRef ptr = nullptr; lbValue res = {}; res.type = default_type(original_type); + if (is_type_string16(res.type) || is_type_cstring16(res.type)) { + TEMPORARY_ALLOCATOR_GUARD(); + String16 s16 = string_to_string16(temporary_allocator(), value.value_string); + ptr = lb_find_or_add_entity_string16_ptr(m, s16, custom_link_section); + } else { + ptr = lb_find_or_add_entity_string_ptr(m, value.value_string, custom_link_section); + } + if (custom_link_section) { LLVMSetSection(ptr, alloc_cstring(permanent_allocator(), cc.link_section)); } - if (is_type_cstring(res.type)) { + if (is_type_cstring(res.type) || is_type_cstring16(res.type)) { res.value = ptr; } else { if (value.value_string.len == 0) { @@ -768,12 +803,46 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb LLVMValueRef str_len = LLVMConstInt(lb_type(m, t_int), value.value_string.len, true); GB_ASSERT(is_type_string(original_type)); - res.value = llvm_const_string_internal(m, original_type, ptr, str_len); + if (is_type_string16(res.type)) { + res.value = llvm_const_string16_internal(m, original_type, ptr, str_len); + } else { + res.value = llvm_const_string_internal(m, original_type, ptr, str_len); + } + } + + return res; + } + + case ExactValue_String16: + { + GB_ASSERT(is_type_string16(res.type) || is_type_cstring16(res.type)); + + bool custom_link_section = cc.link_section.len > 0; + + LLVMValueRef ptr = lb_find_or_add_entity_string16_ptr(m, value.value_string16, custom_link_section); + lbValue res = {}; + res.type = default_type(original_type); + + if (custom_link_section) { + LLVMSetSection(ptr, alloc_cstring(permanent_allocator(), cc.link_section)); + } + + if (is_type_cstring16(res.type)) { + res.value = ptr; + } else { + if (value.value_string16.len == 0) { + ptr = LLVMConstNull(lb_type(m, t_u8_ptr)); + } + LLVMValueRef str_len = LLVMConstInt(lb_type(m, t_int), value.value_string16.len, true); + GB_ASSERT(is_type_string(original_type)); + + res.value = llvm_const_string16_internal(m, original_type, ptr, str_len); } return res; } + case ExactValue_Integer: if (is_type_pointer(type) || is_type_multi_pointer(type) || is_type_proc(type)) { LLVMTypeRef t = lb_type(m, original_type); diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 024c5564e..182920fc7 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -802,6 +802,20 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { LLVMMetadataRef char_type = lb_debug_type_basic_type(m, str_lit("char"), 8, LLVMDWARFTypeEncoding_Unsigned); return LLVMDIBuilderCreatePointerType(m->debug_builder, char_type, ptr_bits, ptr_bits, 0, "cstring", 7); } + + case Basic_string16: + { + LLVMMetadataRef elements[2] = {}; + elements[0] = lb_debug_struct_field(m, str_lit("data"), t_u16_ptr, 0); + elements[1] = lb_debug_struct_field(m, str_lit("len"), t_int, int_bits); + return lb_debug_basic_struct(m, str_lit("string16"), 2*int_bits, int_bits, elements, gb_count_of(elements)); + } + case Basic_cstring16: + { + LLVMMetadataRef char_type = lb_debug_type_basic_type(m, str_lit("wchar_t"), 16, LLVMDWARFTypeEncoding_Unsigned); + return LLVMDIBuilderCreatePointerType(m->debug_builder, char_type, ptr_bits, ptr_bits, 0, "cstring16", 7); + } + case Basic_any: { LLVMMetadataRef elements[2] = {}; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index fbf0dea11..3463b6083 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4354,12 +4354,13 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { } - case Type_Basic: { // Basic_string + case Type_Basic: { // Basic_string/Basic_string16 lbValue str; lbValue elem; lbValue len; lbValue index; + str = lb_build_expr(p, ie->expr); if (deref) { str = lb_emit_load(p, str); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index d9771a75b..9ef1c23c0 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -2715,6 +2715,43 @@ gb_internal LLVMValueRef lb_find_or_add_entity_string_ptr(lbModule *m, String co } } +gb_internal LLVMValueRef lb_find_or_add_entity_string16_ptr(lbModule *m, String16 const &str, bool custom_link_section) { + // TODO(bill): caching for UTF-16 strings + + LLVMValueRef indices[2] = {llvm_zero(m), llvm_zero(m)}; + + LLVMValueRef data = nullptr; + { + LLVMTypeRef llvm_u16 = LLVMInt16TypeInContext(m->ctx); + + TEMPORARY_ALLOCATOR_GUARD(); + + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, str.len+1); + + for (isize i = 0; i < str.len; i++) { + values[i] = LLVMConstInt(llvm_u16, str.text[i], false); + } + values[str.len] = LLVMConstInt(llvm_u16, 0, false); + + data = LLVMConstArray(llvm_u16, values, cast(unsigned)(str.len+1)); + } + + + u32 id = m->global_array_index.fetch_add(1); + gbString name = gb_string_make(temporary_allocator(), "csbs$"); + name = gb_string_appendc(name, m->module_name); + name = gb_string_append_fmt(name, "$%x", id); + + LLVMTypeRef type = LLVMTypeOf(data); + LLVMValueRef global_data = LLVMAddGlobal(m->mod, type, name); + LLVMSetInitializer(global_data, data); + lb_make_global_private_const(global_data); + LLVMSetAlignment(global_data, 1); + + LLVMValueRef ptr = LLVMConstInBoundsGEP2(type, global_data, indices, 2); + return ptr; +} + gb_internal lbValue lb_find_or_add_entity_string(lbModule *m, String const &str, bool custom_link_section) { LLVMValueRef ptr = nullptr; if (str.len != 0) { diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index d4117b7ff..ea1bae4e9 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -6,6 +6,7 @@ gb_internal bool lb_is_type_aggregate(Type *t) { case Type_Basic: switch (t->Basic.kind) { case Basic_string: + case Basic_string16: case Basic_any: return true; @@ -981,7 +982,8 @@ gb_internal i32 lb_convert_struct_index(lbModule *m, Type *t, i32 index) { } else if (build_context.ptr_size != build_context.int_size) { switch (t->kind) { case Type_Basic: - if (t->Basic.kind != Basic_string) { + if (t->Basic.kind != Basic_string && + t->Basic.kind != Basic_string16) { break; } /*fallthrough*/ @@ -1160,6 +1162,11 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) { case 0: result_type = alloc_type_pointer(t->Slice.elem); break; case 1: result_type = t_int; break; } + } else if (is_type_string16(t)) { + switch (index) { + case 0: result_type = t_u16_ptr; break; + case 1: result_type = t_int; break; + } } else if (is_type_string(t)) { switch (index) { case 0: result_type = t_u8_ptr; break; @@ -1273,6 +1280,12 @@ gb_internal lbValue lb_emit_struct_ev(lbProcedure *p, lbValue s, i32 index) { switch (t->kind) { case Type_Basic: switch (t->Basic.kind) { + case Basic_string16: + switch (index) { + case 0: result_type = t_u16_ptr; break; + case 1: result_type = t_int; break; + } + break; case Basic_string: switch (index) { case 0: result_type = t_u8_ptr; break; @@ -1440,6 +1453,10 @@ gb_internal lbValue lb_emit_deep_field_gep(lbProcedure *p, lbValue e, Selection e = lb_emit_struct_ep(p, e, index); break; + case Basic_string16: + e = lb_emit_struct_ep(p, e, index); + break; + default: GB_PANIC("un-gep-able type %s", type_to_string(type)); break; diff --git a/src/string.cpp b/src/string.cpp index 8405938f4..8cc0e93f3 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -79,6 +79,13 @@ gb_internal String substring(String const &s, isize lo, isize hi) { return make_string(s.text+lo, hi-lo); } +gb_internal String16 substring(String16 const &s, isize lo, isize hi) { + isize max = s.len; + GB_ASSERT_MSG(lo <= hi && hi <= max, "%td..%td..%td", lo, hi, max); + + return make_string16(s.text+lo, hi-lo); +} + gb_internal char *alloc_cstring(gbAllocator a, String s) { char *c_str = gb_alloc_array(a, char, s.len+1); -- cgit v1.2.3 From bb4bc316a4bd86774953f1e8fcefffb5ed8bbf37 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 2 Aug 2025 12:20:35 +0100 Subject: `for in string16`; Support `string16` across core --- base/runtime/internal.odin | 62 +++++++++++++++++ core/c/libc/locale.odin | 16 ++--- core/debug/trace/trace_windows.odin | 2 +- core/dynlib/lib_windows.odin | 2 +- core/mem/virtual/virtual_windows.odin | 2 +- core/os/dir_windows.odin | 2 +- core/os/os2/file_windows.odin | 2 +- core/os/stat_windows.odin | 4 +- core/path/filepath/path_windows.odin | 4 +- core/sys/info/platform_windows.odin | 12 ++-- core/sys/windows/comctl32.odin | 10 +-- core/sys/windows/ip_helper.odin | 6 +- core/sys/windows/types.odin | 4 +- core/sys/windows/util.odin | 50 +++++++------- core/time/timezone/tz_windows.odin | 8 +-- core/unicode/utf16/utf16.odin | 32 ++++++++- src/check_builtin.cpp | 6 +- src/check_expr.cpp | 2 +- src/check_stmt.cpp | 26 +++++++- src/llvm_backend_stmt.cpp | 121 +++++++++++++++++++++++++++++++++- src/llvm_backend_utility.cpp | 17 +++++ 21 files changed, 321 insertions(+), 69 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 660af58ab..4f9509b23 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -781,6 +781,68 @@ string_decode_last_rune :: proc "contextless" (s: string) -> (rune, int) { return r, size } + +string16_decode_rune :: #force_inline proc "contextless" (s: string16) -> (rune, int) { + REPLACEMENT_CHAR :: '\ufffd' + _surr1 :: 0xd800 + _surr2 :: 0xdc00 + _surr3 :: 0xe000 + _surr_self :: 0x10000 + + r := rune(REPLACEMENT_CHAR) + + if len(s) < 1 { + return r, 0 + } + + w := 1 + switch c := s[0]; { + case c < _surr1, _surr3 <= c: + r = rune(c) + case _surr1 <= c && c < _surr2 && 1 < len(s) && + _surr2 <= s[1] && s[1] < _surr3: + r1, r2 := rune(c), rune(s[1]) + if _surr1 <= r1 && r1 < _surr2 && _surr2 <= r2 && r2 < _surr3 { + r = (r1-_surr1)<<10 | (r2 - _surr2) + _surr_self + } + w += 1 + } + return r, w +} + +string16_decode_last_rune :: proc "contextless" (s: string16) -> (rune, int) { + REPLACEMENT_CHAR :: '\ufffd' + _surr1 :: 0xd800 + _surr2 :: 0xdc00 + _surr3 :: 0xe000 + _surr_self :: 0x10000 + + r := rune(REPLACEMENT_CHAR) + + if len(s) < 1 { + return r, 0 + } + + n := len(s)-1 + c := s[n] + w := 1 + if _surr2 <= c && c < _surr3 { + if n >= 1 { + r1 := rune(s[n-1]) + r2 := rune(c) + if _surr1 <= r1 && r1 < _surr2 { + r = (r1-_surr1)<<10 | (r2 - _surr2) + _surr_self + } + w = 2 + } + } else if c < _surr1 || _surr3 <= c { + r = rune(c) + } + return r, w +} + + + abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 { p, q := abs(real(x)), abs(imag(x)) if p < q { diff --git a/core/c/libc/locale.odin b/core/c/libc/locale.odin index 27317526c..3216e0f90 100644 --- a/core/c/libc/locale.odin +++ b/core/c/libc/locale.odin @@ -72,14 +72,14 @@ when ODIN_OS == .Windows { n_sep_by_space: c.char, p_sign_posn: c.char, n_sign_posn: c.char, - _W_decimal_point: [^]u16 `fmt:"s,0"`, - _W_thousands_sep: [^]u16 `fmt:"s,0"`, - _W_int_curr_symbol: [^]u16 `fmt:"s,0"`, - _W_currency_symbol: [^]u16 `fmt:"s,0"`, - _W_mon_decimal_point: [^]u16 `fmt:"s,0"`, - _W_mon_thousands_sep: [^]u16 `fmt:"s,0"`, - _W_positive_sign: [^]u16 `fmt:"s,0"`, - _W_negative_sign: [^]u16 `fmt:"s,0"`, + _W_decimal_point: cstring16, + _W_thousands_sep: cstring16, + _W_int_curr_symbol: cstring16, + _W_currency_symbol: cstring16, + _W_mon_decimal_point: cstring16, + _W_mon_thousands_sep: cstring16, + _W_positive_sign: cstring16, + _W_negative_sign: cstring16, } } else { lconv :: struct { diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin index 96507714c..04e92f125 100644 --- a/core/debug/trace/trace_windows.odin +++ b/core/debug/trace/trace_windows.odin @@ -54,7 +54,7 @@ _resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> ( symbol.SizeOfStruct = size_of(symbol^) symbol.MaxNameLen = 255 if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) { - fl.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator) + fl.procedure, _ = win32.wstring_to_utf8(cstring16(&symbol.Name[0]), -1, allocator) } else { fl.procedure = fmt.aprintf("(procedure: 0x%x)", frame, allocator=allocator) } diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index 05cd2cb3c..95372dac6 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -13,7 +13,7 @@ _LIBRARY_FILE_EXTENSION :: "dll" _load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { // NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL wide_path := win32.utf8_to_wstring(path, allocator) - defer free(wide_path, allocator) + defer free(rawptr(wide_path), allocator) handle := cast(Library)win32.LoadLibraryW(wide_path) return handle, handle != nil } diff --git a/core/mem/virtual/virtual_windows.odin b/core/mem/virtual/virtual_windows.odin index 0da8498d5..3fd4eeb68 100644 --- a/core/mem/virtual/virtual_windows.odin +++ b/core/mem/virtual/virtual_windows.odin @@ -72,7 +72,7 @@ foreign Kernel32 { flProtect: u32, dwMaximumSizeHigh: u32, dwMaximumSizeLow: u32, - lpName: [^]u16, + lpName: cstring16, ) -> rawptr --- MapViewOfFile :: proc( diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index ae3e6922c..40f4b9e9b 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -87,7 +87,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F defer delete(path) find_data := &win32.WIN32_FIND_DATAW{} - find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data) + find_handle := win32.FindFirstFileW(cstring16(raw_data(wpath_search)), find_data) if find_handle == win32.INVALID_HANDLE_VALUE { err = get_last_error() return dfi[:], err diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 40d012183..1134e765c 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -619,7 +619,7 @@ _symlink :: proc(old_name, new_name: string) -> Error { return .Unsupported } -_open_sym_link :: proc(p: [^]u16) -> (handle: win32.HANDLE, err: Error) { +_open_sym_link :: proc(p: cstring16) -> (handle: win32.HANDLE, err: Error) { attrs := u32(win32.FILE_FLAG_BACKUP_SEMANTICS) attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT handle = win32.CreateFileW(p, 0, 0, nil, win32.OPEN_EXISTING, attrs, nil) diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index ca4f87668..662c9f9e6 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -17,7 +17,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa buf := make([dynamic]u16, 100) defer delete(buf) for { - n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) + n := win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) if n == 0 { return "", get_last_error() } @@ -154,7 +154,7 @@ cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ( return nil, get_last_error() } buf := make([]u16, max(n, win32.DWORD(260))+1, allocator) - buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0) + buf_len := win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), n, 0) return buf[:buf_len], nil } @(private, require_results) diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 0dcb28cf8..24c6e00a5 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -61,13 +61,13 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Error) { } p := win32.utf8_to_utf16(name, ta) - n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) + n := win32.GetFullPathNameW(cstring16(raw_data(p)), 0, nil, nil) if n == 0 { return "", os.get_last_error() } buf := make([]u16, n, ta) - n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) + n = win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) if n == 0 { delete(buf) return "", os.get_last_error() diff --git a/core/sys/info/platform_windows.odin b/core/sys/info/platform_windows.odin index 4c00ddadf..dd1441d30 100644 --- a/core/sys/info/platform_windows.odin +++ b/core/sys/info/platform_windows.odin @@ -324,8 +324,8 @@ read_reg_string :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: string, ok status := sys.RegGetValueW( hkey, - &key_name_wide[0], - &val_name_wide[0], + cstring16(&key_name_wide[0]), + cstring16(&val_name_wide[0]), sys.RRF_RT_REG_SZ, nil, raw_data(result_wide[:]), @@ -359,8 +359,8 @@ read_reg_i32 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i32, ok: bool result_size := sys.DWORD(size_of(i32)) status := sys.RegGetValueW( hkey, - &key_name_wide[0], - &val_name_wide[0], + cstring16(&key_name_wide[0]), + cstring16(&val_name_wide[0]), sys.RRF_RT_REG_DWORD, nil, &res, @@ -386,8 +386,8 @@ read_reg_i64 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i64, ok: bool result_size := sys.DWORD(size_of(i64)) status := sys.RegGetValueW( hkey, - &key_name_wide[0], - &val_name_wide[0], + cstring16(&key_name_wide[0]), + cstring16(&val_name_wide[0]), sys.RRF_RT_REG_QWORD, nil, &res, diff --git a/core/sys/windows/comctl32.odin b/core/sys/windows/comctl32.odin index d954f952c..c7a166634 100644 --- a/core/sys/windows/comctl32.odin +++ b/core/sys/windows/comctl32.odin @@ -573,10 +573,10 @@ Button_GetTextMargin :: #force_inline proc "system" (hwnd: HWND, pmargin: ^RECT) return cast(BOOL)SendMessageW(hwnd, BCM_GETTEXTMARGIN, 0, cast(LPARAM)uintptr(pmargin)) } Button_SetNote :: #force_inline proc "system" (hwnd: HWND, psz: LPCWSTR) -> BOOL { - return cast(BOOL)SendMessageW(hwnd, BCM_SETNOTE, 0, cast(LPARAM)uintptr(psz)) + return cast(BOOL)SendMessageW(hwnd, BCM_SETNOTE, 0, cast(LPARAM)uintptr(rawptr(psz))) } Button_GetNote :: #force_inline proc "system" (hwnd: HWND, psz: LPCWSTR, pcc: ^c_int) -> BOOL { - return cast(BOOL)SendMessageW(hwnd, BCM_GETNOTE, uintptr(pcc), cast(LPARAM)uintptr(psz)) + return cast(BOOL)SendMessageW(hwnd, BCM_GETNOTE, uintptr(pcc), cast(LPARAM)uintptr(rawptr(psz))) } Button_GetNoteLength :: #force_inline proc "system" (hwnd: HWND) -> LRESULT { return SendMessageW(hwnd, BCM_GETNOTELENGTH, 0, 0) @@ -604,10 +604,10 @@ EDITBALLOONTIP :: struct { PEDITBALLOONTIP :: ^EDITBALLOONTIP Edit_SetCueBannerText :: #force_inline proc "system" (hwnd: HWND, lpcwText: LPCWSTR) -> BOOL { - return cast(BOOL)SendMessageW(hwnd, EM_SETCUEBANNER, 0, cast(LPARAM)uintptr(lpcwText)) + return cast(BOOL)SendMessageW(hwnd, EM_SETCUEBANNER, 0, cast(LPARAM)uintptr(rawptr(lpcwText))) } Edit_SetCueBannerTextFocused :: #force_inline proc "system" (hwnd: HWND, lpcwText: LPCWSTR, fDrawFocused: BOOL) -> BOOL { - return cast(BOOL)SendMessageW(hwnd, EM_SETCUEBANNER, cast(WPARAM)fDrawFocused, cast(LPARAM)uintptr(lpcwText)) + return cast(BOOL)SendMessageW(hwnd, EM_SETCUEBANNER, cast(WPARAM)fDrawFocused, cast(LPARAM)uintptr(rawptr(lpcwText))) } Edit_GetCueBannerText :: #force_inline proc "system" (hwnd: HWND, lpwText: LPWSTR, cchText: LONG) -> BOOL { return cast(BOOL)SendMessageW(hwnd, EM_GETCUEBANNER, uintptr(lpwText), cast(LPARAM)cchText) @@ -1197,7 +1197,7 @@ ListView_GetItemPosition :: #force_inline proc "system" (hwnd: HWND, i: c_int, p return cast(BOOL)SendMessageW(hwnd, LVM_GETITEMPOSITION, cast(WPARAM)i, cast(LPARAM)uintptr(ppt)) } ListView_GetStringWidth :: #force_inline proc "system" (hwndLV: HWND, psz: LPCWSTR) -> c_int { - return cast(c_int)SendMessageW(hwndLV, LVM_GETSTRINGWIDTHW, 0, cast(LPARAM)uintptr(psz)) + return cast(c_int)SendMessageW(hwndLV, LVM_GETSTRINGWIDTHW, 0, cast(LPARAM)uintptr(rawptr(psz))) } ListView_HitTest :: #force_inline proc "system" (hwndLV: HWND, pinfo: ^LV_HITTESTINFO) -> c_int { return cast(c_int)SendMessageW(hwndLV, LVM_HITTEST, 0, cast(LPARAM)uintptr(pinfo)) diff --git a/core/sys/windows/ip_helper.odin b/core/sys/windows/ip_helper.odin index 7a6e545ac..d2e75d531 100644 --- a/core/sys/windows/ip_helper.odin +++ b/core/sys/windows/ip_helper.odin @@ -38,9 +38,9 @@ IP_Adapter_Addresses :: struct { FirstAnycastAddress: ^IP_ADAPTER_ANYCAST_ADDRESS_XP, FirstMulticastAddress: ^IP_ADAPTER_MULTICAST_ADDRESS_XP, FirstDnsServerAddress: ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP, - DnsSuffix: ^u16, - Description: ^u16, - FriendlyName: ^u16, + DnsSuffix: cstring16, + Description: cstring16, + FriendlyName: cstring16, PhysicalAddress: [8]u8, PhysicalAddressLength: u32, Anonymous2: struct #raw_union { diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 92b1cb15c..be16d2fdd 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -107,8 +107,8 @@ PDWORD64 :: ^DWORD64 PDWORD_PTR :: ^DWORD_PTR ATOM :: distinct WORD -wstring :: [^]WCHAR -PWSTR :: [^]WCHAR +wstring :: cstring16 +PWSTR :: cstring16 PBYTE :: ^BYTE LPBYTE :: ^BYTE diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index 995e8e0e5..10dc907e7 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -122,14 +122,14 @@ utf8_to_utf16 :: proc{utf8_to_utf16_alloc, utf8_to_utf16_buf} utf8_to_wstring_alloc :: proc(s: string, allocator := context.temp_allocator) -> wstring { if res := utf8_to_utf16(s, allocator); len(res) > 0 { - return raw_data(res) + return wstring(raw_data(res)) } return nil } utf8_to_wstring_buf :: proc(buf: []u16, s: string) -> wstring { if res := utf8_to_utf16(buf, s); len(res) > 0 { - return raw_data(res) + return wstring(raw_data(res)) } return nil } @@ -215,7 +215,7 @@ utf16_to_utf8_alloc :: proc(s: []u16, allocator := context.temp_allocator) -> (r if len(s) == 0 { return "", nil } - return wstring_to_utf8(raw_data(s), len(s), allocator) + return wstring_to_utf8(wstring(raw_data(s)), len(s), allocator) } /* @@ -236,7 +236,7 @@ utf16_to_utf8_buf :: proc(buf: []u8, s: []u16) -> (res: string) { if len(s) == 0 { return } - return wstring_to_utf8(buf, raw_data(s), len(s)) + return wstring_to_utf8(buf, wstring(raw_data(s)), len(s)) } utf16_to_utf8 :: proc{utf16_to_utf8_alloc, utf16_to_utf8_buf} @@ -298,7 +298,7 @@ _add_user :: proc(servername: string, username: string, password: string) -> (ok servername_w = nil } else { server := utf8_to_utf16(servername, context.temp_allocator) - servername_w = &server[0] + servername_w = wstring(&server[0]) } if len(username) == 0 || len(username) > LM20_UNLEN { @@ -348,7 +348,7 @@ get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: s res := LookupAccountNameW( nil, // Look on this computer first - &username_w[0], + wstring(&username_w[0]), &sid, &cbsid, nil, @@ -364,10 +364,10 @@ get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: s res = LookupAccountNameW( nil, - &username_w[0], + wstring(&username_w[0]), &sid, &cbsid, - &cname_w[0], + wstring(&cname_w[0]), &computer_name_size, &pe_use, ) @@ -390,7 +390,7 @@ get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) { res := LookupAccountNameW( nil, // Look on this computer first - &username_w[0], + wstring(&username_w[0]), sid, &cbsid, nil, @@ -406,10 +406,10 @@ get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) { res = LookupAccountNameW( nil, - &username_w[0], + wstring(&username_w[0]), sid, &cbsid, - &cname_w[0], + wstring(&cname_w[0]), &computer_name_size, &pe_use, ) @@ -428,7 +428,7 @@ add_user_to_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) { group_name := utf8_to_utf16(group, context.temp_allocator) ok = NetLocalGroupAddMembers( nil, - &group_name[0], + wstring(&group_name[0]), 0, &group_member, 1, @@ -443,7 +443,7 @@ add_del_from_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) { group_name := utf8_to_utf16(group, context.temp_allocator) ok = NetLocalGroupDelMembers( nil, - &group_name[0], + cstring16(&group_name[0]), 0, &group_member, 1, @@ -465,19 +465,19 @@ add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) { if res == false { return false, "" } - defer LocalFree(sb) + defer LocalFree(rawptr(sb)) pszProfilePath := make([]u16, 257, context.temp_allocator) res2 := CreateProfile( sb, - &username_w[0], - &pszProfilePath[0], + cstring16(&username_w[0]), + cstring16(&pszProfilePath[0]), 257, ) if res2 != 0 { return false, "" } - profile_path = wstring_to_utf8(&pszProfilePath[0], 257) or_else "" + profile_path = wstring_to_utf8(wstring(&pszProfilePath[0]), 257) or_else "" return true, profile_path } @@ -495,7 +495,7 @@ delete_user_profile :: proc(username: string) -> (ok: bool) { if res == false { return false } - defer LocalFree(sb) + defer LocalFree(rawptr(sb)) res2 := DeleteProfileW( sb, @@ -548,13 +548,13 @@ delete_user :: proc(servername: string, username: string) -> (ok: bool) { servername_w = nil } else { server := utf8_to_utf16(servername, context.temp_allocator) - servername_w = &server[0] + servername_w = wstring(&server[0]) } username_w := utf8_to_utf16(username) res := NetUserDel( servername_w, - &username_w[0], + wstring(&username_w[0]), ) if res != .Success { return false @@ -586,9 +586,9 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P user_token: HANDLE ok = bool(LogonUserW( - lpszUsername = &username_w[0], - lpszDomain = &domain_w[0], - lpszPassword = &password_w[0], + lpszUsername = wstring(&username_w[0]), + lpszDomain = wstring(&domain_w[0]), + lpszPassword = wstring(&password_w[0]), dwLogonType = .NEW_CREDENTIALS, dwLogonProvider = .WINNT50, phToken = &user_token, @@ -605,8 +605,8 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P ok = bool(CreateProcessAsUserW( user_token, - &app_w[0], - &commandline_w[0], + wstring(&app_w[0]), + wstring(&commandline_w[0]), nil, // lpProcessAttributes, nil, // lpThreadAttributes, false, // bInheritHandles, diff --git a/core/time/timezone/tz_windows.odin b/core/time/timezone/tz_windows.odin index 8dc5f533c..fe00719a2 100644 --- a/core/time/timezone/tz_windows.odin +++ b/core/time/timezone/tz_windows.odin @@ -159,9 +159,9 @@ iana_to_windows_tz :: proc(iana_name: string, allocator := context.allocator) -> status: windows.UError iana_name_wstr := windows.utf8_to_wstring(iana_name, allocator) - defer free(iana_name_wstr, allocator) + defer free(rawptr(iana_name_wstr), allocator) - wintz_name_len := windows.ucal_getWindowsTimeZoneID(iana_name_wstr, -1, raw_data(wintz_name_buffer[:]), len(wintz_name_buffer), &status) + wintz_name_len := windows.ucal_getWindowsTimeZoneID(iana_name_wstr, -1, cstring16(raw_data(wintz_name_buffer[:])), len(wintz_name_buffer), &status) if status != .U_ZERO_ERROR { return } @@ -178,7 +178,7 @@ local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: iana_name_buffer: [128]u16 status: windows.UError - zone_str_len := windows.ucal_getDefaultTimeZone(raw_data(iana_name_buffer[:]), len(iana_name_buffer), &status) + zone_str_len := windows.ucal_getDefaultTimeZone(cstring16(raw_data(iana_name_buffer[:])), len(iana_name_buffer), &status) if status != .U_ZERO_ERROR { return } @@ -291,7 +291,7 @@ _region_load :: proc(reg_str: string, allocator := context.allocator) -> (out_re defer delete(tz_key, allocator) tz_key_wstr := windows.utf8_to_wstring(tz_key, allocator) - defer free(tz_key_wstr, allocator) + defer free(rawptr(tz_key_wstr), allocator) key: windows.HKEY res := windows.RegOpenKeyExW(windows.HKEY_LOCAL_MACHINE, tz_key_wstr, 0, windows.KEY_READ, &key) diff --git a/core/unicode/utf16/utf16.odin b/core/unicode/utf16/utf16.odin index 9a8cfe438..d3f98584b 100644 --- a/core/unicode/utf16/utf16.odin +++ b/core/unicode/utf16/utf16.odin @@ -126,7 +126,37 @@ decode_rune_in_string :: proc(s: string16) -> (r: rune, width: int) { return } -rune_count :: proc(s: []u16) -> (n: int) { +string_to_runes :: proc "odin" (s: string16, allocator := context.allocator) -> (runes: []rune) { + n := rune_count(s) + + runes = make([]rune, n, allocator) + i := 0 + for r in s { + runes[i] = r + i += 1 + } + return +} + + +rune_count :: proc{ + rune_count_in_string, + rune_count_in_slice, +} +rune_count_in_string :: proc(s: string16) -> (n: int) { + for i := 0; i < len(s); i += 1 { + c := s[i] + if _surr1 <= c && c < _surr2 && i+1 < len(s) && + _surr2 <= s[i+1] && s[i+1] < _surr3 { + i += 1 + } + n += 1 + } + return +} + + +rune_count_in_slice :: proc(s: []u16) -> (n: int) { for i := 0; i < len(s); i += 1 { c := s[i] if _surr1 <= c && c < _surr2 && i+1 < len(s) && diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4abace637..66ea0cfbd 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -7179,7 +7179,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } operand->mode = Addressing_Value; - operand->type = alloc_type_multi_pointer(t_u16); + if (type_hint != nullptr && is_type_cstring16(type_hint)) { + operand->type = type_hint; + } else { + operand->type = alloc_type_multi_pointer(t_u16); + } operand->value = {}; break; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8d2e4d637..34149f92b 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3426,7 +3426,7 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type if (are_types_identical(src, t_cstring16) && is_type_u16_multi_ptr(dst)) { return !is_constant; } - // cstring -> rawptr + // cstring16 -> rawptr if (are_types_identical(src, t_cstring16) && is_type_rawptr(dst)) { return !is_constant; } diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index bc9b6c5dd..ae88ff333 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -974,7 +974,14 @@ gb_internal void check_unroll_range_stmt(CheckerContext *ctx, Ast *node, u32 mod Type *t = base_type(operand.type); switch (t->kind) { case Type_Basic: - if (is_type_string(t) && t->Basic.kind != Basic_cstring) { + if (is_type_string16(t) && t->Basic.kind != Basic_cstring) { + val0 = t_rune; + val1 = t_int; + inline_for_depth = exact_value_i64(operand.value.value_string.len); + if (unroll_count > 0) { + error(node, "#unroll(%lld) does not support strings", cast(long long)unroll_count); + } + } else if (is_type_string(t) && t->Basic.kind != Basic_cstring) { val0 = t_rune; val1 = t_int; inline_for_depth = exact_value_i64(operand.value.value_string.len); @@ -1236,7 +1243,11 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags add_to_seen_map(ctx, &seen, upper_op, x, lhs, rhs); - if (is_type_string(x.type)) { + if (is_type_string16(x.type)) { + // NOTE(bill): Force dependency for strings here + add_package_dependency(ctx, "runtime", "string16_le"); + add_package_dependency(ctx, "runtime", "string16_lt"); + } else if (is_type_string(x.type)) { // NOTE(bill): Force dependency for strings here add_package_dependency(ctx, "runtime", "string_le"); add_package_dependency(ctx, "runtime", "string_lt"); @@ -1770,7 +1781,16 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) switch (t->kind) { case Type_Basic: - if (t->Basic.kind == Basic_string || t->Basic.kind == Basic_UntypedString) { + if (t->Basic.kind == Basic_string16) { + is_possibly_addressable = false; + array_add(&vals, t_rune); + array_add(&vals, t_int); + if (is_reverse) { + add_package_dependency(ctx, "runtime", "string16_decode_last_rune"); + } else { + add_package_dependency(ctx, "runtime", "string16_decode_rune"); + } + } else if (t->Basic.kind == Basic_string || t->Basic.kind == Basic_UntypedString) { is_possibly_addressable = false; array_add(&vals, t_rune); array_add(&vals, t_int); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 027837f3f..5481ca447 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -622,6 +622,121 @@ gb_internal void lb_build_range_string(lbProcedure *p, lbValue expr, Type *val_t if (done_) *done_ = done; } +gb_internal void lb_build_range_string16(lbProcedure *p, lbValue expr, Type *val_type, + lbValue *val_, lbValue *idx_, lbBlock **loop_, lbBlock **done_, + bool is_reverse) { + + lbModule *m = p->module; + lbValue count = lb_const_int(m, t_int, 0); + Type *expr_type = base_type(expr.type); + switch (expr_type->kind) { + case Type_Basic: + count = lb_string_len(p, expr); + break; + default: + GB_PANIC("Cannot do range_string of %s", type_to_string(expr_type)); + break; + } + + lbValue val = {}; + lbValue idx = {}; + lbBlock *loop = nullptr; + lbBlock *done = nullptr; + lbBlock *body = nullptr; + + loop = lb_create_block(p, "for.string16.loop"); + body = lb_create_block(p, "for.string16.body"); + done = lb_create_block(p, "for.string16.done"); + + lbAddr offset_ = lb_add_local_generated(p, t_int, false); + lbValue offset = {}; + lbValue cond = {}; + + if (!is_reverse) { + /* + for c, offset in str { + ... + } + + offset := 0 + for offset < len(str) { + c, _w := string16_decode_rune(str[offset:]) + ... + offset += _w + } + */ + lb_addr_store(p, offset_, lb_const_int(m, t_int, 0)); + + lb_emit_jump(p, loop); + lb_start_block(p, loop); + + + offset = lb_addr_load(p, offset_); + cond = lb_emit_comp(p, Token_Lt, offset, count); + } else { + // NOTE(bill): REVERSED LOGIC + /* + #reverse for c, offset in str { + ... + } + + offset := len(str) + for offset > 0 { + c, _w := string16_decode_last_rune(str[:offset]) + offset -= _w + ... + } + */ + lb_addr_store(p, offset_, count); + + lb_emit_jump(p, loop); + lb_start_block(p, loop); + + offset = lb_addr_load(p, offset_); + cond = lb_emit_comp(p, Token_Gt, offset, lb_const_int(m, t_int, 0)); + } + lb_emit_if(p, cond, body, done); + lb_start_block(p, body); + + + lbValue rune_and_len = {}; + if (!is_reverse) { + lbValue str_elem = lb_emit_ptr_offset(p, lb_string_elem(p, expr), offset); + lbValue str_len = lb_emit_arith(p, Token_Sub, count, offset, t_int); + auto args = array_make(permanent_allocator(), 1); + args[0] = lb_emit_string16(p, str_elem, str_len); + + rune_and_len = lb_emit_runtime_call(p, "string16_decode_rune", args); + lbValue len = lb_emit_struct_ev(p, rune_and_len, 1); + lb_addr_store(p, offset_, lb_emit_arith(p, Token_Add, offset, len, t_int)); + + idx = offset; + } else { + // NOTE(bill): REVERSED LOGIC + lbValue str_elem = lb_string_elem(p, expr); + lbValue str_len = offset; + auto args = array_make(permanent_allocator(), 1); + args[0] = lb_emit_string16(p, str_elem, str_len); + + rune_and_len = lb_emit_runtime_call(p, "string16_decode_last_rune", args); + lbValue len = lb_emit_struct_ev(p, rune_and_len, 1); + lb_addr_store(p, offset_, lb_emit_arith(p, Token_Sub, offset, len, t_int)); + + idx = lb_addr_load(p, offset_); + } + + + if (val_type != nullptr) { + val = lb_emit_struct_ev(p, rune_and_len, 0); + } + + if (val_) *val_ = val; + if (idx_) *idx_ = idx; + if (loop_) *loop_ = loop; + if (done_) *done_ = done; +} + + gb_internal Ast *lb_strip_and_prefix(Ast *ident) { if (ident != nullptr) { @@ -1138,7 +1253,11 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc } Type *t = base_type(string.type); GB_ASSERT(!is_type_cstring(t)); - lb_build_range_string(p, string, val0_type, &val, &key, &loop, &done, rs->reverse); + if (is_type_string16(t)) { + lb_build_range_string16(p, string, val0_type, &val, &key, &loop, &done, rs->reverse); + } else { + lb_build_range_string(p, string, val0_type, &val, &key, &loop, &done, rs->reverse); + } break; } case Type_Tuple: diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index ea1bae4e9..dcb95a9a2 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -191,6 +191,23 @@ gb_internal lbValue lb_emit_clamp(lbProcedure *p, Type *t, lbValue x, lbValue mi return z; } +gb_internal lbValue lb_emit_string16(lbProcedure *p, lbValue str_elem, lbValue str_len) { + if (false && lb_is_const(str_elem) && lb_is_const(str_len)) { + LLVMValueRef values[2] = { + str_elem.value, + str_len.value, + }; + lbValue res = {}; + res.type = t_string16; + res.value = llvm_const_named_struct(p->module, t_string16, values, gb_count_of(values)); + return res; + } else { + lbAddr res = lb_add_local_generated(p, t_string16, false); + lb_emit_store(p, lb_emit_struct_ep(p, res.addr, 0), str_elem); + lb_emit_store(p, lb_emit_struct_ep(p, res.addr, 1), str_len); + return lb_addr_load(p, res); + } +} gb_internal lbValue lb_emit_string(lbProcedure *p, lbValue str_elem, lbValue str_len) { -- cgit v1.2.3 From 9d8e15b3af6e41b3b3d4359eb0d2324cfa34608f Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Wed, 6 Aug 2025 15:13:12 -0400 Subject: Implementes the block ABI.2010.3.16 natively via the `objc_block` intrinsic and the `Objc_Block` builtin type. See: https://clang.llvm.org/docs/Block-ABI-Apple.html --- base/intrinsics/intrinsics.odin | 1 + base/runtime/core_builtin.odin | 5 + base/runtime/procs_darwin.odin | 12 +- src/check_builtin.cpp | 224 +++++++++++++++++++++++ src/checker.cpp | 4 + src/checker_builtin_procs.hpp | 2 + src/llvm_backend.hpp | 6 +- src/llvm_backend_proc.cpp | 1 + src/llvm_backend_utility.cpp | 391 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 644 insertions(+), 2 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index d45d24f48..4f644728e 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -381,6 +381,7 @@ objc_register_selector :: proc($name: string) -> objc_SEL --- 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) --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 09118998c..3a51d71fb 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -5,6 +5,11 @@ import "base:intrinsics" @builtin Maybe :: union($T: typeid) {T} +/* +Represents an Objective-C block with a given procedure signature T +*/ +@builtin +Objc_Block :: struct($T: typeid) where intrinsics.type_is_proc(T) { using _: intrinsics.objc_object } /* Recovers the containing/parent struct from a pointer to one of its fields. diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin index 20f09400d..d176f0f63 100644 --- a/base/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -1,9 +1,12 @@ #+private package runtime -@(priority_index=-1e6) +@(priority_index=-1e5) foreign import ObjC "system:objc" +@(priority_index=-1e6) +foreign import libSystem "system:System" + import "base:intrinsics" objc_id :: ^intrinsics.objc_object @@ -34,3 +37,10 @@ foreign ObjC { object_getClass :: proc "c" (obj: objc_id) -> objc_Class --- } +foreign libSystem { + _NSConcreteGlobalBlock: intrinsics.objc_class + _NSConcreteStackBlock: intrinsics.objc_class + + _Block_object_assign :: proc "c" (rawptr, rawptr, i32) --- + _Block_object_dispose :: proc "c" (rawptr, i32) --- +} diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 57413f519..9e2ad86c0 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -457,6 +457,229 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan return true; } break; + + case BuiltinProc_objc_block: + { + // NOTE(harold): The last argument specified in the call is the handler proc, + // any other arguments before it are capture by-copy arguments. + auto param_operands = slice_make(permanent_allocator(), ce->args.count); + + 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. + param_operands[0] = *operand; + + for (isize i = 0; i < ce->args.count-1; i++) { + Operand x = {}; + check_expr(c, &x, ce->args[i]); + + switch (x.mode) { + case Addressing_Value: + case Addressing_Context: + case Addressing_Variable: + case Addressing_Constant: + param_operands[i] = x; + break; + + default: + gbString e = expr_to_string(x.expr); + gbString t = type_to_string(x.type); + error(x.expr, "'%.*s' capture arguments must be values, but got %s of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + } + + // Validate handler proc + Operand handler = {}; + + if (capture_arg_count == 0) { + // It's already been checked and assigned + handler = param_operands[0]; + } else { + check_expr_or_type(c, &handler, ce->args[capture_arg_count]); + param_operands[capture_arg_count] = handler; + } + + if (!is_operand_value(handler) || handler.type->kind != Type_Proc) { + gbString e = expr_to_string(handler.expr); + gbString t = type_to_string(handler.type); + error(handler.expr, "'%.*s' expected a procedure, but got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + Ast *handler_node = unparen_expr(handler.expr); + + // Only direct reference to procs are allowed + switch (handler_node->kind) { + case Ast_ProcLit: break; // ok + case Ast_Ident: { + auto& ident = handler_node->Ident; + + if (ident.entity == nullptr) { + error(handler.expr, "'%.*s' failed to resolve entity from expression", LIT(builtin_name)); + return false; + } + + if (ident.entity->kind != Entity_Procedure) { + gbString e = expr_to_string(handler_node); + + ERROR_BLOCK(); + error(handler.expr, "'%.*s' expected a direct reference to a procedure", LIT(builtin_name)); + if(ident.entity->kind == Entity_Variable) { + error_line("\tSuggestion: Variables referencing a procedure are not allowed, they are not a direct procedure reference."); + } else { + error_line("\tSuggestion: Ensure '%s' is not a runtime-evaluated expression.", e); // NOTE(harold): Is this case possible to hit? + } + error_line("\n\t Refer to a procedure directly by its name or declare it anonymously: %.*s(proc(){})", LIT(builtin_name)); + + gb_string_free(e); + return false; + } + } break; + + default: { + gbString e = expr_to_string(handler_node); + ERROR_BLOCK(); + error(handler.expr, "'%.*s' expected a direct reference to a procedure", LIT(builtin_name)); + if( handler_node->kind == Ast_CallExpr) { + error_line("\tSuggestion: Do not use a procedure returned from another procedure."); + } else { + error_line("\tSuggestion: Ensure '%s' is not a runtime-evaluated expression.", e); + } + error_line("\n\t Refer to a procedure directly by its name or declare it anonymously: %.*s(proc(){})", LIT(builtin_name)); + + gb_string_free(e); + } return false; + } // End switch + + auto& handler_type_proc = handler.type->Proc; + + if (capture_arg_count > handler_type_proc.param_count) { + error(handler.expr, "'%.*s' captured arguments exceeded the handler's parameter count", LIT(builtin_name)); + return false; + } + + // If the handler proc is odin calling convention, but there must be a context defined in this scope. + if (handler_type_proc.calling_convention == ProcCC_Odin) { + if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) { + ERROR_BLOCK(); + error(handler.expr, "The handler procedure for '%.*s' requires a context, but no context is defined in the current scope", LIT(builtin_name)); + error_line("\tSuggestion: 'context = runtime.default_context()', or use the \"c\" calling convention for the handler procedure"); + return false; + } + } + + // At most a single return value is supported + if (handler_type_proc.result_count > 1) { + error(handler_type_proc.node->ProcType.results, "Handler procedures for '%.*s' cannot have multiple return values", LIT(builtin_name)); + return false; + } + + // Ensure that captured args are assignable to the handler's corresponding capture params + if (handler_type_proc.param_count > 0) { + auto& handler_param_types = handler.type->Proc.params->Tuple.variables; + Slice handler_capture_param_types = slice(handler_param_types, handler_param_types.count - capture_arg_count, handler_param_types.count); + + for (isize i = 0; i < capture_arg_count; i++) { + Operand op = param_operands[i]; + if (!check_is_assignable_to(c, &op, handler_capture_param_types[i]->type)) { + gbString e = expr_to_string(op.expr); + gbString src = type_to_string(op.type); + gbString dst = type_to_string(handler_capture_param_types[i]->type); + error(op.expr, "'%.*s' captured value '%s' of type '%s' is not assignable to type '%s'", LIT(builtin_name), e, src, dst); + gb_string_free(e); + gb_string_free(src); + gb_string_free(dst); + return false; + } + } + } + + ProcCallingConvention cc = handler_type_proc.calling_convention; + switch (cc) { + case ProcCC_Odin: + case ProcCC_Contextless: + case ProcCC_CDecl: + break; // ok + default: + ERROR_BLOCK(); + + error(handler.expr, "'%.*s' Invalid calling convention for block procedure.", LIT(builtin_name)); + error_line("\tSuggestion: Do not specify a calling convention ot else use \"c\" or \"cotextless\""); + return false; + } + + if (handler_type_proc.is_polymorphic) { + error(handler.expr, "'%.*s' Unspecialized polymorphic procedures are not allowed.", LIT(builtin_name)); + return false; + } + + // Create the specialized Objc_Block type that this intrinsic will return + Token ident = {}; + ident.kind = Token_Ident; + ident.string = str_lit("Objc_Block"); + ident.pos = ast_token(call).pos; + + Token l_paren = {}; + l_paren.kind = Token_OpenParen; + l_paren.string = str_lit("("); + l_paren.pos = ident.pos; + + Token r_paren = {}; + r_paren.kind = Token_CloseParen; + l_paren.string = str_lit(")"); + r_paren.pos = ident.pos; + + // Remove the capture args from the resulting Objc_Block type signature + Ast* handler_proc_type_copy = clone_ast(handler_type_proc.node); + handler_proc_type_copy->ProcType.params->FieldList.list.count -= capture_arg_count; + + // Make sure the Objc_Block's specialized proc is always "c" calling conv, + // even if we have a context, as the invoker is always "c". + // This allows us to have compatibility with the target block types with either calling convention used. + handler_proc_type_copy->ProcType.calling_convention = ProcCC_CDecl; + + Array poly_args = {}; + array_init(&poly_args, permanent_allocator(), 1, 1); + poly_args[0] = handler_proc_type_copy; + + + Type *t_Objc_Block = find_core_type(c->checker, str_lit("Objc_Block")); + Operand poly_op = {}; + poly_op.type = t_Objc_Block; + poly_op.mode = Addressing_Type; + + Ast *poly_call = ast_call_expr(nullptr, ast_ident(nullptr, ident), poly_args, l_paren, r_paren, {}); + + auto err = check_polymorphic_record_type(c, &poly_op, poly_call); + + if (err != 0) { + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + error(handler.expr, "'%.*s' failed to determine resulting Objc_Block handler procedure", LIT(builtin_name)); + return false; + } + + GB_ASSERT(poly_op.type != t_Objc_Block); + GB_ASSERT(poly_op.mode == Addressing_Type); + + bool is_global_block = capture_arg_count == 0 && handler_type_proc.calling_convention != ProcCC_Odin; + if (is_global_block) { + try_to_add_package_dependency(c, "runtime", "_NSConcreteGlobalBlock"); + } else { + try_to_add_package_dependency(c, "runtime", "_NSConcreteStackBlock"); + } + + *operand = poly_op; + operand->type = alloc_type_pointer(operand->type); + operand->mode = Addressing_Value; + return true; + } break; } } @@ -2291,6 +2514,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_objc_register_selector: case BuiltinProc_objc_register_class: case BuiltinProc_objc_ivar_get: + case BuiltinProc_objc_block: return check_builtin_objc_procedure(c, operand, call, id, type_hint); case BuiltinProc___entry_point: diff --git a/src/checker.cpp b/src/checker.cpp index e72061f56..a13290750 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1460,6 +1460,10 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { mpsc_destroy(&i->foreign_decls_to_check); 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); + string_map_destroy(&i->load_file_cache); string_map_destroy(&i->load_directory_cache); map_destroy(&i->load_directory_map); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index b8b105fd2..da5418c1c 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -351,6 +351,7 @@ BuiltinProc__type_end, BuiltinProc_objc_register_selector, BuiltinProc_objc_register_class, BuiltinProc_objc_ivar_get, + BuiltinProc_objc_block, BuiltinProc_constant_utf16_cstring, @@ -711,6 +712,7 @@ 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("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 648e8a732..cc3dcaa4a 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -198,6 +198,7 @@ struct lbModule { StringMap objc_classes; StringMap objc_selectors; StringMap objc_ivars; + isize objc_next_block_id; // Used to name objective-c blocks, per module PtrMap map_cell_info_map; // address of runtime.Map_Info PtrMap map_info_map; // address of runtime.Map_Cell_Info @@ -483,7 +484,10 @@ gb_internal void lb_emit_if(lbProcedure *p, lbValue cond, lbBlock *true_block, l gb_internal void lb_start_block(lbProcedure *p, lbBlock *b); gb_internal lbValue lb_build_call_expr(lbProcedure *p, Ast *expr); - +gb_internal lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name, Type *type); +gb_internal void lb_begin_procedure_body(lbProcedure *p); +gb_internal void lb_end_procedure_body(lbProcedure *p); +gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array const &args, ProcInlining inlining); gb_internal lbAddr lb_find_or_generate_context_ptr(lbProcedure *p); gb_internal lbContextData *lb_push_context_onto_stack(lbProcedure *p, lbAddr ctx); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 8f306b771..c95bb0c29 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3746,6 +3746,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr); 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_constant_utf16_cstring: diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index dcb95a9a2..f7807364a 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2263,6 +2263,397 @@ gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) { return lb_handle_objc_ivar_for_objc_object_pointer(p, self); } +gb_internal void lb_create_objc_block_helper_procs( + lbModule *m, LLVMTypeRef block_lit_type, isize capture_field_offset, + Slice capture_values, Slice objc_object_indices, + lbProcedure *&out_copy_helper, lbProcedure *&out_dispose_helper +) { + gbString copy_helper_name = gb_string_append_fmt(gb_string_make(temporary_allocator(), ""), "__$objc_block_copy_helper_%lld", m->objc_next_block_id); + gbString dispose_helper_name = gb_string_append_fmt(gb_string_make(temporary_allocator(), ""), "__$objc_block_dispose_helper_%lld", m->objc_next_block_id); + + // copy: Block_Literal *dst, Block_Literal *src, i32 field_apropos + // dispose: Block_Literal *src, i32 field_apropos + Type *types[3] = { t_rawptr, t_rawptr, t_i32 }; + + Type *copy_tuple = alloc_type_tuple_from_field_types(types, 3, false, true); + Type *dispose_tuple = alloc_type_tuple_from_field_types(&types[1], 2, false, true); + + Type *copy_proc_type = alloc_type_proc(nullptr, copy_tuple, 3, nullptr, 0, false, ProcCC_CDecl); + Type *dispose_proc_type = alloc_type_proc(nullptr, dispose_tuple, 2, nullptr, 0, false, ProcCC_CDecl); + + lbProcedure *copy_proc = lb_create_dummy_procedure(m, make_string((u8*)copy_helper_name, gb_string_length(copy_helper_name)), copy_proc_type); + lbProcedure *dispose_proc = lb_create_dummy_procedure(m, make_string((u8*)dispose_helper_name, gb_string_length(dispose_helper_name)), dispose_proc_type); + LLVMSetLinkage(copy_proc->value, LLVMPrivateLinkage); + LLVMSetLinkage(dispose_proc->value, LLVMPrivateLinkage); + + + const int BLOCK_FIELD_IS_OBJECT = 3; // id, NSObject, __attribute__((NSObject)), block, ... + const int BLOCK_FIELD_IS_BLOCK = 7; // a block variable + + Type *block_base_type = find_core_type(m->info->checker, str_lit("Objc_Block")); + + auto is_object_objc_block = [](Type *type, Type *block_base_type) -> bool { + + Type *base = base_type(type_deref(type)); + GB_ASSERT(base->kind == Type_Struct); + + while (is_type_polymorphic_record_specialized(base)) { + if (base->Struct.polymorphic_parent) { + base = base->Struct.polymorphic_parent; + + if (base == block_base_type) { + return true; + } + base = base_type(base); + GB_ASSERT(base->kind == Type_Struct); + } + } + + return false; + }; + + lb_begin_procedure_body(copy_proc); + lb_begin_procedure_body(dispose_proc); + { + for (isize object_index : objc_object_indices) { + const auto field_offset = unsigned(capture_field_offset+object_index); + + Type *field_type = capture_values[object_index].type; + LLVMTypeRef field_raw_type = lb_type(m, field_type); + + GB_ASSERT(is_type_objc_object(field_type)); + bool is_block_obj = is_object_objc_block(field_type, block_base_type); + + auto copy_args = array_make(temporary_allocator(), 3, 3); + auto dispose_args = array_make(temporary_allocator(), 2, 2); + + // Copy helper + { + LLVMValueRef dst_field = LLVMBuildStructGEP2(copy_proc->builder, block_lit_type, copy_proc->raw_input_parameters[0], field_offset, ""); + LLVMValueRef src_field = LLVMBuildStructGEP2(copy_proc->builder, block_lit_type, copy_proc->raw_input_parameters[1], field_offset, ""); + + lbValue dst_value = {}, src_value = {}; + dst_value.type = alloc_type_pointer(field_type); + dst_value.value = dst_field; + + src_value.type = field_type; + src_value.value = LLVMBuildLoad2(copy_proc->builder, field_raw_type, src_field, ""); + + copy_args[0] = dst_value; + copy_args[1] = src_value; + copy_args[2] = lb_const_int(m, t_i32, u64(is_block_obj ? BLOCK_FIELD_IS_BLOCK : BLOCK_FIELD_IS_OBJECT)); + + lb_emit_runtime_call(copy_proc, "_Block_object_assign", copy_args); + } + + // Dispose helper + { + LLVMValueRef src_field = LLVMBuildStructGEP2(dispose_proc->builder, block_lit_type, dispose_proc->raw_input_parameters[0], field_offset, ""); + lbValue src_value = {}; + src_value.type = field_type; + src_value.value = LLVMBuildLoad2(dispose_proc->builder, field_raw_type, src_field, ""); + + dispose_args[0] = src_value; + dispose_args[1] = lb_const_int(m, t_i32, u64(is_block_obj ? BLOCK_FIELD_IS_BLOCK : BLOCK_FIELD_IS_OBJECT)); + + lb_emit_runtime_call(dispose_proc, "_Block_object_dispose", dispose_args); + } + } + } + lb_end_procedure_body(copy_proc); + lb_end_procedure_body(dispose_proc); + + + out_copy_helper = copy_proc; + out_dispose_helper = dispose_proc; +} + +gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { + /// #See: https://clang.llvm.org/docs/Block-ABI-Apple.html + /// 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 capture_arg_count = ce->args.count - 1; + + Type *block_result_type = type_of_expr(expr); + GB_ASSERT(block_result_type != nullptr && block_result_type->kind == Type_Pointer); + + LLVMTypeRef lb_type_rawptr = lb_type(m, t_rawptr); + LLVMTypeRef lb_type_i32 = lb_type(m, t_i32); + LLVMTypeRef lb_type_int = lb_type(m, t_int); + + // Build user proc + // Type * user_proc_type = type_of_expr(ce->args[capture_arg_count]); + lbValue user_proc_value = lb_build_expr(p, ce->args[capture_arg_count]); + auto& user_proc = user_proc_value.type->Proc; + GB_ASSERT(user_proc_value.type->kind == Type_Proc); + + const bool is_global = capture_arg_count == 0 && user_proc.calling_convention != ProcCC_Odin; + const isize block_forward_args = user_proc.param_count - capture_arg_count; + const isize capture_fields_offset = user_proc.calling_convention != ProcCC_Odin ? 5 : 6; + + Ast *proc_lit = unparen_expr(ce->args[capture_arg_count]); + if (proc_lit->kind == Ast_Ident) { + proc_lit = proc_lit->Ident.entity->decl_info->proc_lit; + } + GB_ASSERT(proc_lit->kind == Ast_ProcLit); + + lbProcedure *copy_helper = {}, *dispose_helper = {}; + + // Build captured arguments & collect the ones that are Objective-C objects + auto captured_values = array_make(temporary_allocator(), capture_arg_count, capture_arg_count); + auto objc_captures = array_make(temporary_allocator()); + + for (isize i = 0; i < capture_arg_count; i++) { + captured_values[i] = lb_build_expr(p, ce->args[i]); + + if (is_type_pointer(captured_values[i].type) && is_type_objc_object(captured_values[i].type)) { + array_add(&objc_captures, i); + } + } + + const bool has_objc_fields = objc_captures.count > 0; + + + // 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); + + // Add + 1 because the first parameter received is the block literal pointer itself + auto invoker_args = array_make(temporary_allocator(), block_forward_args + 1, block_forward_args + 1); + invoker_args[0] = t_rawptr; + + GB_ASSERT(block_forward_args <= user_proc.param_count); + if (user_proc.param_count > 0) { + Slice user_proc_param_types = user_proc.params->Tuple.variables; + for (isize i = 0; i < block_forward_args; i++) { + invoker_args[i+1] = user_proc_param_types[i]->type; + } + } + + GB_ASSERT(user_proc.result_count <= 1); + + Type *invoker_args_tuple = alloc_type_tuple_from_field_types(invoker_args.data, invoker_args.count, false, true); + Type *invoker_results_tuple = nullptr; + if (user_proc.result_count > 0) { + invoker_results_tuple = alloc_type_tuple_from_field_types(&user_proc.results->Tuple.variables[0]->type, 1, false, true); + } + + Type *invoker_proc_type = alloc_type_proc(nullptr, invoker_args_tuple, invoker_args_tuple->Tuple.variables.count, + invoker_results_tuple, user_proc.result_count, false, ProcCC_CDecl); + + 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); + + // 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_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); + + LLVMTypeRef block_lit_type = {}; + LLVMTypeRef block_desc_type = {}; + LLVMValueRef block_desc_initializer = {}; + + { + block_desc_type = LLVMStructCreateNamed(m->ctx, block_desc_type_name); + + LLVMTypeRef fields_types[4] = { + lb_type_int, // Reserved + lb_type_int, // Block size + lb_type_rawptr, // Copy helper func pointer + lb_type_rawptr, // Dispose helper func pointer + }; + + LLVMStructSetBody(block_desc_type, fields_types, has_objc_fields ? 4 : 2, false); + } + + { + block_lit_type = LLVMStructCreateNamed(m->ctx, block_lit_type_name); + + auto fields = array_make(temporary_allocator()); + + array_add(&fields, lb_type_rawptr); // isa + array_add(&fields, lb_type_i32); // flags + array_add(&fields, lb_type_i32); // reserved + array_add(&fields, lb_type_rawptr); // invoke + array_add(&fields, block_desc_type); // descriptor + + if (user_proc.calling_convention == ProcCC_Odin) { + array_add(&fields, lb_type(m, t_context)); // context + } + + // From here on, fields for the captured vars are added + for (lbValue cap_arg : captured_values) { + array_add(&fields, lb_type(m, cap_arg.type)); + } + + LLVMStructSetBody(block_lit_type, fields.data, (unsigned)fields.count, false); + } + + // 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, + slice(captured_values, 0, captured_values.count), + slice(objc_captures, 0, objc_captures.count), + copy_helper, dispose_helper); + } + + { + LLVMValueRef fields_values[4] = { + lb_const_int(m, t_int, 0).value, // Reserved + lb_const_int(m, t_int, u64(lb_sizeof(block_lit_type))).value, // Block size + has_objc_fields ? copy_helper->value : nullptr, // Copy helper + has_objc_fields ? dispose_helper->value : nullptr, // Dispose helper + }; + + block_desc_initializer = LLVMConstNamedStruct(block_desc_type, fields_values, has_objc_fields ? 4 : 2); + } + + // 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); + + LLVMValueRef p_descriptor = LLVMAddGlobal(m->mod, block_desc_type, desc_global_name); + LLVMSetInitializer(p_descriptor, block_desc_initializer); + + + /// Invoker body + lb_begin_procedure_body(invoker_proc); + { + auto call_args = array_make(temporary_allocator(), user_proc.param_count, user_proc.param_count); + + 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; + } + + LLVMValueRef block_literal = invoker_proc->raw_input_parameters[0]; + + // Push context, if needed + if (user_proc.calling_convention == ProcCC_Odin) { + LLVMValueRef p_context = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, 5, "context"); + lbValue ctx_val = {}; + ctx_val.type = t_context_ptr; + ctx_val.value = p_context; + + lb_push_context_onto_stack(invoker_proc, lb_addr(ctx_val)); + } + + // Copy capture parameters from the block literal + for (isize i = 0; i < capture_arg_count; i++) { + LLVMValueRef cap_value = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, unsigned(capture_fields_offset + i), ""); + + lbValue cap_arg = {}; + cap_arg.value = cap_value; + cap_arg.type = alloc_type_pointer(captured_values[i].type); + + lbValue arg = lb_emit_load(invoker_proc, cap_arg); + call_args[block_forward_args+i] = arg; + } + + lbValue result = lb_emit_call(invoker_proc, user_proc_value, call_args, proc_lit->ProcLit.inlining); + + GB_ASSERT(user_proc.result_count <= 1); + if (user_proc.result_count > 0) { + GB_ASSERT(result.value != nullptr); + LLVMBuildRet(p->builder, result.value); + } + } + lb_end_procedure_body(invoker_proc); + + + /// Create local block literal + const int BLOCK_HAS_COPY_DISPOSE = (1 << 25); + const int BLOCK_IS_GLOBAL = (1 << 28); + + int raw_flags = is_global ? BLOCK_IS_GLOBAL : 0; + if (has_objc_fields) { + raw_flags |= BLOCK_HAS_COPY_DISPOSE; + } + + 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); + + lbValue result = {}; + 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); + lbValue reserved_val = lb_const_int(m, t_i32, 0); + + if (is_global) { + LLVMValueRef p_block_lit = LLVMAddGlobal(m->mod, block_lit_type, block_var_name); + result.value = p_block_lit; + + LLVMValueRef fields_values[5] = { + isa_val.value, // isa + flags_val.value, // flags + reserved_val.value, // reserved + invoker_proc->value, // invoke + p_descriptor // descriptor + }; + + LLVMValueRef g_block_lit_initializer = LLVMConstNamedStruct(block_lit_type, fields_values, gb_count_of(fields_values)); + LLVMSetInitializer(p_block_lit, g_block_lit_initializer); + + } else { + LLVMValueRef p_block_lit = llvm_alloca(p, block_lit_type, lb_alignof(block_lit_type), block_var_name); + result.value = p_block_lit; + + // Initialize it + LLVMValueRef f_isa = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 0, "isa"); + LLVMValueRef f_flags = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 1, "flags"); + LLVMValueRef f_reserved = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 2, "reserved"); + LLVMValueRef f_invoke = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 3, "invoke"); + LLVMValueRef f_descriptor = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 4, "descriptor"); + + LLVMBuildStore(p->builder, isa_val.value, f_isa); + LLVMBuildStore(p->builder, flags_val.value, f_flags); + LLVMBuildStore(p->builder, reserved_val.value, f_reserved); + LLVMBuildStore(p->builder, invoker_proc->value, f_invoke); + LLVMBuildStore(p->builder, p_descriptor, f_descriptor); + + // Store current context, if there is one + if (user_proc.calling_convention == ProcCC_Odin) { + LLVMValueRef f_context = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 5, "context"); + lbAddr p_current_context = lb_find_or_generate_context_ptr(p); + + LLVMValueRef context_size = LLVMConstInt(LLVMInt64TypeInContext(m->ctx), (u64)lb_sizeof(lb_type(m, t_context)), false); + LLVMBuildMemCpy(p->builder, f_context, lb_try_get_alignment(f_context, 1), + p_current_context.addr.value, lb_try_get_alignment(p_current_context.addr.value, 1), context_size); + } + + // Store captured args into the block + for (isize i = 0; i < captured_values.count; i++) { + lbValue capture_arg = captured_values[i]; + + unsigned field_index = unsigned(capture_fields_offset + i); + LLVMValueRef f_capture = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, field_index, "capture_arg"); + + lbValue f_capture_val = {}; + f_capture_val.type = alloc_type_pointer(capture_arg.type); + f_capture_val.value = f_capture; + + lb_emit_store(p, f_capture_val, capture_arg); + } + } + + return result; +} + gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { ast_node(ce, CallExpr, expr); -- cgit v1.2.3 From 53f4fc1cbbd58396241264785dc1c8a75798f062 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 14:03:32 +0100 Subject: Add `-para-poly-diagnostics` --- src/build_settings.cpp | 2 + src/check_expr.cpp | 1 + src/checker.hpp | 2 + src/llvm_backend.cpp | 4 + src/llvm_backend_utility.cpp | 180 +++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 7 ++ 6 files changed, 196 insertions(+) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index a8d74d1da..abf8e6809 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -548,6 +548,8 @@ struct BuildContext { bool ignore_microsoft_magic; bool linker_map_file; + bool para_poly_diagnostics; + bool use_single_module; bool use_separate_modules; bool module_per_file; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a59f145c7..2a22e5c48 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -587,6 +587,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E d->proc_lit = proc_lit; d->proc_checked_state = ProcCheckedState_Unchecked; d->defer_use_checked = false; + d->para_poly_original = old_decl->entity; Entity *entity = alloc_entity_procedure(nullptr, token, final_proc_type, tags); entity->state.store(EntityState_Resolved); diff --git a/src/checker.hpp b/src/checker.hpp index 9693c3e38..bda7b2746 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -218,6 +218,8 @@ struct DeclInfo { Ast * proc_lit; // Ast_ProcLit Type * gen_proc_type; // Precalculated + Entity * para_poly_original; + bool is_using; bool where_clauses_evaluated; bool foreign_require_results; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index ec9630a47..b47e2788f 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3556,6 +3556,10 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Correct Entity Linkage"); lb_correct_entity_linkage(gen); + if (build_context.para_poly_diagnostics) { + lb_do_para_poly_diagnostics(gen); + } + llvm_error = nullptr; defer (LLVMDisposeMessage(llvm_error)); diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index f7807364a..fd3214f24 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2787,3 +2787,183 @@ gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(Ast *expr) { ExactValue value = type_and_value_of_expr(expr).value; return llvm_atomic_ordering_from_odin(value); } + + + +struct lbParaPolyEntry { + Entity *entity; + String canonical_name; + isize count; + isize total_code_size; +}; + +gb_internal isize lb_total_code_size(lbProcedure *p) { + isize instruction_count = 0; + + LLVMBasicBlockRef first = LLVMGetFirstBasicBlock(p->value); + + for (LLVMBasicBlockRef block = first; block != nullptr; block = LLVMGetNextBasicBlock(block)) { + for (LLVMValueRef instr = LLVMGetFirstInstruction(block); instr != nullptr; instr = LLVMGetNextInstruction(instr)) { + instruction_count += 1; + } + } + return instruction_count; + +} + +gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { + PtrMap procs = {}; + map_init(&procs); + defer (map_destroy(&procs)); + + for (auto &entry : gen->modules) { + lbModule *m = entry.value; + for (lbProcedure *p : m->generated_procedures) { + Entity *e = p->entity; + if (e == nullptr) { + continue; + } + if (p->builder == nullptr) { + continue; + } + + DeclInfo *d = e->decl_info; + Entity *para_poly_parent = d->para_poly_original; + if (para_poly_parent == nullptr) { + continue; + } + + lbParaPolyEntry *entry = map_get(&procs, para_poly_parent); + if (entry == nullptr) { + lbParaPolyEntry entry = {}; + entry.entity = para_poly_parent; + entry.count = 0; + + + gbString w = string_canonical_entity_name(permanent_allocator(), entry.entity); + String name = make_string_c(w); + + for (isize i = 0; i < name.len; i++) { + String s = substring(name, i, name.len); + if (string_starts_with(s, str_lit(":proc"))) { + name = substring(name, 0, i); + break; + } + } + + entry.canonical_name = name; + + map_set(&procs, para_poly_parent, entry); + } + entry = map_get(&procs, para_poly_parent); + GB_ASSERT(entry != nullptr); + entry->count += 1; + entry->total_code_size += lb_total_code_size(p); + } + } + + + auto entries = array_make(heap_allocator(), 0, procs.count); + defer (array_free(&entries)); + + for (auto &entry : procs) { + array_add(&entries, entry.value); + } + + array_sort(entries, [](void const *a, void const *b) -> int { + lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; + lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + if (x->total_code_size > y->total_code_size) { + return -1; + } + if (x->total_code_size < y->total_code_size) { + return +1; + } + return string_compare(x->canonical_name, y->canonical_name); + }); + + + gb_printf("Parametric Polymorphic Procedure Diagnostics\n"); + gb_printf("------------------------------------------------------------------------------------------\n"); + + gb_printf("Sorted by Total Instruction Count Descending (Top 100)\n\n"); + gb_printf("Total Instruction Count | Instantiation Count | Average Instruction Count | Procedure Name\n"); + + isize max_count = 100; + for (auto &entry : entries) { + isize code_size = entry.total_code_size; + isize count = entry.count; + String name = entry.canonical_name; + + f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); + + gb_printf("%23td | %19d | %25.2f | %.*s\n", code_size, count, average, LIT(name)); + if (max_count-- <= 0) { + break; + } + } + + gb_printf("------------------------------------------------------------------------------------------\n"); + + array_sort(entries, [](void const *a, void const *b) -> int { + lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; + lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + if (x->count > y->count) { + return -1; + } + if (x->count < y->count) { + return +1; + } + + return string_compare(x->canonical_name, y->canonical_name); + }); + + gb_printf("Sorted by Total Instantiation Count Descending (Top 100)\n\n"); + gb_printf("Instantiation Count | Total Instruction Count | Average Instruction Count | Procedure Name\n"); + + max_count = 100; + for (auto &entry : entries) { + isize code_size = entry.total_code_size; + isize count = entry.count; + String name = entry.canonical_name; + + + f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); + + gb_printf("%19d | %23td | %25.2f | %.*s\n", count, code_size, average, LIT(name)); + if (max_count-- <= 0) { + break; + } + } + + gb_printf("------------------------------------------------------------------------------------------\n"); + + + array_sort(entries, [](void const *a, void const *b) -> int { + lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; + lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + if (x->count < y->count) { + return -1; + } + if (x->count > y->count) { + return +1; + } + + return string_compare(x->canonical_name, y->canonical_name); + }); + + gb_printf("Single Instanced Parametric Polymorphic Procedures\n\n"); + gb_printf("Instruction Count | Procedure Name\n"); + for (auto &entry : entries) { + isize code_size = entry.total_code_size; + isize count = entry.count; + String name = entry.canonical_name; + if (count != 1) { + break; + } + + gb_printf("%17td | %.*s\n", code_size, LIT(name)); + } + + gb_printf("------------------------------------------------------------------------------------------\n"); +} diff --git a/src/main.cpp b/src/main.cpp index acc4773c0..707b85232 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -394,6 +394,8 @@ enum BuildFlagKind { BuildFlag_IntegerDivisionByZero, + BuildFlag_ParaPolyDiagnostics, + // internal use only BuildFlag_InternalFastISel, BuildFlag_InternalIgnoreLazy, @@ -619,6 +621,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_IntegerDivisionByZero, str_lit("integer-division-by-zero"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_ParaPolyDiagnostics, str_lit("para-poly-diagnostics"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_InternalFastISel, str_lit("internal-fast-isel"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all); @@ -1562,6 +1565,10 @@ gb_internal bool parse_build_flags(Array args) { } break; + case BuildFlag_ParaPolyDiagnostics: + build_context.para_poly_diagnostics = true; + break; + case BuildFlag_InternalFastISel: build_context.fast_isel = true; break; -- cgit v1.2.3 From 1fc1d8e451d5346d35c4a6618a24414e1a290edd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 14:32:21 +0100 Subject: Change sort for single instanced procedures --- src/llvm_backend_utility.cpp | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index fd3214f24..7fe6b1458 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2949,6 +2949,13 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { return +1; } + if (x->total_code_size > y->total_code_size) { + return -1; + } + if (x->total_code_size < y->total_code_size) { + return +1; + } + return string_compare(x->canonical_name, y->canonical_name); }); -- cgit v1.2.3 From 11712627cbb95e3ae15d6a8e2c5863e192caa68a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 16:14:55 +0100 Subject: Add module stuff to `-para-poly-diagnostics` --- src/llvm_backend.cpp | 37 +++++++++----- src/llvm_backend_utility.cpp | 112 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 126 insertions(+), 23 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index b47e2788f..bbfd91d30 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3245,10 +3245,6 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { continue; } - // if (!ptr_set_exists(min_dep_set, e)) { - // continue; - // } - DeclInfo *decl = decl_info_of_entity(e); if (decl == nullptr) { continue; @@ -3259,8 +3255,16 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { bool is_foreign = e->Variable.is_foreign; bool is_export = e->Variable.is_export; + lbModule *default_module = &gen->default_module; + + lbModule *m = default_module; + lbModule *e_module = lb_module_of_entity(gen, e, default_module); + + bool const split_globals_across_modules = false; + if (split_globals_across_modules) { + m = e_module; + } - lbModule *m = &gen->default_module; String name = lb_get_entity_name(m, e); lbGlobalVariable var = {}; @@ -3361,15 +3365,26 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { } } - g.value = LLVMConstPointerCast(g.value, lb_type(m, alloc_type_pointer(e->type))); + if (default_module == m) { + g.value = LLVMConstPointerCast(g.value, lb_type(m, alloc_type_pointer(e->type))); - var.var = g; - array_add(&global_variables, var); + var.var = g; + array_add(&global_variables, var); + } else { + lbValue local_g = {}; + local_g.type = alloc_type_pointer(e->type); + local_g.value = LLVMAddGlobal(default_module->mod, lb_type(default_module, e->type), alloc_cstring(permanent_allocator(), name)); + LLVMSetLinkage(local_g.value, LLVMExternalLinkage); - lb_add_entity(m, e, g); - lb_add_member(m, name, g); + var.var = local_g; + array_add(&global_variables, var); + lb_add_entity(default_module, e, local_g); + lb_add_member(default_module, name, local_g); + } + lb_add_entity(m, e, g); + lb_add_member(m, name, g); } if (build_context.ODIN_DEBUG) { @@ -3557,7 +3572,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lb_correct_entity_linkage(gen); if (build_context.para_poly_diagnostics) { - lb_do_para_poly_diagnostics(gen); + lb_do_code_gen_diagnostics(gen); } llvm_error = nullptr; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 7fe6b1458..244ad6d73 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2790,7 +2790,7 @@ gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(Ast *expr) { -struct lbParaPolyEntry { +struct lbDiagParaPolyEntry { Entity *entity; String canonical_name; isize count; @@ -2812,7 +2812,7 @@ gb_internal isize lb_total_code_size(lbProcedure *p) { } gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { - PtrMap procs = {}; + PtrMap procs = {}; map_init(&procs); defer (map_destroy(&procs)); @@ -2833,9 +2833,9 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { continue; } - lbParaPolyEntry *entry = map_get(&procs, para_poly_parent); + lbDiagParaPolyEntry *entry = map_get(&procs, para_poly_parent); if (entry == nullptr) { - lbParaPolyEntry entry = {}; + lbDiagParaPolyEntry entry = {}; entry.entity = para_poly_parent; entry.count = 0; @@ -2863,7 +2863,7 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { } - auto entries = array_make(heap_allocator(), 0, procs.count); + auto entries = array_make(heap_allocator(), 0, procs.count); defer (array_free(&entries)); for (auto &entry : procs) { @@ -2871,8 +2871,8 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { } array_sort(entries, [](void const *a, void const *b) -> int { - lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; - lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + lbDiagParaPolyEntry *x = cast(lbDiagParaPolyEntry *)a; + lbDiagParaPolyEntry *y = cast(lbDiagParaPolyEntry *)b; if (x->total_code_size > y->total_code_size) { return -1; } @@ -2906,8 +2906,8 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { gb_printf("------------------------------------------------------------------------------------------\n"); array_sort(entries, [](void const *a, void const *b) -> int { - lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; - lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + lbDiagParaPolyEntry *x = cast(lbDiagParaPolyEntry *)a; + lbDiagParaPolyEntry *y = cast(lbDiagParaPolyEntry *)b; if (x->count > y->count) { return -1; } @@ -2940,8 +2940,8 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { array_sort(entries, [](void const *a, void const *b) -> int { - lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; - lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + lbDiagParaPolyEntry *x = cast(lbDiagParaPolyEntry *)a; + lbDiagParaPolyEntry *y = cast(lbDiagParaPolyEntry *)b; if (x->count < y->count) { return -1; } @@ -2972,5 +2972,93 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { gb_printf("%17td | %.*s\n", code_size, LIT(name)); } - gb_printf("------------------------------------------------------------------------------------------\n"); } + +struct lbDiagModuleEntry { + lbModule *m; + String name; + isize global_internal_count; + isize global_external_count; + isize proc_internal_count; + isize proc_external_count; + isize total_instruction_count; +}; + +gb_internal void lb_do_module_diagnostics(lbGenerator *gen) { + Array modules = {}; + array_init(&modules, heap_allocator()); + defer (array_free(&modules)); + + for (auto &entry : gen->modules) { + lbModule *m = entry.value; + + { + lbDiagModuleEntry entry = {}; + entry.m = m; + entry.name = make_string_c(m->module_name); + array_add(&modules, entry); + } + lbDiagModuleEntry &entry = modules[modules.count-1]; + + for (LLVMValueRef p = LLVMGetFirstFunction(m->mod); p != nullptr; p = LLVMGetNextFunction(p)) { + LLVMBasicBlockRef block = LLVMGetFirstBasicBlock(p); + if (block == nullptr) { + entry.proc_external_count += 1; + } else { + entry.proc_internal_count += 1; + + for (; block != nullptr; block = LLVMGetNextBasicBlock(block)) { + for (LLVMValueRef i = LLVMGetFirstInstruction(block); i != nullptr; i = LLVMGetNextInstruction(i)) { + entry.total_instruction_count += 1; + } + } + } + } + + for (LLVMValueRef g = LLVMGetFirstGlobal(m->mod); g != nullptr; g = LLVMGetNextGlobal(g)) { + LLVMLinkage linkage = LLVMGetLinkage(g); + if (linkage == LLVMExternalLinkage) { + entry.global_external_count += 1; + } else { + entry.global_internal_count += 1; + } + } + } + + array_sort(modules, [](void const *a, void const *b) -> int { + lbDiagModuleEntry *x = cast(lbDiagModuleEntry *)a; + lbDiagModuleEntry *y = cast(lbDiagModuleEntry *)b; + + if (x->total_instruction_count > y->total_instruction_count) { + return -1; + } + if (x->total_instruction_count < y->total_instruction_count) { + return +1; + } + + return string_compare(x->name, y->name); + }); + + gb_printf("Module Diagnostics\n\n"); + gb_printf("Total Instructions | Global Internals | Global Externals | Proc Internals | Proc Externals | Module Name\n"); + for (auto &entry : modules) { + gb_printf("%18td | %16td | %16td | %14td | %14d | %s \n", + entry.total_instruction_count, + entry.global_internal_count, + entry.global_external_count, + entry.proc_internal_count, + entry.proc_external_count, + entry.m->module_name); + } + + +} + +gb_internal void lb_do_code_gen_diagnostics(lbGenerator *gen) { + lb_do_para_poly_diagnostics(gen); + gb_printf("------------------------------------------------------------------------------------------\n"); + gb_printf("------------------------------------------------------------------------------------------\n\n"); + lb_do_module_diagnostics(gen); + gb_printf("------------------------------------------------------------------------------------------\n"); + gb_printf("------------------------------------------------------------------------------------------\n\n"); +} \ No newline at end of file -- cgit v1.2.3 From 9e8be055c1152bd42f533f544308355de87a361e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 16:16:19 +0100 Subject: Rename to `-build-diagnostics` --- src/build_settings.cpp | 2 +- src/llvm_backend.cpp | 4 ++-- src/llvm_backend_utility.cpp | 2 +- src/main.cpp | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index abf8e6809..53953600e 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -548,7 +548,7 @@ struct BuildContext { bool ignore_microsoft_magic; bool linker_map_file; - bool para_poly_diagnostics; + bool build_diagnostics; bool use_single_module; bool use_separate_modules; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index bbfd91d30..ab0811085 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3571,8 +3571,8 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Correct Entity Linkage"); lb_correct_entity_linkage(gen); - if (build_context.para_poly_diagnostics) { - lb_do_code_gen_diagnostics(gen); + if (build_context.build_diagnostics) { + lb_do_build_diagnostics(gen); } llvm_error = nullptr; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 244ad6d73..fa8f85d0f 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -3054,7 +3054,7 @@ gb_internal void lb_do_module_diagnostics(lbGenerator *gen) { } -gb_internal void lb_do_code_gen_diagnostics(lbGenerator *gen) { +gb_internal void lb_do_build_diagnostics(lbGenerator *gen) { lb_do_para_poly_diagnostics(gen); gb_printf("------------------------------------------------------------------------------------------\n"); gb_printf("------------------------------------------------------------------------------------------\n\n"); diff --git a/src/main.cpp b/src/main.cpp index 707b85232..6ad20cf26 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -394,7 +394,7 @@ enum BuildFlagKind { BuildFlag_IntegerDivisionByZero, - BuildFlag_ParaPolyDiagnostics, + BuildFlag_BuildDiagnostics, // internal use only BuildFlag_InternalFastISel, @@ -621,7 +621,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_IntegerDivisionByZero, str_lit("integer-division-by-zero"), BuildFlagParam_String, Command__does_check); - add_flag(&build_flags, BuildFlag_ParaPolyDiagnostics, str_lit("para-poly-diagnostics"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_BuildDiagnostics, str_lit("build-diagnostics"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_InternalFastISel, str_lit("internal-fast-isel"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all); @@ -1565,8 +1565,8 @@ gb_internal bool parse_build_flags(Array args) { } break; - case BuildFlag_ParaPolyDiagnostics: - build_context.para_poly_diagnostics = true; + case BuildFlag_BuildDiagnostics: + build_context.build_diagnostics = true; break; case BuildFlag_InternalFastISel: -- cgit v1.2.3 From 34d040cef18635b95f5bc13bef793c35ff0647ed Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 16:51:38 +0100 Subject: Correct format strings --- src/llvm_backend_general.cpp | 29 ++++++++++++++++++++++++++--- src/llvm_backend_utility.cpp | 26 ++++++++++++++++++++------ 2 files changed, 46 insertions(+), 9 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 123af51f5..39cf70a6a 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -186,9 +186,32 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { lb_init_module(pm, do_threading); } - if (pkg->kind == Package_Runtime) { - // allow this to be per file - } else if (!module_per_file) { + bool allow_for_per_file = pkg->kind == Package_Runtime || module_per_file; + + #if 0 + if (!allow_for_per_file) { + if (pkg->files.count >= 20) { + isize proc_count = 0; + for (Entity *e : gen->info->entities) { + if (e->kind != Entity_Procedure) { + continue; + } + if (e->Procedure.is_foreign) { + continue; + } + if (e->pkg == pkg) { + proc_count += 1; + } + } + + if (proc_count >= 300) { + allow_for_per_file = true; + } + } + } + #endif + + if (!allow_for_per_file) { continue; } // NOTE(bill): Probably per file is not a good idea, so leave this for later diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index fa8f85d0f..155c55675 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2897,7 +2897,7 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); - gb_printf("%23td | %19d | %25.2f | %.*s\n", code_size, count, average, LIT(name)); + gb_printf("%23td | %19td | %25.2f | %.*s\n", code_size, count, average, LIT(name)); if (max_count-- <= 0) { break; } @@ -2930,7 +2930,7 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); - gb_printf("%19d | %23td | %25.2f | %.*s\n", count, code_size, average, LIT(name)); + gb_printf("%19td | %23td | %25.2f | %.*s\n", count, code_size, average, LIT(name)); if (max_count-- <= 0) { break; } @@ -2989,8 +2989,8 @@ gb_internal void lb_do_module_diagnostics(lbGenerator *gen) { array_init(&modules, heap_allocator()); defer (array_free(&modules)); - for (auto &entry : gen->modules) { - lbModule *m = entry.value; + for (auto &em : gen->modules) { + lbModule *m = em.value; { lbDiagModuleEntry entry = {}; @@ -3040,14 +3040,28 @@ gb_internal void lb_do_module_diagnostics(lbGenerator *gen) { }); gb_printf("Module Diagnostics\n\n"); - gb_printf("Total Instructions | Global Internals | Global Externals | Proc Internals | Proc Externals | Module Name\n"); + gb_printf("Total Instructions | Global Internals | Global Externals | Proc Internals | Proc Externals | Files | Instructions/File | Instructions/Proc | Module Name\n"); + gb_printf("-------------------+------------------+------------------+----------------+----------------+-------+-------------------+-------------------+------------\n"); for (auto &entry : modules) { - gb_printf("%18td | %16td | %16td | %14td | %14d | %s \n", + isize file_count = 1; + if (entry.m->file != nullptr) { + file_count = 1; + } else if (entry.m->pkg) { + file_count = entry.m->pkg->files.count; + } + + f64 instructions_per_file = cast(f64)entry.total_instruction_count / gb_max(1.0, cast(f64)file_count); + f64 instructions_per_proc = cast(f64)entry.total_instruction_count / gb_max(1.0, cast(f64)entry.proc_internal_count); + + gb_printf("%18td | %16td | %16td | %14td | %14td | %5td | %17.1f | %17.1f | %s \n", entry.total_instruction_count, entry.global_internal_count, entry.global_external_count, entry.proc_internal_count, entry.proc_external_count, + file_count, + instructions_per_file, + instructions_per_proc, entry.m->module_name); } -- cgit v1.2.3 From 5af13f5d53b4e5f5d472cd8a8bc4444f05ea36d6 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Tue, 16 Sep 2025 00:49:31 -0400 Subject: Automatically emit objc_msgSend calls when calling imported or implemented Objective-C methods - Add intrinsics.objc_super() - Emit objc_msgSendSuper2 calls when an objc method call is combined with objc_super(self) - Fix objc_block return value ABI for large struct returns - Fix objc_implement method wrappers bad ABI for large struct returns and indirect args - Simplify parameter forwarding for objc_imlpement methods - Add intrinsics.objc_instancetype to mimi Objective-C instancetype* returns This facilitates returning the correct type on subclasses when calling mehtods such as `alloc`, `init`, `retain`, etc. - Refactor Objective-C class implementations generation so that hierarchies are properly initialized - Better codegen for context passing with ivar-based autocontext - Allow @superclass on imported objc-c objects - Better codegen for block forwarding invoker, arguments are forwarded directly --- base/intrinsics/intrinsics.odin | 10 +- base/runtime/procs_darwin.odin | 16 ++- src/check_builtin.cpp | 59 ++++++++++- src/check_decl.cpp | 122 +++++++++++++++-------- src/check_expr.cpp | 73 ++++++++++++++ src/checker.cpp | 8 ++ src/checker_builtin_procs.hpp | 4 +- src/entity.cpp | 4 + src/llvm_backend.cpp | 170 ++++++++++++++++++++++---------- src/llvm_backend_proc.cpp | 22 +++-- src/llvm_backend_utility.cpp | 212 ++++++++++++++++++++++++++++++++++------ src/parser.hpp | 3 +- src/types.cpp | 11 +++ 13 files changed, 569 insertions(+), 145 deletions(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 2d940cf67..dd508180d 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -374,10 +374,11 @@ objc_selector :: struct{} objc_class :: struct{} objc_ivar :: struct{} -objc_id :: ^objc_object -objc_SEL :: ^objc_selector -objc_Class :: ^objc_class -objc_Ivar :: ^objc_ivar +objc_id :: ^objc_object +objc_SEL :: ^objc_selector +objc_Class :: ^objc_class +objc_Ivar :: ^objc_ivar +objc_instancetype :: distinct objc_id objc_find_selector :: proc($name: string) -> objc_SEL --- objc_register_selector :: proc($name: string) -> objc_SEL --- @@ -385,6 +386,7 @@ objc_find_class :: proc($name: string) -> objc_Class --- objc_register_class :: proc($name: string) -> objc_Class --- objc_ivar_get :: proc(self: ^$T) -> ^$U --- objc_block :: proc(invoke: $T, ..any) -> ^Objc_Block(T) where type_is_proc(T) --- +objc_super :: proc(obj: ^$T) -> ^$U where type_is_subtype_of(T, objc_object) && type_is_subtype_of(U, objc_object) --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin index d176f0f63..cc3dabc9b 100644 --- a/base/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -15,16 +15,23 @@ 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) --- + objc_msgSendSuper2 :: proc "c" (super: ^objc_super, op: objc_SEL, #c_vararg args: ..any) --- + 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 +40,7 @@ foreign ObjC { class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL --- class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar --- class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint --- + class_getSuperclass :: proc "c" (cls : objc_Class) -> objc_Class --- ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr --- object_getClass :: proc "c" (obj: objc_id) -> objc_Class --- } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 7e1567750..f142f04b7 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -210,7 +210,7 @@ gb_internal ObjcMsgKind get_objc_proc_kind(Type *return_type) { return ObjcMsg_normal; } -gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice param_types) { +void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice param_types) { ObjcMsgKind kind = get_objc_proc_kind(return_type); Scope *scope = create_scope(c->info, nullptr); @@ -248,6 +248,12 @@ gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_t try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret"); try_to_add_package_dependency(c, "runtime", "objc_msgSend_fp2ret"); try_to_add_package_dependency(c, "runtime", "objc_msgSend_stret"); + + Slice args = call->CallExpr.args; + if (args.count > 0 && args[0]->tav.objc_super_target) { + try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2"); + try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2_stret"); + } } gb_internal bool is_constant_string(CheckerContext *c, String const &builtin_name, Ast *expr, String *name_) { @@ -466,8 +472,8 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan isize capture_arg_count = ce->args.count - 1; - // NOTE(harold): The first parameter is already checked at check_builtin_procedure(). - // Checking again would invalidate the Entity -> Value map for direct parameters if it's the handler proc. + // NOTE(harold): The first argument is already checked at check_builtin_procedure(). + // Checking again would invalidate the Entity -> Value map for direct arguments if it's the handler proc. param_operands[0] = *operand; for (isize i = 0; i < ce->args.count-1; i++) { @@ -680,6 +686,52 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan operand->mode = Addressing_Value; return true; } break; + + case BuiltinProc_objc_super: + { + // Must be a pointer to an Objective-C object. + Type *objc_obj = operand->type; + if (!is_type_objc_ptr_to_object(objc_obj)) { + gbString e = expr_to_string(operand->expr); + gbString t = type_to_string(objc_obj); + error(operand->expr, "'%.*s' expected a pointer to an Objective-C object, but got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (operand->mode != Addressing_Value && operand->mode != Addressing_Variable) { + gbString e = expr_to_string(operand->expr); + gbString t = type_to_string(operand->type); + error(operand->expr, "'%.*s' expression '%s', of type %s, must be a value or variable.", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + Type *obj_type = type_deref(objc_obj); + GB_ASSERT(obj_type->kind == Type_Named); + + // NOTE(harold) Track original type before transforming it to the superclass. + // This is needed because objc_msgSendSuper2 must start its search on the subclass, not the superclass. + call->tav.objc_super_target = obj_type; + + // The superclass type must be known at compile time. We require this so that the selector method expressions + // methods are resolved to the superclass's methods instead of the subclass's. + Type *superclass = obj_type->Named.type_name->TypeName.objc_superclass; + if (superclass == nullptr) { + gbString t = type_to_string(obj_type); + error(operand->expr, "'%.*s' target object '%.*s' does not have an Objective-C superclass. One must be set via the @(objc_superclass) attribute", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + GB_ASSERT(superclass->Named.type_name->TypeName.objc_class_name.len > 0); + + operand->type = alloc_type_pointer(superclass); + return true; + + } break; } } @@ -2515,6 +2567,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_objc_register_class: case BuiltinProc_objc_ivar_get: case BuiltinProc_objc_block: + case BuiltinProc_objc_super: return check_builtin_objc_procedure(c, operand, call, id, type_hint); case BuiltinProc___entry_point: diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 842f8653c..113c1e171 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -587,9 +587,7 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, super = named_type->Named.type_name->TypeName.objc_superclass; } } else { - if (ac.objc_superclass != nullptr) { - error(e->token, "@(objc_superclass) may only be applied when the @(obj_implement) attribute is also applied"); - } else if (ac.objc_ivar != nullptr) { + if (ac.objc_ivar != nullptr) { error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied"); } else if (ac.objc_context_provider != nullptr) { error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied"); @@ -1040,61 +1038,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* method_list = map_get(&info->objc_method_implementations, t); - if (method_list) { - array_add(method_list, method); - } else { - auto list = array_make(permanent_allocator(), 1, 8); - list[0] = method; + Array* method_list = map_get(&info->objc_method_implementations, t); + if (method_list) { + array_add(method_list, method); + } else { + auto list = array_make(permanent_allocator(), 1, 8); + list[0] = method; - map_set(&info->objc_method_implementations, t, list); + 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)); @@ -1479,7 +1516,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 { @@ -1527,6 +1564,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { // NOTE(bill): this must be delayed because the foreign import paths might not be evaluated yet until much later mpsc_enqueue(&ctx->info->foreign_decls_to_check, e); } else { + // TODO(harold): Check if it's an objective-C foreign, if so, I don't think we need to check it. check_foreign_procedure(ctx, e, d); } } else { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2a22e5c48..5f36bf3a1 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8151,6 +8151,73 @@ gb_internal ExprKind check_call_expr_as_type_cast(CheckerContext *c, Operand *op } +void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice param_types); + +gb_internal void check_objc_call_expr(CheckerContext *c, Operand *operand, Ast *call, Entity *proc_entity, Type *proc_type) { + auto &proc = proc_type->Proc; + Slice params = proc.params ? proc.params->Tuple.variables : Slice{}; + + Type *self_type = nullptr; + isize params_start = 1; + + ast_node(ce, CallExpr, call); + + Type *return_type = proc.result_count == 0 ? nullptr : proc.results->Tuple.variables[0]->type; + bool is_return_instancetype = return_type != nullptr && return_type == t_objc_instancetype; + + if (params.count == 0 || !is_type_objc_ptr_to_object(params[0]->type)) { + if (!proc_entity->Procedure.is_objc_class_method) { + // Not a class method, invalid call + error(call, "Invalid Objective-C call: The Objective-C method is not a class method but this first parameter is not an Objective-C object pointer."); + return; + } + + if (is_return_instancetype) { + if (ce->proc->kind == Ast_SelectorExpr) { + ast_node(se, SelectorExpr, ce->proc); + + // NOTE(harold): These should have already been checked, right? + GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named); + + return_type = alloc_type_pointer(se->expr->tav.type); + } else { + return_type = proc_entity->Procedure.objc_class->type; + } + } + + self_type = t_objc_Class; + params_start = 0; + } else if (ce->args.count > 0) { + GB_ASSERT(is_type_objc_ptr_to_object(params[0]->type)); + + if (ce->args[0]->tav.objc_super_target) { + self_type = t_objc_super_ptr; + } else { + self_type = ce->args[0]->tav.type; + } + + if (is_return_instancetype) { + // NOTE(harold): These should have already been checked, right? + GB_ASSERT(ce->args[0]->tav.type && ce->args[0]->tav.type->kind == Type_Pointer && ce->args[0]->tav.type->Pointer.elem->kind == Type_Named); + + return_type = ce->args[0]->tav.type; + } + } + + auto param_types = slice_make(permanent_allocator(), proc.param_count + 2 - params_start); + param_types[0] = self_type; + param_types[1] = t_objc_SEL; + + for (isize i = params_start; i < params.count; i++) { + param_types[i+2-params_start] = params[i]->type; + } + + if (is_return_instancetype) { + operand->type = return_type; + } + + add_objc_proc_type(c, call, return_type, param_types); +} gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice const &args, ProcInlining inlining, Type *type_hint) { if (proc != nullptr && @@ -8414,6 +8481,12 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } + Entity *proc_entity = entity_from_expr(call->CallExpr.proc); + bool is_objc_call = proc_entity && proc_entity->kind == Entity_Procedure && proc_entity->Procedure.is_objc_impl_or_import; + if (is_objc_call) { + check_objc_call_expr(c, operand, call, proc_entity, pt); + } + return Expr_Expr; } diff --git a/src/checker.cpp b/src/checker.cpp index 32bda2e43..d3c111de4 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1416,6 +1416,8 @@ gb_internal void init_universal(void) { t_objc_SEL = alloc_type_pointer(t_objc_selector); t_objc_Class = alloc_type_pointer(t_objc_class); t_objc_Ivar = alloc_type_pointer(t_objc_ivar); + + t_objc_instancetype = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_instancetype"), t_objc_id); } } @@ -3386,12 +3388,18 @@ 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) { + t_objc_super = find_core_type(c, str_lit("objc_super")); + t_objc_super_ptr = alloc_type_pointer(t_objc_super); +} + gb_internal void init_preload(Checker *c) { init_core_type_info(c); init_mem_allocator(c); init_core_context(c); init_core_source_code_location(c); init_core_map_type(c); + init_core_objc_c(c); } gb_internal ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index c6071bf98..c2255a6ba 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -354,6 +354,7 @@ BuiltinProc__type_end, BuiltinProc_objc_register_class, BuiltinProc_objc_ivar_get, BuiltinProc_objc_block, + BuiltinProc_objc_super, BuiltinProc_constant_utf16_cstring, @@ -715,7 +716,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_ivar_get"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, - {STR_LIT("objc_block"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("objc_block"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("objc_super"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/entity.cpp b/src/entity.cpp index d6d8f58de..2b21fdcac 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -251,6 +251,8 @@ struct Entity { String link_name; String link_prefix; String link_suffix; + String objc_selector_name; + Entity *objc_class; DeferredProcedure deferred_procedure; struct GenProcsData *gen_procs; @@ -266,6 +268,8 @@ struct Entity { bool is_anonymous : 1; bool no_sanitize_address : 1; bool no_sanitize_memory : 1; + bool is_objc_impl_or_import : 1; + bool is_objc_class_method : 1; } Procedure; struct { Array entities; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index b47e2788f..86c83b91f 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1417,8 +1417,21 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) { return str_lit("?"); case Type_Proc: return str_lit("?"); - case Type_BitSet: - return lb_get_objc_type_encoding(t->BitSet.underlying, pointer_depth); + case Type_BitSet: { + Type *bitset_integer_type = t->BitSet.underlying; + if (!bitset_integer_type) { + switch (t->cached_size) { + case 1: bitset_integer_type = t_u8; break; + case 2: bitset_integer_type = t_u16; break; + case 4: bitset_integer_type = t_u32; break; + case 8: bitset_integer_type = t_u64; break; + case 16: bitset_integer_type = t_u128; break; + } + } + GB_ASSERT_MSG(bitset_integer_type, "Could not determine bit_set integer size for objc_type_encoding"); + + return lb_get_objc_type_encoding(bitset_integer_type, pointer_depth); + } case Type_SimdVector: { String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth); @@ -1452,7 +1465,10 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) { struct lbObjCGlobalClass { lbObjCGlobal g; - lbValue class_value; // Local registered class value + union { + lbValue class_value; // Local registered class value + lbAddr class_global; // Global class pointer. Placeholder for class implementations which are registered in order of definition. + }; }; gb_internal void lb_register_objc_thing( @@ -1482,44 +1498,43 @@ gb_internal void lb_register_objc_thing( LLVMSetInitializer(v.value, LLVMConstNull(t)); } - lbValue class_ptr = {}; - lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name)); - // If this class requires an implementation, save it for registration below. if (g.class_impl_type != nullptr) { // Make sure the superclass has been initialized before us - lbValue superclass_value = lb_const_nil(m, t_objc_Class); - auto &tn = g.class_impl_type->Named.type_name->TypeName; Type *superclass = tn.objc_superclass; if (superclass != nullptr) { auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name); lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call); - GB_ASSERT(superclass_global.class_value.value); - - superclass_value = superclass_global.class_value; + GB_ASSERT(superclass_global.class_global.addr.value); } - args.count = 3; - args[0] = superclass_value; - args[1] = class_name; - args[2] = lb_const_int(m, t_uint, 0); - class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args); + lbObjCGlobalClass impl_global = {}; + impl_global.g = g; + impl_global.class_global = addr; - array_add(&class_impls, lbObjCGlobalClass{g, class_ptr}); + array_add(&class_impls, impl_global); + + lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); + if (class_global != nullptr) { + class_global->class_global = addr; + } } else { + lbValue class_ptr = {}; + lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name)); + args.count = 1; args[0] = class_name; class_ptr = lb_emit_runtime_call(p, call, args); - } - lb_addr_store(p, addr, class_ptr); + lb_addr_store(p, addr, class_ptr); - lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); - if (class_global != nullptr) { - class_global->class_value = class_ptr; + lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); + if (class_global != nullptr) { + class_global->class_value = class_ptr; + } } } @@ -1582,7 +1597,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { string_map_init(&global_class_map, (usize)gen->objc_classes.count); defer (string_map_destroy(&global_class_map)); - for (lbObjCGlobal g :referenced_classes) { + for (lbObjCGlobal g : referenced_classes) { string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g}); } @@ -1629,9 +1644,36 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { for (const auto &cd : class_impls) { auto &g = cd.g; - Type *class_type = g.class_impl_type; + + Type *class_type = g.class_impl_type; Type *class_ptr_type = alloc_type_pointer(class_type); - lbValue class_value = cd.class_value; + + // Begin class registration: create class pair and update global reference + lbValue class_value = {}; + + { + lbValue superclass_value = lb_const_nil(m, t_objc_Class); + + auto& tn = class_type->Named.type_name->TypeName; + Type *superclass = tn.objc_superclass; + + if (superclass != nullptr) { + auto& superclass_global = string_map_must_get(&global_class_map, superclass->Named.type_name->TypeName.objc_class_name); + superclass_value = superclass_global.class_value; + } + + args.count = 3; + args[0] = superclass_value; + args[1] = lb_const_value(m, t_cstring, exact_value_string(g.name)); + args[2] = lb_const_int(m, t_uint, 0); + class_value = lb_emit_runtime_call(p, "objc_allocateClassPair", args); + + lbObjCGlobalClass &mapped_global = string_map_must_get(&global_class_map, tn.objc_class_name); + lb_addr_store(p, mapped_global.class_global, class_value); + + mapped_global.class_value = class_value; + } + Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar; @@ -1651,7 +1693,6 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type); } - Array *methods = map_get(&m->info->objc_method_implementations, class_type); if (!methods) { continue; @@ -1710,17 +1751,21 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl); lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type); - lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind"); + + lb_add_function_type_attributes(wrapper_proc->value, lb_get_function_type(m, wrapper_proc_type), ProcCC_CDecl); // Emit the wrapper - LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage); + // LLVMSetLinkage(wrapper_proc->value, LLVMInternalLinkage); + LLVMSetDLLStorageClass(wrapper_proc->value, LLVMDLLExportStorageClass); + lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind"); + lb_begin_procedure_body(wrapper_proc); { + LLVMValueRef context_addr = nullptr; if (method_type->Proc.calling_convention == ProcCC_Odin) { GB_ASSERT(context_provider); // Emit the get odin context call - get_context_args[0] = lbValue { wrapper_proc->raw_input_parameters[0], contex_provider_self_ptr_type, @@ -1736,44 +1781,58 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { get_context_args[0] = lb_handle_objc_ivar_for_objc_object_pointer(wrapper_proc, real_self); } - lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args); - lbAddr context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context)); - lb_push_context_onto_stack(wrapper_proc, context_addr); + lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args); + context_addr = lb_address_from_load(wrapper_proc, context).value;//lb_address_from_load_or_generate_local(wrapper_proc, context)); + // context_addr = LLVMGetOperand(context.value, 0); } + isize method_forward_arg_count = method_param_count + method_param_offset; + isize method_forward_return_arg_offset = 0; + auto raw_method_args = array_make(temporary_allocator(), 0, method_forward_arg_count+1); - auto method_call_args = array_make(temporary_allocator(), method_param_count + method_param_offset); + lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity); + lbFunctionType* ft = lb_get_function_type(m, method_type); + bool has_return = false; + lbArgKind return_kind = {}; + + if (wrapper_results_tuple != nullptr) { + has_return = true; + return_kind = ft->ret.kind; + + if (return_kind == lbArg_Indirect) { + method_forward_return_arg_offset = 1; + array_add(&raw_method_args, wrapper_proc->return_ptr.addr.value); + } + } if (!md.ac.objc_is_class_method) { - method_call_args[0] = lbValue { - wrapper_proc->raw_input_parameters[0], - class_ptr_type, - }; + array_add(&raw_method_args, wrapper_proc->raw_input_parameters[method_forward_return_arg_offset]); } for (isize i = 0; i < method_param_count; i++) { - method_call_args[i+method_param_offset] = lbValue { - wrapper_proc->raw_input_parameters[i+2], - method_type->Proc.params->Tuple.variables[i+method_param_offset]->type, - }; + array_add(&raw_method_args, wrapper_proc->raw_input_parameters[i+2+method_forward_return_arg_offset]); + } + + if (method_type->Proc.calling_convention == ProcCC_Odin) { + array_add(&raw_method_args, context_addr); } - lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity); // Call real procedure for method from here, passing the parameters expected, if any. - lbValue return_value = lb_emit_call(wrapper_proc, method_proc_value, method_call_args); + LLVMTypeRef fnp = lb_type_internal_for_procedures_raw(m, method_type); + LLVMValueRef ret_val_raw = LLVMBuildCall2(wrapper_proc->builder, fnp, method_proc_value.value, raw_method_args.data, (unsigned)raw_method_args.count, ""); - if (wrapper_results_tuple != nullptr) { - auto &result_var = method_type->Proc.results->Tuple.variables[0]; - return_value = lb_emit_conv(wrapper_proc, return_value, result_var->type); - lb_build_return_stmt_internal(wrapper_proc, return_value, result_var->token.pos); + if (has_return && return_kind != lbArg_Indirect) { + LLVMBuildRet(wrapper_proc->builder, ret_val_raw); + } + else { + LLVMBuildRetVoid(wrapper_proc->builder); } } lb_end_procedure_body(wrapper_proc); - // Add the method to the class String method_encoding = str_lit("v"); - // TODO (harold): Checker must ensure that objc_methods have a single return value or none! + GB_ASSERT(method_type->Proc.result_count <= 1); if (method_type->Proc.result_count != 0) { method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type); @@ -1785,8 +1844,8 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:")); } - for (isize i = method_param_offset; i < method_param_count; i++) { - Type *param_type = method_type->Proc.params->Tuple.variables[i]->type; + for (isize i = 0; i < method_param_count; i++) { + Type *param_type = method_type->Proc.params->Tuple.variables[i + method_param_offset]->type; String param_encoding = lb_get_objc_type_encoding(param_type); method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding); @@ -1805,7 +1864,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { args[2] = lbValue { wrapper_proc->value, wrapper_proc->type }; args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding)); - // TODO(harold): Emit check BOOL result and panic if false. + // TODO(harold): Emit check BOOL result and panic if false? lb_emit_runtime_call(p, "class_addMethod", args); } // End methods @@ -1853,7 +1912,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { // Defined in an external package, define it now in the main package LLVMTypeRef t = lb_type(m, t_int); - lbValue global{}; + lbValue global = {}; global.value = LLVMAddGlobal(m->mod, t, g.global_name); global.type = t_int_ptr; @@ -2192,6 +2251,11 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker GB_ASSERT(m != nullptr); if (e->kind == Entity_Procedure) { + if (e->Procedure.is_foreign && e->Procedure.is_objc_impl_or_import) { + // Do not generate declarations for foreign Objective-C methods. These are called indirectly through the Objective-C runtime. + continue; + } + array_add(&m->global_procedures_to_create, e); } else if (e->kind == Entity_TypeName) { array_add(&m->global_types_to_create, e); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 7680c5e76..3bd5f4ef2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3753,6 +3753,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_objc_register_class: return lb_handle_objc_register_class(p, expr); case BuiltinProc_objc_ivar_get: return lb_handle_objc_ivar_get(p, expr); case BuiltinProc_objc_block: return lb_handle_objc_block(p, expr); + case BuiltinProc_objc_super: return lb_handle_objc_super(p, expr); case BuiltinProc_constant_utf16_cstring: @@ -4122,21 +4123,23 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } Ast *proc_expr = unparen_expr(ce->proc); + Entity *proc_entity = entity_of_node(proc_expr); + if (proc_mode == Addressing_Builtin) { - Entity *e = entity_of_node(proc_expr); BuiltinProcId id = BuiltinProc_Invalid; - if (e != nullptr) { - id = cast(BuiltinProcId)e->Builtin.id; + if (proc_entity != nullptr) { + id = cast(BuiltinProcId)proc_entity->Builtin.id; } else { id = BuiltinProc_DIRECTIVE; } return lb_build_builtin_proc(p, expr, tv, id); } + bool is_objc_call = proc_entity->Procedure.is_objc_impl_or_import; + // NOTE(bill): Regular call lbValue value = {}; - Entity *proc_entity = entity_of_node(proc_expr); if (proc_entity != nullptr) { if (proc_entity->flags & EntityFlag_Disabled) { GB_ASSERT(tv.type == nullptr); @@ -4170,11 +4173,13 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } } - if (value.value == nullptr) { + if (is_objc_call) { + value.type = proc_tv.type; + } else if (value.value == nullptr) { value = lb_build_expr(p, proc_expr); } - GB_ASSERT(value.value != nullptr); + GB_ASSERT(value.value != nullptr || is_objc_call); Type *proc_type_ = base_type(value.type); GB_ASSERT(proc_type_->kind == Type_Proc); TypeProc *pt = &proc_type_->Proc; @@ -4402,6 +4407,11 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { isize final_count = is_c_vararg ? args.count : pt->param_count; auto call_args = array_slice(args, 0, final_count); + + if (is_objc_call) { + return lb_handle_objc_auto_send(p, expr, slice(call_args, 0, call_args.count)); + } + return lb_emit_call(p, value, call_args, ce->inlining); } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 7fe6b1458..d124f164e 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2373,7 +2373,7 @@ 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 - + // TODO(harold): Ensure we don't have any issues with large struct arguments or returns in block wrappers. ast_node(ce, CallExpr, expr); GB_ASSERT(ce->args.count > 0); @@ -2452,7 +2452,9 @@ 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_"); @@ -2531,45 +2533,66 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { /// Invoker body lb_begin_procedure_body(invoker_proc); { - auto call_args = array_make(temporary_allocator(), user_proc.param_count, user_proc.param_count); + // Reserve 2 extra arguments for: Indirect return values and context. + auto call_args = array_make(temporary_allocator(), 0, user_proc.param_count + 2); - for (isize i = 1; i < invoker_proc->raw_input_parameters.count; i++) { - lbValue arg = {}; - arg.type = invoker_args[i]; - arg.value = invoker_proc->raw_input_parameters[i], - call_args[i-1] = arg; - } + isize block_literal_arg_index = 0; - 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 = {}; + + 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; + } + } - lb_push_context_onto_stack(invoker_proc, lb_addr(ctx_val)); + // 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); @@ -2587,8 +2610,8 @@ 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); - 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 +2619,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 +2634,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 +2674,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 +2803,120 @@ gb_internal lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) { return lb_emit_call(p, the_proc, args); } +gb_internal lbValue lb_handle_objc_auto_send(lbProcedure *p, Ast *expr, Slice const arg_values) { + ast_node(ce, CallExpr, expr); + + lbModule *m = p->module; + CheckerInfo *info = m->info; + ObjcMsgData data = map_must_get(&info->objc_msgSend_types, expr); + + Type *proc_type = data.proc_type; + GB_ASSERT(proc_type != nullptr); + + Entity *objc_method_ent = entity_of_node(ce->proc); + GB_ASSERT(objc_method_ent != nullptr); + GB_ASSERT(objc_method_ent->kind == Entity_Procedure); + GB_ASSERT(objc_method_ent->Procedure.objc_selector_name.len > 0); + + auto &proc = proc_type->Proc; + GB_ASSERT(proc.param_count >= 2); + + Type *objc_super_orig_type = nullptr; + if (ce->args.count > 0) { + objc_super_orig_type = unparen_expr(ce->args[0])->tav.objc_super_target; + } + + isize arg_offset = 1; + lbValue id = {}; + if (!objc_method_ent->Procedure.is_objc_class_method) { + GB_ASSERT(ce->args.count > 0); + id = arg_values[0]; + + if (objc_super_orig_type) { + GB_ASSERT(objc_super_orig_type->kind == Type_Named); + + auto& tn = objc_super_orig_type->Named.type_name->TypeName; + lbAddr p_supercls = lb_handle_objc_find_or_register_class(p, tn.objc_class_name, tn.objc_is_implementation ? objc_super_orig_type : nullptr); + + lbValue supercls = lb_addr_load(p, p_supercls); + lbAddr p_objc_super = lb_add_local_generated(p, t_objc_super, false); + + lbValue f_id = lb_emit_struct_ep(p, p_objc_super.addr, 0); + lbValue f_superclass = lb_emit_struct_ep(p, p_objc_super.addr, 1); + + id = lb_emit_conv(p, id, t_objc_id); + lb_emit_store(p, f_id, id); + lb_emit_store(p, f_superclass, supercls); + + id = p_objc_super.addr; + } + } else { + Entity *objc_class = objc_method_ent->Procedure.objc_class; + if (ce->proc->kind == Ast_SelectorExpr) { + // NOTE (harold): If called via a selector expression (ex: Foo.alloc()), then we should use + // the lhs-side to determine the class. This allows for class methods to be called + // with the correct class as the target, even when the method is defined in a superclass. + ast_node(se, SelectorExpr, ce->proc); + GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named); + + objc_class = entity_from_expr(se->expr); + + GB_ASSERT(objc_class); + GB_ASSERT(objc_class->kind == Entity_TypeName); + GB_ASSERT(objc_class->TypeName.objc_class_name != ""); + } + + Type *class_impl_type = objc_class->TypeName.objc_is_implementation ? objc_class->type : nullptr; + + id = lb_addr_load(p, lb_handle_objc_find_or_register_class(p, objc_class->TypeName.objc_class_name, class_impl_type)); + arg_offset = 0; + } + + lbValue sel = lb_addr_load(p, lb_handle_objc_find_or_register_selector(p, objc_method_ent->Procedure.objc_selector_name)); + + auto args = array_make(permanent_allocator(), 0, arg_values.count + 2 - arg_offset); + + array_add(&args, id); + array_add(&args, sel); + + for (isize i = arg_offset; i < ce->args.count; i++) { + array_add(&args, arg_values[i]); + } + + lbValue the_proc = {}; + + if (!objc_super_orig_type) { + switch (data.kind) { + default: + GB_PANIC("unhandled ObjcMsgKind %u", data.kind); + break; + case ObjcMsg_normal: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend")); break; + case ObjcMsg_fpret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fpret")); break; + case ObjcMsg_fp2ret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fp2ret")); break; + case ObjcMsg_stret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_stret")); break; + } + } else { + switch (data.kind) { + default: + GB_PANIC("unhandled ObjcMsgKind %u", data.kind); + break; + case ObjcMsg_normal: + case ObjcMsg_fpret: + case ObjcMsg_fp2ret: + the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2")); + break; + case ObjcMsg_stret: + the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2_stret")); + break; + } + } + + the_proc = lb_emit_conv(p, the_proc, data.proc_type); + + return lb_emit_call(p, the_proc, args); +} + + gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) { GB_ASSERT(value.kind == ExactValue_Integer); i64 v = exact_value_to_i64(value); diff --git a/src/parser.hpp b/src/parser.hpp index 979b44618..6127468d4 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -48,7 +48,8 @@ gb_global String const addressing_mode_strings[] = { struct TypeAndValue { Type * type; AddressingMode mode; - bool is_lhs; // Debug info + bool is_lhs; // Debug info + Type * objc_super_target; // Original type of the Obj-C object before being converted to the superclass' type by the objc_super() intrinsic. ExactValue value; }; diff --git a/src/types.cpp b/src/types.cpp index effa8ef64..372c2e991 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -752,11 +752,14 @@ gb_global Type *t_objc_object = nullptr; gb_global Type *t_objc_selector = nullptr; gb_global Type *t_objc_class = nullptr; gb_global Type *t_objc_ivar = nullptr; +gb_global Type *t_objc_super = nullptr; // Struct used in lieu of the 'self' instance when calling objc_msgSendSuper. +gb_global Type *t_objc_super_ptr = nullptr; gb_global Type *t_objc_id = nullptr; gb_global Type *t_objc_SEL = nullptr; gb_global Type *t_objc_Class = nullptr; gb_global Type *t_objc_Ivar = nullptr; +gb_global Type *t_objc_instancetype = nullptr; // Special distinct variant of t_objc_id used mimic auto-typing of instancetype* in Objective-C enum OdinAtomicMemoryOrder : i32 { OdinAtomicMemoryOrder_relaxed = 0, // unordered @@ -4735,6 +4738,14 @@ gb_internal bool is_type_objc_object(Type *t) { return internal_check_is_assignable_to(t, t_objc_object); } +gb_internal bool is_type_objc_ptr_to_object(Type *t) { + // NOTE (harold): is_type_objc_object() returns true if it's a pointer to an object or the object itself. + // This returns true ONLY if Type is a shallow pointer to an Objective-C object. + + Type *elem = type_deref(t); + return elem != t && elem->kind == Type_Named && is_type_objc_object(elem); +} + gb_internal Type *get_struct_field_type(Type *t, isize index) { t = base_type(type_deref(t)); GB_ASSERT(t->kind == Type_Struct); -- cgit v1.2.3 From 6d9ace4a2cd4a05af5ce46d6975108a0cc5c0086 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Mon, 29 Sep 2025 15:58:30 -0400 Subject: Remove outdated todo --- src/llvm_backend_utility.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index d124f164e..10a258ab1 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2373,7 +2373,6 @@ 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 - // TODO(harold): Ensure we don't have any issues with large struct arguments or returns in block wrappers. ast_node(ce, CallExpr, expr); GB_ASSERT(ce->args.count > 0); -- cgit v1.2.3 From 588513a510f5906d214633bfcdce6b4441e900bb Mon Sep 17 00:00:00 2001 From: Jakub Tomsu Date: Wed, 1 Oct 2025 15:54:32 +0200 Subject: bitcast only non-pointer-like-element simd vectors, handle others like other aggregate types --- src/llvm_backend_utility.cpp | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/llvm_backend_utility.cpp') diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 155c55675..1abbf60bc 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -286,7 +286,14 @@ gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t) { } } + bool is_simd_vector_bitcastable = false; if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { + if (!is_type_internally_pointer_like(src->SimdVector.elem) && !is_type_internally_pointer_like(dst->SimdVector.elem)) { + is_simd_vector_bitcastable = true; + } + } + + if (is_simd_vector_bitcastable) { res.value = LLVMBuildBitCast(p->builder, value.value, lb_type(p->module, t), ""); return res; } else if (is_type_array_like(src) && (is_type_simd_vector(dst) || is_type_integer_128bit(dst))) { -- cgit v1.2.3