Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 99 additions & 39 deletions src/check_asm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,26 @@ gb_internal bool check_asm_is_immediate_param(Entity *tmpl_entity, Operand const
return false;
}

// NOTE(bill): a template opting in to a feature with @(require_target_feature) or @(enable_target_feature)
// must not be rejected because the global build did not enable it
gb_internal bool check_asm_template_feature_is_enabled(Entity *tmpl_entity, String const &feature) {
if (check_target_feature_is_enabled(feature, nullptr)) {
return true;
}
Type *pt = tmpl_entity->AsmTemplate.proc_type;
GB_ASSERT(pt->kind == Type_Proc);
String req = pt->Proc.require_target_feature;
String en = pt->Proc.enable_target_feature;

if (req.len != 0 && check_target_feature_is_superset_of(req, feature, nullptr)) {
return true;
}
if (en.len != 0 && check_target_feature_is_superset_of(en, feature, nullptr)) {
return true;
}
return false;
}


template <typename AsmCtx>
gb_internal void check_asm_specs(AsmCtx *asm_ctx, CheckerContext *ctx, Scope *scope, Slice<Ast *> const &specs, Array<AsmTemplateEntityDecl> *asm_template_entity_decls) {
Expand Down Expand Up @@ -1681,31 +1701,14 @@ 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.
// NOTE(bill): Feature Gating
// 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)
// see `check_asm_template_feature_is_enabled`
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;
}
}
bool ok = check_asm_template_feature_is_enabled(tmpl_entity, feat);

if (!ok) {
error(instr->name,
Expand Down Expand Up @@ -2272,16 +2275,24 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext *
return;
}

if (found->kind != Entity_Constant) {
// NOTE(bill): resolved like any other identifier, as the constant may not have been checked yet,
// e.g. one declared later in the same block, or one in a lazy file
found = resolve_alias_entity(ctx, found, nullptr);
if (found->kind == Entity_Constant) {
GB_ASSERT(ctx->scope == param_scope->parent);
check_ident(ctx, operand, expr, nullptr, nullptr, false);
if (operand->mode == Addressing_Invalid) {
// NOTE(bill): already reported, e.g. an illegal declaration cycle
return;
}
}
if (operand->mode != Addressing_Constant) {
error(expr, "Only asm parameters or constants are allowed to be used within an 'asm' template");
operand->mode = Addressing_Invalid;
operand->type = t_invalid;
return;
}

add_entity_use(ctx, expr, entity);
operand->mode = Addressing_Constant;
operand->value = found->Constant.value;
operand->type = found->type;

add_type_and_value(ctx, expr, operand->mode, operand->type, operand->value);
return;
case_end;
Expand Down Expand Up @@ -2867,8 +2878,8 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext *
}


template <typename AsmCtx>
gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *entity, DeclInfo *d) {
// NOTE(bill): the signature is everything needed to call an asm template, the rest is checked by `check_asm_template_body`
gb_internal bool check_asm_template_signature(CheckerContext *ctx, Entity *entity, DeclInfo *d) {
GB_ASSERT(entity->kind == Entity_AsmTemplate);
auto *ate = &entity->AsmTemplate;

Expand All @@ -2880,7 +2891,8 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity
GB_ASSERT(at->signature != nullptr);
if (at->signature->kind != Ast_ProcType) {
error(at->signature, "Expected a valid signature, got %.*s", LIT(ast_strings[at->signature->kind]));
return;
entity->type = t_invalid;
return false;
}
AstProcType *pt = &at->signature->ProcType;

Expand All @@ -2898,13 +2910,27 @@ 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;
entity->type = type;
ate->proc_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);
return true;
}

