Browse Source

Merge pull request #5727 from harold-b/hb.intrinsics.objc_super

Add intrinsics.objc_super and Automatically emit objc_msgSend calls
gingerBill 1 week ago
parent
commit
810ca89253

+ 6 - 0
.gitignore

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

+ 6 - 4
base/intrinsics/intrinsics.odin

@@ -374,10 +374,11 @@ objc_selector :: struct{}
 objc_class    :: struct{}
 objc_class    :: struct{}
 objc_ivar     :: 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_find_selector     :: proc($name: string) -> objc_SEL   ---
 objc_register_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_register_class    :: proc($name: string) -> objc_Class ---
 objc_ivar_get          :: proc(self: ^$T) -> ^$U ---
 objc_ivar_get          :: proc(self: ^$T) -> ^$U ---
 objc_block             :: proc(invoke: $T, ..any) -> ^Objc_Block(T) where type_is_proc(T) ---
 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 ---
 valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
 
 

+ 14 - 4
base/runtime/procs_darwin.odin

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

+ 56 - 3
src/check_builtin.cpp

@@ -210,7 +210,7 @@ gb_internal ObjcMsgKind get_objc_proc_kind(Type *return_type) {
 	return ObjcMsg_normal;
 	return ObjcMsg_normal;
 }
 }
 
 
-gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice<Type *> param_types) {
+void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice<Type *> param_types) {
 	ObjcMsgKind kind = get_objc_proc_kind(return_type);
 	ObjcMsgKind kind = get_objc_proc_kind(return_type);
 
 
 	Scope *scope = create_scope(c->info, nullptr);
 	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_fpret");
 	try_to_add_package_dependency(c, "runtime", "objc_msgSend_fp2ret");
 	try_to_add_package_dependency(c, "runtime", "objc_msgSend_fp2ret");
 	try_to_add_package_dependency(c, "runtime", "objc_msgSend_stret");
 	try_to_add_package_dependency(c, "runtime", "objc_msgSend_stret");
+
+	Slice<Ast *> args = call->CallExpr.args;
+	if (args.count > 0 && args[0]->tav.objc_super_target) {
+		try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2");
+		try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2_stret");
+	}
 }
 }
 
 
 gb_internal bool is_constant_string(CheckerContext *c, String const &builtin_name, Ast *expr, String *name_) {
 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;
 		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;
 		param_operands[0] = *operand;
 
 
 		for (isize i = 0; i < ce->args.count-1; i++) {
 		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;
 		operand->mode = Addressing_Value;
 		return true;
 		return true;
 	} break;
 	} 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_register_class:
 	case BuiltinProc_objc_ivar_get:
 	case BuiltinProc_objc_ivar_get:
 	case BuiltinProc_objc_block:
 	case BuiltinProc_objc_block:
+	case BuiltinProc_objc_super:
 		return check_builtin_objc_procedure(c, operand, call, id, type_hint);
 		return check_builtin_objc_procedure(c, operand, call, id, type_hint);
 
 
 	case BuiltinProc___entry_point:
 	case BuiltinProc___entry_point:

+ 80 - 42
src/check_decl.cpp

@@ -587,9 +587,7 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
 					super = named_type->Named.type_name->TypeName.objc_superclass;
 					super = named_type->Named.type_name->TypeName.objc_superclass;
 				}
 				}
 			} else {
 			} 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");
 					error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied");
 				} else if (ac.objc_context_provider != nullptr) {
 				} 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");
 					error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied");
