mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-03 22:28:46 +00:00
Merge pull request #1482 from odin-lang/objc-intrinsics
Improve support Objective-C code through intrinsics and Metal API
This commit is contained in:
+273
-1
@@ -143,6 +143,238 @@ void check_or_return_split_types(CheckerContext *c, Operand *x, String const &na
|
||||
}
|
||||
|
||||
|
||||
bool does_require_msgSend_stret(Type *return_type) {
|
||||
if (return_type == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) {
|
||||
i64 struct_limit = type_size_of(t_uintptr) << 1;
|
||||
return type_size_of(return_type) > struct_limit;
|
||||
}
|
||||
if (build_context.metrics.arch == TargetArch_arm64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (build_context.metrics.arch == TargetArch_arm32) {
|
||||
// i64 struct_limit = type_size_of(t_uintptr);
|
||||
// // NOTE(bill): This is technically wrong
|
||||
// return is_type_struct(return_type) && !is_type_raw_union(return_type) && type_size_of(return_type) > struct_limit;
|
||||
// }
|
||||
GB_PANIC("unsupported architecture");
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjcMsgKind get_objc_proc_kind(Type *return_type) {
|
||||
if (return_type == nullptr) {
|
||||
return ObjcMsg_normal;
|
||||
}
|
||||
|
||||
if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) {
|
||||
if (is_type_float(return_type)) {
|
||||
return ObjcMsg_fpret;
|
||||
}
|
||||
if (build_context.metrics.arch == TargetArch_amd64) {
|
||||
if (is_type_complex(return_type)) {
|
||||
// URL: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/message.h#L143-L159
|
||||
return ObjcMsg_fpret;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (build_context.metrics.arch != TargetArch_arm64) {
|
||||
if (does_require_msgSend_stret(return_type)) {
|
||||
return ObjcMsg_stret;
|
||||
}
|
||||
}
|
||||
return ObjcMsg_normal;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// NOTE(bill, 2022-02-08): the backend's ABI handling should handle this correctly, I hope
|
||||
Type *params = alloc_type_tuple();
|
||||
{
|
||||
auto variables = array_make<Entity *>(permanent_allocator(), 0, param_types.count);
|
||||
|
||||
for_array(i, param_types) {
|
||||
Type *type = param_types[i];
|
||||
Entity *param = alloc_entity_param(scope, blank_token, type, false, true);
|
||||
array_add(&variables, param);
|
||||
}
|
||||
params->Tuple.variables = slice_from_array(variables);
|
||||
}
|
||||
|
||||
Type *results = alloc_type_tuple();
|
||||
if (return_type) {
|
||||
auto variables = array_make<Entity *>(permanent_allocator(), 1);
|
||||
results->Tuple.variables = slice_from_array(variables);
|
||||
Entity *param = alloc_entity_param(scope, blank_token, return_type, false, true);
|
||||
results->Tuple.variables[0] = param;
|
||||
}
|
||||
|
||||
|
||||
ObjcMsgData data = {};
|
||||
data.kind = kind;
|
||||
data.proc_type = alloc_type_proc(scope, params, param_types.count, results, results->Tuple.variables.count, false, ProcCC_CDecl);
|
||||
|
||||
mutex_lock(&c->info->objc_types_mutex);
|
||||
map_set(&c->info->objc_msgSend_types, call, data);
|
||||
mutex_unlock(&c->info->objc_types_mutex);
|
||||
|
||||
add_package_dependency(c, "runtime", "objc_lookUpClass");
|
||||
add_package_dependency(c, "runtime", "sel_registerName");
|
||||
add_package_dependency(c, "runtime", "objc_allocateClassPair");
|
||||
|
||||
add_package_dependency(c, "runtime", "objc_msgSend");
|
||||
add_package_dependency(c, "runtime", "objc_msgSend_fpret");
|
||||
add_package_dependency(c, "runtime", "objc_msgSend_fp2ret");
|
||||
add_package_dependency(c, "runtime", "objc_msgSend_stret");
|
||||
}
|
||||
|
||||
bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) {
|
||||
auto const is_constant_string = [](CheckerContext *c, String const &builtin_name, Ast *expr, String *name_) -> bool {
|
||||
Operand op = {};
|
||||
check_expr(c, &op, expr);
|
||||
if (op.mode == Addressing_Constant && op.value.kind == ExactValue_String) {
|
||||
if (name_) *name_ = op.value.value_string;
|
||||
return true;
|
||||
}
|
||||
gbString e = expr_to_string(op.expr);
|
||||
gbString t = type_to_string(op.type);
|
||||
error(op.expr, "'%.*s' expected a constant string value, got %s of type %s", LIT(builtin_name), e, t);
|
||||
gb_string_free(t);
|
||||
gb_string_free(e);
|
||||
return false;
|
||||
};
|
||||
String builtin_name = builtin_procs[id].name;
|
||||
|
||||
if (build_context.metrics.os != TargetOs_darwin) {
|
||||
error(call, "'%.*s' only works on darwin", LIT(builtin_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ast_node(ce, CallExpr, call);
|
||||
switch (id) {
|
||||
default:
|
||||
GB_PANIC("Implement objective built-in procedure: %.*s", LIT(builtin_name));
|
||||
return false;
|
||||
|
||||
case BuiltinProc_objc_send: {
|
||||
Type *return_type = nullptr;
|
||||
|
||||
Operand rt = {};
|
||||
check_expr_or_type(c, &rt, ce->args[0]);
|
||||
if (rt.mode == Addressing_Type) {
|
||||
return_type = rt.type;
|
||||
} else if (is_operand_nil(rt)) {
|
||||
return_type = nullptr;
|
||||
} else {
|
||||
gbString e = expr_to_string(rt.expr);
|
||||
error(rt.expr, "'%.*s' expected a type or nil to define the return type of the Objective-C call, got %s", LIT(builtin_name), e);
|
||||
gb_string_free(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
operand->type = return_type;
|
||||
operand->mode = return_type ? Addressing_Value : Addressing_NoValue;
|
||||
|
||||
String class_name = {};
|
||||
String sel_name = {};
|
||||
|
||||
Type *sel_type = t_objc_SEL;
|
||||
Operand self = {};
|
||||
check_expr_or_type(c, &self, ce->args[1]);
|
||||
if (self.mode == Addressing_Type) {
|
||||
if (!is_type_objc_object(self.type)) {
|
||||
gbString t = type_to_string(self.type);
|
||||
error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got type %s", LIT(builtin_name), t);
|
||||
gb_string_free(t);
|
||||
return false;
|
||||
}
|
||||
if (!has_type_got_objc_class_attribute(self.type)) {
|
||||
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;
|
||||
}
|
||||
|
||||
sel_type = t_objc_Class;
|
||||
} else 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 %d", LIT(builtin_name), e, t, self.type->kind);
|
||||
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;
|
||||
} else {
|
||||
Type *type = type_deref(self.type);
|
||||
if (!(type->kind == Type_Named &&
|
||||
type->Named.type_name != nullptr &&
|
||||
type->Named.type_name->TypeName.objc_class_name != "")) {
|
||||
gbString t = type_to_string(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 (!is_constant_string(c, builtin_name, ce->args[2], &sel_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
isize const arg_offset = 1;
|
||||
auto param_types = slice_make<Type *>(permanent_allocator(), ce->args.count-arg_offset);
|
||||
param_types[0] = t_objc_id;
|
||||
param_types[1] = sel_type;
|
||||
|
||||
for (isize i = 2+arg_offset; i < ce->args.count; i++) {
|
||||
Operand x = {};
|
||||
check_expr(c, &x, ce->args[i]);
|
||||
param_types[i-arg_offset] = x.type;
|
||||
}
|
||||
|
||||
add_objc_proc_type(c, call, return_type, param_types);
|
||||
|
||||
return true;
|
||||
} break;
|
||||
|
||||
case BuiltinProc_objc_find_selector:
|
||||
case BuiltinProc_objc_find_class:
|
||||
case BuiltinProc_objc_register_selector:
|
||||
case BuiltinProc_objc_register_class:
|
||||
{
|
||||
String sel_name = {};
|
||||
if (!is_constant_string(c, builtin_name, ce->args[0], &sel_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
case BuiltinProc_objc_find_selector:
|
||||
case BuiltinProc_objc_register_selector:
|
||||
operand->type = t_objc_SEL;
|
||||
break;
|
||||
case BuiltinProc_objc_find_class:
|
||||
case BuiltinProc_objc_register_class:
|
||||
operand->type = t_objc_Class;
|
||||
break;
|
||||
|
||||
}
|
||||
operand->mode = Addressing_Value;
|
||||
return true;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) {
|
||||
ast_node(ce, CallExpr, call);
|
||||
@@ -179,6 +411,12 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
case BuiltinProc_len:
|
||||
case BuiltinProc_min:
|
||||
case BuiltinProc_max:
|
||||
case BuiltinProc_type_is_subtype_of:
|
||||
case BuiltinProc_objc_send:
|
||||
case BuiltinProc_objc_find_selector:
|
||||
case BuiltinProc_objc_find_class:
|
||||
case BuiltinProc_objc_register_selector:
|
||||
case BuiltinProc_objc_register_class:
|
||||
// NOTE(bill): The first arg may be a Type, this will be checked case by case
|
||||
break;
|
||||
|
||||
@@ -202,7 +440,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
break;
|
||||
}
|
||||
|
||||
String builtin_name = builtin_procs[id].name;;
|
||||
String builtin_name = builtin_procs[id].name;
|
||||
|
||||
|
||||
if (ce->args.count > 0) {
|
||||
@@ -219,6 +457,13 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
GB_PANIC("Implement built-in procedure: %.*s", LIT(builtin_name));
|
||||
break;
|
||||
|
||||
case BuiltinProc_objc_send:
|
||||
case BuiltinProc_objc_find_selector:
|
||||
case BuiltinProc_objc_find_class:
|
||||
case BuiltinProc_objc_register_selector:
|
||||
case BuiltinProc_objc_register_class:
|
||||
return check_builtin_objc_procedure(c, operand, call, id, type_hint);
|
||||
|
||||
case BuiltinProc___entry_point:
|
||||
operand->mode = Addressing_NoValue;
|
||||
operand->type = nullptr;
|
||||
@@ -3726,6 +3971,31 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
|
||||
break;
|
||||
|
||||
case BuiltinProc_type_is_subtype_of:
|
||||
{
|
||||
Operand op_src = {};
|
||||
Operand op_dst = {};
|
||||
|
||||
check_expr_or_type(c, &op_src, ce->args[0]);
|
||||
if (op_src.mode != Addressing_Type) {
|
||||
gbString e = expr_to_string(op_src.expr);
|
||||
error(op_src.expr, "'%.*s' expects a type, got %s", LIT(builtin_name), e);
|
||||
gb_string_free(e);
|
||||
return false;
|
||||
}
|
||||
check_expr_or_type(c, &op_dst, ce->args[1]);
|
||||
if (op_dst.mode != Addressing_Type) {
|
||||
gbString e = expr_to_string(op_dst.expr);
|
||||
error(op_dst.expr, "'%.*s' expects a type, got %s", LIT(builtin_name), e);
|
||||
gb_string_free(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
operand->value = exact_value_bool(is_type_subtype_of(op_src.type, op_dst.type));
|
||||
operand->mode = Addressing_Constant;
|
||||
operand->type = t_untyped_bool;
|
||||
} break;
|
||||
|
||||
case BuiltinProc_type_field_index_of:
|
||||
{
|
||||
Operand op = {};
|
||||
@@ -3815,6 +4085,8 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
operand->type = t_hasher_proc;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -338,6 +338,13 @@ void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *def)
|
||||
if (decl != nullptr) {
|
||||
AttributeContext ac = {};
|
||||
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;
|
||||
|
||||
if (type_size_of(e->type) > 0) {
|
||||
error(e->token, "@(objc_class) marked type must be of zero size");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -819,6 +826,63 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
|
||||
}
|
||||
e->Procedure.optimization_mode = cast(ProcedureOptimizationMode)ac.optimization_mode;
|
||||
|
||||
if (ac.objc_name.len || ac.objc_is_class_method || ac.objc_type) {
|
||||
if (ac.objc_name.len == 0 && ac.objc_is_class_method) {
|
||||
error(e->token, "@(objc_name) is required with @(objc_is_class_method)");
|
||||
} else if (ac.objc_type == nullptr) {
|
||||
error(e->token, "@(objc_name) requires that @(objc_type) to be set");
|
||||
} else if (ac.objc_name.len == 0 && ac.objc_type) {
|
||||
error(e->token, "@(objc_name) is required with @(objc_type)");
|
||||
} else {
|
||||
Type *t = ac.objc_type;
|
||||
if (t->kind == Type_Named) {
|
||||
Entity *tn = t->Named.type_name;
|
||||
|
||||
GB_ASSERT(tn->kind == Entity_TypeName);
|
||||
|
||||
if (tn->scope != e->scope) {
|
||||
error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
|
||||
} else {
|
||||
mutex_lock(&global_type_name_objc_metadata_mutex);
|
||||
defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
|
||||
|
||||
if (!tn->TypeName.objc_metadata) {
|
||||
tn->TypeName.objc_metadata = create_type_name_obj_c_metadata();
|
||||
}
|
||||
auto *md = tn->TypeName.objc_metadata;
|
||||
mutex_lock(md->mutex);
|
||||
defer (mutex_unlock(md->mutex));
|
||||
|
||||
if (!ac.objc_is_class_method) {
|
||||
bool ok = true;
|
||||
for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
|
||||
if (entry.name == ac.objc_name) {
|
||||
error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
|
||||
}
|
||||
} else {
|
||||
bool ok = true;
|
||||
for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
|
||||
if (entry.name == ac.objc_name) {
|
||||
error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch (e->Procedure.optimization_mode) {
|
||||
case ProcedureOptimizationMode_None:
|
||||
|
||||
+3
-39
@@ -228,42 +228,6 @@ void check_scope_decls(CheckerContext *c, Slice<Ast *> const &nodes, isize reser
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isize check_is_assignable_to_using_subtype(Type *src, Type *dst, isize level = 0, bool src_is_ptr = false) {
|
||||
Type *prev_src = src;
|
||||
src = type_deref(src);
|
||||
if (!src_is_ptr) {
|
||||
src_is_ptr = src != prev_src;
|
||||
}
|
||||
src = base_type(src);
|
||||
|
||||
if (!is_type_struct(src)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for_array(i, src->Struct.fields) {
|
||||
Entity *f = src->Struct.fields[i];
|
||||
if (f->kind != Entity_Variable || (f->flags&EntityFlag_Using) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (are_types_identical(f->type, dst)) {
|
||||
return level+1;
|
||||
}
|
||||
if (src_is_ptr && is_type_pointer(dst)) {
|
||||
if (are_types_identical(f->type, type_deref(dst))) {
|
||||
return level+1;
|
||||
}
|
||||
}
|
||||
isize nested_level = check_is_assignable_to_using_subtype(f->type, dst, level+1, src_is_ptr);
|
||||
if (nested_level > 0) {
|
||||
return nested_level;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, Entity *base_entity, Type *type,
|
||||
Array<Operand> *param_operands, Ast *poly_def_node, PolyProcData *poly_proc_data) {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -6432,10 +6396,10 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
|
||||
return builtin_procs[id].kind;
|
||||
}
|
||||
|
||||
Entity *e = entity_of_node(operand->expr);
|
||||
Entity *initial_entity = entity_of_node(operand->expr);
|
||||
|
||||
if (e != nullptr && e->kind == Entity_Procedure) {
|
||||
if (e->Procedure.deferred_procedure.entity != nullptr) {
|
||||
if (initial_entity != nullptr && initial_entity->kind == Entity_Procedure) {
|
||||
if (initial_entity->Procedure.deferred_procedure.entity != nullptr) {
|
||||
call->viral_state_flags |= ViralStateFlag_ContainsDeferredProcedure;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +323,10 @@ void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, Type *named_t
|
||||
}
|
||||
|
||||
named_type->Named.type_name = e;
|
||||
GB_ASSERT(original_type->kind == Type_Named);
|
||||
e->TypeName.objc_class_name = original_type->Named.type_name->TypeName.objc_class_name;
|
||||
// TODO(bill): Is this even correct? Or should the metadata be copied?
|
||||
e->TypeName.objc_metadata = original_type->Named.type_name->TypeName.objc_metadata;
|
||||
|
||||
mutex_lock(&ctx->info->gen_types_mutex);
|
||||
auto *found_gen_types = map_get(&ctx->info->gen_types, original_type);
|
||||
|
||||
+81
-1
@@ -4,7 +4,7 @@
|
||||
void check_expr(CheckerContext *c, Operand *operand, Ast *expression);
|
||||
void check_expr_or_type(CheckerContext *c, Operand *operand, Ast *expression, Type *type_hint=nullptr);
|
||||
void add_comparison_procedures_for_fields(CheckerContext *c, Type *t);
|
||||
|
||||
Type *check_type(CheckerContext *ctx, Ast *e);
|
||||
|
||||
bool is_operand_value(Operand o) {
|
||||
switch (o.mode) {
|
||||
@@ -841,6 +841,17 @@ void add_global_enum_constant(Slice<Entity *> const &fields, char const *name, i
|
||||
GB_PANIC("Unfound enum value for global constant: %s %lld", name, cast(long long)value);
|
||||
}
|
||||
|
||||
Type *add_global_type_name(Scope *scope, String const &type_name, Type *backing_type) {
|
||||
Entity *e = alloc_entity_type_name(scope, make_token_ident(type_name), nullptr, EntityState_Resolved);
|
||||
Type *named_type = alloc_type_named(type_name, backing_type, e);
|
||||
e->type = named_type;
|
||||
set_base_type(named_type, backing_type);
|
||||
if (scope_insert(scope, e)) {
|
||||
compiler_error("double declaration of %.*s", LIT(e->token.string));
|
||||
}
|
||||
return named_type;
|
||||
}
|
||||
|
||||
|
||||
void init_universal(void) {
|
||||
BuildContext *bc = &build_context;
|
||||
@@ -985,6 +996,17 @@ void init_universal(void) {
|
||||
t_f64_ptr = alloc_type_pointer(t_f64);
|
||||
t_u8_slice = alloc_type_slice(t_u8);
|
||||
t_string_slice = alloc_type_slice(t_string);
|
||||
|
||||
// intrinsics types for objective-c stuff
|
||||
{
|
||||
t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct());
|
||||
t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct());
|
||||
t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct());
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1041,6 +1063,9 @@ void init_checker_info(CheckerInfo *i) {
|
||||
semaphore_init(&i->collect_semaphore);
|
||||
|
||||
mpmc_init(&i->intrinsics_entry_point_usage, a, 1<<10); // just waste some memory here, even if it probably never used
|
||||
|
||||
mutex_init(&i->objc_types_mutex);
|
||||
map_init(&i->objc_msgSend_types, a);
|
||||
}
|
||||
|
||||
void destroy_checker_info(CheckerInfo *i) {
|
||||
@@ -1073,6 +1098,9 @@ void destroy_checker_info(CheckerInfo *i) {
|
||||
mutex_destroy(&i->type_and_value_mutex);
|
||||
mutex_destroy(&i->identifier_uses_mutex);
|
||||
mutex_destroy(&i->foreign_mutex);
|
||||
|
||||
mutex_destroy(&i->objc_types_mutex);
|
||||
map_destroy(&i->objc_msgSend_types);
|
||||
}
|
||||
|
||||
CheckerContext make_checker_context(Checker *c) {
|
||||
@@ -2712,6 +2740,14 @@ ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) {
|
||||
return ev;
|
||||
}
|
||||
|
||||
Type *check_decl_attribute_type(CheckerContext *c, Ast *value) {
|
||||
if (value != nullptr) {
|
||||
return check_type(c, value);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
#define ATTRIBUTE_USER_TAG_NAME "tag"
|
||||
|
||||
|
||||
@@ -3011,6 +3047,42 @@ DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
|
||||
error(elem, "Expected a string for '%.*s'", LIT(name));
|
||||
}
|
||||
return true;
|
||||
} else if (name == "objc_name") {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind == ExactValue_String) {
|
||||
if (string_is_valid_identifier(ev.value_string)) {
|
||||
ac->objc_name = 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 == "objc_is_class_method") {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind == ExactValue_Bool) {
|
||||
ac->objc_is_class_method = ev.value_bool;
|
||||
} else {
|
||||
error(elem, "Expected a boolean value for '%.*s'", LIT(name));
|
||||
}
|
||||
return true;
|
||||
} else if (name == "objc_type") {
|
||||
if (value == nullptr) {
|
||||
error(elem, "Expected a type for '%.*s'", LIT(name));
|
||||
} else {
|
||||
Type *objc_type = check_type(c, value);
|
||||
if (objc_type != nullptr) {
|
||||
if (!has_type_got_objc_class_attribute(objc_type)) {
|
||||
gbString t = type_to_string(objc_type);
|
||||
error(value, "'%.*s' expected a named type with the attribute @(obj_class=<string>), got type %s", LIT(name), t);
|
||||
gb_string_free(t);
|
||||
} else {
|
||||
ac->objc_type = objc_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -3161,6 +3233,14 @@ DECL_ATTRIBUTE_PROC(type_decl_attribute) {
|
||||
} else if (name == "private") {
|
||||
// NOTE(bill): Handled elsewhere `check_collect_value_decl`
|
||||
return true;
|
||||
} else if (name == "objc_class") {
|
||||
ExactValue ev = check_decl_attribute_value(c, value);
|
||||
if (ev.kind != ExactValue_String || ev.value_string == "") {
|
||||
error(elem, "Expected a non-empty string value for '%.*s'", LIT(name));
|
||||
} else {
|
||||
ac->objc_class = ev.value_string;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+18
-1
@@ -118,6 +118,11 @@ struct AttributeContext {
|
||||
bool init : 1;
|
||||
bool set_cold : 1;
|
||||
u32 optimization_mode; // ProcedureOptimizationMode
|
||||
|
||||
String objc_class;
|
||||
String objc_name;
|
||||
bool objc_is_class_method;
|
||||
Type * objc_type;
|
||||
};
|
||||
|
||||
AttributeContext make_attribute_context(String link_prefix) {
|
||||
@@ -267,6 +272,17 @@ struct UntypedExprInfo {
|
||||
typedef PtrMap<Ast *, ExprInfo *> UntypedExprInfoMap;
|
||||
typedef MPMCQueue<ProcInfo *> ProcBodyQueue;
|
||||
|
||||
enum ObjcMsgKind : u32 {
|
||||
ObjcMsg_normal,
|
||||
ObjcMsg_fpret,
|
||||
ObjcMsg_fp2ret,
|
||||
ObjcMsg_stret,
|
||||
};
|
||||
struct ObjcMsgData {
|
||||
ObjcMsgKind kind;
|
||||
Type *proc_type;
|
||||
};
|
||||
|
||||
// CheckerInfo stores all the symbol information for a type-checked program
|
||||
struct CheckerInfo {
|
||||
Checker *checker;
|
||||
@@ -340,7 +356,8 @@ struct CheckerInfo {
|
||||
|
||||
MPMCQueue<Ast *> intrinsics_entry_point_usage;
|
||||
|
||||
|
||||
BlockingMutex objc_types_mutex;
|
||||
PtrMap<Ast *, ObjcMsgData> objc_msgSend_types;
|
||||
};
|
||||
|
||||
struct CheckerContext {
|
||||
|
||||
@@ -241,6 +241,8 @@ BuiltinProc__type_simple_boolean_end,
|
||||
BuiltinProc_type_polymorphic_record_parameter_count,
|
||||
BuiltinProc_type_polymorphic_record_parameter_value,
|
||||
|
||||
BuiltinProc_type_is_subtype_of,
|
||||
|
||||
BuiltinProc_type_field_index_of,
|
||||
|
||||
BuiltinProc_type_equal_proc,
|
||||
@@ -250,6 +252,12 @@ BuiltinProc__type_end,
|
||||
|
||||
BuiltinProc___entry_point,
|
||||
|
||||
BuiltinProc_objc_send,
|
||||
BuiltinProc_objc_find_selector,
|
||||
BuiltinProc_objc_find_class,
|
||||
BuiltinProc_objc_register_selector,
|
||||
BuiltinProc_objc_register_class,
|
||||
|
||||
BuiltinProc_COUNT,
|
||||
};
|
||||
gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
|
||||
@@ -491,6 +499,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
|
||||
{STR_LIT("type_polymorphic_record_parameter_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
{STR_LIT("type_polymorphic_record_parameter_value"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("type_is_subtype_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("type_field_index_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("type_equal_proc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
@@ -500,4 +510,11 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
|
||||
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("__entry_point"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("objc_send"), 3, true, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
|
||||
{STR_LIT("objc_find_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
{STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
{STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
{STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||
};
|
||||
|
||||
@@ -122,6 +122,28 @@ enum ProcedureOptimizationMode : u32 {
|
||||
ProcedureOptimizationMode_Speed,
|
||||
};
|
||||
|
||||
|
||||
BlockingMutex global_type_name_objc_metadata_mutex;
|
||||
|
||||
struct TypeNameObjCMetadataEntry {
|
||||
String name;
|
||||
Entity *entity;
|
||||
};
|
||||
struct TypeNameObjCMetadata {
|
||||
BlockingMutex *mutex;
|
||||
Array<TypeNameObjCMetadataEntry> type_entries;
|
||||
Array<TypeNameObjCMetadataEntry> value_entries;
|
||||
};
|
||||
|
||||
TypeNameObjCMetadata *create_type_name_obj_c_metadata() {
|
||||
TypeNameObjCMetadata *md = gb_alloc_item(permanent_allocator(), TypeNameObjCMetadata);
|
||||
md->mutex = gb_alloc_item(permanent_allocator(), BlockingMutex);
|
||||
mutex_init(md->mutex);
|
||||
array_init(&md->type_entries, heap_allocator());
|
||||
array_init(&md->value_entries, heap_allocator());
|
||||
return md;
|
||||
}
|
||||
|
||||
// An Entity is a named "thing" in the language
|
||||
struct Entity {
|
||||
EntityKind kind;
|
||||
@@ -186,6 +208,8 @@ struct Entity {
|
||||
Type * type_parameter_specialization;
|
||||
String ir_mangled_name;
|
||||
bool is_type_alias;
|
||||
String objc_class_name;
|
||||
TypeNameObjCMetadata *objc_metadata;
|
||||
} TypeName;
|
||||
struct {
|
||||
u64 tags;
|
||||
|
||||
+58
-2
@@ -652,7 +652,54 @@ lbProcedure *lb_create_startup_type_info(lbModule *m) {
|
||||
return p;
|
||||
}
|
||||
|
||||
lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *startup_type_info, Array<lbGlobalVariable> &global_variables) { // Startup Runtime
|
||||
lbProcedure *lb_create_objc_names(lbModule *main_module) {
|
||||
if (build_context.metrics.os != TargetOs_darwin) {
|
||||
return nullptr;
|
||||
}
|
||||
Type *proc_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_CDecl);
|
||||
lbProcedure *p = lb_create_dummy_procedure(main_module, str_lit("__$init_objc_names"), proc_type);
|
||||
p->is_startup = true;
|
||||
return p;
|
||||
}
|
||||
|
||||
void lb_finalize_objc_names(lbProcedure *p) {
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
}
|
||||
lbModule *m = p->module;
|
||||
|
||||
LLVMPassManagerRef default_function_pass_manager = LLVMCreateFunctionPassManagerForModule(m->mod);
|
||||
lb_populate_function_pass_manager(m, default_function_pass_manager, false, build_context.optimization_level);
|
||||
LLVMFinalizeFunctionPassManager(default_function_pass_manager);
|
||||
|
||||
|
||||
auto args = array_make<lbValue>(permanent_allocator(), 1);
|
||||
|
||||
LLVMSetLinkage(p->value, LLVMInternalLinkage);
|
||||
lb_begin_procedure_body(p);
|
||||
for_array(i, m->objc_classes.entries) {
|
||||
auto const &entry = m->objc_classes.entries[i];
|
||||
String name = entry.key.string;
|
||||
args[0] = lb_const_value(m, t_cstring, exact_value_string(name));
|
||||
lbValue ptr = lb_emit_runtime_call(p, "objc_lookUpClass", args);
|
||||
lb_addr_store(p, entry.value, ptr);
|
||||
}
|
||||
|
||||
for_array(i, m->objc_selectors.entries) {
|
||||
auto const &entry = m->objc_selectors.entries[i];
|
||||
String name = entry.key.string;
|
||||
args[0] = lb_const_value(m, t_cstring, exact_value_string(name));
|
||||
lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args);
|
||||
lb_addr_store(p, entry.value, ptr);
|
||||
}
|
||||
|
||||
lb_end_procedure_body(p);
|
||||
|
||||
lb_run_function_pass_manager(default_function_pass_manager, p);
|
||||
|
||||
}
|
||||
|
||||
lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *startup_type_info, lbProcedure *objc_names, Array<lbGlobalVariable> &global_variables) { // Startup Runtime
|
||||
LLVMPassManagerRef default_function_pass_manager = LLVMCreateFunctionPassManagerForModule(main_module->mod);
|
||||
lb_populate_function_pass_manager(main_module, default_function_pass_manager, false, build_context.optimization_level);
|
||||
LLVMFinalizeFunctionPassManager(default_function_pass_manager);
|
||||
@@ -666,6 +713,10 @@ lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *start
|
||||
|
||||
LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(main_module, startup_type_info->type)), startup_type_info->value, nullptr, 0, "");
|
||||
|
||||
if (objc_names) {
|
||||
LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(main_module, objc_names->type)), objc_names->value, nullptr, 0, "");
|
||||
}
|
||||
|
||||
for_array(i, global_variables) {
|
||||
auto *var = &global_variables[i];
|
||||
if (var->is_initialized) {
|
||||
@@ -1570,8 +1621,10 @@ void lb_generate_code(lbGenerator *gen) {
|
||||
TIME_SECTION("LLVM Runtime Type Information Creation");
|
||||
lbProcedure *startup_type_info = lb_create_startup_type_info(default_module);
|
||||
|
||||
lbProcedure *objc_names = lb_create_objc_names(default_module);
|
||||
|
||||
TIME_SECTION("LLVM Runtime Startup Creation (Global Variables)");
|
||||
lbProcedure *startup_runtime = lb_create_startup_runtime(default_module, startup_type_info, global_variables);
|
||||
lbProcedure *startup_runtime = lb_create_startup_runtime(default_module, startup_type_info, objc_names, global_variables);
|
||||
gb_unused(startup_runtime);
|
||||
|
||||
TIME_SECTION("LLVM Global Procedures and Types");
|
||||
@@ -1650,6 +1703,8 @@ void lb_generate_code(lbGenerator *gen) {
|
||||
}
|
||||
}
|
||||
|
||||
lb_finalize_objc_names(objc_names);
|
||||
|
||||
if (build_context.ODIN_DEBUG) {
|
||||
TIME_SECTION("LLVM Debug Info Complete Types and Finalize");
|
||||
for_array(j, gen->modules.entries) {
|
||||
@@ -1662,6 +1717,7 @@ void lb_generate_code(lbGenerator *gen) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
TIME_SECTION("LLVM Function Pass");
|
||||
for_array(i, gen->modules.entries) {
|
||||
lbModule *m = gen->modules.entries[i].value;
|
||||
|
||||
@@ -144,6 +144,9 @@ struct lbModule {
|
||||
PtrMap<void *, LLVMMetadataRef> debug_values;
|
||||
|
||||
Array<lbIncompleteDebugType> debug_incomplete_types;
|
||||
|
||||
StringMap<lbAddr> objc_classes;
|
||||
StringMap<lbAddr> objc_selectors;
|
||||
};
|
||||
|
||||
struct lbGenerator {
|
||||
@@ -293,7 +296,6 @@ struct lbProcedure {
|
||||
|
||||
|
||||
bool lb_init_generator(lbGenerator *gen, Checker *c);
|
||||
void lb_generate_module(lbGenerator *gen);
|
||||
|
||||
String lb_mangle_name(lbModule *m, Entity *e);
|
||||
String lb_get_entity_name(lbModule *m, Entity *e, String name = {});
|
||||
@@ -366,7 +368,7 @@ lbContextData *lb_push_context_onto_stack(lbProcedure *p, lbAddr ctx);
|
||||
lbContextData *lb_push_context_onto_stack_from_implicit_parameter(lbProcedure *p);
|
||||
|
||||
|
||||
lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value={});
|
||||
lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value={}, Entity **entity_=nullptr);
|
||||
lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e=nullptr, bool zero_init=true, i32 param_index=0, bool force_no_init=false);
|
||||
|
||||
void lb_add_foreign_library_path(lbModule *m, Entity *e);
|
||||
|
||||
@@ -3303,9 +3303,9 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) {
|
||||
case_end;
|
||||
|
||||
case_ast_node(se, SelectorExpr, expr);
|
||||
Ast *sel = unparen_expr(se->selector);
|
||||
if (sel->kind == Ast_Ident) {
|
||||
String selector = sel->Ident.token.string;
|
||||
Ast *sel_node = unparen_expr(se->selector);
|
||||
if (sel_node->kind == Ast_Ident) {
|
||||
String selector = sel_node->Ident.token.string;
|
||||
TypeAndValue tav = type_and_value_of_expr(se->expr);
|
||||
|
||||
if (tav.mode == Addressing_Invalid) {
|
||||
@@ -3320,7 +3320,12 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) {
|
||||
|
||||
Type *type = base_type(tav.type);
|
||||
if (tav.mode == Addressing_Type) { // Addressing_Type
|
||||
GB_PANIC("Unreachable");
|
||||
Selection sel = lookup_field(tav.type, selector, true);
|
||||
if (sel.pseudo_field) {
|
||||
GB_ASSERT(sel.entity->kind == Entity_Procedure);
|
||||
return lb_addr(lb_find_value_from_entity(p->module, sel.entity));
|
||||
}
|
||||
GB_PANIC("Unreachable %.*s", LIT(selector));
|
||||
}
|
||||
|
||||
if (se->swizzle_count > 0) {
|
||||
@@ -3347,6 +3352,11 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) {
|
||||
|
||||
Selection sel = lookup_field(type, selector, false);
|
||||
GB_ASSERT(sel.entity != nullptr);
|
||||
if (sel.pseudo_field) {
|
||||
GB_ASSERT(sel.entity->kind == Entity_Procedure);
|
||||
Entity *e = entity_of_node(sel_node);
|
||||
return lb_addr(lb_find_value_from_entity(p->module, e));
|
||||
}
|
||||
|
||||
{
|
||||
lbAddr addr = lb_build_addr(p, se->expr);
|
||||
|
||||
@@ -72,6 +72,9 @@ void lb_init_module(lbModule *m, Checker *c) {
|
||||
|
||||
map_init(&m->debug_values, a);
|
||||
array_init(&m->debug_incomplete_types, a, 0, 1024);
|
||||
|
||||
string_map_init(&m->objc_classes, a);
|
||||
string_map_init(&m->objc_selectors, a);
|
||||
}
|
||||
|
||||
bool lb_init_generator(lbGenerator *gen, Checker *c) {
|
||||
@@ -2450,7 +2453,7 @@ lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) {
|
||||
return {};
|
||||
}
|
||||
|
||||
lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value) {
|
||||
lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value, Entity **entity_) {
|
||||
GB_ASSERT(type != nullptr);
|
||||
type = default_type(type);
|
||||
|
||||
@@ -2476,6 +2479,9 @@ lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value) {
|
||||
|
||||
lb_add_entity(m, e, g);
|
||||
lb_add_member(m, name, g);
|
||||
|
||||
if (entity_) *entity_ = e;
|
||||
|
||||
return lb_addr(g);
|
||||
}
|
||||
|
||||
|
||||
@@ -2106,6 +2106,14 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv,
|
||||
res.type = t_uintptr;
|
||||
return res;
|
||||
}
|
||||
|
||||
case BuiltinProc_objc_send:
|
||||
return lb_handle_objc_send(p, expr);
|
||||
|
||||
case BuiltinProc_objc_find_selector: return lb_handle_objc_find_selector(p, expr);
|
||||
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);
|
||||
}
|
||||
|
||||
GB_PANIC("Unhandled built-in procedure %.*s", LIT(builtin_procs[id].name));
|
||||
|
||||
@@ -1819,3 +1819,175 @@ void lb_set_wasm_export_attributes(LLVMValueRef value, String export_name) {
|
||||
LLVMSetVisibility(value, LLVMDefaultVisibility);
|
||||
LLVMAddTargetDependentFunctionAttr(value, "wasm-export-name", alloc_cstring(permanent_allocator(), export_name));
|
||||
}
|
||||
|
||||
|
||||
lbValue lb_lookup_runtime_procedure(lbModule *m, String const &name);
|
||||
|
||||
|
||||
lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) {
|
||||
lbAddr *found = string_map_get(&p->module->objc_selectors, name);
|
||||
if (found) {
|
||||
return *found;
|
||||
} else {
|
||||
lbModule *default_module = &p->module->gen->default_module;
|
||||
Entity *e = nullptr;
|
||||
lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e);
|
||||
|
||||
lbValue ptr = lb_find_value_from_entity(p->module, e);
|
||||
lbAddr local_addr = lb_addr(ptr);
|
||||
|
||||
string_map_set(&default_module->objc_selectors, name, default_addr);
|
||||
if (default_module != p->module) {
|
||||
string_map_set(&p->module->objc_selectors, name, local_addr);
|
||||
}
|
||||
return local_addr;
|
||||
}
|
||||
}
|
||||
|
||||
lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) {
|
||||
ast_node(ce, CallExpr, 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_selector(p, name));
|
||||
}
|
||||
|
||||
lbValue lb_handle_objc_register_selector(lbProcedure *p, Ast *expr) {
|
||||
ast_node(ce, CallExpr, expr);
|
||||
lbModule *m = p->module;
|
||||
|
||||
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_selector(p, name);
|
||||
|
||||
auto args = array_make<lbValue>(permanent_allocator(), 1);
|
||||
args[0] = lb_const_value(m, t_cstring, exact_value_string(name));
|
||||
lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args);
|
||||
lb_addr_store(p, dst, ptr);
|
||||
|
||||
return lb_addr_load(p, dst);
|
||||
}
|
||||
|
||||
lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) {
|
||||
lbAddr *found = string_map_get(&p->module->objc_classes, name);
|
||||
if (found) {
|
||||
return *found;
|
||||
} else {
|
||||
lbModule *default_module = &p->module->gen->default_module;
|
||||
Entity *e = nullptr;
|
||||
lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e);
|
||||
|
||||
lbValue ptr = lb_find_value_from_entity(p->module, e);
|
||||
lbAddr local_addr = lb_addr(ptr);
|
||||
|
||||
string_map_set(&default_module->objc_classes, name, default_addr);
|
||||
if (default_module != p->module) {
|
||||
string_map_set(&p->module->objc_classes, name, local_addr);
|
||||
}
|
||||
return local_addr;
|
||||
}
|
||||
}
|
||||
|
||||
lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) {
|
||||
ast_node(ce, CallExpr, 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));
|
||||
}
|
||||
|
||||
lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) {
|
||||
ast_node(ce, CallExpr, expr);
|
||||
lbModule *m = p->module;
|
||||
|
||||
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);
|
||||
|
||||
auto args = array_make<lbValue>(permanent_allocator(), 3);
|
||||
args[0] = lb_const_nil(m, t_objc_Class);
|
||||
args[1] = lb_const_nil(m, t_objc_Class);
|
||||
args[2] = lb_const_int(m, t_uint, 0);
|
||||
lbValue ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args);
|
||||
lb_addr_store(p, dst, ptr);
|
||||
|
||||
return lb_addr_load(p, dst);
|
||||
}
|
||||
|
||||
|
||||
lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) {
|
||||
TypeAndValue const &tav = type_and_value_of_expr(expr);
|
||||
if (tav.mode == Addressing_Type) {
|
||||
Type *type = tav.type;
|
||||
GB_ASSERT_MSG(type->kind == Type_Named, "%s", type_to_string(type));
|
||||
Entity *e = type->Named.type_name;
|
||||
GB_ASSERT(e->kind == Entity_TypeName);
|
||||
String name = e->TypeName.objc_class_name;
|
||||
|
||||
lbAddr *found = string_map_get(&p->module->objc_classes, name);
|
||||
if (found) {
|
||||
return lb_addr_load(p, *found);
|
||||
} else {
|
||||
lbModule *default_module = &p->module->gen->default_module;
|
||||
Entity *e = nullptr;
|
||||
lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &e);
|
||||
|
||||
lbValue ptr = lb_find_value_from_entity(p->module, e);
|
||||
lbAddr local_addr = lb_addr(ptr);
|
||||
|
||||
string_map_set(&default_module->objc_classes, name, default_addr);
|
||||
if (default_module != p->module) {
|
||||
string_map_set(&p->module->objc_classes, name, local_addr);
|
||||
}
|
||||
return lb_addr_load(p, local_addr);
|
||||
}
|
||||
}
|
||||
|
||||
return lb_build_expr(p, expr);
|
||||
}
|
||||
|
||||
lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) {
|
||||
ast_node(ce, CallExpr, expr);
|
||||
|
||||
lbModule *m = p->module;
|
||||
CheckerInfo *info = m->info;
|
||||
ObjcMsgData data = map_must_get(&info->objc_msgSend_types, expr);
|
||||
GB_ASSERT(data.proc_type != nullptr);
|
||||
|
||||
GB_ASSERT(ce->args.count >= 3);
|
||||
auto args = array_make<lbValue>(permanent_allocator(), 0, ce->args.count-1);
|
||||
|
||||
lbValue id = lb_handle_objc_id(p, ce->args[1]);
|
||||
Ast *sel_expr = ce->args[2];
|
||||
GB_ASSERT(sel_expr->tav.value.kind == ExactValue_String);
|
||||
lbValue sel = lb_addr_load(p, lb_handle_objc_find_or_register_selector(p, sel_expr->tav.value.value_string));
|
||||
|
||||
array_add(&args, id);
|
||||
array_add(&args, sel);
|
||||
for (isize i = 3; i < ce->args.count; i++) {
|
||||
lbValue arg = lb_build_expr(p, ce->args[i]);
|
||||
array_add(&args, arg);
|
||||
}
|
||||
|
||||
|
||||
lbValue the_proc = {};
|
||||
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;
|
||||
}
|
||||
|
||||
the_proc = lb_emit_conv(p, the_proc, data.proc_type);
|
||||
|
||||
return lb_emit_call(p, the_proc, args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-31
@@ -585,37 +585,6 @@ void usage(String argv0) {
|
||||
print_usage_line(1, "e.g. odin build -help");
|
||||
}
|
||||
|
||||
|
||||
bool string_is_valid_identifier(String str) {
|
||||
if (str.len <= 0) return false;
|
||||
|
||||
isize rune_count = 0;
|
||||
|
||||
isize w = 0;
|
||||
isize offset = 0;
|
||||
while (offset < str.len) {
|
||||
Rune r = 0;
|
||||
w = utf8_decode(str.text, str.len, &r);
|
||||
if (r == GB_RUNE_INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rune_count == 0) {
|
||||
if (!rune_is_letter(r)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!rune_is_letter(r) && !rune_is_digit(r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
rune_count += 1;
|
||||
offset += w;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum BuildFlagKind {
|
||||
BuildFlag_Invalid,
|
||||
|
||||
@@ -2448,6 +2417,7 @@ int main(int arg_count, char const **arg_ptr) {
|
||||
virtual_memory_init();
|
||||
mutex_init(&fullpath_mutex);
|
||||
mutex_init(&hash_exact_value_mutex);
|
||||
mutex_init(&global_type_name_objc_metadata_mutex);
|
||||
|
||||
init_string_buffer_memory();
|
||||
init_string_interner();
|
||||
|
||||
@@ -781,3 +781,34 @@ i32 unquote_string(gbAllocator a, String *s_, u8 quote=0, bool has_carriage_retu
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool string_is_valid_identifier(String str) {
|
||||
if (str.len <= 0) return false;
|
||||
|
||||
isize rune_count = 0;
|
||||
|
||||
isize w = 0;
|
||||
isize offset = 0;
|
||||
while (offset < str.len) {
|
||||
Rune r = 0;
|
||||
w = utf8_decode(str.text, str.len, &r);
|
||||
if (r == GB_RUNE_INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rune_count == 0) {
|
||||
if (!rune_is_letter(r)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!rune_is_letter(r) && !rune_is_digit(r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
rune_count += 1;
|
||||
offset += w;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+116
@@ -393,6 +393,7 @@ struct Selection {
|
||||
bool indirect; // Set if there was a pointer deref anywhere down the line
|
||||
u8 swizzle_count; // maximum components = 4
|
||||
u8 swizzle_indices; // 2 bits per component, representing which swizzle index
|
||||
bool pseudo_field;
|
||||
};
|
||||
Selection empty_selection = {0};
|
||||
|
||||
@@ -685,6 +686,16 @@ gb_global Type *t_map_header = nullptr;
|
||||
gb_global Type *t_equal_proc = nullptr;
|
||||
gb_global Type *t_hasher_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_id = nullptr;
|
||||
gb_global Type *t_objc_SEL = nullptr;
|
||||
gb_global Type *t_objc_Class = nullptr;
|
||||
|
||||
|
||||
|
||||
gb_global RecursiveMutex g_type_mutex;
|
||||
|
||||
struct TypePath;
|
||||
@@ -2772,6 +2783,7 @@ Selection lookup_field_from_index(Type *type, i64 index) {
|
||||
}
|
||||
|
||||
Entity *scope_lookup_current(Scope *s, String const &name);
|
||||
bool has_type_got_objc_class_attribute(Type *t);
|
||||
|
||||
Selection lookup_field_with_selection(Type *type_, String field_name, bool is_type, Selection sel, bool allow_blank_ident) {
|
||||
GB_ASSERT(type_ != nullptr);
|
||||
@@ -2784,9 +2796,40 @@ Selection lookup_field_with_selection(Type *type_, String field_name, bool is_ty
|
||||
bool is_ptr = type != type_;
|
||||
sel.indirect = sel.indirect || is_ptr;
|
||||
|
||||
Type *original_type = type;
|
||||
|
||||
type = base_type(type);
|
||||
|
||||
if (is_type) {
|
||||
if (has_type_got_objc_class_attribute(original_type) && original_type->kind == Type_Named) {
|
||||
Entity *e = original_type->Named.type_name;
|
||||
GB_ASSERT(e->kind == Entity_TypeName);
|
||||
if (e->TypeName.objc_metadata) {
|
||||
auto *md = e->TypeName.objc_metadata;
|
||||
mutex_lock(md->mutex);
|
||||
defer (mutex_unlock(md->mutex));
|
||||
for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
|
||||
GB_ASSERT(entry.entity->kind == Entity_Procedure);
|
||||
if (entry.name == field_name) {
|
||||
sel.entity = entry.entity;
|
||||
sel.pseudo_field = true;
|
||||
return sel;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type->kind == Type_Struct) {
|
||||
for_array(i, type->Struct.fields) {
|
||||
Entity *f = type->Struct.fields[i];
|
||||
if (f->flags&EntityFlag_Using) {
|
||||
sel = lookup_field_with_selection(f->type, field_name, is_type, sel, allow_blank_ident);
|
||||
if (sel.entity) {
|
||||
return sel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_type_enum(type)) {
|
||||
// NOTE(bill): These may not have been added yet, so check in case
|
||||
for_array(i, type->Enum.fields) {
|
||||
@@ -2833,6 +2876,24 @@ Selection lookup_field_with_selection(Type *type_, String field_name, bool is_ty
|
||||
} else if (type->kind == Type_Union) {
|
||||
|
||||
} else if (type->kind == Type_Struct) {
|
||||
if (has_type_got_objc_class_attribute(original_type) && original_type->kind == Type_Named) {
|
||||
Entity *e = original_type->Named.type_name;
|
||||
GB_ASSERT(e->kind == Entity_TypeName);
|
||||
if (e->TypeName.objc_metadata) {
|
||||
auto *md = e->TypeName.objc_metadata;
|
||||
mutex_lock(md->mutex);
|
||||
defer (mutex_unlock(md->mutex));
|
||||
for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
|
||||
GB_ASSERT(entry.entity->kind == Entity_Procedure);
|
||||
if (entry.name == field_name) {
|
||||
sel.entity = entry.entity;
|
||||
sel.pseudo_field = true;
|
||||
return sel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for_array(i, type->Struct.fields) {
|
||||
Entity *f = type->Struct.fields[i];
|
||||
if (f->kind != Entity_Variable || (f->flags & EntityFlag_Field) == 0) {
|
||||
@@ -3738,6 +3799,61 @@ i64 type_offset_of_from_selection(Type *type, Selection sel) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
isize check_is_assignable_to_using_subtype(Type *src, Type *dst, isize level = 0, bool src_is_ptr = false) {
|
||||
Type *prev_src = src;
|
||||
src = type_deref(src);
|
||||
if (!src_is_ptr) {
|
||||
src_is_ptr = src != prev_src;
|
||||
}
|
||||
src = base_type(src);
|
||||
|
||||
if (!is_type_struct(src)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for_array(i, src->Struct.fields) {
|
||||
Entity *f = src->Struct.fields[i];
|
||||
if (f->kind != Entity_Variable || (f->flags&EntityFlag_Using) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (are_types_identical(f->type, dst)) {
|
||||
return level+1;
|
||||
}
|
||||
if (src_is_ptr && is_type_pointer(dst)) {
|
||||
if (are_types_identical(f->type, type_deref(dst))) {
|
||||
return level+1;
|
||||
}
|
||||
}
|
||||
isize nested_level = check_is_assignable_to_using_subtype(f->type, dst, level+1, src_is_ptr);
|
||||
if (nested_level > 0) {
|
||||
return nested_level;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool is_type_subtype_of(Type *src, Type *dst) {
|
||||
if (are_types_identical(src, dst)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 0 < check_is_assignable_to_using_subtype(src, dst, 0, is_type_pointer(src));
|
||||
}
|
||||
|
||||
|
||||
bool has_type_got_objc_class_attribute(Type *t) {
|
||||
return t->kind == Type_Named && t->Named.type_name != nullptr && t->Named.type_name->TypeName.objc_class_name != "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool is_type_objc_object(Type *t) {
|
||||
bool internal_check_is_assignable_to(Type *src, Type *dst);
|
||||
|
||||
return internal_check_is_assignable_to(t, t_objc_object);
|
||||
}
|
||||
|
||||
Type *get_struct_field_type(Type *t, isize index) {
|
||||
t = base_type(type_deref(t));
|
||||
|
||||
Reference in New Issue
Block a user