diff --git a/core/prelude/types/int.carbon b/core/prelude/types/int.carbon index 27b42e59d7615..fa74eb712d3a3 100644 --- a/core/prelude/types/int.carbon +++ b/core/prelude/types/int.carbon @@ -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 { diff --git a/core/prelude/types/uint.carbon b/core/prelude/types/uint.carbon index 8a706e47ccb99..540795f190b02 100644 --- a/core/prelude/types/uint.carbon +++ b/core/prelude/types/uint.carbon @@ -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 { diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 72b6ee057a494..b9135185865ab 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -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(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); diff --git a/toolchain/check/testdata/builtins/int/convert.carbon b/toolchain/check/testdata/builtins/int/convert.carbon new file mode 100644 index 0000000000000..91970fadfaeee --- /dev/null +++ b/toolchain/check/testdata/builtins/int/convert.carbon @@ -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); diff --git a/toolchain/check/testdata/class/import.carbon b/toolchain/check/testdata/class/import.carbon index aa40f5210a220..5aa195469ef1c 100644 --- a/toolchain/check/testdata/class/import.carbon +++ b/toolchain/check/testdata/class/import.carbon @@ -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 .inst1019: %ptr.6cf]() [from "a.carbon"]; +// CHECK:STDOUT: fn @G[addr .inst1063: %ptr.6cf]() [from "a.carbon"]; // CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/overloaded/fail_error_recovery.carbon b/toolchain/check/testdata/operators/overloaded/fail_error_recovery.carbon index cbdbf29ff459e..f9cd4a5047796 100644 --- a/toolchain/check/testdata/operators/overloaded/fail_error_recovery.carbon +++ b/toolchain/check/testdata/operators/overloaded/fail_error_recovery.carbon @@ -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: = 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: = 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: diff --git a/toolchain/check/testdata/struct/import.carbon b/toolchain/check/testdata/struct/import.carbon index 37894ada66288..9008f325c7d02 100644 --- a/toolchain/check/testdata/struct/import.carbon +++ b/toolchain/check/testdata/struct/import.carbon @@ -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: = 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: = 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: = 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 { diff --git a/toolchain/check/testdata/tuple/import.carbon b/toolchain/check/testdata/tuple/import.carbon index 66355dc8f4f07..47d4fa8a0f9b6 100644 --- a/toolchain/check/testdata/tuple/import.carbon +++ b/toolchain/check/testdata/tuple/import.carbon @@ -293,7 +293,7 @@ var c_bad: C((3, 4)) = F(); // CHECK:STDOUT: import Core//prelude/... // CHECK:STDOUT: } // CHECK:STDOUT: %import_ref.8f2: = 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: = 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: = 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 { diff --git a/toolchain/lower/handle_call.cpp b/toolchain/lower/handle_call.cpp index d246ed27455d5..bff3b6a170534 100644 --- a/toolchain/lower/handle_call.cpp +++ b/toolchain/lower/handle_call.cpp @@ -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]); diff --git a/toolchain/lower/testdata/builtins/int.carbon b/toolchain/lower/testdata/builtins/int.carbon index d9273ef196cef..015d3e5ab45a3 100644 --- a/toolchain/lower/testdata/builtins/int.carbon +++ b/toolchain/lower/testdata/builtins/int.carbon @@ -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: 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: 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: 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) diff --git a/toolchain/lower/testdata/function/generic/call.carbon b/toolchain/lower/testdata/function/generic/call.carbon index 46b827f525474..bf5008f2d6a30 100644 --- a/toolchain/lower/testdata/function/generic/call.carbon +++ b/toolchain/lower/testdata/function/generic/call.carbon @@ -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 } diff --git a/toolchain/lower/testdata/function/generic/call_method.carbon b/toolchain/lower/testdata/function/generic/call_method.carbon index 9dcd371b8628e..ff116d7003117 100644 --- a/toolchain/lower/testdata/function/generic/call_method.carbon +++ b/toolchain/lower/testdata/function/generic/call_method.carbon @@ -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 } diff --git a/toolchain/sem_ir/builtin_function_kind.cpp b/toolchain/sem_ir/builtin_function_kind.cpp index 875165cf6ef9f..3e5fb32ab128c 100644 --- a/toolchain/sem_ir/builtin_function_kind.cpp +++ b/toolchain/sem_ir/builtin_function_kind.cpp @@ -238,6 +238,10 @@ constexpr BuiltinInfo FloatMakeType = {"float.make_type", constexpr BuiltinInfo BoolMakeType = {"bool.make_type", ValidateSignatureType>}; +// Converts between integer types, truncating if necessary. +constexpr BuiltinInfo IntConvert = {"int.convert", + ValidateSignatureAnyInt>}; + // Converts between integer types, with a diagnostic if the value doesn't fit. constexpr BuiltinInfo IntConvertChecked = { "int.convert_checked", ValidateSignatureAnyInt>}; @@ -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 arg_ids, + TypeId return_type_id) -> bool { + if (sem_ir.types().Is(return_type_id)) { + return true; + } + for (auto arg_id : arg_ids) { + if (sem_ir.types().Is( + sem_ir.insts().Get(arg_id).type_id())) { + return true; + } + } + return false; +} + auto BuiltinFunctionKind::IsCompTimeOnly(const File& sem_ir, llvm::ArrayRef 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(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( - 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(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( - sem_ir.insts().Get(arg_ids[0]).type_id()) || - sem_ir.types().Is( - 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 diff --git a/toolchain/sem_ir/builtin_function_kind.def b/toolchain/sem_ir/builtin_function_kind.def index 0b643707cd976..8ae651d977b36 100644 --- a/toolchain/sem_ir/builtin_function_kind.def +++ b/toolchain/sem_ir/builtin_function_kind.def @@ -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.