From c3b2029f649480b416f8113e7c83080f243fd971 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 17 Aug 2026 19:54:21 +0100 Subject: [PATCH] Support `asm` template groups --- src/check_asm.cpp | 8 +++ src/check_decl.cpp | 151 ++++++++++++++++++++++++++++++++++++++++++++- src/check_expr.cpp | 16 +++++ src/checker.cpp | 9 ++- src/entity.cpp | 1 + src/parser.cpp | 44 +++++++++++++ src/parser.hpp | 6 ++ src/parser_pos.cpp | 2 + 8 files changed, 235 insertions(+), 2 deletions(-) diff --git a/src/check_asm.cpp b/src/check_asm.cpp index e3ad7d6e9..86769af6a 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -1037,6 +1037,14 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext * gb_unused(label_scope); switch (expr->kind) { + case_ast_node(pe, ParenExpr, expr); + check_expr(ctx, operand, expr); + if (operand->mode != Addressing_Constant) { + error(expr, "Asm operands within parentheses can only compile time constants"); + } + return; + case_end; + case_ast_node(i, Ident, expr); Entity *found = scope_lookup_current(param_scope, i->interned, i->hash); if (found != nullptr) { diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 520aa5e5d..6e89dbfcd 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1985,6 +1985,151 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D check_objc_methods(ctx, pg_entity, ac); } +gb_internal void check_asm_group_decl(CheckerContext *ctx, Entity *asm_entity, DeclInfo *d) { + GB_ASSERT(asm_entity->kind == Entity_ProcGroup); + auto *pge = &asm_entity->ProcGroup; + String proc_group_name = asm_entity->token.string; + + ast_node(pg, AsmGroup, d->init_expr); + + pge->entities = array_make(permanent_allocator(), 0, pg->args.count); + + // NOTE(bill): This must be set here to prevent cycles in checking if someone + // places the entity within itself + asm_entity->type = t_invalid; + + PtrSet entity_set = {}; + ptr_set_init(&entity_set, 2*pg->args.count); + + for (Ast *arg_ : pg->args) { + Ast *arg = arg_; + Entity *e = nullptr; + Operand o = {}; + if (arg->kind == Ast_BinaryExpr && arg->BinaryExpr.op.kind == Token_where) { + Ast *cond_expr = arg->BinaryExpr.right; + Operand cond = {}; + check_expr(ctx, &cond, cond_expr); + if (cond.mode != Addressing_Invalid) { + if (cond.mode != Addressing_Constant || !is_type_boolean(cond.type) || cond.value.kind != ExactValue_Bool) { + error(arg, "Expected a constant binary expression for the 'where' clause"); + } else if (!cond.value.value_bool) { + continue; + } + } + + arg = arg->BinaryExpr.left; + } + + if (arg->kind == Ast_Ident) { + e = check_ident(ctx, &o, arg, nullptr, nullptr, true); + } else if (arg->kind == Ast_SelectorExpr) { + e = check_selector(ctx, &o, arg, nullptr); + } + if (e == nullptr) { + error(arg, "Expected a valid entity name in asm group, got %.*s", LIT(ast_strings[arg->kind])); + continue; + } + if (e->kind != Entity_AsmTemplate) { + error(arg, "Expected an asm template"); + continue; + } + + if (ptr_set_update(&entity_set, e)) { + error(arg, "Previous use of `%.*s` in asm group", LIT(e->token.string)); + continue; + } + array_add(&pge->entities, e); + } + + ptr_set_destroy(&entity_set); + + for (isize j = 0; j < pge->entities.count; j++) { + Entity *p = pge->entities[j]; + if (p->type == t_invalid) { + // NOTE(bill): This invalid overload has already been handled + continue; + } + + if (p->flags & EntityFlag_Disabled) { + continue; + } + + String name = p->token.string; + + for (isize k = j+1; k < pge->entities.count; k++) { + Entity *q = pge->entities[k]; + GB_ASSERT(p != q); + + bool is_invalid = false; + + TokenPos pos = q->token.pos; + + if (q->type == nullptr || q->type == t_invalid) { + continue; + } + + + ERROR_BLOCK(); + + if (q->flags & EntityFlag_Disabled) { + continue; + } + + ProcTypeOverloadKind kind = are_proc_types_overload_safe(p->type, q->type); + bool both_have_where_clauses = false; + if (p->decl_info != nullptr && q->decl_info != nullptr && + p->decl_info->proc_lit != nullptr && q->decl_info->proc_lit != nullptr) { + GB_ASSERT(p->decl_info->proc_lit->kind == Ast_ProcLit); + GB_ASSERT(q->decl_info->proc_lit->kind == Ast_ProcLit); + auto pl = &p->decl_info->proc_lit->ProcLit; + auto ql = &q->decl_info->proc_lit->ProcLit; + + // Allow collisions if the procedures both have 'where' clauses and are both polymorphic + bool pw = pl->where_token.kind != Token_Invalid && is_type_polymorphic(p->type, true); + bool qw = ql->where_token.kind != Token_Invalid && is_type_polymorphic(q->type, true); + both_have_where_clauses = pw && qw; + } + + if (!both_have_where_clauses) switch (kind) { + case ProcOverload_Identical: + error(p->token, "Overloaded asm template '%.*s' has the same type as another asm template in the asm template group '%.*s'", LIT(name), LIT(proc_group_name)); + is_invalid = true; + break; + // case ProcOverload_CallingConvention: + // error(p->token, "Overloaded asm template '%.*s' has the same type as another asm template in the asm template group '%.*s'", LIT(name), LIT(proc_group_name)); + // is_invalid = true; + // break; + case ProcOverload_ParamVariadic: + error(p->token, "Overloaded asm template '%.*s' has the same type as another asm template in the asm template group '%.*s'", LIT(name), LIT(proc_group_name)); + is_invalid = true; + break; + case ProcOverload_ResultCount: + case ProcOverload_ResultTypes: + error(p->token, "Overloaded asm template '%.*s' has the same parameters but different results in the asm template group '%.*s'", LIT(name), LIT(proc_group_name)); + is_invalid = true; + break; + case ProcOverload_Polymorphic: + break; + case ProcOverload_ParamCount: + case ProcOverload_ParamTypes: + case ProcOverload_TargetFeatures: + // This is okay :) + break; + + } + + if (is_invalid) { + error_line("\tprevious asm template at %s\n", token_pos_to_string(pos)); + q->type = t_invalid; + } + } + } + + AttributeContext ac = {}; + check_decl_attributes(ctx, d->attributes, proc_group_attribute, &ac); + check_objc_methods(ctx, asm_entity, ac); +} + #include "check_asm.cpp" @@ -2061,7 +2206,11 @@ gb_internal void check_entity_decl(CheckerContext *ctx, Entity *e, DeclInfo *d, check_proc_decl(&c, e, d); break; case Entity_ProcGroup: - check_proc_group_decl(&c, e, d); + if (e->ProcGroup.is_asm_group) { + check_asm_group_decl(&c, e, d); + } else { + check_proc_group_decl(&c, e, d); + } break; case Entity_AsmTemplate: diff --git a/src/check_expr.cpp b/src/check_expr.cpp index dd6449c61..1bc79a10a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -12518,6 +12518,12 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast o->mode = Addressing_Invalid; case_end; + case_ast_node(ag, AsmGroup, node); + error(node, "Illegal use of a asm group"); + o->mode = Addressing_Invalid; + case_end; + + case_ast_node(pl, ProcLit, node); CheckerContext ctx = *c; @@ -13039,6 +13045,16 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan str = gb_string_append_rune(str, '}'); case_end; + case_ast_node(pg, AsmGroup, node); + str = gb_string_appendc(str, "asm{"); + for_array(i, pg->args) { + if (i > 0) str = gb_string_appendc(str, ", "); + str = write_expr_to_string(str, pg->args[i], shorthand); + } + str = gb_string_append_rune(str, '}'); + case_end; + + case_ast_node(pl, ProcLit, node); str = write_expr_to_string(str, pl->type, shorthand); if (pl->body) { diff --git a/src/checker.cpp b/src/checker.cpp index 22f557275..d6ca0b350 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -5064,7 +5064,14 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { if (fl != nullptr) { error(name, "Procedure groups are not allowed within a foreign block"); } - } else if (init->kind == Ast_AsmTemplate) { + } else if (init->kind == Ast_AsmGroup) { + ast_node(ag, AsmGroup, init); + e = alloc_entity_proc_group(d->scope, token, nullptr); + e->ProcGroup.is_asm_group = true; + if (fl != nullptr) { + error(name, "Asm template groups are not allowed within a foreign block"); + } + }else if (init->kind == Ast_AsmTemplate) { if (c->scope->flags&ScopeFlag_Type) { error(name, "Asm templates are not allowed within a struct"); continue; diff --git a/src/entity.cpp b/src/entity.cpp index ea5317447..8248515f4 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -316,6 +316,7 @@ struct Entity { bool is_objc_class_method : 1; } Procedure; struct { + bool is_asm_group; Array entities; } ProcGroup; struct { diff --git a/src/parser.cpp b/src/parser.cpp index 02c35687e..99175c3fa 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -237,6 +237,9 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { case Ast_ProcGroup: n->ProcGroup.args = clone_ast_array(n->ProcGroup.args, f); break; + case Ast_AsmGroup: + n->AsmGroup.args = clone_ast_array(n->AsmGroup.args, f); + break; case Ast_ProcLit: n->ProcLit.type = clone_ast(n->ProcLit.type, f); n->ProcLit.body = clone_ast(n->ProcLit.body, f); @@ -975,6 +978,15 @@ gb_internal Ast *ast_proc_group(AstFile *f, Token token, Token open, Token close return result; } +gb_internal Ast *ast_asm_group(AstFile *f, Token token, Token open, Token close, Array const &args) { + Ast *result = alloc_ast_node(f, Ast_AsmGroup); + result->AsmGroup.token = token; + result->AsmGroup.open = open; + result->AsmGroup.close = close; + result->AsmGroup.args = slice_from_array(args); + return result; +} + gb_internal Ast *ast_proc_lit(AstFile *f, Ast *type, Ast *body, u64 tags, Token where_token, Array const &where_clauses) { Ast *result = alloc_ast_node(f, Ast_ProcLit); result->ProcLit.type = type; @@ -2466,6 +2478,8 @@ gb_internal Ast *parse_asm_operand(AstFile *f, bool allow_memory_operand) { case Token_Float: case Token_Rune: return ast_basic_lit(f, advance_token(f)); + case Token_OpenParen: + return parse_expr(f, false); case Token_OpenBracket: if (allow_memory_operand) { Token open = expect_token(f, Token_OpenBracket); @@ -3484,6 +3498,36 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { } case Token_asm: + if (peek_token(f).kind == Token_OpenBrace) { // asm group + Token token = expect_token(f, Token_asm); + Token open = expect_token(f, Token_OpenBrace); + + auto args = array_make(ast_allocator(f)); + + while (f->curr_token.kind != Token_CloseBrace && + f->curr_token.kind != Token_EOF) { + Ast *elem = parse_expr(f, false); + + if (f->curr_token.kind == Token_where) { + Token where = expect_token(f, Token_where); + Ast *cond = parse_expr(f, false); + elem = ast_binary_expr(f, where, elem, cond); + } + + array_add(&args, elem); + if (!allow_field_separator(f)) { + break; + } + } + + Token close = expect_token(f, Token_CloseBrace); + + if (args.count == 0) { + syntax_error(token, "Expected a least 1 argument in a procedure group"); + } + + return ast_asm_group(f, token, open, close, args); + } return parse_asm_template(f); } diff --git a/src/parser.hpp b/src/parser.hpp index d39b28589..e945cadc3 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -451,6 +451,12 @@ struct AstSplitArgs { Token close; \ Slice args; \ }) \ + AST_KIND(AsmGroup, "asm group", struct { \ + Token token; \ + Token open; \ + Token close; \ + Slice args; \ + }) \ AST_KIND(ProcLit, "procedure literal", struct { \ Ast *type; \ Ast *body; \ diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index 99f1ce65d..70ba92728 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -6,6 +6,7 @@ gb_internal Token ast_token(Ast *node) { case Ast_BasicLit: return node->BasicLit.token; case Ast_BasicDirective: return node->BasicDirective.token; case Ast_ProcGroup: return node->ProcGroup.token; + case Ast_AsmGroup: return node->AsmGroup.token; case Ast_ProcLit: return ast_token(node->ProcLit.type); case Ast_CompoundLit: if (node->CompoundLit.type != nullptr) { @@ -164,6 +165,7 @@ Token ast_end_token(Ast *node) { case Ast_BasicLit: return node->BasicLit.token; case Ast_BasicDirective: return node->BasicDirective.token; case Ast_ProcGroup: return node->ProcGroup.close; + case Ast_AsmGroup: return node->AsmGroup.close; case Ast_ProcLit: if (node->ProcLit.body) { return ast_end_token(node->ProcLit.body);