Skip to content

Commit

Permalink
Represent integer literals as IntLiteral not as i32. (#4532)
Browse files Browse the repository at this point in the history
When an `IntLiteral` appears as an operand of an `if` expression,
convert it to `i32` for now, so that we don't reject things like `if
cond then 1 else 2` due to having a non-constant value of type
`IntLiteral`.

For tuple indexing expressions such as `(a, b).0`, convert the index to
type `IntLiteral`, not to type `i32`. This isn't strictly necessary to
do in this PR, but avoids the need to provide an `IntLiteral` -> `i32`
implicit conversion for `no_prelude` tests using this syntax.
  • Loading branch information
zygoloid authored Nov 15, 2024
1 parent 980ce6b commit bc395eb
Show file tree
Hide file tree
Showing 249 changed files with 19,306 additions and 6,693 deletions.
2 changes: 1 addition & 1 deletion examples/sieve.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class Sieve {
}

fn MarkMultiplesNotPrime[addr self: Self*](p: i32) {
var n: i32 = 2 * p;
var n: i32 = p * 2;
while (n < 1000) {
self->is_prime[n] = false;
n += p;
Expand Down
16 changes: 16 additions & 0 deletions toolchain/check/handle_if_expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/handle.h"
#include "toolchain/sem_ir/builtin_inst_kind.h"

namespace Carbon::Check {

Expand All @@ -30,12 +31,27 @@ auto HandleParseNode(Context& context, Parse::IfExprIfId node_id) -> bool {
return true;
}

// If the operand is an `IntLiteral`, convert it to a suitably-sized `Int` type.
// TODO: For now we always pick `i32`.
static auto DecayIntLiteralToSizedInt(Context& context, Parse::NodeId node_id,
SemIR::InstId operand_id)
-> SemIR::InstId {
if (context.types().GetInstId(context.insts().Get(operand_id).type_id()) ==
SemIR::InstId::BuiltinIntLiteralType) {
operand_id = ConvertToValueOfType(
context, node_id, operand_id,
context.GetBuiltinType(SemIR::BuiltinInstKind::IntType));
}
return operand_id;
}

auto HandleParseNode(Context& context, Parse::IfExprThenId node_id) -> bool {
auto then_value_id = context.node_stack().PopExpr();
auto else_block_id = context.node_stack().Peek<Parse::NodeKind::IfExprIf>();

// Convert the first operand to a value.
then_value_id = ConvertToValueExpr(context, then_value_id);
then_value_id = DecayIntLiteralToSizedInt(context, node_id, then_value_id);

// Start emitting the `else` block.
context.inst_block_stack().Push(else_block_id);
Expand Down
32 changes: 2 additions & 30 deletions toolchain/check/handle_literal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,47 +28,19 @@ auto HandleParseNode(Context& context, Parse::BoolLiteralTrueId node_id)
return true;
}

// Forms an IntValue instruction with type `i32` for a given literal integer
// value, which is assumed to be unsigned.
static auto MakeI32Literal(Context& context, Parse::NodeId node_id,
IntId int_id) -> SemIR::InstId {
auto val = context.ints().Get(int_id);
CARBON_CHECK(val.isNonNegative(),
"Unexpected negative literal from the lexer: {0}", val);

// Make sure the value fits in an `i32`.
if (val.getSignificantBits() > 32) {
CARBON_DIAGNOSTIC(IntLiteralTooLargeForI32, Error,
"integer literal with value {0} does not fit in i32",
llvm::APInt);
context.emitter().Emit(node_id, IntLiteralTooLargeForI32, val);
return SemIR::InstId::BuiltinError;
}

// We directly reuse the integer ID as it represents the canonical value.
return context.AddInst<SemIR::IntValue>(
node_id,
{.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::IntType),
.int_id = int_id});
}

// Forms an IntValue instruction with type `IntLiteral` for a given literal
// integer value, which is assumed to be unsigned.
static auto MakeIntLiteral(Context& context, Parse::NodeId node_id,
IntId int_id) -> SemIR::InstId {
// TODO: `IntId`s with different bit-widths are considered different values
// here. Decide how we want to canonicalize these. For now this is only used
// by type literals, so we rely on the lexer picking some consistent rule.
// We rely on the lexer having normalized the `int_id` to a canonical width.
return context.AddInst<SemIR::IntValue>(
node_id, {.type_id = context.GetBuiltinType(
SemIR::BuiltinInstKind::IntLiteralType),
.int_id = int_id});
}

auto HandleParseNode(Context& context, Parse::IntLiteralId node_id) -> bool {
// Convert the literal to i32.
// TODO: Form an integer literal value and a corresponding type here instead.
auto int_literal_id = MakeI32Literal(
auto int_literal_id = MakeIntLiteral(
context, node_id,
context.tokens().GetIntLiteral(context.parse_tree().node_token(node_id)));
context.node_stack().Push(node_id, int_literal_id);
Expand Down
21 changes: 15 additions & 6 deletions toolchain/check/member_access.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,20 +464,29 @@ auto PerformTupleAccess(Context& context, SemIR::LocId loc_id,
return SemIR::InstId::BuiltinError;
}

auto diag_non_constant_index = [&] {
// TODO: Decide what to do if the index is a symbolic constant.
CARBON_DIAGNOSTIC(TupleIndexNotConstant, Error,
"tuple index must be a constant");
context.emitter().Emit(loc_id, TupleIndexNotConstant);
return SemIR::InstId::BuiltinError;
};
// Diagnose a non-constant index prior to conversion to IntLiteral, because
// the conversion will fail if the index is not constant.
if (!context.constant_values().Get(index_inst_id).is_template()) {
return diag_non_constant_index();
}

SemIR::TypeId element_type_id = SemIR::TypeId::Error;
auto index_node_id = context.insts().GetLocId(index_inst_id);
index_inst_id = ConvertToValueOfType(
context, index_node_id, index_inst_id,
context.GetBuiltinType(SemIR::BuiltinInstKind::IntType));
context.GetBuiltinType(SemIR::BuiltinInstKind::IntLiteralType));
auto index_const_id = context.constant_values().Get(index_inst_id);
if (index_const_id == SemIR::ConstantId::Error) {
return SemIR::InstId::BuiltinError;
} else if (!index_const_id.is_template()) {
// TODO: Decide what to do if the index is a symbolic constant.
CARBON_DIAGNOSTIC(TupleIndexNotConstant, Error,
"tuple index must be a constant");
context.emitter().Emit(loc_id, TupleIndexNotConstant);
return SemIR::InstId::BuiltinError;
return diag_non_constant_index();
}

auto index_literal = context.insts().GetAs<SemIR::IntValue>(
Expand Down
38 changes: 13 additions & 25 deletions toolchain/check/testdata/array/array_in_place.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,16 @@ fn G() {
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %G.type: type = fn_type @G [template]
// CHECK:STDOUT: %G: %G.type = struct_value () [template]
// CHECK:STDOUT: %.1: i32 = int_value 2 [template]
// CHECK:STDOUT: %Convert.type.2: type = fn_type @Convert.1, @ImplicitAs(Core.IntLiteral) [template]
// CHECK:STDOUT: %Convert.type.15: type = fn_type @Convert.11 [template]
// CHECK:STDOUT: %Convert.15: %Convert.type.15 = struct_value () [template]
// CHECK:STDOUT: %.25: <witness> = interface_witness (%Convert.15) [template]
// CHECK:STDOUT: %.26: <bound method> = bound_method %.1, %Convert.15 [template]
// CHECK:STDOUT: %.27: Core.IntLiteral = int_value 2 [template]
// CHECK:STDOUT: %.28: type = array_type %.27, %tuple.type.2 [template]
// CHECK:STDOUT: %.1: Core.IntLiteral = int_value 2 [template]
// CHECK:STDOUT: %.2: type = array_type %.1, %tuple.type.2 [template]
// CHECK:STDOUT: %tuple.type.3: type = tuple_type (%tuple.type.2, %tuple.type.2) [template]
// CHECK:STDOUT: %.31: i32 = int_value 0 [template]
// CHECK:STDOUT: %.32: i32 = int_value 1 [template]
// CHECK:STDOUT: %.5: i32 = int_value 0 [template]
// CHECK:STDOUT: %.6: i32 = int_value 1 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Int32 = %import_ref.1
// CHECK:STDOUT: .ImplicitAs = %import_ref.2
// CHECK:STDOUT: .Int32 = %import_ref
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
Expand Down Expand Up @@ -83,37 +76,32 @@ fn G() {
// CHECK:STDOUT: %int.make_type_32.loc14_17: init type = call constants.%Int32() [template = i32]
// CHECK:STDOUT: %int.make_type_32.loc14_22: init type = call constants.%Int32() [template = i32]
// CHECK:STDOUT: %.loc14_25.1: %tuple.type.1 = tuple_literal (%int.make_type_32.loc14_12, %int.make_type_32.loc14_17, %int.make_type_32.loc14_22)
// CHECK:STDOUT: %.loc14_28.1: i32 = int_value 2 [template = constants.%.1]
// CHECK:STDOUT: %.loc14_28: Core.IntLiteral = int_value 2 [template = constants.%.1]
// CHECK:STDOUT: %.loc14_25.2: type = value_of_initializer %int.make_type_32.loc14_12 [template = i32]
// CHECK:STDOUT: %.loc14_25.3: type = converted %int.make_type_32.loc14_12, %.loc14_25.2 [template = i32]
// CHECK:STDOUT: %.loc14_25.4: type = value_of_initializer %int.make_type_32.loc14_17 [template = i32]
// CHECK:STDOUT: %.loc14_25.5: type = converted %int.make_type_32.loc14_17, %.loc14_25.4 [template = i32]
// CHECK:STDOUT: %.loc14_25.6: type = value_of_initializer %int.make_type_32.loc14_22 [template = i32]
// CHECK:STDOUT: %.loc14_25.7: type = converted %int.make_type_32.loc14_22, %.loc14_25.6 [template = i32]
// CHECK:STDOUT: %.loc14_25.8: type = converted %.loc14_25.1, constants.%tuple.type.2 [template = constants.%tuple.type.2]
// CHECK:STDOUT: %.loc14_28.2: %Convert.type.2 = interface_witness_access constants.%.25, element0 [template = constants.%Convert.15]
// CHECK:STDOUT: %.loc14_28.3: <bound method> = bound_method %.loc14_28.1, %.loc14_28.2 [template = constants.%.26]
// CHECK:STDOUT: %int.convert_checked: init Core.IntLiteral = call %.loc14_28.3(%.loc14_28.1) [template = constants.%.27]
// CHECK:STDOUT: %.loc14_28.4: Core.IntLiteral = value_of_initializer %int.convert_checked [template = constants.%.27]
// CHECK:STDOUT: %.loc14_28.5: Core.IntLiteral = converted %.loc14_28.1, %.loc14_28.4 [template = constants.%.27]
// CHECK:STDOUT: %.loc14_29: type = array_type %.loc14_28.5, %tuple.type.2 [template = constants.%.28]
// CHECK:STDOUT: %v.var: ref %.28 = var v
// CHECK:STDOUT: %v: ref %.28 = bind_name v, %v.var
// CHECK:STDOUT: %.loc14_29: type = array_type %.loc14_28, %tuple.type.2 [template = constants.%.2]
// CHECK:STDOUT: %v.var: ref %.2 = var v
// CHECK:STDOUT: %v: ref %.2 = bind_name v, %v.var
// CHECK:STDOUT: %F.ref.loc14_34: %F.type = name_ref F, file.%F.decl [template = constants.%F]
// CHECK:STDOUT: %.loc14_42.3: ref %tuple.type.2 = splice_block %.loc14_42.2 {
// CHECK:STDOUT: %.loc14_42.1: i32 = int_value 0 [template = constants.%.31]
// CHECK:STDOUT: %.loc14_42.1: i32 = int_value 0 [template = constants.%.5]
// CHECK:STDOUT: %.loc14_42.2: ref %tuple.type.2 = array_index %v.var, %.loc14_42.1
// CHECK:STDOUT: }
// CHECK:STDOUT: %F.call.loc14_35: init %tuple.type.2 = call %F.ref.loc14_34() to %.loc14_42.3
// CHECK:STDOUT: %F.ref.loc14_39: %F.type = name_ref F, file.%F.decl [template = constants.%F]
// CHECK:STDOUT: %.loc14_42.6: ref %tuple.type.2 = splice_block %.loc14_42.5 {
// CHECK:STDOUT: %.loc14_42.4: i32 = int_value 1 [template = constants.%.32]
// CHECK:STDOUT: %.loc14_42.4: i32 = int_value 1 [template = constants.%.6]
// CHECK:STDOUT: %.loc14_42.5: ref %tuple.type.2 = array_index %v.var, %.loc14_42.4
// CHECK:STDOUT: }
// CHECK:STDOUT: %F.call.loc14_40: init %tuple.type.2 = call %F.ref.loc14_39() to %.loc14_42.6
// CHECK:STDOUT: %.loc14_42.7: %tuple.type.3 = tuple_literal (%F.call.loc14_35, %F.call.loc14_40)
// CHECK:STDOUT: %.loc14_42.8: init %.28 = array_init (%F.call.loc14_35, %F.call.loc14_40) to %v.var
// CHECK:STDOUT: %.loc14_43: init %.28 = converted %.loc14_42.7, %.loc14_42.8
// CHECK:STDOUT: %.loc14_42.8: init %.2 = array_init (%F.call.loc14_35, %F.call.loc14_40) to %v.var
// CHECK:STDOUT: %.loc14_43: init %.2 = converted %.loc14_42.7, %.loc14_42.8
// CHECK:STDOUT: assign %v.var, %.loc14_43
// CHECK:STDOUT: return
// CHECK:STDOUT: }
Expand Down
Loading

0 comments on commit bc395eb

Please sign in to comment.