mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-18 12:30:28 +00:00
Add initial support for Objective-C class implementation
This commit is contained in:
@@ -353,15 +353,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
|
||||
objc_object :: struct{}
|
||||
objc_selector :: struct{}
|
||||
objc_class :: struct{}
|
||||
objc_ivar :: struct{}
|
||||
|
||||
objc_id :: ^objc_object
|
||||
objc_SEL :: ^objc_selector
|
||||
objc_Class :: ^objc_class
|
||||
objc_Ivar :: ^objc_ivar
|
||||
|
||||
objc_find_selector :: proc($name: string) -> objc_SEL ---
|
||||
objc_register_selector :: proc($name: string) -> objc_SEL ---
|
||||
objc_find_class :: proc($name: string) -> objc_Class ---
|
||||
objc_register_class :: proc($name: string) -> objc_Class ---
|
||||
|
||||
ivar_get :: proc(self: ^$T, $U: typeid) -> ^U ---
|
||||
|
||||
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
|
||||
|
||||
|
||||
@@ -2,21 +2,34 @@
|
||||
package runtime
|
||||
|
||||
@(priority_index=-1e6)
|
||||
foreign import "system:Foundation.framework"
|
||||
foreign import ObjC "system:objc"
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
objc_id :: ^intrinsics.objc_object
|
||||
objc_id :: ^intrinsics.objc_object
|
||||
objc_Class :: ^intrinsics.objc_class
|
||||
objc_SEL :: ^intrinsics.objc_selector
|
||||
objc_SEL :: ^intrinsics.objc_selector
|
||||
objc_Ivar :: ^intrinsics.objc_ivar
|
||||
objc_BOOL :: bool
|
||||
|
||||
foreign Foundation {
|
||||
objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
|
||||
|
||||
objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id
|
||||
|
||||
foreign ObjC {
|
||||
sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
|
||||
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
|
||||
|
||||
objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
|
||||
objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
|
||||
objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
|
||||
objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
|
||||
|
||||
objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
|
||||
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
|
||||
objc_registerClassPair :: proc "c" (cls : objc_Class) ---
|
||||
class_addMethod :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL ---
|
||||
class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL ---
|
||||
class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar ---
|
||||
class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint ---
|
||||
ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr ---
|
||||
}
|
||||
|
||||
|
||||
@@ -387,6 +387,80 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan
|
||||
try_to_add_package_dependency(c, "runtime", "objc_allocateClassPair");
|
||||
return true;
|
||||
} break;
|
||||
|
||||
case BuiltinProc_objc_ivar_get:
|
||||
{
|
||||
Type *self_type = nullptr;
|
||||
Type *ivar_type = nullptr;
|
||||
|
||||
Operand self {};
|
||||
check_expr_or_type(c, &self, ce->args[0]);
|
||||
|
||||
if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) {
|
||||
gbString e = expr_to_string(self.expr);
|
||||
gbString t = type_to_string(self.type);
|
||||
error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t);
|
||||
gb_string_free(t);
|
||||
gb_string_free(e);
|
||||
return false;
|
||||
}
|
||||
else if (!is_type_pointer(self.type)) {
|
||||
gbString e = expr_to_string(self.expr);
|
||||
gbString t = type_to_string(self.type);
|
||||
error(self.expr, "'%.*s' expected a pointer of a value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t);
|
||||
gb_string_free(t);
|
||||
gb_string_free(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
self_type = type_deref(self.type);
|
||||
|
||||
if (!(self_type->kind == Type_Named &&
|
||||
self_type->Named.type_name != nullptr &&
|
||||
self_type->Named.type_name->TypeName.objc_class_name != "")) {
|
||||
gbString t = type_to_string(self_type);
|
||||
error(self.expr, "'%.*s' expected a named type with the attribute @(obj_class=<string>) , got type %s", LIT(builtin_name), t);
|
||||
gb_string_free(t);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self_type->Named.type_name->TypeName.objc_ivar == nullptr) {
|
||||
gbString t = type_to_string(self_type);
|
||||
error(self.expr, "'%.*s' requires that type %s have the attribute @(obj_ivar=<ivar_type_name>).", LIT(builtin_name), t);
|
||||
gb_string_free(t);
|
||||
return false;
|
||||
}
|
||||
|
||||
Operand ivar {};
|
||||
check_expr_or_type(c, &ivar, ce->args[1]);
|
||||
if (ivar.mode == Addressing_Type) {
|
||||
ivar_type = ivar.type;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self_type->Named.type_name->TypeName.objc_ivar != ivar_type) {
|
||||
gbString name_self = type_to_string(self_type);
|
||||
gbString name_expected = type_to_string(self_type->Named.type_name->TypeName.objc_ivar);
|
||||
gbString name_given = type_to_string(ivar_type);
|
||||
error(self.expr, "'%.*s' ivar type %s does not match @obj_ivar type %s on Objective-C class %s.",
|
||||
LIT(builtin_name), name_given, name_expected, name_self);
|
||||
gb_string_free(name_self);
|
||||
gb_string_free(name_expected);
|
||||
gb_string_free(name_given);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) {
|
||||
operand->type = type_hint;
|
||||
} else {
|
||||
operand->type = alloc_type_pointer(ivar_type);
|
||||
}
|
||||
|
||||
operand->mode = Addressing_Value;
|
||||
|
||||
return true;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2132,7 +2206,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
|
||||
case BuiltinProc_objc_find_selector:
|
||||
case BuiltinProc_objc_find_class:
|
||||
case BuiltinProc_objc_register_selector:
|
||||
case BuiltinProc_objc_register_class:
|
||||
case BuiltinProc_objc_register_class:
|
||||
case BuiltinProc_objc_ivar_get:
|
||||
return check_builtin_objc_procedure(c, operand, call, id, type_hint);
|
||||
|
||||
case BuiltinProc___entry_point:
|
||||
|
||||
@@ -526,6 +526,54 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
|
||||
check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac);
|
||||
if (e->kind == Entity_TypeName && ac.objc_class != "") {
|
||||
e->TypeName.objc_class_name = ac.objc_class;
|
||||
e->TypeName.objc_superclass = ac.objc_superclass;
|
||||
e->TypeName.objc_ivar = ac.objc_ivar;
|
||||
|
||||
if (ac.objc_is_implementation) {
|
||||
e->TypeName.objc_is_implementation = true;
|
||||
mpsc_enqueue(&ctx->info->objc_class_implementations, e); // TODO(harold): Don't need this for anything. Remove.
|
||||
|
||||
GB_ASSERT(e->TypeName.objc_ivar == nullptr || e->TypeName.objc_ivar->kind == Type_Named);
|
||||
|
||||
// Ensure superclass hierarchy are all Objective-C classes and does not cycle
|
||||
Type *super = ac.objc_superclass;
|
||||
if (super != nullptr) {
|
||||
TypeSet super_set{};
|
||||
type_set_init(&super_set, 8);
|
||||
defer (type_set_destroy(&super_set));
|
||||
|
||||
type_set_update(&super_set, e->type);
|
||||
|
||||
for (;;) {
|
||||
if (type_set_update(&super_set, super)) {
|
||||
error(e->token, "@(objc_superclass) Superclass hierarchy cycle encountered");
|
||||
break;
|
||||
}
|
||||
|
||||
if (super->kind != Type_Named) {
|
||||
error(e->token, "@(objc_superclass) References type must be a named struct.");
|
||||
break;
|
||||
}
|
||||
|
||||
Type* named_type = base_type(super->Named.type_name->type);
|
||||
if (!is_type_objc_object(named_type)) {
|
||||
error(e->token, "@(objc_superclass) Superclass must be an Objective-C class.");
|
||||
break;
|
||||
}
|
||||
|
||||
super = super->Named.type_name->TypeName.objc_superclass;
|
||||
if (super == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO(harold): Is this the right way to do this??? The referenced entity must be already resolved
|
||||
// so that we can access its objc_superclass attribute
|
||||
check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info);
|
||||
}
|
||||
}
|
||||
} else if (e->TypeName.objc_superclass != nullptr) {
|
||||
error(e->token, "@(objc_superclass) can only be applied when the obj_implement attribute is also applied");
|
||||
}
|
||||
|
||||
if (type_size_of(e->type) > 0) {
|
||||
error(e->token, "@(objc_class) marked type must be of zero size");
|
||||
@@ -942,6 +990,31 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon
|
||||
if (tn->scope != e->scope) {
|
||||
error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
|
||||
} else {
|
||||
|
||||
if (ac.objc_is_implementation) {
|
||||
GB_ASSERT(e->kind == Entity_Procedure);
|
||||
|
||||
CheckerInfo *info = ctx->info;
|
||||
mutex_lock(&info->objc_method_mutex);
|
||||
defer (mutex_unlock(&info->objc_method_mutex));
|
||||
|
||||
auto method = ObjcMethodData{ ac, e };
|
||||
|
||||
if (ac.objc_selector == "") {
|
||||
method.ac.objc_selector = ac.objc_name;
|
||||
}
|
||||
|
||||
Array<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);
|
||||
defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
|
||||
|
||||
|
||||
@@ -1351,10 +1351,12 @@ gb_internal void init_universal(void) {
|
||||
t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete());
|
||||
t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete());
|
||||
t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete());
|
||||
t_objc_ivar = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_ivar"), alloc_type_struct_complete());
|
||||
|
||||
t_objc_id = alloc_type_pointer(t_objc_object);
|
||||
t_objc_SEL = alloc_type_pointer(t_objc_selector);
|
||||
t_objc_Class = alloc_type_pointer(t_objc_class);
|
||||
t_objc_Ivar = alloc_type_pointer(t_objc_ivar);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1387,6 +1389,9 @@ gb_internal void init_checker_info(CheckerInfo *i) {
|
||||
array_init(&i->defineables, a);
|
||||
|
||||
map_init(&i->objc_msgSend_types);
|
||||
mpsc_init(&i->objc_class_implementations, a);
|
||||
map_init(&i->objc_method_implementations);
|
||||
|
||||
string_map_init(&i->load_file_cache);
|
||||
array_init(&i->all_procedures, heap_allocator());
|
||||
|
||||
@@ -3345,6 +3350,11 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
|
||||
ac->test = true;
|
||||
return true;
|
||||
} else if (name == "export") {
|
||||
if (ac->objc_is_implementation) {
|
||||
error(value, "Setting @(export) explicitly is not allowed when @(objc_implement) is set. It is exported implicitly.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind == ExactValue_Invalid) {
|
||||
ac->is_export = true;
|
||||
@@ -3356,6 +3366,12 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
|
||||
}
|
||||
return true;
|
||||
} else if (name == "linkage") {
|
||||
|
||||
if (ac->objc_is_implementation) {
|
||||
error(value, "Explicit linkage not allowed when @(objc_implement) is set. It is set implicitly");
|
||||
return false;
|
||||
}
|
||||
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind != ExactValue_String) {
|
||||
error(value, "Expected either a string 'linkage'");
|
||||
@@ -3662,6 +3678,35 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (name == "objc_implement") {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind == ExactValue_Bool) {
|
||||
ac->objc_is_implementation = ev.value_bool;
|
||||
} else if (ev.kind == ExactValue_Invalid) {
|
||||
ac->objc_is_implementation = true;
|
||||
} else {
|
||||
error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name));
|
||||
}
|
||||
|
||||
// This implies exported, strongly linked
|
||||
if (ac->objc_is_implementation) {
|
||||
ac->is_export = true;
|
||||
ac->linkage = str_lit("strong");
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (name == "objc_selector") {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind == ExactValue_String) {
|
||||
if (string_is_valid_identifier(ev.value_string)) {
|
||||
ac->objc_selector = ev.value_string;
|
||||
} else {
|
||||
error(elem, "Invalid identifier for '%.*s', got '%.*s'", LIT(name), LIT(ev.value_string));
|
||||
}
|
||||
} else {
|
||||
error(elem, "Expected a string value for '%.*s'", LIT(name));
|
||||
}
|
||||
return true;
|
||||
} else if (name == "require_target_feature") {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind == ExactValue_String) {
|
||||
@@ -3901,8 +3946,36 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) {
|
||||
ac->objc_class = ev.value_string;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (name == "objc_implement") {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind == ExactValue_Bool) {
|
||||
ac->objc_is_implementation = ev.value_bool;
|
||||
} else if (ev.kind == ExactValue_Invalid) {
|
||||
ac->objc_is_implementation = true;
|
||||
} else {
|
||||
error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name));
|
||||
}
|
||||
return true;
|
||||
} else if (name == "objc_superclass") {
|
||||
Type *objc_superclass = check_type(c, value);
|
||||
|
||||
if (objc_superclass != nullptr) {
|
||||
ac->objc_superclass = objc_superclass;
|
||||
} else {
|
||||
error(value, "'%.*s' expected a named type", LIT(name));
|
||||
}
|
||||
return true;
|
||||
} else if (name == "objc_ivar") {
|
||||
Type *objc_ivar = check_type(c, value);
|
||||
|
||||
if (objc_ivar != nullptr) {
|
||||
ac->objc_ivar = objc_ivar;
|
||||
} else {
|
||||
error(value, "'%.*s' expected a named type", LIT(name));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -148,8 +148,12 @@ struct AttributeContext {
|
||||
|
||||
String objc_class;
|
||||
String objc_name;
|
||||
bool objc_is_class_method;
|
||||
String objc_selector;
|
||||
Type * objc_type;
|
||||
Type * objc_superclass;
|
||||
Type * objc_ivar;
|
||||
bool objc_is_class_method : 1;
|
||||
bool objc_is_implementation : 1; // This struct or proc provides a class/method implementation, not a binding to an existing type.
|
||||
|
||||
String require_target_feature; // required by the target micro-architecture
|
||||
String enable_target_feature; // will be enabled for the procedure only
|
||||
@@ -365,6 +369,11 @@ struct ObjcMsgData {
|
||||
Type *proc_type;
|
||||
};
|
||||
|
||||
struct ObjcMethodData {
|
||||
AttributeContext ac;
|
||||
Entity *proc_entity;
|
||||
};
|
||||
|
||||
enum LoadFileTier {
|
||||
LoadFileTier_Invalid,
|
||||
LoadFileTier_Exists,
|
||||
@@ -479,6 +488,12 @@ struct CheckerInfo {
|
||||
BlockingMutex objc_types_mutex;
|
||||
PtrMap<Ast *, ObjcMsgData> objc_msgSend_types;
|
||||
|
||||
MPSCQueue<Entity *> objc_class_implementations;
|
||||
|
||||
BlockingMutex objc_method_mutex;
|
||||
PtrMap<Type *, Array<ObjcMethodData>> objc_method_implementations;
|
||||
|
||||
|
||||
BlockingMutex load_file_mutex;
|
||||
StringMap<LoadFileCache *> load_file_cache;
|
||||
|
||||
|
||||
@@ -331,6 +331,7 @@ BuiltinProc__type_end,
|
||||
BuiltinProc_objc_find_class,
|
||||
BuiltinProc_objc_register_selector,
|
||||
BuiltinProc_objc_register_class,
|
||||
BuiltinProc_objc_ivar_get,
|
||||
|
||||
BuiltinProc_constant_utf16_cstring,
|
||||
|
||||
@@ -673,6 +674,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
|
||||
{STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
{STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
|
||||
{STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
|
||||
{STR_LIT("ivar_get"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
|
||||
|
||||
{STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
|
||||
@@ -235,6 +235,9 @@ struct Entity {
|
||||
Type * type_parameter_specialization;
|
||||
String ir_mangled_name;
|
||||
bool is_type_alias;
|
||||
bool objc_is_implementation;
|
||||
Type* objc_superclass;
|
||||
Type* objc_ivar;
|
||||
String objc_class_name;
|
||||
TypeNameObjCMetadata *objc_metadata;
|
||||
} TypeName;
|
||||
|
||||
@@ -1173,6 +1173,332 @@ gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) {
|
||||
return p;
|
||||
}
|
||||
|
||||
// TODO(harold): Move this out of here and into a more suitable place.
|
||||
// TODO(harold): Should not take an allocator, but always use temp, as we return string literals as well.
|
||||
String lb_get_objc_type_encoding(Type *t, gbAllocator allocator, isize pointer_depth = 0) {
|
||||
// NOTE(harold): See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
|
||||
|
||||
// NOTE(harold): Darwin targets are always 64-bit. Should we drop this and assume "q" always?
|
||||
#define INT_SIZE_ENCODING (build_context.metrics.ptr_size == 4 ? "i" : "q")
|
||||
switch (t->kind) {
|
||||
case Type_Basic: {
|
||||
switch (t->Basic.kind) {
|
||||
case Basic_Invalid:
|
||||
return str_lit("?");
|
||||
|
||||
case Basic_llvm_bool:
|
||||
case Basic_bool:
|
||||
case Basic_b8:
|
||||
return str_lit("B");
|
||||
|
||||
case Basic_b16:
|
||||
return str_lit("C");
|
||||
case Basic_b32:
|
||||
return str_lit("I");
|
||||
case Basic_b64:
|
||||
return str_lit("q");
|
||||
case Basic_i8:
|
||||
return str_lit("c");
|
||||
case Basic_u8:
|
||||
return str_lit("C");
|
||||
case Basic_i16:
|
||||
case Basic_i16le:
|
||||
case Basic_i16be:
|
||||
return str_lit("s");
|
||||
case Basic_u16:
|
||||
case Basic_u16le:
|
||||
case Basic_u16be:
|
||||
return str_lit("S");
|
||||
case Basic_i32:
|
||||
case Basic_i32le:
|
||||
case Basic_i32be:
|
||||
return str_lit("i");
|
||||
case Basic_u32le:
|
||||
case Basic_u32:
|
||||
case Basic_u32be:
|
||||
return str_lit("I");
|
||||
case Basic_i64:
|
||||
case Basic_i64le:
|
||||
case Basic_i64be:
|
||||
return str_lit("q");
|
||||
case Basic_u64:
|
||||
case Basic_u64le:
|
||||
case Basic_u64be:
|
||||
return str_lit("Q");
|
||||
case Basic_i128:
|
||||
case Basic_i128le:
|
||||
case Basic_i128be:
|
||||
return str_lit("t");
|
||||
case Basic_u128:
|
||||
case Basic_u128le:
|
||||
case Basic_u128be:
|
||||
return str_lit("T");
|
||||
case Basic_rune:
|
||||
return str_lit("I");
|
||||
case Basic_f16:
|
||||
case Basic_f16le:
|
||||
case Basic_f16be:
|
||||
return str_lit("s"); // @harold: Closest we've got?
|
||||
case Basic_f32:
|
||||
case Basic_f32le:
|
||||
case Basic_f32be:
|
||||
return str_lit("f");
|
||||
case Basic_f64:
|
||||
case Basic_f64le:
|
||||
case Basic_f64be:
|
||||
return str_lit("d");
|
||||
|
||||
// TODO(harold) These:
|
||||
case Basic_complex32:
|
||||
case Basic_complex64:
|
||||
case Basic_complex128:
|
||||
case Basic_quaternion64:
|
||||
case Basic_quaternion128:
|
||||
case Basic_quaternion256:
|
||||
return str_lit("?");
|
||||
|
||||
case Basic_int:
|
||||
return str_lit(INT_SIZE_ENCODING);
|
||||
case Basic_uint:
|
||||
return build_context.metrics.ptr_size == 4 ? str_lit("I") : str_lit("Q");
|
||||
case Basic_uintptr:
|
||||
case Basic_rawptr:
|
||||
return str_lit("^v");
|
||||
|
||||
case Basic_string:
|
||||
return build_context.metrics.ptr_size == 4 ? str_lit("{string=*i}") : str_lit("{string=*q}");
|
||||
|
||||
case Basic_cstring: return str_lit("*");
|
||||
case Basic_any: return str_lit("{any=^v^v"); // rawptr + ^Type_Info
|
||||
|
||||
case Basic_typeid:
|
||||
GB_ASSERT(t->Basic.size == 8);
|
||||
return str_lit("q");
|
||||
|
||||
// Untyped types
|
||||
case Basic_UntypedBool:
|
||||
case Basic_UntypedInteger:
|
||||
case Basic_UntypedFloat:
|
||||
case Basic_UntypedComplex:
|
||||
case Basic_UntypedQuaternion:
|
||||
case Basic_UntypedString:
|
||||
case Basic_UntypedRune:
|
||||
case Basic_UntypedNil:
|
||||
case Basic_UntypedUninit:
|
||||
GB_PANIC("Untyped types cannot be @encoded()");
|
||||
return str_lit("?");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Type_Named:
|
||||
case Type_Struct:
|
||||
case Type_Union: {
|
||||
Type* base = t;
|
||||
if (base->kind == Type_Named) {
|
||||
base = base_type(base);
|
||||
if(base->kind != Type_Struct && base->kind != Type_Union) {
|
||||
return lb_get_objc_type_encoding(base, allocator, pointer_depth);
|
||||
}
|
||||
}
|
||||
|
||||
const bool is_union = base->kind == Type_Union;
|
||||
if (!is_union) {
|
||||
// Check for objc_SEL
|
||||
if (internal_check_is_assignable_to(base, t_objc_SEL)) {
|
||||
return str_lit(":");
|
||||
}
|
||||
|
||||
// Check for objc_Class
|
||||
if (internal_check_is_assignable_to(base, t_objc_SEL)) {
|
||||
return str_lit("#");
|
||||
}
|
||||
|
||||
// Treat struct as an Objective-C Class?
|
||||
if (has_type_got_objc_class_attribute(base) && pointer_depth == 0) {
|
||||
return str_lit("#");
|
||||
}
|
||||
}
|
||||
|
||||
if (is_type_objc_object(base)) {
|
||||
return str_lit("@");
|
||||
}
|
||||
|
||||
|
||||
gbString s = gb_string_make_reserve(allocator, 16);
|
||||
s = gb_string_append_length(s, is_union ? "(" :"{", 1);
|
||||
if (t->kind == Type_Named) {
|
||||
s = gb_string_append_length(s, t->Named.name.text, t->Named.name.len);
|
||||
}
|
||||
|
||||
// Write fields
|
||||
if (pointer_depth < 2) {
|
||||
s = gb_string_append_length(s, "=", 1);
|
||||
|
||||
if (!is_union) {
|
||||
for( auto& f : t->Struct.fields ) {
|
||||
String field_type = lb_get_objc_type_encoding(f->type, allocator, pointer_depth);
|
||||
s = gb_string_append_length(s, field_type.text, field_type.len);
|
||||
}
|
||||
} else {
|
||||
// #TODO(harold): Encode fields
|
||||
}
|
||||
}
|
||||
|
||||
s = gb_string_append_length(s, is_union ? ")" :"}", 1);
|
||||
|
||||
return make_string_c(s);
|
||||
}
|
||||
|
||||
case Type_Generic:
|
||||
GB_PANIC("Generic types cannot be @encoded()");
|
||||
return str_lit("?");
|
||||
|
||||
case Type_Pointer: {
|
||||
String pointee = lb_get_objc_type_encoding(t->Pointer.elem, allocator, pointer_depth +1);
|
||||
// Special case for Objective-C Objects
|
||||
if (pointer_depth == 0 && pointee == "@") {
|
||||
return pointee;
|
||||
}
|
||||
|
||||
return concatenate_strings(allocator, str_lit("^"), pointee);
|
||||
}
|
||||
|
||||
case Type_MultiPointer:
|
||||
return concatenate_strings(allocator, str_lit("^"), lb_get_objc_type_encoding(t->Pointer.elem, allocator, pointer_depth +1));
|
||||
|
||||
case Type_Array: {
|
||||
String type_str = lb_get_objc_type_encoding(t->Array.elem, allocator, pointer_depth);
|
||||
|
||||
gbString s = gb_string_make_reserve(allocator, type_str.len + 8);
|
||||
s = gb_string_append_fmt(s, "[%lld%s]", t->Array.count, type_str.text);
|
||||
return make_string_c(s);
|
||||
}
|
||||
|
||||
case Type_EnumeratedArray: {
|
||||
String type_str = lb_get_objc_type_encoding(t->EnumeratedArray.elem, allocator, pointer_depth);
|
||||
|
||||
gbString s = gb_string_make_reserve(allocator, type_str.len + 8);
|
||||
s = gb_string_append_fmt(s, "[%lld%s]", t->EnumeratedArray.count, type_str.text);
|
||||
return make_string_c(s);
|
||||
}
|
||||
|
||||
case Type_Slice: {
|
||||
String type_str = lb_get_objc_type_encoding(t->Slice.elem, allocator, pointer_depth);
|
||||
gbString s = gb_string_make_reserve(allocator, type_str.len + 8);
|
||||
s = gb_string_append_fmt(s, "{slice=^%s%s}", type_str, INT_SIZE_ENCODING);
|
||||
return make_string_c(s);
|
||||
}
|
||||
|
||||
case Type_DynamicArray: {
|
||||
String type_str = lb_get_objc_type_encoding(t->DynamicArray.elem, allocator, pointer_depth);
|
||||
gbString s = gb_string_make_reserve(allocator, type_str.len + 8);
|
||||
s = gb_string_append_fmt(s, "{dynamic=^%s%s%sAllocator={?^v}}", type_str, INT_SIZE_ENCODING, INT_SIZE_ENCODING);
|
||||
return make_string_c(s);
|
||||
}
|
||||
|
||||
case Type_Map:
|
||||
return str_lit("{^v^v{Allocator=?^v}}");
|
||||
case Type_Enum:
|
||||
return lb_get_objc_type_encoding(t->Enum.base_type, allocator, pointer_depth);
|
||||
case Type_Tuple:
|
||||
// NOTE(harold): Is this allowed here?
|
||||
return str_lit("?");
|
||||
case Type_Proc:
|
||||
return str_lit("?");
|
||||
case Type_BitSet:
|
||||
return lb_get_objc_type_encoding(t->BitSet.underlying, allocator, pointer_depth);
|
||||
case Type_SimdVector:
|
||||
break;
|
||||
case Type_Matrix:
|
||||
break;
|
||||
case Type_BitField:
|
||||
return lb_get_objc_type_encoding(t->BitField.backing_type, allocator, pointer_depth);
|
||||
case Type_SoaPointer: {
|
||||
gbString s = gb_string_make_reserve(allocator, 8);
|
||||
s = gb_string_append_fmt(s, "{=^v%s}", INT_SIZE_ENCODING);
|
||||
return make_string_c(s);
|
||||
}
|
||||
|
||||
} // End switch t->kind
|
||||
#undef INT_SIZE_ENCODING
|
||||
|
||||
GB_PANIC("Unreachable");
|
||||
}
|
||||
|
||||
struct lbObjCGlobalClass {
|
||||
lbObjCGlobal g;
|
||||
lbValue class_value; // Local registered class value
|
||||
};
|
||||
|
||||
gb_internal void lb_register_objc_thing(
|
||||
StringSet &handled,
|
||||
lbModule *m,
|
||||
Array<lbValue> &args,
|
||||
Array<lbObjCGlobalClass> &class_impls,
|
||||
StringMap<lbObjCGlobalClass> &class_map,
|
||||
lbProcedure *p,
|
||||
lbObjCGlobal const &g,
|
||||
char const *call
|
||||
) {
|
||||
if (string_set_update(&handled, g.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
lbAddr addr = {};
|
||||
lbValue *found = string_map_get(&m->members, g.global_name);
|
||||
if (found) {
|
||||
addr = lb_addr(*found);
|
||||
} else {
|
||||
lbValue v = {};
|
||||
LLVMTypeRef t = lb_type(m, g.type);
|
||||
v.value = LLVMAddGlobal(m->mod, t, g.global_name);
|
||||
v.type = alloc_type_pointer(g.type);
|
||||
addr = lb_addr(v);
|
||||
LLVMSetInitializer(v.value, LLVMConstNull(t));
|
||||
}
|
||||
|
||||
lbValue class_ptr{};
|
||||
lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name));
|
||||
|
||||
// If this class requires an implementation, save it for registration below.
|
||||
if (g.class_impl_type != nullptr) {
|
||||
|
||||
// Make sure the superclass has been initialized before us
|
||||
lbValue superclass_value{};
|
||||
|
||||
auto& tn = g.class_impl_type->Named.type_name->TypeName;
|
||||
Type *superclass = tn.objc_superclass;
|
||||
if (superclass != nullptr) {
|
||||
auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name);
|
||||
lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call);
|
||||
GB_ASSERT(superclass_global.class_value.value);
|
||||
|
||||
superclass_value = superclass_global.class_value;
|
||||
}
|
||||
|
||||
args.count = 3;
|
||||
args[0] = superclass == nullptr ? lb_const_nil(m, t_objc_Class) : superclass_value;
|
||||
args[1] = class_name;
|
||||
args[2] = lb_const_int(m, t_uint, 0);
|
||||
class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args);
|
||||
|
||||
array_add(&class_impls, lbObjCGlobalClass{g, class_ptr});
|
||||
}
|
||||
else {
|
||||
args.count = 1;
|
||||
args[0] = class_name;
|
||||
class_ptr = lb_emit_runtime_call(p, call, args);
|
||||
}
|
||||
|
||||
lb_addr_store(p, addr, class_ptr);
|
||||
|
||||
lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
|
||||
if (class_global != nullptr) {
|
||||
class_global->class_value = class_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
@@ -1186,39 +1512,238 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
|
||||
string_set_init(&handled);
|
||||
defer (string_set_destroy(&handled));
|
||||
|
||||
auto args = array_make<lbValue>(temporary_allocator(), 1);
|
||||
auto args = array_make<lbValue>(temporary_allocator(), 3, 8);
|
||||
auto class_impls = array_make<lbObjCGlobalClass>(temporary_allocator(), 0, 16);
|
||||
|
||||
LLVMSetLinkage(p->value, LLVMInternalLinkage);
|
||||
lb_begin_procedure_body(p);
|
||||
// Ensure classes that have been implicitly referenced through
|
||||
// the objc_superclass attribute have a global variable available for them.
|
||||
TypeSet class_set{};
|
||||
type_set_init(&class_set, gen->objc_classes.count+16);
|
||||
defer (type_set_destroy(&class_set));
|
||||
|
||||
auto register_thing = [&handled, &m, &args](lbProcedure *p, lbObjCGlobal const &g, char const *call) {
|
||||
if (!string_set_update(&handled, g.name)) {
|
||||
lbAddr addr = {};
|
||||
lbValue *found = string_map_get(&m->members, g.global_name);
|
||||
if (found) {
|
||||
addr = lb_addr(*found);
|
||||
} else {
|
||||
lbValue v = {};
|
||||
LLVMTypeRef t = lb_type(m, g.type);
|
||||
v.value = LLVMAddGlobal(m->mod, t, g.global_name);
|
||||
v.type = alloc_type_pointer(g.type);
|
||||
addr = lb_addr(v);
|
||||
LLVMSetInitializer(v.value, LLVMConstNull(t));
|
||||
}
|
||||
auto referenced_classes = array_make<lbObjCGlobal>(temporary_allocator());
|
||||
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
|
||||
array_add( &referenced_classes, g);
|
||||
|
||||
args[0] = lb_const_value(m, t_cstring, exact_value_string(g.name));
|
||||
lbValue ptr = lb_emit_runtime_call(p, call, args);
|
||||
lb_addr_store(p, addr, ptr);
|
||||
}
|
||||
};
|
||||
Type *cls = g.class_impl_type;
|
||||
while (cls) {
|
||||
if (type_set_update(&class_set, cls)) {
|
||||
break;
|
||||
}
|
||||
GB_ASSERT(cls->kind == Type_Named);
|
||||
|
||||
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
|
||||
register_thing(p, g, "objc_lookUpClass");
|
||||
}
|
||||
cls = cls->Named.type_name->TypeName.objc_superclass;
|
||||
}
|
||||
}
|
||||
|
||||
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) {
|
||||
register_thing(p, g, "sel_registerName");
|
||||
}
|
||||
for (auto pair : class_set) {
|
||||
auto& tn = pair.type->Named.type_name->TypeName;
|
||||
Type *class_impl = !tn.objc_is_implementation ? nullptr : pair.type;
|
||||
lb_handle_objc_find_or_register_class(p, tn.objc_class_name, class_impl);
|
||||
}
|
||||
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
|
||||
array_add( &referenced_classes, g );
|
||||
}
|
||||
|
||||
// Add all class globals to a map so that we can look them up dynamically
|
||||
// in order to resolve out-of-order because classes that are being implemented
|
||||
// need their superclasses to have been registered before them.
|
||||
StringMap<lbObjCGlobalClass> global_class_map{};
|
||||
string_map_init(&global_class_map, (usize)gen->objc_classes.count);
|
||||
defer (string_map_destroy(&global_class_map));
|
||||
|
||||
for (lbObjCGlobal g :referenced_classes) {
|
||||
string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g});
|
||||
}
|
||||
|
||||
LLVMSetLinkage(p->value, LLVMInternalLinkage);
|
||||
lb_begin_procedure_body(p);
|
||||
|
||||
// Register class globals, gathering classes that must be implemented
|
||||
for (auto& kv : global_class_map) {
|
||||
lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, kv.value.g, "objc_lookUpClass");
|
||||
}
|
||||
|
||||
// Prefetch selectors for implemented methods so that they can also be registered.
|
||||
for (const auto& cd : class_impls) {
|
||||
auto& g = cd.g;
|
||||
Type *class_type = g.class_impl_type;
|
||||
|
||||
Array<ObjcMethodData>* methods = map_get(&m->info->objc_method_implementations, class_type);
|
||||
if (!methods) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const ObjcMethodData& md : *methods) {
|
||||
lb_handle_objc_find_or_register_selector(p, md.ac.objc_selector);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can register all referenced selectors
|
||||
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) {
|
||||
lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, g, "sel_registerName");
|
||||
}
|
||||
|
||||
|
||||
// Emit method wrapper implementations and registration
|
||||
auto wrapper_args = array_make<Type *>(temporary_allocator(), 2, 8);
|
||||
|
||||
for (const auto& cd : class_impls) {
|
||||
auto& g = cd.g;
|
||||
Type *class_type = g.class_impl_type;
|
||||
|
||||
Array<ObjcMethodData>* methods = map_get(&m->info->objc_method_implementations, class_type);
|
||||
if (!methods) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Type *class_ptr_type = alloc_type_pointer(class_type);
|
||||
lbValue class_value = cd.class_value;
|
||||
|
||||
for (const ObjcMethodData& md : *methods) {
|
||||
GB_ASSERT( md.proc_entity->kind == Entity_Procedure);
|
||||
Type *method_type = md.proc_entity->type;
|
||||
|
||||
String proc_name = make_string_c("__$objc_method::");
|
||||
proc_name = concatenate_strings(temporary_allocator(), proc_name, g.name);
|
||||
proc_name = concatenate_strings(temporary_allocator(), proc_name, str_lit("::"));
|
||||
proc_name = concatenate_strings( permanent_allocator(), proc_name, md.ac.objc_name);
|
||||
|
||||
wrapper_args.count = 2;
|
||||
wrapper_args[0] = md.ac.objc_is_class_method ? t_objc_Class : class_ptr_type;
|
||||
wrapper_args[1] = t_objc_SEL;
|
||||
|
||||
auto method_param_count = (isize)method_type->Proc.param_count;
|
||||
i32 method_param_offset = 0;
|
||||
|
||||
// TODO(harold): Need to make sure (at checker stage) that the non-class method has the self parameter already.
|
||||
// (Maybe this is already accounted for?.)
|
||||
if (!md.ac.objc_is_class_method) {
|
||||
GB_ASSERT(method_param_count >= 1);
|
||||
method_param_count -= 1;
|
||||
method_param_offset = 1;
|
||||
}
|
||||
|
||||
for (i32 i = 0; i < method_param_count; i++) {
|
||||
array_add(&wrapper_args, method_type->Proc.params->Tuple.variables[method_param_offset+i]->type);
|
||||
}
|
||||
|
||||
Type *wrapper_args_tuple = alloc_type_tuple_from_field_types(wrapper_args.data, wrapper_args.count, false, true);
|
||||
Type *wrapper_proc_type = alloc_type_proc(nullptr, wrapper_args_tuple, (isize)wrapper_args_tuple->Tuple.variables.count, nullptr, 0, false, ProcCC_CDecl);
|
||||
|
||||
lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type);
|
||||
lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind");
|
||||
|
||||
// Emit the wrapper
|
||||
LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage);
|
||||
lb_begin_procedure_body(wrapper_proc);
|
||||
{
|
||||
auto method_call_args = array_make<lbValue>(temporary_allocator(), method_param_count + (isize)method_param_offset);
|
||||
|
||||
if (!md.ac.objc_is_class_method) {
|
||||
method_call_args[0] = lbValue {
|
||||
wrapper_proc->raw_input_parameters[0],
|
||||
class_ptr_type,
|
||||
};
|
||||
}
|
||||
|
||||
for (isize i = 0; i < method_param_count; i++) {
|
||||
method_call_args[i+method_param_offset] = lbValue {
|
||||
wrapper_proc->raw_input_parameters[i+2],
|
||||
method_type->Proc.params->Tuple.variables[i+method_param_offset]->type,
|
||||
};
|
||||
}
|
||||
lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity);
|
||||
|
||||
// Call real procedure for method from here, passing the parameters expected, if any.
|
||||
lb_emit_call(wrapper_proc, method_proc_value, method_call_args);
|
||||
}
|
||||
lb_end_procedure_body(wrapper_proc);
|
||||
|
||||
|
||||
// Add the method to the class
|
||||
String method_encoding = str_lit("v");
|
||||
// TODO (harold): Checker must ensure that objc_methods have a single return value or none!
|
||||
GB_ASSERT(method_type->Proc.result_count <= 1);
|
||||
if (method_type->Proc.result_count != 0) {
|
||||
method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type, temporary_allocator());
|
||||
}
|
||||
|
||||
if (!md.ac.objc_is_class_method) {
|
||||
method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("@:"));
|
||||
} else {
|
||||
method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:"));
|
||||
}
|
||||
|
||||
for (i32 i = method_param_offset; i < method_param_count; i++) {
|
||||
Type *param_type = method_type->Proc.params->Tuple.variables[i]->type;
|
||||
String param_encoding = lb_get_objc_type_encoding(param_type, temporary_allocator());
|
||||
|
||||
method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding);
|
||||
}
|
||||
|
||||
// Emit method registration
|
||||
lbAddr* sel_address = string_map_get(&m->objc_selectors, md.ac.objc_selector);
|
||||
GB_ASSERT(sel_address);
|
||||
lbValue selector_value = lb_addr_load(p, *sel_address);
|
||||
|
||||
args.count = 4;
|
||||
args[0] = class_value; // Class
|
||||
args[1] = selector_value; // SEL
|
||||
args[2] = lbValue { wrapper_proc->value, wrapper_proc->type };
|
||||
args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding));
|
||||
|
||||
// TODO(harold): Emit check BOOL result and panic if false.
|
||||
lb_emit_runtime_call(p, "class_addMethod", args);
|
||||
|
||||
} // End methods
|
||||
|
||||
// Add ivar if we have one
|
||||
Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar;
|
||||
if (ivar_type != nullptr) {
|
||||
// Register a single ivar for this class
|
||||
Type *ivar_base = ivar_type->Named.base;
|
||||
// TODO(harold): No idea if I can use this, but I assume so?
|
||||
const i64 size = ivar_base->cached_size;
|
||||
const i64 alignment = ivar_base->cached_align;
|
||||
// TODO(harold): Checker: Alignment must be compatible with ivar rules. Or we should increase the alignment if needed.
|
||||
|
||||
String ivar_name = str_lit("__$ivar");
|
||||
String ivar_types = str_lit("{= }");
|
||||
args.count = 5;
|
||||
args[0] = class_value;
|
||||
args[1] = lb_const_value(m, t_cstring, exact_value_string(ivar_name));
|
||||
args[2] = lb_const_value(m, t_uint, exact_value_u64((u64)size));
|
||||
args[3] = lb_const_value(m, t_u8, exact_value_u64((u64)alignment));
|
||||
args[4] = lb_const_value(m, t_cstring, exact_value_string(ivar_types));
|
||||
lb_emit_runtime_call(p, "class_addIvar", args);
|
||||
}
|
||||
|
||||
// Complete the class registration
|
||||
args.count = 1;
|
||||
args[0] = class_value;
|
||||
lb_emit_runtime_call(p, "objc_registerClassPair", args);
|
||||
|
||||
// If we have an ivar, store its offset globally for an intrinsic
|
||||
// TODO(harold): Only do this for types that had ivar_get calls registered!
|
||||
if (ivar_type != nullptr) {
|
||||
args.count = 2;
|
||||
args[0] = class_value;
|
||||
args[1] = lb_const_value(m, t_cstring, exact_value_string(str_lit("__$ivar")));
|
||||
lbValue ivar = lb_emit_runtime_call(p, "class_getInstanceVariable", args);
|
||||
|
||||
args.count = 1;
|
||||
args[0] = ivar;
|
||||
lbValue ivar_offset = lb_emit_runtime_call(p, "ivar_getOffset", args);
|
||||
lbValue ivar_offset_u32 = lb_emit_conv(p, ivar_offset, t_u32);
|
||||
|
||||
String class_name = class_type->Named.type_name->TypeName.objc_class_name;
|
||||
// TODO(harold): Oops! This is wrong, that map is there to prevent re-entry.
|
||||
// Simply emit from referred ivars. For now use a single module only.
|
||||
lbAddr ivar_addr = string_map_must_get(&m->objc_ivars, class_name);
|
||||
lb_addr_store(p, ivar_addr, ivar_offset_u32);
|
||||
}
|
||||
}
|
||||
|
||||
lb_end_procedure_body(p);
|
||||
}
|
||||
|
||||
@@ -196,6 +196,7 @@ struct lbModule {
|
||||
|
||||
StringMap<lbAddr> objc_classes;
|
||||
StringMap<lbAddr> objc_selectors;
|
||||
StringMap<lbAddr> objc_ivars;
|
||||
|
||||
PtrMap<u64/*type hash*/, lbAddr> map_cell_info_map; // address of runtime.Map_Info
|
||||
PtrMap<u64/*type hash*/, lbAddr> map_info_map; // address of runtime.Map_Cell_Info
|
||||
@@ -219,6 +220,7 @@ struct lbObjCGlobal {
|
||||
gbString global_name;
|
||||
String name;
|
||||
Type * type;
|
||||
Type * class_impl_type; // This is set when the class has the objc_implement attribute set to true.
|
||||
};
|
||||
|
||||
struct lbGenerator : LinkerData {
|
||||
@@ -240,6 +242,7 @@ struct lbGenerator : LinkerData {
|
||||
MPSCQueue<lbEntityCorrection> entities_to_correct_linkage;
|
||||
MPSCQueue<lbObjCGlobal> objc_selectors;
|
||||
MPSCQueue<lbObjCGlobal> objc_classes;
|
||||
MPSCQueue<lbObjCGlobal> objc_ivars;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) {
|
||||
|
||||
string_map_init(&m->objc_classes);
|
||||
string_map_init(&m->objc_selectors);
|
||||
string_map_init(&m->objc_ivars);
|
||||
|
||||
map_init(&m->map_info_map, 0);
|
||||
map_init(&m->map_cell_info_map, 0);
|
||||
@@ -173,6 +174,7 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) {
|
||||
mpsc_init(&gen->entities_to_correct_linkage, heap_allocator());
|
||||
mpsc_init(&gen->objc_selectors, heap_allocator());
|
||||
mpsc_init(&gen->objc_classes, heap_allocator());
|
||||
mpsc_init(&gen->objc_ivars, heap_allocator());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3290,6 +3290,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
|
||||
case BuiltinProc_objc_find_class: return lb_handle_objc_find_class(p, expr);
|
||||
case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr);
|
||||
case BuiltinProc_objc_register_class: return lb_handle_objc_register_class(p, expr);
|
||||
case BuiltinProc_objc_ivar_get: return lb_handle_objc_ivar_get(p, expr);
|
||||
|
||||
|
||||
case BuiltinProc_constant_utf16_cstring:
|
||||
|
||||
@@ -2125,7 +2125,7 @@ gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, Stri
|
||||
return addr;
|
||||
}
|
||||
|
||||
gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) {
|
||||
gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name, Type *class_impl_type) {
|
||||
lbModule *m = p->module;
|
||||
lbAddr *found = string_map_get(&m->objc_classes, name);
|
||||
if (found) {
|
||||
@@ -2148,13 +2148,72 @@ gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String
|
||||
} else {
|
||||
LLVMSetLinkage(g.value, LLVMExternalLinkage);
|
||||
}
|
||||
mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class});
|
||||
mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class, class_impl_type});
|
||||
|
||||
lbAddr addr = lb_addr(g);
|
||||
string_map_set(&m->objc_classes, name, addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_type) {
|
||||
|
||||
String name = self_type->Named.type_name->TypeName.objc_class_name;
|
||||
GB_ASSERT(name != "");
|
||||
|
||||
lbAddr *found = string_map_get(&m->objc_ivars, name);
|
||||
if (found) {
|
||||
return *found;
|
||||
}
|
||||
|
||||
|
||||
lbModule *default_module = &m->gen->default_module;
|
||||
|
||||
gbString global_name = gb_string_make(permanent_allocator(), "__$objc_ivar::");
|
||||
global_name = gb_string_append_length(global_name, name.text, name.len);
|
||||
|
||||
// Create a global variable to store offset of the ivar in an instance of an object
|
||||
Type *p_ivar_offset = alloc_type_pointer(t_u32);
|
||||
|
||||
LLVMTypeRef t = lb_type(m, p_ivar_offset);
|
||||
lbValue g = {};
|
||||
g.value = LLVMAddGlobal(m->mod, t, global_name);
|
||||
g.type = p_ivar_offset;
|
||||
|
||||
if (default_module == m) {
|
||||
LLVMSetInitializer(g.value, LLVMConstNull(t));
|
||||
lb_add_member(m, make_string_c(global_name), g);
|
||||
} else {
|
||||
LLVMSetLinkage(g.value, LLVMExternalLinkage);
|
||||
}
|
||||
|
||||
mpsc_enqueue(&m->gen->objc_ivars, lbObjCGlobal{m, global_name, name, self_type});
|
||||
|
||||
lbAddr addr = lb_addr(g);
|
||||
string_map_set(&m->objc_ivars, name, addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) {
|
||||
ast_node(ce, CallExpr, expr);
|
||||
lbModule *m = p->module;
|
||||
|
||||
GB_ASSERT(ce->args[0]->tav.type->kind == Type_Pointer);
|
||||
Type *self_type = ce->args[0]->tav.type->Pointer.elem;
|
||||
Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar;
|
||||
|
||||
Type* p_ivar = alloc_type_pointer(ivar_type);
|
||||
|
||||
lbValue ivar_offset = lb_addr_load(p, lb_handle_objc_find_or_register_ivar(m, self_type));
|
||||
lbValue ivar_offset_uptr = lb_emit_conv(p, ivar_offset, t_uintptr);
|
||||
|
||||
lbValue self = lb_build_expr(p, ce->args[0]);
|
||||
lbValue self_uptr = lb_emit_conv(p, self, t_uintptr);
|
||||
|
||||
lbValue ivar_uptr = lb_emit_arith(p, Token_Add, self_uptr, ivar_offset_uptr, t_uintptr);
|
||||
|
||||
return lb_emit_conv(p, ivar_uptr, p_ivar);
|
||||
}
|
||||
|
||||
gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) {
|
||||
ast_node(ce, CallExpr, expr);
|
||||
|
||||
@@ -2188,7 +2247,7 @@ gb_internal lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) {
|
||||
auto tav = ce->args[0]->tav;
|
||||
GB_ASSERT(tav.value.kind == ExactValue_String);
|
||||
String name = tav.value.value_string;
|
||||
return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name));
|
||||
return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name, nullptr));
|
||||
}
|
||||
|
||||
gb_internal lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) {
|
||||
@@ -2198,7 +2257,7 @@ gb_internal lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) {
|
||||
auto tav = ce->args[0]->tav;
|
||||
GB_ASSERT(tav.value.kind == ExactValue_String);
|
||||
String name = tav.value.value_string;
|
||||
lbAddr dst = lb_handle_objc_find_or_register_class(p, name);
|
||||
lbAddr dst = lb_handle_objc_find_or_register_class(p, name, nullptr);
|
||||
|
||||
auto args = array_make<lbValue>(permanent_allocator(), 3);
|
||||
args[0] = lb_const_nil(m, t_objc_Class);
|
||||
@@ -2220,7 +2279,9 @@ gb_internal lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) {
|
||||
GB_ASSERT(e->kind == Entity_TypeName);
|
||||
String name = e->TypeName.objc_class_name;
|
||||
|
||||
return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name));
|
||||
Type *class_impl_type = e->TypeName.objc_is_implementation ? type : nullptr;
|
||||
|
||||
return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name, class_impl_type));
|
||||
}
|
||||
|
||||
return lb_build_expr(p, expr);
|
||||
@@ -2266,9 +2327,6 @@ gb_internal lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) {
|
||||
return lb_emit_call(p, the_proc, args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) {
|
||||
GB_ASSERT(value.kind == ExactValue_Integer);
|
||||
i64 v = exact_value_to_i64(value);
|
||||
|
||||
@@ -729,10 +729,12 @@ gb_global Type *t_map_set_proc = nullptr;
|
||||
gb_global Type *t_objc_object = nullptr;
|
||||
gb_global Type *t_objc_selector = nullptr;
|
||||
gb_global Type *t_objc_class = nullptr;
|
||||
gb_global Type *t_objc_ivar = nullptr;
|
||||
|
||||
gb_global Type *t_objc_id = nullptr;
|
||||
gb_global Type *t_objc_SEL = nullptr;
|
||||
gb_global Type *t_objc_Class = nullptr;
|
||||
gb_global Type *t_objc_Ivar = nullptr;
|
||||
|
||||
enum OdinAtomicMemoryOrder : i32 {
|
||||
OdinAtomicMemoryOrder_relaxed = 0, // unordered
|
||||
|
||||
Reference in New Issue
Block a user