diff --git a/cranelift/filetests/isa/riscv/abi.cton b/cranelift/filetests/isa/riscv/abi.cton index adb2dfed07..e3bafb83e1 100644 --- a/cranelift/filetests/isa/riscv/abi.cton +++ b/cranelift/filetests/isa/riscv/abi.cton @@ -2,6 +2,9 @@ test legalizer isa riscv +; regex: V=v\d+ +; regex: VX=vx\d+ + function f(i32) { sig0 = signature(i32) -> i32 @@ -21,3 +24,11 @@ function f(i32) { ebb0(v0: i32): return_reg v0 } + +function int_split_args(i64) -> i64 { +ebb0(v0: i64): +; check: $ebb0($(v0l=$VX): i32, $(v0h=$VX): i32): +; check: iconcat_lohi $v0l, $v0h + v1 = iadd_imm v0, 1 + return v0 +} diff --git a/cranelift/filetests/isa/riscv/legalize-i64.cton b/cranelift/filetests/isa/riscv/legalize-i64.cton index 41bc2e2947..fb8d412d8c 100644 --- a/cranelift/filetests/isa/riscv/legalize-i64.cton +++ b/cranelift/filetests/isa/riscv/legalize-i64.cton @@ -10,8 +10,8 @@ ebb0(v1: i64, v2: i64): v3 = band v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi ; check: [R#ec ; sameln: $(v3l=$V) = band $v1l, $v2l ; check: [R#ec @@ -23,8 +23,8 @@ ebb0(v1: i64, v2: i64): v3 = bor v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi ; check: [R#cc ; sameln: $(v3l=$V) = bor $v1l, $v2l ; check: [R#cc @@ -36,8 +36,8 @@ ebb0(v1: i64, v2: i64): v3 = bxor v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi ; check: [R#8c ; sameln: $(v3l=$V) = bxor $v1l, $v2l ; check: [R#8c @@ -52,8 +52,8 @@ ebb0(v1: i64, v2: i64): v3 = iadd v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi ; check: [R#0c ; sameln: $(v3l=$V) = iadd $v1l, $v2l ; check: $(c=$V) = icmp ult, $v3l, $v1l diff --git a/lib/cretonne/src/abi.rs b/lib/cretonne/src/abi.rs index 897133bc76..8d02de4aca 100644 --- a/lib/cretonne/src/abi.rs +++ b/lib/cretonne/src/abi.rs @@ -3,23 +3,65 @@ //! This module provides functions and data structures that are useful for implementing the //! `TargetIsa::legalize_signature()` method. -use ir::{ArgumentLoc, ArgumentType, Type}; +use ir::{ArgumentLoc, ArgumentType, ArgumentExtension, Type}; +use std::cmp::Ordering; -/// Legalization action to perform on a single argument or return value. +/// Legalization action to perform on a single argument or return value when converting a +/// signature. /// /// An argument may go through a sequence of legalization steps before it reaches the final /// `Assign` action. +#[derive(Clone, Copy)] pub enum ArgAction { /// Assign the argument to the given location. Assign(ArgumentLoc), - /// Split the argument into smaller parts, then call again. + /// Convert the argument, then call again. /// /// This action can split an integer type into two smaller integer arguments, or it can split a /// SIMD vector into halves. - /// - /// Floating point scalar types can't be split. - Split, + Convert(ValueConversion), +} + +/// Legalization action to be applied to a value that is being passed to or from a legalized ABI. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ValueConversion { + /// Split an integer types into low and high parts, using `isplit_lohi`. + IntSplit, + + /// Split a vector type into halves with identical lane types. + VectorSplit, + + /// Bit-cast to an integer type of the same size. + IntBits, + + /// Sign-extend integer value to the required type. + Sext(Type), + + /// Unsigned zero-extend value to the required type. + Uext(Type), +} + +impl ValueConversion { + /// Apply this conversion to a type, return the converted type. + pub fn apply(self, ty: Type) -> Type { + match self { + ValueConversion::IntSplit => ty.half_width().expect("Integer type too small to split"), + ValueConversion::VectorSplit => ty.half_vector().expect("Not a vector"), + ValueConversion::IntBits => Type::int(ty.bits()).expect("Bad integer size"), + ValueConversion::Sext(nty) => nty, + ValueConversion::Uext(nty) => nty, + } + } + + /// Is this a split conversion that results in two arguments? + pub fn is_split(self) -> bool { + match self { + ValueConversion::IntSplit => true, + ValueConversion::VectorSplit => true, + _ => false, + } + } } /// Common trait for assigning arguments to registers or stack locations. @@ -53,20 +95,104 @@ pub fn legalize_args(args: &mut Vec, aa: &mut AA) argno += 1; } // Split this argument into two smaller ones. Then revisit both. - ArgAction::Split => { - let new_arg = ArgumentType { value_type: split_type(arg.value_type), ..arg }; + ArgAction::Convert(conv) => { + let new_arg = ArgumentType { value_type: conv.apply(arg.value_type), ..arg }; args[argno].value_type = new_arg.value_type; - args.insert(argno + 1, new_arg); + if conv.is_split() { + args.insert(argno + 1, new_arg); + } } } } } -/// Given a value type that isn't legal, compute a replacement type half the size. -fn split_type(ty: Type) -> Type { - if ty.is_int() { - ty.half_width().expect("Integer type too small to split") - } else { - ty.half_vector().expect("Can only split integers and vectors") +/// Determine the right action to take when passing a `have` value type to a call signature where +/// the next argument is `arg` which has a different value type. +/// +/// The signature legalization process in `legalize_args` above can replace a single argument value +/// with multiple arguments of smaller types. It can also change the type of an integer argument to +/// a larger integer type, requiring the smaller value to be sign- or zero-extended. +/// +/// The legalizer needs to repair the values at all ABI boundaries: +/// +/// - Incoming function arguments to the entry EBB. +/// - Function arguments passed to a call. +/// - Return values from a call. +/// - Return values passed to a return instruction. +/// +/// The `legalize_abi_value` function helps the legalizer with the process. When the legalizer +/// needs to pass a pre-legalized `have` argument, but the ABI argument `arg` has a different value +/// type, `legalize_abi_value(have, arg)` tells the legalizer how to create the needed value type +/// for the argument. +/// +/// It may be necessary to call `legalize_abi_value` more than once for a given argument before the +/// desired argument type appears. This will happen when a vector or integer type needs to be split +/// more than once, for example. +pub fn legalize_abi_value(have: Type, arg: &ArgumentType) -> ValueConversion { + let have_bits = have.bits(); + let arg_bits = arg.value_type.bits(); + + match have_bits.cmp(&arg_bits) { + // We have fewer bits than the ABI argument. + Ordering::Less => { + assert!(have.is_int() && arg.value_type.is_int(), + "Can only extend integer values"); + match arg.extension { + ArgumentExtension::Uext => ValueConversion::Uext(arg.value_type), + ArgumentExtension::Sext => ValueConversion::Sext(arg.value_type), + _ => panic!("No argument extension specified"), + } + } + // We have the same number of bits as the argument. + Ordering::Equal => { + // This must be an integer vector that is split and then extended. + assert!(arg.value_type.is_int()); + assert!(!have.is_scalar()); + ValueConversion::VectorSplit + } + // We have more bits than the argument. + Ordering::Greater => { + if have.is_scalar() { + if have.is_float() { + // Convert a float to int so it can be split the next time. + // ARM would do this to pass an `f64` in two registers. + ValueConversion::IntBits + } else { + ValueConversion::IntSplit + } + } else { + ValueConversion::VectorSplit + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ir::types; + use ir::ArgumentType; + + #[test] + fn legalize() { + let mut arg = ArgumentType::new(types::I32); + + assert_eq!(legalize_abi_value(types::I64X2, &arg), + ValueConversion::VectorSplit); + assert_eq!(legalize_abi_value(types::I64, &arg), + ValueConversion::IntSplit); + + // Vector of integers is broken down, then sign-extended. + arg.extension = ArgumentExtension::Sext; + assert_eq!(legalize_abi_value(types::I16X4, &arg), + ValueConversion::VectorSplit); + assert_eq!(legalize_abi_value(types::I16.by(2).unwrap(), &arg), + ValueConversion::VectorSplit); + assert_eq!(legalize_abi_value(types::I16, &arg), + ValueConversion::Sext(types::I32)); + + // 64-bit float is split as an integer. + assert_eq!(legalize_abi_value(types::F64, &arg), + ValueConversion::IntBits); } } diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index cdd2207aad..2391ca6072 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -67,6 +67,17 @@ impl Type { } } + /// Get an integer type with the requested number of bits. + pub fn int(bits: u16) -> Option { + match bits { + 8 => Some(I8), + 16 => Some(I16), + 32 => Some(I32), + 64 => Some(I64), + _ => None, + } + } + /// Get a type with the same number of lanes as this type, but with the lanes replaced by /// booleans of the same size. /// diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index 5c9630f8f4..7813a59cef 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -5,7 +5,7 @@ //! //! This doesn't support the soft-float ABI at the moment. -use abi::{ArgAction, ArgAssigner, legalize_args}; +use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; use ir::{Signature, ArgumentType, ArgumentLoc}; use isa::riscv::registers::{GPR, FPR}; use settings as shared_settings; @@ -39,7 +39,7 @@ impl ArgAssigner for Args { // Check for a legal type. // RISC-V doesn't have SIMD at all, so break all vectors down. if !ty.is_scalar() { - return ArgAction::Split; + return ArgAction::Convert(ValueConversion::VectorSplit); } // Large integers and booleans are broken down to fit in a register. @@ -47,7 +47,7 @@ impl ArgAssigner for Args { // Align registers and stack to a multiple of two pointers. self.regs = align(self.regs, 2); self.offset = align(self.offset, 2 * self.pointer_bytes); - return ArgAction::Split; + return ArgAction::Convert(ValueConversion::IntSplit); } if self.regs < 8 { diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 3d57c73b95..885a034084 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -13,7 +13,9 @@ //! The legalizer does not deal with register allocation constraints. These constraints are derived //! from the encoding recipes, and solved later by the register allocator. -use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder}; +use abi::{legalize_abi_value, ValueConversion}; +use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder, Ebb, Type, Value, + ArgumentType}; use ir::condcodes::IntCC; use isa::{TargetIsa, Legalize}; @@ -87,4 +89,115 @@ fn legalize_signatures(func: &mut Function, isa: &TargetIsa) { for sig in func.dfg.signatures.keys() { isa.legalize_signature(&mut func.dfg.signatures[sig]); } + + if let Some(entry) = func.layout.entry_block() { + legalize_entry_arguments(func, entry); + } +} + +/// Legalize the entry block arguments after `func`'s signature has been legalized. +/// +/// The legalized signature may contain more arguments than the original signature, and the +/// argument types have been changed. This function goes through the arguments to the entry EBB and +/// replaces them with arguments of the right type for the ABI. +/// +/// The original entry EBB arguments are computed from the new ABI arguments by code inserted at +/// the top of the entry block. +fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { + // Insert position for argument conversion code. + // We want to insert instructions before the first instruction in the entry block. + // If the entry block is empty, append instructions to it instead. + let mut pos = Cursor::new(&mut func.layout); + pos.goto_top(entry); + pos.next_inst(); + + // Keep track of the argument types in the ABI-legalized signature. + let abi_types = &func.signature.argument_types; + let mut abi_arg = 0; + + // Process the EBB arguments one at a time, possibly replacing one argument with multiple new + // ones. We do this by detaching the entry EBB arguments first. + let mut next_arg = func.dfg.take_ebb_args(entry); + while let Some(arg) = next_arg { + // Get the next argument before we mutate `arg`. + next_arg = func.dfg.next_ebb_arg(arg); + + let arg_type = func.dfg.value_type(arg); + if arg_type == abi_types[abi_arg].value_type { + // No value translation is necessary, this argument matches the ABI type. + // Just use the original EBB argument value. This is the most common case. + func.dfg.put_ebb_arg(entry, arg); + abi_arg += 1; + } else { + // Compute the value we want for `arg` from the legalized ABI arguments. + let converted = convert_from_abi(&mut func.dfg, + &mut pos, + entry, + &mut abi_arg, + abi_types, + arg_type); + // The old `arg` is no longer an attached EBB argument, but there are probably still + // uses of the value. Make it an alias to the converted value. + func.dfg.change_to_alias(arg, converted); + } + } +} + +/// Compute original value of type `ty` from the legalized ABI arguments beginning at `abi_arg`. +/// +/// Update `abi_arg` to reflect the ABI arguments consumed and return the computed value. +fn convert_from_abi(dfg: &mut DataFlowGraph, + pos: &mut Cursor, + entry: Ebb, + abi_arg: &mut usize, + abi_types: &[ArgumentType], + ty: Type) + -> Value { + // Terminate the recursion when we get the desired type. + if ty == abi_types[*abi_arg].value_type { + return dfg.append_ebb_arg(entry, ty); + } + + // Reconstruct how `ty` was legalized into the argument at `abi_arg`. + let conversion = legalize_abi_value(ty, &abi_types[*abi_arg]); + + // The conversion describes value to ABI argument. We implement the reverse conversion here. + match conversion { + // Construct a `ty` by concatenating two ABI integers. + ValueConversion::IntSplit => { + let abi_ty = ty.half_width().expect("Invalid type for conversion"); + let lo = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let hi = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + dfg.ins(pos).iconcat_lohi(lo, hi) + } + // Construct a `ty` by concatenating two halves of a vector. + ValueConversion::VectorSplit => { + let abi_ty = ty.half_vector().expect("Invalid type for conversion"); + let _lo = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let _hi = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + unimplemented!() + } + // Construct a `ty` by bit-casting from an integer type. + ValueConversion::IntBits => { + assert!(!ty.is_int()); + let abi_ty = Type::int(ty.bits()).expect("Invalid type for conversion"); + let arg = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + dfg.ins(pos).bitcast(ty, arg) + } + // ABI argument is a sign-extended version of the value we want. + ValueConversion::Sext(abi_ty) => { + let arg = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. + // We could insert an `assert_sreduce` which would fold with a following `sextend` of + // this value. + dfg.ins(pos).ireduce(ty, arg) + } + ValueConversion::Uext(abi_ty) => { + let arg = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. + // We could insert an `assert_ureduce` which would fold with a following `uextend` of + // this value. + dfg.ins(pos).ireduce(ty, arg) + } + } }