From e59b47c41a0bf932bca3e4add7346dbd72d3cfc2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 3 Nov 2016 18:59:32 -0700 Subject: [PATCH] Return a Result from the TargetIsa::encode() method. When an instruction can't be encoded, provide a viable legalization action in the form of a Legalize enum. --- lib/cretonne/src/isa/enc_tables.rs | 21 +++++++----- lib/cretonne/src/isa/mod.rs | 15 ++++++++- lib/cretonne/src/isa/riscv/mod.rs | 17 +++++----- lib/cretonne/src/legalizer.rs | 51 ++++++++++++++++-------------- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 185e162f7f..d0a371dbc4 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -3,7 +3,7 @@ //! This module contains types and functions for working with the encoding tables generated by //! `lib/cretonne/meta/gen_encoding.py`. use ir::{Type, Opcode}; -use isa::Encoding; +use isa::{Encoding, Legalize}; use constant_hash::{Table, probe}; /// Level 1 hash table entry. @@ -83,16 +83,21 @@ pub fn lookup_enclist(ctrl_typevar: Type, opcode: Opcode, level1_table: &[Level1Entry], level2_table: &[Level2Entry]) - -> Option + -> Result where OffT1: Into + Copy, OffT2: Into + Copy { - probe(level1_table, ctrl_typevar, ctrl_typevar.index()).and_then(|l1idx| { - let l1ent = &level1_table[l1idx]; - let l2off = l1ent.offset.into() as usize; - let l2tab = &level2_table[l2off..l2off + (1 << l1ent.log2len)]; - probe(l2tab, opcode, opcode as usize).map(|l2idx| l2tab[l2idx].offset.into() as usize) - }) + // TODO: The choice of legalization actions here is naive. This needs to be configurable. + probe(level1_table, ctrl_typevar, ctrl_typevar.index()) + .ok_or(Legalize::Narrow) + .and_then(|l1idx| { + let l1ent = &level1_table[l1idx]; + let l2off = l1ent.offset.into() as usize; + let l2tab = &level2_table[l2off..l2off + (1 << l1ent.log2len)]; + probe(l2tab, opcode, opcode as usize) + .map(|l2idx| l2tab[l2idx].offset.into() as usize) + .ok_or(Legalize::Expand) + }) } /// Encoding list entry. diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 5705fe3a55..aa7b9a76d8 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -87,6 +87,19 @@ impl settings::Configurable for Builder { } } +/// After determining that an instruction doesn't have an encoding, how should we proceed to +/// legalize it? +/// +/// These actions correspond to the transformation groups defined in `meta/cretonne/legalize.py`. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Legalize { + /// Legalize in terms of narrower types. + Narrow, + + /// Expanding in terms of other instructions using the same types. + Expand, +} + /// Methods that are specialized to a target ISA. pub trait TargetIsa { /// Get the name of this ISA. @@ -101,7 +114,7 @@ pub trait TargetIsa { /// Otherwise, return `None`. /// /// This is also the main entry point for determining if an instruction is legal. - fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Option; + fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result; /// Get a static array of names associated with encoding recipes in this ISA. Encoding recipes /// are numbered starting from 0, corresponding to indexes into th name array. diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 06a9c890d0..63ab5ccf89 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -6,7 +6,7 @@ mod enc_tables; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, Encoding}; +use isa::{TargetIsa, Encoding, Legalize}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -48,7 +48,7 @@ impl TargetIsa for Isa { &self.shared_flags } - fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Option { + fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { lookup_enclist(inst.first_type(), inst.opcode(), self.cpumode, @@ -58,6 +58,7 @@ impl TargetIsa for Isa { &enc_tables::ENCLISTS[..], |instp| enc_tables::check_instp(inst, instp), |isap| self.isa_flags.numbered_predicate(isap as usize)) + .ok_or(Legalize::Expand) }) } @@ -109,7 +110,7 @@ mod tests { }; // Immediate is out of range for ADDI. - assert_eq!(isa.encode(&dfg, &inst64_large), None); + assert_eq!(isa.encode(&dfg, &inst64_large), Err(isa::Legalize::Expand)); // Create an iadd_imm.i32 which is encodable in RV64. let inst32 = InstructionData::BinaryImm { @@ -144,8 +145,8 @@ mod tests { imm: immediates::Imm64::new(-10), }; - // ADDI is I/0b00100 - assert_eq!(isa.encode(&dfg, &inst64), None); + // In 32-bit mode, an i64 bit add should be narrowed. + assert_eq!(isa.encode(&dfg, &inst64), Err(isa::Legalize::Narrow)); // Try to encode iadd_imm.i64 vx1, -10000. let inst64_large = InstructionData::BinaryImm { @@ -155,8 +156,8 @@ mod tests { imm: immediates::Imm64::new(-10000), }; - // Immediate is out of range for ADDI. - assert_eq!(isa.encode(&dfg, &inst64_large), None); + // In 32-bit mode, an i64 bit add should be narrowed. + assert_eq!(isa.encode(&dfg, &inst64_large), Err(isa::Legalize::Narrow)); // Create an iadd_imm.i32 which is encodable in RV32. let inst32 = InstructionData::BinaryImm { @@ -176,7 +177,7 @@ mod tests { args: [arg32, arg32], }; - assert_eq!(isa.encode(&dfg, &mul32), None); + assert_eq!(isa.encode(&dfg, &mul32), Err(isa::Legalize::Expand)); } #[test] diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 89ba8cd569..2a84ee520c 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -15,7 +15,7 @@ use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, Inst, InstBuilder}; use ir::condcodes::IntCC; -use isa::TargetIsa; +use isa::{TargetIsa, Legalize}; /// Legalize `func` for `isa`. /// @@ -25,30 +25,35 @@ use isa::TargetIsa; pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { // TODO: This is very simplified and incomplete. func.encodings.resize(func.dfg.num_insts()); - for ebb in func.layout.ebbs() { - for inst in func.layout.ebb_insts(ebb) { + let mut pos = Cursor::new(&mut func.layout); + while let Some(_ebb) = pos.next_ebb() { + while let Some(inst) = pos.next_inst() { match isa.encode(&func.dfg, &func.dfg[inst]) { - Some(encoding) => func.encodings[inst] = encoding, - None => { - // TODO: We should transform the instruction into legal equivalents. - // Possible strategies are: - // 1. Expand instruction into sequence of legal instructions. Possibly - // iteratively. - // 2. Split the controlling type variable into high and low parts. This applies - // both to SIMD vector types which can be halved and to integer types such - // as `i64` used on a 32-bit ISA. - // 3. Promote the controlling type variable to a larger type. This typically - // means expressing `i8` and `i16` arithmetic in terms if `i32` operations - // on RISC targets. (It may or may not be beneficial to promote small vector - // types versus splitting them.) - // 4. Convert to library calls. For example, floating point operations on an - // ISA with no IEEE 754 support. - // - // The iteration scheme used here is not going to cut it. Transforming - // instructions involves changing `function.layout` which is impossiblr while - // it is referenced by the two iterators. We need a layout cursor that can - // maintain a position *and* permit inserting and replacing instructions. + Ok(encoding) => func.encodings[inst] = encoding, + Err(Legalize::Expand) => { + expand(&mut pos, &mut func.dfg); } + Err(Legalize::Narrow) => { + narrow(&mut pos, &mut func.dfg); + } + // TODO: We should transform the instruction into legal equivalents. + // Possible strategies are: + // 1. Expand instruction into sequence of legal instructions. Possibly + // iteratively. + // 2. Split the controlling type variable into high and low parts. This applies + // both to SIMD vector types which can be halved and to integer types such + // as `i64` used on a 32-bit ISA. + // 3. Promote the controlling type variable to a larger type. This typically + // means expressing `i8` and `i16` arithmetic in terms if `i32` operations + // on RISC targets. (It may or may not be beneficial to promote small vector + // types versus splitting them.) + // 4. Convert to library calls. For example, floating point operations on an + // ISA with no IEEE 754 support. + // + // The iteration scheme used here is not going to cut it. Transforming + // instructions involves changing `function.layout` which is impossiblr while + // it is referenced by the two iterators. We need a layout cursor that can + // maintain a position *and* permit inserting and replacing instructions. } } }