From 598c81c12ef00d0d52a943457e7668e048e31af3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Apr 2017 11:04:17 -0700 Subject: [PATCH] Add encoding size information to EncInfo. Two new pieces of information are available for all encoding recipes: - The size in bytes of an encoded instruction, and - The range of a branch encoded with the recipe, if any. In the meta language, EncRecipe takes two new constructor arguments. The size is required for all encodings and branch_range is required for all recipes used to encode branches. --- lib/cretonne/meta/cdsl/isa.py | 32 ++++++++++++- lib/cretonne/meta/gen_encoding.py | 23 +++++++++ lib/cretonne/meta/isa/riscv/recipes.py | 24 ++++++---- lib/cretonne/src/isa/arm32/enc_tables.rs | 1 + lib/cretonne/src/isa/arm64/enc_tables.rs | 1 + lib/cretonne/src/isa/constraints.rs | 60 +++++++++++++++++++++++- lib/cretonne/src/isa/encoding.rs | 38 ++++++++++++++- lib/cretonne/src/isa/intel/enc_tables.rs | 1 + lib/cretonne/src/isa/mod.rs | 2 +- lib/cretonne/src/isa/riscv/enc_tables.rs | 1 + 10 files changed, 169 insertions(+), 14 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 9db0faeb61..1ead583c5a 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -19,6 +19,7 @@ try: # Instruction specification for encodings. Allows for predicated # instructions. InstSpec = Union[MaybeBoundInst, Apply] + BranchRange = Sequence[int] except ImportError: pass @@ -164,17 +165,38 @@ class EncRecipe(object): - An integer indicating that this result is tied to a value operand, so they must use the same register. + The `branch_range` argument must be provided for recipes that can encode + branch instructions. It is an `(origin, bits)` tuple describing the exact + range that can be encoded in a branch instruction. + :param name: Short mnemonic name for this recipe. :param format: All encoded instructions must have this :py:class:`InstructionFormat`. + :param size: Number of bytes in the binary encoded instruction. :param: ins Tuple of register constraints for value operands. :param: outs Tuple of register constraints for results. + :param: branch_range `(origin, bits)` range for branches. + :param: instp Instruction predicate. + :param: isap ISA predicate. """ - def __init__(self, name, format, ins, outs, instp=None, isap=None): - # type: (str, InstructionFormat, ConstraintSeq, ConstraintSeq, PredNode, PredNode) -> None # noqa + def __init__( + self, + name, # type: str + format, # type: InstructionFormat + size, # type: int + ins, # type: ConstraintSeq + outs, # type: ConstraintSeq + branch_range=None, # type: BranchRange + instp=None, # type: PredNode + isap=None # type: PredNode + ): + # type: (...) -> None self.name = name self.format = format + assert size >= 0 + self.size = size + self.branch_range = branch_range self.instp = instp self.isap = isap if instp: @@ -249,6 +271,12 @@ class Encoding(object): assert self.inst.format == recipe.format, ( "Format {} must match recipe: {}".format( self.inst.format, recipe.format)) + + if self.inst.is_branch: + assert recipe.branch_range, ( + 'Recipe {} for {} must have a branch_range' + .format(recipe, self.inst.name)) + self.recipe = recipe self.encbits = encbits # Combine recipe predicates with the manually specified ones. diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 21b121487f..a68710f540 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -498,6 +498,27 @@ def emit_operand_constraints(seq, field, fmt): 'Unsupported constraint {}'.format(cons)) +def emit_recipe_sizing(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None + """ + Emit a table of encoding recipe code size information. + """ + with fmt.indented( + 'static RECIPE_SIZING: [RecipeSizing; {}] = [' + .format(len(isa.all_recipes)), '];'): + for r in isa.all_recipes: + fmt.comment(r.name) + with fmt.indented('RecipeSizing {', '},'): + fmt.format('bytes: {},', r.size) + if r.branch_range: + fmt.format( + 'branch_range: ' + 'Some(BranchRange {{ origin: {}, bits: {} }}),', + *r.branch_range) + else: + fmt.line('branch_range: None,') + + def gen_isa(isa, fmt): # type: (TargetISA, srcgen.Formatter) -> None # First assign numbers to relevant instruction predicates and generate the @@ -535,10 +556,12 @@ def gen_isa(isa, fmt): emit_recipe_names(isa, fmt) emit_recipe_constraints(isa, fmt) + emit_recipe_sizing(isa, fmt) # Finally, tie it all together in an `EncInfo`. with fmt.indented('pub static INFO: EncInfo = EncInfo {', '};'): fmt.line('constraints: &RECIPE_CONSTRAINTS,') + fmt.line('sizing: &RECIPE_SIZING,') fmt.line('names: &RECIPE_NAMES,') diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 2be2192002..63ca9b78ba 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -85,37 +85,43 @@ def LUI(): # R-type 32-bit instructions: These are mostly binary arithmetic instructions. # The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) -R = EncRecipe('R', Binary, ins=(GPR, GPR), outs=GPR) +R = EncRecipe('R', Binary, size=4, ins=(GPR, GPR), outs=GPR) # R-type with an immediate shift amount instead of rs2. -Rshamt = EncRecipe('Rshamt', BinaryImm, ins=GPR, outs=GPR) +Rshamt = EncRecipe('Rshamt', BinaryImm, size=4, ins=GPR, outs=GPR) # R-type encoding of an integer comparison. -Ricmp = EncRecipe('Ricmp', IntCompare, ins=(GPR, GPR), outs=GPR) +Ricmp = EncRecipe('Ricmp', IntCompare, size=4, ins=(GPR, GPR), outs=GPR) I = EncRecipe( - 'I', BinaryImm, ins=GPR, outs=GPR, + 'I', BinaryImm, size=4, ins=GPR, outs=GPR, instp=IsSignedInt(BinaryImm.imm, 12)) # I-type encoding of an integer comparison. Iicmp = EncRecipe( - 'Iicmp', IntCompareImm, ins=GPR, outs=GPR, + 'Iicmp', IntCompareImm, size=4, ins=GPR, outs=GPR, instp=IsSignedInt(IntCompareImm.imm, 12)) # I-type encoding for `jalr` as a return instruction. We won't use the # immediate offset. # The variable return values are not encoded. -Iret = EncRecipe('Iret', MultiAry, ins=GPR, outs=()) +Iret = EncRecipe('Iret', MultiAry, size=4, ins=GPR, outs=()) # U-type instructions have a 20-bit immediate that targets bits 12-31. U = EncRecipe( - 'U', UnaryImm, ins=(), outs=GPR, + 'U', UnaryImm, size=4, ins=(), outs=GPR, instp=IsSignedInt(UnaryImm.imm, 32, 12)) # SB-type branch instructions. # TODO: These instructions have a +/- 4 KB branch range. How to encode that # constraint? -SB = EncRecipe('SB', BranchIcmp, ins=(GPR, GPR), outs=()) +SB = EncRecipe( + 'SB', BranchIcmp, size=4, + ins=(GPR, GPR), outs=(), + branch_range=(0, 13)) # SB-type branch instruction with rs2 fixed to zero. -SBzero = EncRecipe('SBzero', Branch, ins=(GPR), outs=()) +SBzero = EncRecipe( + 'SBzero', Branch, size=4, + ins=(GPR), outs=(), + branch_range=(0, 13)) diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index 67cde87ed4..9fdfbcda95 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -5,5 +5,6 @@ use ir::types; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index d092c0eb8f..cdfc255748 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -5,5 +5,6 @@ use ir::types; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs")); diff --git a/lib/cretonne/src/isa/constraints.rs b/lib/cretonne/src/isa/constraints.rs index 2b8700ec4a..23cd923f93 100644 --- a/lib/cretonne/src/isa/constraints.rs +++ b/lib/cretonne/src/isa/constraints.rs @@ -7,6 +7,7 @@ //! It is the register allocator's job to make sure that the register constraints on value operands //! are satisfied. +use binemit::CodeOffset; use isa::{RegClass, RegUnit}; /// Register constraint for a single value operand or instruction result. @@ -48,7 +49,7 @@ pub enum ConstraintKind { Stack, } -/// Constraints for an encoding recipe. +/// Value operand constraints for an encoding recipe. #[derive(Clone)] pub struct RecipeConstraints { /// Constraints for the instruction's fixed value operands. @@ -66,3 +67,60 @@ pub struct RecipeConstraints { /// constraints must be derived from the calling convention ABI. pub outs: &'static [OperandConstraint], } + +/// Constraints on the range of a branch instruction. +/// +/// A branch instruction usually encodes its destination as a signed n-bit offset from an origin. +/// The origin depends on the ISA and the specific instruction: +/// +/// - RISC-V and ARM Aarch64 use the address of the branch instruction, `origin = 0`. +/// - Intel uses the address of the instruction following the branch, `origin = 2` for a 2-byte +/// branch instruction. +/// - ARM's A32 encoding uses the address of the branch instruction + 8 bytes, `origin = 8`. +#[derive(Clone, Copy)] +pub struct BranchRange { + /// Offset in bytes from the address of the branch instruction to the origin used for computing + /// the branch displacement. This is the destination of a branch that encodes a 0 displacement. + pub origin: u8, + + /// Number of bits in the signed byte displacement encoded in the instruction. This does not + /// account for branches that can only target aligned addresses. + pub bits: u8, +} + +impl BranchRange { + /// Determine if this branch range can represent the range from `branch` to `dest`, where + /// `branch` is the code offset of the branch instruction itself and `dest` is the code offset + /// of the destination EBB header. + /// + /// This method does not detect if the range is larger than 2 GB. + pub fn contains(self, branch: CodeOffset, dest: CodeOffset) -> bool { + let d = dest.wrapping_sub(branch + self.origin as CodeOffset) as i32; + let s = 32 - self.bits; + d == d << s >> s + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn branch_range() { + // ARM T1 branch. + let t1 = BranchRange { origin: 4, bits: 9 }; + assert!(t1.contains(0, 0)); + assert!(t1.contains(0, 2)); + assert!(t1.contains(2, 0)); + assert!(t1.contains(1000, 1000)); + + // Forward limit. + assert!(t1.contains(1000, 1258)); + assert!(!t1.contains(1000, 1260)); + + // Backward limit + assert!(t1.contains(1000, 748)); + assert!(!t1.contains(1000, 746)); + + } +} diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs index 88040925ad..c5151978aa 100644 --- a/lib/cretonne/src/isa/encoding.rs +++ b/lib/cretonne/src/isa/encoding.rs @@ -1,7 +1,8 @@ //! The `Encoding` struct. +use binemit::CodeOffset; +use isa::constraints::{RecipeConstraints, BranchRange}; use std::fmt; -use isa::constraints::RecipeConstraints; /// Bits needed to encode an instruction as binary machine code. /// @@ -78,12 +79,28 @@ impl fmt::Display for DisplayEncoding { } } +/// Code size information for an encoding recipe. +/// +/// All encoding recipes correspond to an exact instruction size. +pub struct RecipeSizing { + /// Size in bytes of instructions encoded with this recipe. + pub bytes: u8, + + /// Allowed branch range in this recipe, if any. + /// + /// All encoding recipes for branches have exact branch range information. + pub branch_range: Option, +} + /// Information about all the encodings in this ISA. #[derive(Clone)] pub struct EncInfo { /// Constraints on value operands per recipe. pub constraints: &'static [RecipeConstraints], + /// Code size information per recipe. + pub sizing: &'static [RecipeSizing], + /// Names of encoding recipes. pub names: &'static [&'static str], } @@ -101,4 +118,23 @@ impl EncInfo { recipe_names: self.names, } } + + /// Get the exact size in bytes of instructions encoded with `enc`. + /// + /// Returns 0 for illegal encodings. + pub fn bytes(&self, enc: Encoding) -> CodeOffset { + self.sizing + .get(enc.recipe()) + .map(|s| s.bytes as CodeOffset) + .unwrap_or(0) + } + + /// Get the branch range that is supported by `enc`, if any. + /// + /// This will never return `None` for a legal branch encoding. + pub fn branch_range(&self, enc: Encoding) -> Option { + self.sizing + .get(enc.recipe()) + .and_then(|s| s.branch_range) + } } diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 8abc8cd9d9..1ee1fbd4b3 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -5,5 +5,6 @@ use ir::types; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 4fc08906ae..469142ab56 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -40,7 +40,7 @@ //! The configured target ISA trait object is a `Box` which can be used for multiple //! concurrent function compilations. -pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind}; +pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind, BranchRange}; pub use isa::encoding::{Encoding, EncInfo}; pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index 87c62bd63c..8ced0c681f 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -6,6 +6,7 @@ use ir::{Opcode, InstructionData}; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::encoding::RecipeSizing; use predicates; use super::registers::*;