Skip to content

Commit

Permalink
Use insertvalue and extractvalue instead of memcpy in CastTarget
Browse files Browse the repository at this point in the history
  • Loading branch information
DianQK committed Aug 11, 2024
1 parent c9bd03c commit 448885c
Show file tree
Hide file tree
Showing 9 changed files with 633 additions and 357 deletions.
34 changes: 2 additions & 32 deletions compiler/rustc_codegen_llvm/src/abi.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use std::cmp;

use libc::c_uint;
use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue};
use rustc_codegen_ssa::mir::place::{PlaceRef, PlaceValue};
use rustc_codegen_ssa::traits::*;
use rustc_codegen_ssa::MemFlags;
use rustc_middle::ty::layout::LayoutOf;
pub use rustc_middle::ty::layout::{FAT_PTR_ADDR, FAT_PTR_EXTRA};
use rustc_middle::ty::Ty;
Expand Down Expand Up @@ -215,35 +212,8 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> {
PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => {
bug!("unsized `ArgAbi` must be handled through `store_fn_arg`");
}
PassMode::Cast { cast, pad_i32: _ } => {
// The ABI mandates that the value is passed as a different struct representation.
// Spill and reload it from the stack to convert from the ABI representation to
// the Rust representation.
let scratch_size = cast.size(bx);
let scratch_align = cast.align(bx);
// Note that the ABI type may be either larger or smaller than the Rust type,
// due to the presence or absence of trailing padding. For example:
// - On some ABIs, the Rust layout { f64, f32, <f32 padding> } may omit padding
// when passed by value, making it smaller.
// - On some ABIs, the Rust layout { u16, u16, u16 } may be padded up to 8 bytes
// when passed by value, making it larger.
let copy_bytes =
cmp::min(cast.unaligned_size(bx).bytes(), self.layout.size.bytes());
// Allocate some scratch space...
let llscratch = bx.alloca(scratch_size, scratch_align);
bx.lifetime_start(llscratch, scratch_size);
// ...store the value...
bx.store(val, llscratch, scratch_align);
// ... and then memcpy it to the intended destination.
bx.memcpy(
dst.val.llval,
self.layout.align.abi,
llscratch,
scratch_align,
bx.const_usize(copy_bytes),
MemFlags::empty(),
);
bx.lifetime_end(llscratch, scratch_size);
PassMode::Cast { cast, .. } => {
cast.cast_other_abi_to_rust(bx, val, dst.val.llval, self.layout);
}
_ => {
OperandRef::from_immediate_or_packed_pair(bx, val, self.layout).val.store(bx, dst);
Expand Down
44 changes: 8 additions & 36 deletions compiler/rustc_codegen_ssa/src/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use super::{CachedLlbb, FunctionCx, LocalRef};
use crate::base::{self, is_call_from_compiler_builtins_to_upstream_monomorphization};
use crate::common::{self, IntPredicate};
use crate::errors::CompilerBuiltinsCannotCall;
use crate::meth;
use crate::traits::*;
use crate::{meth, MemFlags};

// Indicates if we are in the middle of merging a BB's successor into it. This
// can happen when BB jumps directly to its successor and the successor has no
Expand Down Expand Up @@ -462,7 +462,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
}
}

PassMode::Cast { cast: cast_ty, pad_i32: _ } => {
PassMode::Cast { cast, pad_i32: _ } => {
let op = match self.locals[mir::RETURN_PLACE] {
LocalRef::Operand(op) => op,
LocalRef::PendingOperand => bug!("use of return before def"),
Expand All @@ -471,23 +471,22 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
}
LocalRef::UnsizedPlace(_) => bug!("return type must be sized"),
};
let llslot = match op.val {
let (llslot, align) = match op.val {
Immediate(_) | Pair(..) => {
let scratch = PlaceRef::alloca(bx, self.fn_abi.ret.layout);
op.val.store(bx, scratch);
scratch.val.llval
(scratch.val.llval, scratch.val.align)
}
Ref(place_val) => {
assert_eq!(
place_val.align, op.layout.align.abi,
"return place is unaligned!"
);
place_val.llval
(place_val.llval, place_val.align)
}
ZeroSized => bug!("ZST return value shouldn't be in PassMode::Cast"),
};
let ty = bx.cast_backend_type(cast_ty);
bx.load(ty, llslot, self.fn_abi.ret.layout.align.abi)
cast.cast_rust_abi_to_other(bx, llslot, align)
}
};
bx.ret(llval);
Expand Down Expand Up @@ -1515,35 +1514,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {

if by_ref && !arg.is_indirect() {
// Have to load the argument, maybe while casting it.
if let PassMode::Cast { cast, pad_i32: _ } = &arg.mode {
// The ABI mandates that the value is passed as a different struct representation.
// Spill and reload it from the stack to convert from the Rust representation to
// the ABI representation.
let scratch_size = cast.size(bx);
let scratch_align = cast.align(bx);
// Note that the ABI type may be either larger or smaller than the Rust type,
// due to the presence or absence of trailing padding. For example:
// - On some ABIs, the Rust layout { f64, f32, <f32 padding> } may omit padding
// when passed by value, making it smaller.
// - On some ABIs, the Rust layout { u16, u16, u16 } may be padded up to 8 bytes
// when passed by value, making it larger.
let copy_bytes = cmp::min(cast.unaligned_size(bx).bytes(), arg.layout.size.bytes());
// Allocate some scratch space...
let llscratch = bx.alloca(scratch_size, scratch_align);
bx.lifetime_start(llscratch, scratch_size);
// ...memcpy the value...
bx.memcpy(
llscratch,
scratch_align,
llval,
align,
bx.const_usize(copy_bytes),
MemFlags::empty(),
);
// ...and then load it with the ABI type.
let cast_ty = bx.cast_backend_type(cast);
llval = bx.load(cast_ty, llscratch, scratch_align);
bx.lifetime_end(llscratch, scratch_size);
if let PassMode::Cast { cast, .. } = &arg.mode {
llval = cast.cast_rust_abi_to_other(bx, llval, align);
} else {
// We can't use `PlaceRef::load` here because the argument
// may have a type we don't treat as immediate, but the ABI
Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_codegen_ssa/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,13 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
let llretptr = start_bx.get_param(0);
return LocalRef::Place(PlaceRef::new_sized(llretptr, layout));
}
PassMode::Cast { ref cast, .. } => {
PassMode::Cast { ref cast, .. }
if start_bx.cast_backend_type(cast)
== start_bx.reg_backend_type(&cast.rest.unit)
&& cast.size(&start_bx) > layout.size =>
{
// When using just a single register, we directly use load or store instructions,
// so we need to ensure that the allocated space is sufficiently large.
debug!("alloc: {:?} (return place) -> place", local);
let size = cast.size(&start_bx);
return LocalRef::Place(PlaceRef::alloca_size(&mut start_bx, size, layout));
Expand Down
145 changes: 144 additions & 1 deletion compiler/rustc_codegen_ssa/src/traits/abi.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,148 @@
use super::BackendTypes;
use rustc_middle::bug;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_target::abi::call::CastTarget;
use rustc_target::abi::Align;

use super::consts::ConstMethods;
use super::type_::BaseTypeMethods;
use super::{BackendTypes, BuilderMethods, LayoutTypeMethods};

pub trait AbiBuilderMethods<'tcx>: BackendTypes {
fn get_param(&mut self, index: usize) -> Self::Value;
}

/// The ABI mandates that the value is passed as a different struct representation.
pub trait CastTargetAbiExt<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
/// Spill and reload it from the stack to convert from the Rust representation to the ABI representation.
fn cast_rust_abi_to_other(&self, bx: &mut Bx, src: Bx::Value, align: Align) -> Bx::Value;
/// Spill and reload it from the stack to convert from the ABI representation to the Rust representation.
fn cast_other_abi_to_rust(
&self,
bx: &mut Bx,
src: Bx::Value,
dst: Bx::Value,
layout: TyAndLayout<'tcx>,
);
}

impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> CastTargetAbiExt<'a, 'tcx, Bx> for CastTarget {
fn cast_rust_abi_to_other(&self, bx: &mut Bx, src: Bx::Value, align: Align) -> Bx::Value {
let cast_ty = bx.cast_backend_type(self);
match bx.type_kind(cast_ty) {
crate::common::TypeKind::Struct | crate::common::TypeKind::Array => {
let mut index = 0;
let mut offset = 0;
let mut target = bx.const_poison(cast_ty);
for reg in self.prefix.iter().filter_map(|&x| x) {
let ptr = if offset == 0 {
src
} else {
bx.inbounds_ptradd(src, bx.const_usize(offset))
};
let load = bx.load(bx.reg_backend_type(&reg), ptr, align);
target = bx.insert_value(target, load, index);
index += 1;
offset += reg.size.bytes();
}
let (rest_count, rem_bytes) = if self.rest.unit.size.bytes() == 0 {
(0, 0)
} else {
(
self.rest.total.bytes() / self.rest.unit.size.bytes(),
self.rest.total.bytes() % self.rest.unit.size.bytes(),
)
};
for _ in 0..rest_count {
let ptr = if offset == 0 {
src
} else {
bx.inbounds_ptradd(src, bx.const_usize(offset))
};
let load = bx.load(bx.reg_backend_type(&self.rest.unit), ptr, align);
target = bx.insert_value(target, load, index);
index += 1;
offset += self.rest.unit.size.bytes();
}
if rem_bytes != 0 {
let ptr = bx.inbounds_ptradd(src, bx.const_usize(offset));
let load = bx.load(bx.reg_backend_type(&self.rest.unit), ptr, align);
target = bx.insert_value(target, load, index);
}
target
}
ty_kind if bx.type_kind(bx.reg_backend_type(&self.rest.unit)) == ty_kind => {
bx.load(cast_ty, src, align)
}
ty_kind => bug!("cannot cast {ty_kind:?} to the ABI representation in CastTarget"),
}
}

fn cast_other_abi_to_rust(
&self,
bx: &mut Bx,
src: Bx::Value,
dst: Bx::Value,
layout: TyAndLayout<'tcx>,
) {
let align = layout.align.abi;
match bx.type_kind(bx.val_ty(src)) {
crate::common::TypeKind::Struct | crate::common::TypeKind::Array => {
let mut index = 0;
let mut offset = 0;
for reg in self.prefix.iter().filter_map(|&x| x) {
let from = bx.extract_value(src, index);
let ptr = if index == 0 {
dst
} else {
bx.inbounds_ptradd(dst, bx.const_usize(offset))
};
bx.store(from, ptr, align);
index += 1;
offset += reg.size.bytes();
}
let (rest_count, rem_bytes) = if self.rest.unit.size.bytes() == 0 {
(0, 0)
} else {
(
self.rest.total.bytes() / self.rest.unit.size.bytes(),
self.rest.total.bytes() % self.rest.unit.size.bytes(),
)
};
for _ in 0..rest_count {
let from = bx.extract_value(src, index);
let ptr = if offset == 0 {
dst
} else {
bx.inbounds_ptradd(dst, bx.const_usize(offset))
};
bx.store(from, ptr, align);
index += 1;
offset += self.rest.unit.size.bytes();
}
if rem_bytes != 0 {
let from = bx.extract_value(src, index);
let ptr = bx.inbounds_ptradd(dst, bx.const_usize(offset));
bx.store(from, ptr, align);
}
}
ty_kind if bx.type_kind(bx.reg_backend_type(&self.rest.unit)) == ty_kind => {
let scratch_size = self.size(bx);
let src = if scratch_size > layout.size {
// When using just a single register, we directly use load or store instructions,
// so we must allocate sufficient space.
let scratch_align = self.align(bx);
let llscratch = bx.alloca(scratch_size, scratch_align);
bx.lifetime_start(llscratch, scratch_size);
bx.store(src, llscratch, scratch_align);
let tmp = bx.load(bx.backend_type(layout), llscratch, scratch_align);
bx.lifetime_end(llscratch, scratch_size);
tmp
} else {
src
};
bx.store(src, dst, align);
}
ty_kind => bug!("cannot cast {ty_kind:?} to the Rust representation in CastTarget"),
};
}
}
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use std::fmt;
use rustc_middle::ty::layout::{HasParamEnv, HasTyCtxt};
use rustc_target::spec::HasTargetSpec;

pub use self::abi::AbiBuilderMethods;
pub use self::abi::{AbiBuilderMethods, CastTargetAbiExt};
pub use self::asm::{AsmBuilderMethods, AsmMethods, GlobalAsmOperandRef, InlineAsmOperandRef};
pub use self::backend::{Backend, BackendTypes, CodegenBackend, ExtraBackendMethods};
pub use self::builder::{BuilderMethods, OverflowOp};
Expand Down
25 changes: 11 additions & 14 deletions tests/codegen/array-codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,23 @@
// CHECK-LABEL: @array_load
#[no_mangle]
pub fn array_load(a: &[u8; 4]) -> [u8; 4] {
// CHECK-NOT: alloca
// CHECK: %[[ALLOCA:.+]] = alloca [4 x i8], align 1
// CHECK-NOT: alloca
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align 1 %[[ALLOCA]], ptr align 1 %a, {{.+}} 4, i1 false)
// CHECK: %[[TEMP:.+]] = load i32, ptr %[[ALLOCA]], align 1
// CHECK: ret i32 %[[TEMP]]
// CHECK-NEXT: [[START:.*:]]
// CHECK-NEXT: %[[ALLOCA:.+]] = alloca [4 x i8], align 1
// CHECK-NEXT: call void @llvm.memcpy.{{.+}}(ptr align 1 %[[ALLOCA]], ptr align 1 %a, {{.+}} 4, i1 false)
// CHECK-NEXT: %[[TEMP:.+]] = load i32, ptr %[[ALLOCA]], align 1
// CHECK-NEXT: ret i32 %[[TEMP]]
*a
}

// CHECK-LABEL: @array_store
#[no_mangle]
pub fn array_store(a: [u8; 4], p: &mut [u8; 4]) {
// CHECK-NOT: alloca
// CHECK: %[[TEMP:.+]] = alloca [4 x i8], [[TEMPALIGN:align [0-9]+]]
// CHECK-NOT: alloca
// CHECK: %a = alloca [4 x i8]
// CHECK-NOT: alloca
// store i32 %0, ptr %[[TEMP]]
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align 1 %a, ptr [[TEMPALIGN]] %[[TEMP]], {{.+}} 4, i1 false)
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align 1 %p, ptr align 1 %a, {{.+}} 4, i1 false)
// CHECK-SAME: i32 [[TMP0:%.*]], ptr{{.*}} [[P:%.*]])
// CHECK-NEXT: [[START:.*:]]
// CHECK-NEXT: [[A:%.*]] = alloca [4 x i8], align 1
// CHECK-NEXT: store i32 [[TMP0]], ptr [[A]], align 1
// CHECK-NEXT: call void @llvm.memcpy.{{.+}}(ptr align 1 [[P]], ptr align 1 [[A]], {{.+}} 4, i1 false)
// CHECK-NEXT: ret void
*p = a;
}

Expand Down
Loading

0 comments on commit 448885c

Please sign in to comment.