Add builtins for integer bitwise, bit-shift, and relational comparison operators. (#3853)

This commit is contained in:
Richard Smith
2024-04-03 22:21:55 +00:00
committed by GitHub
parent 11c56d8ca1
commit 762d07fa3e
27 changed files with 2367 additions and 234 deletions
+112 -30
View File
@@ -74,6 +74,22 @@ static auto MakeNonConstantResult(Phase phase) -> SemIR::ConstantId {
: SemIR::ConstantId::NotConstant;
}
// Converts a bool value into a ConstantId.
static auto MakeBoolResult(Context& context, SemIR::TypeId bool_type_id,
bool result) -> SemIR::ConstantId {
return MakeConstantResult(
context, SemIR::BoolLiteral{bool_type_id, SemIR::BoolValue::From(result)},
Phase::Template);
}
// Converts an APInt value into a ConstantId.
static auto MakeIntResult(Context& context, SemIR::TypeId type_id,
llvm::APInt value) -> SemIR::ConstantId {
auto result = context.ints().Add(std::move(value));
return MakeConstantResult(context, SemIR::IntLiteral{type_id, result},
Phase::Template);
}
// `GetConstantValue` checks to see whether the provided ID describes a value
// with constant phase, and if so, returns the corresponding constant value.
// Overloads are provided for different kinds of ID.
@@ -297,23 +313,28 @@ static auto PerformBuiltinUnaryIntOp(Context& context, SemIRLoc loc,
SemIR::BuiltinFunctionKind builtin_kind,
SemIR::InstId arg_id)
-> SemIR::ConstantId {
CARBON_CHECK(builtin_kind == SemIR::BuiltinFunctionKind::IntNegate)
<< "Unexpected builtin kind";
auto op = context.insts().GetAs<SemIR::IntLiteral>(arg_id);
auto op_val = context.ints().Get(op.int_id);
if (context.types().IsSignedInt(op.type_id) && op_val.isMinSignedValue()) {
CARBON_DIAGNOSTIC(CompileTimeIntegerNegateOverflow, Error,
"Integer overflow in negation of {0}.", TypedInt);
context.emitter().Emit(loc, CompileTimeIntegerNegateOverflow,
TypedInt{op.type_id, op_val});
switch (builtin_kind) {
case SemIR::BuiltinFunctionKind::IntNegate:
if (context.types().IsSignedInt(op.type_id) &&
op_val.isMinSignedValue()) {
CARBON_DIAGNOSTIC(CompileTimeIntegerNegateOverflow, Error,
"Integer overflow in negation of {0}.", TypedInt);
context.emitter().Emit(loc, CompileTimeIntegerNegateOverflow,
TypedInt{op.type_id, op_val});
}
op_val.negate();
break;
case SemIR::BuiltinFunctionKind::IntComplement:
op_val.flipAllBits();
break;
default:
CARBON_FATAL() << "Unexpected builtin kind";
}
op_val.negate();
auto result = context.ints().Add(op_val);
return MakeConstantResult(context, SemIR::IntLiteral{op.type_id, result},
Phase::Template);
return MakeIntResult(context, op.type_id, std::move(op_val));
}
// Performs a builtin binary integer -> integer operation.
@@ -324,14 +345,15 @@ static auto PerformBuiltinBinaryIntOp(Context& context, SemIRLoc loc,
-> SemIR::ConstantId {
auto lhs = context.insts().GetAs<SemIR::IntLiteral>(lhs_id);
auto rhs = context.insts().GetAs<SemIR::IntLiteral>(rhs_id);
auto lhs_val = context.ints().Get(lhs.int_id);
auto rhs_val = context.ints().Get(rhs.int_id);
const auto& lhs_val = context.ints().Get(lhs.int_id);
const auto& rhs_val = context.ints().Get(rhs.int_id);
bool is_signed = context.types().IsSignedInt(lhs.type_id);
bool overflow = false;
llvm::APInt result_val;
llvm::StringLiteral op_str = "<error>";
switch (builtin_kind) {
// Arithmetic.
case SemIR::BuiltinFunctionKind::IntAdd:
result_val =
is_signed ? lhs_val.sadd_ov(rhs_val, overflow) : lhs_val + rhs_val;
@@ -368,6 +390,49 @@ static auto PerformBuiltinBinaryIntOp(Context& context, SemIRLoc loc,
op_str = "%";
break;
// Bitwise.
case SemIR::BuiltinFunctionKind::IntAnd:
result_val = lhs_val & rhs_val;
op_str = "&";
break;
case SemIR::BuiltinFunctionKind::IntOr:
result_val = lhs_val | rhs_val;
op_str = "|";
break;
case SemIR::BuiltinFunctionKind::IntXor:
result_val = lhs_val ^ rhs_val;
op_str = "^";
break;
// Bit shift.
case SemIR::BuiltinFunctionKind::IntLeftShift:
case SemIR::BuiltinFunctionKind::IntRightShift:
op_str = (builtin_kind == SemIR::BuiltinFunctionKind::IntLeftShift)
? llvm::StringLiteral("<<")
: llvm::StringLiteral(">>");
if (rhs_val.uge(lhs_val.getBitWidth()) ||
(rhs_val.isNegative() && context.types().IsSignedInt(rhs.type_id))) {
CARBON_DIAGNOSTIC(
CompileTimeShiftOutOfRange, Error,
"Shift distance not in range [0, {0}) in {1} {2} {3}.", unsigned,
TypedInt, llvm::StringLiteral, TypedInt);
context.emitter().Emit(loc, CompileTimeShiftOutOfRange,
lhs_val.getBitWidth(),
TypedInt{lhs.type_id, lhs_val}, op_str,
TypedInt{rhs.type_id, rhs_val});
// TODO: Is it useful to recover by returning 0 or -1?
return SemIR::ConstantId::Error;
}
if (builtin_kind == SemIR::BuiltinFunctionKind::IntLeftShift) {
result_val = lhs_val.shl(rhs_val);
} else if (is_signed) {
result_val = lhs_val.ashr(rhs_val);
} else {
result_val = lhs_val.lshr(rhs_val);
}
break;
default:
CARBON_FATAL() << "Unexpected operation kind.";
}
@@ -381,9 +446,7 @@ static auto PerformBuiltinBinaryIntOp(Context& context, SemIRLoc loc,
TypedInt{rhs.type_id, rhs_val});
}
auto result = context.ints().Add(result_val);
return MakeConstantResult(context, SemIR::IntLiteral{lhs.type_id, result},
Phase::Template);
return MakeIntResult(context, lhs.type_id, std::move(result_val));
}
// Performs a builtin integer comparison.
@@ -393,10 +456,11 @@ static auto PerformBuiltinIntComparison(Context& context,
SemIR::InstId rhs_id,
SemIR::TypeId bool_type_id)
-> SemIR::ConstantId {
auto lhs_val = context.ints().Get(
context.insts().GetAs<SemIR::IntLiteral>(lhs_id).int_id);
auto rhs_val = context.ints().Get(
auto lhs = context.insts().GetAs<SemIR::IntLiteral>(lhs_id);
const auto& lhs_val = context.ints().Get(lhs.int_id);
const auto& rhs_val = context.ints().Get(
context.insts().GetAs<SemIR::IntLiteral>(rhs_id).int_id);
bool is_signed = context.types().IsSignedInt(lhs.type_id);
bool result;
switch (builtin_kind) {
@@ -406,13 +470,23 @@ static auto PerformBuiltinIntComparison(Context& context,
case SemIR::BuiltinFunctionKind::IntNeq:
result = (lhs_val != rhs_val);
break;
case SemIR::BuiltinFunctionKind::IntLess:
result = is_signed ? lhs_val.slt(rhs_val) : lhs_val.ult(rhs_val);
break;
case SemIR::BuiltinFunctionKind::IntLessEq:
result = is_signed ? lhs_val.sle(rhs_val) : lhs_val.ule(rhs_val);
break;
case SemIR::BuiltinFunctionKind::IntGreater:
result = is_signed ? lhs_val.sgt(rhs_val) : lhs_val.sgt(rhs_val);
break;
case SemIR::BuiltinFunctionKind::IntGreaterEq:
result = is_signed ? lhs_val.sge(rhs_val) : lhs_val.sge(rhs_val);
break;
default:
CARBON_FATAL() << "Unexpected operation kind.";
}
return MakeConstantResult(
context, SemIR::BoolLiteral{bool_type_id, SemIR::BoolValue::From(result)},
Phase::Template);
return MakeBoolResult(context, bool_type_id, result);
}
static auto PerformBuiltinCall(Context& context, SemIRLoc loc, SemIR::Call call,
@@ -424,20 +498,25 @@ static auto PerformBuiltinCall(Context& context, SemIRLoc loc, SemIR::Call call,
CARBON_FATAL() << "Not a builtin function.";
// Unary integer -> integer operations.
case SemIR::BuiltinFunctionKind::IntNegate: {
// TODO: Complement.
case SemIR::BuiltinFunctionKind::IntNegate:
case SemIR::BuiltinFunctionKind::IntComplement: {
if (phase != Phase::Template) {
break;
}
return PerformBuiltinUnaryIntOp(context, loc, builtin_kind, arg_ids[0]);
}
// Homogeneous binary integer -> integer operations.
// Binary integer -> integer operations.
case SemIR::BuiltinFunctionKind::IntAdd:
case SemIR::BuiltinFunctionKind::IntSub:
case SemIR::BuiltinFunctionKind::IntMul:
case SemIR::BuiltinFunctionKind::IntDiv:
case SemIR::BuiltinFunctionKind::IntMod: {
case SemIR::BuiltinFunctionKind::IntMod:
case SemIR::BuiltinFunctionKind::IntAnd:
case SemIR::BuiltinFunctionKind::IntOr:
case SemIR::BuiltinFunctionKind::IntXor:
case SemIR::BuiltinFunctionKind::IntLeftShift:
case SemIR::BuiltinFunctionKind::IntRightShift: {
// TODO: Bitwise operators.
if (phase != Phase::Template) {
break;
@@ -448,7 +527,11 @@ static auto PerformBuiltinCall(Context& context, SemIRLoc loc, SemIR::Call call,
// Integer comparisons.
case SemIR::BuiltinFunctionKind::IntEq:
case SemIR::BuiltinFunctionKind::IntNeq: {
case SemIR::BuiltinFunctionKind::IntNeq:
case SemIR::BuiltinFunctionKind::IntLess:
case SemIR::BuiltinFunctionKind::IntLessEq:
case SemIR::BuiltinFunctionKind::IntGreater:
case SemIR::BuiltinFunctionKind::IntGreaterEq: {
// TODO: Relational comparisons.
if (phase != Phase::Template) {
break;
@@ -696,8 +779,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
if (phase == Phase::Template) {
auto value =
context.insts().GetAs<SemIR::BoolLiteral>(const_id.inst_id());
value.value = SemIR::BoolValue::From(!value.value.ToBool());
return MakeConstantResult(context, value, Phase::Template);
return MakeBoolResult(context, value.type_id, !value.value.ToBool());
}
if (phase == Phase::UnknownDueToError) {
return SemIR::ConstantId::Error;