mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 20:01:14 +01:00
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:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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 }
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user