Implement passing arguments by ref for win64 ABI
This commit is contained in:
@@ -54,6 +54,9 @@ pub enum ValueConversion {
|
|||||||
|
|
||||||
/// Unsigned zero-extend value to the required type.
|
/// Unsigned zero-extend value to the required type.
|
||||||
Uext(Type),
|
Uext(Type),
|
||||||
|
|
||||||
|
/// Pass value by pointer of given integer type.
|
||||||
|
Pointer(Type),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValueConversion {
|
impl ValueConversion {
|
||||||
@@ -63,7 +66,7 @@ impl ValueConversion {
|
|||||||
Self::IntSplit => ty.half_width().expect("Integer type too small to split"),
|
Self::IntSplit => ty.half_width().expect("Integer type too small to split"),
|
||||||
Self::VectorSplit => ty.half_vector().expect("Not a vector"),
|
Self::VectorSplit => ty.half_vector().expect("Not a vector"),
|
||||||
Self::IntBits => Type::int(ty.bits()).expect("Bad integer size"),
|
Self::IntBits => Type::int(ty.bits()).expect("Bad integer size"),
|
||||||
Self::Sext(nty) | Self::Uext(nty) => nty,
|
Self::Sext(nty) | Self::Uext(nty) | Self::Pointer(nty) => nty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +77,11 @@ impl ValueConversion {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this a conversion to pointer?
|
||||||
|
pub fn is_pointer(self) -> bool {
|
||||||
|
matches!(self, Self::Pointer(_))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Common trait for assigning arguments to registers or stack locations.
|
/// Common trait for assigning arguments to registers or stack locations.
|
||||||
@@ -110,10 +118,16 @@ pub fn legalize_args<AA: ArgAssigner>(args: &[AbiParam], aa: &mut AA) -> Option<
|
|||||||
}
|
}
|
||||||
// Split this argument into two smaller ones. Then revisit both.
|
// Split this argument into two smaller ones. Then revisit both.
|
||||||
ArgAction::Convert(conv) => {
|
ArgAction::Convert(conv) => {
|
||||||
|
debug_assert!(
|
||||||
|
!arg.legalized_to_pointer,
|
||||||
|
"No more conversions allowed after conversion to pointer"
|
||||||
|
);
|
||||||
let value_type = conv.apply(arg.value_type);
|
let value_type = conv.apply(arg.value_type);
|
||||||
let new_arg = AbiParam { value_type, ..arg };
|
|
||||||
args.to_mut()[argno].value_type = value_type;
|
args.to_mut()[argno].value_type = value_type;
|
||||||
if conv.is_split() {
|
if conv.is_pointer() {
|
||||||
|
args.to_mut()[argno].legalized_to_pointer = true;
|
||||||
|
} else if conv.is_split() {
|
||||||
|
let new_arg = AbiParam { value_type, ..arg };
|
||||||
args.to_mut().insert(argno + 1, new_arg);
|
args.to_mut().insert(argno + 1, new_arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,6 +166,10 @@ pub fn legalize_abi_value(have: Type, arg: &AbiParam) -> ValueConversion {
|
|||||||
let have_bits = have.bits();
|
let have_bits = have.bits();
|
||||||
let arg_bits = arg.value_type.bits();
|
let arg_bits = arg.value_type.bits();
|
||||||
|
|
||||||
|
if arg.legalized_to_pointer {
|
||||||
|
return ValueConversion::Pointer(arg.value_type);
|
||||||
|
}
|
||||||
|
|
||||||
match have_bits.cmp(&arg_bits) {
|
match have_bits.cmp(&arg_bits) {
|
||||||
// We have fewer bits than the ABI argument.
|
// We have fewer bits than the ABI argument.
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
@@ -226,5 +244,12 @@ mod tests {
|
|||||||
legalize_abi_value(types::F64, &arg),
|
legalize_abi_value(types::F64, &arg),
|
||||||
ValueConversion::IntBits
|
ValueConversion::IntBits
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Value is passed by reference
|
||||||
|
arg.legalized_to_pointer = true;
|
||||||
|
assert_eq!(
|
||||||
|
legalize_abi_value(types::F64, &arg),
|
||||||
|
ValueConversion::Pointer(types::I32)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,8 @@ pub struct AbiParam {
|
|||||||
/// ABI-specific location of this argument, or `Unassigned` for arguments that have not yet
|
/// ABI-specific location of this argument, or `Unassigned` for arguments that have not yet
|
||||||
/// been legalized.
|
/// been legalized.
|
||||||
pub location: ArgumentLoc,
|
pub location: ArgumentLoc,
|
||||||
|
/// Was the argument converted to pointer during legalization?
|
||||||
|
pub legalized_to_pointer: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AbiParam {
|
impl AbiParam {
|
||||||
@@ -166,6 +168,7 @@ impl AbiParam {
|
|||||||
extension: ArgumentExtension::None,
|
extension: ArgumentExtension::None,
|
||||||
purpose: ArgumentPurpose::Normal,
|
purpose: ArgumentPurpose::Normal,
|
||||||
location: Default::default(),
|
location: Default::default(),
|
||||||
|
legalized_to_pointer: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +179,7 @@ impl AbiParam {
|
|||||||
extension: ArgumentExtension::None,
|
extension: ArgumentExtension::None,
|
||||||
purpose,
|
purpose,
|
||||||
location: Default::default(),
|
location: Default::default(),
|
||||||
|
legalized_to_pointer: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +190,7 @@ impl AbiParam {
|
|||||||
extension: ArgumentExtension::None,
|
extension: ArgumentExtension::None,
|
||||||
purpose,
|
purpose,
|
||||||
location: ArgumentLoc::Reg(regunit),
|
location: ArgumentLoc::Reg(regunit),
|
||||||
|
legalized_to_pointer: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +224,9 @@ pub struct DisplayAbiParam<'a>(&'a AbiParam, Option<&'a RegInfo>);
|
|||||||
impl<'a> fmt::Display for DisplayAbiParam<'a> {
|
impl<'a> fmt::Display for DisplayAbiParam<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", self.0.value_type)?;
|
write!(f, "{}", self.0.value_type)?;
|
||||||
|
if self.0.legalized_to_pointer {
|
||||||
|
write!(f, " ptr")?;
|
||||||
|
}
|
||||||
match self.0.extension {
|
match self.0.extension {
|
||||||
ArgumentExtension::None => {}
|
ArgumentExtension::None => {}
|
||||||
ArgumentExtension::Uext => write!(f, " uext")?,
|
ArgumentExtension::Uext => write!(f, " uext")?,
|
||||||
@@ -415,6 +423,8 @@ mod tests {
|
|||||||
assert_eq!(t.sext().to_string(), "i32 sext");
|
assert_eq!(t.sext().to_string(), "i32 sext");
|
||||||
t.purpose = ArgumentPurpose::StructReturn;
|
t.purpose = ArgumentPurpose::StructReturn;
|
||||||
assert_eq!(t.to_string(), "i32 uext sret");
|
assert_eq!(t.to_string(), "i32 uext sret");
|
||||||
|
t.legalized_to_pointer = true;
|
||||||
|
assert_eq!(t.to_string(), "i32 ptr uext sret");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ struct Args {
|
|||||||
shared_flags: shared_settings::Flags,
|
shared_flags: shared_settings::Flags,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
isa_flags: isa_settings::Flags,
|
isa_flags: isa_settings::Flags,
|
||||||
|
assigning_returns: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
@@ -80,6 +81,7 @@ impl Args {
|
|||||||
call_conv: CallConv,
|
call_conv: CallConv,
|
||||||
shared_flags: &shared_settings::Flags,
|
shared_flags: &shared_settings::Flags,
|
||||||
isa_flags: &isa_settings::Flags,
|
isa_flags: &isa_settings::Flags,
|
||||||
|
assigning_returns: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let offset = if call_conv.extends_windows_fastcall() {
|
let offset = if call_conv.extends_windows_fastcall() {
|
||||||
WIN_SHADOW_STACK_SPACE
|
WIN_SHADOW_STACK_SPACE
|
||||||
@@ -99,6 +101,7 @@ impl Args {
|
|||||||
call_conv,
|
call_conv,
|
||||||
shared_flags: shared_flags.clone(),
|
shared_flags: shared_flags.clone(),
|
||||||
isa_flags: isa_flags.clone(),
|
isa_flags: isa_flags.clone(),
|
||||||
|
assigning_returns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,6 +110,17 @@ impl ArgAssigner for Args {
|
|||||||
fn assign(&mut self, arg: &AbiParam) -> ArgAction {
|
fn assign(&mut self, arg: &AbiParam) -> ArgAction {
|
||||||
let ty = arg.value_type;
|
let ty = arg.value_type;
|
||||||
|
|
||||||
|
if ty.bits() > u16::from(self.pointer_bits) {
|
||||||
|
if !self.assigning_returns && self.call_conv.extends_windows_fastcall() {
|
||||||
|
// "Any argument that doesn't fit in 8 bytes, or isn't
|
||||||
|
// 1, 2, 4, or 8 bytes, must be passed by reference"
|
||||||
|
return ValueConversion::Pointer(self.pointer_type).into();
|
||||||
|
} else if !ty.is_vector() && !ty.is_float() {
|
||||||
|
// On SystemV large integers and booleans are broken down to fit in a register.
|
||||||
|
return ValueConversion::IntSplit.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Vectors should stay in vector registers unless SIMD is not enabled--then they are split
|
// Vectors should stay in vector registers unless SIMD is not enabled--then they are split
|
||||||
if ty.is_vector() {
|
if ty.is_vector() {
|
||||||
if self.shared_flags.enable_simd() {
|
if self.shared_flags.enable_simd() {
|
||||||
@@ -117,11 +131,6 @@ impl ArgAssigner for Args {
|
|||||||
return ValueConversion::VectorSplit.into();
|
return ValueConversion::VectorSplit.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Large integers and booleans are broken down to fit in a register.
|
|
||||||
if !ty.is_float() && ty.bits() > u16::from(self.pointer_bits) {
|
|
||||||
return ValueConversion::IntSplit.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Small integers are extended to the size of a pointer register.
|
// Small integers are extended to the size of a pointer register.
|
||||||
if ty.is_int() && ty.bits() < u16::from(self.pointer_bits) {
|
if ty.is_int() && ty.bits() < u16::from(self.pointer_bits) {
|
||||||
match arg.extension {
|
match arg.extension {
|
||||||
@@ -203,7 +212,7 @@ pub fn legalize_signature(
|
|||||||
PointerWidth::U16 => panic!(),
|
PointerWidth::U16 => panic!(),
|
||||||
PointerWidth::U32 => {
|
PointerWidth::U32 => {
|
||||||
bits = 32;
|
bits = 32;
|
||||||
args = Args::new(bits, &[], 0, sig.call_conv, shared_flags, isa_flags);
|
args = Args::new(bits, &[], 0, sig.call_conv, shared_flags, isa_flags, false);
|
||||||
}
|
}
|
||||||
PointerWidth::U64 => {
|
PointerWidth::U64 => {
|
||||||
bits = 64;
|
bits = 64;
|
||||||
@@ -215,6 +224,7 @@ pub fn legalize_signature(
|
|||||||
sig.call_conv,
|
sig.call_conv,
|
||||||
shared_flags,
|
shared_flags,
|
||||||
isa_flags,
|
isa_flags,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Args::new(
|
Args::new(
|
||||||
@@ -224,6 +234,7 @@ pub fn legalize_signature(
|
|||||||
sig.call_conv,
|
sig.call_conv,
|
||||||
shared_flags,
|
shared_flags,
|
||||||
isa_flags,
|
isa_flags,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -243,6 +254,7 @@ pub fn legalize_signature(
|
|||||||
sig.call_conv,
|
sig.call_conv,
|
||||||
shared_flags,
|
shared_flags,
|
||||||
isa_flags,
|
isa_flags,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we don't have enough available return registers
|
// If we don't have enough available return registers
|
||||||
@@ -267,6 +279,7 @@ pub fn legalize_signature(
|
|||||||
purpose: ArgumentPurpose::StructReturn,
|
purpose: ArgumentPurpose::StructReturn,
|
||||||
extension: ArgumentExtension::None,
|
extension: ArgumentExtension::None,
|
||||||
location: ArgumentLoc::Unassigned,
|
location: ArgumentLoc::Unassigned,
|
||||||
|
legalized_to_pointer: false,
|
||||||
};
|
};
|
||||||
match args.assign(&ret_ptr_param) {
|
match args.assign(&ret_ptr_param) {
|
||||||
ArgAction::Assign(ArgumentLoc::Reg(reg)) => {
|
ArgAction::Assign(ArgumentLoc::Reg(reg)) => {
|
||||||
@@ -283,6 +296,7 @@ pub fn legalize_signature(
|
|||||||
purpose: ArgumentPurpose::StructReturn,
|
purpose: ArgumentPurpose::StructReturn,
|
||||||
extension: ArgumentExtension::None,
|
extension: ArgumentExtension::None,
|
||||||
location: ArgumentLoc::Unassigned,
|
location: ArgumentLoc::Unassigned,
|
||||||
|
legalized_to_pointer: false,
|
||||||
};
|
};
|
||||||
match backup_rets.assign(&ret_ptr_return) {
|
match backup_rets.assign(&ret_ptr_return) {
|
||||||
ArgAction::Assign(ArgumentLoc::Reg(reg)) => {
|
ArgAction::Assign(ArgumentLoc::Reg(reg)) => {
|
||||||
|
|||||||
@@ -504,6 +504,13 @@ where
|
|||||||
// this value.
|
// this value.
|
||||||
pos.ins().with_results([into_result]).ireduce(ty, arg)
|
pos.ins().with_results([into_result]).ireduce(ty, arg)
|
||||||
}
|
}
|
||||||
|
// ABI argument is a pointer to the value we want.
|
||||||
|
ValueConversion::Pointer(abi_ty) => {
|
||||||
|
let arg = convert_from_abi(pos, abi_ty, None, get_arg);
|
||||||
|
pos.ins()
|
||||||
|
.with_results([into_result])
|
||||||
|
.load(ty, MemFlags::new(), arg, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,6 +570,18 @@ fn convert_to_abi<PutArg>(
|
|||||||
let arg = pos.ins().uextend(abi_ty, value);
|
let arg = pos.ins().uextend(abi_ty, value);
|
||||||
convert_to_abi(pos, cfg, arg, put_arg);
|
convert_to_abi(pos, cfg, arg, put_arg);
|
||||||
}
|
}
|
||||||
|
ValueConversion::Pointer(abi_ty) => {
|
||||||
|
// Note: This conversion can only happen for call arguments,
|
||||||
|
// so we can allocate the value on stack safely.
|
||||||
|
let stack_slot = pos.func.create_stack_slot(StackSlotData {
|
||||||
|
kind: StackSlotKind::ExplicitSlot,
|
||||||
|
size: ty.bytes(),
|
||||||
|
offset: None,
|
||||||
|
});
|
||||||
|
let arg = pos.ins().stack_addr(abi_ty, stack_slot, 0);
|
||||||
|
pos.ins().store(MemFlags::new(), value, arg, 0);
|
||||||
|
convert_to_abi(pos, cfg, arg, put_arg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -815,7 +834,12 @@ pub fn handle_return_abi(inst: Inst, func: &mut Function, cfg: &ControlFlowGraph
|
|||||||
pos.use_srcloc(inst);
|
pos.use_srcloc(inst);
|
||||||
|
|
||||||
legalize_inst_arguments(pos, cfg, abi_args, |func, abi_arg| {
|
legalize_inst_arguments(pos, cfg, abi_args, |func, abi_arg| {
|
||||||
func.signature.returns[abi_arg]
|
let arg = func.signature.returns[abi_arg];
|
||||||
|
debug_assert!(
|
||||||
|
!arg.legalized_to_pointer,
|
||||||
|
"Return value cannot be legalized to pointer"
|
||||||
|
);
|
||||||
|
arg
|
||||||
});
|
});
|
||||||
// Append special return arguments for any `sret`, `link`, and `vmctx` return values added to
|
// Append special return arguments for any `sret`, `link`, and `vmctx` return values added to
|
||||||
// the legalized signature. These values should simply be propagated from the entry block
|
// the legalized signature. These values should simply be propagated from the entry block
|
||||||
|
|||||||
@@ -117,6 +117,20 @@ block0(v0: i64, v1: i64):
|
|||||||
}
|
}
|
||||||
; check: function %ret_val_i128(i64 [%rdx], i64 [%r8], i64 sret [%rcx], i64 fp [%rbp]) -> i64 sret [%rax], i64 fp [%rbp] windows_fastcall {
|
; check: function %ret_val_i128(i64 [%rdx], i64 [%r8], i64 sret [%rcx], i64 fp [%rbp]) -> i64 sret [%rax], i64 fp [%rbp] windows_fastcall {
|
||||||
|
|
||||||
|
; check if i128 is passed by reference
|
||||||
|
function %i128_arg(i128) windows_fastcall {
|
||||||
|
block0(v0: i128):
|
||||||
|
return
|
||||||
|
}
|
||||||
|
; check: function %i128_arg(i64 ptr [%rcx], i64 fp [%rbp]) -> i64 fp [%rbp] windows_fastcall {
|
||||||
|
|
||||||
|
; check if vector types are passed by reference
|
||||||
|
function %i32x4_arg(i32x4) windows_fastcall {
|
||||||
|
block0(v0: i32x4):
|
||||||
|
return
|
||||||
|
}
|
||||||
|
; check: function %i32x4_arg(i64 ptr [%rcx], i64 fp [%rbp]) -> i64 fp [%rbp] windows_fastcall {
|
||||||
|
|
||||||
function %internal_stack_arg_function_call(i64) -> i64 windows_fastcall {
|
function %internal_stack_arg_function_call(i64) -> i64 windows_fastcall {
|
||||||
fn0 = %foo(i64, i64, i64, i64) -> i64 windows_fastcall
|
fn0 = %foo(i64, i64, i64, i64) -> i64 windows_fastcall
|
||||||
fn1 = %foo2(i64, i64, i64, i64) -> i64 windows_fastcall
|
fn1 = %foo2(i64, i64, i64, i64) -> i64 windows_fastcall
|
||||||
|
|||||||
31
cranelift/filetests/filetests/legalizer/pass_by_ref.clif
Normal file
31
cranelift/filetests/filetests/legalizer/pass_by_ref.clif
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
test legalizer
|
||||||
|
target x86_64
|
||||||
|
|
||||||
|
function %legalize_entry(i128) -> i64 windows_fastcall {
|
||||||
|
block0(v0: i128):
|
||||||
|
v1, v2 = isplit v0
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
; check: function %legalize_entry(i64 ptr [%rcx]) -> i64 [%rax] windows_fastcall {
|
||||||
|
; nextln: block0(v3: i64):
|
||||||
|
; nextln: v4 = load.i64 v3
|
||||||
|
; nextln: v1 -> v4
|
||||||
|
; nextln: v5 = load.i64 v3+8
|
||||||
|
; nextln: v2 -> v5
|
||||||
|
; nextln: v0 = iconcat v4, v5
|
||||||
|
; nextln: return v2
|
||||||
|
|
||||||
|
function %legalize_call() {
|
||||||
|
fn0 = %foo(i32x4) windows_fastcall
|
||||||
|
block0:
|
||||||
|
v0 = vconst.i32x4 [1 2 3 4]
|
||||||
|
call fn0(v0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
; check: ss0 = explicit_slot 16
|
||||||
|
; check: sig0 = (i64 ptr [%rcx]) windows_fastcall
|
||||||
|
; check: v0 = vconst.i32x4 const0
|
||||||
|
; nextln: v1 = stack_addr.i64 ss0
|
||||||
|
; nextln: store v0, v1
|
||||||
|
; nextln: v2 = func_addr.i64 fn0
|
||||||
|
; nextln: call_indirect sig0, v2(v1)
|
||||||
Reference in New Issue
Block a user