// NOTE(bill): the specification block, the clobbers, and the instructions
template <typename AsmCtx>
gb_internal void check_asm_template_body(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *entity, DeclInfo *d) {
GB_ASSERT(entity->kind == Entity_AsmTemplate);
auto *ate = &entity->AsmTemplate;

ast_node(at, AsmTemplate, d->init_expr);

Type *type = ate->proc_type;
GB_ASSERT(type != nullptr && type->kind == Type_Proc);

check_asm_specs(asm_ctx, ctx, ate->param_scope, at->specs, &ate->decls);

Expand Down Expand Up @@ -3286,7 +3312,7 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity
if (is_type_simd_vector(ed.entity->type)) {
i32 vw = cast(i32)(type_size_of(ed.entity->type) * 8);
String feat = asm_ctx->required_vector_feature(vw);
if (feat.len != 0 && !check_target_feature_is_enabled(feat, nullptr)) {
if (feat.len != 0 && !check_asm_template_feature_is_enabled(entity, feat)) {
error(ed.entity->token,
"'asm' vector operand '%.*s' is %d-bit, which requires the '%.*s' target feature",
LIT(ed.entity->token.string), vw, LIT(feat));
Expand Down Expand Up @@ -3391,7 +3417,7 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity
}

GB_ASSERT(entity->kind == Entity_AsmTemplate);
if (results->Tuple.variables.count == 0 && !entity->AsmTemplate.is_volatile &&
if (type->Proc.result_count == 0 && !entity->AsmTemplate.is_volatile &&
!entity->AsmTemplate.clobber_memory &&
entity->AsmTemplate.has_observable_side_effect) {
warning(entity->token,
Expand Down Expand Up @@ -3434,13 +3460,47 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity
}

gb_internal void check_asm_template_from_entity(CheckerContext *c, Entity *e, DeclInfo *d) {
GB_ASSERT(e->decl_info == d);
if (!check_asm_template_signature(c, e, d)) {
return;
}
switch (build_context.metrics.arch) {
case TargetArch_amd64:
case TargetArch_riscv64:
case TargetArch_arm64:
check_asm_template_body_later(c, e);
break;
default:
error(e->token, "asm templates are not currently supported for this target");
break;
}
}

gb_internal void check_asm_template_body_from_entity(CheckerContext *c, Entity *e) {
GB_ASSERT(e->kind == Entity_AsmTemplate);
DeclInfo *d = e->decl_info;
GB_ASSERT(d != nullptr);

Entity *anonymous_entity = e->AsmTemplate.node->AsmTemplate.anonymous_entity;
if (anonymous_entity != nullptr && anonymous_entity != e) {
// NOTE(bill): the call it is in was checked again and only the entity it made last is used
return;
}

CheckerContext ctx = *c;
ctx.scope = d->scope;
ctx.decl = d;

if (build_context.metrics.arch == TargetArch_amd64) {
check_asm_template(&g_asm_amd64, c, e, d);
check_asm_template_body(&g_asm_amd64, &ctx, e, d);
} else if (build_context.metrics.arch == TargetArch_riscv64) {
check_asm_template(&g_asm_riscv, c, e, d);
check_asm_template_body(&g_asm_riscv, &ctx, e, d);
} else if (build_context.metrics.arch == TargetArch_arm64) {
check_asm_template(&g_asm_arm64, c, e, d);
check_asm_template_body(&g_asm_arm64, &ctx, e, d);
} else {
error(e->token, "asm templates are not currently supported for this target");
GB_PANIC("asm templates are not supported for this target");
}

add_deps_from_child_to_parent(d);
e->flags |= EntityFlag_ProcBodyChecked;
}
2 changes: 1 addition & 1 deletion src/check_asm_cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont
GB_ASSERT(entity->kind == Entity_AsmTemplate);
auto *ate = &entity->AsmTemplate;
auto const &decls = ate->decls;
bool diverging = entity->type->Proc.diverging;
bool diverging = ate->proc_type->Proc.diverging;

if (cfg->blocks.count == 0) {
// With an empty body, the CFG cannot really do nothing
Expand Down
13 changes: 11 additions & 2 deletions src/check_decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2206,10 +2206,10 @@ gb_internal void check_asm_group_decl(CheckerContext *ctx, Entity *asm_entity, D
check_objc_methods(ctx, asm_entity, ac);
}

#include "check_asm.cpp"

gb_internal void add_deps_from_child_to_parent(DeclInfo *decl);

#include "check_asm.cpp"

gb_internal void check_entity_decl(CheckerContext *ctx, Entity *e, DeclInfo *d, Type *named_type) {
if (e->state == EntityState_Resolved) {
return;
Expand Down Expand Up @@ -2512,6 +2512,8 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
return false;
}

ctx->asm_templates_to_check = &decl->proc->asm_templates_to_check;

check_open_scope(ctx, body);
{
ctx->scope->decl_info = decl;
Expand Down Expand Up @@ -2570,6 +2572,13 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
}
check_close_scope(ctx);

// NOTE(bill): with every name in the body now known, see `check_asm_template_body_later`
ctx->asm_templates_to_check = nullptr;
for (Entity *e : decl->proc->asm_templates_to_check) {
check_asm_template_body_from_entity(ctx, e);
}
array_clear(&decl->proc->asm_templates_to_check);

check_scope_usage(ctx->checker, ctx->scope, check_vet_flags(body));

add_deps_from_child_to_parent(decl);
Expand Down
33 changes: 31 additions & 2 deletions src/check_expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9729,6 +9729,8 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
DeclInfo *d = make_decl_info(c->scope, c->decl);
Entity *e = alloc_entity_asm_template(d->scope, token, nullptr, unnested_proc);
d->init_expr = unnested_proc;
e->decl_info = d;
e->file = c->file;
at->anonymous_entity = e;

check_asm_template_from_entity(c, e, d);
Expand Down Expand Up @@ -9987,8 +9989,35 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
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 (pt->Proc.calling_convention == ProcCC_InlineAsm) {
// NOTE(bill): an asm template is always inlined, so its instructions are assembled with the features
// of the procedure calling it, which must then have every feature the template enables
String scope_enable = {};
String scope_require = {};
if (c->curr_proc_sig != nullptr && c->curr_proc_sig->kind == Type_Proc) {
scope_enable = c->curr_proc_sig->Proc.enable_target_feature;
scope_require = c->curr_proc_sig->Proc.require_target_feature;
}
String_Iterator it = {pt->Proc.enable_target_feature, 0};
String feature = {};
while (string_split_iterator_next(&it, ',', &feature)) {
if (check_target_feature_is_enabled(feature, nullptr) ||
check_target_feature_is_superset_of(scope_enable, feature, nullptr) ||
check_target_feature_is_superset_of(scope_require, feature, nullptr)) {
continue;
}
ERROR_BLOCK();
if (c->curr_proc_sig == nullptr) {
error(call, "Called %s enables target feature '%.*s', which must be enabled globally to call it at file scope", kind, LIT(feature));
error_line("\tSuggested Example: -target-features:\"%.*s\"\n", LIT(feature));
} else {
error(call, "Called %s enables target feature '%.*s', this requires the calling procedure to at least enable the same feature, as the template is inlined into it", kind, LIT(feature));
error_line("\tSuggested Example: @(enable_target_feature=\"%.*s\")\n", LIT(feature));
}
break;
}
} else if (is_call_inlined) {
// NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features.
if (c->curr_proc_decl == nullptr) {
error(call, "Calling a '#force_inline' %s that enables target features is not allowed at file scope", kind);
} else {
Expand Down
Loading
Loading