Implement FloatLiteral addition and subtraction (#7621)

Addresses part of issue raised in
https://github.com/carbon-language/carbon-lang/issues/7159 by
implementing float.add & float.sub builtin for FloatLiteralValues

The following code now compiles:

```
let a: f64 = 1.0 + 1.0;
```

Code handles case where operands are both decadic (base 10) and dyadic
(base 2) real literals, with the result being whichever format results
in smaller mantisssa.

File tests assert equality by converting to f128, this can possibly be
improved once CompareWith is implemented for FloatLiteral too.

Assisted-By: Gemini
This commit is contained in:
DavidLoftus
2026-08-18 17:47:58 +00:00
committed by GitHub
parent 0c186053ec
commit 70b6abd6f1
8 changed files with 495 additions and 37 deletions
+8
View File
@@ -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";
}
+232 -11
View File
@@ -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<SemIR::FloatLiteralValue>(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<SemIR::FloatValue>(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<FactoredExponent> {
if (real.exponent.getSignificantBits() > 32) {
// Reject evaluation if we can't fit exponent into int.
return std::nullopt;
}
auto exponent = static_cast<int>(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<int>(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<Real> {
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<unsigned>(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<Real> 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<SemIR::FloatLiteralValue>(lhs_id)) {
auto literal_lhs = context.insts().GetAs<SemIR::FloatLiteralValue>(lhs_id);
auto literal_rhs = context.insts().GetAs<SemIR::FloatLiteralValue>(rhs_id);
return PerformBuiltinBinaryFloatLiteralOp(context, loc_id, builtin_kind,
literal_lhs.real_id,
literal_rhs.real_id);
}
auto lhs = context.insts().GetAs<SemIR::FloatValue>(lhs_id);
auto rhs = context.insts().GetAs<SemIR::FloatValue>(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.
-11
View File
@@ -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 {
-11
View File
@@ -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 {
@@ -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> = 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: <namespace> = 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: <namespace> = 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: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -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> = 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: <namespace> = 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: <namespace> = 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: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT:
+1
View File
@@ -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)
+7 -4
View File
@@ -729,13 +729,11 @@ constexpr BuiltinInfo FloatNegate = {"float.negate",
// "float.add": float addition.
constexpr BuiltinInfo FloatAdd = {
"float.add",
ValidateSignature<auto(SizedFloatT, SizedFloatT)->SizedFloatT>};
"float.add", ValidateSignature<auto(FloatT, FloatT)->FloatT>};
// "float.sub": float subtraction.
constexpr BuiltinInfo FloatSub = {
"float.sub",
ValidateSignature<auto(SizedFloatT, SizedFloatT)->SizedFloatT>};
"float.sub", ValidateSignature<auto(FloatT, FloatT)->FloatT>};
// "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: