The legalizer often splits values into parts with the vsplit and isplit_lohi instructions. Avoid doing that for values that are already defined by the corresponding concatenation instructions. This reduces the number of instructions created during legalization, and it simplifies later optimizations. A number of dead concatenation instructions are left behind. They can be trivially cleaned up by a dead code elimination pass.
121 lines
4.4 KiB
Rust
121 lines
4.4 KiB
Rust
//! Value splitting.
|
|
//!
|
|
//! Some value types are too large to fit in registers, so they need to be split into smaller parts
|
|
//! that the ISA can operate on. There's two dimensions of splitting, represented by two
|
|
//! complementary instruction pairs:
|
|
//!
|
|
//! - `isplit` and `iconcat` for splitting integer types into smaller integers.
|
|
//! - `vsplit` and `vconcat` for splitting vector types into smaller vector types with the same
|
|
//! lane types.
|
|
//!
|
|
//! There is no floating point splitting. If an ISA doesn't support `f64` values, they probably
|
|
//! have to be bit-cast to `i64` and possibly split into two `i32` values that fit in registers.
|
|
//! This breakdown is handled by the ABI lowering.
|
|
//!
|
|
//! When legalizing a single instruction, it is wrapped in splits and concatenations:
|
|
//!
|
|
//!```cton
|
|
//! v1 = bxor.i64 v2, v3
|
|
//! ```
|
|
//!
|
|
//! becomes:
|
|
//!
|
|
//!```cton
|
|
//! v20, v21 = isplit v2
|
|
//! v30, v31 = isplit v3
|
|
//! v10 = bxor.i32 v20, v30
|
|
//! v11 = bxor.i32 v21, v31
|
|
//! v1 = iconcat v10, v11
|
|
//! ```
|
|
//!
|
|
//! This local expansion approach still leaves the original `i64` values in the code as operands on
|
|
//! the `split` and `concat` instructions. It also creates a lot of redundant code to clean up as
|
|
//! values are constantly split and concatenated.
|
|
//!
|
|
//! # Optimized splitting
|
|
//!
|
|
//! We can eliminate a lot of the splitting code quite easily. Whenever we need to split a value,
|
|
//! first check if the value is defined by the corresponding concatenation. If so, then just use
|
|
//! the two concatenation inputs directly:
|
|
//!
|
|
//! ```cton
|
|
//! v4 = iadd_imm.i64 v1, 1
|
|
//! ```
|
|
//!
|
|
//! becomes, using the expanded code from above:
|
|
//!
|
|
//! ```cton
|
|
//! v40, v5 = iadd_imm_cout.i32 v10, 1
|
|
//! v6 = bint.i32
|
|
//! v41 = iadd.i32 v11, v6
|
|
//! v4 = iconcat v40, v41
|
|
//! ```
|
|
//!
|
|
//! This means that the `iconcat` instructions defining `v1` and `v4` end up with no uses, so they
|
|
//! can be trivially deleted by a dead code elimination pass.
|
|
//!
|
|
//! # EBB arguments
|
|
//!
|
|
//! If all instructions that produce an `i64` value are legalized as above, we will eventually end
|
|
//! up with no `i64` values anywhere, except for EBB arguments. We can work around this by
|
|
//! iteratively splitting EBB arguments too. That should leave us with no illegal value types
|
|
//! anywhere.
|
|
//!
|
|
//! It is possible to have circular dependencies of EBB arguments that are never used by any real
|
|
//! instructions. These loops will remain in the program.
|
|
|
|
use ir::{DataFlowGraph, Cursor, Value, Opcode, ValueDef, InstructionData, InstBuilder};
|
|
|
|
/// Split `value` into two values using the `isplit` semantics. Do this by reusing existing values
|
|
/// if possible.
|
|
pub fn isplit(dfg: &mut DataFlowGraph, pos: &mut Cursor, value: Value) -> (Value, Value) {
|
|
split_value(dfg, pos, value, Opcode::Iconcat)
|
|
}
|
|
|
|
/// Split `value` into halves using the `vsplit` semantics. Do this by reusing existing values if
|
|
/// possible.
|
|
pub fn vsplit(dfg: &mut DataFlowGraph, pos: &mut Cursor, value: Value) -> (Value, Value) {
|
|
split_value(dfg, pos, value, Opcode::Vconcat)
|
|
}
|
|
|
|
/// Split a single value using the integer or vector semantics given by the `concat` opcode.
|
|
///
|
|
/// If the value is defined by a `concat` instruction, just reuse the operand values of that
|
|
/// instruction.
|
|
///
|
|
/// Return the two new values representing the parts of `value`.
|
|
fn split_value(dfg: &mut DataFlowGraph,
|
|
pos: &mut Cursor,
|
|
value: Value,
|
|
concat: Opcode)
|
|
-> (Value, Value) {
|
|
let value = dfg.resolve_copies(value);
|
|
let mut reuse = None;
|
|
|
|
match dfg.value_def(value) {
|
|
ValueDef::Res(inst, num) => {
|
|
// This is an instruction result. See if the value was created by a `concat`
|
|
// instruction.
|
|
if let InstructionData::Binary { opcode, args, .. } = dfg[inst] {
|
|
assert_eq!(num, 0);
|
|
if opcode == concat {
|
|
reuse = Some((args[0], args[1]));
|
|
}
|
|
}
|
|
}
|
|
ValueDef::Arg(_ebb, _num) => {}
|
|
}
|
|
|
|
// Did the code above succeed in finding values we can reuse?
|
|
if let Some(pair) = reuse {
|
|
pair
|
|
} else {
|
|
// No, we'll just have to insert the requested split instruction at `pos`.
|
|
match concat {
|
|
Opcode::Iconcat => dfg.ins(pos).isplit(value),
|
|
Opcode::Vconcat => dfg.ins(pos).vsplit(value),
|
|
_ => panic!("Unhandled concat opcode: {}", concat),
|
|
}
|
|
}
|
|
}
|