@@ -1084,61 +1082,100 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon
 		// Enable implementation by default if the class is an implementer too and
 		// Enable implementation by default if the class is an implementer too and
 		// @objc_implement was not set to false explicitly in this proc.
 		// @objc_implement was not set to false explicitly in this proc.
 		bool implement = tn->TypeName.objc_is_implementation;
 		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) {
 		if (ac.objc_is_disabled_implement) {
 			implement = false;
 			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;
 			auto &proc = e->type->Proc;
 			Type *first_param = proc.param_count > 0 ? proc.params->Tuple.variables[0]->type : t_untyped_nil;
 			Type *first_param = proc.param_count > 0 ? proc.params->Tuple.variables[0]->type : t_untyped_nil;
 
 
-			if (!tn->TypeName.objc_is_implementation) {
-				error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied");
-			} else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) {
-				error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)");
-			} else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) {
-				error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set");
-			} else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) {
-				error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention");
-			} else if (proc.result_count > 1) {
-				error(e->token, "Objective-C method implementations may return at most 1 value");
-			} else {
-				// Always export unconditionally
-				// NOTE(harold): This means check_objc_methods() MUST be called before
-				//				 e->Procedure.is_export is set in check_proc_decl()!
-				if (ac.is_export) {
-					error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly");
-				}
-				if (ac.link_name != "") {
-					error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly");
-				}
+			if (implement) {
+				if( !has_body ) {
+					error(e->token, "Procedures with @(objc_is_implement) must have a body");
+				} else if (!tn->TypeName.objc_is_implementation) {
+					error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied");
+				} else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) {
+					error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)");
+				} else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) {
+					error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set");
+				} else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) {
+					error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention");
+				} else if (proc.result_count > 1) {
+					error(e->token, "Objective-C method implementations may return at most 1 value");
+				} else {
+					// Always export unconditionally
+					// NOTE(harold): This means check_objc_methods() MUST be called before
+					//               e->Procedure.is_export is set in check_proc_decl()!
+					if (ac.is_export) {
+						error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly");
+					}
+					if (ac.link_name != "") {
+						error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly");
+					}
 
 
-				ac.is_export = true;
-				ac.linkage   = STR_LIT("strong");
+					ac.is_export = true;
+					ac.linkage   = STR_LIT("strong");
 
 
-				auto method = ObjcMethodData{ ac, e };
-				method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name;
+					auto method = ObjcMethodData{ ac, e };
+					method.ac.objc_selector = objc_selector;
 
 
-				CheckerInfo *info = ctx->info;
-				mutex_lock(&info->objc_method_mutex);
-				defer (mutex_unlock(&info->objc_method_mutex));
+					CheckerInfo *info = ctx->info;
+					mutex_lock(&info->objc_method_mutex);
+					defer (mutex_unlock(&info->objc_method_mutex));
 
 
-				Array<ObjcMethodData>* method_list = map_get(&info->objc_method_implementations, t);
-				if (method_list) {
-					array_add(method_list, method);
-				} else {
-					auto list = array_make<ObjcMethodData>(permanent_allocator(), 1, 8);
-					list[0] = method;
+					Array<ObjcMethodData>* method_list = map_get(&info->objc_method_implementations, t);
+					if (method_list) {
+						array_add(method_list, method);
+					} else {
+						auto list = array_make<ObjcMethodData>(permanent_allocator(), 1, 8);
+						list[0] = method;
 
 
-					map_set(&info->objc_method_implementations, t, list);
+						map_set(&info->objc_method_implementations, t, list);
+					}
+				}
+			} else if (!has_body) {
+				if (ac.objc_selector == "The @(objc_selector) attribute is required for imported Objective-C methods.") {
+					return;
+				} else if (proc.calling_convention != ProcCC_CDecl) {
+					error(e->token, "Imported Objective-C methods must use the \"c\" calling convention");
+					return;
+				} else if (tn->TypeName.objc_context_provider) {
+					error(e->token, "Imported Objective-C class '%.*s' must not declare context providers.", tn->type->Named.name);
+					return;
+				} else if (tn->TypeName.objc_is_implementation) {
+					error(e->token, "Imported Objective-C methods used in a class with @(objc_implement) is not allowed.");
+					return;
+				} else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) {
+					error(e->token, "Objective-C instance methods require the first parameter to be a pointer to the class type set by @(objc_type)");
+					return;
 				}
 				}
 			}
 			}
-		} else if (ac.objc_selector != "") {
-			error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C implementations.");
+			else if(ac.objc_selector != "") {
+				error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C method implementations or are imported.");
+				return;
+			}
+		} else {
+			GB_ASSERT(e->kind == Entity_ProcGroup);
+			if (tn->TypeName.objc_is_implementation) {
+				error(e->token, "Objective-C procedure groups cannot use the @(objc_implement) attribute.");
+				return;
+			}
 		}
 		}
 
 
+
 		mutex_lock(&global_type_name_objc_metadata_mutex);
 		mutex_lock(&global_type_name_objc_metadata_mutex);
 		defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
 		defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
 
 
