Add support for converting between integer types (#4753)

Add a builtin `"int.convert"` supporting unchecked conversions between
different integer types. This performs a truncation, zero-extension, or
sign-extension, depending on the widths of the operands and the
signedness of the source type. Add explicit `As` support to the prelude.
No implicit conversions are supported yet as we don't have a way to
express the constraint that we can only implicitly convert to wider
types.
This commit is contained in:
Richard Smith
2025-01-08 08:25:20 +00:00
committed by GitHub
parent 96d836f965
commit 246ec785df
14 changed files with 545 additions and 98 deletions
+5
View File
@@ -32,6 +32,11 @@ impl forall [N:! IntLiteral()] Int(N) as As(IntLiteral()) {
fn Convert[self: Self]() -> IntLiteral() = "int.convert_checked";
}
// TODO: Allow as an implicit conversion if N > M.
impl forall [M:! IntLiteral(), N:! IntLiteral()] Int(M) as As(Int(N)) {
fn Convert[self: Self]() -> Int(N) = "int.convert";
}
// Comparisons.
impl forall [N:! IntLiteral()] Int(N) as Eq {
+16
View File
@@ -5,6 +5,7 @@
package Core library "prelude/types/uint";
import library "prelude/types/int_literal";
import library "prelude/types/int";
import library "prelude/operators";
private fn MakeUInt(size: IntLiteral()) -> type = "int.make_type_unsigned";
@@ -32,6 +33,21 @@ impl forall [N:! IntLiteral()] UInt(N) as As(IntLiteral()) {
fn Convert[self: Self]() -> IntLiteral() = "int.convert_checked";
}
// TODO: Allow as an implicit conversion if N > M.
impl forall [M:! IntLiteral(), N:! IntLiteral()] UInt(M) as As(UInt(N)) {
fn Convert[self: Self]() -> UInt(N) = "int.convert";
}
// TODO: Allow as an implicit conversion if N > M.
impl forall [M:! IntLiteral(), N:! IntLiteral()] UInt(M) as As(Int(N)) {
fn Convert[self: Self]() -> Int(N) = "int.convert";
}
// Never implicit.
impl forall [M:! IntLiteral(), N:! IntLiteral()] Int(M) as As(UInt(N)) {
fn Convert[self: Self]() -> UInt(N) = "int.convert";
}
// Comparisons.
impl forall [N:! IntLiteral()] UInt(N) as Eq {
+26
View File
@@ -721,6 +721,26 @@ static auto ValidateFloatType(Context& context, SemIRLoc loc,
return ValidateFloatBitWidth(context, loc, result.bit_width_id);
}
// Performs a conversion between integer types, truncating if the value doesn't
// fit in the destination type.
static auto PerformIntConvert(Context& context, SemIR::InstId arg_id,
SemIR::TypeId dest_type_id) -> SemIR::ConstantId {
auto arg_val =
context.ints().Get(context.insts().GetAs<SemIR::IntValue>(arg_id).int_id);
auto [dest_is_signed, bit_width_id] =
context.sem_ir().types().GetIntTypeInfo(dest_type_id);
if (bit_width_id.is_valid()) {
// TODO: If the value fits in the destination type, reuse the existing
// int_id rather than recomputing it. This is probably the most common case.
bool src_is_signed = context.sem_ir().types().IsSignedInt(
context.insts().Get(arg_id).type_id());
unsigned width = context.ints().Get(bit_width_id).getZExtValue();
arg_val =
src_is_signed ? arg_val.sextOrTrunc(width) : arg_val.zextOrTrunc(width);
}
return MakeIntResult(context, dest_type_id, dest_is_signed, arg_val);
}
// Performs a conversion between integer types, diagnosing if the value doesn't
// fit in the destination type.
static auto PerformCheckedIntConvert(Context& context, SemIRLoc loc,
@@ -1284,6 +1304,12 @@ static auto MakeConstantForBuiltinCall(Context& context, SemIRLoc loc,
}
// Integer conversions.
case SemIR::BuiltinFunctionKind::IntConvert: {
if (phase == Phase::Symbolic) {
return MakeConstantResult(context, call, phase);
}
return PerformIntConvert(context, arg_ids[0], call.type_id);
}
case SemIR::BuiltinFunctionKind::IntConvertChecked: {
if (phase == Phase::Symbolic) {
return MakeConstantResult(context, call, phase);
+226
View File
@@ -0,0 +1,226 @@
// 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
//
// EXTRA-ARGS: --no-dump-sem-ir
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtins/int/convert.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/int/convert.carbon
// --- int_ops.carbon
library "[[@TEST_NAME]]";
// Size preserving
fn Int32ToInt32(a: i32) -> i32 = "int.convert";
fn Int32ToUint32(a: i32) -> u32 = "int.convert";
fn Uint32ToInt32(a: u32) -> i32 = "int.convert";
fn Uint32ToUint32(a: u32) -> u32 = "int.convert";
fn IntLiteralToIntLiteral(a: Core.IntLiteral()) -> Core.IntLiteral() = "int.convert";
// Narrowing
fn Int32ToInt16(a: i32) -> i16 = "int.convert";
fn Int32ToUint16(a: i32) -> u16 = "int.convert";
fn Uint32ToInt16(a: u32) -> i16 = "int.convert";
fn Uint32ToUint16(a: u32) -> u16 = "int.convert";
fn IntLiteralToInt16(a: Core.IntLiteral()) -> i16 = "int.convert";
fn IntLiteralToUint16(a: Core.IntLiteral()) -> u16 = "int.convert";
// Widening
fn Int32ToInt64(a: i32) -> i64 = "int.convert";
fn Int32ToUint64(a: i32) -> u64 = "int.convert";
fn Uint32ToInt64(a: u32) -> i64 = "int.convert";
fn Uint32ToUint64(a: u32) -> u64 = "int.convert";
fn Int32ToIntLiteral(a: i32) -> Core.IntLiteral() = "int.convert";
fn Uint32ToIntLiteral(a: u32) -> Core.IntLiteral() = "int.convert";
class Expect[T:! type](N:! T) {}
fn Test[T:! type](N:! T) -> Expect(N) { return {}; }
// --- fail_self_test.carbon
library "[[@TEST_NAME]]";
import library "int_ops";
fn F() {
// Ensure our testing machinery works.
// CHECK:STDERR: fail_self_test.carbon:[[@LINE+7]]:3: error: cannot convert from `Expect(0)` to `Expect(1)` with `as` [ExplicitAsConversionFailure]
// CHECK:STDERR: Test(Int32ToInt32(0)) as Expect(1 as i32);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_self_test.carbon:[[@LINE+4]]:3: note: type `Expect(0)` does not implement interface `Core.As(Expect(1))` [MissingImplInMemberAccessNote]
// CHECK:STDERR: Test(Int32ToInt32(0)) as Expect(1 as i32);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Test(Int32ToInt32(0)) as Expect(1 as i32);
}
// --- identity.carbon
library "[[@TEST_NAME]]";
import library "int_ops";
fn F() {
Test(Int32ToInt32(-0x8000_0000)) as Expect(-0x8000_0000 as i32);
Test(Int32ToInt32(-1)) as Expect(-1 as i32);
Test(Int32ToInt32(0)) as Expect(0 as i32);
Test(Int32ToInt32(0x7FFF_FFFF)) as Expect(0x7FFF_FFFF as i32);
Test(Uint32ToUint32(0)) as Expect(0 as u32);
Test(Uint32ToUint32(0x7FFF_FFFF)) as Expect(0x7FFF_FFFF as u32);
Test(Uint32ToUint32(0x8000_0000)) as Expect(0x8000_0000 as u32);
Test(Uint32ToUint32(0xFFFF_FFFF)) as Expect(0xFFFF_FFFF as u32);
Test(IntLiteralToIntLiteral(0x1_0000_0000_0000_0000)) as
Expect(0x1_0000_0000_0000_0000);
Test(IntLiteralToIntLiteral(-1)) as Expect(-1);
}
// --- same_size.carbon
library "[[@TEST_NAME]]";
import library "int_ops";
fn F() {
Test(Int32ToUint32(-0x8000_0000)) as Expect(0x8000_0000 as u32);
Test(Int32ToUint32(-1)) as Expect(0xFFFF_FFFF as u32);
Test(Int32ToUint32(0)) as Expect(0 as u32);
Test(Int32ToUint32(0x7FFF_FFFF)) as Expect(0x7FFF_FFFF as u32);
Test(Uint32ToInt32(0)) as Expect(0 as i32);
Test(Uint32ToInt32(0x7FFF_FFFF)) as Expect(0x7FFF_FFFF as i32);
Test(Uint32ToInt32(0x8000_0000)) as Expect(-0x8000_0000 as i32);
Test(Uint32ToInt32(0xFFFF_FFFF)) as Expect(-1 as i32);
}
// --- truncate.carbon
library "[[@TEST_NAME]]";
import library "int_ops";
fn F() {
Test(Int32ToInt16(-0x8000_0000)) as Expect(0 as i16);
Test(Int32ToInt16(-0x7FFF_EDCC)) as Expect(0x1234 as i16);
Test(Int32ToInt16(-0x7FFF_1234)) as Expect(-0x1234 as i16);
Test(Int32ToInt16(-0x8000)) as Expect(-0x8000 as i16);
Test(Int32ToInt16(-1)) as Expect(-1 as i16);
Test(Int32ToInt16(0)) as Expect(0 as i16);
Test(Int32ToInt16(0x7FFF)) as Expect(0x7FFF as i16);
Test(Int32ToInt16(0xFFFF)) as Expect(-1 as i16);
Test(Int32ToInt16(0x7FFF_1234)) as Expect(0x1234 as i16);
Test(Int32ToInt16(0x7FFF_EDCC)) as Expect(-0x1234 as i16);
Test(Int32ToInt16(0x7FFF_FFFF)) as Expect(-1 as i16);
Test(Int32ToUint16(-0x8000_0000)) as Expect(0 as u16);
Test(Int32ToUint16(-0x7FFF_EDCC)) as Expect(0x1234 as u16);
Test(Int32ToUint16(-0x7FFF_1234)) as Expect(0xEDCC as u16);
Test(Int32ToUint16(-0x8000)) as Expect(0x8000 as u16);
Test(Int32ToUint16(-1)) as Expect(0xFFFF as u16);
Test(Int32ToUint16(0)) as Expect(0 as u16);
Test(Int32ToUint16(0x7FFF)) as Expect(0x7FFF as u16);
Test(Int32ToUint16(0xFFFF)) as Expect(0xFFFF as u16);
Test(Int32ToUint16(0x7FFF_1234)) as Expect(0x1234 as u16);
Test(Int32ToUint16(0x7FFF_EDCC)) as Expect(0xEDCC as u16);
Test(Int32ToUint16(0x7FFF_FFFF)) as Expect(0xFFFF as u16);
Test(Uint32ToInt16(0x8000_0000)) as Expect(0 as i16);
Test(Uint32ToInt16(0xFFFF_1234)) as Expect(0x1234 as i16);
Test(Uint32ToInt16(0xFFFF_EDCC)) as Expect(-0x1234 as i16);
Test(Uint32ToInt16(0xFFFF_8000)) as Expect(-0x8000 as i16);
Test(Uint32ToInt16(0xFFFF_FFFF)) as Expect(-1 as i16);
Test(Uint32ToInt16(0)) as Expect(0 as i16);
Test(Uint32ToInt16(0x7FFF)) as Expect(0x7FFF as i16);
Test(Uint32ToInt16(0xFFFF)) as Expect(-1 as i16);
Test(Uint32ToInt16(0x7FFF_1234)) as Expect(0x1234 as i16);
Test(Uint32ToInt16(0x7FFF_EDCC)) as Expect(-0x1234 as i16);
Test(Uint32ToInt16(0x7FFF_FFFF)) as Expect(-1 as i16);
Test(Uint32ToUint16(0x8000_0000)) as Expect(0 as u16);
Test(Uint32ToUint16(0xFFFF_1234)) as Expect(0x1234 as u16);
Test(Uint32ToUint16(0xFFFF_EDCC)) as Expect(0xEDCC as u16);
Test(Uint32ToUint16(0xFFFF_8000)) as Expect(0x8000 as u16);
Test(Uint32ToUint16(0xFFFF_FFFF)) as Expect(0xFFFF as u16);
Test(Uint32ToUint16(0)) as Expect(0 as u16);
Test(Uint32ToUint16(0x7FFF)) as Expect(0x7FFF as u16);
Test(Uint32ToUint16(0xFFFF)) as Expect(0xFFFF as u16);
Test(Uint32ToUint16(0x7FFF_1234)) as Expect(0x1234 as u16);
Test(Uint32ToUint16(0x7FFF_EDCC)) as Expect(0xEDCC as u16);
Test(Uint32ToUint16(0x7FFF_FFFF)) as Expect(0xFFFF as u16);
Test(IntLiteralToInt16(0)) as Expect(0 as i16);
Test(IntLiteralToInt16(0x7FFF)) as Expect(0x7FFF as i16);
Test(IntLiteralToInt16(0x8000)) as Expect(-0x8000 as i16);
Test(IntLiteralToInt16(0xFFFF)) as Expect(-1 as i16);
Test(IntLiteralToInt16(0x1_2345)) as Expect(0x2345 as i16);
Test(IntLiteralToInt16(-1)) as Expect(-1 as i16);
Test(IntLiteralToUint16(0)) as Expect(0 as u16);
Test(IntLiteralToUint16(0x7FFF)) as Expect(0x7FFF as u16);
Test(IntLiteralToUint16(0x8000)) as Expect(0x8000 as u16);
Test(IntLiteralToUint16(0xFFFF)) as Expect(0xFFFF as u16);
Test(IntLiteralToUint16(0x1_2345)) as Expect(0x2345 as u16);
Test(IntLiteralToUint16(-1)) as Expect(0xFFFF as u16);
}
// --- zero_extend.carbon
library "[[@TEST_NAME]]";
import library "int_ops";
fn F() {
Test(Uint32ToInt64(0)) as Expect(0 as i64);
Test(Uint32ToInt64(0x1234_5678)) as Expect(0x1234_5678 as i64);
Test(Uint32ToInt64(0x7FFF_FFFF)) as Expect(0x7FFF_FFFF as i64);
Test(Uint32ToInt64(0x8000_0000)) as Expect(0x8000_0000 as i64);
Test(Uint32ToInt64(0xFFFF_FFFF)) as Expect(0xFFFF_FFFF as i64);
Test(Uint32ToUint64(0)) as Expect(0 as u64);
Test(Uint32ToUint64(0x1234_5678)) as Expect(0x1234_5678 as u64);
Test(Uint32ToUint64(0x7FFF_FFFF)) as Expect(0x7FFF_FFFF as u64);
Test(Uint32ToUint64(0x8000_0000)) as Expect(0x8000_0000 as u64);
Test(Uint32ToUint64(0xFFFF_FFFF)) as Expect(0xFFFF_FFFF as u64);
Test(Uint32ToIntLiteral(0x1234_5678)) as Expect(0x1234_5678);
Test(Uint32ToIntLiteral(0x8765_4321)) as Expect(0x8765_4321);
Test(Uint32ToIntLiteral(0xFFFF_FFFF)) as Expect(0xFFFF_FFFF);
}
// --- sign_extend.carbon
library "[[@TEST_NAME]]";
import library "int_ops";
fn F() {
Test(Int32ToInt64(0)) as Expect(0 as i64);
Test(Int32ToInt64(0x1234_5678)) as Expect(0x1234_5678 as i64);
Test(Int32ToInt64(0x7FFF_FFFF)) as Expect(0x7FFF_FFFF as i64);
Test(Int32ToInt64(-1)) as Expect(-1 as i64);
Test(Int32ToUint64(0)) as Expect(0 as u64);
Test(Int32ToUint64(0x1234_5678)) as Expect(0x1234_5678 as u64);
Test(Int32ToUint64(0x7FFF_FFFF)) as Expect(0x7FFF_FFFF as u64);
Test(Int32ToUint64(-1)) as Expect(0xFFFF_FFFF_FFFF_FFFF as u64);
Test(Int32ToUint64(-0x8000_0000)) as Expect(0xFFFF_FFFF_8000_0000 as u64);
Test(Int32ToIntLiteral(0x1234_5678)) as Expect(0x1234_5678);
Test(Int32ToIntLiteral(-0x1234_5678)) as Expect(-0x1234_5678);
Test(Int32ToIntLiteral(-1)) as Expect(-1);
}
// --- fail_not_constant.carbon
library "[[@TEST_NAME]]";
import library "int_ops";
let not_constant: Core.IntLiteral() = 0;
// CHECK:STDERR: fail_not_constant.carbon:[[@LINE+7]]:33: error: non-constant call to compile-time-only function [NonConstantCallToCompTimeOnlyFunction]
// CHECK:STDERR: let convert_not_constant: i16 = IntLiteralToInt16(not_constant);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_not_constant.carbon:[[@LINE-7]]:1: in import [InImport]
// CHECK:STDERR: int_ops.carbon:16:1: note: compile-time-only function declared here [CompTimeOnlyFunctionHere]
// CHECK:STDERR: fn IntLiteralToInt16(a: Core.IntLiteral()) -> i16 = "int.convert";
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let convert_not_constant: i16 = IntLiteralToInt16(not_constant);
+1 -1
View File
@@ -319,5 +319,5 @@ fn Run() {
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F[%self.param_patt: %ForwardDeclared.7b34f2.1]() [from "a.carbon"];
// CHECK:STDOUT:
// CHECK:STDOUT: fn @G[addr <unexpected>.inst1019: %ptr.6cf]() [from "a.carbon"];
// CHECK:STDOUT: fn @G[addr <unexpected>.inst1063: %ptr.6cf]() [from "a.carbon"];
// CHECK:STDOUT:
@@ -37,8 +37,8 @@ fn G(n: i32) {
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [template]
// CHECK:STDOUT: %G.type: type = fn_type @G [template]
// CHECK:STDOUT: %G: %G.type = struct_value () [template]
// CHECK:STDOUT: %impl_witness.a03: <witness> = impl_witness (imports.%import_ref.c16), @impl.13(%int_32) [template]
// CHECK:STDOUT: %Op.type.16e: type = fn_type @Op.2, @impl.13(%int_32) [template]
// CHECK:STDOUT: %impl_witness.a03: <witness> = impl_witness (imports.%import_ref.c16), @impl.14(%int_32) [template]
// CHECK:STDOUT: %Op.type.16e: type = fn_type @Op.2, @impl.14(%int_32) [template]
// CHECK:STDOUT: %Op.ceb: %Op.type.16e = struct_value () [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
+3 -3
View File
@@ -276,7 +276,7 @@ var c_bad: C({.a = 3, .b = 4}) = F();
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref.8f2: <witness> = import_ref Implicit//default, loc8_34, loaded [template = constants.%complete_type.357]
// CHECK:STDOUT: %import_ref.5c7 = import_ref Implicit//default, inst1017 [no loc], unloaded
// CHECK:STDOUT: %import_ref.5c7 = import_ref Implicit//default, inst1061 [no loc], unloaded
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
@@ -396,7 +396,7 @@ var c_bad: C({.a = 3, .b = 4}) = F();
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref.8f2: <witness> = import_ref Implicit//default, loc8_34, loaded [template = constants.%complete_type.357]
// CHECK:STDOUT: %import_ref.5c7 = import_ref Implicit//default, inst1017 [no loc], unloaded
// CHECK:STDOUT: %import_ref.5c7 = import_ref Implicit//default, inst1061 [no loc], unloaded
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
@@ -486,7 +486,7 @@ var c_bad: C({.a = 3, .b = 4}) = F();
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref.8f2: <witness> = import_ref Implicit//default, loc8_34, loaded [template = constants.%complete_type.357]
// CHECK:STDOUT: %import_ref.5c7 = import_ref Implicit//default, inst1017 [no loc], unloaded
// CHECK:STDOUT: %import_ref.5c7 = import_ref Implicit//default, inst1061 [no loc], unloaded
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
+3 -3
View File
@@ -293,7 +293,7 @@ var c_bad: C((3, 4)) = F();
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref.8f2: <witness> = import_ref Implicit//default, loc7_26, loaded [template = constants.%complete_type.357]
// CHECK:STDOUT: %import_ref.2e0 = import_ref Implicit//default, inst1052 [no loc], unloaded
// CHECK:STDOUT: %import_ref.2e0 = import_ref Implicit//default, inst1096 [no loc], unloaded
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
@@ -421,7 +421,7 @@ var c_bad: C((3, 4)) = F();
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref.8f2: <witness> = import_ref Implicit//default, loc7_26, loaded [template = constants.%complete_type.357]
// CHECK:STDOUT: %import_ref.2e0 = import_ref Implicit//default, inst1052 [no loc], unloaded
// CHECK:STDOUT: %import_ref.2e0 = import_ref Implicit//default, inst1096 [no loc], unloaded
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
@@ -511,7 +511,7 @@ var c_bad: C((3, 4)) = F();
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref.8f2: <witness> = import_ref Implicit//default, loc7_26, loaded [template = constants.%complete_type.357]
// CHECK:STDOUT: %import_ref.2e0 = import_ref Implicit//default, inst1052 [no loc], unloaded
// CHECK:STDOUT: %import_ref.2e0 = import_ref Implicit//default, inst1096 [no loc], unloaded
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
+24 -5
View File
@@ -67,13 +67,22 @@ static auto IsSignedInt(FunctionContext& context, SemIR::InstId int_id)
// Creates a zext or sext instruction depending on the signedness of the
// operand.
static auto CreateZExtOrSExt(FunctionContext& context, llvm::Value* value,
llvm::Type* type, bool is_signed,
const llvm::Twine& name = "") -> llvm::Value* {
static auto CreateExt(FunctionContext& context, llvm::Value* value,
llvm::Type* type, bool is_signed,
const llvm::Twine& name = "") -> llvm::Value* {
return is_signed ? context.builder().CreateSExt(value, type, name)
: context.builder().CreateZExt(value, type, name);
}
// Creates a zext, sext, or trunc instruction depending on the signedness of the
// operand.
static auto CreateExtOrTrunc(FunctionContext& context, llvm::Value* value,
llvm::Type* type, bool is_signed,
const llvm::Twine& name = "") -> llvm::Value* {
return is_signed ? context.builder().CreateSExtOrTrunc(value, type, name)
: context.builder().CreateZExtOrTrunc(value, type, name);
}
// Handles a call to a builtin integer bit shift operator.
static auto HandleIntShift(FunctionContext& context, SemIR::InstId inst_id,
llvm::Instruction::BinaryOps bin_op,
@@ -129,8 +138,8 @@ static auto HandleIntComparison(FunctionContext& context, SemIR::InstId inst_id,
auto* cmp_type = llvm::IntegerType::get(context.llvm_context(), cmp_width);
// Widen the operands as needed.
lhs = CreateZExtOrSExt(context, lhs, cmp_type, lhs_signed, "lhs");
rhs = CreateZExtOrSExt(context, rhs, cmp_type, rhs_signed, "rhs");
lhs = CreateExt(context, lhs, cmp_type, lhs_signed, "lhs");
rhs = CreateExt(context, rhs, cmp_type, rhs_signed, "rhs");
context.SetLocal(
inst_id,
@@ -204,6 +213,16 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
context.SetLocal(inst_id, context.GetTypeAsValue());
return;
case SemIR::BuiltinFunctionKind::IntConvert: {
context.SetLocal(
inst_id,
CreateExtOrTrunc(
context, context.GetValue(arg_ids[0]),
context.GetType(context.sem_ir().insts().Get(inst_id).type_id()),
IsSignedInt(context, arg_ids[0])));
return;
}
case SemIR::BuiltinFunctionKind::IntSNegate: {
// Lower `-x` as `0 - x`.
auto* operand = context.GetValue(arg_ids[0]);
+181 -30
View File
@@ -105,6 +105,8 @@ fn TestRightShiftLargerUU(a: u16, b: u32) -> u16 { return RightShiftLargerUU(a,
// --- mixed_compare.carbon
library "[[@TEST_NAME]]";
fn Eq_u16_u32(a: u16, b: u32) -> bool = "int.eq";
fn Eq_i16_u32(a: i16, b: u32) -> bool = "int.eq";
fn Eq_u16_i32(a: u16, b: i32) -> bool = "int.eq";
@@ -129,6 +131,43 @@ fn TestLess_u16_i32(a: u16, b: i32) -> bool { return Less_u16_i32(a, b); }
fn TestLess_i16_i32(a: i16, b: i32) -> bool { return Less_i16_i32(a, b); }
fn TestLess_i32_u32(a: i32, b: u32) -> bool { return Less_i32_u32(a, b); }
// --- convert.carbon
library "[[@TEST_NAME]]";
// Size preserving
fn Int32ToInt32(a: i32) -> i32 = "int.convert";
fn Int32ToUint32(a: i32) -> u32 = "int.convert";
fn Uint32ToInt32(a: u32) -> i32 = "int.convert";
fn Uint32ToUint32(a: u32) -> u32 = "int.convert";
fn TestInt32ToInt32(a: i32) -> i32 { return Int32ToInt32(a); }
fn TestInt32ToUint32(a: i32) -> u32 { return Int32ToUint32(a); }
fn TestUint32ToInt32(a: u32) -> i32 { return Uint32ToInt32(a); }
fn TestUint32ToUint32(a: u32) -> u32 { return Uint32ToUint32(a); }
// Narrowing
fn Int32ToInt16(a: i32) -> i16 = "int.convert";
fn Int32ToUint16(a: i32) -> u16 = "int.convert";
fn Uint32ToInt16(a: u32) -> i16 = "int.convert";
fn Uint32ToUint16(a: u32) -> u16 = "int.convert";
fn TestInt32ToInt16(a: i32) -> i16 { return Int32ToInt16(a); }
fn TestInt32ToUint16(a: i32) -> u16 { return Int32ToUint16(a); }
fn TestUint32ToInt16(a: u32) -> i16 { return Uint32ToInt16(a); }
fn TestUint32ToUint16(a: u32) -> u16 { return Uint32ToUint16(a); }
// Widening
fn Int32ToInt64(a: i32) -> i64 = "int.convert";
fn Int32ToUint64(a: i32) -> u64 = "int.convert";
fn Uint32ToInt64(a: u32) -> i64 = "int.convert";
fn Uint32ToUint64(a: u32) -> u64 = "int.convert";
fn TestInt32ToInt64(a: i32) -> i64 { return Int32ToInt64(a); }
fn TestInt32ToUint64(a: i32) -> u64 { return Int32ToUint64(a); }
fn TestUint32ToInt64(a: u32) -> i64 { return Uint32ToInt64(a); }
fn TestUint32ToUint64(a: u32) -> u64 { return Uint32ToUint64(a); }
// CHECK:STDOUT: ; ModuleID = 'basic.carbon'
// CHECK:STDOUT: source_filename = "basic.carbon"
// CHECK:STDOUT:
@@ -508,35 +547,147 @@ fn TestLess_i32_u32(a: i32, b: u32) -> bool { return Less_i32_u32(a, b); }
// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
// CHECK:STDOUT: !3 = !DIFile(filename: "mixed_compare.carbon", directory: "")
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "TestEq_u16_u32", linkageName: "_CTestEq_u16_u32.Main", scope: null, file: !3, line: 8, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "TestEq_u16_u32", linkageName: "_CTestEq_u16_u32.Main", scope: null, file: !3, line: 10, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
// CHECK:STDOUT: !6 = !{}
// CHECK:STDOUT: !7 = !DILocation(line: 8, column: 52, scope: !4)
// CHECK:STDOUT: !8 = !DILocation(line: 8, column: 45, scope: !4)
// CHECK:STDOUT: !9 = distinct !DISubprogram(name: "TestEq_i16_u32", linkageName: "_CTestEq_i16_u32.Main", scope: null, file: !3, line: 9, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !10 = !DILocation(line: 9, column: 52, scope: !9)
// CHECK:STDOUT: !11 = !DILocation(line: 9, column: 45, scope: !9)
// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "TestEq_u16_i32", linkageName: "_CTestEq_u16_i32.Main", scope: null, file: !3, line: 10, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !13 = !DILocation(line: 10, column: 52, scope: !12)
// CHECK:STDOUT: !14 = !DILocation(line: 10, column: 45, scope: !12)
// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "TestEq_i16_i32", linkageName: "_CTestEq_i16_i32.Main", scope: null, file: !3, line: 11, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !16 = !DILocation(line: 11, column: 52, scope: !15)
// CHECK:STDOUT: !17 = !DILocation(line: 11, column: 45, scope: !15)
// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "TestEq_i32_u32", linkageName: "_CTestEq_i32_u32.Main", scope: null, file: !3, line: 12, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !19 = !DILocation(line: 12, column: 52, scope: !18)
// CHECK:STDOUT: !20 = !DILocation(line: 12, column: 45, scope: !18)
// CHECK:STDOUT: !21 = distinct !DISubprogram(name: "TestLess_u16_u32", linkageName: "_CTestLess_u16_u32.Main", scope: null, file: !3, line: 20, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !22 = !DILocation(line: 20, column: 54, scope: !21)
// CHECK:STDOUT: !23 = !DILocation(line: 20, column: 47, scope: !21)
// CHECK:STDOUT: !24 = distinct !DISubprogram(name: "TestLess_i16_u32", linkageName: "_CTestLess_i16_u32.Main", scope: null, file: !3, line: 21, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !25 = !DILocation(line: 21, column: 54, scope: !24)
// CHECK:STDOUT: !26 = !DILocation(line: 21, column: 47, scope: !24)
// CHECK:STDOUT: !27 = distinct !DISubprogram(name: "TestLess_u16_i32", linkageName: "_CTestLess_u16_i32.Main", scope: null, file: !3, line: 22, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !28 = !DILocation(line: 22, column: 54, scope: !27)
// CHECK:STDOUT: !29 = !DILocation(line: 22, column: 47, scope: !27)
// CHECK:STDOUT: !30 = distinct !DISubprogram(name: "TestLess_i16_i32", linkageName: "_CTestLess_i16_i32.Main", scope: null, file: !3, line: 23, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !31 = !DILocation(line: 23, column: 54, scope: !30)
// CHECK:STDOUT: !32 = !DILocation(line: 23, column: 47, scope: !30)
// CHECK:STDOUT: !33 = distinct !DISubprogram(name: "TestLess_i32_u32", linkageName: "_CTestLess_i32_u32.Main", scope: null, file: !3, line: 24, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !34 = !DILocation(line: 24, column: 54, scope: !33)
// CHECK:STDOUT: !35 = !DILocation(line: 24, column: 47, scope: !33)
// CHECK:STDOUT: !7 = !DILocation(line: 10, column: 52, scope: !4)
// CHECK:STDOUT: !8 = !DILocation(line: 10, column: 45, scope: !4)
// CHECK:STDOUT: !9 = distinct !DISubprogram(name: "TestEq_i16_u32", linkageName: "_CTestEq_i16_u32.Main", scope: null, file: !3, line: 11, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !10 = !DILocation(line: 11, column: 52, scope: !9)
// CHECK:STDOUT: !11 = !DILocation(line: 11, column: 45, scope: !9)
// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "TestEq_u16_i32", linkageName: "_CTestEq_u16_i32.Main", scope: null, file: !3, line: 12, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !13 = !DILocation(line: 12, column: 52, scope: !12)
// CHECK:STDOUT: !14 = !DILocation(line: 12, column: 45, scope: !12)
// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "TestEq_i16_i32", linkageName: "_CTestEq_i16_i32.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !16 = !DILocation(line: 13, column: 52, scope: !15)
// CHECK:STDOUT: !17 = !DILocation(line: 13, column: 45, scope: !15)
// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "TestEq_i32_u32", linkageName: "_CTestEq_i32_u32.Main", scope: null, file: !3, line: 14, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !19 = !DILocation(line: 14, column: 52, scope: !18)
// CHECK:STDOUT: !20 = !DILocation(line: 14, column: 45, scope: !18)
// CHECK:STDOUT: !21 = distinct !DISubprogram(name: "TestLess_u16_u32", linkageName: "_CTestLess_u16_u32.Main", scope: null, file: !3, line: 22, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !22 = !DILocation(line: 22, column: 54, scope: !21)
// CHECK:STDOUT: !23 = !DILocation(line: 22, column: 47, scope: !21)
// CHECK:STDOUT: !24 = distinct !DISubprogram(name: "TestLess_i16_u32", linkageName: "_CTestLess_i16_u32.Main", scope: null, file: !3, line: 23, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !25 = !DILocation(line: 23, column: 54, scope: !24)
// CHECK:STDOUT: !26 = !DILocation(line: 23, column: 47, scope: !24)
// CHECK:STDOUT: !27 = distinct !DISubprogram(name: "TestLess_u16_i32", linkageName: "_CTestLess_u16_i32.Main", scope: null, file: !3, line: 24, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !28 = !DILocation(line: 24, column: 54, scope: !27)
// CHECK:STDOUT: !29 = !DILocation(line: 24, column: 47, scope: !27)
// CHECK:STDOUT: !30 = distinct !DISubprogram(name: "TestLess_i16_i32", linkageName: "_CTestLess_i16_i32.Main", scope: null, file: !3, line: 25, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !31 = !DILocation(line: 25, column: 54, scope: !30)
// CHECK:STDOUT: !32 = !DILocation(line: 25, column: 47, scope: !30)
// CHECK:STDOUT: !33 = distinct !DISubprogram(name: "TestLess_i32_u32", linkageName: "_CTestLess_i32_u32.Main", scope: null, file: !3, line: 26, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !34 = !DILocation(line: 26, column: 54, scope: !33)
// CHECK:STDOUT: !35 = !DILocation(line: 26, column: 47, scope: !33)
// CHECK:STDOUT: ; ModuleID = 'convert.carbon'
// CHECK:STDOUT: source_filename = "convert.carbon"
// CHECK:STDOUT:
// CHECK:STDOUT: define i32 @_CTestInt32ToInt32.Main(i32 %a) !dbg !4 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: ret i32 %a, !dbg !7
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i32 @_CTestInt32ToUint32.Main(i32 %a) !dbg !8 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: ret i32 %a, !dbg !9
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i32 @_CTestUint32ToInt32.Main(i32 %a) !dbg !10 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: ret i32 %a, !dbg !11
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i32 @_CTestUint32ToUint32.Main(i32 %a) !dbg !12 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: ret i32 %a, !dbg !13
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i16 @_CTestInt32ToInt16.Main(i32 %a) !dbg !14 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: %int.convert = trunc i32 %a to i16, !dbg !15
// CHECK:STDOUT: ret i16 %int.convert, !dbg !16
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i16 @_CTestInt32ToUint16.Main(i32 %a) !dbg !17 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: %int.convert = trunc i32 %a to i16, !dbg !18
// CHECK:STDOUT: ret i16 %int.convert, !dbg !19
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i16 @_CTestUint32ToInt16.Main(i32 %a) !dbg !20 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: %int.convert = trunc i32 %a to i16, !dbg !21
// CHECK:STDOUT: ret i16 %int.convert, !dbg !22
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i16 @_CTestUint32ToUint16.Main(i32 %a) !dbg !23 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: %int.convert = trunc i32 %a to i16, !dbg !24
// CHECK:STDOUT: ret i16 %int.convert, !dbg !25
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i64 @_CTestInt32ToInt64.Main(i32 %a) !dbg !26 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: %int.convert = sext i32 %a to i64, !dbg !27
// CHECK:STDOUT: ret i64 %int.convert, !dbg !28
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i64 @_CTestInt32ToUint64.Main(i32 %a) !dbg !29 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: %int.convert = sext i32 %a to i64, !dbg !30
// CHECK:STDOUT: ret i64 %int.convert, !dbg !31
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i64 @_CTestUint32ToInt64.Main(i32 %a) !dbg !32 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: %int.convert = zext i32 %a to i64, !dbg !33
// CHECK:STDOUT: ret i64 %int.convert, !dbg !34
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define i64 @_CTestUint32ToUint64.Main(i32 %a) !dbg !35 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: %int.convert = zext i32 %a to i64, !dbg !36
// CHECK:STDOUT: ret i64 %int.convert, !dbg !37
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
// CHECK:STDOUT:
// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
// CHECK:STDOUT: !3 = !DIFile(filename: "convert.carbon", directory: "")
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "TestInt32ToInt32", linkageName: "_CTestInt32ToInt32.Main", scope: null, file: !3, line: 10, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
// CHECK:STDOUT: !6 = !{}
// CHECK:STDOUT: !7 = !DILocation(line: 10, column: 38, scope: !4)
// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "TestInt32ToUint32", linkageName: "_CTestInt32ToUint32.Main", scope: null, file: !3, line: 11, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !9 = !DILocation(line: 11, column: 39, scope: !8)
// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "TestUint32ToInt32", linkageName: "_CTestUint32ToInt32.Main", scope: null, file: !3, line: 12, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !11 = !DILocation(line: 12, column: 39, scope: !10)
// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "TestUint32ToUint32", linkageName: "_CTestUint32ToUint32.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !13 = !DILocation(line: 13, column: 40, scope: !12)
// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "TestInt32ToInt16", linkageName: "_CTestInt32ToInt16.Main", scope: null, file: !3, line: 21, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !15 = !DILocation(line: 21, column: 45, scope: !14)
// CHECK:STDOUT: !16 = !DILocation(line: 21, column: 38, scope: !14)
// CHECK:STDOUT: !17 = distinct !DISubprogram(name: "TestInt32ToUint16", linkageName: "_CTestInt32ToUint16.Main", scope: null, file: !3, line: 22, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !18 = !DILocation(line: 22, column: 46, scope: !17)
// CHECK:STDOUT: !19 = !DILocation(line: 22, column: 39, scope: !17)
// CHECK:STDOUT: !20 = distinct !DISubprogram(name: "TestUint32ToInt16", linkageName: "_CTestUint32ToInt16.Main", scope: null, file: !3, line: 23, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !21 = !DILocation(line: 23, column: 46, scope: !20)
// CHECK:STDOUT: !22 = !DILocation(line: 23, column: 39, scope: !20)
// CHECK:STDOUT: !23 = distinct !DISubprogram(name: "TestUint32ToUint16", linkageName: "_CTestUint32ToUint16.Main", scope: null, file: !3, line: 24, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !24 = !DILocation(line: 24, column: 47, scope: !23)
// CHECK:STDOUT: !25 = !DILocation(line: 24, column: 40, scope: !23)
// CHECK:STDOUT: !26 = distinct !DISubprogram(name: "TestInt32ToInt64", linkageName: "_CTestInt32ToInt64.Main", scope: null, file: !3, line: 32, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !27 = !DILocation(line: 32, column: 45, scope: !26)
// CHECK:STDOUT: !28 = !DILocation(line: 32, column: 38, scope: !26)
// CHECK:STDOUT: !29 = distinct !DISubprogram(name: "TestInt32ToUint64", linkageName: "_CTestInt32ToUint64.Main", scope: null, file: !3, line: 33, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !30 = !DILocation(line: 33, column: 46, scope: !29)
// CHECK:STDOUT: !31 = !DILocation(line: 33, column: 39, scope: !29)
// CHECK:STDOUT: !32 = distinct !DISubprogram(name: "TestUint32ToInt64", linkageName: "_CTestUint32ToInt64.Main", scope: null, file: !3, line: 34, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !33 = !DILocation(line: 34, column: 46, scope: !32)
// CHECK:STDOUT: !34 = !DILocation(line: 34, column: 39, scope: !32)
// CHECK:STDOUT: !35 = distinct !DISubprogram(name: "TestUint32ToUint64", linkageName: "_CTestUint32ToUint64.Main", scope: null, file: !3, line: 35, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !36 = !DILocation(line: 35, column: 47, scope: !35)
// CHECK:STDOUT: !37 = !DILocation(line: 35, column: 40, scope: !35)
+8 -8
View File
@@ -44,11 +44,11 @@ fn G() {
// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %d.var, ptr align 1 @D.val.loc19_16, i64 0, i1 false), !dbg !9
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(i64 4, ptr %n.var), !dbg !7
// CHECK:STDOUT: store i32 0, ptr %n.var, align 4, !dbg !10
// CHECK:STDOUT: call void @_CF.Main.118(ptr %c.var), !dbg !11
// CHECK:STDOUT: call void @_CF.Main.119(ptr %d.var), !dbg !12
// CHECK:STDOUT: call void @_CF.Main.129(ptr %c.var), !dbg !11
// CHECK:STDOUT: call void @_CF.Main.130(ptr %d.var), !dbg !12
// CHECK:STDOUT: %.loc24 = load i32, ptr %n.var, align 4, !dbg !13
// CHECK:STDOUT: call void @_CF.Main.120(i32 %.loc24), !dbg !14
// CHECK:STDOUT: call void @_CF.Main.121(%type zeroinitializer), !dbg !15
// CHECK:STDOUT: call void @_CF.Main.131(i32 %.loc24), !dbg !14
// CHECK:STDOUT: call void @_CF.Main.132(%type zeroinitializer), !dbg !15
// CHECK:STDOUT: ret void, !dbg !16
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -58,13 +58,13 @@ fn G() {
// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #1
// CHECK:STDOUT:
// CHECK:STDOUT: declare void @_CF.Main.118(ptr)
// CHECK:STDOUT: declare void @_CF.Main.129(ptr)
// CHECK:STDOUT:
// CHECK:STDOUT: declare void @_CF.Main.119(ptr)
// CHECK:STDOUT: declare void @_CF.Main.130(ptr)
// CHECK:STDOUT:
// CHECK:STDOUT: declare void @_CF.Main.120(i32)
// CHECK:STDOUT: declare void @_CF.Main.131(i32)
// CHECK:STDOUT:
// CHECK:STDOUT: declare void @_CF.Main.121(%type)
// CHECK:STDOUT: declare void @_CF.Main.132(%type)
// CHECK:STDOUT:
// CHECK:STDOUT: ; uselistorder directives
// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 2, 1, 0 }
@@ -34,7 +34,7 @@ fn CallF() -> i32 {
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(i64 4, ptr %n.var), !dbg !7
// CHECK:STDOUT: store i32 0, ptr %n.var, align 4, !dbg !9
// CHECK:STDOUT: %.loc20_14 = load i32, ptr %n.var, align 4, !dbg !10
// CHECK:STDOUT: %F.call = call i32 @_CF.C.Main.118(ptr %c.var, i32 %.loc20_14), !dbg !11
// CHECK:STDOUT: %F.call = call i32 @_CF.C.Main.129(ptr %c.var, i32 %.loc20_14), !dbg !11
// CHECK:STDOUT: ret i32 %F.call, !dbg !12
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -44,7 +44,7 @@ fn CallF() -> i32 {
// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #1
// CHECK:STDOUT:
// CHECK:STDOUT: declare i32 @_CF.C.Main.118(ptr, i32)
// CHECK:STDOUT: declare i32 @_CF.C.Main.129(ptr, i32)
// CHECK:STDOUT:
// CHECK:STDOUT: ; uselistorder directives
// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 1, 0 }
+47 -44
View File
@@ -238,6 +238,10 @@ constexpr BuiltinInfo FloatMakeType = {"float.make_type",
constexpr BuiltinInfo BoolMakeType = {"bool.make_type",
ValidateSignature<auto()->Type>};
// Converts between integer types, truncating if necessary.
constexpr BuiltinInfo IntConvert = {"int.convert",
ValidateSignature<auto(AnyInt)->AnyInt>};
// Converts between integer types, with a diagnostic if the value doesn't fit.
constexpr BuiltinInfo IntConvertChecked = {
"int.convert_checked", ValidateSignature<auto(AnyInt)->AnyInt>};
@@ -421,26 +425,54 @@ auto BuiltinFunctionKind::IsValidType(const File& sem_ir,
return ValidateFns[AsInt()](sem_ir, arg_types, return_type);
}
// Determines whether a builtin call involves an integer literal in its
// arguments or return type. If so, for many builtins we want to treat the call
// as being compile-time-only. This is because `Core.IntLiteral` has an empty
// runtime representation, and a value of that type isn't necessarily a
// compile-time constant, so an arbitrary runtime value of type
// `Core.IntLiteral` may not have a value available for the builtin to use. For
// example, given:
//
// var n: Core.IntLiteral() = 123;
//
// we would be unable to lower a runtime operation such as `(1 as i32) << n`
// because the runtime representation of `n` doesn't track its value at all.
//
// For now, we treat all operations involving `Core.IntLiteral` as being
// compile-time-only.
//
// TODO: We will need to accept things like `some_i32 << 5` eventually. We could
// allow builtin calls at runtime if all the IntLiteral arguments have constant
// values, or add logic to the prelude to promote the `IntLiteral` operand to a
// different type in such cases.
//
// TODO: For now, we also treat builtins *returning* `Core.IntLiteral` as being
// compile-time-only. This is mostly done for simplicity, but should probably be
// revisited.
static auto AnyIntLiteralTypes(const File& sem_ir,
llvm::ArrayRef<InstId> arg_ids,
TypeId return_type_id) -> bool {
if (sem_ir.types().Is<SemIR::IntLiteralType>(return_type_id)) {
return true;
}
for (auto arg_id : arg_ids) {
if (sem_ir.types().Is<SemIR::IntLiteralType>(
sem_ir.insts().Get(arg_id).type_id())) {
return true;
}
}
return false;
}
auto BuiltinFunctionKind::IsCompTimeOnly(const File& sem_ir,
llvm::ArrayRef<InstId> arg_ids,
TypeId return_type_id) const -> bool {
// Some builtin functions are unconditionally compile-time-only, or
// unconditionally usable at runtime. However, we need to take extra care for
// builtins operating on an arbitrary integer type, because `Core.IntLiteral`
// has an empty runtime representation and a value of that type isn't
// necessarily a compile-time constant. For example, given:
//
// var n: Core.IntLiteral() = 123;
//
// we would be unable to lower a runtime operation such as `(1 as i32) << n`
// because the runtime representation of `n` doesn't track its value at all.
// So we treat operations involving `Core.IntLiteral` as being
// compile-time-only.
switch (*this) {
case IntConvertChecked:
// Checked integer conversions are compile-time only.
return true;
case IntConvert:
case IntSNegate:
case IntComplement:
case IntSAdd:
@@ -451,46 +483,17 @@ auto BuiltinFunctionKind::IsCompTimeOnly(const File& sem_ir,
case IntAnd:
case IntOr:
case IntXor:
// Integer builtins producing an IntLiteral are compile-time only.
// TODO: We could allow these at runtime and just produce an empty struct
// result. Should we?
return sem_ir.types().Is<SemIR::IntLiteralType>(return_type_id);
case IntLeftShift:
case IntRightShift:
// Shifts by an integer literal amount are compile-time only. We don't
// have a value for the shift amount at runtime in general.
// TODO: Decide how shifting a non-literal by a literal amount should
// work. We could support these with a builtin in the case where the shift
// amount has a compile-time value, or we could perform a conversion in
// the prelude.
if (sem_ir.types().Is<SemIR::IntLiteralType>(
sem_ir.insts().Get(arg_ids[1]).type_id())) {
return true;
}
// Integer builtins producing an IntLiteral are compile-time only.
// TODO: We could allow these at runtime and just produce an empty struct
// result. Should we?
return sem_ir.types().Is<SemIR::IntLiteralType>(return_type_id);
case IntEq:
case IntNeq:
case IntLess:
case IntLessEq:
case IntGreater:
case IntGreaterEq:
// Comparisons involving an integer literal operand are compile-time only.
// We don't have a value for an integer literal operand argument at
// runtime in general.
// TODO: Figure out how mixed literal / non-literal comparisons should
// work. We could support these with builtins in the case where the
// operand has a compile-time value, or we could perform a conversion in
// the prelude.
return sem_ir.types().Is<SemIR::IntLiteralType>(
sem_ir.insts().Get(arg_ids[0]).type_id()) ||
sem_ir.types().Is<SemIR::IntLiteralType>(
sem_ir.insts().Get(arg_ids[1]).type_id());
// Integer operations are compile-time-only if they involve integer
// literal types. See AnyIntLiteralTypes comment for explanation.
return AnyIntLiteralTypes(sem_ir, arg_ids, return_type_id);
default:
// TODO: Should the sized MakeType functions be compile-time only? We
@@ -31,6 +31,7 @@ CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(FloatMakeType)
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(BoolMakeType)
// Integer conversion.
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntConvert)
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntConvertChecked)
// Integer arithmetic.