From f3210eccaf6d5a9ccb46040d74aabf4f4b85cd86 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 7 Mar 2025 09:44:18 +0000 Subject: Fix #4866 --- src/llvm_backend_proc.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index a835ae2c8..ad14e4fcd 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3621,6 +3621,7 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { GB_ASSERT(ce->args.count == 1); lbValue x = lb_build_expr(p, ce->args[0]); lbValue y = lb_emit_conv(p, x, tv.type); + y.type = tv.type; return y; } -- cgit v1.2.3 From 6fda041e178bf44fae87cc56bef46db418dee0c7 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Wed, 26 Mar 2025 19:25:06 -0400 Subject: Add `~{memory}` clobber to syscall intrinsics for platforms where it was not yet added. This fixes #4972 and #4588. As laytan asserts, there is no guarantee that a syscall will not modify memory that it just obtained from the user, or held onto via a previous syscall. Therefore this constraint should be required for all syscalls. --- src/llvm_backend_proc.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index a835ae2c8..3c4913d50 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2972,6 +2972,8 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu constraints = gb_string_appendc(constraints, "}"); } + constraints = gb_string_appendc(constraints, ",~{memory}"); + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } break; @@ -3034,6 +3036,8 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu constraints = gb_string_appendc(constraints, "}"); } + constraints = gb_string_appendc(constraints, ",~{memory}"); + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } break; @@ -3059,6 +3063,8 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu constraints = gb_string_appendc(constraints, "}"); } + constraints = gb_string_appendc(constraints, ",~{memory}"); + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } else { char asm_string[] = "svc #0"; @@ -3078,6 +3084,8 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu constraints = gb_string_appendc(constraints, "}"); } + constraints = gb_string_appendc(constraints, ",~{memory}"); + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } } @@ -3104,6 +3112,8 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu constraints = gb_string_appendc(constraints, "}"); } + constraints = gb_string_appendc(constraints, ",~{memory}"); + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } break; -- cgit v1.2.3 From e13b05168cb7e532c3ca1a64528a0f520946e4b2 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 6 Apr 2025 19:58:23 -0400 Subject: Fix `syscall_bsd` on NetBSD ARM64 --- src/llvm_backend_proc.cpp | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 304db75bc..3212abd9a 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3226,11 +3226,28 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu { GB_ASSERT(arg_count <= 7); - char asm_string[] = "svc #0; cset x8, cc"; - gbString constraints = gb_string_make(heap_allocator(), "={x0},={x8}"); - for (unsigned i = 0; i < arg_count; i++) { - constraints = gb_string_appendc(constraints, ",{"); - static char const *regs[] = { + char const *asm_string; + char const **regs; + gbString constraints; + + if (build_context.metrics.os == TargetOs_netbsd) { + asm_string = "svc #0; cset x17, cc"; + constraints = gb_string_make(heap_allocator(), "={x0},={x17}"); + static char const *_regs[] = { + "x17", + "x0", + "x1", + "x2", + "x3", + "x4", + "x5", + }; + regs = _regs; + } else { + // FreeBSD (tested), OpenBSD (untested). + asm_string = "svc #0; cset x8, cc"; + constraints = gb_string_make(heap_allocator(), "={x0},={x8}"); + static char const *_regs[] = { "x8", "x0", "x1", @@ -3239,13 +3256,19 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu "x4", "x5", }; + regs = _regs; + + // FreeBSD clobbered x1 on a call to sysctl. + constraints = gb_string_appendc(constraints, ",~{x1}"); + } + + for (unsigned i = 0; i < arg_count; i++) { + constraints = gb_string_appendc(constraints, ",{"); constraints = gb_string_appendc(constraints, regs[i]); constraints = gb_string_appendc(constraints, "}"); } - // FreeBSD clobbered x1 on a call to sysctl. - constraints = gb_string_appendc(constraints, ",~{x1},~{cc},~{memory}"); - + constraints = gb_string_appendc(constraints, ",~{cc},~{memory}"); inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); } break; -- 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_proc.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 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_proc.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 396a18efce4ce25872d6d272ea3617fd1b62e76b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 30 Apr 2025 20:03:17 +0200 Subject: fix variable NaN comparisons --- src/llvm_backend_expr.cpp | 4 +- src/llvm_backend_proc.cpp | 2 +- tests/internal/test_nan_comparison.odin | 91 ++++++++++++++++++++++++ tests/internal/test_nan_constant_comparison.odin | 47 ------------ 4 files changed, 94 insertions(+), 50 deletions(-) create mode 100644 tests/internal/test_nan_comparison.odin delete mode 100644 tests/internal/test_nan_constant_comparison.odin (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index c5ea0ddac..d313d65a3 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2944,7 +2944,7 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left case Token_GtEq: pred = LLVMRealOGE; break; case Token_Lt: pred = LLVMRealOLT; break; case Token_LtEq: pred = LLVMRealOLE; break; - case Token_NotEq: pred = LLVMRealONE; break; + case Token_NotEq: pred = LLVMRealUNE; break; } if (is_type_different_to_arch_endianness(left.type)) { @@ -2972,7 +2972,7 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left LLVMRealPredicate pred = {}; switch (op_kind) { case Token_CmpEq: pred = LLVMRealOEQ; break; - case Token_NotEq: pred = LLVMRealONE; break; + case Token_NotEq: pred = LLVMRealUNE; break; } mask = LLVMBuildFCmp(p->builder, pred, left.value, right.value, ""); } else { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3212abd9a..2c49d8c5c 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1442,7 +1442,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn LLVMRealPredicate pred = cast(LLVMRealPredicate)0; switch (builtin_id) { case BuiltinProc_simd_lanes_eq: pred = LLVMRealOEQ; break; - case BuiltinProc_simd_lanes_ne: pred = LLVMRealONE; break; + case BuiltinProc_simd_lanes_ne: pred = LLVMRealUNE; break; case BuiltinProc_simd_lanes_lt: pred = LLVMRealOLT; break; case BuiltinProc_simd_lanes_le: pred = LLVMRealOLE; break; case BuiltinProc_simd_lanes_gt: pred = LLVMRealOGT; break; diff --git a/tests/internal/test_nan_comparison.odin b/tests/internal/test_nan_comparison.odin new file mode 100644 index 000000000..5740be477 --- /dev/null +++ b/tests/internal/test_nan_comparison.odin @@ -0,0 +1,91 @@ +package test_internal + +import "core:testing" + +@(test) +compare_constant_nans_f32 :: proc(t: ^testing.T) { + NaN :: f32(0h7fc0_0000) + NaN2 :: f32(0h7fc0_0001) + Inf :: f32(0h7F80_0000) + Neg_Inf :: f32(0hFF80_0000) + + testing.expect_value(t, NaN == NaN, false) + testing.expect_value(t, NaN == NaN2, false) + testing.expect_value(t, NaN != 0, true) + testing.expect_value(t, NaN != 5, true) + testing.expect_value(t, NaN != -5, true) + testing.expect_value(t, NaN != NaN, true) + testing.expect_value(t, NaN != NaN2, true) + testing.expect_value(t, NaN != Inf, true) + testing.expect_value(t, NaN != Neg_Inf, true) + testing.expect_value(t, NaN < NaN, false) + testing.expect_value(t, NaN <= NaN, false) + testing.expect_value(t, NaN > NaN, false) + testing.expect_value(t, NaN >= NaN, false) +} + +@(test) +compare_constant_nans_f64 :: proc(t: ^testing.T) { + NaN :: f64(0h7fff_0000_0000_0000) + NaN2 :: f64(0h7fff_0000_0000_0001) + Inf :: f64(0h7FF0_0000_0000_0000) + Neg_Inf :: f64(0hFFF0_0000_0000_0000) + + testing.expect_value(t, NaN == NaN, false) + testing.expect_value(t, NaN == NaN2, false) + testing.expect_value(t, NaN != 0, true) + testing.expect_value(t, NaN != 5, true) + testing.expect_value(t, NaN != -5, true) + testing.expect_value(t, NaN != NaN, true) + testing.expect_value(t, NaN != NaN2, true) + testing.expect_value(t, NaN != Inf, true) + testing.expect_value(t, NaN != Neg_Inf, true) + testing.expect_value(t, NaN < NaN, false) + testing.expect_value(t, NaN <= NaN, false) + testing.expect_value(t, NaN > NaN, false) + testing.expect_value(t, NaN >= NaN, false) +} + +@(test) +compare_variable_nans_f32 :: proc(t: ^testing.T) { + NaN := f32(0h7fc0_0000) + NaN2 := f32(0h7fc0_0001) + Inf := f32(0h7F80_0000) + Neg_Inf := f32(0hFF80_0000) + + testing.expect_value(t, NaN == NaN, false) + testing.expect_value(t, NaN == NaN2, false) + testing.expect_value(t, NaN != 0, true) + testing.expect_value(t, NaN != 5, true) + testing.expect_value(t, NaN != -5, true) + testing.expect_value(t, NaN != NaN, true) + testing.expect_value(t, NaN != NaN2, true) + testing.expect_value(t, NaN != Inf, true) + testing.expect_value(t, NaN != Neg_Inf, true) + testing.expect_value(t, NaN < NaN, false) + testing.expect_value(t, NaN <= NaN, false) + testing.expect_value(t, NaN > NaN, false) + testing.expect_value(t, NaN >= NaN, false) +} + +@(test) +compare_variable_nans_f64 :: proc(t: ^testing.T) { + NaN := f64(0h7fff_0000_0000_0000) + NaN2 := f64(0h7fff_0000_0000_0001) + Inf := f64(0h7FF0_0000_0000_0000) + Neg_Inf := f64(0hFFF0_0000_0000_0000) + + testing.expect_value(t, NaN == NaN, false) + testing.expect_value(t, NaN == NaN2, false) + testing.expect_value(t, NaN != 0, true) + testing.expect_value(t, NaN != 5, true) + testing.expect_value(t, NaN != -5, true) + testing.expect_value(t, NaN != NaN, true) + testing.expect_value(t, NaN != NaN2, true) + testing.expect_value(t, NaN != Inf, true) + testing.expect_value(t, NaN != Neg_Inf, true) + testing.expect_value(t, NaN < NaN, false) + testing.expect_value(t, NaN <= NaN, false) + testing.expect_value(t, NaN > NaN, false) + testing.expect_value(t, NaN >= NaN, false) +} diff --git a/tests/internal/test_nan_constant_comparison.odin b/tests/internal/test_nan_constant_comparison.odin deleted file mode 100644 index 4eb3163a8..000000000 --- a/tests/internal/test_nan_constant_comparison.odin +++ /dev/null @@ -1,47 +0,0 @@ -package test_internal - -import "core:testing" - -@(test) -compare_constant_nans_f32 :: proc(t: ^testing.T) { - NaN :: f32(0h7fc0_0000) - NaN2 :: f32(0h7fc0_0001) - Inf :: f32(0h7F80_0000) - Neg_Inf :: f32(0hFF80_0000) - - testing.expect_value(t, NaN == NaN, false) - testing.expect_value(t, NaN == NaN2, false) - testing.expect_value(t, NaN != 0, true) - testing.expect_value(t, NaN != 5, true) - testing.expect_value(t, NaN != -5, true) - testing.expect_value(t, NaN != NaN, true) - testing.expect_value(t, NaN != NaN2, true) - testing.expect_value(t, NaN != Inf, true) - testing.expect_value(t, NaN != Neg_Inf, true) - testing.expect_value(t, NaN < NaN, false) - testing.expect_value(t, NaN <= NaN, false) - testing.expect_value(t, NaN > NaN, false) - testing.expect_value(t, NaN >= NaN, false) -} - -@(test) -compare_constant_nans_f64 :: proc(t: ^testing.T) { - NaN :: f64(0h7fff_0000_0000_0000) - NaN2 :: f64(0h7fff_0000_0000_0001) - Inf :: f64(0h7FF0_0000_0000_0000) - Neg_Inf :: f64(0hFFF0_0000_0000_0000) - - testing.expect_value(t, NaN == NaN, false) - testing.expect_value(t, NaN == NaN2, false) - testing.expect_value(t, NaN != 0, true) - testing.expect_value(t, NaN != 5, true) - testing.expect_value(t, NaN != -5, true) - testing.expect_value(t, NaN != NaN, true) - testing.expect_value(t, NaN != NaN2, true) - testing.expect_value(t, NaN != Inf, true) - testing.expect_value(t, NaN != Neg_Inf, true) - testing.expect_value(t, NaN < NaN, false) - testing.expect_value(t, NaN <= NaN, false) - testing.expect_value(t, NaN > NaN, false) - testing.expect_value(t, NaN >= NaN, false) -} -- cgit v1.2.3 From 5c73b4ef5829f0d722fdbb8ba8d84709563505a0 Mon Sep 17 00:00:00 2001 From: Lucas Perlind Date: Thu, 1 May 2025 20:42:21 +1000 Subject: Add attribute @(no_sanitize_address) The purposes of this attribute is to let procedures opt-out of being instrumented with asan. Typically an allocator that includes 'in-band' meta-data will be accessing poisoned values (such as tlsf). Making asan work with these allocators becomes very challenging so just being to ignore asan within specific allocator procedures makes it easier to reason and removes the need to temporarily poison and unpoison allocator data. --- src/check_decl.cpp | 1 + src/checker.cpp | 6 ++++++ src/checker.hpp | 3 ++- src/entity.cpp | 1 + src/llvm_backend_proc.cpp | 2 +- 5 files changed, 11 insertions(+), 2 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index ba6445ea4..2392775b1 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1229,6 +1229,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { e->Procedure.has_instrumentation = has_instrumentation; + e->Procedure.no_sanitize_address = ac.no_sanitize_address; e->deprecated_message = ac.deprecated_message; e->warning_message = ac.warning_message; diff --git a/src/checker.cpp b/src/checker.cpp index 038c5aa1a..5e1517875 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3711,6 +3711,12 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } ac->instrumentation_exit = true; return true; + } else if (name == "no_sanitize_address") { + if (value != nullptr) { + error(value, "'%.*s' expects no parameter", LIT(name)); + } + ac->no_sanitize_address = true; + return true; } return false; } diff --git a/src/checker.hpp b/src/checker.hpp index d3b2d7d89..dd82d9bdd 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -139,6 +139,7 @@ struct AttributeContext { bool entry_point_only : 1; bool instrumentation_enter : 1; bool instrumentation_exit : 1; + bool no_sanitize_address : 1; bool rodata : 1; bool ignore_duplicates : 1; u32 optimization_mode; // ProcedureOptimizationMode @@ -629,4 +630,4 @@ gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type); -gb_internal void init_map_internal_types(Type *type); \ No newline at end of file +gb_internal void init_map_internal_types(Type *type); diff --git a/src/entity.cpp b/src/entity.cpp index b2148aa7b..9946a3a5f 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -258,6 +258,7 @@ struct Entity { bool is_memcpy_like : 1; bool uses_branch_location : 1; bool is_anonymous : 1; + bool no_sanitize_address : 1; } Procedure; struct { Array entities; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3212abd9a..c442f3d58 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -333,7 +333,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i } if (p->body && entity->pkg && ((entity->pkg->kind == Package_Normal) || (entity->pkg->kind == Package_Init))) { - if (build_context.sanitizer_flags & SanitizerFlag_Address) { + if (build_context.sanitizer_flags & SanitizerFlag_Address && !entity->Procedure.no_sanitize_address) { lb_add_attribute_to_proc(m, p->value, "sanitize_address"); } if (build_context.sanitizer_flags & SanitizerFlag_Memory) { -- cgit v1.2.3 From ee8aeea38163c18a9b3513717bd09d3765c0d6d8 Mon Sep 17 00:00:00 2001 From: bogwi Date: Mon, 5 May 2025 14:18:11 +0900 Subject: CHECK 1 done Fix panic in LLVM backend when using generic procedure with default arguments - Fixed panic in `llvm_backend_proc.cpp` when using unspecialized polymorphic procedures as defaults. - Ensured correct type inference when generic procedures are used as default parameters. --- src/llvm_backend_const.cpp | 5 ++++- src/llvm_backend_general.cpp | 12 ++++++++++-- src/llvm_backend_proc.cpp | 12 ++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index dada2cff5..51c8a4449 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -533,7 +533,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb Entity *e = entity_from_expr(expr); res = lb_find_procedure_value_from_entity(m, e); } - GB_ASSERT(res.value != nullptr); + if (res.value == nullptr) { + // This is an unspecialized polymorphic procedure, return nil or dummy value + return lb_const_nil(m, original_type); + } GB_ASSERT(LLVMGetValueKind(res.value) == LLVMFunctionValueKind); if (LLVMGetIntrinsicID(res.value) == 0) { diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 421720c4c..41a6fb34a 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -885,8 +885,8 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { Type *t = base_type(type_deref(addr.addr.type)); GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None); lbValue len = lb_soa_struct_len(p, addr.addr); - if (addr.soa.index_expr != nullptr) { - lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), index, len); + if (addr.soa.index_expr != nullptr && (!lb_is_const(addr.soa.index) || t->Struct.soa_kind != StructSoa_Fixed)) { + lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), addr.soa.index, len); } } @@ -2728,6 +2728,14 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) ignore_body = other_module != m; lbProcedure *missing_proc = lb_create_procedure(m, e, ignore_body); + if (missing_proc == nullptr) { + // This is an unspecialized polymorphic procedure, which should not be codegen'd + lbValue dummy = {}; + dummy.value = nullptr; + dummy.type = nullptr; + return dummy; + } + if (ignore_body) { mutex_lock(&gen->anonymous_proc_lits_mutex); defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index be51f529d..519ab3e9d 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -67,6 +67,14 @@ gb_internal void lb_mem_copy_non_overlapping(lbProcedure *p, lbValue dst, lbValu gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) { GB_ASSERT(entity != nullptr); GB_ASSERT(entity->kind == Entity_Procedure); + // Skip codegen for unspecialized polymorphic procedures + if (is_type_polymorphic(entity->type) && !entity->Procedure.is_foreign) { + Type *bt = base_type(entity->type); + if (bt->kind == Type_Proc && bt->Proc.is_polymorphic && !bt->Proc.is_poly_specialized) { + // Do not generate code for unspecialized polymorphic procedures + return nullptr; + } + } if (!entity->Procedure.is_foreign) { if ((entity->flags & EntityFlag_ProcBodyChecked) == 0) { GB_PANIC("%.*s :: %s (was parapoly: %d %d)", LIT(entity->token.string), type_to_string(entity->type), is_type_polymorphic(entity->type, true), is_type_polymorphic(entity->type, false)); @@ -815,6 +823,10 @@ gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e) e->Procedure.link_name = name; lbProcedure *nested_proc = lb_create_procedure(p->module, e); + if (nested_proc == nullptr) { + // This is an unspecialized polymorphic procedure, skip codegen + return; + } e->code_gen_procedure = nested_proc; lbValue value = {}; -- cgit v1.2.3 From 36945079f8131973abb9e59bf642ab13c1c602be Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 5 May 2025 11:41:54 +0100 Subject: Add `intrinsics.simd_indices` --- src/check_builtin.cpp | 31 +++++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 5 +++++ src/llvm_backend_proc.cpp | 17 +++++++++++++++++ 3 files changed, 53 insertions(+) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index f66a8605c..9d07de2b6 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -760,6 +760,36 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return true; } + case BuiltinProc_simd_indices: + { + Operand x = {}; + check_expr_or_type(c, &x, ce->args[0], nullptr); + if (x.mode == Addressing_Invalid) return false; + if (x.mode != Addressing_Type) { + gbString s = expr_to_string(x.expr); + error(x.expr, "'%.*s' expected a simd vector type, got '%s'", LIT(builtin_name), s); + gb_string_free(s); + return false; + } + if (!is_type_simd_vector(x.type)) { + gbString s = type_to_string(x.type); + error(x.expr, "'%.*s' expected a simd vector type, got '%s'", LIT(builtin_name), s); + gb_string_free(s); + return false; + } + + Type *elem = base_array_type(x.type); + if (!is_type_numeric(elem)) { + gbString s = type_to_string(x.type); + error(x.expr, "'%.*s' expected a simd vector type with a numeric element type, got '%s'", LIT(builtin_name), s); + gb_string_free(s); + } + + operand->mode = Addressing_Value; + operand->type = x.type; + return true; + } + case BuiltinProc_simd_extract: { Operand x = {}; @@ -2059,6 +2089,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_atomic_type_is_lock_free: case BuiltinProc_has_target_feature: case BuiltinProc_procedure_of: + case BuiltinProc_simd_indices: // NOTE(bill): The first arg may be a Type, this will be checked case by case break; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 40dde8240..d8ac10b11 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -205,6 +205,9 @@ BuiltinProc__simd_begin, BuiltinProc_simd_masked_expand_load, BuiltinProc_simd_masked_compress_store, + BuiltinProc_simd_indices, + + // Platform specific SIMD intrinsics BuiltinProc_simd_x86__MM_SHUFFLE, BuiltinProc__simd_end, @@ -551,6 +554,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_masked_expand_load"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_masked_compress_store"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_indices"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_x86__MM_SHUFFLE"), 4, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index be51f529d..7bd8dea59 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1293,6 +1293,23 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn lbValue res = {}; res.type = tv.type; + switch (builtin_id) { + case BuiltinProc_simd_indices: { + Type *type = base_type(res.type); + GB_ASSERT(type->kind == Type_SimdVector); + Type *elem = type->SimdVector.elem; + + i64 count = type->SimdVector.count; + LLVMValueRef *scalars = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); + for (i64 i = 0; i < count; i++) { + scalars[i] = lb_const_value(m, elem, exact_value_i64(i)).value; + } + + res.value = LLVMConstVector(scalars, cast(unsigned)count); + return res; + } + } + lbValue arg0 = {}; if (ce->args.count > 0) arg0 = lb_build_expr(p, ce->args[0]); lbValue arg1 = {}; if (ce->args.count > 1) arg1 = lb_build_expr(p, ce->args[1]); lbValue arg2 = {}; if (ce->args.count > 2) arg2 = lb_build_expr(p, ce->args[2]); -- cgit v1.2.3 From dd5b7852ce569027e87d77f46601210aa4180947 Mon Sep 17 00:00:00 2001 From: Barinzaya Date: Mon, 5 May 2025 15:13:10 -0400 Subject: Added alternate reduce-add/reduce-mul intrinsics. The new reduce_add/reduce_mul procs perform the corresponding arithmetic reduction in different orders than sequential order. These alternative orders can often offer better SIMD hardware utilization. Two different orders are added: pair-wise (operating on pairs of adjacent elements) or bisection-wise (operating element-wise on the first and last N/2 elements of the vector). --- base/intrinsics/intrinsics.odin | 4 + core/simd/simd.odin | 194 +++++++++++++++++++++++++++++++++++++++- src/check_builtin.cpp | 4 + src/checker_builtin_procs.hpp | 8 ++ src/llvm_backend_proc.cpp | 66 ++++++++++++++ 5 files changed, 274 insertions(+), 2 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index c275dedaf..9429ec023 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -274,8 +274,12 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- simd_extract :: proc(a: #simd[N]T, idx: uint) -> T --- simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T --- +simd_reduce_add_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_mul_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_add_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_mul_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_min :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_max :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_and :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 0e69304c3..a97155f58 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -1759,7 +1759,103 @@ Returns: replace :: intrinsics.simd_replace /* -Reduce a vector to a scalar by adding up all the lanes. +Reduce a vector to a scalar by adding up all the lanes in a bisecting fashion. + +This procedure returns a scalar that is the sum of all lanes, calculated by +bisecting the vector into two parts, where the first contains lanes [0, N/2) +and the second contains lanes [N/2, N), and adding the two halves element-wise +to produce N/2 values. This is repeated until only a single element remains. +This order may be faster to compute than the ordered sum for floats, as it can +often be better parallelized. + +The order of the sum may be important for accounting for precision errors in +floating-point computation, as floating-point addition is not associative, that +is `(a+b)+c` may not be equal to `a+(b+c)`. + +Inputs: +- `v`: The vector to reduce. + +Result: +- Sum of all lanes, as a scalar. + +**Operation**: + + for n > 1 { + n = n / 2 + for i in 0 ..< n { + a[i] += a[i+n] + } + } + res := a[0] + +Graphical representation of the operation for N=4: + + +-----------------------+ + | v0 | v1 | v2 | v3 | + +-----------------------+ + | | | | + [+]<-- | ---' | + | [+]<--------' + | | + `>[+]<' + | + v + +-----+ + result: | y0 | + +-----+ +*/ +reduce_add_bisect :: intrinsics.simd_reduce_add_bisect + +/* +Reduce a vector to a scalar by multiplying up all the lanes in a bisecting fashion. + +This procedure returns a scalar that is the product of all lanes, calculated by +bisecting the vector into two parts, where the first contains indices [0, N/2) +and the second contains indices [N/2, N), and multiplying the two halves +together element-wise to produce N/2 values. This is repeated until only a +single element remains. This order may be faster to compute than the ordered +product for floats, as it can often be better parallelized. + +The order of the product may be important for accounting for precision errors +in floating-point computation, as floating-point multiplication is not +associative, that is `(a*b)*c` may not be equal to `a*(b*c)`. + +Inputs: +- `v`: The vector to reduce. + +Result: +- Product of all lanes, as a scalar. + +**Operation**: + + for n > 1 { + n = n / 2 + for i in 0 ..< n { + a[i] *= a[i+n] + } + } + res := a[0] + +Graphical representation of the operation for N=4: + + +-----------------------+ + | v0 | v1 | v2 | v3 | + +-----------------------+ + | | | | + [x]<-- | ---' | + | [x]<--------' + | | + `>[x]<' + | + v + +-----+ + result: | y0 | + +-----+ +*/ +reduce_mul_bisect :: intrinsics.simd_reduce_mul_bisect + +/* +Reduce a vector to a scalar by adding up all the lanes in an ordered fashion. This procedure returns a scalar that is the ordered sum of all lanes. The ordered sum may be important for accounting for precision errors in @@ -1782,7 +1878,7 @@ Result: reduce_add_ordered :: intrinsics.simd_reduce_add_ordered /* -Reduce a vector to a scalar by multiplying all the lanes. +Reduce a vector to a scalar by multiplying all the lanes in an ordered fashion. This procedure returns a scalar that is the ordered product of all lanes. The ordered product may be important for accounting for precision errors in @@ -1804,6 +1900,100 @@ Result: */ reduce_mul_ordered :: intrinsics.simd_reduce_mul_ordered +/* +Reduce a vector to a scalar by adding up all the lanes in a pairwise fashion. + +This procedure returns a scalar that is the sum of all lanes, calculated by +adding each even-indexed element with the following odd-indexed element to +produce N/2 values. This is repeated until only a single element remains. This +order is supported by hardware instructions for some types/architectures (e.g. +i16/i32/f32/f64 on x86 SSE, i8/i16/i32/f32 on ARM NEON). + +The order of the sum may be important for accounting for precision errors in +floating-point computation, as floating-point addition is not associative, that +is `(a+b)+c` may not be equal to `a+(b+c)`. + +Inputs: +- `v`: The vector to reduce. + +Result: +- Sum of all lanes, as a scalar. + +**Operation**: + + for n > 1 { + n = n / 2 + for i in 0 ..< n { + a[i] = a[2*i+0] + a[2*i+1] + } + } + res := a[0] + +Graphical representation of the operation for N=4: + + +-----------------------+ + v: | v0 | v1 | v2 | v3 | + +-----------------------+ + | | | | + `>[+]<' `>[+]<' + | | + `--->[+]<--' + | + v + +-----+ + result: | y0 | + +-----+ +*/ +reduce_add_pairs :: intrinsics.simd_reduce_add_pairs + +/* +Reduce a vector to a scalar by multiplying all the lanes in a pairwise fashion. + +This procedure returns a scalar that is the product of all lanes, calculated by +bisecting the vector into two parts, where the first contains lanes [0, N/2) +and the second contains lanes [N/2, N), and multiplying the two halves together +multiplying each even-indexed element with the following odd-indexed element to +produce N/2 values. This is repeated until only a single element remains. This +order may be faster to compute than the ordered product for floats, as it can +often be better parallelized. + +The order of the product may be important for accounting for precision errors +in floating-point computation, as floating-point multiplication is not +associative, that is `(a*b)*c` may not be equal to `a*(b*c)`. + +Inputs: +- `v`: The vector to reduce. + +Result: +- Product of all lanes, as a scalar. + +**Operation**: + + for n > 1 { + n = n / 2 + for i in 0 ..< n { + a[i] = a[2*i+0] * a[2*i+1] + } + } + res := a[0] + +Graphical representation of the operation for N=4: + + +-----------------------+ + v: | v0 | v1 | v2 | v3 | + +-----------------------+ + | | | | + `>[x]<' `>[x]<' + | | + `--->[x]<--' + | + v + +-----+ + result: | y0 | + +-----+ +*/ +reduce_mul_pairs :: intrinsics.simd_reduce_mul_pairs + /* Reduce a vector to a scalar by finding the minimum value between all of the lanes. diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index a315d1880..84e30d5b4 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -853,8 +853,12 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan } break; + case BuiltinProc_simd_reduce_add_bisect: + case BuiltinProc_simd_reduce_mul_bisect: case BuiltinProc_simd_reduce_add_ordered: case BuiltinProc_simd_reduce_mul_ordered: + case BuiltinProc_simd_reduce_add_pairs: + case BuiltinProc_simd_reduce_mul_pairs: case BuiltinProc_simd_reduce_min: case BuiltinProc_simd_reduce_max: { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index d8ac10b11..4c4387923 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -170,8 +170,12 @@ BuiltinProc__simd_begin, BuiltinProc_simd_extract, BuiltinProc_simd_replace, + BuiltinProc_simd_reduce_add_bisect, + BuiltinProc_simd_reduce_mul_bisect, BuiltinProc_simd_reduce_add_ordered, BuiltinProc_simd_reduce_mul_ordered, + BuiltinProc_simd_reduce_add_pairs, + BuiltinProc_simd_reduce_mul_pairs, BuiltinProc_simd_reduce_min, BuiltinProc_simd_reduce_max, BuiltinProc_simd_reduce_and, @@ -518,8 +522,12 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_extract"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_replace"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_add_bisect"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_mul_bisect"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_add_ordered"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_mul_ordered"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_add_pairs"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_mul_pairs"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_min"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_max"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_and"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 7bd8dea59..14157455e 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1495,6 +1495,38 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn res.value = LLVMBuildInsertElement(p->builder, arg0.value, arg2.value, arg1.value, ""); return res; + case BuiltinProc_simd_reduce_add_bisect: + case BuiltinProc_simd_reduce_mul_bisect: + { + GB_ASSERT(arg0.type->kind == Type_SimdVector); + i64 num_elems = arg0.type->SimdVector.count; + + LLVMValueRef *indices = gb_alloc_array(temporary_allocator(), LLVMValueRef, num_elems); + for (i64 i = 0; i < num_elems; i++) { + indices[i] = lb_const_int(m, t_uint, cast(u64)i).value; + } + + switch (builtin_id) { + case BuiltinProc_simd_reduce_add_bisect: op_code = is_float ? LLVMFAdd : LLVMAdd; break; + case BuiltinProc_simd_reduce_mul_bisect: op_code = is_float ? LLVMFMul : LLVMMul; break; + } + + LLVMValueRef remaining = arg0.value; + i64 num_remaining = num_elems; + + while (num_remaining > 1) { + num_remaining /= 2; + LLVMValueRef left_indices = LLVMConstVector(&indices[0], cast(unsigned)num_remaining); + LLVMValueRef left_value = LLVMBuildShuffleVector(p->builder, remaining, remaining, left_indices, ""); + LLVMValueRef right_indices = LLVMConstVector(&indices[num_remaining], cast(unsigned)num_remaining); + LLVMValueRef right_value = LLVMBuildShuffleVector(p->builder, remaining, remaining, right_indices, ""); + remaining = LLVMBuildBinOp(p->builder, op_code, left_value, right_value, ""); + } + + res.value = LLVMBuildExtractElement(p->builder, remaining, indices[0], ""); + return res; + } + case BuiltinProc_simd_reduce_add_ordered: case BuiltinProc_simd_reduce_mul_ordered: { @@ -1527,6 +1559,40 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn res.value = lb_call_intrinsic(p, name, args, cast(unsigned)args_count, types, gb_count_of(types)); return res; } + + case BuiltinProc_simd_reduce_add_pairs: + case BuiltinProc_simd_reduce_mul_pairs: + { + GB_ASSERT(arg0.type->kind == Type_SimdVector); + i64 num_elems = arg0.type->SimdVector.count; + + LLVMValueRef *indices = gb_alloc_array(temporary_allocator(), LLVMValueRef, num_elems); + for (i64 i = 0; i < num_elems/2; i++) { + indices[i] = lb_const_int(m, t_uint, cast(u64)(2*i)).value; + indices[i+num_elems/2] = lb_const_int(m, t_uint, cast(u64)(2*i+1)).value; + } + + switch (builtin_id) { + case BuiltinProc_simd_reduce_add_pairs: op_code = is_float ? LLVMFAdd : LLVMAdd; break; + case BuiltinProc_simd_reduce_mul_pairs: op_code = is_float ? LLVMFMul : LLVMMul; break; + } + + LLVMValueRef remaining = arg0.value; + i64 num_remaining = num_elems; + + while (num_remaining > 1) { + num_remaining /= 2; + LLVMValueRef left_indices = LLVMConstVector(&indices[0], cast(unsigned)num_remaining); + LLVMValueRef left_value = LLVMBuildShuffleVector(p->builder, remaining, remaining, left_indices, ""); + LLVMValueRef right_indices = LLVMConstVector(&indices[num_elems/2], cast(unsigned)num_remaining); + LLVMValueRef right_value = LLVMBuildShuffleVector(p->builder, remaining, remaining, right_indices, ""); + remaining = LLVMBuildBinOp(p->builder, op_code, left_value, right_value, ""); + } + + res.value = LLVMBuildExtractElement(p->builder, remaining, indices[0], ""); + return res; + } + case BuiltinProc_simd_reduce_min: case BuiltinProc_simd_reduce_max: case BuiltinProc_simd_reduce_and: -- cgit v1.2.3 From 83bc2d3c4a186d6a8c362eed901acd6bc6363a8d Mon Sep 17 00:00:00 2001 From: Lucas Perlind Date: Wed, 30 Apr 2025 19:21:00 +1000 Subject: Add asan support for various allocators --- base/runtime/default_temp_allocator_arena.odin | 8 +++ base/runtime/heap_allocator_windows.odin | 12 +++- base/runtime/internal.odin | 9 +++ base/sanitizer/address.odin | 84 +++++++++++++++++++++- base/sanitizer/doc.odin | 4 +- core/mem/rollback_stack_allocator.odin | 50 +++++++++----- core/mem/tlsf/tlsf.odin | 2 +- core/mem/tlsf/tlsf_internal.odin | 96 ++++++++++++++------------ core/mem/tracking_allocator.odin | 11 ++- core/mem/virtual/arena.odin | 43 +++++++++--- core/mem/virtual/virtual.odin | 25 ++++--- core/mem/virtual/virtual_platform.odin | 3 + core/mem/virtual/virtual_windows.odin | 12 +++- src/llvm_backend.hpp | 2 + src/llvm_backend_general.cpp | 7 ++ src/llvm_backend_proc.cpp | 24 ++++--- src/llvm_backend_stmt.cpp | 16 +++++ 17 files changed, 308 insertions(+), 100 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/base/runtime/default_temp_allocator_arena.odin b/base/runtime/default_temp_allocator_arena.odin index 5f25dac95..74994344a 100644 --- a/base/runtime/default_temp_allocator_arena.odin +++ b/base/runtime/default_temp_allocator_arena.odin @@ -1,6 +1,7 @@ package runtime import "base:intrinsics" +import "base:sanitizer" DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) @@ -43,6 +44,8 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint block.base = ([^]byte)(uintptr(block) + base_offset) block.capacity = uint(end - uintptr(block.base)) + sanitizer.address_poison(block.base, block.capacity) + // Should be zeroed assert(block.used == 0) assert(block.prev == nil) @@ -52,6 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { if block_to_free != nil { allocator := block_to_free.allocator + sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity) mem_free(block_to_free, allocator, loc) } } @@ -83,6 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) return } data = block.base[block.used+alignment_offset:][:min_size] + sanitizer.address_unpoison(block.base[block.used:block.used+size]) block.used += size return } @@ -162,6 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { if arena.curr_block != nil { intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) arena.curr_block.used = 0 + sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity) } arena.total_used = 0 } @@ -226,6 +232,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, // grow data in-place, adjusting next allocation block.used = uint(new_end) data = block.base[start:new_end] + sanitizer.address_unpoison(data) return } } @@ -299,6 +306,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) amount_to_zero := block.used-temp.used intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) + sanitizer.address_poison(block.base[temp.used:block.capacity]) block.used = temp.used arena.total_used -= amount_to_zero } diff --git a/base/runtime/heap_allocator_windows.odin b/base/runtime/heap_allocator_windows.odin index e07df7559..04a6f149b 100644 --- a/base/runtime/heap_allocator_windows.odin +++ b/base/runtime/heap_allocator_windows.odin @@ -1,5 +1,7 @@ package runtime +import "../sanitizer" + foreign import kernel32 "system:Kernel32.lib" @(private="file") @@ -16,7 +18,10 @@ foreign kernel32 { _heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { HEAP_ZERO_MEMORY :: 0x00000008 - return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) + ptr := HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) + // NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves + sanitizer.address_unpoison(ptr, size) + return ptr } _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { if new_size == 0 { @@ -28,7 +33,10 @@ _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { } HEAP_ZERO_MEMORY :: 0x00000008 - return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) + new_ptr := HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) + // NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves + sanitizer.address_unpoison(new_ptr, new_size) + return new_ptr } _heap_free :: proc "contextless" (ptr: rawptr) { if ptr == nil { diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 59811a525..7c8a8294b 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1106,3 +1106,12 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin dst[j>>3] |= the_bit<<(j&7) } } + +@(no_sanitize_address) +__asan_unpoison_memory_region :: #force_inline proc "contextless" (address: rawptr, size: uint) { + foreign { + __asan_unpoison_memory_region :: proc(address: rawptr, size: uint) --- + } + __asan_unpoison_memory_region(address, size) +} + diff --git a/base/sanitizer/address.odin b/base/sanitizer/address.odin index 3924e02bf..edfdfc172 100644 --- a/base/sanitizer/address.odin +++ b/base/sanitizer/address.odin @@ -60,6 +60,7 @@ poison or unpoison memory in the same memory region region simultaneously. When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_poison_slice :: proc "contextless" (region: $T/[]$E) { when ASAN_ENABLED { __asan_poison_memory_region(raw_data(region), size_of(E) * len(region)) @@ -75,6 +76,7 @@ can poison or unpoison memory in the same memory region region simultaneously. When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_unpoison_slice :: proc "contextless" (region: $T/[]$E) { when ASAN_ENABLED { __asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region)) @@ -90,6 +92,7 @@ two threads can poison or unpoison memory in the same memory region region simul When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_poison_ptr :: proc "contextless" (ptr: ^$T) { when ASAN_ENABLED { __asan_poison_memory_region(ptr, size_of(T)) @@ -106,6 +109,7 @@ region simultaneously. When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_unpoison_ptr :: proc "contextless" (ptr: ^$T) { when ASAN_ENABLED { __asan_unpoison_memory_region(ptr, size_of(T)) @@ -121,6 +125,7 @@ poison or unpoison memory in the same memory region region simultaneously. When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) { when ASAN_ENABLED { assert_contextless(len >= 0) @@ -128,6 +133,22 @@ address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) { } } +/* +Marks the region covering `[ptr, ptr+len)` as unaddressable + +Code instrumented with `-sanitize:address` is forbidden from accessing any address +within the region. This procedure is not thread-safe because no two threads can +poison or unpoison memory in the same memory region region simultaneously. + +When asan is not enabled this procedure does nothing. +*/ +@(no_sanitize_address) +address_poison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) { + when ASAN_ENABLED { + __asan_poison_memory_region(ptr, len) + } +} + /* Marks the region covering `[ptr, ptr+len)` as addressable @@ -137,6 +158,7 @@ threads can poison or unpoison memory in the same memory region region simultane When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) { when ASAN_ENABLED { assert_contextless(len >= 0) @@ -144,16 +166,34 @@ address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) { } } +/* +Marks the region covering `[ptr, ptr+len)` as addressable + +Code instrumented with `-sanitize:address` is allowed to access any address +within the region again. This procedure is not thread-safe because no two +threads can poison or unpoison memory in the same memory region region simultaneously. + +When asan is not enabled this procedure does nothing. +*/ +@(no_sanitize_address) +address_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) { + when ASAN_ENABLED { + __asan_unpoison_memory_region(ptr, len) + } +} + address_poison :: proc { address_poison_slice, address_poison_ptr, address_poison_rawptr, + address_poison_rawptr_uint, } address_unpoison :: proc { address_unpoison_slice, address_unpoison_ptr, address_unpoison_rawptr, + address_unpoison_rawptr_uint, } /* @@ -164,6 +204,7 @@ This can be used for logging and/or debugging purposes. When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) { when ASAN_ENABLED { __sanitizer_set_death_callback(callback) @@ -178,7 +219,8 @@ in an asan error. When asan is not enabled this procedure returns `nil`. */ -address_region_is_poisoned_slice :: proc "contextless" (region: []$T/$E) -> rawptr { +@(no_sanitize_address) +address_region_is_poisoned_slice :: proc "contextless" (region: $T/[]$E) -> rawptr { when ASAN_ENABLED { return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region)) } else { @@ -194,6 +236,7 @@ in an asan error. When asan is not enabled this procedure returns `nil`. */ +@(no_sanitize_address) address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr { when ASAN_ENABLED { return __asan_region_is_poisoned(ptr, size_of(T)) @@ -210,6 +253,7 @@ in an asan error. When asan is not enabled this procedure returns `nil`. */ +@(no_sanitize_address) address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr { when ASAN_ENABLED { assert_contextless(len >= 0) @@ -219,10 +263,29 @@ address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: in } } +/* +Checks if the memory region covered by `[ptr, ptr+len)` is poisoned. + +If it is poisoned this procedure returns the address which would result +in an asan error. + +When asan is not enabled this procedure returns `nil`. +*/ +@(no_sanitize_address) +address_region_is_poisoned_rawptr_uint :: proc "contextless" (region: rawptr, len: uint) -> rawptr { + when ASAN_ENABLED { + return __asan_region_is_poisoned(region, len) + } else { + return nil + } +} + + address_region_is_poisoned :: proc { address_region_is_poisoned_slice, address_region_is_poisoned_ptr, address_region_is_poisoned_rawptr, + address_region_is_poisoned_rawptr_uint, } /* @@ -233,6 +296,7 @@ If it is poisoned this procedure returns `true`, otherwise it returns When asan is not enabled this procedure returns `false`. */ +@(no_sanitize_address) address_is_poisoned :: proc "contextless" (address: rawptr) -> bool { when ASAN_ENABLED { return __asan_address_is_poisoned(address) != 0 @@ -248,6 +312,7 @@ This procedure prints the description out to `stdout`. When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_describe_address :: proc "contextless" (address: rawptr) { when ASAN_ENABLED { __asan_describe_address(address) @@ -260,6 +325,7 @@ Returns `true` if an asan error has occured, otherwise it returns When asan is not enabled this procedure returns `false`. */ +@(no_sanitize_address) address_report_present :: proc "contextless" () -> bool { when ASAN_ENABLED { return __asan_report_present() != 0 @@ -275,6 +341,7 @@ If no asan error has occurd `nil` is returned. When asan is not enabled this procedure returns `nil`. */ +@(no_sanitize_address) address_get_report_pc :: proc "contextless" () -> rawptr { when ASAN_ENABLED { return __asan_get_report_pc() @@ -290,6 +357,7 @@ If no asan error has occurd `nil` is returned. When asan is not enabled this procedure returns `nil`. */ +@(no_sanitize_address) address_get_report_bp :: proc "contextless" () -> rawptr { when ASAN_ENABLED { return __asan_get_report_bp() @@ -305,6 +373,7 @@ If no asan error has occurd `nil` is returned. When asan is not enabled this procedure returns `nil`. */ +@(no_sanitize_address) address_get_report_sp :: proc "contextless" () -> rawptr { when ASAN_ENABLED { return __asan_get_report_sp() @@ -320,6 +389,7 @@ If no asan error has occurd `nil` is returned. When asan is not enabled this procedure returns `nil`. */ +@(no_sanitize_address) address_get_report_address :: proc "contextless" () -> rawptr { when ASAN_ENABLED { return __asan_get_report_address() @@ -335,6 +405,7 @@ If no asan error has occurd `.none` is returned. When asan is not enabled this procedure returns `.none`. */ +@(no_sanitize_address) address_get_report_access_type :: proc "contextless" () -> Address_Access_Type { when ASAN_ENABLED { if ! address_report_present() { @@ -353,6 +424,7 @@ If no asan error has occurd `0` is returned. When asan is not enabled this procedure returns `0`. */ +@(no_sanitize_address) address_get_report_access_size :: proc "contextless" () -> uint { when ASAN_ENABLED { return __asan_get_report_access_size() @@ -368,6 +440,7 @@ If no asan error has occurd an empty string is returned. When asan is not enabled this procedure returns an empty string. */ +@(no_sanitize_address) address_get_report_description :: proc "contextless" () -> string { when ASAN_ENABLED { return string(__asan_get_report_description()) @@ -386,6 +459,7 @@ The information provided include: When asan is not enabled this procedure returns zero initialised values. */ +@(no_sanitize_address) address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address { when ASAN_ENABLED { out_addr: rawptr @@ -404,6 +478,7 @@ The stack trace is filled into the `data` slice. When asan is not enabled this procedure returns a zero initialised value. */ +@(no_sanitize_address) address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) { when ASAN_ENABLED { out_thread: i32 @@ -421,6 +496,7 @@ The stack trace is filled into the `data` slice. When asan is not enabled this procedure returns zero initialised values. */ +@(no_sanitize_address) address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) { when ASAN_ENABLED { out_thread: i32 @@ -436,6 +512,7 @@ Returns the current asan shadow memory mapping. When asan is not enabled this procedure returns a zero initialised value. */ +@(no_sanitize_address) address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping { when ASAN_ENABLED { result: Address_Shadow_Mapping @@ -451,6 +528,7 @@ Prints asan statistics to `stderr` When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_print_accumulated_stats :: proc "contextless" () { when ASAN_ENABLED { __asan_print_accumulated_stats() @@ -464,6 +542,7 @@ This pointer can be then used for `address_is_in_fake_stack`. When asan is not enabled this procedure returns `nil`. */ +@(no_sanitize_address) address_get_current_fake_stack :: proc "contextless" () -> rawptr { when ASAN_ENABLED { return __asan_get_current_fake_stack() @@ -477,6 +556,7 @@ Returns if an address belongs to a given fake stack and if so the region of the When asan is not enabled this procedure returns zero initialised values. */ +@(no_sanitize_address) address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) { when ASAN_ENABLED { begin: rawptr @@ -496,6 +576,7 @@ i.e. a procedure such as `panic` and `os.exit`. When asan is not enabled this procedure does nothing. */ +@(no_sanitize_address) address_handle_no_return :: proc "contextless" () { when ASAN_ENABLED { __asan_handle_no_return() @@ -509,6 +590,7 @@ Returns `true` if successful, otherwise it returns `false`. When asan is not enabled this procedure returns `false`. */ +@(no_sanitize_address) address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool { when ASAN_ENABLED { return __asan_update_allocation_context(addr) != 0 diff --git a/base/sanitizer/doc.odin b/base/sanitizer/doc.odin index e389842b1..707f41ce0 100644 --- a/base/sanitizer/doc.odin +++ b/base/sanitizer/doc.odin @@ -14,12 +14,14 @@ related bugs. Typically asan interacts with libc but Odin code can be marked up with the asan runtime to extend the memory error detection outside of libc using this package. For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html +Procedures can be made exempt from asan when marked up with @(no_sanitize_address) + ## Memory Enabled with `-sanitize:memory` when building an odin project. The memory sanitizer is another runtime memory error detector with the sole purpose to catch the -use of uninitialized memory. This is not a very common bug in Odin as be default everything is +use of uninitialized memory. This is not a very common bug in Odin as by default everything is set to zero when initialised (ZII). For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index 43ef10fe9..a00131b7f 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -1,6 +1,7 @@ package mem import "base:runtime" +import "base:sanitizer" /* Rollback stack default block size. @@ -47,14 +48,14 @@ Rollback_Stack :: struct { block_allocator: Allocator, } -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { start := raw_data(block.buffer) end := start[block.offset:] return start < ptr && ptr <= end } -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( parent: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block, @@ -71,7 +72,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( return nil, nil, nil, .Invalid_Pointer } -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header, @@ -86,9 +87,10 @@ rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( return nil, nil, false } -@(private="file") +@(private="file", no_sanitize_address) rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) { header := header + for block.offset > 0 && header.is_free { block.offset = header.prev_offset block.last_alloc = raw_data(block.buffer)[header.prev_ptr:] @@ -99,9 +101,10 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_ /* Free memory to a rollback stack allocator. */ -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { parent, block, header := rb_find_ptr(stack, ptr) or_return + if header.is_free { return .Invalid_Pointer } @@ -120,7 +123,7 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { /* Free all memory owned by the rollback stack allocator. */ -@(private="file") +@(private="file", no_sanitize_address) rb_free_all :: proc(stack: ^Rollback_Stack) { for block := stack.head.next_block; block != nil; /**/ { next_block := block.next_block @@ -131,12 +134,13 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { stack.head.next_block = nil stack.head.last_alloc = nil stack.head.offset = 0 + sanitizer.address_poison(stack.head.buffer) } /* Allocate memory using the rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_alloc :: proc( stack: ^Rollback_Stack, size: int, @@ -153,7 +157,7 @@ rb_alloc :: proc( /* Allocate memory using the rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_alloc_bytes :: proc( stack: ^Rollback_Stack, size: int, @@ -170,7 +174,7 @@ rb_alloc_bytes :: proc( /* Allocate non-initialized memory using the rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_alloc_non_zeroed :: proc( stack: ^Rollback_Stack, size: int, @@ -184,7 +188,7 @@ rb_alloc_non_zeroed :: proc( /* Allocate non-initialized memory using the rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_alloc_bytes_non_zeroed :: proc( stack: ^Rollback_Stack, size: int, @@ -194,6 +198,7 @@ rb_alloc_bytes_non_zeroed :: proc( assert(size >= 0, "Size must be positive or zero.", loc) assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) parent: ^Rollback_Stack_Block + for block := stack.head; /**/; block = block.next_block { when !ODIN_DISABLE_ASSERT { allocated_new_block: bool @@ -235,7 +240,9 @@ rb_alloc_bytes_non_zeroed :: proc( // Prevent any further allocations on it. block.offset = cast(uintptr)len(block.buffer) } - #no_bounds_check return ptr[:size], nil + res := ptr[:size] + sanitizer.address_unpoison(res) + return res, nil } return nil, .Out_Of_Memory } @@ -243,7 +250,7 @@ rb_alloc_bytes_non_zeroed :: proc( /* Resize an allocation owned by rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_resize :: proc( stack: ^Rollback_Stack, old_ptr: rawptr, @@ -266,7 +273,7 @@ rb_resize :: proc( /* Resize an allocation owned by rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_resize_bytes :: proc( stack: ^Rollback_Stack, old_memory: []byte, @@ -289,7 +296,7 @@ rb_resize_bytes :: proc( Resize an allocation owned by rollback stack allocator without explicit zero-initialization. */ -@(require_results) +@(require_results, no_sanitize_address) rb_resize_non_zeroed :: proc( stack: ^Rollback_Stack, old_ptr: rawptr, @@ -306,7 +313,7 @@ rb_resize_non_zeroed :: proc( Resize an allocation owned by rollback stack allocator without explicit zero-initialization. */ -@(require_results) +@(require_results, no_sanitize_address) rb_resize_bytes_non_zeroed :: proc( stack: ^Rollback_Stack, old_memory: []byte, @@ -330,7 +337,9 @@ rb_resize_bytes_non_zeroed :: proc( if len(block.buffer) <= stack.block_size { block.offset += cast(uintptr)size - cast(uintptr)old_size } - #no_bounds_check return (ptr)[:size], nil + res := (ptr)[:size] + sanitizer.address_unpoison(res) + #no_bounds_check return res, nil } } } @@ -340,7 +349,7 @@ rb_resize_bytes_non_zeroed :: proc( return } -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return block = cast(^Rollback_Stack_Block)raw_data(buffer) @@ -351,6 +360,7 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac /* Initialize the rollback stack allocator using a fixed backing buffer. */ +@(no_sanitize_address) rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) @@ -365,6 +375,7 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc /* Initialize the rollback stack alocator using a backing block allocator. */ +@(no_sanitize_address) rollback_stack_init_dynamic :: proc( stack: ^Rollback_Stack, block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, @@ -396,6 +407,7 @@ rollback_stack_init :: proc { /* Destroy a rollback stack. */ +@(no_sanitize_address) rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { if stack.block_allocator.procedure != nil { rb_free_all(stack) @@ -435,7 +447,7 @@ from the last allocation backwards. Each allocation has an overhead of 8 bytes and any extra bytes to satisfy the requested alignment. */ -@(require_results) +@(require_results, no_sanitize_address) rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { return Allocator { data = stack, @@ -443,7 +455,7 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { } } -@(require_results) +@(require_results, no_sanitize_address) rollback_stack_allocator_proc :: proc( allocator_data: rawptr, mode: Allocator_Mode, diff --git a/core/mem/tlsf/tlsf.odin b/core/mem/tlsf/tlsf.odin index 4ce6e54d9..0ae8c28e0 100644 --- a/core/mem/tlsf/tlsf.odin +++ b/core/mem/tlsf/tlsf.odin @@ -198,4 +198,4 @@ fls :: proc "contextless" (word: u32) -> (bit: i32) { fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { N :: (size_of(uint) * 8) - 1 return i32(N - intrinsics.count_leading_zeros(size)) -} \ No newline at end of file +} diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin index f8a9bf60c..89b875679 100644 --- a/core/mem/tlsf/tlsf_internal.odin +++ b/core/mem/tlsf/tlsf_internal.odin @@ -10,6 +10,7 @@ package mem_tlsf import "base:intrinsics" +import "base:sanitizer" import "base:runtime" // log2 of number of linear subdivisions of block sizes. @@ -209,6 +210,8 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> return nil, .Out_Of_Memory } + sanitizer.address_poison(new_pool_buf) + // Allocate a new link in the `control.pool` tracking structure. new_pool := new_clone(Pool{ data = new_pool_buf, @@ -254,7 +257,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> return block_prepare_used(control, block, adjust) } -@(private, require_results) +@(private, require_results, no_sanitize_address) alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { res, err = alloc_bytes_non_zeroed(control, size, align) if err == nil { @@ -273,6 +276,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) { block := block_from_ptr(ptr) assert(!block_is_free(block), "block already marked as free") // double free + sanitizer.address_poison(ptr, block.size) block_mark_as_free(block) block = block_merge_prev(control, block) block = block_merge_next(control, block) @@ -316,6 +320,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align block_trim_used(control, block, adjust) res = ([^]byte)(ptr)[:new_size] + sanitizer.address_unpoison(res) if min_size < new_size { to_zero := ([^]byte)(ptr)[min_size:new_size] @@ -374,95 +379,96 @@ resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31. */ -@(private, require_results) +@(private, require_results, no_sanitize_address) block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) { return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE) } -@(private) +@(private, no_sanitize_address) block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) { old_size := block.size block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)) } -@(private, require_results) +@(private, require_results, no_sanitize_address) block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) { return block_size(block) == 0 } -@(private, require_results) +@(private, require_results, no_sanitize_address) block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) { return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE } -@(private) +@(private, no_sanitize_address) block_set_free :: proc "contextless" (block: ^Block_Header) { block.size |= BLOCK_HEADER_FREE } -@(private) +@(private, no_sanitize_address) block_set_used :: proc "contextless" (block: ^Block_Header) { block.size &~= BLOCK_HEADER_FREE } -@(private, require_results) +@(private, require_results, no_sanitize_address) block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) { return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE } -@(private) +@(private, no_sanitize_address) block_set_prev_free :: proc "contextless" (block: ^Block_Header) { block.size |= BLOCK_HEADER_PREV_FREE } -@(private) +@(private, no_sanitize_address) block_set_prev_used :: proc "contextless" (block: ^Block_Header) { block.size &~= BLOCK_HEADER_PREV_FREE } -@(private, require_results) +@(private, require_results, no_sanitize_address) block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) { return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET) } -@(private, require_results) +@(private, require_results, no_sanitize_address) block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) { return rawptr(uintptr(block) + BLOCK_START_OFFSET) } // Return location of next block after block of given size. -@(private, require_results) +@(private, require_results, no_sanitize_address) offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { return (^Block_Header)(uintptr(ptr) + uintptr(size)) } -@(private, require_results) +@(private, require_results, no_sanitize_address) offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { return (^Block_Header)(uintptr(ptr) - uintptr(size)) } // Return location of previous block. -@(private, require_results) +@(private, require_results, no_sanitize_address) block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) { assert(block_is_prev_free(block), "previous block must be free") + return block.prev_phys_block } // Return location of next existing block. -@(private, require_results) +@(private, require_results, no_sanitize_address) block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD) } // Link a new block with its physical neighbor, return the neighbor. -@(private, require_results) +@(private, require_results, no_sanitize_address) block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { next = block_next(block) next.prev_phys_block = block return } -@(private) +@(private, no_sanitize_address) block_mark_as_free :: proc(block: ^Block_Header) { // Link the block to the next block, first. next := block_link_next(block) @@ -470,26 +476,26 @@ block_mark_as_free :: proc(block: ^Block_Header) { block_set_free(block) } -@(private) -block_mark_as_used :: proc(block: ^Block_Header) { +@(private, no_sanitize_address) +block_mark_as_used :: proc(block: ^Block_Header, ) { next := block_next(block) block_set_prev_used(next) block_set_used(block) } -@(private, require_results) +@(private, require_results, no_sanitize_address) align_up :: proc(x, align: uint) -> (aligned: uint) { assert(0 == (align & (align - 1)), "must align to a power of two") return (x + (align - 1)) &~ (align - 1) } -@(private, require_results) +@(private, require_results, no_sanitize_address) align_down :: proc(x, align: uint) -> (aligned: uint) { assert(0 == (align & (align - 1)), "must align to a power of two") return x - (x & (align - 1)) } -@(private, require_results) +@(private, require_results, no_sanitize_address) align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { assert(0 == (align & (align - 1)), "must align to a power of two") align_mask := uintptr(align) - 1 @@ -499,7 +505,7 @@ align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { } // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. -@(private, require_results) +@(private, require_results, no_sanitize_address) adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { if size == 0 { return 0 @@ -513,7 +519,7 @@ adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { } // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. -@(private, require_results) +@(private, require_results, no_sanitize_address) adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) { if size == 0 { return 0, nil @@ -531,7 +537,7 @@ adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: // TLSF utility functions. In most cases these are direct translations of // the documentation in the research paper. -@(optimization_mode="favor_size", private, require_results) +@(optimization_mode="favor_size", private, require_results, no_sanitize_address) mapping_insert :: proc(size: uint) -> (fl, sl: i32) { if size < SMALL_BLOCK_SIZE { // Store small blocks in first list. @@ -544,7 +550,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) { return } -@(optimization_mode="favor_size", private, require_results) +@(optimization_mode="favor_size", private, require_results, no_sanitize_address) mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { rounded = size if size >= SMALL_BLOCK_SIZE { @@ -555,12 +561,12 @@ mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { } // This version rounds up to the next block size (for allocations) -@(optimization_mode="favor_size", private, require_results) +@(optimization_mode="favor_size", private, require_results, no_sanitize_address) mapping_search :: proc(size: uint) -> (fl, sl: i32) { return mapping_insert(mapping_round(size)) } -@(private, require_results) +@(private, require_results, no_sanitize_address) search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) { // First, search for a block in the list associated with the given fl/sl index. fl := fli^; sl := sli^ @@ -587,7 +593,7 @@ search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^B } // Remove a free block from the free list. -@(private) +@(private, no_sanitize_address) remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { prev := block.prev_free next := block.next_free @@ -613,7 +619,7 @@ remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl } // Insert a free block into the free block list. -@(private) +@(private, no_sanitize_address) insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { current := control.blocks[fl][sl] assert(current != nil, "free lists cannot have a nil entry") @@ -631,26 +637,26 @@ insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl } // Remove a given block from the free list. -@(private) +@(private, no_sanitize_address) block_remove :: proc(control: ^Allocator, block: ^Block_Header) { fl, sl := mapping_insert(block_size(block)) remove_free_block(control, block, fl, sl) } // Insert a given block into the free list. -@(private) +@(private, no_sanitize_address) block_insert :: proc(control: ^Allocator, block: ^Block_Header) { fl, sl := mapping_insert(block_size(block)) insert_free_block(control, block, fl, sl) } -@(private, require_results) +@(private, require_results, no_sanitize_address) block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) { return block_size(block) >= size_of(Block_Header) + size } // Split a block into two, the second of which is free. -@(private, require_results) +@(private, require_results, no_sanitize_address) block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { // Calculate the amount of space left in the remaining block. remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD) @@ -671,9 +677,10 @@ block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Head } // Absorb a free block's storage into an adjacent previous free block. -@(private, require_results) +@(private, require_results, no_sanitize_address) block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) { assert(!block_is_last(prev), "previous block can't be last") + // Note: Leaves flags untouched. prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD _ = block_link_next(prev) @@ -681,7 +688,7 @@ block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^B } // Merge a just-freed block with an adjacent previous free block. -@(private, require_results) +@(private, require_results, no_sanitize_address) block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { merged = block if (block_is_prev_free(block)) { @@ -695,7 +702,7 @@ block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: } // Merge a just-freed block with an adjacent free block. -@(private, require_results) +@(private, require_results, no_sanitize_address) block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { merged = block next := block_next(block) @@ -710,7 +717,7 @@ block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: } // Trim any trailing block space off the end of a free block, return to pool. -@(private) +@(private, no_sanitize_address) block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { assert(block_is_free(block), "block must be free") if (block_can_split(block, size)) { @@ -722,7 +729,7 @@ block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { } // Trim any trailing block space off the end of a used block, return to pool. -@(private) +@(private, no_sanitize_address) block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { assert(!block_is_free(block), "Block must be used") if (block_can_split(block, size)) { @@ -736,7 +743,7 @@ block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { } // Trim leading block space, return to pool. -@(private, require_results) +@(private, require_results, no_sanitize_address) block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { remaining = block if block_can_split(block, size) { @@ -750,7 +757,7 @@ block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: return remaining } -@(private, require_results) +@(private, require_results, no_sanitize_address) block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) { fl, sl: i32 if size != 0 { @@ -774,13 +781,14 @@ block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Hea return block } -@(private, require_results) +@(private, require_results, no_sanitize_address) block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) { if block != nil { assert(size != 0, "Size must be non-zero") block_trim_free(control, block, size) block_mark_as_used(block) res = ([^]byte)(block_to_ptr(block))[:size] + sanitizer.address_unpoison(res) } return -} \ No newline at end of file +} diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index 25c547471..01080075e 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -64,6 +64,7 @@ This procedure initializes the tracking allocator `t` with a backing allocator specified with `backing_allocator`. The `internals_allocator` will used to allocate the tracked data. */ +@(no_sanitize_address) tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { t.backing = backing_allocator t.allocation_map.allocator = internals_allocator @@ -77,6 +78,7 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc /* Destroy the tracking allocator. */ +@(no_sanitize_address) tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { delete(t.allocation_map) delete(t.bad_free_array) @@ -90,6 +92,7 @@ This procedure clears the tracked data from a tracking allocator. **Note**: This procedure clears only the current allocation data while keeping the totals intact. */ +@(no_sanitize_address) tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -103,6 +106,7 @@ Reset the tracking allocator. Reset all of a Tracking Allocator's allocation data back to zero. */ +@(no_sanitize_address) tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -124,6 +128,7 @@ Override Tracking_Allocator.bad_free_callback to have something else happen. For example, you can use tracking_allocator_bad_free_callback_add_to_array to return the tracking allocator to the old behavior, where the bad_free_array was used. */ +@(no_sanitize_address) tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { runtime.print_caller_location(location) runtime.print_string(" Tracking allocator error: Bad free of pointer ") @@ -136,6 +141,7 @@ tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memor Alternative behavior for a bad free: Store in `bad_free_array`. If you use this, then you must make sure to check Tracking_Allocator.bad_free_array at some point. */ +@(no_sanitize_address) tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry { memory = memory, @@ -175,7 +181,7 @@ Example: } } */ -@(require_results) +@(require_results, no_sanitize_address) tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { return Allocator{ data = data, @@ -183,6 +189,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { } } +@(no_sanitize_address) tracking_allocator_proc :: proc( allocator_data: rawptr, mode: Allocator_Mode, @@ -191,6 +198,7 @@ tracking_allocator_proc :: proc( old_size: int, loc := #caller_location, ) -> (result: []byte, err: Allocator_Error) { + @(no_sanitize_address) track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { data.total_memory_allocated += i64(entry.size) data.total_allocation_count += 1 @@ -200,6 +208,7 @@ tracking_allocator_proc :: proc( } } + @(no_sanitize_address) track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { data.total_memory_freed += i64(entry.size) data.total_free_count += 1 diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index 4fc2e0e35..4e1cc2466 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -3,6 +3,8 @@ package mem_virtual import "core:mem" import "core:sync" +import "base:sanitizer" + Arena_Kind :: enum uint { Growing = 0, // Chained memory blocks (singly linked list). Static = 1, // Fixed reservation sized. @@ -43,7 +45,7 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els // Initialization of an `Arena` to be a `.Growing` variant. // A growing arena is a linked list of `Memory_Block`s allocated with virtual memory. -@(require_results) +@(require_results, no_sanitize_address) arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) { arena.kind = .Growing arena.curr_block = memory_block_alloc(0, reserved, {}) or_return @@ -53,24 +55,26 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING if arena.minimum_block_size == 0 { arena.minimum_block_size = reserved } + sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) return } // Initialization of an `Arena` to be a `.Static` variant. // A static arena contains a single `Memory_Block` allocated with virtual memory. -@(require_results) +@(require_results, no_sanitize_address) arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) { arena.kind = .Static arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return arena.total_used = 0 arena.total_reserved = arena.curr_block.reserved + sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) return } // Initialization of an `Arena` to be a `.Buffer` variant. // A buffer arena contains single `Memory_Block` created from a user provided []byte. -@(require_results) +@(require_results, no_sanitize_address) arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) { if len(buffer) < size_of(Memory_Block) { return .Out_Of_Memory @@ -78,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro arena.kind = .Buffer - mem.zero_slice(buffer) + sanitizer.address_poison(buffer[:]) block_base := raw_data(buffer) block := (^Memory_Block)(block_base) @@ -94,7 +98,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro } // Allocates memory from the provided arena. -@(require_results) +@(require_results, no_sanitize_address) arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc) @@ -158,10 +162,13 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0) arena.total_used = arena.curr_block.used } + + sanitizer.address_unpoison(data) return } // Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory. +@(no_sanitize_address) arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool { sync.mutex_guard(&arena.mutex) @@ -175,6 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos]) } arena.total_used = arena.curr_block.used + sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) return true } else if pos == 0 { arena.total_used = 0 @@ -184,6 +192,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) } // Frees the last memory block of a Growing Arena +@(no_sanitize_address) arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) { if free_block := arena.curr_block; free_block != nil { assert(arena.kind == .Growing, "expected a .Growing arena", loc) @@ -191,11 +200,13 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat arena.total_reserved -= free_block.reserved arena.curr_block = free_block.prev + sanitizer.address_poison(free_block.base[:free_block.committed]) memory_block_dealloc(free_block) } } // Deallocates all but the first memory block of the arena and resets the allocator's usage to 0. +@(no_sanitize_address) arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { switch arena.kind { case .Growing: @@ -208,7 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { if arena.curr_block != nil { curr_block_used := int(arena.curr_block.used) arena.curr_block.used = 0 + sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used]) mem.zero(arena.curr_block.base, curr_block_used) + sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) } arena.total_used = 0 case .Static, .Buffer: @@ -219,6 +232,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { // Frees all of the memory allocated by the arena and zeros all of the values of an arena. // A buffer based arena does not `delete` the provided `[]byte` bufffer. +@(no_sanitize_address) arena_destroy :: proc(arena: ^Arena, loc := #caller_location) { sync.mutex_guard(&arena.mutex) switch arena.kind { @@ -250,7 +264,7 @@ arena_static_bootstrap_new :: proc{ } // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. -@(require_results) +@(require_results, no_sanitize_address) arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) { bootstrap: Arena bootstrap.kind = .Growing @@ -266,13 +280,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp } // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. -@(require_results) +@(require_results, no_sanitize_address) arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) { return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size) } // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. -@(require_results) +@(require_results, no_sanitize_address) arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { bootstrap: Arena bootstrap.kind = .Static @@ -288,19 +302,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt } // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. -@(require_results) +@(require_results, no_sanitize_address) arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved) } // Create an `Allocator` from the provided `Arena` -@(require_results) +@(require_results, no_sanitize_address) arena_allocator :: proc(arena: ^Arena) -> mem.Allocator { return mem.Allocator{arena_allocator_proc, arena} } // The allocator procedure used by an `Allocator` produced by `arena_allocator` +@(no_sanitize_address) arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, @@ -334,6 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, if size < old_size { // shrink data in-place data = old_data[:size] + sanitizer.address_poison(old_data[size:old_size]) return } @@ -347,6 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, _ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return arena.total_used += block.used - prev_used data = block.base[start:new_end] + sanitizer.address_unpoison(data) return } } @@ -357,6 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, return } copy(new_memory, old_data[:old_size]) + sanitizer.address_poison(old_data[:old_size]) return new_memory, nil case .Query_Features: set := (^mem.Allocator_Mode_Set)(old_memory) @@ -382,7 +400,7 @@ Arena_Temp :: struct { } // Begins the section of temporary arena memory. -@(require_results) +@(require_results, no_sanitize_address) arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) { assert(arena != nil, "nil arena", loc) sync.mutex_guard(&arena.mutex) @@ -397,6 +415,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena } // Ends the section of temporary arena memory by resetting the memory to the stored position. +@(no_sanitize_address) arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { assert(temp.arena != nil, "nil arena", loc) arena := temp.arena @@ -432,6 +451,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { } // Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position. +@(no_sanitize_address) arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { assert(temp.arena != nil, "nil arena", loc) arena := temp.arena @@ -442,6 +462,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { } // Asserts that all uses of `Arena_Temp` has been used by an `Arena` +@(no_sanitize_address) arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) { assert(arena.temp_count == 0, "Arena_Temp not been ended", loc) } diff --git a/core/mem/virtual/virtual.odin b/core/mem/virtual/virtual.odin index 4afc33813..031fb721a 100644 --- a/core/mem/virtual/virtual.odin +++ b/core/mem/virtual/virtual.odin @@ -2,6 +2,7 @@ package mem_virtual import "core:mem" import "base:intrinsics" +import "base:sanitizer" import "base:runtime" _ :: runtime @@ -14,27 +15,33 @@ platform_memory_init :: proc() { Allocator_Error :: mem.Allocator_Error -@(require_results) +@(require_results, no_sanitize_address) reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { return _reserve(size) } +@(no_sanitize_address) commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { + sanitizer.address_unpoison(data, size) return _commit(data, size) } -@(require_results) +@(require_results, no_sanitize_address) reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { data = reserve(size) or_return commit(raw_data(data), size) or_return return } +@(no_sanitize_address) decommit :: proc "contextless" (data: rawptr, size: uint) { + sanitizer.address_poison(data, size) _decommit(data, size) } +@(no_sanitize_address) release :: proc "contextless" (data: rawptr, size: uint) { + sanitizer.address_unpoison(data, size) _release(data, size) } @@ -46,13 +53,11 @@ Protect_Flag :: enum u32 { Protect_Flags :: distinct bit_set[Protect_Flag; u32] Protect_No_Access :: Protect_Flags{} +@(no_sanitize_address) protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { return _protect(data, size, flags) } - - - Memory_Block :: struct { prev: ^Memory_Block, base: [^]byte, @@ -66,13 +71,13 @@ Memory_Block_Flag :: enum u32 { Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32] -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint { result := size + align-1 return result - result%align } -@(require_results) +@(require_results, no_sanitize_address) memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) { page_size := DEFAULT_PAGE_SIZE assert(mem.is_power_of_two(uintptr(page_size))) @@ -116,8 +121,9 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags return &pmblock.block, nil } -@(require_results) +@(require_results, no_sanitize_address) alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) { + @(no_sanitize_address) calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { alignment_offset := uint(0) ptr := uintptr(block.base[block.used:]) @@ -128,6 +134,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, return alignment_offset } + @(no_sanitize_address) do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) { if block.committed - block.used < size { pmblock := (^Platform_Memory_Block)(block) @@ -172,10 +179,12 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, data = block.base[block.used+alignment_offset:][:min_size] block.used += size + sanitizer.address_unpoison(data) return } +@(no_sanitize_address) memory_block_dealloc :: proc(block_to_free: ^Memory_Block) { if block := (^Platform_Memory_Block)(block_to_free); block != nil { platform_memory_free(block) diff --git a/core/mem/virtual/virtual_platform.odin b/core/mem/virtual/virtual_platform.odin index 31e9cfca8..c9dde4e9d 100644 --- a/core/mem/virtual/virtual_platform.odin +++ b/core/mem/virtual/virtual_platform.odin @@ -7,6 +7,7 @@ Platform_Memory_Block :: struct { reserved: uint, } +@(no_sanitize_address) platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) { to_commit, to_reserve := to_commit, to_reserve to_reserve = max(to_commit, to_reserve) @@ -26,12 +27,14 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl } +@(no_sanitize_address) platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) { if block != nil { release(block, block.reserved) } } +@(no_sanitize_address) platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) { if to_commit < block.committed { return nil diff --git a/core/mem/virtual/virtual_windows.odin b/core/mem/virtual/virtual_windows.odin index acd30ae33..0da8498d5 100644 --- a/core/mem/virtual/virtual_windows.odin +++ b/core/mem/virtual/virtual_windows.odin @@ -83,6 +83,8 @@ foreign Kernel32 { dwNumberOfBytesToMap: uint, ) -> rawptr --- } + +@(no_sanitize_address) _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE) if result == nil { @@ -93,6 +95,7 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err return } +@(no_sanitize_address) _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE) if result == nil { @@ -107,12 +110,18 @@ _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { } return nil } + +@(no_sanitize_address) _decommit :: proc "contextless" (data: rawptr, size: uint) { VirtualFree(data, size, MEM_DECOMMIT) } + +@(no_sanitize_address) _release :: proc "contextless" (data: rawptr, size: uint) { VirtualFree(data, 0, MEM_RELEASE) } + +@(no_sanitize_address) _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { pflags: u32 pflags = PAGE_NOACCESS @@ -136,7 +145,7 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) } - +@(no_sanitize_address) _platform_memory_init :: proc() { sys_info: SYSTEM_INFO GetSystemInfo(&sys_info) @@ -147,6 +156,7 @@ _platform_memory_init :: proc() { } +@(no_sanitize_address) _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { page_flags: u32 if flags == {.Read} { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 6177fcf6e..de6841ed8 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -383,6 +383,8 @@ struct lbProcedure { PtrMap selector_values; PtrMap selector_addr; PtrMap tuple_fix_map; + + Array asan_stack_locals; }; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 421720c4c..dad5d4dd5 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3070,6 +3070,13 @@ gb_internal lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e, bool zero if (e != nullptr) { lb_add_entity(p->module, e, val); lb_add_debug_local_variable(p, ptr, type, e->token); + + // NOTE(lucas): In LLVM 20 and below we do not have the option to have asan cleanup poisoned stack + // locals ourselves. So we need to manually track and unpoison these locals on proc return. + // LLVM 21 adds the 'use-after-scope' asan option which does this for us. + if (build_context.sanitizer_flags & SanitizerFlag_Address && !p->entity->Procedure.no_sanitize_address) { + array_add(&p->asan_stack_locals, val); + } } if (zero_init) { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 7bd8dea59..8f1619006 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -115,12 +115,13 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i p->is_entry_point = false; gbAllocator a = heap_allocator(); - p->children.allocator = a; - p->defer_stmts.allocator = a; - p->blocks.allocator = a; - p->branch_blocks.allocator = a; - p->context_stack.allocator = a; - p->scope_stack.allocator = a; + p->children.allocator = a; + p->defer_stmts.allocator = a; + p->blocks.allocator = a; + p->branch_blocks.allocator = a; + p->context_stack.allocator = a; + p->scope_stack.allocator = a; + p->asan_stack_locals.allocator = a; // map_init(&p->selector_values, 0); // map_init(&p->selector_addr, 0); // map_init(&p->tuple_fix_map, 0); @@ -385,11 +386,12 @@ gb_internal lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name p->is_entry_point = false; gbAllocator a = permanent_allocator(); - p->children.allocator = a; - p->defer_stmts.allocator = a; - p->blocks.allocator = a; - p->branch_blocks.allocator = a; - p->context_stack.allocator = a; + p->children.allocator = a; + p->defer_stmts.allocator = a; + p->blocks.allocator = a; + p->branch_blocks.allocator = a; + p->context_stack.allocator = a; + p->asan_stack_locals.allocator = a; map_init(&p->tuple_fix_map, 0); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 96a5d0db1..d5e3e4c75 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -2917,6 +2917,22 @@ gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlo } defer (p->branch_location_pos = prev_token_pos); + // TODO(lucas): In LLVM 21 use the 'use-after-scope' asan option which does this for us. + #if LLVM_VERSION_MAJOR < 21 + if (kind == lbDeferExit_Return) { + for_array(i, p->asan_stack_locals) { + lbValue local = p->asan_stack_locals[i]; + + auto args = array_make(temporary_allocator(), 2); + args[0] = lb_emit_conv(p, local, t_rawptr); + args[1] = lb_const_int(p->module, t_int, type_size_of(local.type->Pointer.elem)); + lb_emit_runtime_call(p, "__asan_unpoison_memory_region", args); + } + } + #else + #error "Need to implement LLVM 21 'use-after-scope' asan option" + #endif + isize count = p->defer_stmts.count; isize i = count; while (i --> 0) { -- cgit v1.2.3 From ad4866653a05a2850d60616b6d7458d4718a2c83 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 7 May 2025 14:38:40 +0200 Subject: fix disposing builder when not created Fixes #5128 p->builder is created in lb_begin_procedure_body, but that isn't called if there is no body, and we were still calling dispose at that point. Moved it into lb_end_procedure_body to match. --- src/llvm_backend.cpp | 1 - src/llvm_backend.hpp | 1 - src/llvm_backend_proc.cpp | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 2f861573a..fd10cd5c1 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2185,7 +2185,6 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { p->is_done = true; m->curr_procedure = nullptr; } - lb_end_procedure(p); // Add Flags if (p->entity && p->entity->kind == Entity_Procedure && p->entity->Procedure.is_memcpy_like) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index de6841ed8..51931dac3 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -409,7 +409,6 @@ gb_internal LLVMAttributeRef lb_create_enum_attribute_with_type(LLVMContextRef c gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name, u64 value); gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name); gb_internal lbProcedure *lb_create_procedure(lbModule *module, Entity *entity, bool ignore_body=false); -gb_internal void lb_end_procedure(lbProcedure *p); gb_internal LLVMTypeRef lb_type(lbModule *m, Type *type); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 1f023037d..057d6562f 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -783,8 +783,7 @@ gb_internal void lb_end_procedure_body(lbProcedure *p) { p->curr_block = nullptr; p->state_flags = 0; -} -gb_internal void lb_end_procedure(lbProcedure *p) { + LLVMDisposeBuilder(p->builder); } -- cgit v1.2.3 From d2d4ac8120409abbfd5be9ac9890fa2b2536f3ed Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 12 May 2025 15:29:43 +0100 Subject: Add `compress_values` --- base/builtin/builtin.odin | 3 +- src/check_builtin.cpp | 180 ++++++++++++++++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 2 + src/llvm_backend_proc.cpp | 62 +++++++++++++++ 4 files changed, 246 insertions(+), 1 deletion(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/base/builtin/builtin.odin b/base/builtin/builtin.odin index 227ceeb49..14da9603d 100644 --- a/base/builtin/builtin.odin +++ b/base/builtin/builtin.odin @@ -119,7 +119,8 @@ jmag :: proc(value: Quaternion) -> Float --- kmag :: proc(value: Quaternion) -> Float --- conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion --- -expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) --- +expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) --- +compress_values :: proc(values: ...) -> Struct_Or_Array_Like_Type --- min :: proc(values: ..T) -> T --- max :: proc(values: ..T) -> T --- diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index dd9896927..f01b61128 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3243,6 +3243,186 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } + case BuiltinProc_compress_values: { + Operand *ops = gb_alloc_array(temporary_allocator(), Operand, ce->args.count); + + isize value_count = 0; + + for_array(i, ce->args) { + Ast *arg = ce->args[i]; + Operand *op = &ops[i]; + check_multi_expr(c, op, arg); + if (op->mode == Addressing_Invalid) { + return false; + } + + if (op->type == nullptr || op->type == t_invalid) { + gbString s = expr_to_string(op->expr); + error(op->expr, "Invalid expression to '%.*s', got %s", LIT(builtin_name), s); + gb_string_free(s); + } + if (is_type_tuple(op->type)) { + value_count += op->type->Tuple.variables.count; + } else { + value_count += 1; + } + } + + GB_ASSERT(value_count >= 1); + + if (value_count == 1) { + *operand = ops[0]; + break; + } + + if (type_hint != nullptr) { + Type *th = base_type(type_hint); + if (th->kind == Type_Struct) { + if (value_count == th->Struct.fields.count) { + isize index = 0; + for_array(i, ce->args) { + Operand *op = &ops[i]; + if (is_type_tuple(op->type)) { + for (Entity *v : op->type->Tuple.variables) { + Operand x = {}; + x.mode = Addressing_Value; + x.type = v->type; + check_assignment(c, &x, th->Struct.fields[index++]->type, builtin_name); + if (x.mode == Addressing_Invalid) { + return false; + } + } + } else { + check_assignment(c, op, th->Struct.fields[index++]->type, builtin_name); + if (op->mode == Addressing_Invalid) { + return false; + } + } + } + + operand->type = type_hint; + operand->mode = Addressing_Value; + break; + } + } else if (is_type_array_like(th)) { + if (cast(i64)value_count == get_array_type_count(th)) { + Type *elem = base_array_type(th); + for_array(i, ce->args) { + Operand *op = &ops[i]; + if (is_type_tuple(op->type)) { + for (Entity *v : op->type->Tuple.variables) { + Operand x = {}; + x.mode = Addressing_Value; + x.type = v->type; + check_assignment(c, &x, elem, builtin_name); + if (x.mode == Addressing_Invalid) { + return false; + } + } + } else { + check_assignment(c, op, elem, builtin_name); + if (op->mode == Addressing_Invalid) { + return false; + } + } + } + + operand->type = type_hint; + operand->mode = Addressing_Value; + break; + } + } + } + + bool all_types_the_same = true; + Type *last_type = nullptr; + for_array(i, ce->args) { + Operand *op = &ops[i]; + if (is_type_tuple(op->type)) { + if (last_type == nullptr) { + op->type->Tuple.variables[0]->type; + } + for (Entity *v : op->type->Tuple.variables) { + if (!are_types_identical(last_type, v->type)) { + all_types_the_same = false; + break; + } + last_type = v->type; + } + } else { + if (last_type == nullptr) { + last_type = op->type; + } else { + if (!are_types_identical(last_type, op->type)) { + all_types_the_same = false; + break; + } + last_type = op->type; + } + } + } + + if (all_types_the_same) { + operand->type = alloc_type_array(last_type, value_count); + operand->mode = Addressing_Value; + } else { + Type *st = alloc_type_struct_complete(); + st->Struct.fields = slice_make(permanent_allocator(), value_count); + st->Struct.tags = gb_alloc_array(permanent_allocator(), String, value_count); + st->Struct.offsets = gb_alloc_array(permanent_allocator(), i64, value_count); + + Scope *scope = create_scope(c->info, nullptr); + + Token token = {}; + token.kind = Token_Ident; + token.pos = ast_token(call).pos; + + isize index = 0; + for_array(i, ce->args) { + Operand *op = &ops[i]; + if (is_type_tuple(op->type)) { + for (Entity *v : op->type->Tuple.variables) { + Type *t = default_type(v->type); + if (is_type_untyped(t)) { + gbString s = expr_to_string(op->expr); + error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name)); + gb_string_free(s); + return false; + } + + gbString s = gb_string_make_reserve(permanent_allocator(), 32); + s = gb_string_append_fmt(s, "v%lld", cast(long long)index); + token.string = make_string_c(s); + Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved); + st->Struct.fields[index++] = e; + } + } else { + Type *t = default_type(op->type); + if (is_type_untyped(t)) { + gbString s = expr_to_string(op->expr); + error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name)); + gb_string_free(s); + return false; + } + + gbString s = gb_string_make_reserve(permanent_allocator(), 32); + s = gb_string_append_fmt(s, "v%lld", cast(long long)index); + token.string = make_string_c(s); + Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved); + st->Struct.fields[index++] = e; + } + } + + + gb_unused(type_size_of(st)); + + operand->type = st; + operand->mode = Addressing_Value; + } + break; + } + + case BuiltinProc_min: { // min :: proc($T: typeid) -> ordered // min :: proc(a: ..ordered) -> ordered diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index c7bd3a7a7..c4e487560 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -26,6 +26,7 @@ enum BuiltinProcId { BuiltinProc_conj, BuiltinProc_expand_values, + BuiltinProc_compress_values, BuiltinProc_min, BuiltinProc_max, @@ -376,6 +377,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("conj"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("expand_values"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, + {STR_LIT("compress_values"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("min"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("max"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index ae1e87f18..f51ed2b4d 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2246,6 +2246,68 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu return lb_emit_load(p, tuple); } + case BuiltinProc_compress_values: { + isize value_count = 0; + for (Ast *arg : ce->args) { + Type *t = arg->tav.type; + if (is_type_tuple(t)) { + value_count += t->Tuple.variables.count; + } else { + value_count += 1; + } + } + + if (value_count == 1) { + lbValue x = lb_build_expr(p, ce->args[0]); + x = lb_emit_conv(p, x, tv.type); + return x; + } + + Type *dt = base_type(tv.type); + lbAddr addr = lb_add_local_generated(p, tv.type, true); + if (is_type_struct(dt) || is_type_tuple(dt)) { + i32 index = 0; + for (Ast *arg : ce->args) { + lbValue x = lb_build_expr(p, arg); + if (is_type_tuple(x.type)) { + for (isize i = 0; i < x.type->Tuple.variables.count; i++) { + lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i); + lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++); + y = lb_emit_conv(p, y, type_deref(ptr.type)); + lb_emit_store(p, ptr, y); + } + } else { + lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++); + x = lb_emit_conv(p, x, type_deref(ptr.type)); + lb_emit_store(p, ptr, x); + } + } + GB_ASSERT(index == value_count); + } else if (is_type_array_like(dt)) { + i32 index = 0; + for (Ast *arg : ce->args) { + lbValue x = lb_build_expr(p, arg); + if (is_type_tuple(x.type)) { + for (isize i = 0; i < x.type->Tuple.variables.count; i++) { + lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i); + lbValue ptr = lb_emit_array_epi(p, addr.addr, index++); + y = lb_emit_conv(p, y, type_deref(ptr.type)); + lb_emit_store(p, ptr, y); + } + } else { + lbValue ptr = lb_emit_array_epi(p, addr.addr, index++); + x = lb_emit_conv(p, x, type_deref(ptr.type)); + lb_emit_store(p, ptr, x); + } + } + GB_ASSERT(index == value_count); + } else { + GB_PANIC("TODO(bill): compress_values -> %s", type_to_string(tv.type)); + } + + return lb_addr_load(p, addr); + } + case BuiltinProc_min: { Type *t = type_of_expr(expr); if (ce->args.count == 2) { -- cgit v1.2.3 From 9c5640886d95cba73b10a59a43692c9bae4037fb Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:36:55 -0400 Subject: Add `@(no_sanitize_memory)` proc attribute with MSan additions to `base:sanitizer` --- base/sanitizer/memory.odin | 74 ++++++++++++++++++++++++++++++++++++++++++++++ src/check_decl.cpp | 1 + src/checker.cpp | 6 ++++ src/checker.hpp | 1 + src/entity.cpp | 1 + src/llvm_backend_proc.cpp | 2 +- 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 base/sanitizer/memory.odin (limited to 'src/llvm_backend_proc.cpp') diff --git a/base/sanitizer/memory.odin b/base/sanitizer/memory.odin new file mode 100644 index 000000000..b16309a49 --- /dev/null +++ b/base/sanitizer/memory.odin @@ -0,0 +1,74 @@ +#+no-instrumentation +package sanitizer + +@(private="file") +MSAN_ENABLED :: .Memory in ODIN_SANITIZER_FLAGS + +@(private="file") +@(default_calling_convention="system") +foreign { + __msan_unpoison :: proc(addr: rawptr, size: uint) --- +} + +/* +Marks a slice as fully initialized. + +Code instrumented with `-sanitize:memory` will be permitted to access any +address within the slice as if it had already been initialized. + +When msan is not enabled this procedure does nothing. +*/ +memory_unpoison_slice :: proc "contextless" (region: $T/[]$E) { + when MSAN_ENABLED { + __msan_unpoison(raw_data(region), size_of(E) * len(region)) + } +} + +/* +Marks a pointer as fully initialized. + +Code instrumented with `-sanitize:memory` will be permitted to access memory +within the region the pointer points to as if it had already been initialized. + +When msan is not enabled this procedure does nothing. +*/ +memory_unpoison_ptr :: proc "contextless" (ptr: ^$T) { + when MSAN_ENABLED { + __msan_unpoison(ptr, size_of(T)) + } +} + +/* +Marks the region covering `[ptr, ptr+len)` as fully initialized. + +Code instrumented with `-sanitize:memory` will be permitted to access memory +within this range as if it had already been initialized. + +When msan is not enabled this procedure does nothing. +*/ +memory_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) { + when MSAN_ENABLED { + __msan_unpoison(ptr, uint(len)) + } +} + +/* +Marks the region covering `[ptr, ptr+len)` as fully initialized. + +Code instrumented with `-sanitize:memory` will be permitted to access memory +within this range as if it had already been initialized. + +When msan is not enabled this procedure does nothing. +*/ +memory_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) { + when MSAN_ENABLED { + __msan_unpoison(ptr, len) + } +} + +memory_unpoison :: proc { + memory_unpoison_slice, + memory_unpoison_ptr, + memory_unpoison_rawptr, + memory_unpoison_rawptr_uint, +} diff --git a/src/check_decl.cpp b/src/check_decl.cpp index d53c3c6b7..c696fc4c1 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1370,6 +1370,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { e->Procedure.has_instrumentation = has_instrumentation; e->Procedure.no_sanitize_address = ac.no_sanitize_address; + e->Procedure.no_sanitize_memory = ac.no_sanitize_memory; e->deprecated_message = ac.deprecated_message; e->warning_message = ac.warning_message; diff --git a/src/checker.cpp b/src/checker.cpp index 9bc02cd87..4a5381014 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3776,6 +3776,12 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } ac->no_sanitize_address = true; return true; + } else if (name == "no_sanitize_memory") { + if (value != nullptr) { + error(value, "'%.*s' expects no parameter", LIT(name)); + } + ac->no_sanitize_memory = true; + return true; } return false; } diff --git a/src/checker.hpp b/src/checker.hpp index 0cdfd69ab..dabb7330a 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -140,6 +140,7 @@ struct AttributeContext { bool instrumentation_enter : 1; bool instrumentation_exit : 1; bool no_sanitize_address : 1; + bool no_sanitize_memory : 1; bool rodata : 1; bool ignore_duplicates : 1; u32 optimization_mode; // ProcedureOptimizationMode diff --git a/src/entity.cpp b/src/entity.cpp index a16779419..6c0aa6ace 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -263,6 +263,7 @@ struct Entity { bool uses_branch_location : 1; bool is_anonymous : 1; bool no_sanitize_address : 1; + bool no_sanitize_memory : 1; } Procedure; struct { Array entities; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index f51ed2b4d..0a51b5cb5 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -345,7 +345,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i if (build_context.sanitizer_flags & SanitizerFlag_Address && !entity->Procedure.no_sanitize_address) { lb_add_attribute_to_proc(m, p->value, "sanitize_address"); } - if (build_context.sanitizer_flags & SanitizerFlag_Memory) { + if (build_context.sanitizer_flags & SanitizerFlag_Memory && !entity->Procedure.no_sanitize_memory) { lb_add_attribute_to_proc(m, p->value, "sanitize_memory"); } if (build_context.sanitizer_flags & SanitizerFlag_Thread) { -- cgit v1.2.3 From 378e72b21153149d59d0f65521da1c0f0a4452c6 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 20 Jun 2025 16:11:08 -0400 Subject: Push `context` onto stack before evaluating procedure parameters Fixes #4711 --- src/llvm_backend_proc.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 0a51b5cb5..2f42e0157 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -546,6 +546,9 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { GB_ASSERT(p->type != nullptr); lb_ensure_abi_function_type(p->module, p); + if (p->type->Proc.calling_convention == ProcCC_Odin) { + lb_push_context_onto_stack_from_implicit_parameter(p); + } { lbFunctionType *ft = p->abi_function_type; @@ -743,9 +746,6 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { } } - if (p->type->Proc.calling_convention == ProcCC_Odin) { - lb_push_context_onto_stack_from_implicit_parameter(p); - } lb_set_debug_position_to_procedure_begin(p); if (p->debug_info != nullptr) { -- cgit v1.2.3 From 5b1113acb42e618cf69617ae2a6a205cd5a4e844 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 20 Jun 2025 19:43:41 -0400 Subject: Ensure `volatile` status for all atomic operations Fixes #2410 --- src/llvm_backend_proc.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 2f42e0157..9f6a1d653 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2774,15 +2774,18 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu LLVMSetMetadata(instr, kind_id, LLVMMetadataAsValue(p->module->ctx, node)); } break; - case BuiltinProc_volatile_store: LLVMSetVolatile(instr, true); break; - case BuiltinProc_atomic_store: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; + case BuiltinProc_volatile_store: + LLVMSetVolatile(instr, true); + break; + case BuiltinProc_atomic_store: + LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); + LLVMSetVolatile(instr, true); + break; case BuiltinProc_atomic_store_explicit: { auto ordering = llvm_atomic_ordering_from_odin(ce->args[2]); LLVMSetOrdering(instr, ordering); - if (ordering == LLVMAtomicOrderingUnordered) { - LLVMSetVolatile(instr, true); - } + LLVMSetVolatile(instr, true); } break; } @@ -2808,15 +2811,18 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu } break; break; - case BuiltinProc_volatile_load: LLVMSetVolatile(instr, true); break; - case BuiltinProc_atomic_load: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; + case BuiltinProc_volatile_load: + LLVMSetVolatile(instr, true); + break; + case BuiltinProc_atomic_load: + LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); + LLVMSetVolatile(instr, true); + break; case BuiltinProc_atomic_load_explicit: { auto ordering = llvm_atomic_ordering_from_odin(ce->args[1]); LLVMSetOrdering(instr, ordering); - if (ordering == LLVMAtomicOrderingUnordered) { - LLVMSetVolatile(instr, true); - } + LLVMSetVolatile(instr, true); } break; } @@ -2901,9 +2907,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu lbValue res = {}; res.value = LLVMBuildAtomicRMW(p->builder, op, dst.value, val.value, ordering, false); res.type = tv.type; - if (ordering == LLVMAtomicOrderingUnordered) { - LLVMSetVolatile(res.value, true); - } + LLVMSetVolatile(res.value, true); return res; } @@ -2939,9 +2943,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu single_threaded ); LLVMSetWeak(value, weak); - if (success_ordering == LLVMAtomicOrderingUnordered || failure_ordering == LLVMAtomicOrderingUnordered) { - LLVMSetVolatile(value, true); - } + LLVMSetVolatile(value, true); if (is_type_tuple(tv.type)) { Type *fix_typed = alloc_type_tuple(); -- cgit v1.2.3 From 019084a17fb179a823a213591b016a43620e47ce Mon Sep 17 00:00:00 2001 From: Jon Lipstate Date: Sat, 5 Jul 2025 13:55:14 -0700 Subject: table lookup intrinsic --- base/intrinsics/intrinsics.odin | 1 + core/simd/simd.odin | 51 ++++++++ src/check_builtin.cpp | 52 ++++++++ src/checker_builtin_procs.hpp | 2 + src/llvm_backend_proc.cpp | 262 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 368 insertions(+) (limited to 'src/llvm_backend_proc.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index c1d16c5e4..9edf7bcd8 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -310,6 +310,7 @@ simd_indices :: proc($T: typeid/#simd[$N]$E) -> T where type_is_numeric(T) --- simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T --- simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- +simd_table_lookup :: proc(table: #simd[N]T, indices: #simd[N]T) -> #simd[N]T where type_is_integer(T) --- // Lane-wise operations simd_ceil :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index b4779b5ff..eb4912e58 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -2440,6 +2440,57 @@ Graphically, the operation looks as follows. The `t` and `f` represent the */ select :: intrinsics.simd_select +/* +Runtime Equivalent to Shuffle. + +Performs element-wise table lookups using runtime indices. +Each element in the indices vector selects an element from the table vector. +The indices are automatically masked to prevent out-of-bounds access. + +This operation is hardware-accelerated on most platforms when using 8-bit +integer vectors. For other element types or unsupported vector sizes, it +falls back to software emulation. + +Inputs: +- `table`: The lookup table vector (should be power-of-2 size for correct masking). +- `indices`: The indices vector (automatically masked to valid range). + +Returns: +- A vector where `result[i] = table[indices[i] & (table_size-1)]`. + +Operation: + + for i in 0 ..< len(indices) { + masked_index := indices[i] & (len(table) - 1) + result[i] = table[masked_index] + } + return result + +Implementation: + + | Platform | Lane Size | Implementation | + |-------------|-------------------------------------------|---------------------| + | x86-64 | pshufb (16B), vpshufb (32B), AVX512 (64B) | Single vector | + | ARM64 | tbl1 (16B), tbl2 (32B), tbl4 (64B) | Automatic splitting | + | ARM32 | vtbl1 (8B), vtbl2 (16B), vtbl4 (32B) | Automatic splitting | + | WebAssembly | i8x16.swizzle (16B), Emulation (>16B) | Mixed | + | Other | Emulation | Software | + +Example: + + import "core:simd" + import "core:fmt" + + table_lookup_example :: proc() { + table := simd.u8x16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + indices := simd.u8x16{15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} + result := simd.table_lookup(table, indices) + fmt.println(result) // Expected: {15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} + } + +*/ +table_lookup :: intrinsics.simd_table_lookup + /* Compute the square root of each lane in a SIMD vector. */ diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 9f9787b61..c7386a97d 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1150,6 +1150,58 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return true; } + case BuiltinProc_simd_table_lookup: + { + if (ce->args.count != 2) { + error(call, "'%.*s' expected 2 arguments, got %td", LIT(builtin_name), ce->args.count); + return false; + } + + Operand table = {}; + Operand indices = {}; + check_expr(c, &table, ce->args[0]); if (table.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &indices, ce->args[1], table.type); if (indices.mode == Addressing_Invalid) return false; + + if (!is_type_simd_vector(table.type)) { + error(table.expr, "'%.*s' expected a simd vector type for table", LIT(builtin_name)); + return false; + } + if (!is_type_simd_vector(indices.type)) { + error(indices.expr, "'%.*s' expected a simd vector type for indices", LIT(builtin_name)); + return false; + } + + Type *table_elem = base_array_type(table.type); + Type *indices_elem = base_array_type(indices.type); + + if (!is_type_integer(table_elem)) { + gbString table_str = type_to_string(table.type); + error(table.expr, "'%.*s' expected table to be a simd vector of integers, got '%s'", LIT(builtin_name), table_str); + gb_string_free(table_str); + return false; + } + + if (!is_type_integer(indices_elem)) { + gbString indices_str = type_to_string(indices.type); + error(indices.expr, "'%.*s' expected indices to be a simd vector of integers, got '%s'", LIT(builtin_name), indices_str); + gb_string_free(indices_str); + return false; + } + + if (!are_types_identical(table.type, indices.type)) { + gbString table_str = type_to_string(table.type); + gbString indices_str = type_to_string(indices.type); + error(indices.expr, "'%.*s' expected table and indices to have the same type, got '%s' vs '%s'", LIT(builtin_name), table_str, indices_str); + gb_string_free(indices_str); + gb_string_free(table_str); + return false; + } + + operand->mode = Addressing_Value; + operand->type = table.type; + return true; + } + case BuiltinProc_simd_ceil: case BuiltinProc_simd_floor: case BuiltinProc_simd_trunc: diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 90652cb0b..59fc84a4e 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -191,6 +191,7 @@ BuiltinProc__simd_begin, BuiltinProc_simd_shuffle, BuiltinProc_simd_select, + BuiltinProc_simd_table_lookup, BuiltinProc_simd_ceil, BuiltinProc_simd_floor, @@ -550,6 +551,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_shuffle"), 2, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_select"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_table_lookup"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_ceil") , 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_floor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 9f6a1d653..da8e0f91c 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1721,6 +1721,268 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } + case BuiltinProc_simd_table_lookup: + { + LLVMValueRef table = arg0.value; + LLVMValueRef indices = lb_build_expr(p, ce->args[1]).value; + + Type *vt = arg0.type; + GB_ASSERT(vt->kind == Type_SimdVector); + i64 count = vt->SimdVector.count; + Type *elem_type = vt->SimdVector.elem; + i64 elem_size = type_size_of(elem_type); + + // Determine strategy based on element size and target architecture + char const *intrinsic_name = nullptr; + bool use_hardware_table_lookup = false; + + // 8-bit elements: Use dedicated table lookup instructions + if (elem_size == 1) { + use_hardware_table_lookup = true; + + if (build_context.metrics.arch == TargetArch_amd64 || build_context.metrics.arch == TargetArch_i386) { + // x86/x86-64: Use pshufb intrinsics + switch (count) { + case 16: + intrinsic_name = "llvm.x86.ssse3.pshuf.b.128"; + break; + case 32: + intrinsic_name = "llvm.x86.avx2.pshuf.b"; + break; + case 64: + intrinsic_name = "llvm.x86.avx512.pshuf.b.512"; + break; + default: + use_hardware_table_lookup = false; + break; + } + } else if (build_context.metrics.arch == TargetArch_arm64) { + // ARM64: Use NEON tbl intrinsics with automatic table splitting + switch (count) { + case 16: + intrinsic_name = "llvm.aarch64.neon.tbl1"; + break; + case 32: + intrinsic_name = "llvm.aarch64.neon.tbl2"; + break; + case 48: + intrinsic_name = "llvm.aarch64.neon.tbl3"; + break; + case 64: + intrinsic_name = "llvm.aarch64.neon.tbl4"; + break; + default: + use_hardware_table_lookup = false; + break; + } + } else if (build_context.metrics.arch == TargetArch_arm32) { + // ARM32: Use NEON vtbl intrinsics with automatic table splitting + switch (count) { + case 8: + intrinsic_name = "llvm.arm.neon.vtbl1"; + break; + case 16: + intrinsic_name = "llvm.arm.neon.vtbl2"; + break; + case 24: + intrinsic_name = "llvm.arm.neon.vtbl3"; + break; + case 32: + intrinsic_name = "llvm.arm.neon.vtbl4"; + break; + default: + use_hardware_table_lookup = false; + break; + } + } else if (build_context.metrics.arch == TargetArch_wasm32 || build_context.metrics.arch == TargetArch_wasm64p32) { + // WebAssembly: Use swizzle (only supports 16-byte vectors) + if (count == 16) { + intrinsic_name = "llvm.wasm.swizzle"; + } else { + use_hardware_table_lookup = false; + } + } else { + use_hardware_table_lookup = false; + } + } + + if (use_hardware_table_lookup && intrinsic_name != nullptr) { + // Use dedicated hardware table lookup instruction + + // Check if required target features are enabled + bool features_enabled = true; + if (build_context.metrics.arch == TargetArch_amd64 || build_context.metrics.arch == TargetArch_i386) { + // x86/x86-64 feature checking + if (count == 32) { + // AVX2 requires ssse3 + avx2 features + if (!check_target_feature_is_enabled(str_lit("ssse3"), nullptr) || + !check_target_feature_is_enabled(str_lit("avx2"), nullptr)) { + features_enabled = false; + } + } else if (count == 64) { + // AVX512 requires ssse3 + avx2 + avx512f + avx512bw features + if (!check_target_feature_is_enabled(str_lit("ssse3"), nullptr) || + !check_target_feature_is_enabled(str_lit("avx2"), nullptr) || + !check_target_feature_is_enabled(str_lit("avx512f"), nullptr) || + !check_target_feature_is_enabled(str_lit("avx512bw"), nullptr)) { + features_enabled = false; + } + } + } else if (build_context.metrics.arch == TargetArch_arm64 || build_context.metrics.arch == TargetArch_arm32) { + // ARM/ARM64 feature checking - NEON is required for all table lookups + if (!check_target_feature_is_enabled(str_lit("neon"), nullptr)) { + features_enabled = false; + } + } + + if (features_enabled) { + // Add target features to function attributes for LLVM instruction selection + if (build_context.metrics.arch == TargetArch_amd64 || build_context.metrics.arch == TargetArch_i386) { + // x86/x86-64 function attributes + if (count == 32) { + lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("target-features"), str_lit("+avx,+avx2,+ssse3")); + lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("min-legal-vector-width"), str_lit("256")); + } else if (count == 64) { + lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("target-features"), str_lit("+avx,+avx2,+avx512f,+avx512bw,+ssse3")); + lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("min-legal-vector-width"), str_lit("512")); + } + } else if (build_context.metrics.arch == TargetArch_arm64) { + // ARM64 function attributes - enable NEON for table lookup instructions + lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("target-features"), str_lit("+neon")); + // Set appropriate vector width for multi-table operations + if (count >= 32) { + lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("min-legal-vector-width"), str_lit("256")); + } + } else if (build_context.metrics.arch == TargetArch_arm32) { + // ARM32 function attributes - enable NEON for table lookup instructions + lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("target-features"), str_lit("+neon")); + } + + // Handle ARM's multi-table intrinsics by splitting the table vector + if (build_context.metrics.arch == TargetArch_arm64 && count > 16) { + // ARM64 TBL2/TBL3/TBL4: Split table into multiple 16-byte vectors + LLVMTypeRef v16i8_type = LLVMVectorType(LLVMInt8TypeInContext(p->module->ctx), 16); + int num_tables = count / 16; + GB_ASSERT_MSG(count % 16 == 0, "ARM64 table size must be multiple of 16 bytes, got %lld bytes", count); + GB_ASSERT_MSG(num_tables <= 4, "ARM64 NEON supports maximum 4 tables (tbl4), got %d tables for %lld-byte vector", num_tables, count); + + LLVMValueRef table_parts[4]; // Max 4 tables for tbl4 + for (int i = 0; i < num_tables; i++) { + // Extract 16-byte slice from the larger table + LLVMValueRef indices_for_extract[16]; + for (int j = 0; j < 16; j++) { + indices_for_extract[j] = LLVMConstInt(LLVMInt32TypeInContext(p->module->ctx), i * 16 + j, false); + } + LLVMValueRef extract_mask = LLVMConstVector(indices_for_extract, 16); + table_parts[i] = LLVMBuildShuffleVector(p->builder, table, LLVMGetUndef(LLVMTypeOf(table)), extract_mask, ""); + } + + // Call appropriate ARM64 tbl intrinsic + if (count == 32) { + LLVMValueRef args[3] = { table_parts[0], table_parts[1], indices }; + res.value = lb_call_intrinsic(p, intrinsic_name, args, 3, nullptr, 0); + } else if (count == 48) { + LLVMValueRef args[4] = { table_parts[0], table_parts[1], table_parts[2], indices }; + res.value = lb_call_intrinsic(p, intrinsic_name, args, 4, nullptr, 0); + } else if (count == 64) { + LLVMValueRef args[5] = { table_parts[0], table_parts[1], table_parts[2], table_parts[3], indices }; + res.value = lb_call_intrinsic(p, intrinsic_name, args, 5, nullptr, 0); + } + } else if (build_context.metrics.arch == TargetArch_arm32 && count > 8) { + // ARM32 VTBL2/VTBL3/VTBL4: Split table into multiple 8-byte vectors + LLVMTypeRef v8i8_type = LLVMVectorType(LLVMInt8TypeInContext(p->module->ctx), 8); + int num_tables = count / 8; + GB_ASSERT_MSG(count % 8 == 0, "ARM32 table size must be multiple of 8 bytes, got %lld bytes", count); + GB_ASSERT_MSG(num_tables <= 4, "ARM32 NEON supports maximum 4 tables (vtbl4), got %d tables for %lld-byte vector", num_tables, count); + + LLVMValueRef table_parts[4]; // Max 4 tables for vtbl4 + for (int i = 0; i < num_tables; i++) { + // Extract 8-byte slice from the larger table + LLVMValueRef indices_for_extract[8]; + for (int j = 0; j < 8; j++) { + indices_for_extract[j] = LLVMConstInt(LLVMInt32TypeInContext(p->module->ctx), i * 8 + j, false); + } + LLVMValueRef extract_mask = LLVMConstVector(indices_for_extract, 8); + table_parts[i] = LLVMBuildShuffleVector(p->builder, table, LLVMGetUndef(LLVMTypeOf(table)), extract_mask, ""); + } + + // Call appropriate ARM32 vtbl intrinsic + if (count == 16) { + LLVMValueRef args[3] = { table_parts[0], table_parts[1], indices }; + res.value = lb_call_intrinsic(p, intrinsic_name, args, 3, nullptr, 0); + } else if (count == 24) { + LLVMValueRef args[4] = { table_parts[0], table_parts[1], table_parts[2], indices }; + res.value = lb_call_intrinsic(p, intrinsic_name, args, 4, nullptr, 0); + } else if (count == 32) { + LLVMValueRef args[5] = { table_parts[0], table_parts[1], table_parts[2], table_parts[3], indices }; + res.value = lb_call_intrinsic(p, intrinsic_name, args, 5, nullptr, 0); + } + } else { + // Single-table case (x86, WebAssembly, ARM single-table) + LLVMValueRef args[2] = { table, indices }; + res.value = lb_call_intrinsic(p, intrinsic_name, args, gb_count_of(args), nullptr, 0); + } + return res; + } else { + // Features not enabled, fall back to emulation + use_hardware_table_lookup = false; + } + } + + // Fallback: Emulate with extracts and inserts for all element sizes + GB_ASSERT(count > 0 && count <= 64); // Sanity check + + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); + LLVMTypeRef i32_type = LLVMInt32TypeInContext(p->module->ctx); + LLVMTypeRef elem_llvm_type = lb_type(p->module, elem_type); + + // Calculate mask based on element size and vector count + i64 max_index = count - 1; + LLVMValueRef index_mask; + + if (elem_size == 1) { + // 8-bit: mask to table size (like pshufb behavior) + index_mask = LLVMConstInt(elem_llvm_type, max_index, false); + } else if (elem_size == 2) { + // 16-bit: mask to table size + index_mask = LLVMConstInt(elem_llvm_type, max_index, false); + } else if (elem_size == 4) { + // 32-bit: mask to table size + index_mask = LLVMConstInt(elem_llvm_type, max_index, false); + } else { + // 64-bit: mask to table size + index_mask = LLVMConstInt(elem_llvm_type, max_index, false); + } + + for (i64 i = 0; i < count; i++) { + LLVMValueRef idx_i = LLVMConstInt(i32_type, cast(unsigned)i, false); + LLVMValueRef index_elem = LLVMBuildExtractElement(p->builder, indices, idx_i, ""); + + // Mask index to valid range + LLVMValueRef masked_index = LLVMBuildAnd(p->builder, index_elem, index_mask, ""); + + // Convert to i32 for extractelement + LLVMValueRef index_i32; + if (LLVMGetIntTypeWidth(LLVMTypeOf(masked_index)) < 32) { + index_i32 = LLVMBuildZExt(p->builder, masked_index, i32_type, ""); + } else if (LLVMGetIntTypeWidth(LLVMTypeOf(masked_index)) > 32) { + index_i32 = LLVMBuildTrunc(p->builder, masked_index, i32_type, ""); + } else { + index_i32 = masked_index; + } + + values[i] = LLVMBuildExtractElement(p->builder, table, index_i32, ""); + } + + // Build result vector + res.value = LLVMGetUndef(LLVMTypeOf(table)); + for (i64 i = 0; i < count; i++) { + LLVMValueRef idx_i = LLVMConstInt(i32_type, cast(unsigned)i, false); + res.value = LLVMBuildInsertElement(p->builder, res.value, values[i], idx_i, ""); + } + return res; + } + case BuiltinProc_simd_ceil: case BuiltinProc_simd_floor: case BuiltinProc_simd_trunc: -- cgit v1.2.3 From 33c6a979e93a4f4c203d596a6244c3fa3c53730f Mon Sep 17 00:00:00 2001 From: Jon Lipstate Date: Sat, 5 Jul 2025 14:11:29 -0700 Subject: fix explicit cast --- src/llvm_backend_proc.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index da8e0f91c..a1c62d555 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1861,8 +1861,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn // Handle ARM's multi-table intrinsics by splitting the table vector if (build_context.metrics.arch == TargetArch_arm64 && count > 16) { // ARM64 TBL2/TBL3/TBL4: Split table into multiple 16-byte vectors - LLVMTypeRef v16i8_type = LLVMVectorType(LLVMInt8TypeInContext(p->module->ctx), 16); - int num_tables = count / 16; + int num_tables = cast(int)(count / 16); GB_ASSERT_MSG(count % 16 == 0, "ARM64 table size must be multiple of 16 bytes, got %lld bytes", count); GB_ASSERT_MSG(num_tables <= 4, "ARM64 NEON supports maximum 4 tables (tbl4), got %d tables for %lld-byte vector", num_tables, count); @@ -1890,8 +1889,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn } } else if (build_context.metrics.arch == TargetArch_arm32 && count > 8) { // ARM32 VTBL2/VTBL3/VTBL4: Split table into multiple 8-byte vectors - LLVMTypeRef v8i8_type = LLVMVectorType(LLVMInt8TypeInContext(p->module->ctx), 8); - int num_tables = count / 8; + int num_tables = cast(int)count / 8; GB_ASSERT_MSG(count % 8 == 0, "ARM32 table size must be multiple of 8 bytes, got %lld bytes", count); GB_ASSERT_MSG(num_tables <= 4, "ARM32 NEON supports maximum 4 tables (vtbl4), got %d tables for %lld-byte vector", num_tables, count); -- cgit v1.2.3 From fc78f6e83bf800c76cd8ac86281359c91261dd89 Mon Sep 17 00:00:00 2001 From: Jon Lipstate Date: Sat, 5 Jul 2025 16:26:07 -0700 Subject: x86 sse --- src/llvm_backend_proc.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index a1c62d555..1fe8a15fe 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1813,7 +1813,12 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn bool features_enabled = true; if (build_context.metrics.arch == TargetArch_amd64 || build_context.metrics.arch == TargetArch_i386) { // x86/x86-64 feature checking - if (count == 32) { + if (count == 16) { + // SSE/SSSE3 for 128-bit vectors + if (!check_target_feature_is_enabled(str_lit("ssse3"), nullptr)) { + features_enabled = false; + } + } else if (count == 32) { // AVX2 requires ssse3 + avx2 features if (!check_target_feature_is_enabled(str_lit("ssse3"), nullptr) || !check_target_feature_is_enabled(str_lit("avx2"), nullptr)) { @@ -1839,7 +1844,11 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn // Add target features to function attributes for LLVM instruction selection if (build_context.metrics.arch == TargetArch_amd64 || build_context.metrics.arch == TargetArch_i386) { // x86/x86-64 function attributes - if (count == 32) { + if (count == 16) { + // SSE/SSSE3 for 128-bit vectors + lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("target-features"), str_lit("+ssse3")); + lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("min-legal-vector-width"), str_lit("128")); + } else if (count == 32) { lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("target-features"), str_lit("+avx,+avx2,+ssse3")); lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("min-legal-vector-width"), str_lit("256")); } else if (count == 64) { -- cgit v1.2.3 From 090cac62f9cc30f759cba086298b4bdb8c7c62b3 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 7 Jul 2025 20:47:49 +0200 Subject: lb_const_string -> lb_const_value --- src/llvm_backend_proc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 9f6a1d653..844154064 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1962,7 +1962,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu LLVMValueRef values[2] = {}; values[0] = lb_const_string(m, file_name).value; - values[1] = lb_const_string(m, file->data).value; + values[1] = lb_const_value(m, t_u8_slice, exact_value_string(file->data)).value; LLVMValueRef element = llvm_const_named_struct(m, t_load_directory_file, values, gb_count_of(values)); elements[i] = element; } -- cgit v1.2.3 From ecd41b155db7a1ed93923ddc296fab1036e14392 Mon Sep 17 00:00:00 2001 From: Jon Lipstate Date: Wed, 16 Jul 2025 21:54:24 -0700 Subject: rename table_lookup to runtime_swizzle --- base/intrinsics/intrinsics.odin | 2 +- core/simd/simd.odin | 6 +++--- src/check_builtin.cpp | 4 ++-- src/checker_builtin_procs.hpp | 4 ++-- src/llvm_backend_proc.cpp | 20 ++++++++++---------- 5 files changed, 18 insertions(+), 18 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 9edf7bcd8..d2ed95ab3 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -310,7 +310,7 @@ simd_indices :: proc($T: typeid/#simd[$N]$E) -> T where type_is_numeric(T) --- simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T --- simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- -simd_table_lookup :: proc(table: #simd[N]T, indices: #simd[N]T) -> #simd[N]T where type_is_integer(T) --- +simd_runtime_swizzle :: proc(table: #simd[N]T, indices: #simd[N]T) -> #simd[N]T where type_is_integer(T) --- // Lane-wise operations simd_ceil :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index eb4912e58..303eceb97 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -2481,15 +2481,15 @@ Example: import "core:simd" import "core:fmt" - table_lookup_example :: proc() { + runtime_swizzle_example :: proc() { table := simd.u8x16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} indices := simd.u8x16{15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} - result := simd.table_lookup(table, indices) + result := simd.runtime_swizzle(table, indices) fmt.println(result) // Expected: {15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} } */ -table_lookup :: intrinsics.simd_table_lookup +runtime_swizzle :: intrinsics.simd_runtime_swizzle /* Compute the square root of each lane in a SIMD vector. diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index c7386a97d..d786afb8e 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1150,7 +1150,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return true; } - case BuiltinProc_simd_table_lookup: + case BuiltinProc_simd_runtime_swizzle: { if (ce->args.count != 2) { error(call, "'%.*s' expected 2 arguments, got %td", LIT(builtin_name), ce->args.count); @@ -1163,7 +1163,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan check_expr_with_type_hint(c, &indices, ce->args[1], table.type); if (indices.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(table.type)) { - error(table.expr, "'%.*s' expected a simd vector type for table", LIT(builtin_name)); + error(table.expr, "'%.*s' expected a simd vector type for runtime swizzle", LIT(builtin_name)); return false; } if (!is_type_simd_vector(indices.type)) { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 59fc84a4e..8898d4c11 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -191,7 +191,7 @@ BuiltinProc__simd_begin, BuiltinProc_simd_shuffle, BuiltinProc_simd_select, - BuiltinProc_simd_table_lookup, + BuiltinProc_simd_runtime_swizzle, BuiltinProc_simd_ceil, BuiltinProc_simd_floor, @@ -551,7 +551,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_shuffle"), 2, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_select"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_table_lookup"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_runtime_swizzle"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_ceil") , 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_floor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 1fe8a15fe..a7766cab2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1721,7 +1721,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } - case BuiltinProc_simd_table_lookup: + case BuiltinProc_simd_runtime_swizzle: { LLVMValueRef table = arg0.value; LLVMValueRef indices = lb_build_expr(p, ce->args[1]).value; @@ -1734,11 +1734,11 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn // Determine strategy based on element size and target architecture char const *intrinsic_name = nullptr; - bool use_hardware_table_lookup = false; + bool use_hardware_runtime_swizzle = false; // 8-bit elements: Use dedicated table lookup instructions if (elem_size == 1) { - use_hardware_table_lookup = true; + use_hardware_runtime_swizzle = true; if (build_context.metrics.arch == TargetArch_amd64 || build_context.metrics.arch == TargetArch_i386) { // x86/x86-64: Use pshufb intrinsics @@ -1753,7 +1753,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn intrinsic_name = "llvm.x86.avx512.pshuf.b.512"; break; default: - use_hardware_table_lookup = false; + use_hardware_runtime_swizzle = false; break; } } else if (build_context.metrics.arch == TargetArch_arm64) { @@ -1772,7 +1772,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn intrinsic_name = "llvm.aarch64.neon.tbl4"; break; default: - use_hardware_table_lookup = false; + use_hardware_runtime_swizzle = false; break; } } else if (build_context.metrics.arch == TargetArch_arm32) { @@ -1791,7 +1791,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn intrinsic_name = "llvm.arm.neon.vtbl4"; break; default: - use_hardware_table_lookup = false; + use_hardware_runtime_swizzle = false; break; } } else if (build_context.metrics.arch == TargetArch_wasm32 || build_context.metrics.arch == TargetArch_wasm64p32) { @@ -1799,14 +1799,14 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn if (count == 16) { intrinsic_name = "llvm.wasm.swizzle"; } else { - use_hardware_table_lookup = false; + use_hardware_runtime_swizzle = false; } } else { - use_hardware_table_lookup = false; + use_hardware_runtime_swizzle = false; } } - if (use_hardware_table_lookup && intrinsic_name != nullptr) { + if (use_hardware_runtime_swizzle && intrinsic_name != nullptr) { // Use dedicated hardware table lookup instruction // Check if required target features are enabled @@ -1932,7 +1932,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } else { // Features not enabled, fall back to emulation - use_hardware_table_lookup = false; + use_hardware_runtime_swizzle = false; } } -- cgit v1.2.3 From 6c81df82a68a2e573ed119f6b6ebd4cd98463ae6 Mon Sep 17 00:00:00 2001 From: Jon Lipstate Date: Wed, 16 Jul 2025 23:43:41 -0700 Subject: cleanup langauge / errors about table vs swizzle --- src/check_builtin.cpp | 32 ++++++++++++------------ src/llvm_backend_proc.cpp | 62 +++++++++++++++++++++++------------------------ 2 files changed, 47 insertions(+), 47 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index d786afb8e..89dc8fb33 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1157,27 +1157,27 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } - Operand table = {}; + Operand src = {}; Operand indices = {}; - check_expr(c, &table, ce->args[0]); if (table.mode == Addressing_Invalid) return false; - check_expr_with_type_hint(c, &indices, ce->args[1], table.type); if (indices.mode == Addressing_Invalid) return false; + check_expr(c, &src, ce->args[0]); if (src.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &indices, ce->args[1], src.type); if (indices.mode == Addressing_Invalid) return false; - if (!is_type_simd_vector(table.type)) { - error(table.expr, "'%.*s' expected a simd vector type for runtime swizzle", LIT(builtin_name)); + if (!is_type_simd_vector(src.type)) { + error(src.expr, "'%.*s' expected first argument to be a simd vector", LIT(builtin_name)); return false; } if (!is_type_simd_vector(indices.type)) { - error(indices.expr, "'%.*s' expected a simd vector type for indices", LIT(builtin_name)); + error(indices.expr, "'%.*s' expected second argument (indices) to be a simd vector", LIT(builtin_name)); return false; } - Type *table_elem = base_array_type(table.type); + Type *src_elem = base_array_type(src.type); Type *indices_elem = base_array_type(indices.type); - if (!is_type_integer(table_elem)) { - gbString table_str = type_to_string(table.type); - error(table.expr, "'%.*s' expected table to be a simd vector of integers, got '%s'", LIT(builtin_name), table_str); - gb_string_free(table_str); + if (!is_type_integer(src_elem)) { + gbString src_str = type_to_string(src.type); + error(src.expr, "'%.*s' expected first argument to be a simd vector of integers, got '%s'", LIT(builtin_name), src_str); + gb_string_free(src_str); return false; } @@ -1188,17 +1188,17 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } - if (!are_types_identical(table.type, indices.type)) { - gbString table_str = type_to_string(table.type); + if (!are_types_identical(src.type, indices.type)) { + gbString src_str = type_to_string(src.type); gbString indices_str = type_to_string(indices.type); - error(indices.expr, "'%.*s' expected table and indices to have the same type, got '%s' vs '%s'", LIT(builtin_name), table_str, indices_str); + error(indices.expr, "'%.*s' expected both arguments to have the same type, got '%s' vs '%s'", LIT(builtin_name), src_str, indices_str); gb_string_free(indices_str); - gb_string_free(table_str); + gb_string_free(src_str); return false; } operand->mode = Addressing_Value; - operand->type = table.type; + operand->type = src.type; return true; } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index a7766cab2..5894a1844 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1723,7 +1723,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn case BuiltinProc_simd_runtime_swizzle: { - LLVMValueRef table = arg0.value; + LLVMValueRef src = arg0.value; LLVMValueRef indices = lb_build_expr(p, ce->args[1]).value; Type *vt = arg0.type; @@ -1807,7 +1807,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn } if (use_hardware_runtime_swizzle && intrinsic_name != nullptr) { - // Use dedicated hardware table lookup instruction + // Use dedicated hardware swizzle instruction // Check if required target features are enabled bool features_enabled = true; @@ -1834,7 +1834,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn } } } else if (build_context.metrics.arch == TargetArch_arm64 || build_context.metrics.arch == TargetArch_arm32) { - // ARM/ARM64 feature checking - NEON is required for all table lookups + // ARM/ARM64 feature checking - NEON is required for all table/swizzle ops if (!check_target_feature_is_enabled(str_lit("neon"), nullptr)) { features_enabled = false; } @@ -1856,77 +1856,77 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("min-legal-vector-width"), str_lit("512")); } } else if (build_context.metrics.arch == TargetArch_arm64) { - // ARM64 function attributes - enable NEON for table lookup instructions + // ARM64 function attributes - enable NEON for swizzle instructions lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("target-features"), str_lit("+neon")); - // Set appropriate vector width for multi-table operations + // Set appropriate vector width for multi-swizzle operations if (count >= 32) { lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("min-legal-vector-width"), str_lit("256")); } } else if (build_context.metrics.arch == TargetArch_arm32) { - // ARM32 function attributes - enable NEON for table lookup instructions + // ARM32 function attributes - enable NEON for swizzle instructions lb_add_attribute_to_proc_with_string(p->module, p->value, str_lit("target-features"), str_lit("+neon")); } - // Handle ARM's multi-table intrinsics by splitting the table vector + // Handle ARM's multi-swizzle intrinsics by splitting the src vector if (build_context.metrics.arch == TargetArch_arm64 && count > 16) { - // ARM64 TBL2/TBL3/TBL4: Split table into multiple 16-byte vectors + // ARM64 TBL2/TBL3/TBL4: Split src into multiple 16-byte vectors int num_tables = cast(int)(count / 16); - GB_ASSERT_MSG(count % 16 == 0, "ARM64 table size must be multiple of 16 bytes, got %lld bytes", count); + GB_ASSERT_MSG(count % 16 == 0, "ARM64 src size must be multiple of 16 bytes, got %lld bytes", count); GB_ASSERT_MSG(num_tables <= 4, "ARM64 NEON supports maximum 4 tables (tbl4), got %d tables for %lld-byte vector", num_tables, count); - LLVMValueRef table_parts[4]; // Max 4 tables for tbl4 + LLVMValueRef src_parts[4]; // Max 4 tables for tbl4 for (int i = 0; i < num_tables; i++) { - // Extract 16-byte slice from the larger table + // Extract 16-byte slice from the larger src LLVMValueRef indices_for_extract[16]; for (int j = 0; j < 16; j++) { indices_for_extract[j] = LLVMConstInt(LLVMInt32TypeInContext(p->module->ctx), i * 16 + j, false); } LLVMValueRef extract_mask = LLVMConstVector(indices_for_extract, 16); - table_parts[i] = LLVMBuildShuffleVector(p->builder, table, LLVMGetUndef(LLVMTypeOf(table)), extract_mask, ""); + src_parts[i] = LLVMBuildShuffleVector(p->builder, src, LLVMGetUndef(LLVMTypeOf(src)), extract_mask, ""); } // Call appropriate ARM64 tbl intrinsic if (count == 32) { - LLVMValueRef args[3] = { table_parts[0], table_parts[1], indices }; + LLVMValueRef args[3] = { src_parts[0], src_parts[1], indices }; res.value = lb_call_intrinsic(p, intrinsic_name, args, 3, nullptr, 0); } else if (count == 48) { - LLVMValueRef args[4] = { table_parts[0], table_parts[1], table_parts[2], indices }; + LLVMValueRef args[4] = { src_parts[0], src_parts[1], src_parts[2], indices }; res.value = lb_call_intrinsic(p, intrinsic_name, args, 4, nullptr, 0); } else if (count == 64) { - LLVMValueRef args[5] = { table_parts[0], table_parts[1], table_parts[2], table_parts[3], indices }; + LLVMValueRef args[5] = { src_parts[0], src_parts[1], src_parts[2], src_parts[3], indices }; res.value = lb_call_intrinsic(p, intrinsic_name, args, 5, nullptr, 0); } } else if (build_context.metrics.arch == TargetArch_arm32 && count > 8) { - // ARM32 VTBL2/VTBL3/VTBL4: Split table into multiple 8-byte vectors + // ARM32 VTBL2/VTBL3/VTBL4: Split src into multiple 8-byte vectors int num_tables = cast(int)count / 8; - GB_ASSERT_MSG(count % 8 == 0, "ARM32 table size must be multiple of 8 bytes, got %lld bytes", count); + GB_ASSERT_MSG(count % 8 == 0, "ARM32 src size must be multiple of 8 bytes, got %lld bytes", count); GB_ASSERT_MSG(num_tables <= 4, "ARM32 NEON supports maximum 4 tables (vtbl4), got %d tables for %lld-byte vector", num_tables, count); - LLVMValueRef table_parts[4]; // Max 4 tables for vtbl4 + LLVMValueRef src_parts[4]; // Max 4 tables for vtbl4 for (int i = 0; i < num_tables; i++) { - // Extract 8-byte slice from the larger table + // Extract 8-byte slice from the larger src LLVMValueRef indices_for_extract[8]; for (int j = 0; j < 8; j++) { indices_for_extract[j] = LLVMConstInt(LLVMInt32TypeInContext(p->module->ctx), i * 8 + j, false); } LLVMValueRef extract_mask = LLVMConstVector(indices_for_extract, 8); - table_parts[i] = LLVMBuildShuffleVector(p->builder, table, LLVMGetUndef(LLVMTypeOf(table)), extract_mask, ""); + src_parts[i] = LLVMBuildShuffleVector(p->builder, src, LLVMGetUndef(LLVMTypeOf(src)), extract_mask, ""); } // Call appropriate ARM32 vtbl intrinsic if (count == 16) { - LLVMValueRef args[3] = { table_parts[0], table_parts[1], indices }; + LLVMValueRef args[3] = { src_parts[0], src_parts[1], indices }; res.value = lb_call_intrinsic(p, intrinsic_name, args, 3, nullptr, 0); } else if (count == 24) { - LLVMValueRef args[4] = { table_parts[0], table_parts[1], table_parts[2], indices }; + LLVMValueRef args[4] = { src_parts[0], src_parts[1], src_parts[2], indices }; res.value = lb_call_intrinsic(p, intrinsic_name, args, 4, nullptr, 0); } else if (count == 32) { - LLVMValueRef args[5] = { table_parts[0], table_parts[1], table_parts[2], table_parts[3], indices }; + LLVMValueRef args[5] = { src_parts[0], src_parts[1], src_parts[2], src_parts[3], indices }; res.value = lb_call_intrinsic(p, intrinsic_name, args, 5, nullptr, 0); } } else { - // Single-table case (x86, WebAssembly, ARM single-table) - LLVMValueRef args[2] = { table, indices }; + // Single runtime swizzle case (x86, WebAssembly, ARM single-table) + LLVMValueRef args[2] = { src, indices }; res.value = lb_call_intrinsic(p, intrinsic_name, args, gb_count_of(args), nullptr, 0); } return res; @@ -1948,16 +1948,16 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn LLVMValueRef index_mask; if (elem_size == 1) { - // 8-bit: mask to table size (like pshufb behavior) + // 8-bit: mask to src size (like pshufb behavior) index_mask = LLVMConstInt(elem_llvm_type, max_index, false); } else if (elem_size == 2) { - // 16-bit: mask to table size + // 16-bit: mask to src size index_mask = LLVMConstInt(elem_llvm_type, max_index, false); } else if (elem_size == 4) { - // 32-bit: mask to table size + // 32-bit: mask to src size index_mask = LLVMConstInt(elem_llvm_type, max_index, false); } else { - // 64-bit: mask to table size + // 64-bit: mask to src size index_mask = LLVMConstInt(elem_llvm_type, max_index, false); } @@ -1978,11 +1978,11 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn index_i32 = masked_index; } - values[i] = LLVMBuildExtractElement(p->builder, table, index_i32, ""); + values[i] = LLVMBuildExtractElement(p->builder, src, index_i32, ""); } // Build result vector - res.value = LLVMGetUndef(LLVMTypeOf(table)); + res.value = LLVMGetUndef(LLVMTypeOf(src)); for (i64 i = 0; i < count; i++) { LLVMValueRef idx_i = LLVMConstInt(i32_type, cast(unsigned)i, false); res.value = LLVMBuildInsertElement(p->builder, res.value, values[i], idx_i, ""); -- cgit v1.2.3 From 1848e0df05a14679d49e5b87988fa6c7b609739b Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Mon, 28 Jul 2025 15:10:42 -0700 Subject: use the correct frequency for the arm tsc timer --- base/intrinsics/intrinsics.odin | 3 +++ core/time/tsc_darwin.odin | 3 ++- src/check_builtin.cpp | 1 + src/checker_builtin_procs.hpp | 3 +++ src/llvm_backend_proc.cpp | 13 +++++++++++++ 5 files changed, 22 insertions(+), 1 deletion(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 7e45abb8f..7057aba84 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -361,6 +361,9 @@ wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_u x86_cpuid :: proc(ax, cx: u32) -> (eax, ebx, ecx, edx: u32) --- x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- +// Arm64 targets +arm64_read_cycle_counter_frequency :: proc() -> i64 --- + // Darwin targets only objc_object :: struct{} diff --git a/core/time/tsc_darwin.odin b/core/time/tsc_darwin.odin index fdda5db77..55abf5868 100644 --- a/core/time/tsc_darwin.odin +++ b/core/time/tsc_darwin.odin @@ -1,13 +1,14 @@ #+private package time +import "base:intrinsics" import "core:sys/unix" _get_tsc_frequency :: proc "contextless" () -> (freq: u64, ok: bool) { if ODIN_ARCH == .amd64 { unix.sysctlbyname("machdep.tsc.frequency", &freq) or_return } else if ODIN_ARCH == .arm64 { - unix.sysctlbyname("hw.tbfrequency", &freq) or_return + freq = u64(intrinsics.arm64_read_cycle_counter_frequency()) } else { return } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 58fa44ec9..e9da79932 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4713,6 +4713,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } break; + case BuiltinProc_arm64_read_cycle_counter_frequency: case BuiltinProc_read_cycle_counter: operand->mode = Addressing_Value; operand->type = t_i64; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 91cef481e..474932970 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -225,6 +225,8 @@ BuiltinProc__simd_end, BuiltinProc_x86_cpuid, BuiltinProc_x86_xgetbv, + BuiltinProc_arm64_read_cycle_counter_frequency, + // Constant type tests BuiltinProc__type_begin, @@ -584,6 +586,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("syscall_bsd"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("x86_cpuid"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("x86_xgetbv"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("arm64_read_cycle_counter_frequency"), 0, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index ffa434278..3f5819b43 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2809,6 +2809,19 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu } return res; } + case BuiltinProc_arm64_read_cycle_counter_frequency: + { + lbValue res = {}; + res.type = tv.type; + + LLVMTypeRef func_type = LLVMFunctionType(LLVMInt64TypeInContext(p->module->ctx), nullptr, 0, false); + bool has_side_effects = false; + LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("mrs $0, cntfrq_el0"), str_lit("=r"), has_side_effects); + GB_ASSERT(the_asm != nullptr); + res.value = LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); + + return res; + } case BuiltinProc_count_trailing_zeros: return lb_emit_count_trailing_zeros(p, lb_build_expr(p, ce->args[0]), tv.type); -- cgit v1.2.3 From c966e635204f3252c295976f7bf02c452d866b81 Mon Sep 17 00:00:00 2001 From: Laytan Date: Tue, 29 Jul 2025 19:04:38 +0200 Subject: fix ignored args in multi return handling Fixes #5483 --- src/llvm_backend_proc.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index ffa434278..5c38367f1 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1073,6 +1073,7 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c lbValue result = {}; + isize ignored_args = 0; auto processed_args = array_make(permanent_allocator(), 0, args.count); { @@ -1095,6 +1096,7 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c lbArgType *arg = &ft->args[param_index]; if (arg->kind == lbArg_Ignore) { param_index += 1; + ignored_args += 1; continue; } @@ -1203,7 +1205,7 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c auto tuple_fix_values = slice_make(permanent_allocator(), ret_count); auto tuple_geps = slice_make(permanent_allocator(), ret_count); - isize offset = ft->original_arg_count; + isize offset = ft->original_arg_count - ignored_args; for (isize j = 0; j < ret_count-1; j++) { lbValue ret_arg_ptr = processed_args[offset + j]; lbValue ret_arg = lb_emit_load(p, ret_arg_ptr); -- cgit v1.2.3 From 9a2e6fff481968f201e6bf6a601ab7a4e3fbef45 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Tue, 29 Jul 2025 12:21:46 -0700 Subject: switch to read_cycle_counter_frequency from arm64_read_cycle_counter_freq, trap on invalid arch --- base/intrinsics/intrinsics.odin | 3 +-- core/time/tsc_darwin.odin | 2 +- core/time/tsc_linux.odin | 2 +- src/check_builtin.cpp | 2 +- src/checker_builtin_procs.hpp | 4 ++-- src/llvm_backend_proc.cpp | 16 ++++++++++------ 6 files changed, 16 insertions(+), 13 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 7057aba84..be75739fe 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -32,6 +32,7 @@ trap :: proc() -> ! --- alloca :: proc(size, align: int) -> [^]u8 --- cpu_relax :: proc() --- read_cycle_counter :: proc() -> i64 --- +read_cycle_counter_frequency :: proc() -> i64 --- count_ones :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- count_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- @@ -361,8 +362,6 @@ wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_u x86_cpuid :: proc(ax, cx: u32) -> (eax, ebx, ecx, edx: u32) --- x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- -// Arm64 targets -arm64_read_cycle_counter_frequency :: proc() -> i64 --- // Darwin targets only diff --git a/core/time/tsc_darwin.odin b/core/time/tsc_darwin.odin index 55abf5868..78d5b33f8 100644 --- a/core/time/tsc_darwin.odin +++ b/core/time/tsc_darwin.odin @@ -8,7 +8,7 @@ _get_tsc_frequency :: proc "contextless" () -> (freq: u64, ok: bool) { if ODIN_ARCH == .amd64 { unix.sysctlbyname("machdep.tsc.frequency", &freq) or_return } else if ODIN_ARCH == .arm64 { - freq = u64(intrinsics.arm64_read_cycle_counter_frequency()) + freq = u64(intrinsics.read_cycle_counter_frequency()) } else { return } diff --git a/core/time/tsc_linux.odin b/core/time/tsc_linux.odin index bfae79fb4..7232e0631 100644 --- a/core/time/tsc_linux.odin +++ b/core/time/tsc_linux.odin @@ -7,7 +7,7 @@ import linux "core:sys/linux" _get_tsc_frequency :: proc "contextless" () -> (u64, bool) { if ODIN_ARCH == .arm64 { - frequency := u64(intrinsics.arm64_read_cycle_counter_frequency()) + frequency := u64(intrinsics.read_cycle_counter_frequency()) return frequency, true } else { // Get the file descriptor for the perf mapping diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index e9da79932..66be3c69e 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4713,7 +4713,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } break; - case BuiltinProc_arm64_read_cycle_counter_frequency: + case BuiltinProc_read_cycle_counter_frequency: case BuiltinProc_read_cycle_counter: operand->mode = Addressing_Value; operand->type = t_i64; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 474932970..8e135ab10 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -61,6 +61,7 @@ enum BuiltinProcId { BuiltinProc_trap, BuiltinProc_debug_trap, BuiltinProc_read_cycle_counter, + BuiltinProc_read_cycle_counter_frequency, BuiltinProc_count_ones, BuiltinProc_count_zeros, @@ -225,7 +226,6 @@ BuiltinProc__simd_end, BuiltinProc_x86_cpuid, BuiltinProc_x86_xgetbv, - BuiltinProc_arm64_read_cycle_counter_frequency, // Constant type tests @@ -423,6 +423,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("trap"), 0, false, Expr_Expr, BuiltinProcPkg_intrinsics, /*diverging*/true}, {STR_LIT("debug_trap"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics, /*diverging*/false}, {STR_LIT("read_cycle_counter"), 0, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("read_cycle_counter_frequency"), 0, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("count_ones"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("count_zeros"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -586,7 +587,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("syscall_bsd"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("x86_cpuid"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("x86_xgetbv"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("arm64_read_cycle_counter_frequency"), 0, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3f5819b43..5e38e6bdc 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2809,16 +2809,20 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu } return res; } - case BuiltinProc_arm64_read_cycle_counter_frequency: + case BuiltinProc_read_cycle_counter_frequency: { lbValue res = {}; res.type = tv.type; - LLVMTypeRef func_type = LLVMFunctionType(LLVMInt64TypeInContext(p->module->ctx), nullptr, 0, false); - bool has_side_effects = false; - LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("mrs $0, cntfrq_el0"), str_lit("=r"), has_side_effects); - GB_ASSERT(the_asm != nullptr); - res.value = LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); + if (build_context.metrics.arch == TargetArch_arm64) { + LLVMTypeRef func_type = LLVMFunctionType(LLVMInt64TypeInContext(p->module->ctx), nullptr, 0, false); + bool has_side_effects = false; + LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("mrs $0, cntfrq_el0"), str_lit("=r"), has_side_effects); + GB_ASSERT(the_asm != nullptr); + res.value = LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); + } else { + GB_PANIC("Unsupported architecture: %.*s", LIT(target_arch_names[build_context.metrics.arch])); + } return res; } -- cgit v1.2.3 From 4d9fdf5bd381c21ae68e08e52a0d98729a390cfe Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Tue, 29 Jul 2025 13:09:05 -0700 Subject: move to checker error, rather than panic --- src/check_builtin.cpp | 8 ++++++++ src/llvm_backend_proc.cpp | 2 -- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 66be3c69e..974224ed2 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4714,6 +4714,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; case BuiltinProc_read_cycle_counter_frequency: + if (build_context.metrics.arch != TargetArch_arm64) { + error(call, "'%.*s' is only allowed on arm64 targets", LIT(builtin_name)); + return false; + } + operand->mode = Addressing_Value; + operand->type = t_i64; + break; + case BuiltinProc_read_cycle_counter: operand->mode = Addressing_Value; operand->type = t_i64; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 5e38e6bdc..d89720e0d 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2820,8 +2820,6 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("mrs $0, cntfrq_el0"), str_lit("=r"), has_side_effects); GB_ASSERT(the_asm != nullptr); res.value = LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); - } else { - GB_PANIC("Unsupported architecture: %.*s", LIT(target_arch_names[build_context.metrics.arch])); } return res; -- 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_proc.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 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_proc.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 c22da490b9416b242235a1748d3aa2d4c9fde539 Mon Sep 17 00:00:00 2001 From: Sunagatov Denis Date: Fri, 8 Aug 2025 04:56:59 +1100 Subject: Print the name of missing runtime procedure This continues the fix made at 46b7abee9fdd339bac31fe7bc79551f2b3881f46, by asserting on missing entity early and printing it's name if its not found. --- src/llvm_backend_proc.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index c95bb0c29..04dcc6662 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -985,6 +985,7 @@ gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue gb_internal lbValue lb_lookup_runtime_procedure(lbModule *m, String const &name) { AstPackage *pkg = m->info->runtime_package; Entity *e = scope_lookup_current(pkg->scope, name); + GB_ASSERT_MSG(e != nullptr, "Runtime procedure not found: %s", name); return lb_find_procedure_value_from_entity(m, e); } -- cgit v1.2.3 From 8c1eda3e3a112ac1475674f306ac876cebdd762a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 8 Aug 2025 11:10:01 +0100 Subject: Handle `fixed_point_div` and `fixed_point_div_sat` --- src/llvm_backend_expr.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++++ src/llvm_backend_proc.cpp | 14 ++++++--- 2 files changed, 83 insertions(+), 4 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index b44d2215e..a29682874 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1200,6 +1200,79 @@ gb_internal LLVMValueRef lb_integer_division(lbProcedure *p, LLVMValueRef lhs, L return res; } +gb_internal LLVMValueRef lb_integer_division_intrinsics(lbProcedure *p, LLVMValueRef lhs, LLVMValueRef rhs, LLVMValueRef scale, Type *platform_type, char const *name) { + LLVMTypeRef type = LLVMTypeOf(rhs); + GB_ASSERT(LLVMTypeOf(lhs) == type); + + LLVMValueRef incoming_values[2] = {}; + LLVMBasicBlockRef incoming_blocks[2] = {}; + + lbBlock *safe_block = lb_create_block(p, "div.safe"); + lbBlock *edge_case_block = lb_create_block(p, "div.edge"); + lbBlock *done_block = lb_create_block(p, "div.done"); + + LLVMValueRef zero = LLVMConstNull(type); + LLVMValueRef dem_check = LLVMBuildICmp(p->builder, LLVMIntNE, rhs, zero, ""); + lbValue cond = {dem_check, t_untyped_bool}; + + lb_emit_if(p, cond, safe_block, edge_case_block); + + lb_start_block(p, safe_block); + + { + LLVMTypeRef types[1] = {lb_type(p->module, platform_type)}; + + LLVMValueRef args[3] = { + lhs, + rhs, + scale }; + + incoming_values[0] = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); + } + + lb_emit_jump(p, done_block); + + lb_start_block(p, edge_case_block); + + + switch (lb_check_for_integer_division_by_zero_behaviour(p)) { + case IntegerDivisionByZero_Trap: + lb_call_intrinsic(p, "llvm.trap", nullptr, 0, nullptr, 0); + LLVMBuildUnreachable(p->builder); + break; + case IntegerDivisionByZero_Zero: + incoming_values[1] = zero; + break; + case IntegerDivisionByZero_Self: + incoming_values[1] = lhs; + break; + } + + lb_emit_jump(p, done_block); + lb_start_block(p, done_block); + + LLVMValueRef res = incoming_values[0]; + + switch (lb_check_for_integer_division_by_zero_behaviour(p)) { + case IntegerDivisionByZero_Trap: + case IntegerDivisionByZero_Self: + res = incoming_values[0]; + break; + case IntegerDivisionByZero_Zero: + res = LLVMBuildPhi(p->builder, type, ""); + + GB_ASSERT(p->curr_block->preds.count >= 2); + incoming_blocks[0] = p->curr_block->preds[0]->block; + incoming_blocks[1] = p->curr_block->preds[1]->block; + + LLVMAddIncoming(res, incoming_values, incoming_blocks, 2); + break; + } + + return res; +} + + gb_internal LLVMValueRef lb_integer_modulo(lbProcedure *p, LLVMValueRef lhs, LLVMValueRef rhs, bool is_unsigned, bool is_floored) { LLVMTypeRef type = LLVMTypeOf(rhs); GB_ASSERT(LLVMTypeOf(lhs) == type); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 04dcc6662..f2e6662c8 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3302,16 +3302,22 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu } GB_ASSERT(name != nullptr); - LLVMTypeRef types[1] = {lb_type(p->module, platform_type)}; lbValue res = {}; + res.type = platform_type; + + if (id == BuiltinProc_fixed_point_div || + id == BuiltinProc_fixed_point_div_sat) { + res.value = lb_integer_division_intrinsics(p, x.value, y.value, scale.value, platform_type, name); + } else { + LLVMTypeRef types[1] = {lb_type(p->module, platform_type)}; - LLVMValueRef args[3] = { + LLVMValueRef args[3] = { x.value, y.value, scale.value }; - res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); - res.type = platform_type; + res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); + } return lb_emit_conv(p, res, tv.type); } -- cgit v1.2.3 From 1e0902677f905e752b42e2f48dcda53141b78eee Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 17:29:11 +0100 Subject: Multithread min dep set by removing the set itself --- src/checker.cpp | 193 ++++++++++++++++++++++-------------------- src/checker.hpp | 7 +- src/entity.cpp | 1 + src/error.cpp | 16 ++-- src/llvm_backend.cpp | 17 ++-- src/llvm_backend_proc.cpp | 3 +- src/llvm_backend_stmt.cpp | 6 +- src/main.cpp | 3 +- src/name_canonicalization.cpp | 61 ++++++++++++- src/name_canonicalization.hpp | 2 + src/ptr_set.cpp | 38 +++++++++ src/threading.cpp | 38 +++++++++ src/types.cpp | 1 + 13 files changed, 263 insertions(+), 123 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/checker.cpp b/src/checker.cpp index 26430359c..b3a702cbd 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1712,7 +1712,7 @@ gb_internal void check_remove_expr_info(CheckerContext *c, Ast *e) { } gb_internal isize type_info_index(CheckerInfo *info, TypeInfoPair pair, bool error_on_failure) { - mutex_lock(&info->minimum_dependency_type_info_mutex); + rw_mutex_shared_lock(&info->minimum_dependency_type_info_mutex); isize entry_index = -1; u64 hash = pair.hash; @@ -1720,7 +1720,7 @@ gb_internal isize type_info_index(CheckerInfo *info, TypeInfoPair pair, bool err if (found_entry_index) { entry_index = *found_entry_index; } - mutex_unlock(&info->minimum_dependency_type_info_mutex); + rw_mutex_shared_unlock(&info->minimum_dependency_type_info_mutex); if (error_on_failure && entry_index < 0) { compiler_error("Type_Info for '%s' could not be found", type_to_string(pair.type)); @@ -2377,11 +2377,8 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { return; } - { - MUTEX_GUARD(&c->info.minimum_dependency_type_info_mutex); - if (type_set_update(&c->info.min_dep_type_info_set, t)) { - return; - } + if (type_set_update_with_mutex(&c->info.min_dep_type_info_set, t, &c->info.min_dep_type_info_set_mutex)) { + return; } // Add nested types @@ -2555,13 +2552,15 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { } } +gb_internal void add_dependency_to_set_threaded(Checker *c, Entity *entity); + +gb_global std::atomic global_checker_ptr; + gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { if (entity == nullptr) { return; } - CheckerInfo *info = &c->info; - if (entity->type != nullptr && is_type_polymorphic(entity->type)) { DeclInfo *decl = decl_info_of_entity(entity); @@ -2570,11 +2569,8 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { } } - { - MUTEX_GUARD(&info->minimum_dependency_type_info_mutex); - if (ptr_set_update(&info->minimum_dependency_set, entity)) { - return; - } + if (entity->min_dep_count.fetch_add(1, std::memory_order_relaxed) > 0) { + return; } DeclInfo *decl = decl_info_of_entity(entity); @@ -2584,46 +2580,45 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { for (TypeInfoPair const tt : decl->type_info_deps) { add_min_dep_type_info(c, tt.type); } - for (Entity *e : decl->deps) { - add_dependency_to_set(c, e); - if (e->kind == Entity_Procedure && e->Procedure.is_foreign) { - Entity *fl = e->Procedure.foreign_library; - if (fl != nullptr) { - GB_ASSERT_MSG(fl->kind == Entity_LibraryName && - (fl->flags&EntityFlag_Used), - "%.*s", LIT(entity->token.string)); - add_dependency_to_set(c, fl); + switch (e->kind) { + case Entity_Procedure: + if (e->Procedure.is_foreign) { + Entity *fl = e->Procedure.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set(c, fl); + } } - } else if (e->kind == Entity_Variable && e->Variable.is_foreign) { - Entity *fl = e->Variable.foreign_library; - if (fl != nullptr) { - GB_ASSERT_MSG(fl->kind == Entity_LibraryName && - (fl->flags&EntityFlag_Used), - "%.*s", LIT(entity->token.string)); - add_dependency_to_set(c, fl); + break; + case Entity_Variable: + if (e->Variable.is_foreign) { + Entity *fl = e->Variable.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set(c, fl); + } } + break; } } -} -struct AddDependecyToSetWorkerData { - Checker *c; - Entity *entity; -}; - -gb_internal void add_dependency_to_set_threaded(Checker *c, Entity *entity); + for (Entity *e : decl->deps) { + add_dependency_to_set(c, e); + } +} gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { - AddDependecyToSetWorkerData *wd = cast(AddDependecyToSetWorkerData *)data; - Checker *c = wd->c; - Entity *entity = wd->entity; + Checker *c = global_checker_ptr.load(std::memory_order_relaxed); + Entity *entity = cast(Entity *)data; if (entity == nullptr) { return 0; } - CheckerInfo *info = &c->info; - if (entity->type != nullptr && is_type_polymorphic(entity->type)) { DeclInfo *decl = decl_info_of_entity(entity); @@ -2632,11 +2627,8 @@ gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { } } - { - MUTEX_GUARD(&info->minimum_dependency_type_info_mutex); - if (ptr_set_update(&info->minimum_dependency_set, entity)) { - return 0; - } + if (entity->min_dep_count.fetch_add(1, std::memory_order_relaxed) > 0) { + return 0; } DeclInfo *decl = decl_info_of_entity(entity); @@ -2648,25 +2640,36 @@ gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { } for (Entity *e : decl->deps) { - add_dependency_to_set(c, e); - if (e->kind == Entity_Procedure && e->Procedure.is_foreign) { - Entity *fl = e->Procedure.foreign_library; - if (fl != nullptr) { - GB_ASSERT_MSG(fl->kind == Entity_LibraryName && - (fl->flags&EntityFlag_Used), - "%.*s", LIT(entity->token.string)); - add_dependency_to_set_threaded(c, fl); + switch (e->kind) { + case Entity_Procedure: + if (e->Procedure.is_foreign) { + Entity *fl = e->Procedure.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set_threaded(c, fl); + } } - } else if (e->kind == Entity_Variable && e->Variable.is_foreign) { - Entity *fl = e->Variable.foreign_library; - if (fl != nullptr) { - GB_ASSERT_MSG(fl->kind == Entity_LibraryName && - (fl->flags&EntityFlag_Used), - "%.*s", LIT(entity->token.string)); - add_dependency_to_set_threaded(c, fl); + break; + case Entity_Variable: + if (e->Variable.is_foreign) { + Entity *fl = e->Variable.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set_threaded(c, fl); + } } + break; } } + + for (Entity *e : decl->deps) { + add_dependency_to_set_threaded(c, e); + } + return 0; } @@ -2675,11 +2678,7 @@ gb_internal void add_dependency_to_set_threaded(Checker *c, Entity *entity) { if (entity == nullptr) { return; } - - AddDependecyToSetWorkerData *wd = gb_alloc_item(temporary_allocator(), AddDependecyToSetWorkerData); - wd->c = c; - wd->entity = entity; - thread_pool_add_task(add_dependency_to_set_worker, wd); + thread_pool_add_task(add_dependency_to_set_worker, entity); } @@ -2732,27 +2731,35 @@ gb_internal void collect_testing_procedures_of_package(Checker *c, AstPackage *p } gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *start) { + // auto const &add_to_set = add_dependency_to_set; + auto const &add_to_set = add_dependency_to_set_threaded; + + Scope *builtin_scope = builtin_pkg->scope; for_array(i, c->info.definitions) { Entity *e = c->info.definitions[i]; - if (e->scope == builtin_pkg->scope) { + if (e->scope == builtin_scope) { if (e->type == nullptr) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); + } + } else if (e->kind == Entity_Procedure) { + if (e->Procedure.is_export) { + add_to_set(c, e); + } + } else if (e->kind == Entity_Variable) { + if (e->Variable.is_export) { + add_to_set(c, e); } - } else if (e->kind == Entity_Procedure && e->Procedure.is_export) { - add_dependency_to_set_threaded(c, e); - } else if (e->kind == Entity_Variable && e->Variable.is_export) { - add_dependency_to_set_threaded(c, e); } } for (Entity *e; mpsc_dequeue(&c->info.required_foreign_imports_through_force_queue, &e); /**/) { array_add(&c->info.required_foreign_imports_through_force, e); - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } for (Entity *e; mpsc_dequeue(&c->info.required_global_variable_queue, &e); /**/) { e->flags |= EntityFlag_Used; - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } for_array(i, c->info.entities) { @@ -2760,16 +2767,16 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st switch (e->kind) { case Entity_Variable: if (e->Variable.is_export) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } else if (e->flags & EntityFlag_Require) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } break; case Entity_Procedure: if (e->Procedure.is_export) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } else if (e->flags & EntityFlag_Require) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } if (e->flags & EntityFlag_Init) { Type *t = base_type(e->type); @@ -2809,7 +2816,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st if (is_init) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); array_add(&c->info.init_procedures, e); } } else if (e->flags & EntityFlag_Fini) { @@ -2844,7 +2851,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st } if (is_fini) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); array_add(&c->info.fini_procedures, e); } } @@ -2861,7 +2868,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st Entity *e = entry.value; if (e != nullptr) { e->flags |= EntityFlag_Used; - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } } @@ -2876,16 +2883,11 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st } } else if (start != nullptr) { start->flags |= EntityFlag_Used; - add_dependency_to_set_threaded(c, start); + add_to_set(c, start); } } gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { - isize entity_count = c->info.entities.count; - isize min_dep_set_cap = next_pow2_isize(entity_count*4); // empirically determined factor - - ptr_set_init(&c->info.minimum_dependency_set, min_dep_set_cap); - #define FORCE_ADD_RUNTIME_ENTITIES(condition, ...) do { \ if (condition) { \ String entities[] = {__VA_ARGS__}; \ @@ -6267,8 +6269,10 @@ gb_internal void check_unchecked_bodies(Checker *c) { // use the `procs_to_check` array global_procedure_body_in_worker_queue = false; - for (Entity *e : c->info.minimum_dependency_set) { - check_procedure_later_from_entity(c, e, "check_unchecked_bodies"); + for (Entity *e : c->info.entities) { + if (e->min_dep_count.load(std::memory_order_relaxed) > 0) { + check_procedure_later_from_entity(c, e, "check_unchecked_bodies"); + } } if (!global_procedure_body_in_worker_queue) { @@ -7042,6 +7046,7 @@ gb_internal void check_merge_queues_into_arrays(Checker *c) { } check_add_entities_from_queues(c); check_add_definitions_from_queues(c); + thread_pool_wait(); } gb_internal GB_COMPARE_PROC(init_procedures_cmp) { @@ -7100,7 +7105,7 @@ gb_internal void add_type_info_for_type_definitions(Checker *c) { Entity *e = c->info.definitions[i]; if (e->kind == Entity_TypeName && e->type != nullptr && is_type_typed(e->type)) { i64 align = type_align_of(e->type); - if (align > 0 && ptr_set_exists(&c->info.minimum_dependency_set, e)) { + if (align > 0 && e->min_dep_count.load(std::memory_order_relaxed) > 0) { add_type_info_type(&c->builtin_ctx, e->type); } } @@ -7170,6 +7175,8 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) { gb_internal void check_parsed_files(Checker *c) { + global_checker_ptr.store(c, std::memory_order_relaxed); + TIME_SECTION("map full filepaths to scope"); add_type_info_type(&c->builtin_ctx, t_invalid); @@ -7312,11 +7319,9 @@ gb_internal void check_parsed_files(Checker *c) { check_unchecked_bodies(c); TIME_SECTION("check #soa types"); - check_merge_queues_into_arrays(c); - thread_pool_wait(); - TIME_SECTION("update minimum dependency set"); + TIME_SECTION("update minimum dependency set again"); generate_minimum_dependency_set_internal(c, c->info.entry_point); // NOTE(laytan): has to be ran after generate_minimum_dependency_set, diff --git a/src/checker.hpp b/src/checker.hpp index 1da46b74a..8b4d61ee2 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -449,11 +449,10 @@ struct CheckerInfo { Scope * init_scope; Entity * entry_point; - BlockingMutex minimum_dependency_set_mutex; - PtrSet minimum_dependency_set; - - BlockingMutex minimum_dependency_type_info_mutex; + RwMutex minimum_dependency_type_info_mutex; PtrMap min_dep_type_info_index_map; + + RWSpinLock min_dep_type_info_set_mutex; TypeSet min_dep_type_info_set; Array type_info_types_hash_map; // 2 * type_info_types.count diff --git a/src/entity.cpp b/src/entity.cpp index 6c0aa6ace..5ca3fa916 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -164,6 +164,7 @@ struct Entity { u64 id; std::atomic flags; std::atomic state; + std::atomic min_dep_count; Token token; Scope * scope; Type * type; diff --git a/src/error.cpp b/src/error.cpp index 006d5ae8d..10bf1caf5 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -86,7 +86,7 @@ gb_internal char *token_pos_to_string(TokenPos const &pos); gb_internal bool set_file_path_string(i32 index, String const &path) { bool ok = false; GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.path_mutex); + // mutex_lock(&global_error_collector.path_mutex); mutex_lock(&global_files_mutex); if (index >= global_file_path_strings.count) { @@ -99,14 +99,14 @@ gb_internal bool set_file_path_string(i32 index, String const &path) { } mutex_unlock(&global_files_mutex); - mutex_unlock(&global_error_collector.path_mutex); + // mutex_unlock(&global_error_collector.path_mutex); return ok; } gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { bool ok = false; GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.path_mutex); + // mutex_lock(&global_error_collector.path_mutex); mutex_lock(&global_files_mutex); if (index >= global_files.count) { @@ -118,13 +118,13 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { ok = true; } mutex_unlock(&global_files_mutex); - mutex_unlock(&global_error_collector.path_mutex); + // mutex_unlock(&global_error_collector.path_mutex); return ok; } gb_internal String get_file_path_string(i32 index) { GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.path_mutex); + // mutex_lock(&global_error_collector.path_mutex); mutex_lock(&global_files_mutex); String path = {}; @@ -133,13 +133,13 @@ gb_internal String get_file_path_string(i32 index) { } mutex_unlock(&global_files_mutex); - mutex_unlock(&global_error_collector.path_mutex); + // mutex_unlock(&global_error_collector.path_mutex); return path; } gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.path_mutex); + // mutex_lock(&global_error_collector.path_mutex); mutex_lock(&global_files_mutex); AstFile *file = nullptr; @@ -148,7 +148,7 @@ gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { } mutex_unlock(&global_files_mutex); - mutex_unlock(&global_error_collector.path_mutex); + // mutex_unlock(&global_error_collector.path_mutex); return file; } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index ff17e9c10..11b979774 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2097,8 +2097,6 @@ gb_internal GB_COMPARE_PROC(llvm_global_entity_cmp) { } gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, CheckerInfo *info, bool do_threading) { - auto *min_dep_set = &info->minimum_dependency_set; - for (Entity *e : info->entities) { String name = e->token.string; Scope * scope = e->scope; @@ -2135,11 +2133,16 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker } } - if (!polymorphic_struct && !ptr_set_exists(min_dep_set, e)) { + if (!polymorphic_struct && e->min_dep_count.load(std::memory_order_relaxed) == 0) { // NOTE(bill): Nothing depends upon it so doesn't need to be built continue; } + // if (!polymorphic_struct && !ptr_set_exists(min_dep_set, e)) { + // // NOTE(bill): Nothing depends upon it so doesn't need to be built + // continue; + // } + lbModule *m = &gen->default_module; if (USE_SEPARATE_MODULES) { m = lb_module_of_entity(gen, e); @@ -2845,8 +2848,6 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lbModule *default_module = &gen->default_module; CheckerInfo *info = gen->info; - auto *min_dep_set = &info->minimum_dependency_set; - switch (build_context.metrics.arch) { case TargetArch_amd64: case TargetArch_i386: @@ -3184,10 +3185,14 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { continue; } - if (!ptr_set_exists(min_dep_set, e)) { + if (e->min_dep_count.load(std::memory_order_relaxed) == 0) { continue; } + // if (!ptr_set_exists(min_dep_set, e)) { + // continue; + // } + DeclInfo *decl = decl_info_of_entity(e); if (decl == nullptr) { continue; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index f2e6662c8..06829efab 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -798,9 +798,8 @@ gb_internal void lb_end_procedure_body(lbProcedure *p) { gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e) { GB_ASSERT(pd->body != nullptr); lbModule *m = p->module; - auto *min_dep_set = &m->info->minimum_dependency_set; - if (ptr_set_exists(min_dep_set, e) == false) { + if (e->min_dep_count.load(std::memory_order_relaxed) == 0) { // NOTE(bill): Nothing depends upon it so doesn't need to be built return; } diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 5481ca447..590920b59 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -3,8 +3,6 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd) return; } - auto *min_dep_set = &p->module->info->minimum_dependency_set; - for (Ast *ident : vd->names) { GB_ASSERT(ident->kind == Ast_Ident); Entity *e = entity_of_node(ident); @@ -21,7 +19,7 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd) } } - if (!polymorphic_struct && !ptr_set_exists(min_dep_set, e)) { + if (!polymorphic_struct && e->min_dep_count.load(std::memory_order_relaxed) == 0) { continue; } @@ -56,7 +54,7 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd) if (gpd) { rw_mutex_shared_lock(&gpd->mutex); for (Entity *e : gpd->procs) { - if (!ptr_set_exists(min_dep_set, e)) { + if (e->min_dep_count.load(std::memory_order_relaxed) == 0) { continue; } DeclInfo *d = decl_info_of_entity(e); diff --git a/src/main.cpp b/src/main.cpp index db4dee080..184b1eaac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3050,7 +3050,8 @@ gb_internal void print_show_unused(Checker *c) { if (e->token.string == "_") { continue; } - if (ptr_set_exists(&info->minimum_dependency_set, e)) { + + if (e->min_dep_count.load(std::memory_order_relaxed) > 0) { continue; } array_add(&unused, e); diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp index 6a4538e26..8bacfabc6 100644 --- a/src/name_canonicalization.cpp +++ b/src/name_canonicalization.cpp @@ -57,10 +57,13 @@ gb_internal isize type_set__find(TypeSet *s, TypeInfoPair pair) { usize mask = s->capacity-1; usize hash_index = cast(usize)hash & mask; for (usize i = 0; i < s->capacity; i++) { - Type *key = s->keys[hash_index].type; - if (are_types_identical_unique_tuples(key, pair.type)) { + auto *e = &s->keys[hash_index]; + u64 hash = e->hash; + Type *key = e->type; + if (hash == pair.hash && + are_types_identical_unique_tuples(key, pair.type)) { return hash_index; - } else if (key == 0) { + } else if (key == nullptr) { return -1; } hash_index = (hash_index+1)&mask; @@ -164,6 +167,48 @@ gb_internal bool type_set_update(TypeSet *s, Type *ptr) { // returns true if it return type_set_update(s, pair); } +gb_internal bool type_set_update_with_mutex(TypeSet *s, TypeInfoPair pair, RWSpinLock *m) { // returns true if it previously existsed + rwlock_acquire_upgrade(m); + if (type_set_exists(s, pair)) { + rwlock_release_upgrade(m); + return true; + } + + rwlock_release_upgrade_and_acquire_write(m); + defer (rwlock_release_write(m)); + + if (s->keys == nullptr) { + type_set_init(s); + } else if (type_set__full(s)) { + type_set_grow(s); + } + GB_ASSERT(s->count < s->capacity); + GB_ASSERT(s->capacity >= 0); + + usize mask = s->capacity-1; + usize hash = cast(usize)pair.hash; + usize hash_index = (cast(usize)hash) & mask; + GB_ASSERT(hash_index < s->capacity); + for (usize i = 0; i < s->capacity; i++) { + TypeInfoPair *key = &s->keys[hash_index]; + GB_ASSERT(!are_types_identical_unique_tuples(key->type, pair.type)); + if (key->hash == TYPE_SET_TOMBSTONE || key->hash == 0) { + *key = pair; + s->count++; + return false; + } + hash_index = (hash_index+1)&mask; + } + + GB_PANIC("ptr set out of memory"); + return false; +} + +gb_internal bool type_set_update_with_mutex(TypeSet *s, Type *ptr, RWSpinLock *m) { // returns true if it previously existsed + TypeInfoPair pair = {ptr, type_hash_canonical_type(ptr)}; + return type_set_update_with_mutex(s, pair, m); +} + gb_internal Type *type_set_add(TypeSet *s, Type *ptr) { type_set_update(s, ptr); @@ -328,12 +373,20 @@ gb_internal u64 type_hash_canonical_type(Type *type) { if (type == nullptr) { return 0; } + u64 prev_hash = type->canonical_hash.load(std::memory_order_relaxed); + if (prev_hash != 0) { + return prev_hash; + } + u64 hash = fnv64a(nullptr, 0); TypeWriter w = {}; type_writer_make_hasher(&w, &hash); write_type_to_canonical_string(&w, type); + hash = hash ? hash : 1; + + type->canonical_hash.store(hash, std::memory_order_relaxed); - return hash ? hash : 1; + return hash; } gb_internal String type_to_canonical_string(gbAllocator allocator, Type *type) { diff --git a/src/name_canonicalization.hpp b/src/name_canonicalization.hpp index 304aff42e..00b450fbe 100644 --- a/src/name_canonicalization.hpp +++ b/src/name_canonicalization.hpp @@ -102,6 +102,8 @@ gb_internal Type *type_set_add (TypeSet *s, Type *ptr); gb_internal Type *type_set_add (TypeSet *s, TypeInfoPair pair); gb_internal bool type_set_update (TypeSet *s, Type *ptr); // returns true if it previously existed gb_internal bool type_set_update (TypeSet *s, TypeInfoPair pair); // returns true if it previously existed +gb_internal bool type_set_update_with_mutex(TypeSet *s, TypeInfoPair pair, RWSpinLock *m); +gb_internal bool type_set_update_with_mutex(TypeSet *s, Type *ptr, RWSpinLock *m); gb_internal bool type_set_exists (TypeSet *s, Type *ptr); gb_internal void type_set_remove (TypeSet *s, Type *ptr); gb_internal void type_set_clear (TypeSet *s); diff --git a/src/ptr_set.cpp b/src/ptr_set.cpp index 5097e2bb6..06c1e4a58 100644 --- a/src/ptr_set.cpp +++ b/src/ptr_set.cpp @@ -134,6 +134,44 @@ gb_internal bool ptr_set_update(PtrSet *s, T ptr) { // returns true if it pre return false; } +template +gb_internal bool ptr_set_update_with_mutex(PtrSet *s, T ptr, RWSpinLock *m) { // returns true if it previously existsed + rwlock_acquire_upgrade(m); + if (ptr_set_exists(s, ptr)) { + rwlock_release_upgrade(m); + return true; + } + + rwlock_release_upgrade_and_acquire_write(m); + defer (rwlock_release_write(m)); + + if (s->keys == nullptr) { + ptr_set_init(s); + } else if (ptr_set__full(s)) { + ptr_set_grow(s); + } + GB_ASSERT(s->count < s->capacity); + GB_ASSERT(s->capacity >= 0); + + usize mask = s->capacity-1; + u32 hash = ptr_map_hash_key(ptr); + usize hash_index = (cast(usize)hash) & mask; + GB_ASSERT(hash_index < s->capacity); + for (usize i = 0; i < s->capacity; i++) { + T *key = &s->keys[hash_index]; + GB_ASSERT(*key != ptr); + if (*key == (T)PtrSet::TOMBSTONE || *key == 0) { + *key = ptr; + s->count++; + return false; + } + hash_index = (hash_index+1)&mask; + } + + GB_PANIC("ptr set out of memory"); + return false; +} + template gb_internal T ptr_set_add(PtrSet *s, T ptr) { ptr_set_update(s, ptr); diff --git a/src/threading.cpp b/src/threading.cpp index a0d1c4049..f1d9264e3 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -448,6 +448,44 @@ gb_internal void semaphore_wait(Semaphore *s) { } #endif +static const int RWLOCK_WRITER = 1; +static const int RWLOCK_UPGRADED = 2; +static const int RWLOCK_READER = 4; +struct RWSpinLock { + Futex bits; +}; + +void rwlock_release_write(RWSpinLock *l) { + l->bits.fetch_and(~(RWLOCK_WRITER | RWLOCK_UPGRADED), std::memory_order_release); + futex_signal(&l->bits); +} + +bool rwlock_try_acquire_upgrade(RWSpinLock *l) { + int value = l->bits.fetch_or(RWLOCK_UPGRADED, std::memory_order_acquire); + return (value & (RWLOCK_UPGRADED | RWLOCK_WRITER)) == 0; +} + +void rwlock_acquire_upgrade(RWSpinLock *l) { + while (!rwlock_try_acquire_upgrade(l)) { + futex_wait(&l->bits, RWLOCK_UPGRADED); + } +} +void rwlock_release_upgrade(RWSpinLock *l) { + l->bits.fetch_add(-RWLOCK_UPGRADED, std::memory_order_acq_rel); +} + +bool rwlock_try_release_upgrade_and_acquire_write(RWSpinLock *l) { + int expect = RWLOCK_UPGRADED; + return l->bits.compare_exchange_strong(expect, RWLOCK_WRITER, std::memory_order_acq_rel); +} + +void rwlock_release_upgrade_and_acquire_write(RWSpinLock *l) { + while (!rwlock_try_release_upgrade_and_acquire_write(l)) { + futex_wait(&l->bits, RWLOCK_WRITER); + } +} + + struct Parker { Futex state; }; diff --git a/src/types.cpp b/src/types.cpp index 6b94b1690..44f9394c7 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -334,6 +334,7 @@ struct Type { // NOTE(bill): These need to be at the end to not affect the unionized data std::atomic cached_size; std::atomic cached_align; + std::atomic canonical_hash; std::atomic flags; // TypeFlag bool failure; }; -- cgit v1.2.3 From 738a72943bdb9d0998b11d38efb5300cd02d8190 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 15:04:16 +0100 Subject: Try moving parapoly procs into a separate module when doing weak monomorphization --- src/build_settings.cpp | 1 + src/llvm_backend.cpp | 2 +- src/llvm_backend.hpp | 2 ++ src/llvm_backend_debug.cpp | 2 +- src/llvm_backend_general.cpp | 63 ++++++++++++++++++++++++++++++++++++++------ src/llvm_backend_proc.cpp | 2 +- src/main.cpp | 5 ++++ 7 files changed, 66 insertions(+), 11 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 077660f10..867c80ac1 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -554,6 +554,7 @@ struct BuildContext { bool internal_no_inline; bool internal_by_value; + bool internal_weak_monomorphization; bool no_threaded_checker; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 11b979774..2bc18872e 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2145,7 +2145,7 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker lbModule *m = &gen->default_module; if (USE_SEPARATE_MODULES) { - m = lb_module_of_entity(gen, e); + m = lb_module_of_entity(gen, e, m); } GB_ASSERT(m != nullptr); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index cc3dcaa4a..e4b8c07fa 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -150,6 +150,8 @@ struct lbModule { struct lbGenerator *gen; LLVMTargetMachineRef target_machine; + lbModule *polymorphic_module; + CheckerInfo *info; AstPackage *pkg; // possibly associated AstFile *file; // possibly associated diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 182920fc7..3372165f2 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -1327,7 +1327,7 @@ gb_internal void lb_add_debug_info_for_global_constant_from_entity(lbGenerator * } lbModule *m = &gen->default_module; if (USE_SEPARATE_MODULES) { - m = lb_module_of_entity(gen, e); + m = lb_module_of_entity(gen, e, m); } GB_ASSERT(m != nullptr); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 49a04641c..4907e114d 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -46,6 +46,12 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { } module_name = gb_string_appendc(module_name, "builtin"); } + if (m->polymorphic_module == m) { + if (gb_string_length(module_name)) { + module_name = gb_string_appendc(module_name, "-"); + } + module_name = gb_string_appendc(module_name, "$parapoly"); + } m->module_name = module_name; m->ctx = LLVMContextCreate(); @@ -147,17 +153,42 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { m->gen = gen; map_set(&gen->modules, cast(void *)pkg, m); lb_init_module(m, c); + + if (build_context.internal_weak_monomorphization) { + auto pm = gb_alloc_item(permanent_allocator(), lbModule); + pm->pkg = pkg; + pm->gen = gen; + m->polymorphic_module = pm; + pm->polymorphic_module = pm; + + map_set(&gen->modules, cast(void *)pm, pm); // point to itself just add it to the list + + lb_init_module(pm, c); + } + if (!module_per_file) { continue; } // NOTE(bill): Probably per file is not a good idea, so leave this for later for (AstFile *file : pkg->files) { - auto m = gb_alloc_item(permanent_allocator(), lbModule); + auto m = gb_alloc_item(permanent_allocator(), lbModule); m->file = file; - m->pkg = pkg; - m->gen = gen; + m->pkg = pkg; + m->gen = gen; map_set(&gen->modules, cast(void *)file, m); lb_init_module(m, c); + + + if (build_context.internal_weak_monomorphization) { + auto pm = gb_alloc_item(permanent_allocator(), lbModule); + pm->file = file; + pm->gen = gen; + pm->polymorphic_module = pm; + + map_set(&gen->modules, cast(void *)pm, pm); // point to itself just add it to the list + + lb_init_module(pm, c); + } } } } @@ -403,9 +434,9 @@ gb_internal lbModule *lb_module_of_expr(lbGenerator *gen, Ast *expr) { return &gen->default_module; } -gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e) { - GB_ASSERT(e != nullptr); +gb_internal lbModule *lb_module_of_entity_internal(lbGenerator *gen, Entity *e, lbModule *curr_module) { lbModule **found = nullptr; + if (e->kind == Entity_Procedure && e->decl_info && e->decl_info->code_gen_module) { @@ -428,6 +459,22 @@ gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e) { return &gen->default_module; } + +gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e, lbModule *curr_module) { + GB_ASSERT(e != nullptr); + GB_ASSERT(curr_module != nullptr); + lbModule *m = lb_module_of_entity_internal(gen, e, curr_module); + + if (USE_SEPARATE_MODULES && build_context.internal_weak_monomorphization) { + if (e->kind == Entity_Procedure && e->Procedure.generated_from_polymorphic) { + if (m->polymorphic_module) { + return m->polymorphic_module; + } + } + } + return m; +} + gb_internal lbAddr lb_addr(lbValue addr) { lbAddr v = {lbAddr_Default, addr}; return v; @@ -2914,7 +2961,7 @@ gb_internal lbValue lb_find_ident(lbProcedure *p, lbModule *m, Entity *e, Ast *e return lb_find_procedure_value_from_entity(m, e); } if (USE_SEPARATE_MODULES) { - lbModule *other_module = lb_module_of_entity(m->gen, e); + lbModule *other_module = lb_module_of_entity(m->gen, e, m); if (other_module != m) { String name = lb_get_entity_name(other_module, e); @@ -2962,7 +3009,7 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) lbModule *other_module = m; if (USE_SEPARATE_MODULES) { - other_module = lb_module_of_entity(gen, e); + other_module = lb_module_of_entity(gen, e, m); } if (other_module == m) { debugf("Missing Procedure (lb_find_procedure_value_from_entity): %.*s module %p\n", LIT(e->token.string), m); @@ -3145,7 +3192,7 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { } if (USE_SEPARATE_MODULES) { - lbModule *other_module = lb_module_of_entity(m->gen, e); + lbModule *other_module = lb_module_of_entity(m->gen, e, m); bool is_external = other_module != m; if (!is_external) { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 06829efab..20d627fa2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -84,7 +84,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i String link_name = {}; if (ignore_body) { - lbModule *other_module = lb_module_of_entity(m->gen, entity); + lbModule *other_module = lb_module_of_entity(m->gen, entity, m); link_name = lb_get_entity_name(other_module, entity); } else { link_name = lb_get_entity_name(m, entity); diff --git a/src/main.cpp b/src/main.cpp index 184b1eaac..130c3f31d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -403,6 +403,7 @@ enum BuildFlagKind { BuildFlag_InternalCached, BuildFlag_InternalNoInline, BuildFlag_InternalByValue, + BuildFlag_InternalWeakMonomorphization, BuildFlag_Tilde, @@ -626,6 +627,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_InternalCached, str_lit("internal-cached"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalNoInline, str_lit("internal-no-inline"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalByValue, str_lit("internal-by-value"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalWeakMonomorphization, str_lit("internal-weak-monomorphization"), BuildFlagParam_None, Command_all); #if ALLOW_TILDE add_flag(&build_flags, BuildFlag_Tilde, str_lit("tilde"), BuildFlagParam_None, Command__does_build); @@ -1584,6 +1586,9 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_InternalByValue: build_context.internal_by_value = true; break; + case BuildFlag_InternalWeakMonomorphization: + build_context.internal_weak_monomorphization = true; + break; case BuildFlag_Tilde: build_context.tilde_backend = true; -- cgit v1.2.3 From 6ce889f4ebf10d44fc6c1e5fba794e412dfcf183 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 11:01:41 +0100 Subject: `Entity *` to `std::atomic` to remove the need for a PtrMap+Mutex --- src/check_decl.cpp | 8 ++++---- src/check_expr.cpp | 9 +++++---- src/checker.cpp | 2 +- src/checker.hpp | 2 +- src/llvm_backend.hpp | 3 --- src/llvm_backend_general.cpp | 21 +++++++++------------ src/llvm_backend_proc.cpp | 2 +- src/llvm_backend_type.cpp | 5 +++-- 8 files changed, 24 insertions(+), 28 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 8fbcb5e40..092222d3c 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1549,7 +1549,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { "\tother at %s", LIT(name), token_pos_to_string(pos)); } else if (name == "main") { - if (d->entity->pkg->kind != Package_Runtime) { + if (d->entity.load()->pkg->kind != Package_Runtime) { error(d->proc_lit, "The link name 'main' is reserved for internal use"); } } else { @@ -1967,8 +1967,8 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de ctx->curr_proc_sig = type; ctx->curr_proc_calling_convention = type->Proc.calling_convention; - if (decl->parent && decl->entity && decl->parent->entity) { - decl->entity->parent_proc_decl = decl->parent; + if (decl->parent && decl->entity.load() && decl->parent->entity) { + decl->entity.load()->parent_proc_decl = decl->parent; } if (ctx->pkg->name != "runtime") { @@ -2072,7 +2072,7 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de GB_ASSERT(decl->proc_checked_state != ProcCheckedState_Checked); if (decl->defer_use_checked) { GB_ASSERT(is_type_polymorphic(type, true)); - error(token, "Defer Use Checked: %.*s", LIT(decl->entity->token.string)); + error(token, "Defer Use Checked: %.*s", LIT(decl->entity.load()->token.string)); GB_ASSERT(decl->defer_use_checked == false); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d2505c047..d863d6cf6 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -608,7 +608,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E entity->flags |= EntityFlag_Disabled; } - d->entity = entity; + d->entity.store(entity); AstFile *file = nullptr; { @@ -8335,9 +8335,10 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c if (c->curr_proc_decl == nullptr) { error(call, "Calling a '#force_inline' procedure that enables target features is not allowed at file scope"); } else { - GB_ASSERT(c->curr_proc_decl->entity); - GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); - String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; + Entity *e = c->curr_proc_decl->entity.load(); + GB_ASSERT(e); + GB_ASSERT(e->type->kind == Type_Proc); + String scope_features = e->type->Proc.enable_target_feature; if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) { ERROR_BLOCK(); error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); diff --git a/src/checker.cpp b/src/checker.cpp index 7da3948dc..32bda2e43 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2055,8 +2055,8 @@ gb_internal void add_entity_and_decl_info(CheckerContext *c, Ast *identifier, En add_entity_definition(info, identifier, e); GB_ASSERT(e->decl_info == nullptr); e->decl_info = d; - d->entity = e; e->pkg = c->pkg; + d->entity.store(e); isize queue_count = -1; bool is_lazy = false; diff --git a/src/checker.hpp b/src/checker.hpp index 68779c280..d22b99e89 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -209,7 +209,7 @@ struct DeclInfo { Scope * scope; - Entity *entity; + std::atomic entity; Ast * decl_node; Ast * type_expr; diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 370a6f2ca..7fe4651bb 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -238,9 +238,6 @@ struct lbGenerator : LinkerData { PtrMap modules_through_ctx; lbModule default_module; - RecursiveMutex anonymous_proc_lits_mutex; - PtrMap anonymous_proc_lits; - isize used_module_count; lbProcedure *startup_runtime; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 64d574920..129634ee6 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -157,7 +157,6 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_init(&gen->modules, gen->info->packages.count*2); map_init(&gen->modules_through_ctx, gen->info->packages.count*2); - map_init(&gen->anonymous_proc_lits, 1024); if (USE_SEPARATE_MODULES) { bool module_per_file = build_context.module_per_file && build_context.optimization_level <= 0; @@ -3084,18 +3083,14 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &prefix_name, Ast *expr, lbProcedure *parent) { - lbGenerator *gen = m->gen; - - mutex_lock(&gen->anonymous_proc_lits_mutex); - defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); + // lbGenerator *gen = m->gen; + ast_node(pl, ProcLit, expr); - TokenPos pos = ast_token(expr).pos; - lbProcedure **found = map_get(&gen->anonymous_proc_lits, expr); - if (found) { - return lb_find_procedure_value_from_entity(m, (*found)->entity); + if (pl->decl->entity.load() != nullptr) { + return lb_find_procedure_value_from_entity(m, pl->decl->entity.load()); } - ast_node(pl, ProcLit, expr); + TokenPos pos = ast_token(expr).pos; // NOTE(bill): Generate a new name // parent$count @@ -3114,15 +3109,18 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr token.string = name; Entity *e = alloc_entity_procedure(nullptr, token, type, pl->tags); e->file = expr->file(); + e->pkg = e->file->pkg; + e->scope = e->file->scope; // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time pl->decl->code_gen_module = m; e->decl_info = pl->decl; - pl->decl->entity = e; e->parent_proc_decl = pl->decl->parent; e->Procedure.is_anonymous = true; e->flags |= EntityFlag_ProcBodyChecked; + pl->decl->entity.store(e); + lbProcedure *p = lb_create_procedure(m, e); GB_ASSERT(e->code_gen_module == m); @@ -3130,7 +3128,6 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr value.value = p->value; value.type = p->type; - map_set(&gen->anonymous_proc_lits, expr, p); array_add(&m->procedures_to_generate, p); if (parent != nullptr) { array_add(&parent->children, p); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 20d627fa2..f71b693eb 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2211,7 +2211,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu GB_ASSERT(e != nullptr); if (e->parent_proc_decl != nullptr && e->parent_proc_decl->entity != nullptr) { - procedure = e->parent_proc_decl->entity->token.string; + procedure = e->parent_proc_decl->entity.load()->token.string; } else { procedure = str_lit(""); } diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index d1e7c0559..b2eec218f 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -394,8 +394,9 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ String proc_name = {}; if (t->Named.type_name->parent_proc_decl) { DeclInfo *decl = t->Named.type_name->parent_proc_decl; - if (decl->entity && decl->entity->kind == Entity_Procedure) { - proc_name = decl->entity->token.string; + Entity *e = decl->entity.load(); + if (e && e->kind == Entity_Procedure) { + proc_name = e->token.string; } } TokenPos pos = t->Named.type_name->token.pos; -- cgit v1.2.3 From 6bca1475edb8e2aec4bcbe942835bcc54f9e837e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 14:15:25 +0100 Subject: Convert `procedures_to_generate` to a queue --- src/llvm_backend.cpp | 54 +++++++++++++++++++++++++------------------- src/llvm_backend.hpp | 7 +++++- src/llvm_backend_general.cpp | 18 ++++++++++++--- src/llvm_backend_proc.cpp | 2 +- src/llvm_backend_stmt.cpp | 2 +- 5 files changed, 54 insertions(+), 29 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index f18d4af64..3ac5970f8 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -246,26 +246,12 @@ gb_internal String lb_internal_gen_name_from_type(char const *prefix, Type *type return proc_name; } - -gb_internal lbValue lb_equal_proc_for_type(lbModule *m, Type *type) { - type = base_type(type); - GB_ASSERT(is_type_comparable(type)); +gb_internal void lb_equal_proc_generate_body(lbModule *m, lbProcedure *p) { + Type *type = p->internal_gen_type; Type *pt = alloc_type_pointer(type); LLVMTypeRef ptr_type = lb_type(m, pt); - String proc_name = lb_internal_gen_name_from_type("__$equal", type); - lbProcedure **found = string_map_get(&m->gen_procs, proc_name); - lbProcedure *compare_proc = nullptr; - if (found) { - compare_proc = *found; - GB_ASSERT(compare_proc != nullptr); - return {compare_proc->value, compare_proc->type}; - } - - - lbProcedure *p = lb_create_dummy_procedure(m, proc_name, t_equal_proc); - string_map_set(&m->gen_procs, proc_name, p); lb_begin_procedure_body(p); LLVMSetLinkage(p->value, LLVMInternalLinkage); @@ -393,9 +379,29 @@ gb_internal lbValue lb_equal_proc_for_type(lbModule *m, Type *type) { } lb_end_procedure_body(p); +} + +gb_internal lbValue lb_equal_proc_for_type(lbModule *m, Type *type) { + type = base_type(type); + GB_ASSERT(is_type_comparable(type)); + + String proc_name = lb_internal_gen_name_from_type("__$equal", type); + lbProcedure **found = string_map_get(&m->gen_procs, proc_name); + if (found) { + lbProcedure *p = *found; + GB_ASSERT(p != nullptr); + return {p->value, p->type}; + } - compare_proc = p; - return {compare_proc->value, compare_proc->type}; + lbProcedure *p = lb_create_dummy_procedure(m, proc_name, t_equal_proc); + string_map_set(&m->gen_procs, proc_name, p); + p->internal_gen_type = type; + p->generate_body = lb_equal_proc_generate_body; + + // p->generate_body(m, p); + mpsc_enqueue(&m->procedures_to_generate, p); + + return {p->value, p->type}; } gb_internal lbValue lb_simple_compare_hash(lbProcedure *p, Type *type, lbValue data, lbValue seed) { @@ -2068,7 +2074,7 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc p->global_variables = &global_variables; p->objc_names = objc_names; - array_add(&main_module->procedures_to_generate, p); + mpsc_enqueue(&main_module->procedures_to_generate, p); return p; } @@ -2110,7 +2116,7 @@ gb_internal WORKER_TASK_PROC(lb_generate_procedures_and_types_per_module) { for (Entity *e : m->global_procedures_to_create) { (void)lb_get_entity_name(m, e); - array_add(&m->procedures_to_generate, lb_create_procedure(m, e)); + mpsc_enqueue(&m->procedures_to_generate, lb_create_procedure(m, e)); } return 0; } @@ -2298,7 +2304,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_function_pass_per_module) { lb_llvm_function_pass_per_function_internal(m, m->gen->objc_names); } - for (lbProcedure *p : m->procedures_to_generate) { + MUTEX_GUARD_BLOCK(&m->generated_procedures_mutex) for (lbProcedure *p : m->generated_procedures) { if (p->body != nullptr) { // Build Procedure lbFunctionPassManagerKind pass_manager_kind = lbFunctionPassManager_default; if (p->flags & lbProcedureFlag_WithoutMemcpyPass) { @@ -2447,8 +2453,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { gb_internal WORKER_TASK_PROC(lb_generate_procedures_worker_proc) { lbModule *m = cast(lbModule *)data; - for (isize i = 0; i < m->procedures_to_generate.count; i++) { - lbProcedure *p = m->procedures_to_generate[i]; + for (lbProcedure *p = nullptr; mpsc_dequeue(&m->procedures_to_generate, &p); /**/) { lb_generate_procedure(p->module, p); } return 0; @@ -2876,6 +2881,9 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { } lb_verify_function(m, p, true); + + MUTEX_GUARD(&m->generated_procedures_mutex); + array_add(&m->generated_procedures, p); } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 7fe4651bb..6d94df399 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -186,10 +186,13 @@ struct lbModule { StringMap gen_procs; // key is the canonicalized name - Array procedures_to_generate; + MPSCQueue procedures_to_generate; Array global_procedures_to_create; Array global_types_to_create; + BlockingMutex generated_procedures_mutex; + Array generated_procedures; + lbProcedure *curr_procedure; LLVMBuilderRef const_dummy_builder; @@ -238,6 +241,8 @@ struct lbGenerator : LinkerData { PtrMap modules_through_ctx; lbModule default_module; + lbModule *equal_module; + isize used_module_count; lbProcedure *startup_runtime; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 49a7fb13f..c84edc178 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -97,12 +97,16 @@ gb_internal WORKER_TASK_PROC(lb_init_module_worker_proc) { map_init(&m->function_type_map); string_map_init(&m->gen_procs); if (USE_SEPARATE_MODULES) { - array_init(&m->procedures_to_generate, a, 0, 1<<10); + mpsc_init(&m->procedures_to_generate, a); map_init(&m->procedure_values, 1<<11); + array_init(&m->generated_procedures, a, 0, 1<<10); } else { - array_init(&m->procedures_to_generate, a, 0, c->info.all_procedures.count); + mpsc_init(&m->procedures_to_generate, a); map_init(&m->procedure_values, c->info.all_procedures.count*2); + array_init(&m->generated_procedures, a, 0, c->info.all_procedures.count*2); } + + array_init(&m->global_procedures_to_create, a, 0, 1024); array_init(&m->global_types_to_create, a, 0, 1024); array_init(&m->missing_procedures_to_check, a, 0, 16); @@ -213,6 +217,14 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { } } } + + if (LLVM_WEAK_MONOMORPHIZATION) { + lbModule *m = gb_alloc_item(permanent_allocator(), lbModule); + gen->equal_module = m; + m->checker = c; + map_set(&gen->modules, cast(void *)m, m); // point to itself just add it to the list + lb_init_module(m, do_threading); + } } gen->default_module.gen = gen; @@ -3169,7 +3181,7 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr value.value = p->value; value.type = p->type; - array_add(&m->procedures_to_generate, p); + mpsc_enqueue(&m->procedures_to_generate, p); if (parent != nullptr) { array_add(&parent->children, p); } else { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index f71b693eb..3b8a829b7 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -835,7 +835,7 @@ gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e) lb_add_entity(m, e, value); array_add(&p->children, nested_proc); - array_add(&m->procedures_to_generate, nested_proc); + mpsc_enqueue(&m->procedures_to_generate, nested_proc); } diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 590920b59..f247fa2a7 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -92,7 +92,7 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd) value.value = nested_proc->value; value.type = nested_proc->type; - array_add(&p->module->procedures_to_generate, nested_proc); + mpsc_enqueue(&p->module->procedures_to_generate, nested_proc); array_add(&p->children, nested_proc); string_map_set(&p->module->members, name, value); } -- cgit v1.2.3 From 9b8771b475a2e0a75205408b2615f69d65a329bd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 16:15:04 +0100 Subject: Handle missing procedures better --- src/llvm_backend.cpp | 16 +++++++++++----- src/llvm_backend.hpp | 6 +++--- src/llvm_backend_general.cpp | 9 +++++++-- src/llvm_backend_proc.cpp | 1 - 4 files changed, 21 insertions(+), 11 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 02df76243..1742271b6 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -12,7 +12,7 @@ #endif #ifndef LLVM_WEAK_MONOMORPHIZATION -#define LLVM_WEAK_MONOMORPHIZATION build_context.internal_weak_monomorphization +#define LLVM_WEAK_MONOMORPHIZATION 1 #endif @@ -2478,8 +2478,14 @@ gb_internal void lb_generate_procedures(lbGenerator *gen, bool do_threading) { gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc) { lbModule *m = cast(lbModule *)data; for (lbProcedure *p = nullptr; mpsc_dequeue(&m->missing_procedures_to_check, &p); /**/) { - debugf("Generate missing procedure: %.*s module %p\n", LIT(p->name), m); - lb_generate_procedure(m, p); + if (!p->is_done.load(std::memory_order_relaxed)) { + debugf("Generate missing procedure: %.*s module %p\n", LIT(p->name), m); + lb_generate_procedure(m, p); + } + + for (lbProcedure *nested = nullptr; mpsc_dequeue(&m->procedures_to_generate, &nested); /**/) { + mpsc_enqueue(&m->missing_procedures_to_check, nested); + } } return 0; } @@ -2860,7 +2866,7 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star } gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { - if (p->is_done) { + if (p->is_done.load(std::memory_order_relaxed)) { return; } @@ -2869,7 +2875,7 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { lb_begin_procedure_body(p); lb_build_stmt(p, p->body); lb_end_procedure_body(p); - p->is_done = true; + p->is_done.store(true, std::memory_order_relaxed); m->curr_procedure = nullptr; } else if (p->generate_body != nullptr) { p->generate_body(m, p); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 559ec8300..cee46701f 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -366,9 +366,9 @@ struct lbProcedure { lbFunctionType *abi_function_type; - LLVMValueRef value; - LLVMBuilderRef builder; - bool is_done; + LLVMValueRef value; + LLVMBuilderRef builder; + std::atomic is_done; lbAddr return_ptr; Array defer_stmts; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index f42087f6b..02f67b1b8 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3082,9 +3082,12 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) if (found == nullptr) { // THIS IS THE RACE CONDITION lbProcedure *missing_proc_in_other_module = lb_create_procedure(other_module, e, false); - mpsc_enqueue(&other_module->missing_procedures_to_check, missing_proc_in_other_module); + if (!missing_proc_in_other_module->is_done.load(std::memory_order_relaxed)) { + mpsc_enqueue(&other_module->missing_procedures_to_check, missing_proc_in_other_module); + } } } else { + GB_PANIC("missing procedure: %.*s", LIT(missing_proc->name)); mpsc_enqueue(&m->missing_procedures_to_check, missing_proc); } @@ -3157,7 +3160,9 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr rw_mutex_shared_unlock(&target_module->values_mutex); if (found == nullptr) { lbProcedure *missing_proc_in_target_module = lb_create_procedure(target_module, e, false); - mpsc_enqueue(&target_module->missing_procedures_to_check, missing_proc_in_target_module); + if (!missing_proc_in_target_module->is_done.load(std::memory_order_relaxed)) { + mpsc_enqueue(&target_module->missing_procedures_to_check, missing_proc_in_target_module); + } } lbProcedure *p = lb_create_procedure(m, e, true); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3b8a829b7..ee17ef771 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -99,7 +99,6 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i } } - lbProcedure *p = gb_alloc_item(permanent_allocator(), lbProcedure); p->module = m; -- cgit v1.2.3 From ffdfbfe2c2d0e09087f166be79f3dbc2859844e6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 20:20:26 +0100 Subject: Begin to support constant array of unions --- src/check_expr.cpp | 28 ++++++++-------------------- src/llvm_backend_const.cpp | 18 ++++++++++-------- src/llvm_backend_general.cpp | 11 ++++++++++- src/llvm_backend_proc.cpp | 2 +- src/llvm_backend_type.cpp | 6 +++--- src/parser.hpp | 18 ++++++++++++++++++ src/types.cpp | 22 +++++++++++++++++++++- 7 files changed, 71 insertions(+), 34 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a825ec7bf..2da8776eb 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3505,24 +3505,6 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type return false; } -gb_internal bool is_type_union_constantable(Type *type) { - Type *bt = base_type(type); - GB_ASSERT(bt->kind == Type_Union); - - if (bt->Union.variants.count == 0) { - return true; - } else if (bt->Union.variants.count == 1) { - return is_type_constant_type(bt->Union.variants[0]); - } - - for (Type *v : bt->Union.variants) { - if (!is_type_constant_type(v)) { - return false; - } - } - return true; -} - gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) { bool is_const_expr = x->mode == Addressing_Constant; @@ -4880,7 +4862,10 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar break; } operand->type = new_type; - operand->mode = Addressing_Value; + if (operand->mode != Addressing_Constant || + !elem_type_can_be_constant(operand->type)) { + operand->mode = Addressing_Value; + } break; } else if (valid_count > 1) { ERROR_BLOCK(); @@ -9895,7 +9880,10 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * Operand o = {}; check_expr_or_type(c, &o, elem, field->type); - if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) { + if (is_type_any(field->type) || + is_type_raw_union(field->type) || + (is_type_union(field->type) && !is_type_union_constantable(field->type)) || + is_type_typeid(field->type)) { is_constant = false; } if (is_constant) { diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 415d6743b..8cdc889e0 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -96,10 +96,6 @@ gb_internal LLVMValueRef llvm_const_cast(LLVMValueRef val, LLVMTypeRef dst) { case LLVMPointerTypeKind: return LLVMConstPointerCast(val, dst); case LLVMStructTypeKind: - // GB_PANIC("%s -> %s", LLVMPrintValueToString(val), LLVMPrintTypeToString(dst)); - // NOTE(bill): It's not possible to do a bit cast on a struct, why was this code even here in the first place? - // It seems mostly to exist to get around the "anonymous -> named" struct assignments - // return LLVMConstBitCast(val, dst); return val; default: GB_PANIC("Unhandled const cast %s to %s", LLVMPrintTypeToString(src), LLVMPrintTypeToString(dst)); @@ -199,11 +195,17 @@ gb_internal LLVMValueRef llvm_const_named_struct_internal(LLVMTypeRef t, LLVMVal return LLVMConstNamedStruct(t, values, value_count); } -gb_internal LLVMValueRef llvm_const_array(LLVMTypeRef elem_type, LLVMValueRef *values, isize value_count_) { +gb_internal LLVMValueRef llvm_const_array(lbModule *m, LLVMTypeRef elem_type, LLVMValueRef *values, isize value_count_) { unsigned value_count = cast(unsigned)value_count_; for (unsigned i = 0; i < value_count; i++) { values[i] = llvm_const_cast(values[i], elem_type); } + for (unsigned i = 0; i < value_count; i++) { + if (elem_type != LLVMTypeOf(values[i])) { + return LLVMConstStructInContext(m->ctx, values, value_count, false); + } + } + return LLVMConstArray(elem_type, values, value_count); } @@ -461,7 +463,7 @@ gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, return lb_addr_load(p, v).value; } - return llvm_const_array(lb_type(m, elem_type), values, cast(unsigned int)count); + return llvm_const_array(m, lb_type(m, elem_type), values, cast(unsigned int)count); } gb_internal LLVMValueRef lb_big_int_to_llvm(lbModule *m, Type *original_type, BigInt const *a) { @@ -1016,7 +1018,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } GB_ASSERT(offset == s.len); - res.value = llvm_const_array(et, elems, cast(unsigned)count); + res.value = llvm_const_array(m, et, elems, cast(unsigned)count); return res; } // NOTE(bill, 2021-10-07): Allow for array programming value constants @@ -1046,7 +1048,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb elems[i] = single_elem.value; } - res.value = llvm_const_array(lb_type(m, elem), elems, cast(unsigned)count); + res.value = llvm_const_array(m, lb_type(m, elem), elems, cast(unsigned)count); return res; } else if (is_type_matrix(type) && value.kind != ExactValue_Invalid && diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 6e513a075..8e5efcb52 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3253,11 +3253,18 @@ gb_internal lbAddr lb_add_global_generated_with_name(lbModule *m, Type *type, lb GB_ASSERT(type != nullptr); type = default_type(type); + LLVMTypeRef actual_type = lb_type(m, type); + if (value.value != nullptr) { + LLVMTypeRef value_type = LLVMTypeOf(value.value); + GB_ASSERT(lb_sizeof(actual_type) == lb_sizeof(value_type)); + actual_type = value_type; + } + 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), alloc_cstring(temporary_allocator(), name)); + g.value = LLVMAddGlobal(m->mod, actual_type, alloc_cstring(temporary_allocator(), name)); if (value.value != nullptr) { GB_ASSERT_MSG(LLVMIsConstant(value.value), LLVMPrintValueToString(value.value)); LLVMSetInitializer(g.value, value.value); @@ -3265,6 +3272,8 @@ gb_internal lbAddr lb_add_global_generated_with_name(lbModule *m, Type *type, lb LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, type))); } + g.value = LLVMConstPointerCast(g.value, lb_type(m, g.type)); + lb_add_entity(m, e, g); lb_add_member(m, name, g); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index ee17ef771..19213e1a3 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2237,7 +2237,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu elements[i] = element; } - LLVMValueRef backing_array = llvm_const_array(lb_type(m, t_load_directory_file), elements, count); + LLVMValueRef backing_array = llvm_const_array(m, lb_type(m, t_load_directory_file), elements, count); Type *array_type = alloc_type_array(t_load_directory_file, count); lbAddr backing_array_addr = lb_add_global_generated_from_procedure(p, array_type, {backing_array, array_type}); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index b2eec218f..7d412eb15 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -302,7 +302,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ (name##_values)[i] = LLVMConstNull(elem); \ } \ } \ - LLVMSetInitializer(name.addr.value, llvm_const_array(elem, name##_values, at->Array.count)); \ + LLVMSetInitializer(name.addr.value, llvm_const_array(m, elem, name##_values, at->Array.count)); \ }) type_info_allocate_values(lb_global_type_info_member_types); @@ -752,8 +752,8 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ value_values[i] = lb_const_value(m, t_i64, fields[i]->Constant.value).value; } - LLVMValueRef name_init = llvm_const_array(lb_type(m, t_string), name_values, cast(unsigned)fields.count); - LLVMValueRef value_init = llvm_const_array(lb_type(m, t_type_info_enum_value), value_values, cast(unsigned)fields.count); + LLVMValueRef name_init = llvm_const_array(m, lb_type(m, t_string), name_values, cast(unsigned)fields.count); + LLVMValueRef value_init = llvm_const_array(m, lb_type(m, t_type_info_enum_value), value_values, cast(unsigned)fields.count); LLVMSetInitializer(name_array.value, name_init); LLVMSetInitializer(value_array.value, value_init); LLVMSetGlobalConstant(name_array.value, true); diff --git a/src/parser.hpp b/src/parser.hpp index 56447df43..979b44618 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -27,6 +27,24 @@ enum AddressingMode : u8 { Addressing_SwizzleVariable = 14, // Swizzle indexed variable }; +gb_global String const addressing_mode_strings[] = { + str_lit("Invalid"), + str_lit("NoValue"), + str_lit("Value"), + str_lit("Context"), + str_lit("Variable"), + str_lit("Constant"), + str_lit("Type"), + str_lit("Builtin"), + str_lit("ProcGroup"), + str_lit("MapIndex"), + str_lit("OptionalOk"), + str_lit("OptionalOkPtr"), + str_lit("SoaVariable"), + str_lit("SwizzleValue"), + str_lit("SwizzleVariable"), +}; + struct TypeAndValue { Type * type; AddressingMode mode; diff --git a/src/types.cpp b/src/types.cpp index 0c6702103..46e41bb4b 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2507,15 +2507,35 @@ gb_internal bool type_has_nil(Type *t) { return false; } +gb_internal bool is_type_union_constantable(Type *type) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_Union); + + if (bt->Union.variants.count == 0) { + return true; + } else if (bt->Union.variants.count == 1) { + return is_type_constant_type(bt->Union.variants[0]); + } + + for (Type *v : bt->Union.variants) { + if (!is_type_constant_type(v)) { + return false; + } + } + return true; +} gb_internal bool elem_type_can_be_constant(Type *t) { t = base_type(t); if (t == t_invalid) { return false; } - if (is_type_any(t) || is_type_union(t) || is_type_raw_union(t)) { + if (is_type_any(t) || is_type_raw_union(t)) { return false; } + if (is_type_union(t)) { + return is_type_union_constantable(t); + } return true; } -- cgit v1.2.3 From b711e9296868accd2a1ca889db60dc54ecd46c75 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 12:27:28 +0100 Subject: Add bit cast --- src/llvm_backend_proc.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 19213e1a3..926401657 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -921,13 +921,20 @@ gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue arg_type != param_type) { LLVMTypeKind arg_kind = LLVMGetTypeKind(arg_type); LLVMTypeKind param_kind = LLVMGetTypeKind(param_type); - if (arg_kind == param_kind && - arg_kind == LLVMPointerTypeKind) { - // NOTE(bill): LLVM's newer `ptr` only type system seems to fail at times - // I don't know why... - args[i] = LLVMBuildPointerCast(p->builder, args[i], param_type, ""); - arg_type = param_type; - continue; + if (arg_kind == param_kind) { + if (arg_kind == LLVMPointerTypeKind) { + // NOTE(bill): LLVM's newer `ptr` only type system seems to fail at times + // I don't know why... + args[i] = LLVMBuildPointerCast(p->builder, args[i], param_type, ""); + arg_type = param_type; + continue; + } else if (arg_kind == LLVMStructTypeKind) { + if (lb_sizeof(arg_type) == lb_sizeof(param_type)) { + args[i] = LLVMBuildBitCast(p->builder, args[i], param_type, ""); + arg_type = param_type; + continue; + } + } } } -- cgit v1.2.3 From 240b2f1819294cc59b48f88ef3344bf77265fec6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 12:54:52 +0100 Subject: Disable `#raw_union` constants for the time being --- src/llvm_backend_proc.cpp | 6 ------ src/types.cpp | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 926401657..7680c5e76 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -928,12 +928,6 @@ gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue args[i] = LLVMBuildPointerCast(p->builder, args[i], param_type, ""); arg_type = param_type; continue; - } else if (arg_kind == LLVMStructTypeKind) { - if (lb_sizeof(arg_type) == lb_sizeof(param_type)) { - args[i] = LLVMBuildBitCast(p->builder, args[i], param_type, ""); - arg_type = param_type; - continue; - } } } } diff --git a/src/types.cpp b/src/types.cpp index 1fbcd429b..effa8ef64 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2541,7 +2541,8 @@ gb_internal bool is_type_raw_union_constantable(Type *type) { return false; } } - return true; + // return true; + return false; // Disable raw union constants for the time being } -- 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_proc.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 481deee4ae409b495fff089c08b6acd9f552a7b3 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Mon, 29 Sep 2025 20:15:46 -0400 Subject: Fix entity nullptr check --- .gitignore | 1 + src/llvm_backend_proc.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/llvm_backend_proc.cpp') diff --git a/.gitignore b/.gitignore index 7057aea2c..4efb5d73e 100644 --- a/.gitignore +++ b/.gitignore @@ -307,3 +307,4 @@ compile_commands.json build/ cmake-build*/ CMakeLists.txt +sandbox/ diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3bd5f4ef2..08ef42f01 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -4135,7 +4135,7 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { return lb_build_builtin_proc(p, expr, tv, id); } - bool is_objc_call = proc_entity->Procedure.is_objc_impl_or_import; + bool is_objc_call = proc_entity && proc_entity->Procedure.is_objc_impl_or_import; // NOTE(bill): Regular call lbValue value = {}; -- cgit v1.2.3