Auto merge of #51583 - cuviper:packed_pair-bool, r=Mark-Simulacrum
Store scalar pair bools as i8 in memory We represent `bool` as `i1` in a `ScalarPair`, unlike other aggregates, to optimize IR for checked operators and the like. With this patch, we still do so when the pair is an immediate value, but we use the `i8` memory type when the value is loaded or stored as an LLVM aggregate. So `(bool, bool)` looks like an `{ i1, i1 }` immediate, but `{ i8, i8 }` in memory. When a pair is a direct function argument, `PassMode::Pair`, it is still passed using the immediate `i1` type, but as a return value it will use the `i8` memory type. Also, `bool`-like` enum tags will now use scalar pairs when possible, where they were previously excluded due to optimization issues. Fixes #51516. Closes #51566. r? @eddyb cc @nox
This commit is contained in:
commit
b3e7d70ce7
9 changed files with 96 additions and 50 deletions
|
@ -1020,13 +1020,8 @@ impl<'a, 'tcx> LayoutCx<'tcx, TyCtxt<'a, 'tcx, 'tcx>> {
|
||||||
let mut abi = Abi::Aggregate { sized: true };
|
let mut abi = Abi::Aggregate { sized: true };
|
||||||
if tag.value.size(dl) == size {
|
if tag.value.size(dl) == size {
|
||||||
abi = Abi::Scalar(tag.clone());
|
abi = Abi::Scalar(tag.clone());
|
||||||
} else if !tag.is_bool() {
|
} else {
|
||||||
// HACK(nox): Blindly using ScalarPair for all tagged enums
|
// Try to use a ScalarPair for all tagged enums.
|
||||||
// where applicable leads to Option<u8> being handled as {i1, i8},
|
|
||||||
// which later confuses SROA and some loop optimisations,
|
|
||||||
// ultimately leading to the repeat-trusted-len test
|
|
||||||
// failing. We make the trade-off of using ScalarPair only
|
|
||||||
// for types where the tag isn't a boolean.
|
|
||||||
let mut common_prim = None;
|
let mut common_prim = None;
|
||||||
for (field_layouts, layout_variant) in variants.iter().zip(&layout_variants) {
|
for (field_layouts, layout_variant) in variants.iter().zip(&layout_variants) {
|
||||||
let offsets = match layout_variant.fields {
|
let offsets = match layout_variant.fields {
|
||||||
|
|
|
@ -582,8 +582,8 @@ impl<'a, 'tcx> FnTypeExt<'a, 'tcx> for FnType<'tcx, Ty<'tcx>> {
|
||||||
PassMode::Ignore => continue,
|
PassMode::Ignore => continue,
|
||||||
PassMode::Direct(_) => arg.layout.immediate_llvm_type(cx),
|
PassMode::Direct(_) => arg.layout.immediate_llvm_type(cx),
|
||||||
PassMode::Pair(..) => {
|
PassMode::Pair(..) => {
|
||||||
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 0));
|
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 0, true));
|
||||||
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 1));
|
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 1, true));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
PassMode::Cast(cast) => cast.llvm_type(cx),
|
PassMode::Cast(cast) => cast.llvm_type(cx),
|
||||||
|
|
|
@ -266,8 +266,8 @@ pub fn unsize_thin_ptr<'a, 'tcx>(
|
||||||
}
|
}
|
||||||
let (lldata, llextra) = result.unwrap();
|
let (lldata, llextra) = result.unwrap();
|
||||||
// HACK(eddyb) have to bitcast pointers until LLVM removes pointee types.
|
// HACK(eddyb) have to bitcast pointers until LLVM removes pointee types.
|
||||||
(bx.bitcast(lldata, dst_layout.scalar_pair_element_llvm_type(bx.cx, 0)),
|
(bx.bitcast(lldata, dst_layout.scalar_pair_element_llvm_type(bx.cx, 0, true)),
|
||||||
bx.bitcast(llextra, dst_layout.scalar_pair_element_llvm_type(bx.cx, 1)))
|
bx.bitcast(llextra, dst_layout.scalar_pair_element_llvm_type(bx.cx, 1, true)))
|
||||||
}
|
}
|
||||||
_ => bug!("unsize_thin_ptr: called on bad types"),
|
_ => bug!("unsize_thin_ptr: called on bad types"),
|
||||||
}
|
}
|
||||||
|
@ -397,9 +397,14 @@ pub fn from_immediate(bx: &Builder, val: ValueRef) -> ValueRef {
|
||||||
|
|
||||||
pub fn to_immediate(bx: &Builder, val: ValueRef, layout: layout::TyLayout) -> ValueRef {
|
pub fn to_immediate(bx: &Builder, val: ValueRef, layout: layout::TyLayout) -> ValueRef {
|
||||||
if let layout::Abi::Scalar(ref scalar) = layout.abi {
|
if let layout::Abi::Scalar(ref scalar) = layout.abi {
|
||||||
if scalar.is_bool() {
|
return to_immediate_scalar(bx, val, scalar);
|
||||||
return bx.trunc(val, Type::i1(bx.cx));
|
}
|
||||||
}
|
val
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_immediate_scalar(bx: &Builder, val: ValueRef, scalar: &layout::Scalar) -> ValueRef {
|
||||||
|
if scalar.is_bool() {
|
||||||
|
return bx.trunc(val, Type::i1(bx.cx));
|
||||||
}
|
}
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ use rustc_data_structures::indexed_vec::Idx;
|
||||||
use rustc_data_structures::sync::Lrc;
|
use rustc_data_structures::sync::Lrc;
|
||||||
|
|
||||||
use base;
|
use base;
|
||||||
use common::{self, CodegenCx, C_null, C_undef, C_usize};
|
use common::{CodegenCx, C_null, C_undef, C_usize};
|
||||||
use builder::{Builder, MemFlags};
|
use builder::{Builder, MemFlags};
|
||||||
use value::Value;
|
use value::Value;
|
||||||
use type_of::LayoutLlvmExt;
|
use type_of::LayoutLlvmExt;
|
||||||
|
@ -128,13 +128,13 @@ impl<'a, 'tcx> OperandRef<'tcx> {
|
||||||
bx.cx,
|
bx.cx,
|
||||||
a,
|
a,
|
||||||
a_scalar,
|
a_scalar,
|
||||||
layout.scalar_pair_element_llvm_type(bx.cx, 0),
|
layout.scalar_pair_element_llvm_type(bx.cx, 0, true),
|
||||||
);
|
);
|
||||||
let b_llval = scalar_to_llvm(
|
let b_llval = scalar_to_llvm(
|
||||||
bx.cx,
|
bx.cx,
|
||||||
b,
|
b,
|
||||||
b_scalar,
|
b_scalar,
|
||||||
layout.scalar_pair_element_llvm_type(bx.cx, 1),
|
layout.scalar_pair_element_llvm_type(bx.cx, 1, true),
|
||||||
);
|
);
|
||||||
OperandValue::Pair(a_llval, b_llval)
|
OperandValue::Pair(a_llval, b_llval)
|
||||||
},
|
},
|
||||||
|
@ -193,8 +193,8 @@ impl<'a, 'tcx> OperandRef<'tcx> {
|
||||||
self, llty);
|
self, llty);
|
||||||
// Reconstruct the immediate aggregate.
|
// Reconstruct the immediate aggregate.
|
||||||
let mut llpair = C_undef(llty);
|
let mut llpair = C_undef(llty);
|
||||||
llpair = bx.insert_value(llpair, a, 0);
|
llpair = bx.insert_value(llpair, base::from_immediate(bx, a), 0);
|
||||||
llpair = bx.insert_value(llpair, b, 1);
|
llpair = bx.insert_value(llpair, base::from_immediate(bx, b), 1);
|
||||||
llpair
|
llpair
|
||||||
} else {
|
} else {
|
||||||
self.immediate()
|
self.immediate()
|
||||||
|
@ -206,13 +206,14 @@ impl<'a, 'tcx> OperandRef<'tcx> {
|
||||||
llval: ValueRef,
|
llval: ValueRef,
|
||||||
layout: TyLayout<'tcx>)
|
layout: TyLayout<'tcx>)
|
||||||
-> OperandRef<'tcx> {
|
-> OperandRef<'tcx> {
|
||||||
let val = if layout.is_llvm_scalar_pair() {
|
let val = if let layout::Abi::ScalarPair(ref a, ref b) = layout.abi {
|
||||||
debug!("Operand::from_immediate_or_packed_pair: unpacking {:?} @ {:?}",
|
debug!("Operand::from_immediate_or_packed_pair: unpacking {:?} @ {:?}",
|
||||||
llval, layout);
|
llval, layout);
|
||||||
|
|
||||||
// Deconstruct the immediate aggregate.
|
// Deconstruct the immediate aggregate.
|
||||||
OperandValue::Pair(bx.extract_value(llval, 0),
|
let a_llval = base::to_immediate_scalar(bx, bx.extract_value(llval, 0), a);
|
||||||
bx.extract_value(llval, 1))
|
let b_llval = base::to_immediate_scalar(bx, bx.extract_value(llval, 1), b);
|
||||||
|
OperandValue::Pair(a_llval, b_llval)
|
||||||
} else {
|
} else {
|
||||||
OperandValue::Immediate(llval)
|
OperandValue::Immediate(llval)
|
||||||
};
|
};
|
||||||
|
@ -264,8 +265,8 @@ impl<'a, 'tcx> OperandRef<'tcx> {
|
||||||
*llval = bx.bitcast(*llval, field.immediate_llvm_type(bx.cx));
|
*llval = bx.bitcast(*llval, field.immediate_llvm_type(bx.cx));
|
||||||
}
|
}
|
||||||
OperandValue::Pair(ref mut a, ref mut b) => {
|
OperandValue::Pair(ref mut a, ref mut b) => {
|
||||||
*a = bx.bitcast(*a, field.scalar_pair_element_llvm_type(bx.cx, 0));
|
*a = bx.bitcast(*a, field.scalar_pair_element_llvm_type(bx.cx, 0, true));
|
||||||
*b = bx.bitcast(*b, field.scalar_pair_element_llvm_type(bx.cx, 1));
|
*b = bx.bitcast(*b, field.scalar_pair_element_llvm_type(bx.cx, 1, true));
|
||||||
}
|
}
|
||||||
OperandValue::Ref(..) => bug!()
|
OperandValue::Ref(..) => bug!()
|
||||||
}
|
}
|
||||||
|
@ -308,11 +309,7 @@ impl<'a, 'tcx> OperandValue {
|
||||||
}
|
}
|
||||||
OperandValue::Pair(a, b) => {
|
OperandValue::Pair(a, b) => {
|
||||||
for (i, &x) in [a, b].iter().enumerate() {
|
for (i, &x) in [a, b].iter().enumerate() {
|
||||||
let mut llptr = bx.struct_gep(dest.llval, i as u64);
|
let llptr = bx.struct_gep(dest.llval, i as u64);
|
||||||
// Make sure to always store i1 as i8.
|
|
||||||
if common::val_ty(x) == Type::i1(bx.cx) {
|
|
||||||
llptr = bx.pointercast(llptr, Type::i8p(bx.cx));
|
|
||||||
}
|
|
||||||
let val = base::from_immediate(bx, x);
|
let val = base::from_immediate(bx, x);
|
||||||
bx.store_with_flags(val, llptr, dest.align, flags);
|
bx.store_with_flags(val, llptr, dest.align, flags);
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,11 +127,7 @@ impl<'a, 'tcx> PlaceRef<'tcx> {
|
||||||
OperandValue::Immediate(base::to_immediate(bx, llval, self.layout))
|
OperandValue::Immediate(base::to_immediate(bx, llval, self.layout))
|
||||||
} else if let layout::Abi::ScalarPair(ref a, ref b) = self.layout.abi {
|
} else if let layout::Abi::ScalarPair(ref a, ref b) = self.layout.abi {
|
||||||
let load = |i, scalar: &layout::Scalar| {
|
let load = |i, scalar: &layout::Scalar| {
|
||||||
let mut llptr = bx.struct_gep(self.llval, i as u64);
|
let llptr = bx.struct_gep(self.llval, i as u64);
|
||||||
// Make sure to always load i1 as i8.
|
|
||||||
if scalar.is_bool() {
|
|
||||||
llptr = bx.pointercast(llptr, Type::i8p(bx.cx));
|
|
||||||
}
|
|
||||||
let load = bx.load(llptr, self.align);
|
let load = bx.load(llptr, self.align);
|
||||||
scalar_load_metadata(load, scalar);
|
scalar_load_metadata(load, scalar);
|
||||||
if scalar.is_bool() {
|
if scalar.is_bool() {
|
||||||
|
|
|
@ -232,7 +232,7 @@ impl<'a, 'tcx> FunctionCx<'a, 'tcx> {
|
||||||
// HACK(eddyb) have to bitcast pointers
|
// HACK(eddyb) have to bitcast pointers
|
||||||
// until LLVM removes pointee types.
|
// until LLVM removes pointee types.
|
||||||
let lldata = bx.pointercast(lldata,
|
let lldata = bx.pointercast(lldata,
|
||||||
cast.scalar_pair_element_llvm_type(bx.cx, 0));
|
cast.scalar_pair_element_llvm_type(bx.cx, 0, true));
|
||||||
OperandValue::Pair(lldata, llextra)
|
OperandValue::Pair(lldata, llextra)
|
||||||
}
|
}
|
||||||
OperandValue::Immediate(lldata) => {
|
OperandValue::Immediate(lldata) => {
|
||||||
|
@ -251,7 +251,7 @@ impl<'a, 'tcx> FunctionCx<'a, 'tcx> {
|
||||||
if let OperandValue::Pair(data_ptr, meta) = operand.val {
|
if let OperandValue::Pair(data_ptr, meta) = operand.val {
|
||||||
if cast.is_llvm_scalar_pair() {
|
if cast.is_llvm_scalar_pair() {
|
||||||
let data_cast = bx.pointercast(data_ptr,
|
let data_cast = bx.pointercast(data_ptr,
|
||||||
cast.scalar_pair_element_llvm_type(bx.cx, 0));
|
cast.scalar_pair_element_llvm_type(bx.cx, 0, true));
|
||||||
OperandValue::Pair(data_cast, meta)
|
OperandValue::Pair(data_cast, meta)
|
||||||
} else { // cast to thin-ptr
|
} else { // cast to thin-ptr
|
||||||
// Cast of fat-ptr to thin-ptr is an extraction of data-ptr and
|
// Cast of fat-ptr to thin-ptr is an extraction of data-ptr and
|
||||||
|
|
|
@ -47,8 +47,8 @@ fn uncached_llvm_type<'a, 'tcx>(cx: &CodegenCx<'a, 'tcx>,
|
||||||
}
|
}
|
||||||
layout::Abi::ScalarPair(..) => {
|
layout::Abi::ScalarPair(..) => {
|
||||||
return Type::struct_(cx, &[
|
return Type::struct_(cx, &[
|
||||||
layout.scalar_pair_element_llvm_type(cx, 0),
|
layout.scalar_pair_element_llvm_type(cx, 0, false),
|
||||||
layout.scalar_pair_element_llvm_type(cx, 1),
|
layout.scalar_pair_element_llvm_type(cx, 1, false),
|
||||||
], false);
|
], false);
|
||||||
}
|
}
|
||||||
layout::Abi::Uninhabited |
|
layout::Abi::Uninhabited |
|
||||||
|
@ -206,7 +206,7 @@ pub trait LayoutLlvmExt<'tcx> {
|
||||||
fn scalar_llvm_type_at<'a>(&self, cx: &CodegenCx<'a, 'tcx>,
|
fn scalar_llvm_type_at<'a>(&self, cx: &CodegenCx<'a, 'tcx>,
|
||||||
scalar: &layout::Scalar, offset: Size) -> Type;
|
scalar: &layout::Scalar, offset: Size) -> Type;
|
||||||
fn scalar_pair_element_llvm_type<'a>(&self, cx: &CodegenCx<'a, 'tcx>,
|
fn scalar_pair_element_llvm_type<'a>(&self, cx: &CodegenCx<'a, 'tcx>,
|
||||||
index: usize) -> Type;
|
index: usize, immediate: bool) -> Type;
|
||||||
fn llvm_field_index(&self, index: usize) -> u64;
|
fn llvm_field_index(&self, index: usize) -> u64;
|
||||||
fn pointee_info_at<'a>(&self, cx: &CodegenCx<'a, 'tcx>, offset: Size)
|
fn pointee_info_at<'a>(&self, cx: &CodegenCx<'a, 'tcx>, offset: Size)
|
||||||
-> Option<PointeeInfo>;
|
-> Option<PointeeInfo>;
|
||||||
|
@ -340,7 +340,7 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyLayout<'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scalar_pair_element_llvm_type<'a>(&self, cx: &CodegenCx<'a, 'tcx>,
|
fn scalar_pair_element_llvm_type<'a>(&self, cx: &CodegenCx<'a, 'tcx>,
|
||||||
index: usize) -> Type {
|
index: usize, immediate: bool) -> Type {
|
||||||
// HACK(eddyb) special-case fat pointers until LLVM removes
|
// HACK(eddyb) special-case fat pointers until LLVM removes
|
||||||
// pointee types, to avoid bitcasting every `OperandRef::deref`.
|
// pointee types, to avoid bitcasting every `OperandRef::deref`.
|
||||||
match self.ty.sty {
|
match self.ty.sty {
|
||||||
|
@ -350,7 +350,7 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyLayout<'tcx> {
|
||||||
}
|
}
|
||||||
ty::TyAdt(def, _) if def.is_box() => {
|
ty::TyAdt(def, _) if def.is_box() => {
|
||||||
let ptr_ty = cx.tcx.mk_mut_ptr(self.ty.boxed_ty());
|
let ptr_ty = cx.tcx.mk_mut_ptr(self.ty.boxed_ty());
|
||||||
return cx.layout_of(ptr_ty).scalar_pair_element_llvm_type(cx, index);
|
return cx.layout_of(ptr_ty).scalar_pair_element_llvm_type(cx, index, immediate);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -361,14 +361,13 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyLayout<'tcx> {
|
||||||
};
|
};
|
||||||
let scalar = [a, b][index];
|
let scalar = [a, b][index];
|
||||||
|
|
||||||
// Make sure to return the same type `immediate_llvm_type` would,
|
// Make sure to return the same type `immediate_llvm_type` would when
|
||||||
// to avoid dealing with two types and the associated conversions.
|
// dealing with an immediate pair. This means that `(bool, bool)` is
|
||||||
// This means that `(bool, bool)` is represented as `{i1, i1}`,
|
// effectively represented as `{i8, i8}` in memory and two `i1`s as an
|
||||||
// both in memory and as an immediate, while `bool` is typically
|
// immediate, just like `bool` is typically `i8` in memory and only `i1`
|
||||||
// `i8` in memory and only `i1` when immediate. While we need to
|
// when immediate. We need to load/store `bool` as `i8` to avoid
|
||||||
// load/store `bool` as `i8` to avoid crippling LLVM optimizations,
|
// crippling LLVM optimizations or triggering other LLVM bugs with `i1`.
|
||||||
// `i1` in a LLVM aggregate is valid and mostly equivalent to `i8`.
|
if immediate && scalar.is_bool() {
|
||||||
if scalar.is_bool() {
|
|
||||||
return Type::i1(cx);
|
return Type::i1(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ pub fn enum_id_1(x: Option<Result<u16, u16>>) -> Option<Result<u16, u16>> {
|
||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: i16 @enum_id_2(i16)
|
// CHECK: { i8, i8 } @enum_id_2(i1 zeroext %x.0, i8 %x.1)
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn enum_id_2(x: Option<u8>) -> Option<u8> {
|
pub fn enum_id_2(x: Option<u8>) -> Option<u8> {
|
||||||
x
|
x
|
||||||
|
|
54
src/test/codegen/scalar-pair-bool.rs
Normal file
54
src/test/codegen/scalar-pair-bool.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
// compile-flags: -O
|
||||||
|
|
||||||
|
#![crate_type = "lib"]
|
||||||
|
|
||||||
|
// CHECK: define { i8, i8 } @pair_bool_bool(i1 zeroext %pair.0, i1 zeroext %pair.1)
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn pair_bool_bool(pair: (bool, bool)) -> (bool, bool) {
|
||||||
|
pair
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: define { i8, i32 } @pair_bool_i32(i1 zeroext %pair.0, i32 %pair.1)
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn pair_bool_i32(pair: (bool, i32)) -> (bool, i32) {
|
||||||
|
pair
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: define { i32, i8 } @pair_i32_bool(i32 %pair.0, i1 zeroext %pair.1)
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn pair_i32_bool(pair: (i32, bool)) -> (i32, bool) {
|
||||||
|
pair
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: define { i8, i8 } @pair_and_or(i1 zeroext %arg0.0, i1 zeroext %arg0.1)
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn pair_and_or((a, b): (bool, bool)) -> (bool, bool) {
|
||||||
|
// Make sure it can operate directly on the unpacked args
|
||||||
|
// CHECK: and i1 %arg0.0, %arg0.1
|
||||||
|
// CHECK: or i1 %arg0.0, %arg0.1
|
||||||
|
(a && b, a || b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: define void @pair_branches(i1 zeroext %arg0.0, i1 zeroext %arg0.1)
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn pair_branches((a, b): (bool, bool)) {
|
||||||
|
// Make sure it can branch directly on the unpacked bool args
|
||||||
|
// CHECK: br i1 %arg0.0
|
||||||
|
if a {
|
||||||
|
println!("Hello!");
|
||||||
|
}
|
||||||
|
// CHECK: br i1 %arg0.1
|
||||||
|
if b {
|
||||||
|
println!("Goodbye!");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue