diff --git a/releasenotes.md b/releasenotes.md index 40e7278ca..fecbd98ec 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -17,6 +17,7 @@ - Duplicate emit of expressions on negation would incorrectly compile negated macros. - Casting a slice address to its pointer type should not compile #1193. - Union is not properly zero-initialized with designated initializer #1194. +- Compile time fmod evaluates to 0 #1195. ### Stdlib changes - Add 'zstr' variants for `string::new_format` / `string::tformat`. diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 32d3d27ba..7b4f5c95e 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2015,6 +2015,7 @@ Float float_sub(Float op1, Float op2); Float float_mul(Float op1, Float op2); Float float_div(Float op1, Float op2); Float float_neg(Float op); +Float float_rem(Float op1, Float op2); Float float_from_string(const char *string, char **error); Float float_from_hex(const char *string, char **error); Int128 i128_from_double(double x); diff --git a/src/compiler/float.c b/src/compiler/float.c index 944c9e627..b1b333a03 100644 --- a/src/compiler/float.c +++ b/src/compiler/float.c @@ -85,6 +85,12 @@ Float float_div(Float op1, Float op2) return (Float){ op1.f / op2.f, op1.type }; } +Float float_rem(Float op1, Float op2) +{ + assert(op1.type == op2.type); + return (Float){fmod(op1.f, op2.f), op1.type }; +} + Float float_neg(Float op) { op.f = -op.f; diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 96abbf10c..0b8f11b43 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -5561,19 +5561,36 @@ static bool sema_expr_analyse_mod(SemaContext *context, Expr *expr, Expr *left, // 1. Analyse both sides and promote to a common type if (!sema_binary_analyse_arithmetic_subexpr(context, expr, NULL, false)) return false; - // 3. a % 0 is not valid, so detect it. - if (expr_is_const(right) && int_is_zero(right->const_expr.ixx)) + Type *flat = type_flatten(left->type); + if (type_is_float(flat)) { - SEMA_ERROR(right, "Cannot perform %% with a constant zero."); - return false; - } + // 3. a % 0 is not valid, so detect it. + if (expr_is_const(right) && right->const_expr.fxx.f == 0.0) + { + RETURN_SEMA_ERROR(right, "Cannot perform %% with a constant zero."); + } - // 4. Constant fold - if (expr_both_const(left, right) && sema_constant_fold_ops(left)) + // 4. Constant fold + if (expr_both_const(left, right) && sema_constant_fold_ops(left)) + { + expr_replace(expr, left); + // 4a. Remember this is remainder. + expr->const_expr.fxx = float_rem(left->const_expr.fxx, right->const_expr.fxx); + } + } + else { - expr_replace(expr, left); - // 4a. Remember this is remainder. - expr->const_expr.ixx = int_rem(left->const_expr.ixx, right->const_expr.ixx); + assert(type_is_integer(flat)); + // 3. a % 0 is not valid, so detect it. + if (expr_is_const(right) && int_is_zero(right->const_expr.ixx)) RETURN_SEMA_ERROR(right, "Cannot perform %% with a constant zero."); + + // 4. Constant fold + if (expr_both_const(left, right) && sema_constant_fold_ops(left)) + { + expr_replace(expr, left); + // 4a. Remember this is remainder. + expr->const_expr.ixx = int_rem(left->const_expr.ixx, right->const_expr.ixx); + } } expr->type = type_add_optional(left->type, IS_OPTIONAL(right)); diff --git a/test/test_suite/compile_time/mod_ct.c3t b/test/test_suite/compile_time/mod_ct.c3t new file mode 100644 index 000000000..3e7212419 --- /dev/null +++ b/test/test_suite/compile_time/mod_ct.c3t @@ -0,0 +1,17 @@ +// #target: macos-x64 +module test; +fn int main() +{ + double a = 4.5 % 5; + double b = -4.5 % 5; + int ai = 4 % 5; + int bi = -4 % 5; + return 0; +} + +/* #expect: test.ll + +store double 4.500000e+00, ptr %a, align 8 +store double -4.500000e+00, ptr %b, align 8 +store i32 4, ptr %ai, align 4 +store i32 -4, ptr %bi, align 4 \ No newline at end of file