@@ -1523,7 +1560,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 		if (!pt->is_polymorphic) {
 		if (!pt->is_polymorphic) {
 			check_procedure_later(ctx->checker, ctx->file, e->token, d, proc_type, pl->body, pl->tags);
 			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) {
 		if (e->Procedure.is_export) {
 			error(e->token, "Foreign export procedures must have a body");
 			error(e->token, "Foreign export procedures must have a body");
 		} else {
 		} else {
@@ -1571,6 +1608,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 			// NOTE(bill): this must be delayed because the foreign import paths might not be evaluated yet until much later
 			// 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);
 			mpsc_enqueue(&ctx->info->foreign_decls_to_check, e);
 		} else {
 		} 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);
 			check_foreign_procedure(ctx, e, d);
 		}
 		}
 	} else {
 	} else {

+ 73 - 0
src/check_expr.cpp

@@ -8146,6 +8146,73 @@ gb_internal ExprKind check_call_expr_as_type_cast(CheckerContext *c, Operand *op
 }
 }
 
 
 
 
+void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice<Type *> param_types);
+
+gb_internal void check_objc_call_expr(CheckerContext *c, Operand *operand, Ast *call, Entity *proc_entity, Type *proc_type) {
+	auto &proc = proc_type->Proc;
+	Slice<Entity *> params = proc.params ? proc.params->Tuple.variables : Slice<Entity *>{};
+
+	Type *self_type = nullptr;
+	isize params_start = 1;
+
+	ast_node(ce, CallExpr, call);
+
+	Type *return_type = proc.result_count == 0 ? nullptr : proc.results->Tuple.variables[0]->type;
+	bool is_return_instancetype = return_type != nullptr && return_type == t_objc_instancetype;
+
+	if (params.count == 0 || !is_type_objc_ptr_to_object(params[0]->type)) {
+		if (!proc_entity->Procedure.is_objc_class_method) {
+			// Not a class method, invalid call
+			error(call, "Invalid Objective-C call: The Objective-C method is not a class method but this first parameter is not an Objective-C object pointer.");
+			return;
+		}
+
+		if (is_return_instancetype) {
+			if (ce->proc->kind == Ast_SelectorExpr) {
+				ast_node(se, SelectorExpr, ce->proc);
+
+				// NOTE(harold): These should have already been checked, right?
+				GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named);
+
+				return_type = alloc_type_pointer(se->expr->tav.type);
+			} else {
+				return_type = proc_entity->Procedure.objc_class->type;
+			}
+		}
+
+		self_type    = t_objc_Class;
+		params_start = 0;
+	} else if (ce->args.count > 0) {
+		GB_ASSERT(is_type_objc_ptr_to_object(params[0]->type));
+
+		if (ce->args[0]->tav.objc_super_target) {
+			self_type = t_objc_super_ptr;
+		} else {
+			self_type = ce->args[0]->tav.type;
+		}
+
+		if (is_return_instancetype) {
+			// NOTE(harold): These should have already been checked, right?
+			GB_ASSERT(ce->args[0]->tav.type && ce->args[0]->tav.type->kind == Type_Pointer && ce->args[0]->tav.type->Pointer.elem->kind == Type_Named);
+
+			return_type = ce->args[0]->tav.type;
+		}
+	}
+
+	auto param_types = slice_make<Type *>(permanent_allocator(), proc.param_count + 2 - params_start);
+	param_types[0] = self_type;
+	param_types[1] = t_objc_SEL;
+
+	for (isize i = params_start; i < params.count; i++) {
+		param_types[i+2-params_start] = params[i]->type;
+	}
+
+	if (is_return_instancetype) {
+		operand->type = return_type;
+	}
+
+	add_objc_proc_type(c, call, return_type, param_types);
+}
 
 
 gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice<Ast *> const &args, ProcInlining inlining, Type *type_hint) {
 gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice<Ast *> const &args, ProcInlining inlining, Type *type_hint) {
 	if (proc != nullptr &&
 	if (proc != nullptr &&
@@ -8409,6 +8476,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;
 	return Expr_Expr;
 }
 }
 
 

+ 10 - 0
src/checker.cpp

@@ -1416,6 +1416,8 @@ gb_internal void init_universal(void) {
 		t_objc_SEL      = alloc_type_pointer(t_objc_selector);
 		t_objc_SEL      = alloc_type_pointer(t_objc_selector);
 		t_objc_Class    = alloc_type_pointer(t_objc_class);
 		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);
+
+		t_objc_instancetype = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_instancetype"), t_objc_id);
 	}
 	}
 }
 }
 
 
