diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 6f20cf8d32..9c5c38e17a 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -262,7 +262,12 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): if len(inst.outs) > 0: sig = ', '.join([op.name for op in inst.outs]) + ' = ' + sig if len(inst.ins) > 0: - sig = sig + ' ' + ', '.join([op.name for op in inst.ins]) + sig = sig + ' ' + inst.ins[0].name + for op in inst.ins[1:]: + if op.typ.operand_kind().name == 'variable_args': + sig += '({}...)'.format(op.name) + else: + sig += ', ' + op.name return sig def add_directive_header(self, sig): diff --git a/docs/langref.rst b/docs/langref.rst index 1d987fb3c7..ca58a9b273 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -313,56 +313,10 @@ arguments, if it has any. Conditional branches only take the branch if their condition is satisfied, otherwise execution continues at the following instruction in the EBB. -.. inst:: br EBB(args...) - - Branch. - - Unconditionally branch to an extended basic block, passing the specified - EBB arguments. The number and types of arguments must match the destination - EBB. - - :arg EBB: Destination extended basic block. - :arg args...: Zero or more arguments passed to EBB. - :result: None. This is a terminator instruction. - -.. inst:: brz x, EBB(args...) - - Branch when zero. - - If ``x`` is a :type:`b1` value, take the branch when ``x`` is false. If - ``x`` is an integer value, take the branch when ``x = 0``. - - :arg Testable x: Value to test. - :arg EBB: Destination extended basic block. - :arg args...: Arguments passed to EBB. - :result: None. - -.. inst:: brnz x, EBB(args...) - - Branch when non-zero. - - If ``x`` is a :type:`b1` value, take the branch when ``x`` is true. If - ``x`` is an integer value, take the branch when ``x != 0``. - - :arg Testable x: Value to test. - :arg EBB: Destination extended basic block. - :arg args...: Zero or more arguments passed to EBB. - :result: None. - -.. inst:: br_table x, JT - - Jump table branch. - - Use ``x`` as an unsigned index into the jump table ``JT``. If a jump table - entry is found, branch to the corresponding EBB. If no entry was found fall - through to the next instruction. - - Note that this branch instruction can't pass arguments to the targeted - blocks. Split critical edges as needed to work around this. - - :arg iN x: Integer index into jump table. - :arg JT: Jump table which was declared in the preamble. - :result: None. +.. autoinst:: jump +.. autoinst:: brz +.. autoinst:: brnz +.. autoinst:: br_table .. inst:: JT = jump_table EBB0, EBB1, ..., EBBn @@ -386,29 +340,9 @@ explicit trap instructions defined below, but some instructions may also cause traps for certain input value. For example, :inst:`udiv` traps when the divisor is zero. -.. inst:: trap - - Terminate execution unconditionally. - - :result: None. This is a terminator instruction. - -.. inst:: trapz x - - Trap when zero. - - if ``x`` is non-zero, execution continues at the following instruction. - - :arg Testable x: Value to test. - :result: None. - -.. inst:: trapnz x - - Trap when non-zero. - - if ``x`` is zero, execution continues at the following instruction. - - :arg Testable x: Value to test. - :result: None. +.. autoinst:: trap +.. autoinst:: trapz +.. autoinst:: trapnz Function calls diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 0447cdfcc0..961a3a500c 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -450,6 +450,8 @@ class Instruction(object): operands and other operand kinds. :param outs: Tuple of output operands. The output operands must be SSA values. + :param is_terminator: This is a terminator instruction. + :param is_branch: This is a branch instruction. """ def __init__(self, name, doc, ins=(), outs=(), **kwargs): diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 3fd8554cf3..9f77fbf860 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -4,18 +4,94 @@ Cretonne base instruction set. This module defines the basic Cretonne instruction set that all targets support. """ -from . import TypeVar, Operand, Instruction, InstructionGroup +from . import TypeVar, Operand, Instruction, InstructionGroup, variable_args from types import i8, f32, f64 from immediates import imm64, ieee32, ieee64, immvector +import entities instructions = InstructionGroup("base", "Shared base instruction set") Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) iB = TypeVar('iB', 'A scalar integer type', ints=True) +Testable = TypeVar( + 'Testable', 'A scalar boolean or integer type', + ints=True, bools=True) TxN = TypeVar( '%Tx%N', 'A SIMD vector type', ints=True, floats=True, bools=True, scalars=False, simd=True) +# +# Control flow +# +c = Operand('c', Testable, doc='Controlling value to test') +EBB = Operand('EBB', entities.ebb, doc='Destination extended basic block') +args = Operand('args', variable_args, doc='EBB arguments') + +jump = Instruction( + 'jump', r""" + Jump. + + Unconditionally jump to an extended basic block, passing the specified + EBB arguments. The number and types of arguments must match the + destination EBB. + """, + ins=(EBB, args), is_terminator=True) + +brz = Instruction( + 'brz', r""" + Branch when zero. + + If ``c`` is a :type:`b1` value, take the branch when ``c`` is false. If + ``c`` is an integer value, take the branch when ``c = 0``. + """, + ins=(c, EBB, args), is_branch=True) + +brnz = Instruction( + 'brnz', r""" + Branch when non-zero. + + If ``c`` is a :type:`b1` value, take the branch when ``c`` is true. If + ``c`` is an integer value, take the branch when ``c != 0``. + """, + ins=(c, EBB, args), is_branch=True) + +x = Operand('x', iB, doc='index into jump table') +JT = Operand('JT', entities.jump_table) +br_table = Instruction( + 'br_table', r""" + Indirect branch via jump table. + + Use ``x`` as an unsigned index into the jump table ``JT``. If a jump table + entry is found, branch to the corresponding EBB. If no entry was found fall + through to the next instruction. + + Note that this branch instruction can't pass arguments to the targeted + blocks. Split critical edges as needed to work around this. + """, + ins=(x, JT), is_branch=True) + +trap = Instruction( + 'trap', r""" + Terminate execution unconditionally. + """, + is_terminator=True) + +trapz = Instruction( + 'trapz', r""" + Trap when zero. + + if ``c`` is non-zero, execution continues at the following instruction. + """, + ins=c) + +trapnz = Instruction( + 'trapnz', r""" + Trap when non-zero. + + if ``c`` is zero, execution continues at the following instruction. + """, + ins=c) + # # Materializing constants. # diff --git a/meta/cretonne/entities.py b/meta/cretonne/entities.py index 67265ee844..14587d402f 100644 --- a/meta/cretonne/entities.py +++ b/meta/cretonne/entities.py @@ -21,3 +21,6 @@ signature = EntityRefKind('signature', 'A function signature.') #: A reference to an external function declared in the function preamble. #: This is used to provide the callee and signature in a call instruction. function = EntityRefKind('function', 'An external function.') + +#: A reference to a jump table declared in the function preamble. +jump_table = EntityRefKind('jump_table', 'A jump table.') diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 9ea555a257..8b32936d7c 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -9,7 +9,7 @@ in this module. from . import InstructionFormat, value, variable_args from immediates import imm64, ieee32, ieee64, immvector -from entities import function +from entities import ebb, function, jump_table Nullary = InstructionFormat() @@ -23,6 +23,10 @@ Binary = InstructionFormat(value, value) BinaryImm = InstructionFormat(value, imm64) BinaryImmRev = InstructionFormat(imm64, value) +Jump = InstructionFormat(ebb, variable_args) +Branch = InstructionFormat(value, ebb, variable_args) +BranchTable = InstructionFormat(value, jump_table) + Call = InstructionFormat(function, variable_args, multiple_results=True) # Finally extract the names of global variables in this module. diff --git a/src/libcretonne/entities.rs b/src/libcretonne/entities.rs index d4e1cb849b..8513447ab2 100644 --- a/src/libcretonne/entities.rs +++ b/src/libcretonne/entities.rs @@ -189,3 +189,34 @@ impl Default for StackSlot { NO_STACK_SLOT } } + +/// An opaque reference to a jump table. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct JumpTable(u32); + +impl JumpTable { + pub fn new(index: usize) -> JumpTable { + assert!(index < (u32::MAX as usize)); + JumpTable(index as u32) + } + + pub fn index(&self) -> usize { + self.0 as usize + } +} + +/// Display a `JumpTable` reference as "jt12". +impl Display for JumpTable { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "jt{}", self.0) + } +} + +/// A guaranteed invalid jump table reference. +pub const NO_JUMP_TABLE: JumpTable = JumpTable(u32::MAX); + +impl Default for JumpTable { + fn default() -> JumpTable { + NO_JUMP_TABLE + } +} diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 39202b000f..3cb1ba5378 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -140,6 +140,22 @@ pub enum InstructionData { rhs: Value, lhs: Imm64, }, + Jump { + opcode: Opcode, + ty: Type, + data: Box, + }, + Branch { + opcode: Opcode, + ty: Type, + data: Box, + }, + BranchTable { + opcode: Opcode, + ty: Type, + arg: Value, + table: JumpTable, + }, Call { opcode: Opcode, ty: Type, @@ -147,6 +163,66 @@ pub enum InstructionData { }, } +/// A variable list of `Value` operands used for function call arguments and passing arguments to +/// basic blocks. +#[derive(Debug)] +pub struct VariableArgs(Vec); + +impl VariableArgs { + pub fn new() -> VariableArgs { + VariableArgs(Vec::new()) + } +} + +impl Display for VariableArgs { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + try!(write!(fmt, "(")); + for (i, val) in self.0.iter().enumerate() { + if i == 0 { + try!(write!(fmt, "{}", val)); + } else { + try!(write!(fmt, ", {}", val)); + } + } + write!(fmt, ")") + } +} + +impl Default for VariableArgs { + fn default() -> VariableArgs { + VariableArgs::new() + } +} + +/// Payload data for jump instructions. These need to carry lists of EBB arguments that won't fit +/// in the allowed InstructionData size. +#[derive(Debug)] +pub struct JumpData { + destination: Ebb, + arguments: VariableArgs, +} + +impl Display for JumpData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}{}", self.destination, self.arguments) + } +} + +/// Payload data for branch instructions. These need to carry lists of EBB arguments that won't fit +/// in the allowed InstructionData size. +#[derive(Debug)] +pub struct BranchData { + arg: Value, + destination: Ebb, + arguments: VariableArgs, +} + +impl Display for BranchData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}, {}{}", self.arg, self.destination, self.arguments) + } +} + /// Payload of a call instruction. #[derive(Debug)] pub struct CallData { @@ -154,9 +230,14 @@ pub struct CallData { second_result: Value, // Dynamically sized array containing call argument values. - arguments: Vec, + arguments: VariableArgs, } +impl Display for CallData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "TBD{}", self.arguments) + } +} impl InstructionData { /// Create data for a call instruction. @@ -166,7 +247,7 @@ impl InstructionData { ty: return_type, data: Box::new(CallData { second_result: NO_VALUE, - arguments: Vec::new(), + arguments: VariableArgs::new(), }), } } @@ -184,6 +265,9 @@ impl InstructionData { Binary { opcode, .. } => opcode, BinaryImm { opcode, .. } => opcode, BinaryImmRev { opcode, .. } => opcode, + Jump { opcode, .. } => opcode, + Branch { opcode, .. } => opcode, + BranchTable { opcode, .. } => opcode, Call { opcode, .. } => opcode, } } @@ -201,6 +285,9 @@ impl InstructionData { Binary { ty, .. } => ty, BinaryImm { ty, .. } => ty, BinaryImmRev { ty, .. } => ty, + Jump { ty, .. } => ty, + Branch { ty, .. } => ty, + BranchTable { ty, .. } => ty, Call { ty, .. } => ty, } } @@ -218,6 +305,9 @@ impl InstructionData { Binary { .. } => None, BinaryImm { .. } => None, BinaryImmRev { .. } => None, + Jump { .. } => None, + Branch { .. } => None, + BranchTable { .. } => None, Call { ref data, .. } => Some(data.second_result), } } @@ -234,6 +324,9 @@ impl InstructionData { Binary { .. } => None, BinaryImm { .. } => None, BinaryImmRev { .. } => None, + Jump { .. } => None, + Branch { .. } => None, + BranchTable { .. } => None, Call { ref mut data, .. } => Some(&mut data.second_result), } } @@ -263,4 +356,15 @@ mod tests { assert_eq!("".parse::(), Err("Unknown opcode")); assert_eq!("\0".parse::(), Err("Unknown opcode")); } + + #[test] + fn instruction_data() { + use std::mem; + // The size of the InstructionData enum is important for performance. It should not exceed + // 16 bytes. Use `Box` out-of-line payloads for instruction formats that require + // more space than that. + // It would be fine with a data structure smaller than 16 bytes, but what are the odds of + // that? + assert_eq!(mem::size_of::(), 16); + } } diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 9f5c28c0b6..74ece4790e 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -155,7 +155,10 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { Binary { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), BinaryImm { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), BinaryImmRev { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), - Call { opcode, .. } => writeln!(w, "{} [...]", opcode), + Jump { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), + Branch { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), + BranchTable { opcode, arg, table, .. } => writeln!(w, "{} {}, {}", opcode, arg, table), + Call { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), } } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index ab37e0896a..713fa7e829 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -657,6 +657,9 @@ impl<'a> Parser<'a> { rhs: rhs, } } + InstructionFormat::Jump | + InstructionFormat::Branch | + InstructionFormat::BranchTable | InstructionFormat::Call => { unimplemented!(); }