diff --git a/CMakeLists.txt b/CMakeLists.txt index e502901bd2..030398d71c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -568,6 +568,7 @@ set(ZIG_STD_FILES "special/compiler_rt/fixunstfdi.zig" "special/compiler_rt/fixunstfsi.zig" "special/compiler_rt/fixunstfti.zig" + "special/compiler_rt/floatuntidf.zig" "special/compiler_rt/muloti4.zig" "special/compiler_rt/index.zig" "special/compiler_rt/udivmod.zig" diff --git a/doc/langref.html.in b/doc/langref.html.in index 4070ef0ac0..1bd28f9c3e 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5035,8 +5035,12 @@ test "main" {
@floatToInt(comptime DestType: type, float: var) DestType
Converts the integer part of a floating point number to the destination type. - To convert the other way, use {#link|@intToFloat#}. This cast is always safe.
++ If the integer part of the floating point number cannot fit in the destination type, + it invokes safety-checked {#link|Undefined Behavior#}. +
+ {#see_also|@intToFloat#} {#header_close#} {#header_open|@frameAddress#} @@ -6207,7 +6211,10 @@ comptime { {#header_close#} {#header_open|Wrong Union Field Access#}TODO
+ {#header_close#} + {#header_open|Out of Bounds Float To Integer Cast#} +TODO
{#header_close#} {#header_close#} diff --git a/src/all_types.hpp b/src/all_types.hpp index 41c9fe18c3..12e054cbeb 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1434,6 +1434,7 @@ enum PanicMsgId { PanicMsgIdIncorrectAlignment, PanicMsgIdBadUnionField, PanicMsgIdBadEnumValue, + PanicMsgIdFloatToInt, PanicMsgIdCount, }; diff --git a/src/codegen.cpp b/src/codegen.cpp index 5f7fd60392..20268d636a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -865,6 +865,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { return buf_create_from_str("access of inactive union field"); case PanicMsgIdBadEnumValue: return buf_create_from_str("invalid enum value"); + case PanicMsgIdFloatToInt: + return buf_create_from_str("integer part of floating point value out of bounds"); } zig_unreachable(); } @@ -1671,7 +1673,7 @@ static LLVMValueRef gen_widen_or_shorten(CodeGen *g, bool want_runtime_safety, T return trunc_val; } LLVMValueRef orig_val; - if (actual_type->data.integral.is_signed) { + if (wanted_type->data.integral.is_signed) { orig_val = LLVMBuildSExt(g->builder, trunc_val, actual_type->type_ref, ""); } else { orig_val = LLVMBuildZExt(g->builder, trunc_val, actual_type->type_ref, ""); @@ -2546,15 +2548,41 @@ static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, } else { return LLVMBuildUIToFP(g->builder, expr_val, wanted_type->type_ref, ""); } - case CastOpFloatToInt: + case CastOpFloatToInt: { assert(wanted_type->id == TypeTableEntryIdInt); ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &cast_instruction->base)); + + bool want_safety = ir_want_runtime_safety(g, &cast_instruction->base); + + LLVMValueRef result; if (wanted_type->data.integral.is_signed) { - return LLVMBuildFPToSI(g->builder, expr_val, wanted_type->type_ref, ""); + result = LLVMBuildFPToSI(g->builder, expr_val, wanted_type->type_ref, ""); } else { - return LLVMBuildFPToUI(g->builder, expr_val, wanted_type->type_ref, ""); + result = LLVMBuildFPToUI(g->builder, expr_val, wanted_type->type_ref, ""); } + if (want_safety) { + LLVMValueRef back_to_float; + if (wanted_type->data.integral.is_signed) { + back_to_float = LLVMBuildSIToFP(g->builder, result, LLVMTypeOf(expr_val), ""); + } else { + back_to_float = LLVMBuildUIToFP(g->builder, result, LLVMTypeOf(expr_val), ""); + } + LLVMValueRef difference = LLVMBuildFSub(g->builder, expr_val, back_to_float, ""); + LLVMValueRef one_pos = LLVMConstReal(LLVMTypeOf(expr_val), 1.0f); + LLVMValueRef one_neg = LLVMConstReal(LLVMTypeOf(expr_val), -1.0f); + LLVMValueRef ok_bit_pos = LLVMBuildFCmp(g->builder, LLVMRealOLT, difference, one_pos, ""); + LLVMValueRef ok_bit_neg = LLVMBuildFCmp(g->builder, LLVMRealOGT, difference, one_neg, ""); + LLVMValueRef ok_bit = LLVMBuildAnd(g->builder, ok_bit_pos, ok_bit_neg, ""); + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "FloatCheckOk"); + LLVMBasicBlockRef bad_block = LLVMAppendBasicBlock(g->cur_fn_val, "FloatCheckFail"); + LLVMBuildCondBr(g->builder, ok_bit, ok_block, bad_block); + LLVMPositionBuilderAtEnd(g->builder, bad_block); + gen_safety_crash(g, PanicMsgIdFloatToInt); + LLVMPositionBuilderAtEnd(g->builder, ok_block); + } + return result; + } case CastOpBoolToInt: assert(wanted_type->id == TypeTableEntryIdInt); assert(actual_type->id == TypeTableEntryIdBool); diff --git a/src/ir.cpp b/src/ir.cpp index 55da8ad944..1fe078dd3f 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -9057,7 +9057,8 @@ static void copy_const_val(ConstExprValue *dest, ConstExprValue *src, bool same_ } } -static void eval_const_expr_implicit_cast(CastOp cast_op, +static bool eval_const_expr_implicit_cast(IrAnalyze *ira, IrInstruction *source_instr, + CastOp cast_op, ConstExprValue *other_val, TypeTableEntry *other_type, ConstExprValue *const_val, TypeTableEntry *new_type) { @@ -9129,6 +9130,20 @@ static void eval_const_expr_implicit_cast(CastOp cast_op, } case CastOpFloatToInt: float_init_bigint(&const_val->data.x_bigint, other_val); + if (new_type->id == TypeTableEntryIdInt) { + if (!bigint_fits_in_bits(&const_val->data.x_bigint, new_type->data.integral.bit_count, + new_type->data.integral.is_signed)) + { + Buf *int_buf = buf_alloc(); + bigint_append_buf(int_buf, &const_val->data.x_bigint, 10); + + ir_add_error(ira, source_instr, + buf_sprintf("integer value '%s' cannot be stored in type '%s'", + buf_ptr(int_buf), buf_ptr(&new_type->name))); + return false; + } + } + const_val->special = ConstValSpecialStatic; break; case CastOpBoolToInt: @@ -9136,6 +9151,7 @@ static void eval_const_expr_implicit_cast(CastOp cast_op, const_val->special = ConstValSpecialStatic; break; } + return true; } static IrInstruction *ir_resolve_cast(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, TypeTableEntry *wanted_type, CastOp cast_op, bool need_alloca) @@ -9145,8 +9161,11 @@ static IrInstruction *ir_resolve_cast(IrAnalyze *ira, IrInstruction *source_inst { IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, source_instr->source_node, wanted_type); - eval_const_expr_implicit_cast(cast_op, &value->value, value->value.type, - &result->value, wanted_type); + if (!eval_const_expr_implicit_cast(ira, source_instr, cast_op, &value->value, value->value.type, + &result->value, wanted_type)) + { + return ira->codegen->invalid_instruction; + } return result; } else { IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node, wanted_type, value, cast_op); diff --git a/std/math/expm1.zig b/std/math/expm1.zig index 438e44ccce..6fa0194b32 100644 --- a/std/math/expm1.zig +++ b/std/math/expm1.zig @@ -20,6 +20,10 @@ pub fn expm1(x: var) @typeOf(x) { fn expm1_32(x_: f32) f32 { @setFloatMode(this, builtin.FloatMode.Strict); + + if (math.isNan(x_)) + return math.nan(f32); + const o_threshold: f32 = 8.8721679688e+01; const ln2_hi: f32 = 6.9313812256e-01; const ln2_lo: f32 = 9.0580006145e-06; @@ -146,6 +150,10 @@ fn expm1_32(x_: f32) f32 { fn expm1_64(x_: f64) f64 { @setFloatMode(this, builtin.FloatMode.Strict); + + if (math.isNan(x_)) + return math.nan(f64); + const o_threshold: f64 = 7.09782712893383973096e+02; const ln2_hi: f64 = 6.93147180369123816490e-01; const ln2_lo: f64 = 1.90821492927058770002e-10; diff --git a/std/special/compiler_rt/floatuntidf.zig b/std/special/compiler_rt/floatuntidf.zig new file mode 100644 index 0000000000..3aabcb7b8a --- /dev/null +++ b/std/special/compiler_rt/floatuntidf.zig @@ -0,0 +1,60 @@ +const builtin = @import("builtin"); +const is_test = builtin.is_test; + +const DBL_MANT_DIG = 53; + +pub extern fn __floatuntidf(arg: u128) f64 { + @setRuntimeSafety(is_test); + + if (arg == 0) + return 0.0; + + var a = arg; + const N: u32 = @sizeOf(u128) * 8; + const sd = @bitCast(i32, N -% @clz(a)); // number of significant digits + var e: i32 = sd -% 1; // exponent + if (sd > DBL_MANT_DIG) { + // start: 0000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQxxxxxxxxxxxxxxxxxx + // finish: 000000000000000000000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQR + // 12345678901234567890123456 + // 1 = msb 1 bit + // P = bit DBL_MANT_DIG-1 bits to the right of 1 + // Q = bit DBL_MANT_DIG bits to the right of 1 + // R = "or" of all bits to the right of Q + switch (sd) { + DBL_MANT_DIG + 1 => { + a <<= 1; + }, + DBL_MANT_DIG + 2 => {}, + else => { + const shift_amt = @bitCast(i32, N +% (DBL_MANT_DIG + 2)) -% sd; + const shift_amt_u7 = @intCast(u7, shift_amt); + a = (a >> @intCast(u7, sd -% (DBL_MANT_DIG + 2))) | + @boolToInt((a & (u128(@maxValue(u128)) >> shift_amt_u7)) != 0); + }, + } + // finish + a |= @boolToInt((a & 4) != 0); // Or P into R + a +%= 1; // round - this step may add a significant bit + a >>= 2; // dump Q and R + // a is now rounded to DBL_MANT_DIG or DBL_MANT_DIG+1 bits + if ((a & (u128(1) << DBL_MANT_DIG)) != 0) { + a >>= 1; + e +%= 1; + } + // a is now rounded to DBL_MANT_DIG bits + } else { + a <<= @intCast(u7, DBL_MANT_DIG -% sd); + // a is now rounded to DBL_MANT_DIG bits + } + + const high: u64 = @bitCast(u32, (e +% 1023) << 20) | // exponent + (@truncate(u32, a >> 32) & 0x000FFFFF); // mantissa-high + const low = @truncate(u32, a); // mantissa-low + + return @bitCast(f64, low | (high << 32)); +} + +test "import floatuntidf" { + _ = @import("floatuntidf_test.zig"); +} diff --git a/std/special/compiler_rt/floatuntidf_test.zig b/std/special/compiler_rt/floatuntidf_test.zig new file mode 100644 index 0000000000..e2c79378e2 --- /dev/null +++ b/std/special/compiler_rt/floatuntidf_test.zig @@ -0,0 +1,81 @@ +const __floatuntidf = @import("floatuntidf.zig").__floatuntidf; +const assert = @import("std").debug.assert; + +fn test__floatuntidf(a: u128, expected: f64) void { + const x = __floatuntidf(a); + assert(x == expected); +} + +test "floatuntidf" { + test__floatuntidf(0, 0.0); + + test__floatuntidf(1, 1.0); + test__floatuntidf(2, 2.0); + test__floatuntidf(20, 20.0); + + test__floatuntidf(0x7FFFFF8000000000, 0x1.FFFFFEp+62); + test__floatuntidf(0x7FFFFFFFFFFFF800, 0x1.FFFFFFFFFFFFEp+62); + test__floatuntidf(0x7FFFFF0000000000, 0x1.FFFFFCp+62); + test__floatuntidf(0x7FFFFFFFFFFFF000, 0x1.FFFFFFFFFFFFCp+62); + + test__floatuntidf(make_ti(0x8000008000000000, 0), 0x1.000001p+127); + test__floatuntidf(make_ti(0x8000000000000800, 0), 0x1.0000000000001p+127); + test__floatuntidf(make_ti(0x8000010000000000, 0), 0x1.000002p+127); + test__floatuntidf(make_ti(0x8000000000001000, 0), 0x1.0000000000002p+127); + + test__floatuntidf(make_ti(0x8000000000000000, 0), 0x1.000000p+127); + test__floatuntidf(make_ti(0x8000000000000001, 0), 0x1.0000000000000002p+127); + + test__floatuntidf(0x0007FB72E8000000, 0x1.FEDCBAp+50); + + test__floatuntidf(0x0007FB72EA000000, 0x1.FEDCBA8p+50); + test__floatuntidf(0x0007FB72EB000000, 0x1.FEDCBACp+50); + test__floatuntidf(0x0007FB72EBFFFFFF, 0x1.FEDCBAFFFFFFCp+50); + test__floatuntidf(0x0007FB72EC000000, 0x1.FEDCBBp+50); + test__floatuntidf(0x0007FB72E8000001, 0x1.FEDCBA0000004p+50); + + test__floatuntidf(0x0007FB72E6000000, 0x1.FEDCB98p+50); + test__floatuntidf(0x0007FB72E7000000, 0x1.FEDCB9Cp+50); + test__floatuntidf(0x0007FB72E7FFFFFF, 0x1.FEDCB9FFFFFFCp+50); + test__floatuntidf(0x0007FB72E4000001, 0x1.FEDCB90000004p+50); + test__floatuntidf(0x0007FB72E4000000, 0x1.FEDCB9p+50); + + test__floatuntidf(0x023479FD0E092DC0, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DA1, 0x1.1A3CFE870496Dp+57); + test__floatuntidf(0x023479FD0E092DB0, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DB8, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DB6, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DBF, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DC1, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DC7, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DC8, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DCF, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DD0, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DD1, 0x1.1A3CFE870496Fp+57); + test__floatuntidf(0x023479FD0E092DD8, 0x1.1A3CFE870496Fp+57); + test__floatuntidf(0x023479FD0E092DDF, 0x1.1A3CFE870496Fp+57); + test__floatuntidf(0x023479FD0E092DE0, 0x1.1A3CFE870496Fp+57); + + test__floatuntidf(make_ti(0x023479FD0E092DC0, 0), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DA1, 1), 0x1.1A3CFE870496Dp+121); + test__floatuntidf(make_ti(0x023479FD0E092DB0, 2), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DB8, 3), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DB6, 4), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DBF, 5), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DC1, 6), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DC7, 7), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DC8, 8), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DCF, 9), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DD0, 0), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DD1, 11), 0x1.1A3CFE870496Fp+121); + test__floatuntidf(make_ti(0x023479FD0E092DD8, 12), 0x1.1A3CFE870496Fp+121); + test__floatuntidf(make_ti(0x023479FD0E092DDF, 13), 0x1.1A3CFE870496Fp+121); + test__floatuntidf(make_ti(0x023479FD0E092DE0, 14), 0x1.1A3CFE870496Fp+121); +} + +fn make_ti(high: u64, low: u64) u128 { + var result: u128 = high; + result <<= 64; + result |= low; + return result; +} diff --git a/std/special/compiler_rt/index.zig b/std/special/compiler_rt/index.zig index dc95aa23f2..6ad7768cb2 100644 --- a/std/special/compiler_rt/index.zig +++ b/std/special/compiler_rt/index.zig @@ -19,6 +19,8 @@ comptime { @export("__unordtf2", @import("comparetf2.zig").__unordtf2, linkage); + @export("__floatuntidf", @import("floatuntidf.zig").__floatuntidf, linkage); + @export("__fixunssfsi", @import("fixunssfsi.zig").__fixunssfsi, linkage); @export("__fixunssfdi", @import("fixunssfdi.zig").__fixunssfdi, linkage); @export("__fixunssfti", @import("fixunssfti.zig").__fixunssfti, linkage); diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 156835bcb3..7b36bcd04a 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -340,11 +340,23 @@ fn testPeerErrorAndArray2(x: u8) error![]const u8 { }; } -test "explicit cast float number literal to integer if no fraction component" { +test "@floatToInt" { + testFloatToInts(); + comptime testFloatToInts(); +} + +fn testFloatToInts() void { const x = i32(1e4); assert(x == 10000); const y = @floatToInt(i32, f32(1e4)); assert(y == 10000); + expectFloatToInt(u8, 255.1, 255); + expectFloatToInt(i8, 127.2, 127); + expectFloatToInt(i8, -128.2, -128); +} + +fn expectFloatToInt(comptime T: type, f: f32, i: T) void { + assert(@floatToInt(T, f) == i); } test "cast u128 to f128 and back" { @@ -426,3 +438,10 @@ test "@bytesToSlice keeps pointer alignment" { const numbers = @bytesToSlice(u32, bytes[0..]); comptime assert(@typeOf(numbers) == []align(@alignOf(@typeOf(bytes))) u32); } + +test "@intCast i32 to u7" { + var x: u128 = @maxValue(u128); + var y: i32 = 120; + var z = x >> @intCast(u7, y); + assert(z == 0xff); +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 609e3f103f..39107b4a21 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,23 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "@floatToInt comptime safety", + \\comptime { + \\ _ = @floatToInt(i8, f32(-129.1)); + \\} + \\comptime { + \\ _ = @floatToInt(u8, f32(-1.1)); + \\} + \\comptime { + \\ _ = @floatToInt(u8, f32(256.1)); + \\} + , + ".tmp_source.zig:2:9: error: integer value '-129' cannot be stored in type 'i8'", + ".tmp_source.zig:5:9: error: integer value '-1' cannot be stored in type 'u8'", + ".tmp_source.zig:8:9: error: integer value '256' cannot be stored in type 'u8'", + ); + cases.add( "use c_void as return type of fn ptr", \\export fn entry() void { diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index ea5beafe8d..a7e8d6dc0e 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -1,6 +1,45 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompareOutputContext) void { + cases.addRuntimeSafety("@floatToInt cannot fit - negative to unsigned", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() void { + \\ baz(bar(-1.1)); + \\} + \\fn bar(a: f32) u8 { + \\ return @floatToInt(u8, a); + \\} + \\fn baz(a: u8) void { } + ); + + cases.addRuntimeSafety("@floatToInt cannot fit - negative out of range", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() void { + \\ baz(bar(-129.1)); + \\} + \\fn bar(a: f32) i8 { + \\ return @floatToInt(i8, a); + \\} + \\fn baz(a: i8) void { } + ); + + cases.addRuntimeSafety("@floatToInt cannot fit - positive out of range", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() void { + \\ baz(bar(256.2)); + \\} + \\fn bar(a: f32) u8 { + \\ return @floatToInt(u8, a); + \\} + \\fn baz(a: u8) void { } + ); + cases.addRuntimeSafety("calling panic", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126);