@@ -3386,12 +3388,20 @@ gb_internal void init_core_map_type(Checker *c) {
 	t_raw_map_ptr       = alloc_type_pointer(t_raw_map);
 	t_raw_map_ptr       = alloc_type_pointer(t_raw_map);
 }
 }
 
 
+gb_internal void init_core_objc_c(Checker *c) {
+	if (build_context.metrics.os == TargetOs_darwin) {
+		t_objc_super     = find_core_type(c, str_lit("objc_super"));
+		t_objc_super_ptr = alloc_type_pointer(t_objc_super);
+	}
+}
+
 gb_internal void init_preload(Checker *c) {
 gb_internal void init_preload(Checker *c) {
 	init_core_type_info(c);
 	init_core_type_info(c);
 	init_mem_allocator(c);
 	init_mem_allocator(c);
 	init_core_context(c);
 	init_core_context(c);
 	init_core_source_code_location(c);
 	init_core_source_code_location(c);
 	init_core_map_type(c);
 	init_core_map_type(c);
+	init_core_objc_c(c);
 }
 }
 
 
 gb_internal ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) {
 gb_internal ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) {

+ 3 - 1
src/checker_builtin_procs.hpp

@@ -354,6 +354,7 @@ BuiltinProc__type_end,
 	BuiltinProc_objc_register_class,
 	BuiltinProc_objc_register_class,
 	BuiltinProc_objc_ivar_get,
 	BuiltinProc_objc_ivar_get,
 	BuiltinProc_objc_block,
 	BuiltinProc_objc_block,
+	BuiltinProc_objc_super,
 
 
 	BuiltinProc_constant_utf16_cstring,
 	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_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_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_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},
 	{STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 

+ 4 - 0
src/entity.cpp

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

+ 117 - 53
src/llvm_backend.cpp

@@ -1417,8 +1417,21 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) {
 		return str_lit("?");
 		return str_lit("?");
 	case Type_Proc:
 	case Type_Proc:
 		return str_lit("?");
 		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: {
 	case Type_SimdVector: {
 		String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth);
 		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 {
 struct lbObjCGlobalClass {
 	lbObjCGlobal g;
 	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(
 gb_internal void lb_register_objc_thing(
@@ -1482,44 +1498,43 @@ gb_internal void lb_register_objc_thing(
 		LLVMSetInitializer(v.value, LLVMConstNull(t));
 		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 this class requires an implementation, save it for registration below.
 	if (g.class_impl_type != nullptr) {
 	if (g.class_impl_type != nullptr) {
 
 
 		// Make sure the superclass has been initialized before us
 		// 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;
 		auto &tn = g.class_impl_type->Named.type_name->TypeName;
 		Type *superclass = tn.objc_superclass;
 		Type *superclass = tn.objc_superclass;
 		if (superclass != nullptr) {
 		if (superclass != nullptr) {
 			auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name);
 			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);
 			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 {
 	else {
+		lbValue class_ptr  = {};
+		lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name));
+
 		args.count = 1;
 		args.count = 1;
 		args[0] = class_name;
 		args[0] = class_name;
 		class_ptr = lb_emit_runtime_call(p, call, args);
 		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);
 	string_map_init(&global_class_map, (usize)gen->objc_classes.count);
 	defer (string_map_destroy(&global_class_map));
 	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});
 		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) {
 	for (const auto &cd : class_impls) {
 		auto &g = cd.g;
 		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);
 		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;
 		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);
 			is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type);
 		}
 		}
 
 
-
 		Array<ObjcMethodData> *methods = map_get(&m->info->objc_method_implementations, class_type);
 		Array<ObjcMethodData> *methods = map_get(&m->info->objc_method_implementations, class_type);
 		if (!methods) {
 		if (!methods) {
 			continue;
 			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);
 														wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl);
 
 
 			lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type);
 			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
 			// 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);
 			lb_begin_procedure_body(wrapper_proc);
 			{
 			{
+				LLVMValueRef context_addr = nullptr;
 				if (method_type->Proc.calling_convention == ProcCC_Odin) {
 				if (method_type->Proc.calling_convention == ProcCC_Odin) {
 					GB_ASSERT(context_provider);
 					GB_ASSERT(context_provider);
 
 
 					// Emit the get odin context call
 					// Emit the get odin context call
-
 					get_context_args[0] = lbValue {
 					get_context_args[0] = lbValue {
 						wrapper_proc->raw_input_parameters[0],
 						wrapper_proc->raw_input_parameters[0],
 						contex_provider_self_ptr_type,
 						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);
 						get_context_args[0] = lb_handle_objc_ivar_for_objc_object_pointer(wrapper_proc, real_self);
 					}
 					}
 
 
-					lbValue context	     = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args);
-					lbAddr  context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context));
-					lb_push_context_onto_stack(wrapper_proc, context_addr);
+					lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args);
+					context_addr    = lb_address_from_load(wrapper_proc, context).value;//lb_address_from_load_or_generate_local(wrapper_proc, context));
+					// context_addr = LLVMGetOperand(context.value, 0);
 				}
 				}
 
 
