From a36a8722dc823c6fe143f7935e79467c6569bc00 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 19:30:32 +0100 Subject: Minimize more thread contention --- src/check_decl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 7dd9db105..49731ad60 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1981,9 +1981,9 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de ast_node(bs, BlockStmt, body); + TEMPORARY_ALLOCATOR_GUARD(); Array using_entities = {}; - using_entities.allocator = heap_allocator(); - defer (array_free(&using_entities)); + using_entities.allocator = temporary_allocator(); { if (type->Proc.param_count > 0) { -- cgit v1.2.3 From bc36ea41706f9773b031e161cbb6ece6da3d4250 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 20:11:36 +0100 Subject: Use macro instead of a C++ iterator - for speed C++ iterators are bad. --- src/check_decl.cpp | 2 +- src/check_expr.cpp | 2 +- src/checker.cpp | 36 ++++++++++++++---------------------- src/ptr_set.cpp | 9 +++++---- 4 files changed, 21 insertions(+), 28 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 49731ad60..ff888b74e 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1915,7 +1915,7 @@ gb_internal void add_deps_from_child_to_parent(DeclInfo *decl) { rw_mutex_shared_lock(&decl->deps_mutex); rw_mutex_lock(&decl->parent->deps_mutex); - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { ptr_set_add(&decl->parent->deps, e); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index dad9e8348..012c50270 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6028,7 +6028,7 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize } rw_mutex_shared_lock(&decl->deps_mutex); rw_mutex_lock(&c->decl->deps_mutex); - for (Entity *dep : decl->deps) { + FOR_PTR_SET(dep, decl->deps) { ptr_set_add(&c->decl->deps, dep); } rw_mutex_unlock(&c->decl->deps_mutex); diff --git a/src/checker.cpp b/src/checker.cpp index dd4c0085a..934e6282b 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2584,7 +2584,7 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { for (TypeInfoPair const tt : decl->type_info_deps) { add_min_dep_type_info(c, tt.type); } - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { switch (e->kind) { case Entity_Procedure: if (e->Procedure.is_foreign) { @@ -2611,7 +2611,7 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { } } - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { add_dependency_to_set(c, e); } @@ -2643,7 +2643,7 @@ gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { add_min_dep_type_info(c, tt.type); } - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { switch (e->kind) { case Entity_Procedure: if (e->Procedure.is_foreign) { @@ -2670,7 +2670,7 @@ gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { } } - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { add_dependency_to_set_threaded(c, e); } @@ -3047,9 +3047,7 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf DeclInfo *decl = decl_info_of_entity(e); GB_ASSERT(decl != nullptr); - FOR_PTR_SET(i_dep, decl->deps) { - Entity *dep = decl->deps.keys[i_dep]; - + FOR_PTR_SET(dep, decl->deps) { if (dep->flags & EntityFlag_Field) { continue; } @@ -3080,21 +3078,14 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf // Connect each pred 'p' of 'n' with each succ 's' and from // the procedure node - FOR_PTR_SET(i_p, n->pred) { - EntityGraphNode *p = n->pred.keys[i_p]; - + FOR_PTR_SET(p, n->pred) { // Ignore self-cycles if (p == n) { continue; } // Each succ 's' of 'n' becomes a succ of 'p', and // each pred 'p' of 'n' becomes a pred of 's' - FOR_PTR_SET(i_s, n->succ) { - EntityGraphNode *s = n->succ.keys[i_s]; - if (s == nullptr || s == cast(EntityGraphNode *)PtrSet::TOMBSTONE) { - // NOTE(bill): This is inlined to improve development build performance - continue; - } + FOR_PTR_SET(s, n->succ) { // Ignore self-cycles if (s == n) { continue; @@ -5859,7 +5850,7 @@ gb_internal void check_import_entities(Checker *c) { } } - for (ImportGraphNode *p : n->pred) { + FOR_PTR_SET(p, n->pred) { p->dep_count = gb_max(p->dep_count-1, 0); priority_queue_fix(&pq, p->index); } @@ -5976,7 +5967,8 @@ gb_internal bool find_entity_path_tuple(Type *tuple, Entity *end, gbAllocator al if (var_decl == nullptr) { continue; } - for (Entity *dep : var_decl->deps) { + + FOR_PTR_SET(dep, var_decl->deps) { if (dep == end) { auto path = array_make(allocator); array_add(&path, dep); @@ -6026,7 +6018,7 @@ gb_internal Array find_entity_path(Entity *start, Entity *end, gbAlloc return path; } } else { - for (Entity *dep : decl->deps) { + FOR_PTR_SET(dep, decl->deps) { if (dep == end) { auto path = array_make(allocator); array_add(&path, dep); @@ -6080,7 +6072,7 @@ gb_internal void calculate_global_init_order(Checker *c) { } } - for (EntityGraphNode *p : n->pred) { + FOR_PTR_SET(p, n->pred) { p->dep_count -= 1; p->dep_count = gb_max(p->dep_count, 0); priority_queue_fix(&pq, p->index); @@ -6273,7 +6265,7 @@ gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *u add_untyped_expressions(&c->info, ctx.untyped); rw_mutex_shared_lock(&ctx.decl->deps_mutex); - for (Entity *dep : ctx.decl->deps) { + FOR_PTR_SET(dep, ctx.decl->deps) { if (dep && dep->kind == Entity_Procedure && (dep->flags & EntityFlag_ProcBodyChecked) == 0) { check_procedure_later_from_entity(c, dep, NULL); @@ -7346,7 +7338,7 @@ gb_internal void check_parsed_files(Checker *c) { DeclInfo *decl = e->decl_info; ast_node(pl, ProcLit, decl->proc_lit); if (pl->inlining == ProcInlining_inline) { - for (Entity *dep : decl->deps) { + FOR_PTR_SET(dep, decl->deps) { if (dep == e) { error(e->token, "Cannot inline recursive procedure '%.*s'", LIT(e->token.string)); break; diff --git a/src/ptr_set.cpp b/src/ptr_set.cpp index 512a157d0..5b1d2cc19 100644 --- a/src/ptr_set.cpp +++ b/src/ptr_set.cpp @@ -16,6 +16,8 @@ template gb_internal bool ptr_set_exists (PtrSet *s, T ptr); template gb_internal void ptr_set_remove (PtrSet *s, T ptr); template gb_internal void ptr_set_clear (PtrSet *s); +#define FOR_PTR_SET(element, set_) for (auto *it = &(set_).keys[0], element = it ? *it : nullptr; (set_).keys != nullptr && it < &(set_).keys[(set_).capacity]; it++) if (element = *it, (*it != nullptr && *it != cast(void *)~(uintptr)(0ull))) + gb_internal gbAllocator ptr_set_allocator(void) { return heap_allocator(); } @@ -83,7 +85,7 @@ gb_internal gb_inline void ptr_set_grow(PtrSet *old_set) { PtrSet new_set = {}; ptr_set_init(&new_set, gb_max(old_set->capacity<<1, 16)); - for (T ptr : *old_set) { + FOR_PTR_SET(ptr, *old_set) { bool was_new = ptr_set_update(&new_set, ptr); GB_ASSERT(!was_new); } @@ -195,7 +197,7 @@ gb_internal gb_inline void ptr_set_clear(PtrSet *s) { gb_zero_size(s->keys, s->capacity*gb_size_of(T)); } -template +/*template struct PtrSetIterator { PtrSet *set; usize index; @@ -239,7 +241,6 @@ gb_internal PtrSetIterator begin(PtrSet &set) noexcept { template gb_internal PtrSetIterator end(PtrSet &set) noexcept { return PtrSetIterator{&set, set.capacity}; -} +}*/ -#define FOR_PTR_SET(index_, set_) for (usize index_ = 0; index_ < (set_).capacity; index_++) if ((set_).keys[index_] != nullptr && (set_).keys[index_] != cast(void *)~(uintptr)(0ull)) \ No newline at end of file -- cgit v1.2.3 From 992cad101c495e9a0c96a46c0eb50f6a59c7e68f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 21:16:56 +0100 Subject: Minor mutex rearrangement --- src/check_decl.cpp | 4 ++-- src/queue.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index ff888b74e..58e4f0120 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -162,8 +162,6 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e if (found_scope == nullptr) { return; } - rw_mutex_lock(&found_scope->mutex); - defer (rw_mutex_unlock(&found_scope->mutex)); // IMPORTANT NOTE(bill, 2021-04-10): Overriding behaviour was flawed in that the // original entity was still used check checked, but the checking was only @@ -172,7 +170,9 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e // Therefore two things can be done: the type can be assigned to state that it // has been "evaluated" and the variant data can be copied across + rw_mutex_lock(&found_scope->mutex); string_map_set(&found_scope->elements, original_name, new_entity); + rw_mutex_unlock(&found_scope->mutex); original_entity->flags |= EntityFlag_Overridden; original_entity->type = new_entity->type; diff --git a/src/queue.cpp b/src/queue.cpp index dee9ad1f8..82f82f3e1 100644 --- a/src/queue.cpp +++ b/src/queue.cpp @@ -36,7 +36,8 @@ gb_internal void mpsc_destroy(MPSCQueue *q) { template gb_internal MPSCNode *mpsc_alloc_node(MPSCQueue *q, T const &value) { - auto new_node = gb_alloc_item(heap_allocator(), MPSCNode); + // auto new_node = gb_alloc_item(heap_allocator(), MPSCNode); + auto new_node = gb_alloc_item(permanent_allocator(), MPSCNode); new_node->value = value; return new_node; } -- cgit v1.2.3 From 5ea2e1fe60872c5d2b20e180a1514a082b6513e4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 21:41:52 +0100 Subject: Minimize mutex usage when in single threaded mode. --- src/check_decl.cpp | 2 +- src/check_expr.cpp | 10 +++++----- src/check_stmt.cpp | 12 +++++++----- src/checker.cpp | 39 +++++++++++++++++++++++++-------------- src/checker.hpp | 4 ++-- src/parser.cpp | 1 + src/parser.hpp | 1 + 7 files changed, 42 insertions(+), 27 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 58e4f0120..8fbcb5e40 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -948,7 +948,7 @@ gb_internal Entity *init_entity_foreign_library(CheckerContext *ctx, Entity *e) error(ident, "foreign library names must be an identifier"); } else { String name = ident->Ident.token.string; - Entity *found = scope_lookup(ctx->scope, name); + Entity *found = scope_lookup(ctx->scope, name, ident->Ident.hash); if (found == nullptr) { if (is_blank_ident(name)) { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index abfd11485..d2505c047 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1738,7 +1738,7 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam o->expr = n; String name = n->Ident.token.string; - Entity *e = scope_lookup(c->scope, name); + Entity *e = scope_lookup(c->scope, name, n->Ident.hash); if (e == nullptr) { if (is_blank_ident(name)) { error(n, "'_' cannot be used as a value"); @@ -5361,7 +5361,7 @@ gb_internal Entity *check_entity_from_ident_or_selector(CheckerContext *c, Ast * } } else */if (node->kind == Ast_Ident) { String name = node->Ident.token.string; - return scope_lookup(c->scope, name); + return scope_lookup(c->scope, name, node->Ident.hash); } else if (!ident_only) if (node->kind == Ast_SelectorExpr) { ast_node(se, SelectorExpr, node); if (se->token.kind == Token_ArrowRight) { @@ -5383,7 +5383,7 @@ gb_internal Entity *check_entity_from_ident_or_selector(CheckerContext *c, Ast * if (op_expr->kind == Ast_Ident) { String op_name = op_expr->Ident.token.string; - Entity *e = scope_lookup(c->scope, op_name); + Entity *e = scope_lookup(c->scope, op_name, op_expr->Ident.hash); if (e == nullptr) { return nullptr; } @@ -5480,7 +5480,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod if (op_expr->kind == Ast_Ident) { String op_name = op_expr->Ident.token.string; - Entity *e = scope_lookup(c->scope, op_name); + Entity *e = scope_lookup(c->scope, op_name, op_expr->Ident.hash); add_entity_use(c, op_expr, e); expr_entity = e; @@ -5903,7 +5903,7 @@ gb_internal bool check_identifier_exists(Scope *s, Ast *node, bool nested = fals return true; } } else { - Entity *e = scope_lookup(s, name); + Entity *e = scope_lookup(s, name, i->hash); if (e != nullptr) { if (out_scope) *out_scope = e->scope; return true; diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index ba9c08fed..62cfc256a 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -484,7 +484,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O } if (ident_node != nullptr) { ast_node(i, Ident, ident_node); - e = scope_lookup(ctx->scope, i->token.string); + e = scope_lookup(ctx->scope, i->token.string, i->hash); if (e != nullptr && e->kind == Entity_Variable) { used = (e->flags & EntityFlag_Used) != 0; // NOTE(bill): Make backup just in case } @@ -1812,8 +1812,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) error(node, "Iteration over a bit_set of an enum is not allowed runtime type information (RTTI) has been disallowed"); } if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) { - String name = rs->vals[0]->Ident.token.string; - Entity *found = scope_lookup(ctx->scope, name); + AstIdent *ident = &rs->vals[0]->Ident; + String name = ident->token.string; + Entity *found = scope_lookup(ctx->scope, name, ident->hash); if (found && are_types_identical(found->type, t->BitSet.elem)) { ERROR_BLOCK(); gbString s = expr_to_string(expr); @@ -1857,8 +1858,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) error(node, "#reverse for is not supported for map types, as maps are unordered"); } if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) { - String name = rs->vals[0]->Ident.token.string; - Entity *found = scope_lookup(ctx->scope, name); + AstIdent *ident = &rs->vals[0]->Ident; + String name = ident->token.string; + Entity *found = scope_lookup(ctx->scope, name, ident->hash); if (found && are_types_identical(found->type, t->Map.key)) { ERROR_BLOCK(); gbString s = expr_to_string(expr); diff --git a/src/checker.cpp b/src/checker.cpp index eb334e147..7da3948dc 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -380,16 +380,25 @@ gb_internal Entity *scope_lookup_current(Scope *s, String const &name) { } -gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) { +gb_global std::atomic in_single_threaded_checker_stage; + +gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_, u32 hash) { + bool is_single_threaded = in_single_threaded_checker_stage.load(std::memory_order_relaxed); if (scope != nullptr) { bool gone_thru_proc = false; bool gone_thru_package = false; - StringHashKey key = string_hash_string(name); + StringHashKey key = {}; + if (hash) { + key.hash = hash; + key.string = name; + } else { + key = string_hash_string(name); + } for (Scope *s = scope; s != nullptr; s = s->parent) { Entity **found = nullptr; - rw_mutex_shared_lock(&s->mutex); + if (!is_single_threaded) rw_mutex_shared_lock(&s->mutex); found = string_map_get(&s->elements, key); - rw_mutex_shared_unlock(&s->mutex); + if (!is_single_threaded) rw_mutex_shared_unlock(&s->mutex); if (found) { Entity *e = *found; if (gone_thru_proc) { @@ -424,9 +433,9 @@ gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **s if (scope_) *scope_ = nullptr; } -gb_internal Entity *scope_lookup(Scope *s, String const &name) { +gb_internal Entity *scope_lookup(Scope *s, String const &name, u32 hash) { Entity *entity = nullptr; - scope_lookup_parent(s, name, nullptr, &entity); + scope_lookup_parent(s, name, nullptr, &entity, hash); return entity; } @@ -507,11 +516,9 @@ end:; return result; } -gb_global bool in_single_threaded_checker_stage = false; - gb_internal Entity *scope_insert(Scope *s, Entity *entity) { String name = entity->token.string; - if (in_single_threaded_checker_stage) { + if (in_single_threaded_checker_stage.load(std::memory_order_relaxed)) { return scope_insert_with_name_no_mutex(s, name, entity); } else { return scope_insert_with_name(s, name, entity); @@ -853,9 +860,13 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { gb_internal void add_dependency(CheckerInfo *info, DeclInfo *d, Entity *e) { - rw_mutex_lock(&d->deps_mutex); - ptr_set_add(&d->deps, e); - rw_mutex_unlock(&d->deps_mutex); + if (in_single_threaded_checker_stage.load(std::memory_order_relaxed)) { + ptr_set_add(&d->deps, e); + } else { + rw_mutex_lock(&d->deps_mutex); + ptr_set_add(&d->deps, e); + rw_mutex_unlock(&d->deps_mutex); + } } gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type *type) { if (d == nullptr || type == nullptr) { @@ -4958,7 +4969,7 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) } gb_internal void check_all_global_entities(Checker *c) { - in_single_threaded_checker_stage = true; + in_single_threaded_checker_stage.store(true, std::memory_order_relaxed); // NOTE(bill): This must be single threaded // Don't bother trying @@ -4980,7 +4991,7 @@ gb_internal void check_all_global_entities(Checker *c) { } } - in_single_threaded_checker_stage = false; + in_single_threaded_checker_stage.store(false, std::memory_order_relaxed); } diff --git a/src/checker.hpp b/src/checker.hpp index 968988962..68779c280 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -623,8 +623,8 @@ gb_internal Entity *entity_of_node(Ast *expr); gb_internal Entity *scope_lookup_current(Scope *s, String const &name); -gb_internal Entity *scope_lookup (Scope *s, String const &name); -gb_internal void scope_lookup_parent (Scope *s, String const &name, Scope **scope_, Entity **entity_); +gb_internal Entity *scope_lookup (Scope *s, String const &name, u32 hash=0); +gb_internal void scope_lookup_parent (Scope *s, String const &name, Scope **scope_, Entity **entity_, u32 hash=0); gb_internal Entity *scope_insert (Scope *s, Entity *entity); diff --git a/src/parser.cpp b/src/parser.cpp index a7006dd39..a32494c05 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -750,6 +750,7 @@ gb_internal Ast *ast_matrix_index_expr(AstFile *f, Ast *expr, Token open, Token gb_internal Ast *ast_ident(AstFile *f, Token token) { Ast *result = alloc_ast_node(f, Ast_Ident); result->Ident.token = token; + result->Ident.hash = string_hash(token.string); return result; } diff --git a/src/parser.hpp b/src/parser.hpp index d2dd22667..56447df43 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -396,6 +396,7 @@ struct AstSplitArgs { AST_KIND(Ident, "identifier", struct { \ Token token; \ Entity *entity; \ + u32 hash; \ }) \ AST_KIND(Implicit, "implicit", Token) \ AST_KIND(Uninit, "uninitialized value", Token) \ -- cgit v1.2.3 From 6ce889f4ebf10d44fc6c1e5fba794e412dfcf183 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 11:01:41 +0100 Subject: `Entity *` to `std::atomic` to remove the need for a PtrMap+Mutex --- src/check_decl.cpp | 8 ++++---- src/check_expr.cpp | 9 +++++---- src/checker.cpp | 2 +- src/checker.hpp | 2 +- src/llvm_backend.hpp | 3 --- src/llvm_backend_general.cpp | 21 +++++++++------------ src/llvm_backend_proc.cpp | 2 +- src/llvm_backend_type.cpp | 5 +++-- 8 files changed, 24 insertions(+), 28 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 8fbcb5e40..092222d3c 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1549,7 +1549,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { "\tother at %s", LIT(name), token_pos_to_string(pos)); } else if (name == "main") { - if (d->entity->pkg->kind != Package_Runtime) { + if (d->entity.load()->pkg->kind != Package_Runtime) { error(d->proc_lit, "The link name 'main' is reserved for internal use"); } } else { @@ -1967,8 +1967,8 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de ctx->curr_proc_sig = type; ctx->curr_proc_calling_convention = type->Proc.calling_convention; - if (decl->parent && decl->entity && decl->parent->entity) { - decl->entity->parent_proc_decl = decl->parent; + if (decl->parent && decl->entity.load() && decl->parent->entity) { + decl->entity.load()->parent_proc_decl = decl->parent; } if (ctx->pkg->name != "runtime") { @@ -2072,7 +2072,7 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de GB_ASSERT(decl->proc_checked_state != ProcCheckedState_Checked); if (decl->defer_use_checked) { GB_ASSERT(is_type_polymorphic(type, true)); - error(token, "Defer Use Checked: %.*s", LIT(decl->entity->token.string)); + error(token, "Defer Use Checked: %.*s", LIT(decl->entity.load()->token.string)); GB_ASSERT(decl->defer_use_checked == false); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d2505c047..d863d6cf6 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -608,7 +608,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E entity->flags |= EntityFlag_Disabled; } - d->entity = entity; + d->entity.store(entity); AstFile *file = nullptr; { @@ -8335,9 +8335,10 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c if (c->curr_proc_decl == nullptr) { error(call, "Calling a '#force_inline' procedure that enables target features is not allowed at file scope"); } else { - GB_ASSERT(c->curr_proc_decl->entity); - GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); - String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; + Entity *e = c->curr_proc_decl->entity.load(); + GB_ASSERT(e); + GB_ASSERT(e->type->kind == Type_Proc); + String scope_features = e->type->Proc.enable_target_feature; if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) { ERROR_BLOCK(); error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); diff --git a/src/checker.cpp b/src/checker.cpp index 7da3948dc..32bda2e43 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2055,8 +2055,8 @@ gb_internal void add_entity_and_decl_info(CheckerContext *c, Ast *identifier, En add_entity_definition(info, identifier, e); GB_ASSERT(e->decl_info == nullptr); e->decl_info = d; - d->entity = e; e->pkg = c->pkg; + d->entity.store(e); isize queue_count = -1; bool is_lazy = false; diff --git a/src/checker.hpp b/src/checker.hpp index 68779c280..d22b99e89 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -209,7 +209,7 @@ struct DeclInfo { Scope * scope; - Entity *entity; + std::atomic entity; Ast * decl_node; Ast * type_expr; diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 370a6f2ca..7fe4651bb 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -238,9 +238,6 @@ struct lbGenerator : LinkerData { PtrMap modules_through_ctx; lbModule default_module; - RecursiveMutex anonymous_proc_lits_mutex; - PtrMap anonymous_proc_lits; - isize used_module_count; lbProcedure *startup_runtime; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 64d574920..129634ee6 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -157,7 +157,6 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_init(&gen->modules, gen->info->packages.count*2); map_init(&gen->modules_through_ctx, gen->info->packages.count*2); - map_init(&gen->anonymous_proc_lits, 1024); if (USE_SEPARATE_MODULES) { bool module_per_file = build_context.module_per_file && build_context.optimization_level <= 0; @@ -3084,18 +3083,14 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &prefix_name, Ast *expr, lbProcedure *parent) { - lbGenerator *gen = m->gen; - - mutex_lock(&gen->anonymous_proc_lits_mutex); - defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); + // lbGenerator *gen = m->gen; + ast_node(pl, ProcLit, expr); - TokenPos pos = ast_token(expr).pos; - lbProcedure **found = map_get(&gen->anonymous_proc_lits, expr); - if (found) { - return lb_find_procedure_value_from_entity(m, (*found)->entity); + if (pl->decl->entity.load() != nullptr) { + return lb_find_procedure_value_from_entity(m, pl->decl->entity.load()); } - ast_node(pl, ProcLit, expr); + TokenPos pos = ast_token(expr).pos; // NOTE(bill): Generate a new name // parent$count @@ -3114,15 +3109,18 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr token.string = name; Entity *e = alloc_entity_procedure(nullptr, token, type, pl->tags); e->file = expr->file(); + e->pkg = e->file->pkg; + e->scope = e->file->scope; // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time pl->decl->code_gen_module = m; e->decl_info = pl->decl; - pl->decl->entity = e; e->parent_proc_decl = pl->decl->parent; e->Procedure.is_anonymous = true; e->flags |= EntityFlag_ProcBodyChecked; + pl->decl->entity.store(e); + lbProcedure *p = lb_create_procedure(m, e); GB_ASSERT(e->code_gen_module == m); @@ -3130,7 +3128,6 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr value.value = p->value; value.type = p->type; - map_set(&gen->anonymous_proc_lits, expr, p); array_add(&m->procedures_to_generate, p); if (parent != nullptr) { array_add(&parent->children, p); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 20d627fa2..f71b693eb 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2211,7 +2211,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu GB_ASSERT(e != nullptr); if (e->parent_proc_decl != nullptr && e->parent_proc_decl->entity != nullptr) { - procedure = e->parent_proc_decl->entity->token.string; + procedure = e->parent_proc_decl->entity.load()->token.string; } else { procedure = str_lit(""); } diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index d1e7c0559..b2eec218f 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -394,8 +394,9 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ String proc_name = {}; if (t->Named.type_name->parent_proc_decl) { DeclInfo *decl = t->Named.type_name->parent_proc_decl; - if (decl->entity && decl->entity->kind == Entity_Procedure) { - proc_name = decl->entity->token.string; + Entity *e = decl->entity.load(); + if (e && e->kind == Entity_Procedure) { + proc_name = e->token.string; } } TokenPos pos = t->Named.type_name->token.pos; -- cgit v1.2.3 From 6338e0a8a3db948624817c99c431e5889afc636a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 11:56:44 +0100 Subject: Allow unions with one variant to be constant --- src/check_decl.cpp | 2 +- src/check_expr.cpp | 28 +++++++++++++++++++++++++++- src/llvm_backend_const.cpp | 43 ++++++++++++++++++++++++++++++++++++++++++- src/llvm_backend_general.cpp | 13 ++++++++++++- 4 files changed, 82 insertions(+), 4 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 092222d3c..bda1059fb 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1565,7 +1565,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } } -gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, Ast *init_expr) { +gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init_expr) { GB_ASSERT(e->type == nullptr); GB_ASSERT(e->kind == Entity_Variable); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d863d6cf6..0ea0952f9 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3500,6 +3500,21 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type return false; } +gb_internal bool is_type_union_constantable(Type *type) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_Union); + + + if (bt->Union.variants.count <= 1) { + return true; + } + + // for (Type *v : bt->Union.variants) { + + // } + return false; +} + gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) { bool is_const_expr = x->mode == Addressing_Constant; @@ -3524,6 +3539,9 @@ gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) } else if (is_type_slice(type) && is_type_string(x->type)) { x->mode = Addressing_Value; } else if (is_type_union(type)) { + if (is_type_union_constantable(type)) { + return true; + } x->mode = Addressing_Value; } if (x->mode == Addressing_Value) { @@ -3582,7 +3600,11 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb Type *final_type = type; if (is_const_expr && !is_type_constant_type(type)) { if (is_type_union(type)) { - convert_to_typed(c, x, type); + if (is_type_union_constantable(type)) { + + } else { + convert_to_typed(c, x, type); + } } final_type = default_type(x->type); } @@ -8151,7 +8173,11 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } else { // NOTE(bill): Otherwise the compiler can override the polymorphic type // as it assumes it is determining the type + AddressingMode old_mode = operand->mode; check_cast(c, operand, t); + if (old_mode == Addressing_Constant && old_mode != operand->mode) { + gb_printf_err("HERE: %d -> %d %s\n", old_mode, operand->mode, expr_to_string(operand->expr)); + } } } operand->type = t; diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index e64be49f2..98e50dd78 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -168,7 +168,7 @@ gb_internal LLVMValueRef llvm_const_named_struct(lbModule *m, Type *t, LLVMValue return llvm_const_named_struct_internal(struct_type, values, value_count_); } Type *bt = base_type(t); - GB_ASSERT(bt->kind == Type_Struct); + GB_ASSERT(bt->kind == Type_Struct || bt->kind == Type_Union); GB_ASSERT(value_count_ == bt->Struct.fields.count); @@ -585,6 +585,47 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb bool is_local = cc.allow_local && m->curr_procedure != nullptr; + if (is_type_union(type) && is_type_union_constantable(type)) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_Union); + GB_ASSERT(bt->Union.variants.count <= 1); + if (bt->Union.variants.count == 0) { + return lb_const_nil(m, original_type); + } else if (bt->Union.variants.count == 1) { + Type *t = bt->Union.variants[0]; + lbValue cv = lb_const_value(m, t, value, cc); + GB_ASSERT(LLVMIsConstant(cv.value)); + + LLVMTypeRef llvm_type = lb_type(m, original_type); + + if (is_type_union_maybe_pointer(type)) { + LLVMValueRef values[1] = {cv.value}; + res.value = llvm_const_named_struct_internal(llvm_type, values, 1); + res.type = original_type; + return res; + } else { + + unsigned tag_value = 1; + if (bt->Union.kind == UnionType_no_nil) { + tag_value = 0; + } + LLVMValueRef tag = LLVMConstInt(LLVMStructGetTypeAtIndex(llvm_type, 1), tag_value, false); + LLVMValueRef padding = nullptr; + LLVMValueRef values[3] = {cv.value, tag, padding}; + + isize value_count = 2; + if (LLVMCountStructElementTypes(llvm_type) > 2) { + value_count = 3; + padding = LLVMConstNull(LLVMStructGetTypeAtIndex(llvm_type, 2)); + } + res.value = llvm_const_named_struct_internal(llvm_type, values, value_count); + res.type = original_type; + return res; + } + } + + } + // GB_ASSERT_MSG(is_type_typed(type), "%s", type_to_string(type)); if (is_type_slice(type)) { diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 57eb869fa..cae08ec2f 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -2226,6 +2226,18 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { if (is_type_union_maybe_pointer(type)) { LLVMTypeRef variant = lb_type(m, type->Union.variants[0]); array_add(&fields, variant); + } else if (type->Union.variants.count == 1) { + LLVMTypeRef block_type = lb_type(m, type->Union.variants[0]); + + LLVMTypeRef tag_type = lb_type(m, union_tag_type(type)); + array_add(&fields, block_type); + array_add(&fields, tag_type); + i64 used_size = lb_sizeof(block_type) + lb_sizeof(tag_type); + i64 padding = size - used_size; + if (padding > 0) { + LLVMTypeRef padding_type = lb_type_padding_filler(m, padding, align); + array_add(&fields, padding_type); + } } else { LLVMTypeRef block_type = nullptr; @@ -3131,7 +3143,6 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr lbValue *found = map_get(&target_module->values, e); rw_mutex_shared_unlock(&target_module->values_mutex); if (found == nullptr) { - // THIS IS THE RACE CONDITION lbProcedure *missing_proc_in_target_module = lb_create_procedure(target_module, e, false); array_add(&target_module->missing_procedures_to_check, missing_proc_in_target_module); } -- cgit v1.2.3 From 8be18d9a401f898320b57379488bad1367632628 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 21:47:56 +0100 Subject: Allow for constant `[]typeid` --- src/check_decl.cpp | 21 +++++++++++++++++++++ src/check_expr.cpp | 27 +++++++++++++++------------ src/types.cpp | 30 +++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 17 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index bda1059fb..842f8653c 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1686,7 +1686,28 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *e, Ast check_expr_with_type_hint(ctx, &o, init_expr, e->type); check_init_variable(ctx, e, &o, str_lit("variable declaration")); if (e->Variable.is_rodata && o.mode != Addressing_Constant) { + ERROR_BLOCK(); error(o.expr, "Variables declared with @(rodata) must have constant initialization"); + Ast *expr = unparen_expr(o.expr); + if (is_type_struct(e->type) && expr && expr->kind == Ast_CompoundLit) { + ast_node(cl, CompoundLit, expr); + for (Ast *elem_ : cl->elems) { + Ast *elem = elem_; + if (elem->kind == Ast_FieldValue) { + elem = elem->FieldValue.value; + } + elem = unparen_expr(elem); + + Entity *e = entity_of_node(elem); + if (elem->tav.mode != Addressing_Constant && e == nullptr && elem->kind != Ast_ProcLit) { + Token tok = ast_token(elem); + TokenPos pos = tok.pos; + gbString s = type_to_string(type_of_expr(elem)); + error_line("%s Element is not constant, which is required for @(rodata), of type %s\n", token_pos_to_string(pos), s); + gb_string_free(s); + } + } + } } check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed"); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ebf61ab0c..3c37284eb 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8655,7 +8655,7 @@ gb_internal bool check_range(CheckerContext *c, Ast *node, bool is_for_loop, Ope return true; } -gb_internal bool check_is_operand_compound_lit_constant(CheckerContext *c, Operand *o) { +gb_internal bool check_is_operand_compound_lit_constant(CheckerContext *c, Operand *o, Type *field_type) { if (is_operand_nil(*o)) { return true; } @@ -8670,6 +8670,9 @@ gb_internal bool check_is_operand_compound_lit_constant(CheckerContext *c, Opera return true; } } + if (field_type != nullptr && is_type_typeid(field_type) && o->mode == Addressing_Type) { + return true; + } return o->mode == Addressing_Constant; } @@ -9620,8 +9623,7 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slicevalue, field->type); - if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) { + if (elem_cannot_be_constant(field->type)) { is_constant = false; } if (is_constant) { - is_constant = check_is_operand_compound_lit_constant(c, &o); + is_constant = check_is_operand_compound_lit_constant(c, &o, field->type); } u8 prev_bit_field_bit_size = c->bit_field_bit_size; @@ -9886,14 +9888,11 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * Operand o = {}; check_expr_or_type(c, &o, elem, field->type); - if (is_type_any(field->type) || - is_type_raw_union(field->type) || - (is_type_union(field->type) && !is_type_union_constantable(field->type)) || - is_type_typeid(field->type)) { + if (elem_cannot_be_constant(field->type)) { is_constant = false; } if (is_constant) { - is_constant = check_is_operand_compound_lit_constant(c, &o); + is_constant = check_is_operand_compound_lit_constant(c, &o, field->type); } check_assignment(c, &o, field->type, str_lit("structure literal")); @@ -10070,7 +10069,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * check_expr_with_type_hint(c, &operand, fv->value, elem_type); check_assignment(c, &operand, elem_type, context_name); - is_constant = is_constant && operand.mode == Addressing_Constant; + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &operand, elem_type); + } } } @@ -10097,7 +10098,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * check_expr_with_type_hint(c, &operand, e, elem_type); check_assignment(c, &operand, elem_type, context_name); - is_constant = is_constant && operand.mode == Addressing_Constant; + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &operand, elem_type); + } } if (max < index) { diff --git a/src/types.cpp b/src/types.cpp index 46e41bb4b..814f99eff 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1377,14 +1377,20 @@ gb_internal bool is_type_ordered_numeric(Type *t) { gb_internal bool is_type_constant_type(Type *t) { t = core_type(t); if (t == nullptr) { return false; } - if (t->kind == Type_Basic) { + switch (t->kind) { + case Type_Basic: + if (t->Basic.kind == Basic_typeid) { + return true; + } return (t->Basic.flags & BasicFlag_ConstantType) != 0; - } - if (t->kind == Type_BitSet) { + case Type_BitSet: return true; - } - if (t->kind == Type_Proc) { + case Type_Proc: return true; + case Type_Array: + return is_type_constant_type(t->Array.elem); + case Type_EnumeratedArray: + return is_type_constant_type(t->EnumeratedArray.elem); } return false; } @@ -2539,6 +2545,20 @@ gb_internal bool elem_type_can_be_constant(Type *t) { return true; } +gb_internal bool elem_cannot_be_constant(Type *t) { + if (is_type_any(t)) { + return true; + } + if (is_type_union(t)) { + return !is_type_union_constantable(t); + } + if (is_type_raw_union(t)) { + return true; + } + return false; +} + + gb_internal bool is_type_lock_free(Type *t) { t = core_type(t); if (t == t_invalid) { -- cgit v1.2.3 From 24daa4427c75ea0f2ea0c7706b20849ac297c6fc Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Mon, 29 Sep 2025 19:54:53 -0400 Subject: Fix various foreign signatures --- core/c/libc/stdio.odin | 2 +- core/sys/darwin/CoreFoundation/CFString.odin | 6 ++++-- core/sys/posix/arpa_inet.odin | 1 - core/sys/posix/pthread.odin | 4 ++-- core/sys/posix/sys_mman.odin | 7 ++++++- src/check_decl.cpp | 6 ++++++ 6 files changed, 19 insertions(+), 7 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 854a98637..aa92e4a6b 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -275,7 +275,7 @@ foreign libc { // 7.21.7 Character input/output functions fgetc :: proc(stream: ^FILE) -> int --- fgets :: proc(s: [^]char, n: int, stream: ^FILE) -> [^]char --- - fputc :: proc(s: cstring, stream: ^FILE) -> int --- + fputc :: proc(s: c.int, stream: ^FILE) -> int --- getc :: proc(stream: ^FILE) -> int --- getchar :: proc() -> int --- putc :: proc(c: int, stream: ^FILE) -> int --- diff --git a/core/sys/darwin/CoreFoundation/CFString.odin b/core/sys/darwin/CoreFoundation/CFString.odin index 24485a494..d245ba793 100644 --- a/core/sys/darwin/CoreFoundation/CFString.odin +++ b/core/sys/darwin/CoreFoundation/CFString.odin @@ -1,10 +1,12 @@ package CoreFoundation +import "core:c" + foreign import CoreFoundation "system:CoreFoundation.framework" String :: distinct TypeRef // same as CFStringRef -StringEncoding :: distinct u32 +StringEncoding :: distinct c.long StringBuiltInEncodings :: enum StringEncoding { MacRoman = 0, @@ -171,7 +173,7 @@ foreign CoreFoundation { // Fetches a range of the characters from a string into a byte buffer after converting the characters to a specified encoding. StringGetBytes :: proc(thestring: String, range: Range, encoding: StringEncoding, lossByte: u8, isExternalRepresentation: b8, buffer: [^]byte, maxBufLen: Index, usedBufLen: ^Index) -> Index --- - StringIsEncodingAvailable :: proc(encoding: StringEncoding) -> bool --- + StringIsEncodingAvailable :: proc(encoding: StringEncoding) -> b8 --- @(link_name = "__CFStringMakeConstantString") StringMakeConstantString :: proc "c" (#const c: cstring) -> String --- diff --git a/core/sys/posix/arpa_inet.odin b/core/sys/posix/arpa_inet.odin index 6edb9e535..70b12678c 100644 --- a/core/sys/posix/arpa_inet.odin +++ b/core/sys/posix/arpa_inet.odin @@ -50,7 +50,6 @@ foreign lib { af: AF, // INET or INET6 src: cstring, dst: rawptr, // either ^in_addr or ^in_addr6 - size: socklen_t, // size_of(dst^) ) -> pton_result --- } diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin index c7255baa3..733725e2c 100644 --- a/core/sys/posix/pthread.odin +++ b/core/sys/posix/pthread.odin @@ -124,7 +124,7 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getscope.html ]] */ - pthread_attr_setscope :: proc(attr: ^pthread_attr_t, contentionscope: ^Thread_Scope) -> Errno --- + pthread_attr_setscope :: proc(attr: ^pthread_attr_t, contentionscope: Thread_Scope) -> Errno --- /* Get the area of storage to be used for the created thread's stack. @@ -400,7 +400,7 @@ when ODIN_OS == .Darwin { PTHREAD_SCOPE_PROCESS :: 2 PTHREAD_SCOPE_SYSTEM :: 1 - pthread_t :: distinct u64 + pthread_t :: distinct rawptr pthread_attr_t :: struct { __sig: c.long, diff --git a/core/sys/posix/sys_mman.odin b/core/sys/posix/sys_mman.odin index 2d51083dc..1bf3615dc 100644 --- a/core/sys/posix/sys_mman.odin +++ b/core/sys/posix/sys_mman.odin @@ -92,7 +92,12 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html ]] */ - shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD --- + when ODIN_OS == .Darwin { + // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/shm_open.2.html + shm_open :: proc(name: cstring, oflag: O_Flags, #c_vararg args: ..any) -> FD --- + } else { + shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD --- + } /* Removes a shared memory object. diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 842f8653c..f7df73953 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -851,6 +851,12 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { } } + Type *x_base = base_type(x); + Type *y_base = base_type(y); + if (x_base->kind == y_base->kind && x_base->kind == Type_Struct) { + return type_size_of(x_base) == type_size_of(y_base) && type_align_of(x_base) == type_align_of(y_base); + } + return are_types_identical(x, y); } -- cgit v1.2.3 From 5af13f5d53b4e5f5d472cd8a8bc4444f05ea36d6 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Tue, 16 Sep 2025 00:49:31 -0400 Subject: Automatically emit objc_msgSend calls when calling imported or implemented Objective-C methods - Add intrinsics.objc_super() - Emit objc_msgSendSuper2 calls when an objc method call is combined with objc_super(self) - Fix objc_block return value ABI for large struct returns - Fix objc_implement method wrappers bad ABI for large struct returns and indirect args - Simplify parameter forwarding for objc_imlpement methods - Add intrinsics.objc_instancetype to mimi Objective-C instancetype* returns This facilitates returning the correct type on subclasses when calling mehtods such as `alloc`, `init`, `retain`, etc. - Refactor Objective-C class implementations generation so that hierarchies are properly initialized - Better codegen for context passing with ivar-based autocontext - Allow @superclass on imported objc-c objects - Better codegen for block forwarding invoker, arguments are forwarded directly --- base/intrinsics/intrinsics.odin | 10 +- base/runtime/procs_darwin.odin | 16 ++- src/check_builtin.cpp | 59 ++++++++++- src/check_decl.cpp | 122 +++++++++++++++-------- src/check_expr.cpp | 73 ++++++++++++++ src/checker.cpp | 8 ++ src/checker_builtin_procs.hpp | 4 +- src/entity.cpp | 4 + src/llvm_backend.cpp | 170 ++++++++++++++++++++++---------- src/llvm_backend_proc.cpp | 22 +++-- src/llvm_backend_utility.cpp | 212 ++++++++++++++++++++++++++++++++++------ src/parser.hpp | 3 +- src/types.cpp | 11 +++ 13 files changed, 569 insertions(+), 145 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 2d940cf67..dd508180d 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -374,10 +374,11 @@ objc_selector :: struct{} objc_class :: struct{} objc_ivar :: struct{} -objc_id :: ^objc_object -objc_SEL :: ^objc_selector -objc_Class :: ^objc_class -objc_Ivar :: ^objc_ivar +objc_id :: ^objc_object +objc_SEL :: ^objc_selector +objc_Class :: ^objc_class +objc_Ivar :: ^objc_ivar +objc_instancetype :: distinct objc_id objc_find_selector :: proc($name: string) -> objc_SEL --- objc_register_selector :: proc($name: string) -> objc_SEL --- @@ -385,6 +386,7 @@ objc_find_class :: proc($name: string) -> objc_Class --- objc_register_class :: proc($name: string) -> objc_Class --- objc_ivar_get :: proc(self: ^$T) -> ^$U --- objc_block :: proc(invoke: $T, ..any) -> ^Objc_Block(T) where type_is_proc(T) --- +objc_super :: proc(obj: ^$T) -> ^$U where type_is_subtype_of(T, objc_object) && type_is_subtype_of(U, objc_object) --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin index d176f0f63..cc3dabc9b 100644 --- a/base/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -15,16 +15,23 @@ objc_SEL :: ^intrinsics.objc_selector objc_Ivar :: ^intrinsics.objc_ivar objc_BOOL :: bool +objc_super :: struct { + receiver: objc_id, + super_class: objc_Class, +} objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id foreign ObjC { sel_registerName :: proc "c" (name: cstring) -> objc_SEL --- - objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- - objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- - objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- - objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- + objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- + objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- + objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- + objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- + objc_msgSendSuper2 :: proc "c" (super: ^objc_super, op: objc_SEL, #c_vararg args: ..any) --- + objc_msgSendSuper2_stret :: proc "c" (super: ^objc_super, op: objc_SEL, #c_vararg args: ..any) --- + objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- @@ -33,6 +40,7 @@ foreign ObjC { class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL --- class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar --- class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint --- + class_getSuperclass :: proc "c" (cls : objc_Class) -> objc_Class --- ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr --- object_getClass :: proc "c" (obj: objc_id) -> objc_Class --- } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 7e1567750..f142f04b7 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -210,7 +210,7 @@ gb_internal ObjcMsgKind get_objc_proc_kind(Type *return_type) { return ObjcMsg_normal; } -gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice param_types) { +void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice param_types) { ObjcMsgKind kind = get_objc_proc_kind(return_type); Scope *scope = create_scope(c->info, nullptr); @@ -248,6 +248,12 @@ gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_t try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret"); try_to_add_package_dependency(c, "runtime", "objc_msgSend_fp2ret"); try_to_add_package_dependency(c, "runtime", "objc_msgSend_stret"); + + Slice args = call->CallExpr.args; + if (args.count > 0 && args[0]->tav.objc_super_target) { + try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2"); + try_to_add_package_dependency(c, "runtime", "objc_msgSendSuper2_stret"); + } } gb_internal bool is_constant_string(CheckerContext *c, String const &builtin_name, Ast *expr, String *name_) { @@ -466,8 +472,8 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan isize capture_arg_count = ce->args.count - 1; - // NOTE(harold): The first parameter is already checked at check_builtin_procedure(). - // Checking again would invalidate the Entity -> Value map for direct parameters if it's the handler proc. + // NOTE(harold): The first argument is already checked at check_builtin_procedure(). + // Checking again would invalidate the Entity -> Value map for direct arguments if it's the handler proc. param_operands[0] = *operand; for (isize i = 0; i < ce->args.count-1; i++) { @@ -680,6 +686,52 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan operand->mode = Addressing_Value; return true; } break; + + case BuiltinProc_objc_super: + { + // Must be a pointer to an Objective-C object. + Type *objc_obj = operand->type; + if (!is_type_objc_ptr_to_object(objc_obj)) { + gbString e = expr_to_string(operand->expr); + gbString t = type_to_string(objc_obj); + error(operand->expr, "'%.*s' expected a pointer to an Objective-C object, but got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (operand->mode != Addressing_Value && operand->mode != Addressing_Variable) { + gbString e = expr_to_string(operand->expr); + gbString t = type_to_string(operand->type); + error(operand->expr, "'%.*s' expression '%s', of type %s, must be a value or variable.", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + Type *obj_type = type_deref(objc_obj); + GB_ASSERT(obj_type->kind == Type_Named); + + // NOTE(harold) Track original type before transforming it to the superclass. + // This is needed because objc_msgSendSuper2 must start its search on the subclass, not the superclass. + call->tav.objc_super_target = obj_type; + + // The superclass type must be known at compile time. We require this so that the selector method expressions + // methods are resolved to the superclass's methods instead of the subclass's. + Type *superclass = obj_type->Named.type_name->TypeName.objc_superclass; + if (superclass == nullptr) { + gbString t = type_to_string(obj_type); + error(operand->expr, "'%.*s' target object '%.*s' does not have an Objective-C superclass. One must be set via the @(objc_superclass) attribute", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + GB_ASSERT(superclass->Named.type_name->TypeName.objc_class_name.len > 0); + + operand->type = alloc_type_pointer(superclass); + return true; + + } break; } } @@ -2515,6 +2567,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_objc_register_class: case BuiltinProc_objc_ivar_get: case BuiltinProc_objc_block: + case BuiltinProc_objc_super: return check_builtin_objc_procedure(c, operand, call, id, type_hint); case BuiltinProc___entry_point: diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 842f8653c..113c1e171 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -587,9 +587,7 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, super = named_type->Named.type_name->TypeName.objc_superclass; } } else { - if (ac.objc_superclass != nullptr) { - error(e->token, "@(objc_superclass) may only be applied when the @(obj_implement) attribute is also applied"); - } else if (ac.objc_ivar != nullptr) { + if (ac.objc_ivar != nullptr) { error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied"); } else if (ac.objc_context_provider != nullptr) { error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied"); @@ -1040,61 +1038,100 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon // Enable implementation by default if the class is an implementer too and // @objc_implement was not set to false explicitly in this proc. bool implement = tn->TypeName.objc_is_implementation; + if( ac.objc_is_implementation && !tn->TypeName.objc_is_implementation ) { + error(e->token, "Cannot apply @(objc_is_implement) to a procedure whose type does not also have @(objc_is_implement) set"); + } + if (ac.objc_is_disabled_implement) { implement = false; } - if (implement) { - GB_ASSERT(e->kind == Entity_Procedure); + String objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name; + + if (e->kind == Entity_Procedure) { + bool has_body = e->decl_info->proc_lit->ProcLit.body != nullptr; + e->Procedure.is_objc_impl_or_import = implement || !has_body; + e->Procedure.is_objc_class_method = ac.objc_is_class_method; + e->Procedure.objc_selector_name = objc_selector; + e->Procedure.objc_class = tn; auto &proc = e->type->Proc; Type *first_param = proc.param_count > 0 ? proc.params->Tuple.variables[0]->type : t_untyped_nil; - if (!tn->TypeName.objc_is_implementation) { - error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied"); - } else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) { - error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)"); - } else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) { - error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set"); - } else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) { - error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention"); - } else if (proc.result_count > 1) { - error(e->token, "Objective-C method implementations may return at most 1 value"); - } else { - // Always export unconditionally - // NOTE(harold): This means check_objc_methods() MUST be called before - // e->Procedure.is_export is set in check_proc_decl()! - if (ac.is_export) { - error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly"); - } - if (ac.link_name != "") { - error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly"); - } + if (implement) { + if( !has_body ) { + error(e->token, "Procedures with @(objc_is_implement) must have a body"); + } else if (!tn->TypeName.objc_is_implementation) { + error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied"); + } else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) { + error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)"); + } else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) { + error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set"); + } else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) { + error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention"); + } else if (proc.result_count > 1) { + error(e->token, "Objective-C method implementations may return at most 1 value"); + } else { + // Always export unconditionally + // NOTE(harold): This means check_objc_methods() MUST be called before + // e->Procedure.is_export is set in check_proc_decl()! + if (ac.is_export) { + error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly"); + } + if (ac.link_name != "") { + error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly"); + } - ac.is_export = true; - ac.linkage = STR_LIT("strong"); + ac.is_export = true; + ac.linkage = STR_LIT("strong"); - auto method = ObjcMethodData{ ac, e }; - method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name; + auto method = ObjcMethodData{ ac, e }; + method.ac.objc_selector = objc_selector; - CheckerInfo *info = ctx->info; - mutex_lock(&info->objc_method_mutex); - defer (mutex_unlock(&info->objc_method_mutex)); + CheckerInfo *info = ctx->info; + mutex_lock(&info->objc_method_mutex); + defer (mutex_unlock(&info->objc_method_mutex)); - Array* method_list = map_get(&info->objc_method_implementations, t); - if (method_list) { - array_add(method_list, method); - } else { - auto list = array_make(permanent_allocator(), 1, 8); - list[0] = method; + Array* method_list = map_get(&info->objc_method_implementations, t); + if (method_list) { + array_add(method_list, method); + } else { + auto list = array_make(permanent_allocator(), 1, 8); + list[0] = method; - map_set(&info->objc_method_implementations, t, list); + map_set(&info->objc_method_implementations, t, list); + } + } + } else if (!has_body) { + if (ac.objc_selector == "The @(objc_selector) attribute is required for imported Objective-C methods.") { + return; + } else if (proc.calling_convention != ProcCC_CDecl) { + error(e->token, "Imported Objective-C methods must use the \"c\" calling convention"); + return; + } else if (tn->TypeName.objc_context_provider) { + error(e->token, "Imported Objective-C class '%.*s' must not declare context providers.", tn->type->Named.name); + return; + } else if (tn->TypeName.objc_is_implementation) { + error(e->token, "Imported Objective-C methods used in a class with @(objc_implement) is not allowed."); + return; + } else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) { + error(e->token, "Objective-C instance methods require the first parameter to be a pointer to the class type set by @(objc_type)"); + return; } } - } else if (ac.objc_selector != "") { - error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C implementations."); + else if(ac.objc_selector != "") { + error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C method implementations or are imported."); + return; + } + } else { + GB_ASSERT(e->kind == Entity_ProcGroup); + if (tn->TypeName.objc_is_implementation) { + error(e->token, "Objective-C procedure groups cannot use the @(objc_implement) attribute."); + return; + } } + mutex_lock(&global_type_name_objc_metadata_mutex); defer (mutex_unlock(&global_type_name_objc_metadata_mutex)); @@ -1479,7 +1516,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { if (!pt->is_polymorphic) { check_procedure_later(ctx->checker, ctx->file, e->token, d, proc_type, pl->body, pl->tags); } - } else if (!is_foreign) { + } else if (!is_foreign && !e->Procedure.is_objc_impl_or_import) { if (e->Procedure.is_export) { error(e->token, "Foreign export procedures must have a body"); } else { @@ -1527,6 +1564,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { // NOTE(bill): this must be delayed because the foreign import paths might not be evaluated yet until much later mpsc_enqueue(&ctx->info->foreign_decls_to_check, e); } else { + // TODO(harold): Check if it's an objective-C foreign, if so, I don't think we need to check it. check_foreign_procedure(ctx, e, d); } } else { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2a22e5c48..5f36bf3a1 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8151,6 +8151,73 @@ gb_internal ExprKind check_call_expr_as_type_cast(CheckerContext *c, Operand *op } +void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_type, Slice param_types); + +gb_internal void check_objc_call_expr(CheckerContext *c, Operand *operand, Ast *call, Entity *proc_entity, Type *proc_type) { + auto &proc = proc_type->Proc; + Slice params = proc.params ? proc.params->Tuple.variables : Slice{}; + + Type *self_type = nullptr; + isize params_start = 1; + + ast_node(ce, CallExpr, call); + + Type *return_type = proc.result_count == 0 ? nullptr : proc.results->Tuple.variables[0]->type; + bool is_return_instancetype = return_type != nullptr && return_type == t_objc_instancetype; + + if (params.count == 0 || !is_type_objc_ptr_to_object(params[0]->type)) { + if (!proc_entity->Procedure.is_objc_class_method) { + // Not a class method, invalid call + error(call, "Invalid Objective-C call: The Objective-C method is not a class method but this first parameter is not an Objective-C object pointer."); + return; + } + + if (is_return_instancetype) { + if (ce->proc->kind == Ast_SelectorExpr) { + ast_node(se, SelectorExpr, ce->proc); + + // NOTE(harold): These should have already been checked, right? + GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named); + + return_type = alloc_type_pointer(se->expr->tav.type); + } else { + return_type = proc_entity->Procedure.objc_class->type; + } + } + + self_type = t_objc_Class; + params_start = 0; + } else if (ce->args.count > 0) { + GB_ASSERT(is_type_objc_ptr_to_object(params[0]->type)); + + if (ce->args[0]->tav.objc_super_target) { + self_type = t_objc_super_ptr; + } else { + self_type = ce->args[0]->tav.type; + } + + if (is_return_instancetype) { + // NOTE(harold): These should have already been checked, right? + GB_ASSERT(ce->args[0]->tav.type && ce->args[0]->tav.type->kind == Type_Pointer && ce->args[0]->tav.type->Pointer.elem->kind == Type_Named); + + return_type = ce->args[0]->tav.type; + } + } + + auto param_types = slice_make(permanent_allocator(), proc.param_count + 2 - params_start); + param_types[0] = self_type; + param_types[1] = t_objc_SEL; + + for (isize i = params_start; i < params.count; i++) { + param_types[i+2-params_start] = params[i]->type; + } + + if (is_return_instancetype) { + operand->type = return_type; + } + + add_objc_proc_type(c, call, return_type, param_types); +} gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice const &args, ProcInlining inlining, Type *type_hint) { if (proc != nullptr && @@ -8414,6 +8481,12 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } + Entity *proc_entity = entity_from_expr(call->CallExpr.proc); + bool is_objc_call = proc_entity && proc_entity->kind == Entity_Procedure && proc_entity->Procedure.is_objc_impl_or_import; + if (is_objc_call) { + check_objc_call_expr(c, operand, call, proc_entity, pt); + } + return Expr_Expr; } diff --git a/src/checker.cpp b/src/checker.cpp index 32bda2e43..d3c111de4 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1416,6 +1416,8 @@ gb_internal void init_universal(void) { t_objc_SEL = alloc_type_pointer(t_objc_selector); t_objc_Class = alloc_type_pointer(t_objc_class); t_objc_Ivar = alloc_type_pointer(t_objc_ivar); + + t_objc_instancetype = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_instancetype"), t_objc_id); } } @@ -3386,12 +3388,18 @@ gb_internal void init_core_map_type(Checker *c) { t_raw_map_ptr = alloc_type_pointer(t_raw_map); } +gb_internal void init_core_objc_c(Checker *c) { + t_objc_super = find_core_type(c, str_lit("objc_super")); + t_objc_super_ptr = alloc_type_pointer(t_objc_super); +} + gb_internal void init_preload(Checker *c) { init_core_type_info(c); init_mem_allocator(c); init_core_context(c); init_core_source_code_location(c); init_core_map_type(c); + init_core_objc_c(c); } gb_internal ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index c6071bf98..c2255a6ba 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -354,6 +354,7 @@ BuiltinProc__type_end, BuiltinProc_objc_register_class, BuiltinProc_objc_ivar_get, BuiltinProc_objc_block, + BuiltinProc_objc_super, BuiltinProc_constant_utf16_cstring, @@ -715,7 +716,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_ivar_get"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, - {STR_LIT("objc_block"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("objc_block"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("objc_super"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/entity.cpp b/src/entity.cpp index d6d8f58de..2b21fdcac 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -251,6 +251,8 @@ struct Entity { String link_name; String link_prefix; String link_suffix; + String objc_selector_name; + Entity *objc_class; DeferredProcedure deferred_procedure; struct GenProcsData *gen_procs; @@ -266,6 +268,8 @@ struct Entity { bool is_anonymous : 1; bool no_sanitize_address : 1; bool no_sanitize_memory : 1; + bool is_objc_impl_or_import : 1; + bool is_objc_class_method : 1; } Procedure; struct { Array entities; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index b47e2788f..86c83b91f 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1417,8 +1417,21 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) { return str_lit("?"); case Type_Proc: return str_lit("?"); - case Type_BitSet: - return lb_get_objc_type_encoding(t->BitSet.underlying, pointer_depth); + case Type_BitSet: { + Type *bitset_integer_type = t->BitSet.underlying; + if (!bitset_integer_type) { + switch (t->cached_size) { + case 1: bitset_integer_type = t_u8; break; + case 2: bitset_integer_type = t_u16; break; + case 4: bitset_integer_type = t_u32; break; + case 8: bitset_integer_type = t_u64; break; + case 16: bitset_integer_type = t_u128; break; + } + } + GB_ASSERT_MSG(bitset_integer_type, "Could not determine bit_set integer size for objc_type_encoding"); + + return lb_get_objc_type_encoding(bitset_integer_type, pointer_depth); + } case Type_SimdVector: { String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth); @@ -1452,7 +1465,10 @@ String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) { struct lbObjCGlobalClass { lbObjCGlobal g; - lbValue class_value; // Local registered class value + union { + lbValue class_value; // Local registered class value + lbAddr class_global; // Global class pointer. Placeholder for class implementations which are registered in order of definition. + }; }; gb_internal void lb_register_objc_thing( @@ -1482,44 +1498,43 @@ gb_internal void lb_register_objc_thing( LLVMSetInitializer(v.value, LLVMConstNull(t)); } - lbValue class_ptr = {}; - lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name)); - // If this class requires an implementation, save it for registration below. if (g.class_impl_type != nullptr) { // Make sure the superclass has been initialized before us - lbValue superclass_value = lb_const_nil(m, t_objc_Class); - auto &tn = g.class_impl_type->Named.type_name->TypeName; Type *superclass = tn.objc_superclass; if (superclass != nullptr) { auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name); lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call); - GB_ASSERT(superclass_global.class_value.value); - - superclass_value = superclass_global.class_value; + GB_ASSERT(superclass_global.class_global.addr.value); } - args.count = 3; - args[0] = superclass_value; - args[1] = class_name; - args[2] = lb_const_int(m, t_uint, 0); - class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args); + lbObjCGlobalClass impl_global = {}; + impl_global.g = g; + impl_global.class_global = addr; - array_add(&class_impls, lbObjCGlobalClass{g, class_ptr}); + array_add(&class_impls, impl_global); + + lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); + if (class_global != nullptr) { + class_global->class_global = addr; + } } else { + lbValue class_ptr = {}; + lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name)); + args.count = 1; args[0] = class_name; class_ptr = lb_emit_runtime_call(p, call, args); - } - lb_addr_store(p, addr, class_ptr); + lb_addr_store(p, addr, class_ptr); - lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); - if (class_global != nullptr) { - class_global->class_value = class_ptr; + lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name); + if (class_global != nullptr) { + class_global->class_value = class_ptr; + } } } @@ -1582,7 +1597,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { string_map_init(&global_class_map, (usize)gen->objc_classes.count); defer (string_map_destroy(&global_class_map)); - for (lbObjCGlobal g :referenced_classes) { + for (lbObjCGlobal g : referenced_classes) { string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g}); } @@ -1629,9 +1644,36 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { for (const auto &cd : class_impls) { auto &g = cd.g; - Type *class_type = g.class_impl_type; + + Type *class_type = g.class_impl_type; Type *class_ptr_type = alloc_type_pointer(class_type); - lbValue class_value = cd.class_value; + + // Begin class registration: create class pair and update global reference + lbValue class_value = {}; + + { + lbValue superclass_value = lb_const_nil(m, t_objc_Class); + + auto& tn = class_type->Named.type_name->TypeName; + Type *superclass = tn.objc_superclass; + + if (superclass != nullptr) { + auto& superclass_global = string_map_must_get(&global_class_map, superclass->Named.type_name->TypeName.objc_class_name); + superclass_value = superclass_global.class_value; + } + + args.count = 3; + args[0] = superclass_value; + args[1] = lb_const_value(m, t_cstring, exact_value_string(g.name)); + args[2] = lb_const_int(m, t_uint, 0); + class_value = lb_emit_runtime_call(p, "objc_allocateClassPair", args); + + lbObjCGlobalClass &mapped_global = string_map_must_get(&global_class_map, tn.objc_class_name); + lb_addr_store(p, mapped_global.class_global, class_value); + + mapped_global.class_value = class_value; + } + Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar; @@ -1651,7 +1693,6 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type); } - Array *methods = map_get(&m->info->objc_method_implementations, class_type); if (!methods) { continue; @@ -1710,17 +1751,21 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl); lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type); - lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind"); + + lb_add_function_type_attributes(wrapper_proc->value, lb_get_function_type(m, wrapper_proc_type), ProcCC_CDecl); // Emit the wrapper - LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage); + // LLVMSetLinkage(wrapper_proc->value, LLVMInternalLinkage); + LLVMSetDLLStorageClass(wrapper_proc->value, LLVMDLLExportStorageClass); + lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind"); + lb_begin_procedure_body(wrapper_proc); { + LLVMValueRef context_addr = nullptr; if (method_type->Proc.calling_convention == ProcCC_Odin) { GB_ASSERT(context_provider); // Emit the get odin context call - get_context_args[0] = lbValue { wrapper_proc->raw_input_parameters[0], contex_provider_self_ptr_type, @@ -1736,44 +1781,58 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { get_context_args[0] = lb_handle_objc_ivar_for_objc_object_pointer(wrapper_proc, real_self); } - lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args); - lbAddr context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context)); - lb_push_context_onto_stack(wrapper_proc, context_addr); + lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args); + context_addr = lb_address_from_load(wrapper_proc, context).value;//lb_address_from_load_or_generate_local(wrapper_proc, context)); + // context_addr = LLVMGetOperand(context.value, 0); } + isize method_forward_arg_count = method_param_count + method_param_offset; + isize method_forward_return_arg_offset = 0; + auto raw_method_args = array_make(temporary_allocator(), 0, method_forward_arg_count+1); - auto method_call_args = array_make(temporary_allocator(), method_param_count + method_param_offset); + lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity); + lbFunctionType* ft = lb_get_function_type(m, method_type); + bool has_return = false; + lbArgKind return_kind = {}; + + if (wrapper_results_tuple != nullptr) { + has_return = true; + return_kind = ft->ret.kind; + + if (return_kind == lbArg_Indirect) { + method_forward_return_arg_offset = 1; + array_add(&raw_method_args, wrapper_proc->return_ptr.addr.value); + } + } if (!md.ac.objc_is_class_method) { - method_call_args[0] = lbValue { - wrapper_proc->raw_input_parameters[0], - class_ptr_type, - }; + array_add(&raw_method_args, wrapper_proc->raw_input_parameters[method_forward_return_arg_offset]); } for (isize i = 0; i < method_param_count; i++) { - method_call_args[i+method_param_offset] = lbValue { - wrapper_proc->raw_input_parameters[i+2], - method_type->Proc.params->Tuple.variables[i+method_param_offset]->type, - }; + array_add(&raw_method_args, wrapper_proc->raw_input_parameters[i+2+method_forward_return_arg_offset]); + } + + if (method_type->Proc.calling_convention == ProcCC_Odin) { + array_add(&raw_method_args, context_addr); } - lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity); // Call real procedure for method from here, passing the parameters expected, if any. - lbValue return_value = lb_emit_call(wrapper_proc, method_proc_value, method_call_args); + LLVMTypeRef fnp = lb_type_internal_for_procedures_raw(m, method_type); + LLVMValueRef ret_val_raw = LLVMBuildCall2(wrapper_proc->builder, fnp, method_proc_value.value, raw_method_args.data, (unsigned)raw_method_args.count, ""); - if (wrapper_results_tuple != nullptr) { - auto &result_var = method_type->Proc.results->Tuple.variables[0]; - return_value = lb_emit_conv(wrapper_proc, return_value, result_var->type); - lb_build_return_stmt_internal(wrapper_proc, return_value, result_var->token.pos); + if (has_return && return_kind != lbArg_Indirect) { + LLVMBuildRet(wrapper_proc->builder, ret_val_raw); + } + else { + LLVMBuildRetVoid(wrapper_proc->builder); } } lb_end_procedure_body(wrapper_proc); - // Add the method to the class String method_encoding = str_lit("v"); - // TODO (harold): Checker must ensure that objc_methods have a single return value or none! + GB_ASSERT(method_type->Proc.result_count <= 1); if (method_type->Proc.result_count != 0) { method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type); @@ -1785,8 +1844,8 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:")); } - for (isize i = method_param_offset; i < method_param_count; i++) { - Type *param_type = method_type->Proc.params->Tuple.variables[i]->type; + for (isize i = 0; i < method_param_count; i++) { + Type *param_type = method_type->Proc.params->Tuple.variables[i + method_param_offset]->type; String param_encoding = lb_get_objc_type_encoding(param_type); method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding); @@ -1805,7 +1864,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { args[2] = lbValue { wrapper_proc->value, wrapper_proc->type }; args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding)); - // TODO(harold): Emit check BOOL result and panic if false. + // TODO(harold): Emit check BOOL result and panic if false? lb_emit_runtime_call(p, "class_addMethod", args); } // End methods @@ -1853,7 +1912,7 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { // Defined in an external package, define it now in the main package LLVMTypeRef t = lb_type(m, t_int); - lbValue global{}; + lbValue global = {}; global.value = LLVMAddGlobal(m->mod, t, g.global_name); global.type = t_int_ptr; @@ -2192,6 +2251,11 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker GB_ASSERT(m != nullptr); if (e->kind == Entity_Procedure) { + if (e->Procedure.is_foreign && e->Procedure.is_objc_impl_or_import) { + // Do not generate declarations for foreign Objective-C methods. These are called indirectly through the Objective-C runtime. + continue; + } + array_add(&m->global_procedures_to_create, e); } else if (e->kind == Entity_TypeName) { array_add(&m->global_types_to_create, e); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 7680c5e76..3bd5f4ef2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3753,6 +3753,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_objc_register_class: return lb_handle_objc_register_class(p, expr); case BuiltinProc_objc_ivar_get: return lb_handle_objc_ivar_get(p, expr); case BuiltinProc_objc_block: return lb_handle_objc_block(p, expr); + case BuiltinProc_objc_super: return lb_handle_objc_super(p, expr); case BuiltinProc_constant_utf16_cstring: @@ -4122,21 +4123,23 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } Ast *proc_expr = unparen_expr(ce->proc); + Entity *proc_entity = entity_of_node(proc_expr); + if (proc_mode == Addressing_Builtin) { - Entity *e = entity_of_node(proc_expr); BuiltinProcId id = BuiltinProc_Invalid; - if (e != nullptr) { - id = cast(BuiltinProcId)e->Builtin.id; + if (proc_entity != nullptr) { + id = cast(BuiltinProcId)proc_entity->Builtin.id; } else { id = BuiltinProc_DIRECTIVE; } return lb_build_builtin_proc(p, expr, tv, id); } + bool is_objc_call = proc_entity->Procedure.is_objc_impl_or_import; + // NOTE(bill): Regular call lbValue value = {}; - Entity *proc_entity = entity_of_node(proc_expr); if (proc_entity != nullptr) { if (proc_entity->flags & EntityFlag_Disabled) { GB_ASSERT(tv.type == nullptr); @@ -4170,11 +4173,13 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } } - if (value.value == nullptr) { + if (is_objc_call) { + value.type = proc_tv.type; + } else if (value.value == nullptr) { value = lb_build_expr(p, proc_expr); } - GB_ASSERT(value.value != nullptr); + GB_ASSERT(value.value != nullptr || is_objc_call); Type *proc_type_ = base_type(value.type); GB_ASSERT(proc_type_->kind == Type_Proc); TypeProc *pt = &proc_type_->Proc; @@ -4402,6 +4407,11 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { isize final_count = is_c_vararg ? args.count : pt->param_count; auto call_args = array_slice(args, 0, final_count); + + if (is_objc_call) { + return lb_handle_objc_auto_send(p, expr, slice(call_args, 0, call_args.count)); + } + return lb_emit_call(p, value, call_args, ce->inlining); } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 7fe6b1458..d124f164e 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2373,7 +2373,7 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { /// https://www.newosxbook.com/src.php?tree=xnu&file=/libkern/libkern/Block_private.h /// https://github.com/llvm/llvm-project/blob/21f1f9558df3830ffa637def364e3c0cb0dbb3c0/compiler-rt/lib/BlocksRuntime/Block_private.h /// https://github.com/apple-oss-distributions/libclosure/blob/3668b0837f47be3cc1c404fb5e360f4ff178ca13/runtime.cpp - + // TODO(harold): Ensure we don't have any issues with large struct arguments or returns in block wrappers. ast_node(ce, CallExpr, expr); GB_ASSERT(ce->args.count > 0); @@ -2452,7 +2452,9 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { lbProcedure *invoker_proc = lb_create_dummy_procedure(m, make_string((u8*)block_invoker_name, gb_string_length(block_invoker_name)), invoker_proc_type); + LLVMSetLinkage(invoker_proc->value, LLVMPrivateLinkage); + lb_add_function_type_attributes(invoker_proc->value, lb_get_function_type(m, invoker_proc_type), ProcCC_CDecl); // Create the block descriptor and block literal gbString block_lit_type_name = gb_string_make(temporary_allocator(), "__$ObjC_Block_Literal_"); @@ -2531,45 +2533,66 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { /// Invoker body lb_begin_procedure_body(invoker_proc); { - auto call_args = array_make(temporary_allocator(), user_proc.param_count, user_proc.param_count); + // Reserve 2 extra arguments for: Indirect return values and context. + auto call_args = array_make(temporary_allocator(), 0, user_proc.param_count + 2); - for (isize i = 1; i < invoker_proc->raw_input_parameters.count; i++) { - lbValue arg = {}; - arg.type = invoker_args[i]; - arg.value = invoker_proc->raw_input_parameters[i], - call_args[i-1] = arg; - } + isize block_literal_arg_index = 0; - LLVMValueRef block_literal = invoker_proc->raw_input_parameters[0]; + lbFunctionType* user_proc_ft = lb_get_function_type(m, user_proc_value.type); - // Push context, if needed - if (user_proc.calling_convention == ProcCC_Odin) { - LLVMValueRef p_context = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, 5, "context"); - lbValue ctx_val = {}; - ctx_val.type = t_context_ptr; - ctx_val.value = p_context; + lbArgKind return_kind = {}; + + GB_ASSERT(user_proc.result_count <= 1); + if (user_proc.result_count > 0) { + return_kind = user_proc_ft->ret.kind; + + if (return_kind == lbArg_Indirect) { + // Forward indirect return value + array_add(&call_args, invoker_proc->raw_input_parameters[0]); + block_literal_arg_index = 1; + } + } - lb_push_context_onto_stack(invoker_proc, lb_addr(ctx_val)); + // Forward raw arguments + for (isize i = block_literal_arg_index+1; i < invoker_proc->raw_input_parameters.count; i++) { + array_add(&call_args, invoker_proc->raw_input_parameters[i]); } + LLVMValueRef block_literal = invoker_proc->raw_input_parameters[block_literal_arg_index]; + // Copy capture parameters from the block literal + isize capture_arg_in_user_proc_start_index = user_proc_ft->args.count - capture_arg_count; + if (user_proc.calling_convention == ProcCC_Odin) { + capture_arg_in_user_proc_start_index -= 1; + } + for (isize i = 0; i < capture_arg_count; i++) { LLVMValueRef cap_value = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, unsigned(capture_fields_offset + i), ""); - lbValue cap_arg = {}; - cap_arg.value = cap_value; - cap_arg.type = alloc_type_pointer(captured_values[i].type); + // Don't emit load if indirect. Pass the pointer as-is + isize cap_arg_index_in_user_proc = capture_arg_in_user_proc_start_index + i; - lbValue arg = lb_emit_load(invoker_proc, cap_arg); - call_args[block_forward_args+i] = arg; + if (user_proc_ft->args[cap_arg_index_in_user_proc].kind != lbArg_Indirect) { + cap_value = OdinLLVMBuildLoad(invoker_proc, lb_type(invoker_proc->module, captured_values[i].type), cap_value); + } + + array_add(&call_args, cap_value); } - lbValue result = lb_emit_call(invoker_proc, user_proc_value, call_args, proc_lit->ProcLit.inlining); + // Push context, if needed + if (user_proc.calling_convention == ProcCC_Odin) { + LLVMValueRef p_context = LLVMBuildStructGEP2(invoker_proc->builder, block_lit_type, block_literal, 5, "context"); + array_add(&call_args, p_context); + } - GB_ASSERT(user_proc.result_count <= 1); - if (user_proc.result_count > 0) { - GB_ASSERT(result.value != nullptr); - LLVMBuildRet(p->builder, result.value); + LLVMTypeRef fnp = lb_type_internal_for_procedures_raw(m, user_proc_value.type); + LLVMValueRef ret_val = LLVMBuildCall2(invoker_proc->builder, fnp, user_proc_value.value, call_args.data, (unsigned)call_args.count, ""); + + if (user_proc.result_count > 0 && return_kind != lbArg_Indirect) { + LLVMBuildRet(invoker_proc->builder, ret_val); + } + else { + LLVMBuildRetVoid(invoker_proc->builder); } } lb_end_procedure_body(invoker_proc); @@ -2587,8 +2610,8 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { gbString block_var_name = gb_string_make(temporary_allocator(), "__$objc_block_literal_"); block_var_name = gb_string_append_fmt(block_var_name, "%lld", m->objc_next_block_id); - lbValue result = {}; - result.type = block_result_type; + lbValue block_result = {}; + block_result.type = block_result_type; lbValue isa_val = lb_find_runtime_value(m, is_global ? str_lit("_NSConcreteGlobalBlock") : str_lit("_NSConcreteStackBlock")); lbValue flags_val = lb_const_int(m, t_i32, (u64)raw_flags); @@ -2596,7 +2619,7 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { if (is_global) { LLVMValueRef p_block_lit = LLVMAddGlobal(m->mod, block_lit_type, block_var_name); - result.value = p_block_lit; + block_result.value = p_block_lit; LLVMValueRef fields_values[5] = { isa_val.value, // isa @@ -2611,7 +2634,7 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { } else { LLVMValueRef p_block_lit = llvm_alloca(p, block_lit_type, lb_alignof(block_lit_type), block_var_name); - result.value = p_block_lit; + block_result.value = p_block_lit; // Initialize it LLVMValueRef f_isa = LLVMBuildStructGEP2(p->builder, block_lit_type, p_block_lit, 0, "isa"); @@ -2651,7 +2674,20 @@ gb_internal lbValue lb_handle_objc_block(lbProcedure *p, Ast *expr) { } } - return result; + return block_result; +} + +gb_internal lbValue lb_handle_objc_block_invoke(lbProcedure *p, Ast *expr) { + return {}; +} + +gb_internal lbValue lb_handle_objc_super(lbProcedure *p, Ast *expr) { + ast_node(ce, CallExpr, expr); + GB_ASSERT(ce->args.count == 1); + + // NOTE(harold): This doesn't actually do anything by itself, + // it has an effect when used on the left side of a selector call expression. + return lb_build_expr(p, ce->args[0]); } gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { @@ -2767,6 +2803,120 @@ gb_internal lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) { return lb_emit_call(p, the_proc, args); } +gb_internal lbValue lb_handle_objc_auto_send(lbProcedure *p, Ast *expr, Slice const arg_values) { + ast_node(ce, CallExpr, expr); + + lbModule *m = p->module; + CheckerInfo *info = m->info; + ObjcMsgData data = map_must_get(&info->objc_msgSend_types, expr); + + Type *proc_type = data.proc_type; + GB_ASSERT(proc_type != nullptr); + + Entity *objc_method_ent = entity_of_node(ce->proc); + GB_ASSERT(objc_method_ent != nullptr); + GB_ASSERT(objc_method_ent->kind == Entity_Procedure); + GB_ASSERT(objc_method_ent->Procedure.objc_selector_name.len > 0); + + auto &proc = proc_type->Proc; + GB_ASSERT(proc.param_count >= 2); + + Type *objc_super_orig_type = nullptr; + if (ce->args.count > 0) { + objc_super_orig_type = unparen_expr(ce->args[0])->tav.objc_super_target; + } + + isize arg_offset = 1; + lbValue id = {}; + if (!objc_method_ent->Procedure.is_objc_class_method) { + GB_ASSERT(ce->args.count > 0); + id = arg_values[0]; + + if (objc_super_orig_type) { + GB_ASSERT(objc_super_orig_type->kind == Type_Named); + + auto& tn = objc_super_orig_type->Named.type_name->TypeName; + lbAddr p_supercls = lb_handle_objc_find_or_register_class(p, tn.objc_class_name, tn.objc_is_implementation ? objc_super_orig_type : nullptr); + + lbValue supercls = lb_addr_load(p, p_supercls); + lbAddr p_objc_super = lb_add_local_generated(p, t_objc_super, false); + + lbValue f_id = lb_emit_struct_ep(p, p_objc_super.addr, 0); + lbValue f_superclass = lb_emit_struct_ep(p, p_objc_super.addr, 1); + + id = lb_emit_conv(p, id, t_objc_id); + lb_emit_store(p, f_id, id); + lb_emit_store(p, f_superclass, supercls); + + id = p_objc_super.addr; + } + } else { + Entity *objc_class = objc_method_ent->Procedure.objc_class; + if (ce->proc->kind == Ast_SelectorExpr) { + // NOTE (harold): If called via a selector expression (ex: Foo.alloc()), then we should use + // the lhs-side to determine the class. This allows for class methods to be called + // with the correct class as the target, even when the method is defined in a superclass. + ast_node(se, SelectorExpr, ce->proc); + GB_ASSERT(se->expr->tav.mode == Addressing_Type && se->expr->tav.type->kind == Type_Named); + + objc_class = entity_from_expr(se->expr); + + GB_ASSERT(objc_class); + GB_ASSERT(objc_class->kind == Entity_TypeName); + GB_ASSERT(objc_class->TypeName.objc_class_name != ""); + } + + Type *class_impl_type = objc_class->TypeName.objc_is_implementation ? objc_class->type : nullptr; + + id = lb_addr_load(p, lb_handle_objc_find_or_register_class(p, objc_class->TypeName.objc_class_name, class_impl_type)); + arg_offset = 0; + } + + lbValue sel = lb_addr_load(p, lb_handle_objc_find_or_register_selector(p, objc_method_ent->Procedure.objc_selector_name)); + + auto args = array_make(permanent_allocator(), 0, arg_values.count + 2 - arg_offset); + + array_add(&args, id); + array_add(&args, sel); + + for (isize i = arg_offset; i < ce->args.count; i++) { + array_add(&args, arg_values[i]); + } + + lbValue the_proc = {}; + + if (!objc_super_orig_type) { + switch (data.kind) { + default: + GB_PANIC("unhandled ObjcMsgKind %u", data.kind); + break; + case ObjcMsg_normal: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend")); break; + case ObjcMsg_fpret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fpret")); break; + case ObjcMsg_fp2ret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_fp2ret")); break; + case ObjcMsg_stret: the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSend_stret")); break; + } + } else { + switch (data.kind) { + default: + GB_PANIC("unhandled ObjcMsgKind %u", data.kind); + break; + case ObjcMsg_normal: + case ObjcMsg_fpret: + case ObjcMsg_fp2ret: + the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2")); + break; + case ObjcMsg_stret: + the_proc = lb_lookup_runtime_procedure(m, str_lit("objc_msgSendSuper2_stret")); + break; + } + } + + the_proc = lb_emit_conv(p, the_proc, data.proc_type); + + return lb_emit_call(p, the_proc, args); +} + + gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) { GB_ASSERT(value.kind == ExactValue_Integer); i64 v = exact_value_to_i64(value); diff --git a/src/parser.hpp b/src/parser.hpp index 979b44618..6127468d4 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -48,7 +48,8 @@ gb_global String const addressing_mode_strings[] = { struct TypeAndValue { Type * type; AddressingMode mode; - bool is_lhs; // Debug info + bool is_lhs; // Debug info + Type * objc_super_target; // Original type of the Obj-C object before being converted to the superclass' type by the objc_super() intrinsic. ExactValue value; }; diff --git a/src/types.cpp b/src/types.cpp index effa8ef64..372c2e991 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -752,11 +752,14 @@ gb_global Type *t_objc_object = nullptr; gb_global Type *t_objc_selector = nullptr; gb_global Type *t_objc_class = nullptr; gb_global Type *t_objc_ivar = nullptr; +gb_global Type *t_objc_super = nullptr; // Struct used in lieu of the 'self' instance when calling objc_msgSendSuper. +gb_global Type *t_objc_super_ptr = nullptr; gb_global Type *t_objc_id = nullptr; gb_global Type *t_objc_SEL = nullptr; gb_global Type *t_objc_Class = nullptr; gb_global Type *t_objc_Ivar = nullptr; +gb_global Type *t_objc_instancetype = nullptr; // Special distinct variant of t_objc_id used mimic auto-typing of instancetype* in Objective-C enum OdinAtomicMemoryOrder : i32 { OdinAtomicMemoryOrder_relaxed = 0, // unordered @@ -4735,6 +4738,14 @@ gb_internal bool is_type_objc_object(Type *t) { return internal_check_is_assignable_to(t, t_objc_object); } +gb_internal bool is_type_objc_ptr_to_object(Type *t) { + // NOTE (harold): is_type_objc_object() returns true if it's a pointer to an object or the object itself. + // This returns true ONLY if Type is a shallow pointer to an Objective-C object. + + Type *elem = type_deref(t); + return elem != t && elem->kind == Type_Named && is_type_objc_object(elem); +} + gb_internal Type *get_struct_field_type(Type *t, isize index) { t = base_type(type_deref(t)); GB_ASSERT(t->kind == Type_Struct); -- cgit v1.2.3 From 668df4a571cdc376a6a13992364f6debf09f0476 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 30 Sep 2025 10:47:31 +0100 Subject: Improve `signature_parameter_similar_enough` for structs --- src/check_decl.cpp | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index f7df73953..779dd6c12 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -853,10 +853,40 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { Type *x_base = base_type(x); Type *y_base = base_type(y); - if (x_base->kind == y_base->kind && x_base->kind == Type_Struct) { - return type_size_of(x_base) == type_size_of(y_base) && type_align_of(x_base) == type_align_of(y_base); + if (x_base->kind == y_base->kind && + x_base->kind == Type_Struct) { + i64 xs = type_size_of(x_base); + i64 ys = type_size_of(y_base); + + i64 xa = type_align_of(x_base); + i64 ya = type_align_of(y_base); + + + if (x_base->Struct.is_raw_union == y_base->Struct.is_raw_union && + xs == ys && xa == ya) { + if (xs > 16) { + // @@ABI NOTE(bill): Just allow anything over 16-bytes to be allowed, because on all current ABIs + // it will be passed by point + // NOTE(bill): this must be changed when ABI changes + return true; + } + if (x->Struct.fields.count == y->Struct.fields.count) { + for (isize i = 0; i < x->Struct.fields.count; i++) { + Entity *a = x->Struct.fields[i]; + Entity *b = y->Struct.fields[i]; + bool similar = signature_parameter_similar_enough(a->type, b->type); + if (!similar) { + // NOTE(bill): If the fields are not similar enough, then stop. + goto end; + } + } + } + // HACK NOTE(bill): Allow this for the time begin until it actually becomes a practical problem + return true; + } } +end:; return are_types_identical(x, y); } -- cgit v1.2.3 From 4945168e6db97516e9af65a17aec83e3297f0f90 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 30 Sep 2025 10:48:05 +0100 Subject: Short circuit for `#raw_union` in `signature_parameter_similar_enough` --- src/check_decl.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 779dd6c12..528ff8abe 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -870,6 +870,9 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { // NOTE(bill): this must be changed when ABI changes return true; } + if (x_base->Struct.is_raw_union) { + return true; + } if (x->Struct.fields.count == y->Struct.fields.count) { for (isize i = 0; i < x->Struct.fields.count; i++) { Entity *a = x->Struct.fields[i]; -- cgit v1.2.3 From a769e341cbb56e1ed018a3581f22c52945f7190a Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Tue, 30 Sep 2025 11:49:14 -0400 Subject: Preempt field checking on `signature_parameter_similar_enough` with a type ptr equality check --- src/check_decl.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 528ff8abe..b61c23fa7 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -853,12 +853,17 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { Type *x_base = base_type(x); Type *y_base = base_type(y); + + if (x_base == y_base) { + return true; + } + if (x_base->kind == y_base->kind && x_base->kind == Type_Struct) { - i64 xs = type_size_of(x_base); + i64 xs = type_size_of(x_base); i64 ys = type_size_of(y_base); - i64 xa = type_align_of(x_base); + i64 xa = type_align_of(x_base); i64 ya = type_align_of(y_base); -- cgit v1.2.3 From 9f5bde4a6f2975f5b783a3e0f7d04f5ecd74b63d Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Wed, 8 Oct 2025 11:38:20 -0400 Subject: Perform type kind check before making use relevant fields in objc_superclass check --- src/check_decl.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index fa4eade0f..e54943d80 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -559,6 +559,11 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *super = ac.objc_superclass; while (super != nullptr) { + if (super->kind != Type_Named) { + error(e->token, "@(objc_superclass) Referenced type must be a named struct"); + break; + } + if (type_set_update(&super_set, super)) { error(e->token, "@(objc_superclass) Superclass hierarchy cycle encountered"); break; @@ -566,11 +571,6 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info); - if (super->kind != Type_Named) { - error(e->token, "@(objc_superclass) Referenced type must be a named struct"); - break; - } - Type* named_type = base_named_type(super); GB_ASSERT(named_type->kind == Type_Named); -- cgit v1.2.3 From 3a07a324117cc7562ecb3de2be7bb7aeb3b398fb Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Fri, 7 Nov 2025 21:38:34 -0500 Subject: Don't check procedure signature similarity when a foreign import proc is an Objective-C method. --- src/check_decl.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/check_decl.cpp') diff --git a/src/check_decl.cpp b/src/check_decl.cpp index e54943d80..27babd255 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1607,8 +1607,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { if (is_arch_wasm() && foreign_library != nullptr) { // NOTE(bill): this must be delayed because the foreign import paths might not be evaluated yet until much later mpsc_enqueue(&ctx->info->foreign_decls_to_check, e); - } else { - // TODO(harold): Check if it's an objective-C foreign, if so, I don't think we need to check it. + } else if (!e->Procedure.is_objc_impl_or_import) { check_foreign_procedure(ctx, e, d); } } else { -- cgit v1.2.3