From 29b488245daa9210ed9b5e1ffb7290024677f0db Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 20 May 2017 23:06:32 -0400 Subject: [PATCH] add setFloatMode builtin and std.math.floor * skip installing std/rand_test.zig as it's not needed beyond running the std lib tests * add std.math.floor function * add setFloatMode builtin function to choose between builtin.FloatMode.Optimized (default) and builtin.FloatMode.Strict (Optimized is equivalent to -ffast-math in gcc) --- CMakeLists.txt | 1 - src/all_types.hpp | 18 +++++ src/codegen.cpp | 66 +++++++++++++++---- src/ir.cpp | 142 ++++++++++++++++++++++++++++++++++++++-- src/ir_print.cpp | 11 ++++ std/math.zig | 84 ++++++++++++++++++++++-- test/cases/eval.zig | 12 +++- test/compile_errors.zig | 18 +++++ 8 files changed, 328 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a44a9c7780..c2a1715d2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,7 +232,6 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/os/linux_x86_64.zig" DESTINATION "${ZIG_S install(FILES "${CMAKE_SOURCE_DIR}/std/os/path.zig" DESTINATION "${ZIG_STD_DEST}/os") install(FILES "${CMAKE_SOURCE_DIR}/std/os/windows.zig" DESTINATION "${ZIG_STD_DEST}/os") install(FILES "${CMAKE_SOURCE_DIR}/std/rand.zig" DESTINATION "${ZIG_STD_DEST}") -install(FILES "${CMAKE_SOURCE_DIR}/std/rand_test.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/sort.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/special/bootstrap.zig" DESTINATION "${ZIG_STD_DEST}/special") install(FILES "${CMAKE_SOURCE_DIR}/std/special/build_file_template.zig" DESTINATION "${ZIG_STD_DEST}/special") diff --git a/src/all_types.hpp b/src/all_types.hpp index aaf5c248ef..ae9913f26b 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1193,6 +1193,7 @@ enum BuiltinFnId { BuiltinFnIdTruncate, BuiltinFnIdIntType, BuiltinFnIdSetDebugSafety, + BuiltinFnIdSetFloatMode, BuiltinFnIdTypeName, BuiltinFnIdCanImplicitCast, BuiltinFnIdSetGlobalAlign, @@ -1580,6 +1581,8 @@ struct ScopeDecls { HashMap decl_table; bool safety_off; AstNode *safety_set_node; + bool fast_math_off; + AstNode *fast_math_set_node; ImportTableEntry *import; // If this is a scope from a container, this is the type entry, otherwise null TypeTableEntry *container_type; @@ -1593,6 +1596,8 @@ struct ScopeBlock { HashMap label_table; bool safety_off; AstNode *safety_set_node; + bool fast_math_off; + AstNode *fast_math_set_node; }; // This scope is created from every defer expression. @@ -1720,6 +1725,7 @@ enum IrInstructionId { IrInstructionIdToPtrType, IrInstructionIdPtrTypeChild, IrInstructionIdSetDebugSafety, + IrInstructionIdSetFloatMode, IrInstructionIdArrayType, IrInstructionIdSliceType, IrInstructionIdAsm, @@ -2078,6 +2084,13 @@ struct IrInstructionSetDebugSafety { IrInstruction *debug_safety_on; }; +struct IrInstructionSetFloatMode { + IrInstruction base; + + IrInstruction *scope_value; + IrInstruction *mode_value; +}; + struct IrInstructionArrayType { IrInstruction base; @@ -2550,4 +2563,9 @@ static const size_t enum_gen_union_index = 1; static const size_t err_union_err_index = 0; static const size_t err_union_payload_index = 1; +enum FloatMode { + FloatModeStrict, + FloatModeOptimized, +}; + #endif diff --git a/src/codegen.cpp b/src/codegen.cpp index 5e752d9a69..23b6acfa01 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -581,6 +581,24 @@ static LLVMValueRef get_handle_value(CodeGen *g, LLVMValueRef ptr, TypeTableEntr } } +static bool ir_want_fast_math(CodeGen *g, IrInstruction *instruction) { + // TODO memoize + Scope *scope = instruction->scope; + while (scope) { + if (scope->id == ScopeIdBlock) { + ScopeBlock *block_scope = (ScopeBlock *)scope; + if (block_scope->fast_math_set_node) + return !block_scope->fast_math_off; + } else if (scope->id == ScopeIdDecls) { + ScopeDecls *decls_scope = (ScopeDecls *)scope; + if (decls_scope->fast_math_set_node) + return !decls_scope->fast_math_off; + } + scope = scope->parent; + } + return true; +} + static bool ir_want_debug_safety(CodeGen *g, IrInstruction *instruction) { if (g->build_mode == BuildModeFastRelease) return false; @@ -1151,9 +1169,12 @@ enum DivKind { DivKindExact, }; -static LLVMValueRef gen_div(CodeGen *g, bool want_debug_safety, LLVMValueRef val1, LLVMValueRef val2, +static LLVMValueRef gen_div(CodeGen *g, bool want_debug_safety, bool want_fast_math, + LLVMValueRef val1, LLVMValueRef val2, TypeTableEntry *type_entry, DivKind div_kind) { + ZigLLVMSetFastMath(g->builder, want_fast_math); + LLVMValueRef zero = LLVMConstNull(type_entry->type_ref); if (want_debug_safety) { LLVMValueRef is_zero_bit; @@ -1287,9 +1308,12 @@ enum RemKind { RemKindMod, }; -static LLVMValueRef gen_rem(CodeGen *g, bool want_debug_safety, LLVMValueRef val1, LLVMValueRef val2, +static LLVMValueRef gen_rem(CodeGen *g, bool want_debug_safety, bool want_fast_math, + LLVMValueRef val1, LLVMValueRef val2, TypeTableEntry *type_entry, RemKind rem_kind) { + ZigLLVMSetFastMath(g->builder, want_fast_math); + LLVMValueRef zero = LLVMConstNull(type_entry->type_ref); if (want_debug_safety) { LLVMValueRef is_zero_bit; @@ -1372,6 +1396,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, case IrBinOpCmpLessOrEq: case IrBinOpCmpGreaterOrEq: if (type_entry->id == TypeTableEntryIdFloat) { + ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base)); LLVMRealPredicate pred = cmp_op_to_real_predicate(op_id); return LLVMBuildFCmp(g->builder, pred, op1_value, op2_value, ""); } else if (type_entry->id == TypeTableEntryIdInt) { @@ -1396,6 +1421,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, case IrBinOpAdd: case IrBinOpAddWrap: if (type_entry->id == TypeTableEntryIdFloat) { + ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base)); return LLVMBuildFAdd(g->builder, op1_value, op2_value, ""); } else if (type_entry->id == TypeTableEntryIdInt) { bool is_wrapping = (op_id == IrBinOpAddWrap); @@ -1442,6 +1468,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, case IrBinOpSub: case IrBinOpSubWrap: if (type_entry->id == TypeTableEntryIdFloat) { + ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base)); return LLVMBuildFSub(g->builder, op1_value, op2_value, ""); } else if (type_entry->id == TypeTableEntryIdInt) { bool is_wrapping = (op_id == IrBinOpSubWrap); @@ -1460,6 +1487,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, case IrBinOpMult: case IrBinOpMultWrap: if (type_entry->id == TypeTableEntryIdFloat) { + ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base)); return LLVMBuildFMul(g->builder, op1_value, op2_value, ""); } else if (type_entry->id == TypeTableEntryIdInt) { bool is_wrapping = (op_id == IrBinOpMultWrap); @@ -1476,17 +1504,23 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, zig_unreachable(); } case IrBinOpDivUnspecified: - return gen_div(g, want_debug_safety, op1_value, op2_value, type_entry, DivKindFloat); + return gen_div(g, want_debug_safety, ir_want_fast_math(g, &bin_op_instruction->base), + op1_value, op2_value, type_entry, DivKindFloat); case IrBinOpDivExact: - return gen_div(g, want_debug_safety, op1_value, op2_value, type_entry, DivKindExact); + return gen_div(g, want_debug_safety, ir_want_fast_math(g, &bin_op_instruction->base), + op1_value, op2_value, type_entry, DivKindExact); case IrBinOpDivTrunc: - return gen_div(g, want_debug_safety, op1_value, op2_value, type_entry, DivKindTrunc); + return gen_div(g, want_debug_safety, ir_want_fast_math(g, &bin_op_instruction->base), + op1_value, op2_value, type_entry, DivKindTrunc); case IrBinOpDivFloor: - return gen_div(g, want_debug_safety, op1_value, op2_value, type_entry, DivKindFloor); + return gen_div(g, want_debug_safety, ir_want_fast_math(g, &bin_op_instruction->base), + op1_value, op2_value, type_entry, DivKindFloor); case IrBinOpRemRem: - return gen_rem(g, want_debug_safety, op1_value, op2_value, type_entry, RemKindRem); + return gen_rem(g, want_debug_safety, ir_want_fast_math(g, &bin_op_instruction->base), + op1_value, op2_value, type_entry, RemKindRem); case IrBinOpRemMod: - return gen_rem(g, want_debug_safety, op1_value, op2_value, type_entry, RemKindMod); + return gen_rem(g, want_debug_safety, ir_want_fast_math(g, &bin_op_instruction->base), + op1_value, op2_value, type_entry, RemKindMod); } zig_unreachable(); } @@ -1602,6 +1636,7 @@ static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, } case CastOpFloatToInt: assert(wanted_type->id == TypeTableEntryIdInt); + ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &cast_instruction->base)); if (wanted_type->data.integral.is_signed) { return LLVMBuildFPToSI(g->builder, expr_val, wanted_type->type_ref, ""); } else { @@ -1774,6 +1809,7 @@ static LLVMValueRef ir_render_un_op(CodeGen *g, IrExecutable *executable, IrInst case IrUnOpNegationWrap: { if (expr_type->id == TypeTableEntryIdFloat) { + ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &un_op_instruction->base)); return LLVMBuildFNeg(g->builder, expr, ""); } else if (expr_type->id == TypeTableEntryIdInt) { if (op_id == IrUnOpNegationWrap) { @@ -2986,6 +3022,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdPtrTypeChild: case IrInstructionIdFieldPtr: case IrInstructionIdSetDebugSafety: + case IrInstructionIdSetFloatMode: case IrInstructionIdArrayType: case IrInstructionIdSliceType: case IrInstructionIdSizeOf: @@ -4432,6 +4469,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdIntType, "IntType", 2); create_builtin_fn(g, BuiltinFnIdSetDebugSafety, "setDebugSafety", 2); + create_builtin_fn(g, BuiltinFnIdSetFloatMode, "setFloatMode", 2); create_builtin_fn(g, BuiltinFnIdSetGlobalAlign, "setGlobalAlign", 2); create_builtin_fn(g, BuiltinFnIdSetGlobalSection, "setGlobalSection", 2); create_builtin_fn(g, BuiltinFnIdSetGlobalLinkage, "setGlobalLinkage", 2); @@ -4588,6 +4626,15 @@ static void define_builtin_compile_vars(CodeGen *g) { } buf_appendf(contents, "};\n\n"); } + { + buf_appendf(contents, + "pub const FloatMode = enum {\n" + " Strict,\n" + " Optimized,\n" + "};\n\n"); + assert(FloatModeStrict == 0); + assert(FloatModeOptimized == 1); + } buf_appendf(contents, "pub const is_big_endian = %s;\n", bool_to_str(g->is_big_endian)); buf_appendf(contents, "pub const is_test = %s;\n", bool_to_str(g->is_test_build)); buf_appendf(contents, "pub const os = Os.%s;\n", cur_os); @@ -4674,9 +4721,6 @@ static void init(CodeGen *g) { g->builder = LLVMCreateBuilder(); g->dbuilder = ZigLLVMCreateDIBuilder(g->module, true); - ZigLLVMSetFastMath(g->builder, true); - - Buf *producer = buf_sprintf("zig %s", ZIG_VERSION_STRING); const char *flags = ""; unsigned runtime_version = 0; diff --git a/src/ir.cpp b/src/ir.cpp index 3c14d808db..cc0e364487 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -297,6 +297,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionSetDebugSafety * return IrInstructionIdSetDebugSafety; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionSetFloatMode *) { + return IrInstructionIdSetFloatMode; +} + static constexpr IrInstructionId ir_instruction_id(IrInstructionArrayType *) { return IrInstructionIdArrayType; } @@ -1190,6 +1194,19 @@ static IrInstruction *ir_build_set_debug_safety(IrBuilder *irb, Scope *scope, As return &instruction->base; } +static IrInstruction *ir_build_set_float_mode(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *scope_value, IrInstruction *mode_value) +{ + IrInstructionSetFloatMode *instruction = ir_build_instruction(irb, scope, source_node); + instruction->scope_value = scope_value; + instruction->mode_value = mode_value; + + ir_ref_instruction(scope_value, irb->current_basic_block); + ir_ref_instruction(mode_value, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_build_array_type(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *size, IrInstruction *child_type) { @@ -2361,6 +2378,14 @@ static IrInstruction *ir_instruction_setdebugsafety_get_dep(IrInstructionSetDebu } } +static IrInstruction *ir_instruction_setfloatmode_get_dep(IrInstructionSetFloatMode *instruction, size_t index) { + switch (index) { + case 0: return instruction->scope_value; + case 1: return instruction->mode_value; + default: return nullptr; + } +} + static IrInstruction *ir_instruction_arraytype_get_dep(IrInstructionArrayType *instruction, size_t index) { switch (index) { case 0: return instruction->size; @@ -2897,6 +2922,8 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t return ir_instruction_ptrtypechild_get_dep((IrInstructionPtrTypeChild *) instruction, index); case IrInstructionIdSetDebugSafety: return ir_instruction_setdebugsafety_get_dep((IrInstructionSetDebugSafety *) instruction, index); + case IrInstructionIdSetFloatMode: + return ir_instruction_setfloatmode_get_dep((IrInstructionSetFloatMode *) instruction, index); case IrInstructionIdArrayType: return ir_instruction_arraytype_get_dep((IrInstructionArrayType *) instruction, index); case IrInstructionIdSliceType: @@ -3841,6 +3868,20 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_set_debug_safety(irb, scope, node, arg0_value, arg1_value); } + case BuiltinFnIdSetFloatMode: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + AstNode *arg1_node = node->data.fn_call_expr.params.at(1); + IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope); + if (arg1_value == irb->codegen->invalid_instruction) + return arg1_value; + + return ir_build_set_float_mode(irb, scope, node, arg0_value, arg1_value); + } case BuiltinFnIdSizeof: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -7740,6 +7781,16 @@ static Buf *ir_resolve_str(IrAnalyze *ira, IrInstruction *value) { return result; } +static ConstExprValue *get_builtin_value(CodeGen *codegen, const char *name) { + Tld *tld = codegen->compile_var_import->decls_scope->decl_table.get(buf_create_from_str(name)); + resolve_top_level_decl(codegen, tld, false); + assert(tld->id == TldIdVar); + TldVar *tld_var = (TldVar *)tld; + ConstExprValue *var_value = tld_var->var->value; + assert(var_value != nullptr); + return var_value; +} + static TypeTableEntry *ir_analyze_instruction_return(IrAnalyze *ira, IrInstructionReturn *return_instruction) { @@ -10556,7 +10607,7 @@ static TypeTableEntry *ir_analyze_instruction_set_debug_safety(IrAnalyze *ira, AstNode *source_node = set_debug_safety_instruction->base.source_node; if (*safety_set_node_ptr) { ErrorMsg *msg = ir_add_error_node(ira, source_node, - buf_sprintf("function test attribute set twice")); + buf_sprintf("debug safety set twice for same scope")); add_error_note(ira->codegen, msg, *safety_set_node_ptr, buf_sprintf("first set here")); return ira->codegen->builtin_types.entry_invalid; } @@ -10567,6 +10618,86 @@ static TypeTableEntry *ir_analyze_instruction_set_debug_safety(IrAnalyze *ira, return ira->codegen->builtin_types.entry_void; } +static TypeTableEntry *ir_analyze_instruction_set_float_mode(IrAnalyze *ira, + IrInstructionSetFloatMode *instruction) +{ + IrInstruction *target_instruction = instruction->scope_value->other; + TypeTableEntry *target_type = target_instruction->value.type; + if (type_is_invalid(target_type)) + return ira->codegen->builtin_types.entry_invalid; + ConstExprValue *target_val = ir_resolve_const(ira, target_instruction, UndefBad); + if (!target_val) + return ira->codegen->builtin_types.entry_invalid; + + if (ira->new_irb.exec->is_inline) { + // ignore setFloatMode when running functions at compile time + ir_build_const_from(ira, &instruction->base); + return ira->codegen->builtin_types.entry_void; + } + + bool *fast_math_off_ptr; + AstNode **fast_math_set_node_ptr; + if (target_type->id == TypeTableEntryIdBlock) { + ScopeBlock *block_scope = (ScopeBlock *)target_val->data.x_block; + fast_math_off_ptr = &block_scope->fast_math_off; + fast_math_set_node_ptr = &block_scope->fast_math_set_node; + } else if (target_type->id == TypeTableEntryIdFn) { + FnTableEntry *target_fn = target_val->data.x_fn.fn_entry; + assert(target_fn->def_scope); + fast_math_off_ptr = &target_fn->def_scope->fast_math_off; + fast_math_set_node_ptr = &target_fn->def_scope->fast_math_set_node; + } else if (target_type->id == TypeTableEntryIdMetaType) { + ScopeDecls *decls_scope; + TypeTableEntry *type_arg = target_val->data.x_type; + if (type_arg->id == TypeTableEntryIdStruct) { + decls_scope = type_arg->data.structure.decls_scope; + } else if (type_arg->id == TypeTableEntryIdEnum) { + decls_scope = type_arg->data.enumeration.decls_scope; + } else if (type_arg->id == TypeTableEntryIdUnion) { + decls_scope = type_arg->data.unionation.decls_scope; + } else { + ir_add_error_node(ira, target_instruction->source_node, + buf_sprintf("expected scope reference, found type '%s'", buf_ptr(&type_arg->name))); + return ira->codegen->builtin_types.entry_invalid; + } + fast_math_off_ptr = &decls_scope->fast_math_off; + fast_math_set_node_ptr = &decls_scope->fast_math_set_node; + } else { + ir_add_error_node(ira, target_instruction->source_node, + buf_sprintf("expected scope reference, found type '%s'", buf_ptr(&target_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + ConstExprValue *float_mode_val = get_builtin_value(ira->codegen, "FloatMode"); + assert(float_mode_val->type->id == TypeTableEntryIdMetaType); + TypeTableEntry *float_mode_enum_type = float_mode_val->data.x_type; + + IrInstruction *float_mode_value = instruction->mode_value->other; + if (type_is_invalid(float_mode_value->value.type)) + return ira->codegen->builtin_types.entry_invalid; + IrInstruction *casted_value = ir_implicit_cast(ira, float_mode_value, float_mode_enum_type); + if (type_is_invalid(casted_value->value.type)) + return ira->codegen->builtin_types.entry_invalid; + ConstExprValue *mode_val = ir_resolve_const(ira, casted_value, UndefBad); + if (!mode_val) + return ira->codegen->builtin_types.entry_invalid; + + bool want_fast_math = (mode_val->data.x_enum.tag == FloatModeOptimized); + + AstNode *source_node = instruction->base.source_node; + if (*fast_math_set_node_ptr) { + ErrorMsg *msg = ir_add_error_node(ira, source_node, + buf_sprintf("float mode set twice for same scope")); + add_error_note(ira->codegen, msg, *fast_math_set_node_ptr, buf_sprintf("first set here")); + return ira->codegen->builtin_types.entry_invalid; + } + *fast_math_set_node_ptr = source_node; + *fast_math_off_ptr = !want_fast_math; + + ir_build_const_from(ira, &instruction->base); + return ira->codegen->builtin_types.entry_void; +} + static TypeTableEntry *ir_analyze_instruction_slice_type(IrAnalyze *ira, IrInstructionSliceType *slice_type_instruction) { @@ -11864,11 +11995,7 @@ static TypeTableEntry *ir_analyze_instruction_type_id(IrAnalyze *ira, if (type_is_invalid(type_entry)) return ira->codegen->builtin_types.entry_invalid; - Tld *tld = ira->codegen->compile_var_import->decls_scope->decl_table.get(buf_create_from_str("TypeId")); - resolve_top_level_decl(ira->codegen, tld, false); - assert(tld->id == TldIdVar); - TldVar *tld_var = (TldVar *)tld; - ConstExprValue *var_value = tld_var->var->value; + ConstExprValue *var_value = get_builtin_value(ira->codegen, "TypeId"); assert(var_value->type->id == TypeTableEntryIdMetaType); TypeTableEntry *result_type = var_value->data.x_type; @@ -13271,6 +13398,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_set_global_linkage(ira, (IrInstructionSetGlobalLinkage *)instruction); case IrInstructionIdSetDebugSafety: return ir_analyze_instruction_set_debug_safety(ira, (IrInstructionSetDebugSafety *)instruction); + case IrInstructionIdSetFloatMode: + return ir_analyze_instruction_set_float_mode(ira, (IrInstructionSetFloatMode *)instruction); case IrInstructionIdSliceType: return ir_analyze_instruction_slice_type(ira, (IrInstructionSliceType *)instruction); case IrInstructionIdAsm: @@ -13482,6 +13611,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdReturn: case IrInstructionIdUnreachable: case IrInstructionIdSetDebugSafety: + case IrInstructionIdSetFloatMode: case IrInstructionIdImport: case IrInstructionIdCompileErr: case IrInstructionIdCompileLog: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 6dd0009faf..583e1bcf90 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -358,6 +358,14 @@ static void ir_print_set_debug_safety(IrPrint *irp, IrInstructionSetDebugSafety fprintf(irp->f, ")"); } +static void ir_print_set_float_mode(IrPrint *irp, IrInstructionSetFloatMode *instruction) { + fprintf(irp->f, "@setFloatMode("); + ir_print_other_instruction(irp, instruction->scope_value); + fprintf(irp->f, ", "); + ir_print_other_instruction(irp, instruction->mode_value); + fprintf(irp->f, ")"); +} + static void ir_print_array_type(IrPrint *irp, IrInstructionArrayType *instruction) { fprintf(irp->f, "["); ir_print_other_instruction(irp, instruction->size); @@ -965,6 +973,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdSetDebugSafety: ir_print_set_debug_safety(irp, (IrInstructionSetDebugSafety *)instruction); break; + case IrInstructionIdSetFloatMode: + ir_print_set_float_mode(irp, (IrInstructionSetFloatMode *)instruction); + break; case IrInstructionIdArrayType: ir_print_array_type(irp, (IrInstructionArrayType *)instruction); break; diff --git a/std/math.zig b/std/math.zig index 4941f4060c..0dae77552f 100644 --- a/std/math.zig +++ b/std/math.zig @@ -252,18 +252,92 @@ fn testRem() { fn isNan(comptime T: type, x: T) -> bool { assert(@typeId(T) == builtin.TypeId.Float); - const bits = floatBits(x); if (T == f32) { + const bits = bitCast(u32, x); return (bits & 0x7fffffff) > 0x7f800000; } else if (T == f64) { + const bits = bitCast(u64, x); return (bits & (@maxValue(u64) >> 1)) > (u64(0x7ff) << 52); + } else if (T == c_longdouble) { + @compileError("TODO support isNan for c_longdouble"); } else { unreachable; } } -fn floatBits(comptime T: type, x: T) -> @IntType(false, T.bit_count) { - assert(@typeId(T) == builtin.TypeId.Float); - const uint = @IntType(false, T.bit_count); - return *@intToPtr(&const uint, &x); +// TODO this should be a builtin +fn bitCast(comptime DestType: type, value: var) -> DestType { + assert(@sizeOf(DestType) == @sizeOf(@typeOf(value))); + return *@ptrCast(&const DestType, &value); +} + +pub fn floor(x: var) -> @typeOf(x) { + switch (@typeOf(x)) { + f32 => floor_f32(x), + f64 => floor_f64(x), + c_longdouble => @compileError("TODO support floor for c_longdouble"), + else => @compileError("Invalid type for floor: " ++ @typeName(@typeOf(x))), + } +} + +fn floor_f32(x: f32) -> f32 { + var i = bitCast(u32, x); + const e = i32((i >> 23) & 0xff) -% 0x7f; + if (e >= 23) + return x; + if (e >= 0) { + const m = bitCast(u32, 0x007fffff >> e); + if ((i & m) == 0) + return x; + if (i >> 31 != 0) + i +%= m; + i &= ~m; + } else { + if (i >> 31 == 0) + return 0; + if (i <<% 1 != 0) + return -1.0; + } + return bitCast(f32, i); +} + +fn floor_f64(x: f64) -> f64 { + const DBL_EPSILON = 2.22044604925031308085e-16; + const toint = 1.0 / DBL_EPSILON; + + var i = bitCast(u64, x); + const e = (i >> 52) & 0x7ff; + + if (e >= 0x3ff +% 52 or x == 0) + return x; + // y = int(x) - x, where int(x) is an integer neighbor of x + const y = { + @setFloatMode(this, builtin.FloatMode.Strict); + if (i >> 63 != 0) { + x - toint + toint - x + } else { + x + toint - toint - x + } + }; + // special case because of non-nearest rounding modes + if (e <= 0x3ff - 1) { + if (i >> 63 != 0) + return -1.0; + return 0.0; + } + if (y > 0) + return x + y - 1; + return x + y; +} + +test "math.floor" { + assert(floor(f32(1.234)) == 1.0); + assert(floor(f32(-1.234)) == -2.0); + assert(floor(f32(999.0)) == 999.0); + assert(floor(f32(-999.0)) == -999.0); + + assert(floor(f64(1.234)) == 1.0); + assert(floor(f64(-1.234)) == -2.0); + assert(floor(f64(999.0)) == 999.0); + assert(floor(f64(-999.0)) == -999.0); } diff --git a/test/cases/eval.zig b/test/cases/eval.zig index 35fc27a3e0..160acbb2c9 100644 --- a/test/cases/eval.zig +++ b/test/cases/eval.zig @@ -1,4 +1,5 @@ const assert = @import("std").debug.assert; +const builtin = @import("builtin"); test "compileTimeRecursion" { assert(some_data.len == 21); @@ -222,7 +223,7 @@ test "comptimeIterateOverFnPtrList" { assert(performFn('w', 99) == 99); } -test "evalSetDebugSafetyAtCompileTime" { +test "eval @setDebugSafety at compile-time" { const result = comptime fnWithSetDebugSafety(); assert(result == 1234); } @@ -232,6 +233,15 @@ fn fnWithSetDebugSafety() -> i32{ return 1234; } +test "eval @setFloatMode at compile-time" { + const result = comptime fnWithFloatMode(); + assert(result == 1234.0); +} + +fn fnWithFloatMode() -> f32 { + @setFloatMode(this, builtin.FloatMode.Strict); + return 1234.0; +} const SimpleStruct = struct { diff --git a/test/compile_errors.zig b/test/compile_errors.zig index a985949b6a..999e1eaa6d 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1835,4 +1835,22 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} , ".tmp_source.zig:3:20: error: cast from 'u16' to 'u8' truncates bits"); + + cases.add("@setDebugSafety twice for same scope", + \\export fn foo() { + \\ @setDebugSafety(this, false); + \\ @setDebugSafety(this, false); + \\} + , + ".tmp_source.zig:3:5: error: debug safety set twice for same scope", + ".tmp_source.zig:2:5: note: first set here"); + + cases.add("@setFloatMode twice for same scope", + \\export fn foo() { + \\ @setFloatMode(this, @import("builtin").FloatMode.Optimized); + \\ @setFloatMode(this, @import("builtin").FloatMode.Optimized); + \\} + , + ".tmp_source.zig:3:5: error: float mode set twice for same scope", + ".tmp_source.zig:2:5: note: first set here"); }