From b0ee0bb63538a6d43e16bafad98bf97bdbd8e89a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 11 May 2026 11:55:07 +0100 Subject: [PATCH] Add `@(fast_math={...})` + `intrinsics.Fast_Math_Flags` --- base/intrinsics/intrinsics.odin | 1 + base/runtime/core.odin | 17 +++++++++ src/build_settings.cpp | 1 + src/check_decl.cpp | 2 ++ src/checker.cpp | 61 ++++++++++++++++++++++++++++++--- src/checker.hpp | 2 ++ src/entity.cpp | 3 ++ src/llvm_backend_opt.cpp | 52 ++++++++++++++++++++++++++++ src/types.cpp | 28 +++++++++++++++ 9 files changed, 163 insertions(+), 4 deletions(-) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index dd3b1d38e..e89036932 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -4,6 +4,7 @@ package intrinsics import "base:runtime" + // Package-Related is_package_imported :: proc(package_name: string) -> bool --- diff --git a/base/runtime/core.odin b/base/runtime/core.odin index d77c41dc8..c1ba7aa3e 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -23,6 +23,23 @@ package runtime import "base:intrinsics" +/* +Fast_Math_Flag :: enum u8 { + Allow_Reassoc = 0, + No_NaNs = 1, + No_Infs = 2, + No_Signed_Zeros = 3, + Allow_Reciprocal = 4, + Allow_Contract = 5, + Approx_Func = 6, +} +*/ +Fast_Math_Flag :: intrinsics.Fast_Math_Flag + +// Fast_Math_Flags :: distinct bit_set[Fast_Math_Flag; u32] +Fast_Math_Flags :: intrinsics.Fast_Math_Flags + + // NOTE(bill): This must match the compiler's Calling_Convention :: enum u8 { Invalid = 0, diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 699304a18..8f8a259d3 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -455,6 +455,7 @@ enum IntegerDivisionByZeroKind : u8 { IntegerDivisionByZero_AllBits, }; + // This stores the information for the specify architecture of this build struct BuildContext { // Constants diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 7bf1cd9bf..f571c7401 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1480,6 +1480,8 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { e->Procedure.no_sanitize_memory = ac.no_sanitize_memory; e->Procedure.no_sanitize_thread = ac.no_sanitize_thread; + e->Procedure.fast_math_flags = ac.fast_math_flags; + e->deprecated_message = ac.deprecated_message; e->warning_message = ac.warning_message; ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix); diff --git a/src/checker.cpp b/src/checker.cpp index 7d9aaf24f..1b668eff5 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1042,14 +1042,14 @@ struct GlobalEnumValue { i64 value; }; -gb_internal Slice add_global_enum_type(String const &type_name, GlobalEnumValue *values, isize value_count, Type **enum_type_ = nullptr) { +gb_internal Slice add_global_enum_type(String const &type_name, GlobalEnumValue *values, isize value_count, Type **enum_type_ = nullptr, Type *backing_type = nullptr) { Scope *scope = create_scope(nullptr, builtin_pkg->scope); Entity *entity = alloc_entity_type_name(scope, make_token_ident(type_name), nullptr, EntityState_Resolved); Type *enum_type = alloc_type_enum(); Type *named_type = alloc_type_named(type_name, enum_type, entity); set_base_type(named_type, enum_type); - enum_type->Enum.base_type = t_int; + enum_type->Enum.base_type = backing_type ? backing_type : t_int; enum_type->Enum.scope = scope; entity->type = named_type; @@ -1250,6 +1250,41 @@ gb_internal void init_universal(void) { add_global_enum_constant(fields, "ODIN_ERROR_POS_STYLE", build_context.ODIN_ERROR_POS_STYLE); } + { + GlobalEnumValue values[OdinFastMath_COUNT] = {}; + for (unsigned i = 0; i < OdinFastMath_COUNT; i++) { + values[i] = {OdinFastMathFlag_strings[i], i}; + } + + auto fields = add_global_enum_type(str_lit("Fast_Math_Flag"), values, gb_count_of(values), &t_fast_math_flag, t_u8); + + GB_ASSERT(t_fast_math_flag->kind == Type_Named); + scope_insert(intrinsics_pkg->scope, t_fast_math_flag->Named.type_name); + + Type *bs = alloc_type_bit_set(); + bs->BitSet.elem = t_fast_math_flag; + bs->BitSet.underlying = t_u32; + bs->BitSet.lower = 0; + bs->BitSet.upper = OdinFastMath_COUNT-1; + bs->BitSet.node = nullptr; + + + { + String type_name = str_lit("Fast_Math_Flags"); + + Scope *scope = create_scope(nullptr, builtin_pkg->scope); + Entity *entity = alloc_entity_type_name(scope, make_token_ident(type_name), nullptr, EntityState_Resolved); + + Type *named_type = alloc_type_named(type_name, bs, entity); + set_base_type(named_type, bs); + entity->type = named_type; + + t_fast_math_flags = named_type; + + scope_insert(intrinsics_pkg->scope, entity); + } + } + { GlobalEnumValue values[OdinAtomicMemoryOrder_COUNT] = { {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_relaxed], OdinAtomicMemoryOrder_relaxed}, @@ -3554,11 +3589,17 @@ gb_internal void init_preload(Checker *c) { init_core_objc_c(c); } -gb_internal ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value) { +gb_internal void check_expr_with_type_hint(CheckerContext *c, Operand *o, Ast *e, Type *t); + +gb_internal ExactValue check_decl_attribute_value(CheckerContext *c, Ast *value, Type *type_hint = nullptr) { ExactValue ev = {}; if (value != nullptr) { Operand op = {}; - check_expr(c, &op, value); + if (type_hint != nullptr) { + check_expr_with_type_hint(c, &op, value, type_hint); + } else { + check_expr(c, &op, value); + } if (op.mode) { if (op.mode == Addressing_Constant) { ev = op.value; @@ -4126,6 +4167,18 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } ac->no_sanitize_thread = true; return true; + } else if (name == "fast_math") { + if (value == nullptr) { + error(elem, "Expected a constant bit_set of type 'intrinsics.Fast_Math_Flags' for '%.*s'", LIT(name)); + } else { + ExactValue ev = check_decl_attribute_value(c, value, t_fast_math_flags); + if (ev.kind != ExactValue_Integer) { + error(elem, "Expected a constant bit_set of type 'intrinsics.Fast_Math_Flags' for '%.*s'", LIT(name)); + } else { + ac->fast_math_flags = exact_value_to_u64(ev); + } + } + return true; } return false; } diff --git a/src/checker.hpp b/src/checker.hpp index 5e295dc84..cd2e580d8 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -163,6 +163,8 @@ struct AttributeContext { String require_target_feature; // required by the target micro-architecture String enable_target_feature; // will be enabled for the procedure only + u64 fast_math_flags; + bool raddbg_type_view; String raddbg_type_view_string; }; diff --git a/src/entity.cpp b/src/entity.cpp index 7bb6e88ca..1879e9d05 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -256,6 +256,9 @@ struct Entity { struct GenProcsData *gen_procs; BlockingMutex gen_procs_mutex; ProcedureOptimizationMode optimization_mode; + + u64 fast_math_flags; + bool is_foreign : 1; bool is_export : 1; bool generated_from_polymorphic : 1; diff --git a/src/llvm_backend_opt.cpp b/src/llvm_backend_opt.cpp index cb7fe1c75..47ea90703 100644 --- a/src/llvm_backend_opt.cpp +++ b/src/llvm_backend_opt.cpp @@ -270,6 +270,55 @@ gb_internal void lb_populate_module_pass_manager(LLVMTargetMachineRef target_mac optimization of Odin programs **************************************************************************/ +gb_internal void lb_run_fast_float_math_pass(lbProcedure *p) { + Entity *e = p->entity; + if (e == nullptr) { + return; + } + GB_ASSERT(e->kind == Entity_Procedure); + + + u64 fast_math_flags = e->Procedure.fast_math_flags; + LLVMFastMathFlags llvm_flags = 0; + if (fast_math_flags & OdinFastMath_Allow_Reassoc) llvm_flags |= LLVMFastMathAllowReassoc; + if (fast_math_flags & OdinFastMath_No_NaNs) llvm_flags |= LLVMFastMathNoNaNs; + if (fast_math_flags & OdinFastMath_No_Infs) llvm_flags |= LLVMFastMathNoInfs; + if (fast_math_flags & OdinFastMath_No_Signed_Zeros) llvm_flags |= LLVMFastMathNoSignedZeros; + if (fast_math_flags & OdinFastMath_Allow_Reciprocal) llvm_flags |= LLVMFastMathAllowReciprocal; + if (fast_math_flags & OdinFastMath_Allow_Contract) llvm_flags |= LLVMFastMathAllowContract; + if (fast_math_flags & OdinFastMath_Approx_Func) llvm_flags |= LLVMFastMathApproxFunc; + + if (llvm_flags == 0) { + return; + } + + for (LLVMBasicBlockRef block = LLVMGetFirstBasicBlock(p->value); + block != nullptr; + block = LLVMGetNextBasicBlock(block)) { + for (LLVMValueRef instr = LLVMGetFirstInstruction(block); + instr != nullptr; + instr = LLVMGetNextInstruction(instr)) { + switch (LLVMGetInstructionOpcode(instr)) { + case LLVMFNeg: + case LLVMFAdd: + case LLVMFSub: + case LLVMFMul: + case LLVMFDiv: + case LLVMFRem: + case LLVMFPToUI: + case LLVMFPToSI: + case LLVMUIToFP: + case LLVMSIToFP: + case LLVMFPTrunc: + case LLVMFPExt: + case LLVMFCmp: + LLVMSetFastMathFlags(instr, llvm_flags); + break; + } + } + } +} + gb_internal void lb_run_remove_dead_instruction_pass(lbProcedure *p) { unsigned debug_declare_id = LLVMLookupIntrinsicID("llvm.dbg.declare", 16); GB_ASSERT(debug_declare_id != 0); @@ -475,6 +524,9 @@ gb_internal void lb_run_function_pass_manager(LLVMPassManagerRef fpm, lbProcedur if (p == nullptr) { return; } + + lb_run_fast_float_math_pass(p); + // NOTE(bill): LLVMAddDCEPass doesn't seem to be exported in the official DLL's for LLVM // which means we cannot rely upon it // This is also useful for read the .ll for debug purposes because a lot of instructions diff --git a/src/types.cpp b/src/types.cpp index f7975838d..f87d62297 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -805,6 +805,34 @@ gb_global Type *t_atomic_memory_order = nullptr; +enum OdinFastMathFlag : u8 { + OdinFastMath_Allow_Reassoc = 0, + OdinFastMath_No_NaNs = 1, + OdinFastMath_No_Infs = 2, + OdinFastMath_No_Signed_Zeros = 3, + OdinFastMath_Allow_Reciprocal = 4, + OdinFastMath_Allow_Contract = 5, + OdinFastMath_Approx_Func = 6, + + OdinFastMath_COUNT, +}; + +char const *OdinFastMathFlag_strings[OdinFastMath_COUNT] = { + "Allow_Reassoc", + "No_NaNs", + "No_Infs", + "No_Signed_Zeros", + "Allow_Reciprocal", + "Allow_Contract", + "Approx_Func", +}; + +gb_global Type *t_fast_math_flag = nullptr; // named enum +gb_global Type *t_fast_math_flags = nullptr; // named bit_set + + + + gb_global RecursiveMutex g_type_mutex; struct TypePath;