mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-28 09:13:55 +00:00
Add support for Objective-C method implementation with Odin calling convention.
Use @objc_context_provider to provide a context for a type.
This commit is contained in:
@@ -528,13 +528,21 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
|
||||
e->TypeName.objc_class_name = ac.objc_class;
|
||||
e->TypeName.objc_superclass = ac.objc_superclass;
|
||||
e->TypeName.objc_ivar = ac.objc_ivar;
|
||||
e->TypeName.objc_context_provider = ac.objc_context_provider;
|
||||
|
||||
if (ac.objc_is_implementation) {
|
||||
e->TypeName.objc_is_implementation = true;
|
||||
mpsc_enqueue(&ctx->info->objc_class_implementations, e); // TODO(harold): Don't need this for anything. Remove.
|
||||
mpsc_enqueue(&ctx->info->objc_class_implementations, e); // TODO(harold): Don't need this for anything? See if needed when using explicit @export
|
||||
|
||||
GB_ASSERT(e->TypeName.objc_ivar == nullptr || e->TypeName.objc_ivar->kind == Type_Named);
|
||||
|
||||
// Enqueue the proc to be checked when resolved
|
||||
if (e->TypeName.objc_context_provider != nullptr) {
|
||||
mpsc_enqueue(&ctx->checker->procs_with_objc_context_provider_to_check, e);
|
||||
}
|
||||
|
||||
// @TODO(harold): I think there's a Check elsewhere in the checker for checking cycles.
|
||||
// See about moving this to the right location.
|
||||
// Ensure superclass hierarchy are all Objective-C classes and does not cycle
|
||||
Type *super = ac.objc_superclass;
|
||||
if (super != nullptr) {
|
||||
@@ -571,8 +579,14 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
|
||||
check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info);
|
||||
}
|
||||
}
|
||||
} else if (e->TypeName.objc_superclass != nullptr) {
|
||||
error(e->token, "@(objc_superclass) can only be applied when the obj_implement attribute is also applied");
|
||||
} else {
|
||||
if (e->TypeName.objc_superclass != nullptr) {
|
||||
error(e->token, "@(objc_superclass) can only be applied when the @(obj_implement) attribute is also applied");
|
||||
} else if (e->TypeName.objc_ivar != nullptr) {
|
||||
error(e->token, "@(objc_ivar) can only be applied when the @(obj_implement) attribute is also applied");
|
||||
} else if (e->TypeName.objc_context_provider != nullptr) {
|
||||
error(e->token, "@(objc_context_provider) can only be applied when the @(obj_implement) attribute is also applied");
|
||||
}
|
||||
}
|
||||
|
||||
if (type_size_of(e->type) > 0) {
|
||||
@@ -994,25 +1008,33 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon
|
||||
if (ac.objc_is_implementation) {
|
||||
GB_ASSERT(e->kind == Entity_Procedure);
|
||||
|
||||
CheckerInfo *info = ctx->info;
|
||||
mutex_lock(&info->objc_method_mutex);
|
||||
defer (mutex_unlock(&info->objc_method_mutex));
|
||||
Type *proc_type = e->type;
|
||||
|
||||
auto method = ObjcMethodData{ ac, e };
|
||||
if (!tn->TypeName.objc_is_implementation) {
|
||||
error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied");
|
||||
} else if (proc_type->Proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) {
|
||||
error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set");
|
||||
} else if (ac.objc_is_class_method && proc_type->Proc.calling_convention != ProcCC_CDecl) {
|
||||
error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention");
|
||||
} else {
|
||||
|
||||
if (ac.objc_selector == "") {
|
||||
method.ac.objc_selector = ac.objc_name;
|
||||
}
|
||||
auto method = ObjcMethodData{ ac, e };
|
||||
method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name;
|
||||
|
||||
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;
|
||||
CheckerInfo *info = ctx->info;
|
||||
mutex_lock(&info->objc_method_mutex);
|
||||
defer (mutex_unlock(&info->objc_method_mutex));
|
||||
|
||||
map_set(&info->objc_method_implementations, t, list);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&global_type_name_objc_metadata_mutex);
|
||||
|
||||
@@ -1502,6 +1502,8 @@ gb_internal void init_checker(Checker *c) {
|
||||
|
||||
TIME_SECTION("init proc queues");
|
||||
mpsc_init(&c->procs_with_deferred_to_check, a); //, 1<<10);
|
||||
mpsc_init(&c->procs_with_objc_context_provider_to_check, a);
|
||||
|
||||
|
||||
// NOTE(bill): 1 Mi elements should be enough on average
|
||||
array_init(&c->procs_to_check, heap_allocator(), 0, 1<<20);
|
||||
@@ -3974,6 +3976,23 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) {
|
||||
error(value, "'%.*s' expected a named type", LIT(name));
|
||||
}
|
||||
return true;
|
||||
} else if (name == "objc_context_provider") {
|
||||
Operand o = {};
|
||||
check_expr(c, &o, value);
|
||||
Entity *e = entity_of_node(o.expr);
|
||||
|
||||
if (e != nullptr) {
|
||||
if (ac->objc_context_provider != nullptr) {
|
||||
error(elem, "Previous usage of a 'objc_context_provider' attribute");
|
||||
}
|
||||
if (e->kind != Entity_Procedure) {
|
||||
error(elem, "'objc_context_provider' must refer to a procedure");
|
||||
} else {
|
||||
ac->objc_context_provider = e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -6462,6 +6481,47 @@ gb_internal void check_deferred_procedures(Checker *c) {
|
||||
|
||||
}
|
||||
|
||||
gb_internal void check_objc_context_provider_procedures(Checker *c) {
|
||||
for (Entity *e = nullptr; mpsc_dequeue(&c->procs_with_objc_context_provider_to_check, &e); /**/) {
|
||||
GB_ASSERT(e->kind == Entity_TypeName);
|
||||
|
||||
Entity *proc_entity = e->TypeName.objc_context_provider;
|
||||
GB_ASSERT(proc_entity->kind == Entity_Procedure);
|
||||
|
||||
Type *proc_type = proc_entity->type;
|
||||
|
||||
// TODO(harold): Give better errors here (specify exactly what's wrong)
|
||||
const char* signature_error = "The procedure for @(objc_context_provider) has an incorrect signature.";
|
||||
|
||||
if (proc_type->Proc.param_count != 1 || proc_type->Proc.result_count != 1) {
|
||||
error(proc_entity->token, signature_error);
|
||||
} else {
|
||||
Type *self_param = base_type(proc_type->Proc.params->Tuple.variables[0]->type);
|
||||
Type *return_type = base_named_type(proc_type->Proc.results->Tuple.variables[0]->type);
|
||||
|
||||
if (self_param->kind != Type_Pointer) {
|
||||
error(proc_entity->token, signature_error);
|
||||
continue;
|
||||
}
|
||||
|
||||
self_param = base_named_type(self_param->Pointer.elem);
|
||||
|
||||
if (return_type != t_context) {
|
||||
error(e->token, signature_error);
|
||||
} else if (!internal_check_is_assignable_to(self_param, e->type) &&
|
||||
(e->TypeName.objc_ivar && !internal_check_is_assignable_to(self_param, e->TypeName.objc_ivar))
|
||||
) {
|
||||
error(e->token, signature_error);
|
||||
} else if (proc_type->Proc.calling_convention != ProcCC_CDecl &&
|
||||
proc_type->Proc.calling_convention != ProcCC_Contextless) {
|
||||
error(e->token, signature_error);
|
||||
} else if (proc_type->Proc.is_polymorphic) {
|
||||
error(e->token, signature_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal void check_unique_package_names(Checker *c) {
|
||||
ERROR_BLOCK();
|
||||
|
||||
@@ -6609,6 +6669,7 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
gb_internal void check_parsed_files(Checker *c) {
|
||||
TIME_SECTION("map full filepaths to scope");
|
||||
add_type_info_type(&c->builtin_ctx, t_invalid);
|
||||
@@ -6718,6 +6779,9 @@ gb_internal void check_parsed_files(Checker *c) {
|
||||
TIME_SECTION("check deferred procedures");
|
||||
check_deferred_procedures(c);
|
||||
|
||||
TIME_SECTION("check objc context provider procedures");
|
||||
check_objc_context_provider_procedures(c);
|
||||
|
||||
TIME_SECTION("calculate global init order");
|
||||
calculate_global_init_order(c);
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ struct AttributeContext {
|
||||
Type * objc_type;
|
||||
Type * objc_superclass;
|
||||
Type * objc_ivar;
|
||||
Entity *objc_context_provider;
|
||||
bool objc_is_class_method : 1;
|
||||
bool objc_is_implementation : 1; // This struct or proc provides a class/method implementation, not a binding to an existing type.
|
||||
|
||||
@@ -570,6 +571,7 @@ struct Checker {
|
||||
CheckerContext builtin_ctx;
|
||||
|
||||
MPSCQueue<Entity *> procs_with_deferred_to_check;
|
||||
MPSCQueue<Entity *> procs_with_objc_context_provider_to_check;
|
||||
Array<ProcInfo *> procs_to_check;
|
||||
|
||||
BlockingMutex nested_proc_lits_mutex;
|
||||
|
||||
@@ -238,6 +238,7 @@ struct Entity {
|
||||
bool objc_is_implementation;
|
||||
Type* objc_superclass;
|
||||
Type* objc_ivar;
|
||||
Entity*objc_context_provider;
|
||||
String objc_class_name;
|
||||
TypeNameObjCMetadata *objc_metadata;
|
||||
} TypeName;
|
||||
|
||||
@@ -1586,7 +1586,9 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
|
||||
|
||||
// Emit method wrapper implementations and registration
|
||||
auto wrapper_args = array_make<Type *>(temporary_allocator(), 2, 8);
|
||||
auto wrapper_args = array_make<Type *>(temporary_allocator(), 2, 8);
|
||||
auto get_context_args = array_make<lbValue>(temporary_allocator(), 1);
|
||||
|
||||
|
||||
PtrMap<Type *, lbObjCGlobal> ivar_map{};
|
||||
map_init(&ivar_map, gen->objc_ivars.count);
|
||||
@@ -1599,6 +1601,13 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
auto& g = cd.g;
|
||||
Type *class_type = g.class_impl_type;
|
||||
|
||||
Entity *context_provider = class_type->Named.type_name->TypeName.objc_context_provider;
|
||||
lbValue context_provider_proc_value{};
|
||||
if (context_provider) {
|
||||
context_provider_proc_value = lb_find_procedure_value_from_entity(m, context_provider);
|
||||
}
|
||||
|
||||
|
||||
Array<ObjcMethodData>* methods = map_get(&m->info->objc_method_implementations, class_type);
|
||||
if (!methods) {
|
||||
continue;
|
||||
@@ -1645,6 +1654,31 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage);
|
||||
lb_begin_procedure_body(wrapper_proc);
|
||||
{
|
||||
if (method_type->Proc.calling_convention == ProcCC_Odin) {
|
||||
GB_ASSERT(context_provider);
|
||||
|
||||
// Emit the get odin context call
|
||||
|
||||
// TODO(harold): Get these values at the top, at the start of the method loop for a class
|
||||
Type *ctx_provider_proc = context_provider->type;
|
||||
|
||||
Type *self_param_ptr_type = base_type(ctx_provider_proc->Proc.params->Tuple.variables[0]->type);
|
||||
GB_ASSERT(self_param_ptr_type->kind == Type_Pointer);
|
||||
|
||||
// TODO(harold): Set the arg type to the ivar's type, if the context provider takes the ivar's type.
|
||||
// Type *self_param_type = base_named_type(type_deref(self_param_ptr_type));
|
||||
|
||||
get_context_args[0] = lbValue {
|
||||
wrapper_proc->raw_input_parameters[0],
|
||||
self_param_ptr_type,
|
||||
};
|
||||
|
||||
lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args);
|
||||
lbAddr context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context));
|
||||
lb_push_context_onto_stack(wrapper_proc, context_addr);
|
||||
}
|
||||
|
||||
|
||||
auto method_call_args = array_make<lbValue>(temporary_allocator(), method_param_count + (isize)method_param_offset);
|
||||
|
||||
if (!md.ac.objc_is_class_method) {
|
||||
@@ -1711,11 +1745,12 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
if (ivar_type != nullptr) {
|
||||
// Register a single ivar for this class
|
||||
Type *ivar_base = ivar_type->Named.base;
|
||||
// TODO(harold): No idea if I can use this, but I assume so?
|
||||
const i64 size = ivar_base->cached_size;
|
||||
const i64 alignment = ivar_base->cached_align;
|
||||
|
||||
const i64 size = type_size_of(ivar_base);
|
||||
const i64 alignment = type_align_of(ivar_base);
|
||||
// TODO(harold): Checker: Alignment must be compatible with ivar rules. Or we should increase the alignment if needed.
|
||||
|
||||
// TODO(harold): Should we pass the actual type encoding? Might not be ideal for obfuscation.
|
||||
String ivar_name = str_lit("__$ivar");
|
||||
String ivar_types = str_lit("{= }"); //lb_get_objc_type_encoding(ivar_type, temporary_allocator());// str_lit("{= }");
|
||||
args.count = 5;
|
||||
|
||||
@@ -874,6 +874,29 @@ gb_internal Type *base_type(Type *t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
gb_internal Type *base_named_type(Type *t) {
|
||||
if (t->kind != Type_Named) {
|
||||
return t_invalid;
|
||||
}
|
||||
|
||||
Type *prev_named = t;
|
||||
t = t->Named.base;
|
||||
for (;;) {
|
||||
if (t == nullptr) {
|
||||
break;
|
||||
}
|
||||
if (t->kind != Type_Named) {
|
||||
break;
|
||||
}
|
||||
if (t == t->Named.base) {
|
||||
return t_invalid;
|
||||
}
|
||||
prev_named = t;
|
||||
t = t->Named.base;
|
||||
}
|
||||
return prev_named;
|
||||
}
|
||||
|
||||
gb_internal Type *base_enum_type(Type *t) {
|
||||
Type *bt = base_type(t);
|
||||
if (bt != nullptr &&
|
||||
|
||||
Reference in New Issue
Block a user