diff --git a/core/rexcode/isa/arm64/tablegen/cpp-compiler/cpp-gen.odin b/core/rexcode/isa/arm64/tablegen/cpp-compiler/cpp-gen.odin index 2066027a5..cbe71fabd 100644 --- a/core/rexcode/isa/arm64/tablegen/cpp-compiler/cpp-gen.odin +++ b/core/rexcode/isa/arm64/tablegen/cpp-compiler/cpp-gen.odin @@ -905,16 +905,17 @@ main :: proc() { } return (enabled_features & (cast(u64)1 << cast(u64)f)) != 0; } - char const *feature_name(u32 f) const { + + char const *feature_name(Feature f) const { switch (f) { - case F_BASE: return "base"; - case F_FP: return "fp"; + case F_BASE: return ""; + case F_FP: return "fp-armv8"; case F_NEON: return "neon"; case F_CRYPTO: return "crypto"; case F_CRC32: return "crc"; case F_LSE: return "lse"; case F_LSE2: return "lse2"; - case F_FP16: return "fp16"; + case F_FP16: return "fullfp16"; case F_BF16: return "bf16"; case F_DOT: return "dotprod"; case F_PAC: return "pauth"; @@ -923,9 +924,9 @@ main :: proc() { case F_SVE: return "sve"; case F_SVE2: return "sve2"; case F_SME: return "sme"; - case F_AMX: return "amx"; + case F_AMX: return ""; // Apple AMX: no LLVM feature flag } - return "?"; + return ""; } u16 operand_type_transfer_bytes(OperandType t) const { gb_unused(t); @@ -942,6 +943,10 @@ main :: proc() { return false; } + String feature_name_from_form(Encoding const &form) const { + char const *str = feature_name(form.feature); + return make_string_c(str); + } """) strings.write_string(&sb, "\n\n") diff --git a/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin b/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin index bfe6dcc40..84cb4d67a 100644 --- a/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin +++ b/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin @@ -823,7 +823,7 @@ main :: proc() { return true; } char const *feature_name(u32 f) const { - return "?"; + return ""; } u16 operand_type_transfer_bytes(OperandType t) const { gb_unused(t); @@ -832,6 +832,10 @@ main :: proc() { bool operand_type_is_lane(OperandType t) const { return false; } + + String feature_name_from_form(Encoding const &form) const { + return {}; + } """) strings.write_string(&sb, "\n\n") diff --git a/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin b/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin index c9a8983cf..7e78e220e 100644 --- a/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin +++ b/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin @@ -799,7 +799,7 @@ main :: proc() { return true; } char const *feature_name(u32 f) const { - return "?"; + return ""; } u16 operand_type_transfer_bytes(OperandType t) const { gb_unused(t); @@ -808,6 +808,10 @@ main :: proc() { bool operand_type_is_lane(OperandType t) const { return false; } + + String feature_name_from_form(Encoding const &form) const { + return {}; + } """) strings.write_string(&sb, "\n\n") diff --git a/src/asm_tables_amd64.cpp b/src/asm_tables_amd64.cpp index 140f640c6..3a37baa78 100644 --- a/src/asm_tables_amd64.cpp +++ b/src/asm_tables_amd64.cpp @@ -803,7 +803,7 @@ struct Asm_amd64 { return true; } char const *feature_name(u32 f) const { - return "?"; + return ""; } u16 operand_type_transfer_bytes(OperandType t) const { gb_unused(t); @@ -813,6 +813,10 @@ struct Asm_amd64 { return false; } + String feature_name_from_form(Encoding const &form) const { + return {}; + } + int form_explicit_slot(Encoding const &form, int explicit_index) const { int seen = 0; for (int j = 0; j < gb_count_of(form.ops); j++) { diff --git a/src/asm_tables_arm64.cpp b/src/asm_tables_arm64.cpp index 3cce54a56..7f4361c45 100644 --- a/src/asm_tables_arm64.cpp +++ b/src/asm_tables_arm64.cpp @@ -1100,16 +1100,17 @@ struct Asm_arm64 { } return (enabled_features & (cast(u64)1 << cast(u64)f)) != 0; } - char const *feature_name(u32 f) const { + + char const *feature_name(Feature f) const { switch (f) { - case F_BASE: return "base"; - case F_FP: return "fp"; + case F_BASE: return ""; + case F_FP: return "fp-armv8"; case F_NEON: return "neon"; case F_CRYPTO: return "crypto"; case F_CRC32: return "crc"; case F_LSE: return "lse"; case F_LSE2: return "lse2"; - case F_FP16: return "fp16"; + case F_FP16: return "fullfp16"; case F_BF16: return "bf16"; case F_DOT: return "dotprod"; case F_PAC: return "pauth"; @@ -1118,9 +1119,9 @@ struct Asm_arm64 { case F_SVE: return "sve"; case F_SVE2: return "sve2"; case F_SME: return "sme"; - case F_AMX: return "amx"; + case F_AMX: return ""; // Apple AMX: no LLVM feature flag } - return "?"; + return ""; } u16 operand_type_transfer_bytes(OperandType t) const { gb_unused(t); @@ -1137,6 +1138,10 @@ struct Asm_arm64 { return false; } + String feature_name_from_form(Encoding const &form) const { + char const *str = feature_name(form.feature); + return make_string_c(str); + } int form_explicit_slot(Encoding const &form, int explicit_index) const { int seen = 0; diff --git a/src/asm_tables_riscv.cpp b/src/asm_tables_riscv.cpp index 2211ca8e0..d108305ab 100644 --- a/src/asm_tables_riscv.cpp +++ b/src/asm_tables_riscv.cpp @@ -773,7 +773,7 @@ struct Asm_riscv { return true; } char const *feature_name(u32 f) const { - return "?"; + return ""; } u16 operand_type_transfer_bytes(OperandType t) const { gb_unused(t); @@ -783,6 +783,10 @@ struct Asm_riscv { return false; } + String feature_name_from_form(Encoding const &form) const { + return {}; + } + int form_explicit_slot(Encoding const &form, int explicit_index) const { int seen = 0; for (int j = 0; j < gb_count_of(form.ops); j++) { diff --git a/src/check_asm.cpp b/src/check_asm.cpp index d9170f10c..268328cce 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -1597,6 +1597,42 @@ gb_internal bool check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm } } } + { + // Feature gate: the matched form may require a target feature (crc32b needs + // +crc, LSE atomics need +lse, ...). The form carries its Feature. It is + // satisfied if the feature is globally enabled for the target, OR if this + // template's own signature declares it via #require_target_feature / + // #enable_target_feature — a template opting in to a feature shouldn't be + // rejected just because the global build didn't enable it. + String feat = asm_ctx->feature_name_from_form(forms[valid_form_index]); + if (feat.len != 0) { + bool ok = check_target_feature_is_enabled(feat, nullptr); + + if (!ok) { + // The proc type may declare the feature itself. + Type *pt = base_type(tmpl_entity->type); + GB_ASSERT(pt->kind == Type_Proc); + String req = pt->Proc.require_target_feature; + String en = pt->Proc.enable_target_feature; + // These are comma-lists; `feat` is a single bare name. is_superset_of + // checks whether the declared list contains it. + if (req.len != 0 && check_target_feature_is_superset_of(req, feat, nullptr)) { + ok = true; + } + if (!ok && en.len != 0 && check_target_feature_is_superset_of(en, feat, nullptr)) { + ok = true; + } + } + + if (!ok) { + error(instr->name, + "'%.*s' requires the target feature '%.*s', which is not enabled; " + "enable it on this 'asm' template (e.g. @(enable_target_feature=\"%.*s\")), " + "or globally via '-target-features:\"%.*s\"' or a matching micro-architecture", + LIT(name), LIT(feat), LIT(feat), LIT(feat)); + } + } + } // Handle clobbering from mnemonic auto clobber = clobber_forms[valid_form_index]; @@ -2477,9 +2513,14 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity // always require the results of `asm` templates type->Proc.require_results = true; } - entity->type = type; + AttributeContext ac = make_attribute_context({}, {}); + if (d != nullptr) { + check_decl_attributes(ctx, d->attributes, proc_decl_attribute, &ac); + } + check_target_feature_attributes(ac, entity, type); + check_asm_specs(asm_ctx, ctx, ate->param_scope, at->specs, &ate->decls); bool is_pure_annotated = false; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index cdb1b9b54..49d695c8a 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1219,6 +1219,41 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon } } +gb_internal void check_target_feature_attributes(AttributeContext &ac, Entity *entity, Type *type) { + GB_ASSERT(type->kind == Type_Proc); + TypeProc *pt = &type->Proc; + if (ac.require_target_feature.len != 0 && ac.enable_target_feature.len != 0) { + error(entity->token, "A procedure cannot have both @(require_target_feature=\"...\") and @(enable_target_feature=\"...\")"); + } + + if (build_context.strict_target_features && ac.enable_target_feature.len != 0) { + ac.require_target_feature = ac.enable_target_feature; + ac.enable_target_feature.len = 0; + } + + if (ac.require_target_feature.len != 0) { + pt->require_target_feature = ac.require_target_feature; + String invalid; + if (!check_target_feature_is_valid_globally(ac.require_target_feature, &invalid)) { + error(entity->token, "Required target feature '%.*s' is not a valid target feature", LIT(invalid)); + } else if (!check_target_feature_is_enabled(ac.require_target_feature, nullptr)) { + entity->flags |= EntityFlag_Disabled; + } + } else if (ac.enable_target_feature.len != 0) { + + // NOTE: disallow wasm, features on that arch are always global to the module. + if (is_arch_wasm()) { + error(entity->token, "@(enable_target_feature=\"...\") is not allowed on wasm, features for wasm must be declared globally"); + } + + pt->enable_target_feature = ac.enable_target_feature; + String invalid; + if (!check_target_feature_is_valid_globally(ac.enable_target_feature, &invalid)) { + error(entity->token, "Procedure enabled target feature '%.*s' is not a valid target feature", LIT(invalid)); + } + } +} + gb_internal void check_foreign_procedure(CheckerContext *ctx, Entity *e, DeclInfo *d) { GB_ASSERT(e != nullptr); GB_ASSERT(e->kind == Entity_Procedure); @@ -1345,38 +1380,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { check_objc_methods(ctx, e, ac); - { - if (ac.require_target_feature.len != 0 && ac.enable_target_feature.len != 0) { - error(e->token, "A procedure cannot have both @(require_target_feature=\"...\") and @(enable_target_feature=\"...\")"); - } - - if (build_context.strict_target_features && ac.enable_target_feature.len != 0) { - ac.require_target_feature = ac.enable_target_feature; - ac.enable_target_feature.len = 0; - } - - if (ac.require_target_feature.len != 0) { - pt->require_target_feature = ac.require_target_feature; - String invalid; - if (!check_target_feature_is_valid_globally(ac.require_target_feature, &invalid)) { - error(e->token, "Required target feature '%.*s' is not a valid target feature", LIT(invalid)); - } else if (!check_target_feature_is_enabled(ac.require_target_feature, nullptr)) { - e->flags |= EntityFlag_Disabled; - } - } else if (ac.enable_target_feature.len != 0) { - - // NOTE: disallow wasm, features on that arch are always global to the module. - if (is_arch_wasm()) { - error(e->token, "@(enable_target_feature=\"...\") is not allowed on wasm, features for wasm must be declared globally"); - } - - pt->enable_target_feature = ac.enable_target_feature; - String invalid; - if (!check_target_feature_is_valid_globally(ac.enable_target_feature, &invalid)) { - error(e->token, "Procedure enabled target feature '%.*s' is not a valid target feature", LIT(invalid)); - } - } - } + check_target_feature_attributes(ac, e, proc_type); switch (e->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 5175ef58c..f4e633c02 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -9150,25 +9150,29 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c break; } - { + if (pt->kind == Type_Proc) { + char const *kind = "procedure"; + if (pt->Proc.calling_convention == ProcCC_InlineAsm) { + kind = "inline 'asm' template"; + } String invalid; - if (pt->kind == Type_Proc && pt->Proc.require_target_feature.len != 0) { + if (pt->Proc.require_target_feature.len != 0) { if (!check_target_feature_is_valid_for_target_arch(pt->Proc.require_target_feature, &invalid)) { - error(call, "Called procedure requires target feature '%.*s' which is invalid for the build target", LIT(invalid)); + error(call, "Called %s requires target feature '%.*s' which is invalid for the build target", kind, LIT(invalid)); } else if (!check_target_feature_is_enabled(pt->Proc.require_target_feature, &invalid)) { - error(call, "Calling this procedure requires target feature '%.*s' to be enabled", LIT(invalid)); + error(call, "Calling this %s requires target feature '%.*s' to be enabled", kind, LIT(invalid)); } } - if (pt->kind == Type_Proc && pt->Proc.enable_target_feature.len != 0) { + if (pt->Proc.enable_target_feature.len != 0) { if (!check_target_feature_is_valid_for_target_arch(pt->Proc.enable_target_feature, &invalid)) { - error(call, "Called procedure enables target feature '%.*s' which is invalid for the build target", LIT(invalid)); + error(call, "Called %s enables target feature '%.*s' which is invalid for the build target", kind, LIT(invalid)); } // NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features. if (is_call_inlined) { if (c->curr_proc_decl == nullptr) { - error(call, "Calling a '#force_inline' procedure that enables target features is not allowed at file scope"); + error(call, "Calling a '#force_inline' %s that enables target features is not allowed at file scope", kind); } else { Entity *e = c->curr_proc_decl->entity.load(); GB_ASSERT(e); @@ -9176,7 +9180,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c 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)); + error(call, "Inlined %s enables target feature '%.*s', this requires the calling %s to at least enable the same feature", kind, LIT(invalid), kind); error_line("\tSuggested Example: @(enable_target_feature=\"%.*s\")\n", LIT(invalid)); } diff --git a/src/checker.cpp b/src/checker.cpp index 2e161505d..f9e1f523f 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4564,6 +4564,35 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) { return false; } +gb_internal DECL_ATTRIBUTE_PROC(asm_decl_attribute) { + if (name == ATTRIBUTE_USER_TAG_NAME) { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind != ExactValue_String) { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; + } else if (name == "private") { + // NOTE(bill): Handled elsewhere `check_collect_value_decl` + return true; + } else if (name == "require_target_feature") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_String) { + ac->require_target_feature = ev.value_string; + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; + } else if (name == "enable_target_feature") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_String) { + ac->enable_target_feature = ev.value_string; + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; + } + return false; +} #include "check_expr.cpp" #include "check_builtin.cpp" diff --git a/src/llvm_backend_asm.cpp b/src/llvm_backend_asm.cpp index b6e648512..903ce8d35 100644 --- a/src/llvm_backend_asm.cpp +++ b/src/llvm_backend_asm.cpp @@ -1,4 +1,4 @@ -#define LLVM_ASM_DEBUG_PRINT true +#define LLVM_ASM_DEBUG_PRINT false struct lbAsmGenerate { Entity * tmpl_entity;