mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-28 17:04:34 +00:00
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
This commit is contained in:
@@ -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 ---
|
||||
|
||||
|
||||
@@ -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 ---
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ gb_internal ObjcMsgKind get_objc_proc_kind(Type *return_type) {
|
||||
return ObjcMsg_normal;
|
||||
}
|
||||
|
||||
gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice<Type *> param_types) {
|
||||
void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice<Type *> param_types) {
|
||||
ObjcMsgKind kind = get_objc_proc_kind(return_type);
|
||||
|
||||
Scope *scope = create_scope(c->info, nullptr);
|
||||
@@ -248,6 +248,12 @@ gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_t
|
||||
try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret");
|
||||
try_to_add_package_dependency(c, "runtime", "objc_msgSend_fp2ret");
|
||||
try_to_add_package_dependency(c, "runtime", "objc_msgSend_stret");
|
||||
|
||||
Slice<Ast *> args = call->CallExpr.args;
|
||||
if (args.count > 0 && args[0]->tav.objc_super_target) {
|
||||
try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2");
|
||||
try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2_stret");
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal bool is_constant_string(CheckerContext *c, String const &builtin_name, Ast *expr, String *name_) {
|
||||
@@ -466,8 +472,8 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan
|
||||
|
||||
isize capture_arg_count = ce->args.count - 1;
|
||||
|
||||
// NOTE(harold): The first parameter is already checked at check_builtin_procedure().
|
||||
// Checking again would invalidate the Entity -> Value map for direct parameters if it's the handler proc.
|
||||
// NOTE(harold): The first argument is already checked at check_builtin_procedure().
|
||||
// Checking again would invalidate the Entity -> Value map for direct arguments if it's the handler proc.
|
||||
param_operands[0] = *operand;
|
||||
|
||||
for (isize i = 0; i < ce->args.count-1; i++) {
|
||||
@@ -680,6 +686,52 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan
|
||||
operand->mode = Addressing_Value;
|
||||
return true;
|
||||
} break;
|
||||
|
||||
case BuiltinProc_objc_super:
|
||||
{
|
||||
// Must be a pointer to an Objective-C object.
|
||||
Type *objc_obj = operand->type;
|
||||
if (!is_type_objc_ptr_to_object(objc_obj)) {
|
||||
gbString e = expr_to_string(operand->expr);
|
||||
gbString t = type_to_string(objc_obj);
|
||||
error(operand->expr, "'%.*s' expected a pointer to an Objective-C object, but got '%s' of type %s", LIT(builtin_name), e, t);
|
||||
gb_string_free(t);
|
||||
gb_string_free(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (operand->mode != Addressing_Value && operand->mode != Addressing_Variable) {
|
||||
gbString e = expr_to_string(operand->expr);
|
||||
gbString t = type_to_string(operand->type);
|
||||
error(operand->expr, "'%.*s' expression '%s', of type %s, must be a value or variable.", LIT(builtin_name), e, t);
|
||||
gb_string_free(t);
|
||||
gb_string_free(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
Type *obj_type = type_deref(objc_obj);
|
||||
GB_ASSERT(obj_type->kind == Type_Named);
|
||||
|
||||
// NOTE(harold) Track original type before transforming it to the superclass.
|
||||
// This is needed because objc_msgSendSuper2 must start its search on the subclass, not the superclass.
|
||||
call->tav.objc_super_target = obj_type;
|
||||
|
||||
// The superclass type must be known at compile time. We require this so that the selector method expressions
|
||||
// methods are resolved to the superclass's methods instead of the subclass's.
|
||||
Type *superclass = obj_type->Named.type_name->TypeName.objc_superclass;
|
||||
if (superclass == nullptr) {
|
||||
gbString t = type_to_string(obj_type);
|
||||
error(operand->expr, "'%.*s' target object '%.*s' does not have an Objective-C superclass. One must be set via the @(objc_superclass) attribute", LIT(builtin_name), t);
|
||||
gb_string_free(t);
|
||||
return false;
|
||||
}
|
||||
|
||||
GB_ASSERT(superclass->Named.type_name->TypeName.objc_class_name.len > 0);
|
||||
|
||||
operand->type = alloc_type_pointer(superclass);
|
||||
return true;
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2515,6 +2567,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
|
||||
case BuiltinProc_objc_register_class:
|
||||
case BuiltinProc_objc_ivar_get:
|
||||
case BuiltinProc_objc_block:
|
||||
case BuiltinProc_objc_super:
|
||||
return check_builtin_objc_procedure(c, operand, call, id, type_hint);
|
||||
|
||||
case BuiltinProc___entry_point:
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
ac.is_export = true;
|
||||
ac.linkage = STR_LIT("strong");
|
||||
|
||||
auto method = ObjcMethodData{ ac, e };
|
||||
method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name;
|
||||
|
||||
CheckerInfo *info = ctx->info;
|
||||
mutex_lock(&info->objc_method_mutex);
|
||||
defer (mutex_unlock(&info->objc_method_mutex));
|
||||
|
||||
Array<ObjcMethodData>* method_list = map_get(&info->objc_method_implementations, t);
|
||||
if (method_list) {
|
||||
array_add(method_list, method);
|
||||
if (implement) {
|
||||
if( !has_body ) {
|
||||
error(e->token, "Procedures with @(objc_is_implement) must have a body");
|
||||
} else if (!tn->TypeName.objc_is_implementation) {
|
||||
error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied");
|
||||
} else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) {
|
||||
error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)");
|
||||
} else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) {
|
||||
error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set");
|
||||
} else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) {
|
||||
error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention");
|
||||
} else if (proc.result_count > 1) {
|
||||
error(e->token, "Objective-C method implementations may return at most 1 value");
|
||||
} else {
|
||||
auto list = array_make<ObjcMethodData>(permanent_allocator(), 1, 8);
|
||||
list[0] = method;
|
||||
// Always export unconditionally
|
||||
// NOTE(harold): This means check_objc_methods() MUST be called before
|
||||
// e->Procedure.is_export is set in check_proc_decl()!
|
||||
if (ac.is_export) {
|
||||
error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly");
|
||||
}
|
||||
if (ac.link_name != "") {
|
||||
error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly");
|
||||
}
|
||||
|
||||
map_set(&info->objc_method_implementations, t, list);
|
||||
ac.is_export = true;
|
||||
ac.linkage = STR_LIT("strong");
|
||||
|
||||
auto method = ObjcMethodData{ ac, e };
|
||||
method.ac.objc_selector = objc_selector;
|
||||
|
||||
CheckerInfo *info = ctx->info;
|
||||
mutex_lock(&info->objc_method_mutex);
|
||||
defer (mutex_unlock(&info->objc_method_mutex));
|
||||
|
||||
Array<ObjcMethodData>* method_list = map_get(&info->objc_method_implementations, t);
|
||||
if (method_list) {
|
||||
array_add(method_list, method);
|
||||
} else {
|
||||
auto list = array_make<ObjcMethodData>(permanent_allocator(), 1, 8);
|
||||
list[0] = method;
|
||||
|
||||
map_set(&info->objc_method_implementations, t, list);
|
||||
}
|
||||
}
|
||||
} else if (!has_body) {
|
||||
if (ac.objc_selector == "The @(objc_selector) attribute is required for imported Objective-C methods.") {
|
||||
return;
|
||||
} else if (proc.calling_convention != ProcCC_CDecl) {
|
||||
error(e->token, "Imported Objective-C methods must use the \"c\" calling convention");
|
||||
return;
|
||||
} else if (tn->TypeName.objc_context_provider) {
|
||||
error(e->token, "Imported Objective-C class '%.*s' must not declare context providers.", tn->type->Named.name);
|
||||
return;
|
||||
} else if (tn->TypeName.objc_is_implementation) {
|
||||
error(e->token, "Imported Objective-C methods used in a class with @(objc_implement) is not allowed.");
|
||||
return;
|
||||
} else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) {
|
||||
error(e->token, "Objective-C instance methods require the first parameter to be a pointer to the class type set by @(objc_type)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (ac.objc_selector != "") {
|
||||
error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C implementations.");
|
||||
else if(ac.objc_selector != "") {
|
||||
error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C method implementations or are imported.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
GB_ASSERT(e->kind == Entity_ProcGroup);
|
||||
if (tn->TypeName.objc_is_implementation) {
|
||||
error(e->token, "Objective-C procedure groups cannot use the @(objc_implement) attribute.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mutex_lock(&global_type_name_objc_metadata_mutex);
|
||||
defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Type *> param_types);
|
||||
|
||||
gb_internal void check_objc_call_expr(CheckerContext *c, Operand *operand, Ast *call, Entity *proc_entity, Type *proc_type) {
|
||||
auto &proc = proc_type->Proc;
|
||||
Slice<Entity *> params = proc.params ? proc.params->Tuple.variables : Slice<Entity *>{};
|
||||
|
||||
Type *self_type = nullptr;
|
||||
isize params_start = 1;
|
||||
|
||||
ast_node(ce, CallExpr, call);
|
||||
|
||||
Type *return_type = proc.result_count == 0 ? nullptr : proc.results->Tuple.variables[0]->type;
|
||||
bool is_return_instancetype = return_type != nullptr && return_type == t_objc_instancetype;
|
||||
|
||||
if (params.count == 0 || !is_type_objc_ptr_to_object(params[0]->type)) {
|
||||
if (!proc_entity->Procedure.is_objc_class_method) {
|
||||
// Not a class method, invalid call
|
||||
error(call, "Invalid Objective-C call: The Objective-C method is not a class method but this first parameter is not an Objective-C object pointer.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_return_instancetype) {
|
||||
if (ce->proc->kind == Ast_SelectorExpr) {
|
||||
ast_node(se, SelectorExpr, ce->proc);
|
||||
|
||||
// NOTE(harold): These should have already been checked, right?
|
||||
GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named);
|
||||
|
||||
return_type = alloc_type_pointer(se->expr->tav.type);
|
||||
} else {
|
||||
return_type = proc_entity->Procedure.objc_class->type;
|
||||
}
|
||||
}
|
||||
|
||||
self_type = t_objc_Class;
|
||||
params_start = 0;
|
||||
} else if (ce->args.count > 0) {
|
||||
GB_ASSERT(is_type_objc_ptr_to_object(params[0]->type));
|
||||
|
||||
if (ce->args[0]->tav.objc_super_target) {
|
||||
self_type = t_objc_super_ptr;
|
||||
} else {
|
||||
self_type = ce->args[0]->tav.type;
|
||||
}
|
||||
|
||||
if (is_return_instancetype) {
|
||||
// NOTE(harold): These should have already been checked, right?
|
||||
GB_ASSERT(ce->args[0]->tav.type && ce->args[0]->tav.type->kind == Type_Pointer && ce->args[0]->tav.type->Pointer.elem->kind == Type_Named);
|
||||
|
||||
return_type = ce->args[0]->tav.type;
|
||||
}
|
||||
}
|
||||
|
||||
auto param_types = slice_make<Type *>(permanent_allocator(), proc.param_count + 2 - params_start);
|
||||
param_types[0] = self_type;
|
||||
param_types[1] = t_objc_SEL;
|
||||
|
||||
for (isize i = params_start; i < params.count; i++) {
|
||||
param_types[i+2-params_start] = params[i]->type;
|
||||
}
|
||||
|
||||
if (is_return_instancetype) {
|
||||
operand->type = return_type;
|
||||
}
|
||||
|
||||
add_objc_proc_type(c, call, return_type, param_types);
|
||||
}
|
||||
|
||||
gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice<Ast *> const &args, ProcInlining inlining, Type *type_hint) {
|
||||
if (proc != nullptr &&
|
||||
@@ -8414,6 +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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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},
|
||||
|
||||
|
||||
@@ -251,6 +251,8 @@ struct Entity {
|
||||
String link_name;
|
||||
String link_prefix;
|
||||
String link_suffix;
|
||||
String objc_selector_name;
|
||||
Entity *objc_class;
|
||||
DeferredProcedure deferred_procedure;
|
||||
|
||||
struct GenProcsData *gen_procs;
|
||||
@@ -266,6 +268,8 @@ struct Entity {
|
||||
bool is_anonymous : 1;
|
||||
bool no_sanitize_address : 1;
|
||||
bool no_sanitize_memory : 1;
|
||||
bool is_objc_impl_or_import : 1;
|
||||
bool is_objc_class_method : 1;
|
||||
} Procedure;
|
||||
struct {
|
||||
Array<Entity *> entities;
|
||||
|
||||
@@ -1417,8 +1417,21 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) {
|
||||
return str_lit("?");
|
||||
case Type_Proc:
|
||||
return str_lit("?");
|
||||
case Type_BitSet:
|
||||
return lb_get_objc_type_encoding(t->BitSet.underlying, pointer_depth);
|
||||
case Type_BitSet: {
|
||||
Type *bitset_integer_type = t->BitSet.underlying;
|
||||
if (!bitset_integer_type) {
|
||||
switch (t->cached_size) {
|
||||
case 1: bitset_integer_type = t_u8; break;
|
||||
case 2: bitset_integer_type = t_u16; break;
|
||||
case 4: bitset_integer_type = t_u32; break;
|
||||
case 8: bitset_integer_type = t_u64; break;
|
||||
case 16: bitset_integer_type = t_u128; break;
|
||||
}
|
||||
}
|
||||
GB_ASSERT_MSG(bitset_integer_type, "Could not determine bit_set integer size for objc_type_encoding");
|
||||
|
||||
return lb_get_objc_type_encoding(bitset_integer_type, pointer_depth);
|
||||
}
|
||||
|
||||
case Type_SimdVector: {
|
||||
String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth);
|
||||
@@ -1452,7 +1465,10 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) {
|
||||
|
||||
struct lbObjCGlobalClass {
|
||||
lbObjCGlobal g;
|
||||
lbValue class_value; // Local registered class value
|
||||
union {
|
||||
lbValue class_value; // Local registered class value
|
||||
lbAddr class_global; // Global class pointer. Placeholder for class implementations which are registered in order of definition.
|
||||
};
|
||||
};
|
||||
|
||||
gb_internal void lb_register_objc_thing(
|
||||
@@ -1482,44 +1498,43 @@ gb_internal void lb_register_objc_thing(
|
||||
LLVMSetInitializer(v.value, LLVMConstNull(t));
|
||||
}
|
||||
|
||||
lbValue class_ptr = {};
|
||||
lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name));
|
||||
|
||||
// If this class requires an implementation, save it for registration below.
|
||||
if (g.class_impl_type != nullptr) {
|
||||
|
||||
// Make sure the superclass has been initialized before us
|
||||
lbValue superclass_value = lb_const_nil(m, t_objc_Class);
|
||||
|
||||
auto &tn = g.class_impl_type->Named.type_name->TypeName;
|
||||
Type *superclass = tn.objc_superclass;
|
||||
if (superclass != nullptr) {
|
||||
auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name);
|
||||
lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call);
|
||||
GB_ASSERT(superclass_global.class_value.value);
|
||||
|
||||
superclass_value = superclass_global.class_value;
|
||||
GB_ASSERT(superclass_global.class_global.addr.value);
|
||||
}
|
||||
|
||||
args.count = 3;
|
||||
args[0] = superclass_value;
|
||||
args[1] = class_name;
|
||||
args[2] = lb_const_int(m, t_uint, 0);
|
||||
class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args);
|
||||
lbObjCGlobalClass impl_global = {};
|
||||
impl_global.g = g;
|
||||
impl_global.class_global = addr;
|
||||
|
||||
array_add(&class_impls, lbObjCGlobalClass{g, class_ptr});
|
||||
array_add(&class_impls, impl_global);
|
||||
|
||||
lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
|
||||
if (class_global != nullptr) {
|
||||
class_global->class_global = addr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
lbValue class_ptr = {};
|
||||
lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name));
|
||||
|
||||
args.count = 1;
|
||||
args[0] = class_name;
|
||||
class_ptr = lb_emit_runtime_call(p, call, args);
|
||||
}
|
||||
|
||||
lb_addr_store(p, addr, class_ptr);
|
||||
lb_addr_store(p, addr, class_ptr);
|
||||
|
||||
lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
|
||||
if (class_global != nullptr) {
|
||||
class_global->class_value = class_ptr;
|
||||
lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
|
||||
if (class_global != nullptr) {
|
||||
class_global->class_value = class_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1582,7 +1597,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
string_map_init(&global_class_map, (usize)gen->objc_classes.count);
|
||||
defer (string_map_destroy(&global_class_map));
|
||||
|
||||
for (lbObjCGlobal g :referenced_classes) {
|
||||
for (lbObjCGlobal g : referenced_classes) {
|
||||
string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g});
|
||||
}
|
||||
|
||||
@@ -1629,9 +1644,36 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
|
||||
for (const auto &cd : class_impls) {
|
||||
auto &g = cd.g;
|
||||
Type *class_type = g.class_impl_type;
|
||||
|
||||
Type *class_type = g.class_impl_type;
|
||||
Type *class_ptr_type = alloc_type_pointer(class_type);
|
||||
lbValue class_value = cd.class_value;
|
||||
|
||||
// Begin class registration: create class pair and update global reference
|
||||
lbValue class_value = {};
|
||||
|
||||
{
|
||||
lbValue superclass_value = lb_const_nil(m, t_objc_Class);
|
||||
|
||||
auto& tn = class_type->Named.type_name->TypeName;
|
||||
Type *superclass = tn.objc_superclass;
|
||||
|
||||
if (superclass != nullptr) {
|
||||
auto& superclass_global = string_map_must_get(&global_class_map, superclass->Named.type_name->TypeName.objc_class_name);
|
||||
superclass_value = superclass_global.class_value;
|
||||
}
|
||||
|
||||
args.count = 3;
|
||||
args[0] = superclass_value;
|
||||
args[1] = lb_const_value(m, t_cstring, exact_value_string(g.name));
|
||||
args[2] = lb_const_int(m, t_uint, 0);
|
||||
class_value = lb_emit_runtime_call(p, "objc_allocateClassPair", args);
|
||||
|
||||
lbObjCGlobalClass &mapped_global = string_map_must_get(&global_class_map, tn.objc_class_name);
|
||||
lb_addr_store(p, mapped_global.class_global, class_value);
|
||||
|
||||
mapped_global.class_value = class_value;
|
||||
}
|
||||
|
||||
|
||||
Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar;
|
||||
|
||||
@@ -1651,7 +1693,6 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type);
|
||||
}
|
||||
|
||||
|
||||
Array<ObjcMethodData> *methods = map_get(&m->info->objc_method_implementations, class_type);
|
||||
if (!methods) {
|
||||
continue;
|
||||
@@ -1710,17 +1751,21 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl);
|
||||
|
||||
lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type);
|
||||
lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind");
|
||||
|
||||
lb_add_function_type_attributes(wrapper_proc->value, lb_get_function_type(m, wrapper_proc_type), ProcCC_CDecl);
|
||||
|
||||
// Emit the wrapper
|
||||
LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage);
|
||||
// LLVMSetLinkage(wrapper_proc->value, LLVMInternalLinkage);
|
||||
LLVMSetDLLStorageClass(wrapper_proc->value, LLVMDLLExportStorageClass);
|
||||
lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind");
|
||||
|
||||
lb_begin_procedure_body(wrapper_proc);
|
||||
{
|
||||
LLVMValueRef context_addr = nullptr;
|
||||
if (method_type->Proc.calling_convention == ProcCC_Odin) {
|
||||
GB_ASSERT(context_provider);
|
||||
|
||||
// Emit the get odin context call
|
||||
|
||||
get_context_args[0] = lbValue {
|
||||
wrapper_proc->raw_input_parameters[0],
|
||||
contex_provider_self_ptr_type,
|
||||
@@ -1736,44 +1781,58 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
get_context_args[0] = lb_handle_objc_ivar_for_objc_object_pointer(wrapper_proc, real_self);
|
||||
}
|
||||
|
||||
lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args);
|
||||
lbAddr context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context));
|
||||
lb_push_context_onto_stack(wrapper_proc, context_addr);
|
||||
lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args);
|
||||
context_addr = lb_address_from_load(wrapper_proc, context).value;//lb_address_from_load_or_generate_local(wrapper_proc, context));
|
||||
// context_addr = LLVMGetOperand(context.value, 0);
|
||||
}
|
||||
|
||||
isize method_forward_arg_count = method_param_count + method_param_offset;
|
||||
isize method_forward_return_arg_offset = 0;
|
||||
auto raw_method_args = array_make<LLVMValueRef>(temporary_allocator(), 0, method_forward_arg_count+1);
|
||||
|
||||
auto method_call_args = array_make<lbValue>(temporary_allocator(), method_param_count + method_param_offset);
|
||||
lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity);
|
||||
lbFunctionType* ft = lb_get_function_type(m, method_type);
|
||||
bool has_return = false;
|
||||
lbArgKind return_kind = {};
|
||||
|
||||
if (wrapper_results_tuple != nullptr) {
|
||||
has_return = true;
|
||||
return_kind = ft->ret.kind;
|
||||
|
||||
if (return_kind == lbArg_Indirect) {
|
||||
method_forward_return_arg_offset = 1;
|
||||
array_add(&raw_method_args, wrapper_proc->return_ptr.addr.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!md.ac.objc_is_class_method) {
|
||||
method_call_args[0] = lbValue {
|
||||
wrapper_proc->raw_input_parameters[0],
|
||||
class_ptr_type,
|
||||
};
|
||||
array_add(&raw_method_args, wrapper_proc->raw_input_parameters[method_forward_return_arg_offset]);
|
||||
}
|
||||
|
||||
for (isize i = 0; i < method_param_count; i++) {
|
||||
method_call_args[i+method_param_offset] = lbValue {
|
||||
wrapper_proc->raw_input_parameters[i+2],
|
||||
method_type->Proc.params->Tuple.variables[i+method_param_offset]->type,
|
||||
};
|
||||
array_add(&raw_method_args, wrapper_proc->raw_input_parameters[i+2+method_forward_return_arg_offset]);
|
||||
}
|
||||
|
||||
if (method_type->Proc.calling_convention == ProcCC_Odin) {
|
||||
array_add(&raw_method_args, context_addr);
|
||||
}
|
||||
lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity);
|
||||
|
||||
// Call real procedure for method from here, passing the parameters expected, if any.
|
||||
lbValue return_value = lb_emit_call(wrapper_proc, method_proc_value, method_call_args);
|
||||
LLVMTypeRef fnp = lb_type_internal_for_procedures_raw(m, method_type);
|
||||
LLVMValueRef ret_val_raw = LLVMBuildCall2(wrapper_proc->builder, fnp, method_proc_value.value, raw_method_args.data, (unsigned)raw_method_args.count, "");
|
||||
|
||||
if (wrapper_results_tuple != nullptr) {
|
||||
auto &result_var = method_type->Proc.results->Tuple.variables[0];
|
||||
return_value = lb_emit_conv(wrapper_proc, return_value, result_var->type);
|
||||
lb_build_return_stmt_internal(wrapper_proc, return_value, result_var->token.pos);
|
||||
if (has_return && return_kind != lbArg_Indirect) {
|
||||
LLVMBuildRet(wrapper_proc->builder, ret_val_raw);
|
||||
}
|
||||
else {
|
||||
LLVMBuildRetVoid(wrapper_proc->builder);
|
||||
}
|
||||
}
|
||||
lb_end_procedure_body(wrapper_proc);
|
||||
|
||||
|
||||
// Add the method to the class
|
||||
String method_encoding = str_lit("v");
|
||||
// TODO (harold): Checker must ensure that objc_methods have a single return value or none!
|
||||
|
||||
GB_ASSERT(method_type->Proc.result_count <= 1);
|
||||
if (method_type->Proc.result_count != 0) {
|
||||
method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type);
|
||||
@@ -1785,8 +1844,8 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:"));
|
||||
}
|
||||
|
||||
for (isize i = method_param_offset; i < method_param_count; i++) {
|
||||
Type *param_type = method_type->Proc.params->Tuple.variables[i]->type;
|
||||
for (isize i = 0; i < method_param_count; i++) {
|
||||
Type *param_type = method_type->Proc.params->Tuple.variables[i + method_param_offset]->type;
|
||||
String param_encoding = lb_get_objc_type_encoding(param_type);
|
||||
|
||||
method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding);
|
||||
@@ -1805,7 +1864,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
args[2] = lbValue { wrapper_proc->value, wrapper_proc->type };
|
||||
args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding));
|
||||
|
||||
// TODO(harold): Emit check BOOL result and panic if false.
|
||||
// TODO(harold): Emit check BOOL result and panic if false?
|
||||
lb_emit_runtime_call(p, "class_addMethod", args);
|
||||
|
||||
} // End methods
|
||||
@@ -1853,7 +1912,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
// Defined in an external package, define it now in the main package
|
||||
LLVMTypeRef t = lb_type(m, t_int);
|
||||
|
||||
lbValue global{};
|
||||
lbValue global = {};
|
||||
global.value = LLVMAddGlobal(m->mod, t, g.global_name);
|
||||
global.type = t_int_ptr;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<lbValue>(temporary_allocator(), user_proc.param_count, user_proc.param_count);
|
||||
// Reserve 2 extra arguments for: Indirect return values and context.
|
||||
auto call_args = array_make<LLVMValueRef>(temporary_allocator(), 0, user_proc.param_count + 2);
|
||||
|
||||
for (isize i = 1; i < invoker_proc->raw_input_parameters.count; i++) {
|
||||
lbValue arg = {};
|
||||
arg.type = invoker_args[i];
|
||||
arg.value = invoker_proc->raw_input_parameters[i],
|
||||
call_args[i-1] = arg;
|
||||
isize block_literal_arg_index = 0;
|
||||
|
||||
lbFunctionType* user_proc_ft = lb_get_function_type(m, user_proc_value.type);
|
||||
|
||||
lbArgKind return_kind = {};
|
||||
|
||||
GB_ASSERT(user_proc.result_count <= 1);
|
||||
if (user_proc.result_count > 0) {
|
||||
return_kind = user_proc_ft->ret.kind;
|
||||
|
||||
if (return_kind == lbArg_Indirect) {
|
||||
// Forward indirect return value
|
||||
array_add(&call_args, invoker_proc->raw_input_parameters[0]);
|
||||
block_literal_arg_index = 1;
|
||||
}
|
||||
}
|
||||
|
||||
LLVMValueRef block_literal = invoker_proc->raw_input_parameters[0];
|
||||
// Forward raw arguments
|
||||
for (isize i = block_literal_arg_index+1; i < invoker_proc->raw_input_parameters.count; i++) {
|
||||
array_add(&call_args, invoker_proc->raw_input_parameters[i]);
|
||||
}
|
||||
|
||||
LLVMValueRef block_literal = invoker_proc->raw_input_parameters[block_literal_arg_index];
|
||||
|
||||
// Copy capture parameters from the block literal
|
||||
isize capture_arg_in_user_proc_start_index = user_proc_ft->args.count - capture_arg_count;
|
||||
if (user_proc.calling_convention == ProcCC_Odin) {
|
||||
capture_arg_in_user_proc_start_index -= 1;
|
||||
}
|
||||
|
||||
for (isize i = 0; i < capture_arg_count; i++) {
|
||||
LLVMValueRef cap_value = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, unsigned(capture_fields_offset + i), "");
|
||||
|
||||
// Don't emit load if indirect. Pass the pointer as-is
|
||||
isize cap_arg_index_in_user_proc = capture_arg_in_user_proc_start_index + i;
|
||||
|
||||
if (user_proc_ft->args[cap_arg_index_in_user_proc].kind != lbArg_Indirect) {
|
||||
cap_value = OdinLLVMBuildLoad(invoker_proc, lb_type(invoker_proc->module, captured_values[i].type), cap_value);
|
||||
}
|
||||
|
||||
array_add(&call_args, cap_value);
|
||||
}
|
||||
|
||||
// Push context, if needed
|
||||
if (user_proc.calling_convention == ProcCC_Odin) {
|
||||
LLVMValueRef p_context = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, 5, "context");
|
||||
lbValue ctx_val = {};
|
||||
ctx_val.type = t_context_ptr;
|
||||
ctx_val.value = p_context;
|
||||
|
||||
lb_push_context_onto_stack(invoker_proc, lb_addr(ctx_val));
|
||||
array_add(&call_args, p_context);
|
||||
}
|
||||
|
||||
// Copy capture parameters from the block literal
|
||||
for (isize i = 0; i < capture_arg_count; i++) {
|
||||
LLVMValueRef cap_value = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, unsigned(capture_fields_offset + i), "");
|
||||
LLVMTypeRef fnp = lb_type_internal_for_procedures_raw(m, user_proc_value.type);
|
||||
LLVMValueRef ret_val = LLVMBuildCall2(invoker_proc->builder, fnp, user_proc_value.value, call_args.data, (unsigned)call_args.count, "");
|
||||
|
||||
lbValue cap_arg = {};
|
||||
cap_arg.value = cap_value;
|
||||
cap_arg.type = alloc_type_pointer(captured_values[i].type);
|
||||
|
||||
lbValue arg = lb_emit_load(invoker_proc, cap_arg);
|
||||
call_args[block_forward_args+i] = arg;
|
||||
if (user_proc.result_count > 0 && return_kind != lbArg_Indirect) {
|
||||
LLVMBuildRet(invoker_proc->builder, ret_val);
|
||||
}
|
||||
|
||||
lbValue result = lb_emit_call(invoker_proc, user_proc_value, call_args, proc_lit->ProcLit.inlining);
|
||||
|
||||
GB_ASSERT(user_proc.result_count <= 1);
|
||||
if (user_proc.result_count > 0) {
|
||||
GB_ASSERT(result.value != nullptr);
|
||||
LLVMBuildRet(p->builder, result.value);
|
||||
else {
|
||||
LLVMBuildRetVoid(invoker_proc->builder);
|
||||
}
|
||||
}
|
||||
lb_end_procedure_body(invoker_proc);
|
||||
@@ -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<lbValue> const arg_values) {
|
||||
ast_node(ce, CallExpr, expr);
|
||||
|
||||
lbModule *m = p->module;
|
||||
CheckerInfo *info = m->info;
|
||||
ObjcMsgData data = map_must_get(&info->objc_msgSend_types, expr);
|
||||
|
||||
Type *proc_type = data.proc_type;
|
||||
GB_ASSERT(proc_type != nullptr);
|
||||
|
||||
Entity *objc_method_ent = entity_of_node(ce->proc);
|
||||
GB_ASSERT(objc_method_ent != nullptr);
|
||||
GB_ASSERT(objc_method_ent->kind == Entity_Procedure);
|
||||
GB_ASSERT(objc_method_ent->Procedure.objc_selector_name.len > 0);
|
||||
|
||||
auto &proc = proc_type->Proc;
|
||||
GB_ASSERT(proc.param_count >= 2);
|
||||
|
||||
Type *objc_super_orig_type = nullptr;
|
||||
if (ce->args.count > 0) {
|
||||
objc_super_orig_type = unparen_expr(ce->args[0])->tav.objc_super_target;
|
||||
}
|
||||
|
||||
isize arg_offset = 1;
|
||||
lbValue id = {};
|
||||
if (!objc_method_ent->Procedure.is_objc_class_method) {
|
||||
GB_ASSERT(ce->args.count > 0);
|
||||
id = arg_values[0];
|
||||
|
||||
if (objc_super_orig_type) {
|
||||
GB_ASSERT(objc_super_orig_type->kind == Type_Named);
|
||||
|
||||
auto& tn = objc_super_orig_type->Named.type_name->TypeName;
|
||||
lbAddr p_supercls = lb_handle_objc_find_or_register_class(p, tn.objc_class_name, tn.objc_is_implementation ? objc_super_orig_type : nullptr);
|
||||
|
||||
lbValue supercls = lb_addr_load(p, p_supercls);
|
||||
lbAddr p_objc_super = lb_add_local_generated(p, t_objc_super, false);
|
||||
|
||||
lbValue f_id = lb_emit_struct_ep(p, p_objc_super.addr, 0);
|
||||
lbValue f_superclass = lb_emit_struct_ep(p, p_objc_super.addr, 1);
|
||||
|
||||
id = lb_emit_conv(p, id, t_objc_id);
|
||||
lb_emit_store(p, f_id, id);
|
||||
lb_emit_store(p, f_superclass, supercls);
|
||||
|
||||
id = p_objc_super.addr;
|
||||
}
|
||||
} else {
|
||||
Entity *objc_class = objc_method_ent->Procedure.objc_class;
|
||||
if (ce->proc->kind == Ast_SelectorExpr) {
|
||||
// NOTE (harold): If called via a selector expression (ex: Foo.alloc()), then we should use
|
||||
// the lhs-side to determine the class. This allows for class methods to be called
|
||||
// with the correct class as the target, even when the method is defined in a superclass.
|
||||
ast_node(se, SelectorExpr, ce->proc);
|
||||
GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named);
|
||||
|
||||
objc_class = entity_from_expr(se->expr);
|
||||
|
||||
GB_ASSERT(objc_class);
|
||||
GB_ASSERT(objc_class->kind == Entity_TypeName);
|
||||
GB_ASSERT(objc_class->TypeName.objc_class_name != "");
|
||||
}
|
||||
|
||||
Type *class_impl_type = objc_class->TypeName.objc_is_implementation ? objc_class->type : nullptr;
|
||||
|
||||
id = lb_addr_load(p, lb_handle_objc_find_or_register_class(p, objc_class->TypeName.objc_class_name, class_impl_type));
|
||||
arg_offset = 0;
|
||||
}
|
||||
|
||||
lbValue sel = lb_addr_load(p, lb_handle_objc_find_or_register_selector(p, objc_method_ent->Procedure.objc_selector_name));
|
||||
|
||||
auto args = array_make<lbValue>(permanent_allocator(), 0, arg_values.count + 2 - arg_offset);
|
||||
|
||||
array_add(&args, id);
|
||||
array_add(&args, sel);
|
||||
|
||||
for (isize i = arg_offset; i < ce->args.count; i++) {
|
||||
array_add(&args, arg_values[i]);
|
||||
}
|
||||
|
||||
lbValue the_proc = {};
|
||||
|
||||
if (!objc_super_orig_type) {
|
||||
switch (data.kind) {
|
||||
default:
|
||||
GB_PANIC("unhandled ObjcMsgKind %u", data.kind);
|
||||
break;
|
||||
case ObjcMsg_normal: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend")); break;
|
||||
case ObjcMsg_fpret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fpret")); break;
|
||||
case ObjcMsg_fp2ret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fp2ret")); break;
|
||||
case ObjcMsg_stret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_stret")); break;
|
||||
}
|
||||
} else {
|
||||
switch (data.kind) {
|
||||
default:
|
||||
GB_PANIC("unhandled ObjcMsgKind %u", data.kind);
|
||||
break;
|
||||
case ObjcMsg_normal:
|
||||
case ObjcMsg_fpret:
|
||||
case ObjcMsg_fp2ret:
|
||||
the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2"));
|
||||
break;
|
||||
case ObjcMsg_stret:
|
||||
the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2_stret"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
the_proc = lb_emit_conv(p, the_proc, data.proc_type);
|
||||
|
||||
return lb_emit_call(p, the_proc, args);
|
||||
}
|
||||
|
||||
|
||||
gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) {
|
||||
GB_ASSERT(value.kind == ExactValue_Integer);
|
||||
i64 v = exact_value_to_i64(value);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user