+				isize method_forward_arg_count = method_param_count + method_param_offset;
+				isize method_forward_return_arg_offset = 0;
+				auto raw_method_args = array_make<LLVMValueRef>(temporary_allocator(), 0, method_forward_arg_count+1);
 
 
-				auto method_call_args = array_make<lbValue>(temporary_allocator(), method_param_count + method_param_offset);
+				lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity);
+				lbFunctionType* ft = lb_get_function_type(m, method_type);
+				bool has_return = false;
+				lbArgKind return_kind = {};
+
+				if (wrapper_results_tuple != nullptr) {
+					has_return = true;
+					return_kind = ft->ret.kind;
+
+					if (return_kind == lbArg_Indirect) {
+						method_forward_return_arg_offset = 1;
+						array_add(&raw_method_args, wrapper_proc->return_ptr.addr.value);
+					}
+				}
 
 
 				if (!md.ac.objc_is_class_method) {
 				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++) {
 				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.
 				// 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);
 			lb_end_procedure_body(wrapper_proc);
 
 
-
 			// Add the method to the class
 			// Add the method to the class
 			String method_encoding = str_lit("v");
 			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);
 			GB_ASSERT(method_type->Proc.result_count <= 1);
 			if (method_type->Proc.result_count != 0) {
 			if (method_type->Proc.result_count != 0) {
 				method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type);
 				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("#:"));
 				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);
 				String param_encoding = lb_get_objc_type_encoding(param_type);
 
 
 				method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding);
 				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[2] = lbValue { wrapper_proc->value, wrapper_proc->type };
 			args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding));
 			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);
 			lb_emit_runtime_call(p, "class_addMethod", args);
 
 
 		} // End methods
 		} // 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
 			// Defined in an external package, define it now in the main package
 			LLVMTypeRef t = lb_type(m, t_int);
 			LLVMTypeRef t = lb_type(m, t_int);
 
 
-			lbValue global{};
+			lbValue global = {};
 			global.value = LLVMAddGlobal(m->mod, t, g.global_name);
 			global.value = LLVMAddGlobal(m->mod, t, g.global_name);
 			global.type  = t_int_ptr;
 			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);
 		GB_ASSERT(m != nullptr);
 
 
 		if (e->kind == Entity_Procedure) {
 		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);
 			array_add(&m->global_procedures_to_create, e);
 		} else if (e->kind == Entity_TypeName) {
 		} else if (e->kind == Entity_TypeName) {
 			array_add(&m->global_types_to_create, e);
 			array_add(&m->global_types_to_create, e);

+ 16 - 6
src/llvm_backend_proc.cpp

@@ -3753,6 +3753,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 	case BuiltinProc_objc_register_class:    return lb_handle_objc_register_class(p, expr);
 	case BuiltinProc_objc_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_objc_block:             return lb_handle_objc_block(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:
 	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);
 	Ast *proc_expr = unparen_expr(ce->proc);
+	Entity *proc_entity = entity_of_node(proc_expr);
+
 	if (proc_mode == Addressing_Builtin) {
 	if (proc_mode == Addressing_Builtin) {
-		Entity *e = entity_of_node(proc_expr);
 		BuiltinProcId id = BuiltinProc_Invalid;
 		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 {
 		} else {
 			id = BuiltinProc_DIRECTIVE;
 			id = BuiltinProc_DIRECTIVE;
 		}
 		}
 		return lb_build_builtin_proc(p, expr, tv, id);
 		return lb_build_builtin_proc(p, expr, tv, id);
 	}
 	}
 
 
+	bool is_objc_call = proc_entity && proc_entity->Procedure.is_objc_impl_or_import;
+
 	// NOTE(bill): Regular call
 	// NOTE(bill): Regular call
 	lbValue value = {};
 	lbValue value = {};
 
 
-	Entity *proc_entity = entity_of_node(proc_expr);
 	if (proc_entity != nullptr) {
 	if (proc_entity != nullptr) {
 		if (proc_entity->flags & EntityFlag_Disabled) {
 		if (proc_entity->flags & EntityFlag_Disabled) {
 			GB_ASSERT(tv.type == nullptr);
 			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);
 		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);
 	Type *proc_type_ = base_type(value.type);
 	GB_ASSERT(proc_type_->kind == Type_Proc);
 	GB_ASSERT(proc_type_->kind == Type_Proc);
 	TypeProc *pt = &proc_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;
 	isize final_count = is_c_vararg ? args.count : pt->param_count;
 	auto call_args = array_slice(args, 0, final_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);
 	return lb_emit_call(p, value, call_args, ce->inlining);
 }
 }
 
 

+ 180 - 31
src/llvm_backend_utility.cpp

@@ -2373,7 +2373,6 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) {
 	///       https://www.newosxbook.com/src.php?tree=xnu&file=/libkern/libkern/Block_private.h
 	///       https://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/llvm/llvm-project/blob/21f1f9558df3830ffa637def364e3c0cb0dbb3c0/compiler-rt/lib/BlocksRuntime/Block_private.h
 	///       https://github.com/apple-oss-distributions/libclosure/blob/3668b0837f47be3cc1c404fb5e360f4ff178ca13/runtime.cpp
 	///       https://github.com/apple-oss-distributions/libclosure/blob/3668b0837f47be3cc1c404fb5e360f4ff178ca13/runtime.cpp
-
 	ast_node(ce, CallExpr, expr);
 	ast_node(ce, CallExpr, expr);
 	GB_ASSERT(ce->args.count > 0);
 	GB_ASSERT(ce->args.count > 0);
 
 
@@ -2452,7 +2451,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,
 	lbProcedure *invoker_proc = lb_create_dummy_procedure(m, make_string((u8*)block_invoker_name,
 									gb_string_length(block_invoker_name)), invoker_proc_type);
 									gb_string_length(block_invoker_name)), invoker_proc_type);
+
 	LLVMSetLinkage(invoker_proc->value, LLVMPrivateLinkage);
 	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
 	// Create the block descriptor and block literal
 	gbString block_lit_type_name = gb_string_make(temporary_allocator(), "__$ObjC_Block_Literal_");
 	gbString block_lit_type_name = gb_string_make(temporary_allocator(), "__$ObjC_Block_Literal_");
@@ -2531,45 +2532,66 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) {
 	/// Invoker body
 	/// Invoker body
 	lb_begin_procedure_body(invoker_proc);
 	lb_begin_procedure_body(invoker_proc);
 	{
 	{
-		auto call_args = array_make<lbValue>(temporary_allocator(), user_proc.param_count, user_proc.param_count);
+		// Reserve 2 extra arguments for: Indirect return values and context.
+		auto call_args = array_make<LLVMValueRef>(temporary_allocator(), 0, user_proc.param_count + 2);
 
 
-		for (isize i = 1; i < invoker_proc->raw_input_parameters.count; i++) {
-			lbValue arg = {};
-			arg.type  = invoker_args[i];
-			arg.value = invoker_proc->raw_input_parameters[i],
-			call_args[i-1] = arg;
-		}
+		isize block_literal_arg_index = 0;
 
 
-		LLVMValueRef block_literal = invoker_proc->raw_input_parameters[0];
+		lbFunctionType* user_proc_ft = lb_get_function_type(m, user_proc_value.type);
 
 
-		// Push context, if needed
-		if (user_proc.calling_convention == ProcCC_Odin) {
-			LLVMValueRef p_context = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, 5, "context");
-			lbValue ctx_val = {};
-			ctx_val.type  = t_context_ptr;
-			ctx_val.value = p_context;
+		lbArgKind return_kind = {};
+
+		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
 		// 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++) {
 		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), "");
 			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);
 	lb_end_procedure_body(invoker_proc);
