""" Generate sources with instruction info. """ from __future__ import absolute_import import srcgen import constant_hash from unique_table import UniqueTable, UniqueSeqTable import cretonne def gen_formats(fmt): """Generate an instruction format enumeration""" fmt.doc_comment('An instruction format') fmt.doc_comment('') fmt.doc_comment('Every opcode has a corresponding instruction format') fmt.doc_comment('which is represented by both the `InstructionFormat`') fmt.doc_comment('and the `InstructionData` enums.') fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') with fmt.indented('pub enum InstructionFormat {', '}'): for f in cretonne.InstructionFormat.all_formats: fmt.line(f.name + ',') fmt.line() # Emit a From which also serves to verify that # InstructionFormat and InstructionData are in sync. with fmt.indented( "impl<'a> From<&'a InstructionData> for InstructionFormat {", '}'): with fmt.indented( "fn from(inst: &'a InstructionData) -> InstructionFormat {", '}'): with fmt.indented('match *inst {', '}'): for f in cretonne.InstructionFormat.all_formats: fmt.line(('InstructionData::{} {{ .. }} => ' + 'InstructionFormat::{},') .format(f.name, f.name)) fmt.line() def gen_instruction_data_impl(fmt): """ Generate the boring parts of the InstructionData implementation. These methods in `impl InstructionData` can be generated automatically from the instruction formats: - `pub fn opcode(&self) -> Opcode` - `pub fn first_type(&self) -> Type` - `pub fn second_result(&self) -> Option` - `pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value>` """ # The `opcode` and `first_type` methods simply read the `opcode` and `ty` # members. This is really a workaround for Rust's enum types missing shared # members. with fmt.indented('impl InstructionData {', '}'): fmt.doc_comment('Get the opcode of this instruction.') with fmt.indented('pub fn opcode(&self) -> Opcode {', '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: fmt.line( 'InstructionData::{} {{ opcode, .. }} => opcode,' .format(f.name)) fmt.doc_comment('Type of the first result, or `VOID`.') with fmt.indented('pub fn first_type(&self) -> Type {', '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: fmt.line( 'InstructionData::{} {{ ty, .. }} => ty,' .format(f.name)) fmt.doc_comment('Mutable reference to the type of the first result.') with fmt.indented( 'pub fn first_type_mut(&mut self) -> &mut Type {', '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: fmt.line( 'InstructionData::{} {{ ref mut ty, .. }} => ty,' .format(f.name)) # Generate shared and mutable accessors for `second_result` which only # applies to instruction formats that can produce multiple results. # Everything else returns `None`. fmt.doc_comment('Second result value, if any.') with fmt.indented( 'pub fn second_result(&self) -> Option {', '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: if f.multiple_results: fmt.line( 'InstructionData::' + f.name + ' { second_result, .. }' + ' => Some(second_result),') else: # Single or no results. fmt.line( 'InstructionData::{} {{ .. }} => None,' .format(f.name)) fmt.doc_comment('Mutable reference to second result value, if any.') with fmt.indented( "pub fn second_result_mut<'a>(&'a mut self)" + " -> Option<&'a mut Value> {", '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: if f.multiple_results: fmt.line( 'InstructionData::' + f.name + ' { ref mut second_result, .. }' + ' => Some(second_result),') else: # Single or no results. fmt.line( 'InstructionData::{} {{ .. }} => None,' .format(f.name)) fmt.doc_comment('Get the controlling type variable operand.') with fmt.indented( 'pub fn typevar_operand(&self) -> Option {', '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: n = 'InstructionData::' + f.name if f.typevar_operand is None: fmt.line(n + ' { .. } => None,') elif len(f.value_operands) == 1: # We have a single value operand called 'arg'. if f.boxed_storage: fmt.line( n + ' { ref data, .. } => Some(data.arg),') else: fmt.line(n + ' { arg, .. } => Some(arg),') else: # We have multiple value operands and an array `args`. # Which `args` index to use? # Map from index into f.kinds into f.value_operands # index. i = f.value_operands.index(f.typevar_operand) if f.boxed_storage: fmt.line( n + ' { ref data, .. } => ' + ('Some(data.args[{}]),'.format(i))) else: fmt.line( n + ' {{ ref args, .. }} => Some(args[{}]),' .format(i)) def collect_instr_groups(isas): seen = set() groups = [] for isa in isas: for g in isa.instruction_groups: if g not in seen: groups.append(g) seen.add(g) return groups def gen_opcodes(groups, fmt): """ Generate opcode enumerations. Return a list of all instructions. """ fmt.doc_comment('An instruction opcode.') fmt.doc_comment('') fmt.doc_comment('All instructions from all supported ISAs are present.') fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') instrs = [] with fmt.indented('pub enum Opcode {', '}'): fmt.line('NotAnOpcode,') for g in groups: for i in g.instructions: instrs.append(i) i.number = len(instrs) # Build a doc comment. prefix = ', '.join(o.name for o in i.outs) if prefix: prefix = prefix + ' = ' suffix = ', '.join(o.name for o in i.ins) fmt.doc_comment( '`{}{} {}`. ({})' .format(prefix, i.name, suffix, i.format.name)) # Document polymorphism. if i.is_polymorphic: if i.use_typevar_operand: fmt.doc_comment( 'Type inferred from {}.' .format(i.ins[i.format.typevar_operand])) # Enum variant itself. fmt.line(i.camel_name + ',') fmt.line() # Generate a private opcode_format table. with fmt.indented( 'const OPCODE_FORMAT: [InstructionFormat; {}] = [' .format(len(instrs)), '];'): for i in instrs: fmt.format( 'InstructionFormat::{}, // {}', i.format.name, i.name) fmt.line() # Generate a private opcode_name function. with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'): with fmt.indented('match opc {', '}'): fmt.line('Opcode::NotAnOpcode => "",') for i in instrs: fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) fmt.line() # Generate an opcode hash table for looking up opcodes by name. hash_table = constant_hash.compute_quadratic( instrs, lambda i: constant_hash.simple_hash(i.name)) with fmt.indented( 'const OPCODE_HASH_TABLE: [Opcode; {}] = [' .format(len(hash_table)), '];'): for i in hash_table: if i is None: fmt.line('Opcode::NotAnOpcode,') else: fmt.format('Opcode::{},', i.camel_name) fmt.line() return instrs def get_constraint(op, ctrl_typevar, type_sets): """ Get the value type constraint for an SSA value operand, where `ctrl_typevar` is the controlling type variable. Each operand constraint is represented as a string, one of: - `Concrete(vt)`, where `vt` is a value type name. - `Free(idx)` where `idx` is an index into `type_sets`. - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. """ t = op.typ assert t.operand_kind() is cretonne.value # A concrete value type. if isinstance(t, cretonne.ValueType): return 'Concrete({})'.format(t.rust_name()) if t.free_typevar() is not ctrl_typevar: assert not t.is_derived return 'Free({})'.format(type_sets.add(t.type_set)) if t.is_derived: assert t.base is ctrl_typevar, "Not derived directly from ctrl_typevar" return t.derived_func assert t is ctrl_typevar return 'Same' def gen_type_constraints(fmt, instrs): """ Generate value type constraints for all instructions. - Emit a compact constant table of ValueTypeSet objects. - Emit a compact constant table of OperandConstraint objects. - Emit an opcode-indexed table of instruction constraints. """ # Table of TypeSet instances. type_sets = UniqueTable() # Table of operand constraint sequences (as tuples). Each operand # constraint is represented as a string, one of: # - `Concrete(vt)`, where `vt` is a value type name. # - `Free(idx)` where `idx` isan index into `type_sets`. # - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. operand_seqs = UniqueSeqTable() # Preload table with constraints for typical binops. operand_seqs.add(['Same'] * 3) # TypeSet indexes are encoded in 8 bits, with `0xff` reserved. typeset_limit = 0xff fmt.comment('Table of opcode constraints.') with fmt.indented( 'const OPCODE_CONSTRAINTS : [OpcodeConstraints; {}] = [' .format(len(instrs)), '];'): for i in instrs: # Collect constraints for the value results, not including # `variable_args` results which are always special cased. constraints = list() ctrl_typevar = None ctrl_typeset = typeset_limit if i.is_polymorphic: ctrl_typevar = i.ctrl_typevar ctrl_typeset = type_sets.add(ctrl_typevar.type_set) for idx in i.value_results: constraints.append( get_constraint(i.outs[idx], ctrl_typevar, type_sets)) for idx in i.format.value_operands: constraints.append( get_constraint(i.ins[idx], ctrl_typevar, type_sets)) offset = operand_seqs.add(constraints) fixed_results = len(i.value_results) use_typevar_operand = i.is_polymorphic and i.use_typevar_operand fmt.comment( '{}: fixed_results={}, use_typevar_operand={}' .format(i.camel_name, fixed_results, use_typevar_operand)) fmt.comment('Constraints={}'.format(constraints)) if i.is_polymorphic: fmt.comment( 'Polymorphic over {}'.format(ctrl_typevar.type_set)) # Compute the bit field encoding, c.f. instructions.rs. assert fixed_results < 8, "Bit field encoding too tight" flags = fixed_results if use_typevar_operand: flags |= 8 with fmt.indented('OpcodeConstraints {', '},'): fmt.line('flags: {:#04x},'.format(flags)) fmt.line('typeset_offset: {},'.format(ctrl_typeset)) fmt.line('constraint_offset: {},'.format(offset)) fmt.comment('Table of value type sets.') assert len(type_sets.table) <= typeset_limit, "Too many type sets" with fmt.indented( 'const TYPE_SETS : [ValueTypeSet; {}] = [' .format(len(type_sets.table)), '];'): for ts in type_sets.table: with fmt.indented('ValueTypeSet {', '},'): ts.emit_fields(fmt) fmt.comment('Table of operand constraint sequences.') with fmt.indented( 'const OPERAND_CONSTRAINTS : [OperandConstraint; {}] = [' .format(len(operand_seqs.table)), '];'): for c in operand_seqs.table: fmt.line('OperandConstraint::{},'.format(c)) def gen_format_constructor(iform, fmt): """ Emit a method for creating and inserting inserting an `iform` instruction, where `iform` is an instruction format. Instruction formats that can produce multiple results take a `ctrl_typevar` argument for deducing the result types. Others take a `result_type` argument. """ # Construct method arguments. args = ['&mut self', 'opcode: Opcode'] if iform.multiple_results: args.append('ctrl_typevar: Type') # `dfg::make_inst_results` will compute the result type. result_type = 'types::VOID' else: args.append('result_type: Type') result_type = 'result_type' # Normal operand arguments. for idx, kind in enumerate(iform.kinds): args.append('op{}: {}'.format(idx, kind.rust_type)) proto = '{}({}) -> Inst'.format(iform.name, ', '.join(args)) fmt.line('#[allow(non_snake_case)]') with fmt.indented('pub fn {} {{'.format(proto), '}'): # Generate the instruction data. with fmt.indented( 'let data = InstructionData::{} {{'.format(iform.name), '};'): fmt.line('opcode: opcode,') fmt.line('ty: {},'.format(result_type)) if iform.multiple_results: fmt.line('second_result: Value::default(),') if iform.boxed_storage: with fmt.indented( 'data: Box::new(instructions::{}Data {{' .format(iform.name), '}),'): gen_member_inits(iform, fmt) else: gen_member_inits(iform, fmt) # Create result values if necessary. if iform.multiple_results: fmt.line('let inst = self.insert_inst(data);') fmt.line('self.dfg.make_inst_results(inst, ctrl_typevar);') fmt.line('inst') else: fmt.line('self.insert_inst(data)') def gen_member_inits(iform, fmt): """ Emit member initializers for an `iform` instruction. """ # Values first. if len(iform.value_operands) == 1: fmt.line('arg: op{},'.format(iform.value_operands[0])) elif len(iform.value_operands) > 1: fmt.line('args: [{}],'.format( ', '.join('op{}'.format(i) for i in iform.value_operands))) # Immediates and entity references. for idx, member in enumerate(iform.members): if member: fmt.line('{}: op{},'.format(member, idx)) def gen_inst_builder(inst, fmt): """ Emit a method for generating the instruction `inst`. The method will create and insert an instruction, then return the result values, or the instruction reference itself for instructions that don't have results. """ # Construct method arguments. args = ['&mut self'] # The controlling type variable will be inferred from the input values if # possible. Otherwise, it is the first method argument. if inst.is_polymorphic and not inst.use_typevar_operand: args.append('{}: Type'.format(inst.ctrl_typevar.name)) tmpl_types = list() into_args = list() for op in inst.ins: if isinstance(op.kind, cretonne.ImmediateKind): t = 'T{}{}'.format(1 + len(tmpl_types), op.kind.name) tmpl_types.append('{}: Into<{}>'.format(t, op.kind.rust_type)) into_args.append(op.name) else: t = op.kind.rust_type args.append('{}: {}'.format(op.name, t)) # Return the inst reference for result-less instructions. if len(inst.value_results) == 0: rtype = 'Inst' elif len(inst.value_results) == 1: rtype = 'Value' else: rvals = ', '.join(len(inst.value_results) * ['Value']) rtype = '({})'.format(rvals) method = inst.name if method == 'return': # Avoid Rust keywords method = '_' + method if len(tmpl_types) > 0: tmpl = '<{}>'.format(', '.join(tmpl_types)) else: tmpl = '' proto = '{}{}({}) -> {}'.format(method, tmpl, ', '.join(args), rtype) fmt.line('#[allow(non_snake_case)]') with fmt.indented('pub fn {} {{'.format(proto), '}'): # Convert all of the `Into<>` arguments. for arg in into_args: fmt.line('let {} = {}.into();'.format(arg, arg)) # Arguments for instruction constructor. args = ['Opcode::' + inst.camel_name] if inst.is_polymorphic and not inst.use_typevar_operand: # This was an explicit method argument. args.append(inst.ctrl_typevar.name) elif len(inst.value_results) == 0: args.append('types::VOID') elif inst.is_polymorphic: # Infer the controlling type variable from the input operands. fmt.line( 'let ctrl_typevar = self.dfg.value_type({});' .format(inst.ins[inst.format.typevar_operand].name)) args.append('ctrl_typevar') else: # This non-polymorphic instruction has a fixed result type. args.append( 'types::' + inst.outs[inst.value_results[0]].typ.name.upper()) args.extend(op.name for op in inst.ins) args = ', '.join(args) fmt.line('let inst = self.{}({});'.format(inst.format.name, args)) if len(inst.value_results) == 0: fmt.line('inst') elif len(inst.value_results) == 1: fmt.line('self.dfg.first_result(inst)') else: fmt.line('let mut results = self.dfg.inst_results(inst);') fmt.line('({})'.format(', '.join( len(inst.value_results) * ['results.next().unwrap()']))) def gen_builder(insts, fmt): """ Generate a Builder trait with methods for all instructions. """ fmt.doc_comment( 'Methods for inserting instructions by instruction format.') with fmt.indented("impl<'a> Builder<'a> {", '}'): for f in cretonne.InstructionFormat.all_formats: gen_format_constructor(f, fmt) fmt.doc_comment('Methods for inserting instructions by opcode.') with fmt.indented("impl<'a> Builder<'a> {", '}'): for inst in insts: gen_inst_builder(inst, fmt) def generate(isas, out_dir): groups = collect_instr_groups(isas) # opcodes.rs fmt = srcgen.Formatter() gen_formats(fmt) gen_instruction_data_impl(fmt) instrs = gen_opcodes(groups, fmt) gen_type_constraints(fmt, instrs) fmt.update_file('opcodes.rs', out_dir) # builder.rs fmt = srcgen.Formatter() gen_builder(instrs, fmt) fmt.update_file('builder.rs', out_dir)