Implement Core.Negate for float literals (#7616)

Addresses part of issue raised in #7159 by implementing float.negate
builtin for FloatLiteralValues

The following code now compiles:

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

To achieve this I switch the mantissa from being unsigned to signed.
Lexed literals within source file will still always be unsigned, however
it is now possible to create negative FloatLiteralValue constants. Main
non-local changes this causes is:

- All llvm::APInt parsing / printing calls flipped isSigned param
- Zero extension replaced with sign extension
- getActiveBits() replaced with getSignificantBits() for min bit width
calculation
This commit is contained in:
DavidLoftus
2026-08-13 20:28:42 +00:00
committed by GitHub
parent 2784f33221
commit 54b7f1345a
9 changed files with 222 additions and 119 deletions
+41 -27
View File
@@ -431,6 +431,19 @@ static auto MakeIntResult(Context& context, SemIR::TypeId type_id,
return MakeIntResult(context, type_id, result);
}
// Converts a Real into a ConstantId.
static auto MakeFloatLiteralResult(Context& context, Real real)
-> SemIR::ConstantId {
auto real_id = context.reals().Add(real);
return MakeConstantResult(
context,
SemIR::FloatLiteralValue{
.type_id =
GetSingletonType(context, SemIR::FloatLiteralType::TypeInstId),
.real_id = real_id},
Phase::Concrete);
}
// Converts an APFloat value into a ConstantId.
static auto MakeFloatResult(Context& context, SemIR::TypeId type_id,
llvm::APFloat value) -> SemIR::ConstantId {
@@ -1438,7 +1451,7 @@ static auto RealToAPFloat(Context& context, RealId real_id,
// Convert the real value to a string.
llvm::SmallString<64> str;
real_value.mantissa.toString(str, real_value.is_decimal ? 10 : 16,
/*signed=*/false, /*formatAsCLiteral=*/true);
/*signed=*/true, /*formatAsCLiteral=*/true);
str += real_value.is_decimal ? "e" : "p";
real_value.exponent.toStringSigned(str);
@@ -1550,20 +1563,10 @@ static auto PerformIntToFloatConvert(Context& context, SemIR::LocId loc_id,
if (!dest_float_type) {
// Target is Core.FloatLiteral, which is always exact.
llvm::APInt mantissa = op_val;
if (src_is_signed && op_val.isNegative()) {
// FloatLiteral can only represent positive real values. Negative
// literals are parsed as Negate(FloatLiteralValue).
context.TODO(loc_id, "negative float literal conversion");
return SemIR::ErrorInst::ConstantId;
}
auto real_id = context.reals().Add(
Real{.mantissa = mantissa,
.exponent = llvm::APInt(32, 0, /*isSigned=*/true),
.is_decimal = true});
return MakeConstantResult(
context,
SemIR::FloatLiteralValue{.type_id = dest_type_id, .real_id = real_id},
Phase::Concrete);
return MakeFloatLiteralResult(
context, Real{.mantissa = mantissa,
.exponent = llvm::APInt(32, 0, /*isSigned=*/true),
.is_decimal = true});
}
llvm::APFloat ap_float(dest_float_type->float_kind.Semantics());
@@ -1702,19 +1705,19 @@ static auto ConvertRealLiteralToInt(Context& context, SemIR::LocId loc_id,
// If the exponent is positive, base^exponent cannot be larger than the result
// size. If it's negative, base^exponent can't be *much* larger than the
// mantissa or we'd have computed a lower bound of 0 bits and bailed out.
CARBON_CHECK(
exponent_upper_bound <=
std::max<unsigned>(mantissa.getActiveBits() * 2, bounds.upper_bound));
CARBON_CHECK(exponent_upper_bound <=
std::max<unsigned>(mantissa.getSignificantBits() * 2,
bounds.upper_bound));
// Compute a bit-width in which we can safely compute the result. We need
// enough space to store the mantissa, base^exponent, the result and a sign
// bit, and the number 10 (4 bits).
unsigned calc_width =
std::max({mantissa.getActiveBits(), exponent_upper_bound,
std::max({mantissa.getSignificantBits(), exponent_upper_bound,
static_cast<unsigned>(bounds.upper_bound + 1), 4U});
// Compute the integer result.
llvm::APInt integer_val = mantissa.zextOrTrunc(calc_width);
llvm::APInt integer_val = mantissa.sextOrTrunc(calc_width);
if (!real_val.is_decimal) {
// Binary exponent (mantissa * 2^exponent).
if (!exponent.isNegative()) {
@@ -2178,16 +2181,27 @@ static auto PerformBuiltinUnaryFloatOp(Context& context,
SemIR::BuiltinFunctionKind builtin_kind,
SemIR::InstId arg_id)
-> SemIR::ConstantId {
CARBON_CHECK(builtin_kind == SemIR::BuiltinFunctionKind::FloatNegate,
"Unexpected builtin kind");
if (auto literal =
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));
}
auto op = context.insts().GetAs<SemIR::FloatValue>(arg_id);
auto op_val = context.floats().Get(op.float_id);
switch (builtin_kind) {
case SemIR::BuiltinFunctionKind::FloatNegate:
op_val.changeSign();
break;
default:
CARBON_FATAL("Unexpected builtin kind");
}
op_val.changeSign();
return MakeFloatResult(context, op.type_id, std::move(op_val));
}