@@ -2587,8 +2609,8 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) {
 	gbString block_var_name = gb_string_make(temporary_allocator(), "__$objc_block_literal_");
 	gbString block_var_name = gb_string_make(temporary_allocator(), "__$objc_block_literal_");
 	block_var_name = gb_string_append_fmt(block_var_name, "%lld", m->objc_next_block_id);
 	block_var_name = gb_string_append_fmt(block_var_name, "%lld", 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 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 flags_val    = lb_const_int(m, t_i32, (u64)raw_flags);
@@ -2596,7 +2618,7 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) {
 
 
 	if (is_global) {
 	if (is_global) {
 		LLVMValueRef p_block_lit = LLVMAddGlobal(m->mod, block_lit_type, block_var_name);
 		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] = {
 		LLVMValueRef fields_values[5] = {
 			isa_val.value,       // isa
 			isa_val.value,       // isa
@@ -2611,7 +2633,7 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) {
 
 
 	} else {
 	} else {
 		LLVMValueRef p_block_lit = llvm_alloca(p, block_lit_type, lb_alignof(block_lit_type), block_var_name);
 		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
 		// Initialize it
 		LLVMValueRef f_isa        = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 0, "isa");
 		LLVMValueRef f_isa        = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 0, "isa");
@@ -2651,7 +2673,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) {
 gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) {
@@ -2767,6 +2802,120 @@ gb_internal lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) {
 	return lb_emit_call(p, the_proc, args);
 	return lb_emit_call(p, the_proc, args);
 }
 }
 
 
+gb_internal lbValue lb_handle_objc_auto_send(lbProcedure *p, Ast *expr, Slice<lbValue> const arg_values) {
+	ast_node(ce, CallExpr, expr);
+
+	lbModule *m = p->module;
+	CheckerInfo *info = m->info;
+	ObjcMsgData data = map_must_get(&info->objc_msgSend_types, expr);
+
+	Type *proc_type = data.proc_type;
+	GB_ASSERT(proc_type != nullptr);
+
+	Entity *objc_method_ent = entity_of_node(ce->proc);
+	GB_ASSERT(objc_method_ent != nullptr);
+	GB_ASSERT(objc_method_ent->kind == Entity_Procedure);
+	GB_ASSERT(objc_method_ent->Procedure.objc_selector_name.len > 0);
+
+	auto &proc = proc_type->Proc;
+	GB_ASSERT(proc.param_count >= 2);
+
+	Type *objc_super_orig_type = nullptr;
+	if (ce->args.count > 0) {
+		objc_super_orig_type = unparen_expr(ce->args[0])->tav.objc_super_target;
+	}
+
+	isize arg_offset = 1;
+	lbValue id = {};
+	if (!objc_method_ent->Procedure.is_objc_class_method) {
+		GB_ASSERT(ce->args.count > 0);
+		id = arg_values[0];
+
+		if (objc_super_orig_type) {
+			GB_ASSERT(objc_super_orig_type->kind == Type_Named);
+
+			auto& tn = objc_super_orig_type->Named.type_name->TypeName;
+			lbAddr p_supercls = lb_handle_objc_find_or_register_class(p, tn.objc_class_name, tn.objc_is_implementation ? objc_super_orig_type : nullptr);
+
+			lbValue supercls     = lb_addr_load(p, p_supercls);
+			lbAddr  p_objc_super = lb_add_local_generated(p, t_objc_super, false);
+
+			lbValue f_id         = lb_emit_struct_ep(p, p_objc_super.addr, 0);
+			lbValue f_superclass = lb_emit_struct_ep(p, p_objc_super.addr, 1);
+
+			id = lb_emit_conv(p, id, t_objc_id);
+			lb_emit_store(p, f_id, id);
+			lb_emit_store(p, f_superclass, supercls);
+
+			id = p_objc_super.addr;
+		}
+	} else {
+		Entity *objc_class = objc_method_ent->Procedure.objc_class;
+		if (ce->proc->kind == Ast_SelectorExpr) {
+			// NOTE (harold): If called via a selector expression (ex: Foo.alloc()), then we should use
+			//                the lhs-side to determine the class. This allows for class methods to be called
+			//                with the correct class as the target, even when the method is defined in a superclass.
+			ast_node(se, SelectorExpr, ce->proc);
+			GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named);
+
+			objc_class = entity_from_expr(se->expr);
+
+			GB_ASSERT(objc_class);
+			GB_ASSERT(objc_class->kind == Entity_TypeName);
+			GB_ASSERT(objc_class->TypeName.objc_class_name != "");
+		}
+
+		Type *class_impl_type = objc_class->TypeName.objc_is_implementation ? objc_class->type : nullptr;
+
+		id = lb_addr_load(p, lb_handle_objc_find_or_register_class(p, objc_class->TypeName.objc_class_name, class_impl_type));
+		arg_offset = 0;
+	}
+
+	lbValue sel = lb_addr_load(p, lb_handle_objc_find_or_register_selector(p, objc_method_ent->Procedure.objc_selector_name));
+
+	auto args = array_make<lbValue>(permanent_allocator(), 0, arg_values.count + 2 - arg_offset);
+
+	array_add(&args, id);
+	array_add(&args, sel);
+
+	for (isize i = arg_offset; i < ce->args.count; i++) {
+		array_add(&args, arg_values[i]);
+	}
+
+	lbValue the_proc = {};
+
+	if (!objc_super_orig_type) {
+		switch (data.kind) {
+			default:
+				GB_PANIC("unhandled ObjcMsgKind %u", data.kind);
+				break;
+			case ObjcMsg_normal: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend"));        break;
+			case ObjcMsg_fpret:  the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fpret"));  break;
+			case ObjcMsg_fp2ret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fp2ret")); break;
+			case ObjcMsg_stret:  the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_stret"));  break;
+		}
+	} else {
+		switch (data.kind) {
+			default:
+				GB_PANIC("unhandled ObjcMsgKind %u", data.kind);
+				break;
+			case ObjcMsg_normal:
+			case ObjcMsg_fpret:
+			case ObjcMsg_fp2ret:
+				the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2"));
+				break;
+			case ObjcMsg_stret:
+				the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2_stret"));
+				break;
+		}
+	}
+
+	the_proc = lb_emit_conv(p, the_proc, data.proc_type);
+
+	return lb_emit_call(p, the_proc, args);
+}
+
+
 gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) {
 gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) {
 	GB_ASSERT(value.kind == ExactValue_Integer);
 	GB_ASSERT(value.kind == ExactValue_Integer);
 	i64 v = exact_value_to_i64(value);
 	i64 v = exact_value_to_i64(value);

+ 2 - 1
src/parser.hpp

@@ -48,7 +48,8 @@ gb_global String const addressing_mode_strings[] = {
 struct TypeAndValue {
 struct TypeAndValue {
 	Type *         type;
 	Type *         type;
 	AddressingMode mode;
 	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;
 	ExactValue     value;
 };
 };
 
 

+ 11 - 0
src/types.cpp

@@ -752,11 +752,14 @@ gb_global Type *t_objc_object   = nullptr;
 gb_global Type *t_objc_selector = nullptr;
 gb_global Type *t_objc_selector = nullptr;
 gb_global Type *t_objc_class    = nullptr;
 gb_global Type *t_objc_class    = nullptr;
 gb_global Type *t_objc_ivar     = 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_id    = nullptr;
 gb_global Type *t_objc_SEL   = nullptr;
 gb_global Type *t_objc_SEL   = nullptr;
 gb_global Type *t_objc_Class = nullptr;
 gb_global Type *t_objc_Class = nullptr;
 gb_global Type *t_objc_Ivar  = 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 {
 enum OdinAtomicMemoryOrder : i32 {
 	OdinAtomicMemoryOrder_relaxed = 0, // unordered
 	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);
 	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) {
 gb_internal Type *get_struct_field_type(Type *t, isize index) {
 	t = base_type(type_deref(t));
 	t = base_type(type_deref(t));
 	GB_ASSERT(t->kind == Type_Struct);
 	GB_ASSERT(t->kind == Type_Struct);