diff --git a/core/prelude/operators/arithmetic.carbon b/core/prelude/operators/arithmetic.carbon index 2e07af4cb2e6..cd052e8eff2b 100644 --- a/core/prelude/operators/arithmetic.carbon +++ b/core/prelude/operators/arithmetic.carbon @@ -140,6 +140,14 @@ impl CharLiteral as SubWith(Self) where .Result = IntLiteral { // Operations for FloatLiteral. These need to be here because FloatLiteral has no // associated library of its own. +impl FloatLiteral as AddWith(Self) where .Result = Self { + fn Op(self, other: Self) -> Self = "float.add"; +} + impl FloatLiteral as Negate where .Result = Self { fn Op(self) -> Self = "float.negate"; } + +impl FloatLiteral as SubWith(Self) where .Result = Self { + fn Op(self, other: Self) -> Self = "float.sub"; +} diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 9919ccbbcba9..808707079859 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -13,8 +13,11 @@ #include "llvm/ADT/APFloat.h" #include "llvm/Support/ConvertUTF.h" #include "toolchain/base/canonical_value_store.h" +#include "toolchain/base/int.h" #include "toolchain/base/kind_switch.h" +#include "toolchain/base/value_ids.h" #include "toolchain/check/action.h" +#include "toolchain/check/context.h" #include "toolchain/check/cpp/constant.h" #include "toolchain/check/diagnostic_helpers.h" #include "toolchain/check/eval_inst.h" @@ -29,6 +32,7 @@ #include "toolchain/diagnostics/diagnostic.h" #include "toolchain/diagnostics/emitter.h" #include "toolchain/diagnostics/format_providers.h" +#include "toolchain/lex/token_kind.h" #include "toolchain/sem_ir/builtin_function_kind.h" #include "toolchain/sem_ir/constant.h" #include "toolchain/sem_ir/declared_facet_type.h" @@ -2176,6 +2180,15 @@ static auto PerformBuiltinIntComparison(Context& context, return MakeBoolResult(context, bool_type_id, result); } +static auto NegateRealLiteral(Real val) -> Real { + // Check if negation would overflow. + if (val.mantissa.isMinSignedValue()) { + val.mantissa = val.mantissa.sext(val.mantissa.getBitWidth() + 1); + } + val.mantissa.negate(); + return val; +} + // Performs a builtin unary float -> float operation. static auto PerformBuiltinUnaryFloatOp(Context& context, SemIR::BuiltinFunctionKind builtin_kind, @@ -2188,14 +2201,7 @@ static auto PerformBuiltinUnaryFloatOp(Context& context, context.insts().TryGetAs(arg_id)) { auto real_val = context.reals().Get(literal->real_id); - // Check if negation would overflow. - if (real_val.mantissa.isMinSignedValue()) { - real_val.mantissa = - real_val.mantissa.sext(real_val.mantissa.getBitWidth() + 1); - } - real_val.mantissa.negate(); - - return MakeFloatLiteralResult(context, std::move(real_val)); + return MakeFloatLiteralResult(context, NegateRealLiteral(real_val)); } auto op = context.insts().GetAs(arg_id); @@ -2206,12 +2212,227 @@ static auto PerformBuiltinUnaryFloatOp(Context& context, return MakeFloatResult(context, op.type_id, std::move(op_val)); } +// Adds two APInts handling overflow by growing result. +// Assumes lhs and rhs have same bit width. +static auto OverflowAdd(const llvm::APInt& lhs, const llvm::APInt& rhs) + -> llvm::APInt { + CARBON_CHECK(lhs.getBitWidth() == rhs.getBitWidth()); + bool is_negative = lhs.isNegative(); + + bool overflow = false; + llvm::APInt result = lhs.sadd_ov(rhs, overflow); + if (overflow) { + unsigned old_width = lhs.getBitWidth(); + unsigned new_width = old_width + 1; + result = result.zext(new_width); + if (is_negative) { + // For positive value overflow, zero-extension is sufficient, for negative + // overflow we need to re-apply the dropped sign bits. + result.setBits(old_width, new_width); + } + } + return result; +} + +struct FactoredExponent { + int twos = 0; + int fives = 0; +}; + +// Decomposes Real's exponents into form 2^a * 5^b where a and b are 32-bit +// ints. Returns nullopt if exponent is too large. +static auto TryGetFactoredExponent(const Real& real) + -> std::optional { + if (real.exponent.getSignificantBits() > 32) { + // Reject evaluation if we can't fit exponent into int. + return std::nullopt; + } + auto exponent = static_cast(real.exponent.getZExtValue()); + return FactoredExponent{ + .twos = exponent, + .fives = real.is_decimal ? exponent : 0, + }; +} + +// Constructs a Real from mantissa and factored exponent. +static auto RecombineFactoredExponent(llvm::APInt mantissa, + const FactoredExponent& exponent) + -> Real { + CARBON_CHECK(exponent.fives == exponent.twos || exponent.fives == 0, + "exponent must by dyadic or decadic"); + return { + .mantissa = mantissa, + .exponent = llvm::APInt(32, exponent.twos, /*isSigned=*/true), + .is_decimal = exponent.fives == exponent.twos, + }; +} + +// Finds a dyadic or decadic exponent that both lhs and rhs can be converted to. +static auto FindCommonExponent(FactoredExponent lhs, FactoredExponent rhs) + -> FactoredExponent { + // In general common exponent of x^a and x^b is x^min(a,b) because we can + // always subtract from exponent (and increase mantisssa by factor) but we + // can't add to exponent unless factor divides the mantisssa. + FactoredExponent min_factors = { + .twos = std::min(lhs.twos, rhs.twos), + .fives = std::min(lhs.fives, rhs.fives), + }; + + // If both lhs and rhs have positive 5^n factor, we can convert to dyadic or + // decadic real. Choose the one that minimizes mantissa size. + // Assume x * 5^n requires 3n bits and x * 2^n requires n bits. + int factor_diff = std::abs(min_factors.fives - min_factors.twos); + int decadic_bits = + min_factors.fives > min_factors.twos ? 3 * factor_diff : factor_diff; + if (min_factors.fives >= 0) { + int dyadic_bits = 3 * min_factors.fives; + if (dyadic_bits < decadic_bits) { + // Result will be (likely) smaller if stored as dyadic real. + return { + .twos = min_factors.twos, + .fives = 0, + }; + } + } + + int min_exponent = std::min(min_factors.fives, min_factors.twos); + return { + .twos = min_exponent, + .fives = min_exponent, + }; +} + +// Finds difference between two exponents. +static auto ComputeExponentDelta(const FactoredExponent& lhs, + const FactoredExponent& rhs) + -> FactoredExponent { + return { + .twos = lhs.twos - rhs.twos, + .fives = lhs.fives - rhs.fives, + }; +} + +// Estimates additional bits required to apply exponent to an APInt. +static auto EstimateBitsForExponent(const FactoredExponent& exponent) -> int { + CARBON_CHECK(exponent.twos >= 0 && exponent.fives >= 0); + return static_cast(std::ceil(std::log2f(5) * exponent.fives)) + + exponent.twos; +} + +// Multiplies factored exponent with provided APInt sign, result is sign +// extended to `bit_width`. +static auto ApplyFactoredExponent(const FactoredExponent& exponent, + const llvm::APInt& mantissa, + unsigned bit_width) -> llvm::APInt { + CARBON_CHECK(exponent.twos >= 0 && exponent.fives >= 0); + auto result = mantissa.sextOrTrunc(bit_width); + if (exponent.twos > 0) { + result <<= exponent.twos; + } + if (exponent.fives > 0) { + result *= llvm::APIntOps::pow(llvm::APInt(bit_width, 5), exponent.fives); + } + return result; +} + +// Adds or subtracts two Real literals. +// Returns std::nullopt if value would be too large to compute without losing +// precision. +static auto TryAddRealLiterals(const Real& lhs, const Real& rhs) + -> std::optional { + auto lhs_exponent = TryGetFactoredExponent(lhs); + if (!lhs_exponent) { + return std::nullopt; + } + auto rhs_exponent = TryGetFactoredExponent(rhs); + if (!rhs_exponent) { + return std::nullopt; + } + + // Find an exponent we can convert both lhs and rhs to. + auto common_exponent = FindCommonExponent(*lhs_exponent, *rhs_exponent); + // Find change to mantisssa (in form of factored exponents) so that lhs and + // rhs have desired exponent. + auto lhs_exponent_delta = + ComputeExponentDelta(*lhs_exponent, common_exponent); + auto rhs_exponent_delta = + ComputeExponentDelta(*rhs_exponent, common_exponent); + + // Assume no overflow during addition, OverflowAdd will grow final result if + // necessary. + auto bit_width = std::max({ + lhs.mantissa.getSignificantBits() + + EstimateBitsForExponent(lhs_exponent_delta), + rhs.mantissa.getSignificantBits() + + EstimateBitsForExponent(rhs_exponent_delta), + static_cast(IntStore::MinAPWidth), + }); + if (bit_width > IntStore::MaxIntWidth) { + // Mantissa is too big. + return std::nullopt; + } + + auto lhs_mantissa = + ApplyFactoredExponent(lhs_exponent_delta, lhs.mantissa, bit_width); + auto rhs_mantissa = + ApplyFactoredExponent(rhs_exponent_delta, rhs.mantissa, bit_width); + return RecombineFactoredExponent(OverflowAdd(lhs_mantissa, rhs_mantissa), + common_exponent); +} + +// Performs a builtin binary real -> real operation. +static auto PerformBuiltinBinaryFloatLiteralOp( + Context& context, SemIR::LocId loc_id, + SemIR::BuiltinFunctionKind builtin_kind, RealId lhs_id, RealId rhs_id) + -> SemIR::ConstantId { + auto lhs_val = context.reals().Get(lhs_id); + auto rhs_val = context.reals().Get(rhs_id); + + Lex::TokenKind op_token; + std::optional result; + switch (builtin_kind) { + case SemIR::BuiltinFunctionKind::FloatAdd: + result = TryAddRealLiterals(lhs_val, rhs_val); + op_token = Lex::TokenKind::Plus; + break; + case SemIR::BuiltinFunctionKind::FloatSub: + result = TryAddRealLiterals(lhs_val, NegateRealLiteral(rhs_val)); + op_token = Lex::TokenKind::Minus; + break; + default: + CARBON_FATAL("Unexpected operation kind."); + } + + if (!result) { + CARBON_DIAGNOSTIC( + CompileTimeFloatLiteralBinaryOperationOutOfRange, Error, + "binary calculation `{0} {1} {2}` would exceed the maximum " + "supported integer width of {3}", + Real, Lex::TokenKind, Real, int); + context.emitter().Emit(loc_id, + CompileTimeFloatLiteralBinaryOperationOutOfRange, + lhs_val, op_token, rhs_val, IntStore::MaxIntWidth); + return SemIR::ErrorInst::ConstantId; + } + + return MakeFloatLiteralResult(context, std::move(*result)); +} + // Performs a builtin binary float -> float operation. -static auto PerformBuiltinBinaryFloatOp(Context& context, +static auto PerformBuiltinBinaryFloatOp(Context& context, SemIR::LocId loc_id, SemIR::BuiltinFunctionKind builtin_kind, SemIR::InstId lhs_id, SemIR::InstId rhs_id) -> SemIR::ConstantId { + if (context.insts().Is(lhs_id)) { + auto literal_lhs = context.insts().GetAs(lhs_id); + auto literal_rhs = context.insts().GetAs(rhs_id); + + return PerformBuiltinBinaryFloatLiteralOp(context, loc_id, builtin_kind, + literal_lhs.real_id, + literal_rhs.real_id); + } + auto lhs = context.insts().GetAs(lhs_id); auto rhs = context.insts().GetAs(rhs_id); auto lhs_val = context.floats().Get(lhs.float_id); @@ -2676,8 +2897,8 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context, if (phase != Phase::Concrete) { break; } - return PerformBuiltinBinaryFloatOp(context, builtin_kind, arg_ids[0], - arg_ids[1]); + return PerformBuiltinBinaryFloatOp(context, loc_id, builtin_kind, + arg_ids[0], arg_ids[1]); } // Float comparisons. diff --git a/toolchain/check/testdata/builtins/float/add.carbon b/toolchain/check/testdata/builtins/float/add.carbon index 52aac1f19d22..495983d51dc4 100644 --- a/toolchain/check/testdata/builtins/float/add.carbon +++ b/toolchain/check/testdata/builtins/float/add.carbon @@ -57,17 +57,6 @@ fn RuntimeCallIsValidBadReturnType(a: f64, b: f64) -> bool { return BadReturnType(a, b); } -// --- fail_literal.carbon - -library "[[@TEST_NAME]]"; - -fn Literal() -> type = "float_literal.make_type"; -// CHECK:STDERR: fail_literal.carbon:[[@LINE+4]]:1: error: invalid signature for builtin function "float.add" [InvalidBuiltinSignature] -// CHECK:STDERR: fn AddLiteral(a: Literal(), b: Literal()) -> Literal() = "float.add"; -// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: -fn AddLiteral(a: Literal(), b: Literal()) -> Literal() = "float.add"; - // CHECK:STDOUT: --- float_add.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { diff --git a/toolchain/check/testdata/builtins/float/sub.carbon b/toolchain/check/testdata/builtins/float/sub.carbon index 67f65d316e6e..441ae0816224 100644 --- a/toolchain/check/testdata/builtins/float/sub.carbon +++ b/toolchain/check/testdata/builtins/float/sub.carbon @@ -66,17 +66,6 @@ fn RuntimeCallIsValidBadReturnType(a: f64, b: f64) -> bool { return BadReturnType(a, b); } -// --- fail_literal.carbon - -library "[[@TEST_NAME]]"; - -fn Literal() -> type = "float_literal.make_type"; -// CHECK:STDERR: fail_literal.carbon:[[@LINE+4]]:1: error: invalid signature for builtin function "float.sub" [InvalidBuiltinSignature] -// CHECK:STDERR: fn SubLiteral(a: Literal(), b: Literal()) -> Literal() = "float.sub"; -// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: -fn SubLiteral(a: Literal(), b: Literal()) -> Literal() = "float.sub"; - // CHECK:STDOUT: --- float_sub.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { diff --git a/toolchain/check/testdata/builtins/float_literal/add.carbon b/toolchain/check/testdata/builtins/float_literal/add.carbon new file mode 100644 index 000000000000..2ae3c61a0897 --- /dev/null +++ b/toolchain/check/testdata/builtins/float_literal/add.carbon @@ -0,0 +1,123 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/primitives.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtins/float_literal/add.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/float_literal/add.carbon + +// --- float_literal_add.carbon + +library "[[@TEST_NAME]]"; + +fn Add(a: Core.FloatLiteral, b: Core.FloatLiteral) -> Core.FloatLiteral = "float.add"; + +// Since FloatLiterals are not canonical we need to compare using sized float. +class Expect(N: f128) {} +fn Test(generic N: Core.FloatLiteral) -> Expect(N) { return {}; } + +//@dump-sem-ir-begin +let v1: Core.FloatLiteral = Add(0x1.0000000000, 100.0); +let v2: Core.FloatLiteral = Add(0x1000.0p10, 0x1.0p10); +//@dump-sem-ir-end + +fn F() { + Test(Add(1.0, 1.0)) as Expect(2.0); + Test(Add(0x1.0p0, 1.0)) as Expect(2.0); + Test(Add(0x1.8p0, 2.5)) as Expect(4.0); + Test(Add(1.0e20, 1.0e-20)) as Expect(1.00000000000000000000000000000000000000001e20); + Test(Add(1.0e-20, 1.0e20)) as Expect(1.00000000000000000000000000000000000000001e20); + Test(Add(1.0, 1.000000000000000000000000000000)) as Expect(2.0); + Test(Add(0.922337203685477580, 0.0000000000000000001)) as Expect(0.9223372036854775801); +} + +// --- fail_too_large.carbon + +library "[[@TEST_NAME]]"; + +fn Add(a: Core.FloatLiteral, b: Core.FloatLiteral) -> Core.FloatLiteral = "float.add"; + +fn F(){ + // CHECK:STDERR: fail_too_large.carbon:[[@LINE+4]]:6: error: binary calculation `16*2^1023999999996 + 1*10^-19` would exceed the maximum supported integer width of 8388608 [CompileTimeFloatLiteralBinaryOperationOutOfRange] + // CHECK:STDERR: Add(0x1.0p1024000000000, 0.0000000000000000001); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + Add(0x1.0p1024000000000, 0.0000000000000000001); +} + +// CHECK:STDOUT: --- float_literal_add.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %pattern_type.dab: type = pattern_type Core.FloatLiteral [concrete] +// CHECK:STDOUT: %Add.type: type = fn_type @Add [concrete] +// CHECK:STDOUT: %Add: %Add.type = struct_value () [concrete] +// CHECK:STDOUT: %Float.type: type = generic_class_type @Float [concrete] +// CHECK:STDOUT: %Float.generic: %Float.type = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.type.0ff: type = generic_interface_type @ImplicitAs [concrete] +// CHECK:STDOUT: %ImplicitAs.generic: %ImplicitAs.type.0ff = struct_value () [concrete] +// CHECK:STDOUT: %v1.patt: %pattern_type.dab = value_binding_pattern v1 [concrete] +// CHECK:STDOUT: %float.b0f: Core.FloatLiteral = float_literal_value 1099511627776p-40 [concrete] +// CHECK:STDOUT: %float.471: Core.FloatLiteral = float_literal_value 1000e-1 [concrete] +// CHECK:STDOUT: %float.9f6: Core.FloatLiteral = float_literal_value 1010000000000000000000000000000000000000000e-40 [concrete] +// CHECK:STDOUT: %v2.patt: %pattern_type.dab = value_binding_pattern v2 [concrete] +// CHECK:STDOUT: %float.846: Core.FloatLiteral = float_literal_value 65536p6 [concrete] +// CHECK:STDOUT: %float.333: Core.FloatLiteral = float_literal_value 16p6 [concrete] +// CHECK:STDOUT: %float.283: Core.FloatLiteral = float_literal_value 65552p6 [concrete] +// CHECK:STDOUT: %Destroy.type: type = facet_type <@Destroy> [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [concrete] { +// CHECK:STDOUT: .FloatLiteral = %Core.FloatLiteral +// CHECK:STDOUT: .Float = %Core.Float +// CHECK:STDOUT: .ImplicitAs = %Core.ImplicitAs +// CHECK:STDOUT: .Destroy = %Core.Destroy +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.FloatLiteral: type = import_ref Core//prelude/parts/float_literal, FloatLiteral, loaded [concrete = Core.FloatLiteral] +// CHECK:STDOUT: %Core.Float: %Float.type = import_ref Core//prelude/parts/float, Float, loaded [concrete = constants.%Float.generic] +// CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.0ff = import_ref Core//prelude/parts/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic] +// CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %.loc11_54.1: Core.FloatLiteral = value_of_initializer @__global_init.%Add.call.loc11 [concrete = constants.%float.9f6] +// CHECK:STDOUT: %.loc11_54.2: Core.FloatLiteral = converted @__global_init.%Add.call.loc11, %.loc11_54.1 [concrete = constants.%float.9f6] +// CHECK:STDOUT: %.loc11_13: type = splice_block %FloatLiteral.ref.loc11 [concrete = Core.FloatLiteral] { +// CHECK:STDOUT: %Core.ref.loc11: = name_ref Core, imports.%Core [concrete = imports.%Core] +// CHECK:STDOUT: %FloatLiteral.ref.loc11: type = name_ref FloatLiteral, imports.%Core.FloatLiteral [concrete = Core.FloatLiteral] +// CHECK:STDOUT: } +// CHECK:STDOUT: %v1: Core.FloatLiteral = wrapper_binding v1, %.loc11_54.2 +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %v1.patt: %pattern_type.dab = value_binding_pattern v1 [concrete = constants.%v1.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc12_54.1: Core.FloatLiteral = value_of_initializer @__global_init.%Add.call.loc12 [concrete = constants.%float.283] +// CHECK:STDOUT: %.loc12_54.2: Core.FloatLiteral = converted @__global_init.%Add.call.loc12, %.loc12_54.1 [concrete = constants.%float.283] +// CHECK:STDOUT: %.loc12_13: type = splice_block %FloatLiteral.ref.loc12 [concrete = Core.FloatLiteral] { +// CHECK:STDOUT: %Core.ref.loc12: = name_ref Core, imports.%Core [concrete = imports.%Core] +// CHECK:STDOUT: %FloatLiteral.ref.loc12: type = name_ref FloatLiteral, imports.%Core.FloatLiteral [concrete = Core.FloatLiteral] +// CHECK:STDOUT: } +// CHECK:STDOUT: %v2: Core.FloatLiteral = wrapper_binding v2, %.loc12_54.2 +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %v2.patt: %pattern_type.dab = value_binding_pattern v2 [concrete = constants.%v2.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Add.ref.loc11: %Add.type = name_ref Add, file.%Add.decl [concrete = constants.%Add] +// CHECK:STDOUT: %float.loc11_33: Core.FloatLiteral = float_literal_value 1099511627776p-40 [concrete = constants.%float.b0f] +// CHECK:STDOUT: %float.loc11_49: Core.FloatLiteral = float_literal_value 1000e-1 [concrete = constants.%float.471] +// CHECK:STDOUT: %Add.call.loc11: init Core.FloatLiteral = call %Add.ref.loc11(%float.loc11_33, %float.loc11_49) [concrete = constants.%float.9f6] +// CHECK:STDOUT: %Add.ref.loc12: %Add.type = name_ref Add, file.%Add.decl [concrete = constants.%Add] +// CHECK:STDOUT: %float.loc12_33: Core.FloatLiteral = float_literal_value 65536p6 [concrete = constants.%float.846] +// CHECK:STDOUT: %float.loc12_46: Core.FloatLiteral = float_literal_value 16p6 [concrete = constants.%float.333] +// CHECK:STDOUT: %Add.call.loc12: init Core.FloatLiteral = call %Add.ref.loc12(%float.loc12_33, %float.loc12_46) [concrete = constants.%float.283] +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/builtins/float_literal/sub.carbon b/toolchain/check/testdata/builtins/float_literal/sub.carbon new file mode 100644 index 000000000000..305cd6f64639 --- /dev/null +++ b/toolchain/check/testdata/builtins/float_literal/sub.carbon @@ -0,0 +1,124 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/primitives.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtins/float_literal/sub.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/float_literal/sub.carbon + +// --- float_literal_sub.carbon + +library "[[@TEST_NAME]]"; + +fn Negate(a: Core.FloatLiteral) -> Core.FloatLiteral = "float.negate"; +fn Sub(a: Core.FloatLiteral, b: Core.FloatLiteral) -> Core.FloatLiteral = "float.sub"; + +// Since FloatLiterals are not canonical we need to compare using sized float. +class Expect(N: f128) {} +fn Test(generic N: Core.FloatLiteral) -> Expect(N) { return {}; } + +//@dump-sem-ir-begin +let v1: Core.FloatLiteral = Sub(1.0, 2.0); +let v2: Core.FloatLiteral = Sub(0x1000.0p10, 0x1.0p10); +//@dump-sem-ir-end + + +fn F() { + Test(Sub(2.0, 1.0)) as Expect(1.0); + Test(Sub(0x1000.0p10, 0x1.0p10)) as Expect(0xFFF.0p10); + Test(Sub(0x1.8p0, 2.5)) as Expect(Negate(1.0)); + Test(Sub(2.5, 0x1.8p0)) as Expect(1.0); + Test(Sub(1.0e20, 1.0e-20)) as Expect(0.9999999999999999999999999999999999999999e20); + Test(Sub(1.0e-20, 1.0e20)) as Expect(Negate(0.9999999999999999999999999999999999999999e20)); + Test(Sub(1.0, 1.000000000000000000000000000001)) as Expect(Negate(0.000000000000000000000000000001)); + Test(Sub(1.0000000000000000000000000000001, 1.0)) as Expect(0.0000000000000000000000000000001); +} + +// --- fail_too_large.carbon + +library "[[@TEST_NAME]]"; + +fn Sub(a: Core.FloatLiteral, b: Core.FloatLiteral) -> Core.FloatLiteral = "float.sub"; + +// CHECK:STDERR: fail_too_large.carbon:[[@LINE+4]]:29: error: binary calculation `16*2^1023999999996 - 1*10^-19` would exceed the maximum supported integer width of 8388608 [CompileTimeFloatLiteralBinaryOperationOutOfRange] +// CHECK:STDERR: let v1: Core.FloatLiteral = Sub(0x1.0p1024000000000, 0.0000000000000000001); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +let v1: Core.FloatLiteral = Sub(0x1.0p1024000000000, 0.0000000000000000001); + +// CHECK:STDOUT: --- float_literal_sub.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %pattern_type.dab: type = pattern_type Core.FloatLiteral [concrete] +// CHECK:STDOUT: %Sub.type: type = fn_type @Sub [concrete] +// CHECK:STDOUT: %Sub: %Sub.type = struct_value () [concrete] +// CHECK:STDOUT: %Float.type: type = generic_class_type @Float [concrete] +// CHECK:STDOUT: %Float.generic: %Float.type = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.type.0ff: type = generic_interface_type @ImplicitAs [concrete] +// CHECK:STDOUT: %ImplicitAs.generic: %ImplicitAs.type.0ff = struct_value () [concrete] +// CHECK:STDOUT: %v1.patt: %pattern_type.dab = value_binding_pattern v1 [concrete] +// CHECK:STDOUT: %float.6daae3.1: Core.FloatLiteral = float_literal_value 10e-1 [concrete] +// CHECK:STDOUT: %float.cceb42.1: Core.FloatLiteral = float_literal_value 20e-1 [concrete] +// CHECK:STDOUT: %float.e85: Core.FloatLiteral = float_literal_value -10e-1 [concrete] +// CHECK:STDOUT: %v2.patt: %pattern_type.dab = value_binding_pattern v2 [concrete] +// CHECK:STDOUT: %float.8466d5.1: Core.FloatLiteral = float_literal_value 65536p6 [concrete] +// CHECK:STDOUT: %float.333a1f.1: Core.FloatLiteral = float_literal_value 16p6 [concrete] +// CHECK:STDOUT: %float.bec4a4.1: Core.FloatLiteral = float_literal_value 65520p6 [concrete] +// CHECK:STDOUT: %Destroy.type: type = facet_type <@Destroy> [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [concrete] { +// CHECK:STDOUT: .FloatLiteral = %Core.FloatLiteral +// CHECK:STDOUT: .Float = %Core.Float +// CHECK:STDOUT: .ImplicitAs = %Core.ImplicitAs +// CHECK:STDOUT: .Destroy = %Core.Destroy +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.FloatLiteral: type = import_ref Core//prelude/parts/float_literal, FloatLiteral, loaded [concrete = Core.FloatLiteral] +// CHECK:STDOUT: %Core.Float: %Float.type = import_ref Core//prelude/parts/float, Float, loaded [concrete = constants.%Float.generic] +// CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.0ff = import_ref Core//prelude/parts/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic] +// CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %.loc12_41.1: Core.FloatLiteral = value_of_initializer @__global_init.%Sub.call.loc12 [concrete = constants.%float.e85] +// CHECK:STDOUT: %.loc12_41.2: Core.FloatLiteral = converted @__global_init.%Sub.call.loc12, %.loc12_41.1 [concrete = constants.%float.e85] +// CHECK:STDOUT: %.loc12_13: type = splice_block %FloatLiteral.ref.loc12 [concrete = Core.FloatLiteral] { +// CHECK:STDOUT: %Core.ref.loc12: = name_ref Core, imports.%Core [concrete = imports.%Core] +// CHECK:STDOUT: %FloatLiteral.ref.loc12: type = name_ref FloatLiteral, imports.%Core.FloatLiteral [concrete = Core.FloatLiteral] +// CHECK:STDOUT: } +// CHECK:STDOUT: %v1: Core.FloatLiteral = wrapper_binding v1, %.loc12_41.2 +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %v1.patt: %pattern_type.dab = value_binding_pattern v1 [concrete = constants.%v1.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc13_54.1: Core.FloatLiteral = value_of_initializer @__global_init.%Sub.call.loc13 [concrete = constants.%float.bec4a4.1] +// CHECK:STDOUT: %.loc13_54.2: Core.FloatLiteral = converted @__global_init.%Sub.call.loc13, %.loc13_54.1 [concrete = constants.%float.bec4a4.1] +// CHECK:STDOUT: %.loc13_13: type = splice_block %FloatLiteral.ref.loc13 [concrete = Core.FloatLiteral] { +// CHECK:STDOUT: %Core.ref.loc13: = name_ref Core, imports.%Core [concrete = imports.%Core] +// CHECK:STDOUT: %FloatLiteral.ref.loc13: type = name_ref FloatLiteral, imports.%Core.FloatLiteral [concrete = Core.FloatLiteral] +// CHECK:STDOUT: } +// CHECK:STDOUT: %v2: Core.FloatLiteral = wrapper_binding v2, %.loc13_54.2 +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %v2.patt: %pattern_type.dab = value_binding_pattern v2 [concrete = constants.%v2.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Sub.ref.loc12: %Sub.type = name_ref Sub, file.%Sub.decl [concrete = constants.%Sub] +// CHECK:STDOUT: %float.loc12_33: Core.FloatLiteral = float_literal_value 10e-1 [concrete = constants.%float.6daae3.1] +// CHECK:STDOUT: %float.loc12_38: Core.FloatLiteral = float_literal_value 20e-1 [concrete = constants.%float.cceb42.1] +// CHECK:STDOUT: %Sub.call.loc12: init Core.FloatLiteral = call %Sub.ref.loc12(%float.loc12_33, %float.loc12_38) [concrete = constants.%float.e85] +// CHECK:STDOUT: %Sub.ref.loc13: %Sub.type = name_ref Sub, file.%Sub.decl [concrete = constants.%Sub] +// CHECK:STDOUT: %float.loc13_33: Core.FloatLiteral = float_literal_value 65536p6 [concrete = constants.%float.8466d5.1] +// CHECK:STDOUT: %float.loc13_46: Core.FloatLiteral = float_literal_value 16p6 [concrete = constants.%float.333a1f.1] +// CHECK:STDOUT: %Sub.call.loc13: init Core.FloatLiteral = call %Sub.ref.loc13(%float.loc13_33, %float.loc13_46) [concrete = constants.%float.bec4a4.1] +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/diagnostics/kind.def b/toolchain/diagnostics/kind.def index a5151dbe1c82..1068035f3f09 100644 --- a/toolchain/diagnostics/kind.def +++ b/toolchain/diagnostics/kind.def @@ -468,6 +468,7 @@ CARBON_DIAGNOSTIC_KIND(CompileTimeDivisionByZero) CARBON_DIAGNOSTIC_KIND(CompileTimeIntegerOverflow) CARBON_DIAGNOSTIC_KIND(CompileTimeIntegerNegateOverflow) CARBON_DIAGNOSTIC_KIND(CompileTimeFloatBitWidth) +CARBON_DIAGNOSTIC_KIND(CompileTimeFloatLiteralBinaryOperationOutOfRange) CARBON_DIAGNOSTIC_KIND(CompileTimeShiftNegative) CARBON_DIAGNOSTIC_KIND(CompileTimeShiftOutOfRange) CARBON_DIAGNOSTIC_KIND(CompileTimeUnsizedShiftOutOfRange) diff --git a/toolchain/sem_ir/builtin_function_kind.cpp b/toolchain/sem_ir/builtin_function_kind.cpp index eddbb614e739..8bb40da7e33e 100644 --- a/toolchain/sem_ir/builtin_function_kind.cpp +++ b/toolchain/sem_ir/builtin_function_kind.cpp @@ -729,13 +729,11 @@ constexpr BuiltinInfo FloatNegate = {"float.negate", // "float.add": float addition. constexpr BuiltinInfo FloatAdd = { - "float.add", - ValidateSignatureSizedFloatT>}; + "float.add", ValidateSignatureFloatT>}; // "float.sub": float subtraction. constexpr BuiltinInfo FloatSub = { - "float.sub", - ValidateSignatureSizedFloatT>}; + "float.sub", ValidateSignatureFloatT>}; // "float.mul": float multiplication. constexpr BuiltinInfo FloatMul = { @@ -947,6 +945,11 @@ auto BuiltinFunctionKind::IsCompTimeOnly(const File& sem_ir, case FloatConvert: case FloatConvertInt: + case FloatNegate: + case FloatAdd: + case FloatSub: + case FloatMul: + case FloatDiv: case IntConvert: case IntConvertChar: case IntConvertFloat: