diff options
author | Ian Romanick <ian.d.romanick@intel.com> | 2019-02-19 20:53:09 -0800 |
---|---|---|
committer | Ian Romanick <ian.d.romanick@intel.com> | 2019-03-26 10:29:22 -0700 |
commit | d1a9aba61bd7f4c8a6d61821fe98df5faaa5a488 (patch) | |
tree | 4941cbd1736e8544aee763c1f9d24cbc4ad9063d /src | |
parent | 63e9b863613fc1c03018410122b2f57f45320ab1 (diff) |
WIP: intel/compiler: Add code generator generator
Diffstat (limited to 'src')
-rw-r--r-- | src/intel/compiler/gen_gen_codegen.py | 862 |
1 files changed, 862 insertions, 0 deletions
diff --git a/src/intel/compiler/gen_gen_codegen.py b/src/intel/compiler/gen_gen_codegen.py new file mode 100644 index 000000000000..7488d87f3e0a --- /dev/null +++ b/src/intel/compiler/gen_gen_codegen.py @@ -0,0 +1,862 @@ +# +# Copyright (C) 2019 Intel Corporation +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +from __future__ import print_function +import ast +from collections import defaultdict +import itertools +import struct +import sys +import mako.template +import re +import traceback +from enum import Enum, unique + +# Generate an Intel Gen architecture code generator. + +# These opcodes are only employed by nir_search. This provides a mapping from +# opcode to destination type. +conv_opcode_types = { + 'i2f' : 'float', + 'u2f' : 'float', + 'f2f' : 'float', + 'f2u' : 'uint', + 'f2i' : 'int', + 'u2u' : 'uint', + 'i2i' : 'int', + 'b2f' : 'float', + 'b2i' : 'int', + 'i2b' : 'bool', + 'f2b' : 'bool', +} + +if sys.version_info < (3, 0): + integer_types = (int, long) + string_type = unicode + +else: + integer_types = (int, ) + string_type = str + +_type_re = re.compile(r"(?P<type>int|uint|bool|float)?(?P<bits>\d+)?") + +def type_bits(type_str): + m = _type_re.match(type_str) + assert m.group('type') + + if m.group('bits') is None: + return 0 + else: + return int(m.group('bits')) + +# Represents a set of variables, each with a unique id +class VarSet(object): + def __init__(self): + self.names = {} + self.ids = itertools.count() + self.immutable = False; + + def __getitem__(self, name): + if name not in self.names: + assert not self.immutable, "Unknown replacement variable: {}".format(name) + self.names[name] = next(self.ids) + + return self.names[name] + + def lock(self): + self.immutable = True + +class Value(object): + @staticmethod + def create(val, name_base, varset): + if isinstance(val, bytes): + val = val.decode('utf-8') + + if isinstance(val, tuple): + return Expression(val, name_base, varset) + elif isinstance(val, Expression): + return val + elif isinstance(val, string_type): + return Variable(val, name_base, varset) + elif isinstance(val, (bool, float) + integer_types): + return Constant(val, name_base) + + __template = mako.template.Template(""" +static const ${val.c_type} ${val.name} = { + { ${val.type_enum}, ${val.c_bit_size} }, +% if isinstance(val, Constant): + ${val.type()}, { ${val.hex()} /* ${val.value} */ }, +% elif isinstance(val, Variable): + ${val.index}, /* ${val.var_name} */ + ${'true' if val.is_constant else 'false'}, + ${val.type() or 'nir_type_invalid' }, + ${val.cond if val.cond else 'NULL'}, +% elif isinstance(val, Expression): + ${'true' if val.inexact else 'false'}, + nir_op_${val.opcode}, + { ${', '.join(src.c_ptr for src in val.sources)} }, + ${val.cond if val.cond else 'NULL'}, +% endif +};""") + + def __init__(self, val, name, type_str): + self.in_val = str(val) + self.name = name + self.type_str = type_str + + def __str__(self): + return self.in_val + + def get_bit_size(self): + """Get the physical bit-size that has been chosen for this value, or if + there is none, the canonical value which currently represents this + bit-size class. Variables will be preferred, i.e. if there are any + variables in the equivalence class, the canonical value will be a + variable. We do this since we'll need to know which variable each value + is equivalent to when constructing the replacement expression. This is + the "find" part of the union-find algorithm. + """ + bit_size = self + + while isinstance(bit_size, Value): + if bit_size._bit_size is None: + break + bit_size = bit_size._bit_size + + if bit_size is not self: + self._bit_size = bit_size + return bit_size + + @property + def type_enum(self): + return "nir_search_value_" + self.type_str + + @property + def c_type(self): + return "nir_search_" + self.type_str + + @property + def c_ptr(self): + return "&{0}.value".format(self.name) + + @property + def c_bit_size(self): + bit_size = self.get_bit_size() + if isinstance(bit_size, int): + return bit_size + elif isinstance(bit_size, Variable): + return -bit_size.index - 1 + else: + # If the bit-size class is neither a variable, nor an actual bit-size, then + # - If it's in the search expression, we don't need to check anything + # - If it's in the replace expression, either it's ambiguous (in which + # case we'd reject it), or it equals the bit-size of the search value + # We represent these cases with a 0 bit-size. + return 0 + + def render(self): + return self.__template.render(val=self, + Constant=Constant, + Variable=Variable, + Expression=Expression) + +_constant_re = re.compile(r"(?P<value>[^@\(]+)(?:@(?P<bits>\d+))?") + +class Constant(Value): + def __init__(self, val, name): + Value.__init__(self, val, name, "constant") + + if isinstance(val, (str)): + m = _constant_re.match(val) + self.value = ast.literal_eval(m.group('value')) + self._bit_size = int(m.group('bits')) if m.group('bits') else None + else: + self.value = val + self._bit_size = None + + if isinstance(self.value, bool): + assert self._bit_size is None or self._bit_size == 1 + self._bit_size = 1 + + def hex(self): + if isinstance(self.value, (bool)): + return 'NIR_TRUE' if self.value else 'NIR_FALSE' + if isinstance(self.value, integer_types): + return hex(self.value) + elif isinstance(self.value, float): + i = struct.unpack('Q', struct.pack('d', self.value))[0] + h = hex(i) + + # On Python 2 this 'L' suffix is automatically added, but not on Python 3 + # Adding it explicitly makes the generated file identical, regardless + # of the Python version running this script. + if h[-1] != 'L' and i > sys.maxsize: + h += 'L' + + return h + else: + assert False + + def type(self): + if isinstance(self.value, (bool)): + return "nir_type_bool" + elif isinstance(self.value, integer_types): + return "nir_type_int" + elif isinstance(self.value, float): + return "nir_type_float" + +_var_name_re = re.compile(r"(?P<const>#)?(?P<name>\w+)" + r"(?:@(?P<type>int|uint|bool|float)?(?P<bits>\d+)?)?" + r"(?P<cond>\([^\)]+\))?") + +class Variable(Value): + def __init__(self, val, name, varset): + Value.__init__(self, val, name, "variable") + + m = _var_name_re.match(val) + assert m and m.group('name') is not None + + self.var_name = m.group('name') + + # Prevent common cases where someone puts quotes around a literal + # constant. If we want to support names that have numeric or + # punctuation characters, we can me the first assertion more flexible. + assert self.var_name.isalpha() + assert self.var_name is not 'True' + assert self.var_name is not 'False' + + self.is_constant = m.group('const') is not None + self.cond = m.group('cond') + self.required_type = m.group('type') + self._bit_size = int(m.group('bits')) if m.group('bits') else None + + if self.required_type == 'bool': + if self._bit_size is not None: + assert self._bit_size in type_sizes(self.required_type) + else: + self._bit_size = 1 + + if self.required_type is not None: + assert self.required_type in ('float', 'bool', 'int', 'uint') + + self.index = varset[self.var_name] + + def type(self): + if self.required_type == 'bool': + return "nir_type_bool" + elif self.required_type in ('int', 'uint'): + return "nir_type_int" + elif self.required_type == 'float': + return "nir_type_float" + +_opcode_re = re.compile(r"(?P<inexact>~)?(?P<opcode>\w+)(?:@(?P<bits>\d+))?" + r"(?P<cond>\([^\)]+\))?") + +class Expression(Value): + def __init__(self, expr, name_base, varset): + Value.__init__(self, expr, name_base, "expression") + assert isinstance(expr, tuple) + + m = _opcode_re.match(expr[0]) + assert m and m.group('opcode') is not None + + self.opcode = m.group('opcode') + self._bit_size = int(m.group('bits')) if m.group('bits') else None + self.inexact = m.group('inexact') is not None + self.cond = m.group('cond') + self.sources = [ Value.create(src, "{0}_{1}".format(name_base, i), varset) + for (i, src) in enumerate(expr[1:]) ] + + if self.opcode in conv_opcode_types: + assert self._bit_size is None, \ + 'Expression cannot use an unsized conversion opcode with ' \ + 'an explicit size; that\'s silly.' + + + def render(self): + srcs = "\n".join(src.render() for src in self.sources) + return srcs + super(Expression, self).render() + + +@unique +class OperandType(Enum): + # Inferred means the type of the operand is inferred from expected source + # type of the NIR instruction. + inferred = 0 + B, W, D, Q, UB, UW, UD, UQ, HF, F, DF, VF = range(1, 13) + + def __str__(self): + return "BRW_REGISTER_TYPE_" + self.name + + +@unique +class OperandFile(Enum): + unknown = 0 + destination = 1 + input = 2 + temporary = 3 + constant = 4 + null = 5 + grf = 6 + + +def grf(number, subnumber, width): + operand = RegisterOperand("g{}.{}".format(number, subnumber)) + operand.nr = number + operand.subnr = subnumber + operand.size = width + operand.file = OperandFile.grf + return operand + + +def retype(operand, new_type): + if not isinstance(operand, Operand): + operand = Operand.create(operand) + + operand.retype(new_type) + return operand + + +def subscript(operand, type, index): + if not isinstance(operand, Operand): + operand = Operand.create(operand) + + operand.subscript(type, index) + return operand + + +def abs(operand): + if not isinstance(operand, Operand): + operand = Operand.create(operand) + + operand.abs() + return operand + + +def neg(operand): + assert not isinstance(operand, Instruction) + + if not isinstance(operand, Operand): + operand = Operand.create(operand) + + operand.negate() + return operand + + +def imm(value, type): + return ConstantOperand(value, type) + + +def null(type): + return retype(RegisterOperand(None), type) + + +class Operand(object): + @staticmethod + def create(val): + if isinstance(val, bytes): + val = val.decode('utf-8') + + if isinstance(val, Operand): + return val + elif isinstance(val, string_type): + return RegisterOperand(val) + else: + return ConstantOperand(val) + + +class ConstantOperand(Operand): + all_constants = [] + + def __init__(self, val, type): + self.val = val + self.file = OperandFile.constant + self.type = type if isinstance(type, OperandType) else OperandType[type] + self.index = len(ConstantOperand.all_constants) + + assert self.type != OperandType.inferred + + ConstantOperand.all_constants.append(self) + + + def negate(self): + assert False + + + def abs(self): + assert False + + + def retype(self, new_type): + assert False + + + def subscript(self, type, index): + assert False + + + def remap_names(self, file, varset): + pass + + + def bytecode_instruction_list(self): + return ["bytecode_instruction(append_constant, {}, {})".format(self.type, self.index)] + + + def __str__(self): + if self.type == OperandType.Q: + return "{{ .q = {} }}".format(self.val) + elif self.type == OperandType.UQ: + return "{{ .uq = {} }}".format(self.val) + elif self.type == OperandType.D: + return "{{ .d = {} }}".format(self.val) + elif self.type == OperandType.UD: + return "{{ .ud = {} }}".format(self.val) + elif self.type == OperandType.W: + return "{{ .w = {} }}".format(self.val) + elif self.type == OperandType.UW: + return "{{ .uw = {} }}".format(self.val) + elif self.type == OperandType.B: + return "{{ .b = {} }}".format(self.val) + elif self.type == OperandType.UB: + return "{{ .ub = {} }}".format(self.val) + elif self.type == OperandType.F: + return "{{ .f = {} }}".format(self.val) + elif self.type == OperandType.DF: + return "{{ .df = {}d }}".format(self.val) + elif self.type == OperandType.HF: + return "{{ .uw = {} /* .hf = {} */ }}".format(hex(struct.unpack('<H', struct.pack('<e', self.val))[0]), self.val) + else: + # Unsupported types for constants + assert False + + +class RegisterOperand(Operand): + def __init__(self, name): + self.type = OperandType.inferred + self._abs = False + self.neg = False + self.subregion = None + self.size = 0 + + if name == "r": + self.name = None + self.file = OperandFile.destination + self.index = 0 + elif name is None: + self.name = None + self.file = OperandFile.null + else: + self.name = name + self.file = OperandFile.unknown + self.index = 0 + + + def negate(self): + self.neg = not self.neg + + + def abs(self): + self.neg = False + self._abs = True + + + def retype(self, new_type): + if isinstance(new_type, OperandType): + self.type = new_type + else: + self.type = OperandType[new_type] + + + def subscript(self, type, index): + assert self.subregion is None + + if not isinstance(type, OperandType): + type = OperandType[type] + + self.subregion = (type, index) + + + def remap_names(self, file, varset): + if self.name is not None and self.file != OperandFile.grf: + if file == OperandFile.input: + self.file = file + self.index = varset[self.name] + self.name = None + elif self.name in varset: + self.file = file + self.index, _ = varset[self.name] + self.name = None + + + def bytecode_instruction_list(self): + assert self.file != OperandFile.unknown + assert self.file != OperandFile.constant + + if self.file == OperandFile.destination: + op = [ "bytecode_instruction(append_output)" ] + elif self.file == OperandFile.input: + op = [ "bytecode_instruction(append_input, {})".format(self.index) ] + elif self.file == OperandFile.temporary: + op = [ "bytecode_instruction(append_temporary, {})".format(self.index) ] + elif self.file == OperandFile.null: + assert self.type != OperandType.inferred + op = [ "bytecode_instruction(append_null_reg, {})".format(self.type) ] + elif self.file == OperandFile.grf: + assert self.size != 0 + op = [ "bytecode_instruction(append_vec{}_grf, {}, {})".format(self.size, self.subnr, self.nr) ] + + + if self.file != OperandFile.null: + if self.type != OperandType.inferred: + op.append("bytecode_instruction(retype_operand, {})".format(self.type)) + + if self._abs: + op.append("bytecode_instruction(abs_operand)") + + if self.neg: + op.append("bytecode_instruction(neg_operand)") + + if self.subregion is not None: + op.append("bytecode_instruction(subscript_operand, {}, {})".format(self.subregion[0], self.subregion[1])) + + return op + + +class Instruction(object): + def __init__(self, opcode, dest, src0=None, src1=None, src2=None): + self.opcode = opcode + self.saturate_mode = False + self.conditional_modifier = None + self.pred = None + self.dest = Operand.create(dest) + + srcs = [] + if src0 is not None: + srcs.append(src0) + + if src1 is not None: + srcs.append(src1) + + if src2 is not None: + srcs.append(src2) + + self.sources = [Operand.create(s) for s in srcs] + + + def remap_names(self, file, varset): + if file == OperandFile.temporary: + self.dest.remap_names(file, varset) + + [src.remap_names(file, varset) for src in self.sources] + + + def saturate(self): + self.saturate_mode = True + return self + + + def cmod(self, m): + # Don't support the "round increment" R cmod from Gen4/Gen5. + assert m in ['Z', 'NZ', 'L', 'LE', 'G', 'GE', 'O', 'U'] + self.conditional_modifier = m + return self + + + def predicate(self, pred="NORMAL"): + self.pred = pred + return self + + + def bytecode_instruction_list(self): + opcode_name = self.opcode if "_OPCODE_" in self.opcode else "BRW_OPCODE_" + self.opcode + instr = [ "bytecode_instruction(emit_instruction, {})".format(opcode_name) ] + + if self.saturate_mode: + instr.append("bytecode_instruction(saturate_instruction)") + + if self.conditional_modifier is not None: + instr.append("bytecode_instruction(conditional_mod, BRW_CONDITIONAL_{})".format(self.conditional_modifier)) + + if self.pred is not None: + instr.append("bytecode_instruction(predicate_instruction, BRW_PREDICATE_{})".format(self.pred)) + + return self.dest.bytecode_instruction_list() +\ + [x for src in self.sources for x in src.bytecode_instruction_list() ] +\ + instr + + +class InstructionList(object): + """Represents a list of instructions and any temporary registers used by those + instructions. + + """ + + def __init__(self, temporary_list, instruction_list): + self.instructions = instruction_list + + self.temporaries = {} + temp_ids = itertools.count() + for (name, type) in temporary_list: + self.temporaries[name] = (next(temp_ids), OperandType[type]) + + [instr.remap_names(OperandFile.temporary, self.temporaries) for instr in self.instructions] + + + def remap_names(self, varset): + [instr.remap_names(OperandFile.input, varset) for instr in self.instructions] + + + def bytecode_instruction_list(self): + declare_temps = ["bytecode_instruction(declare_temporary, {})".format(type) for (_, (_, type)) in self.temporaries.items()] + instr = [x for instr in self.instructions for x in instr.bytecode_instruction_list()] + + return declare_temps + instr + + +_optimization_ids = itertools.count() +condition_list = ['true'] + +class SearchAndReplace(object): + def __init__(self, transform): + self.id = next(_optimization_ids) + + search = transform[0] + replace = transform[1] + + self.condition = transform[2] if len(transform) > 2 else 'true' + if self.condition not in condition_list: + condition_list.append(self.condition) + self.condition_index = condition_list.index(self.condition) + + varset = VarSet() + if isinstance(search, Expression): + self.search = search + else: + self.search = Expression(search, "search{0}".format(self.id), varset) + + varset.lock() + + assert isinstance(replace, Instruction) or isinstance(replace, InstructionList) + + self.replace = InstructionList([], (replace,)) if isinstance(replace, Instruction) else replace + self.replace.remap_names(varset) + +_code_generator_template = mako.template.Template(""" +#ifndef STRUCT_TRANSFORM +#define STRUCT_TRANSFORM + +struct transform { + const nir_search_expression *search; + unsigned bytecode; + unsigned condition_offset; +}; + +#endif /* STRUCT_TRANSFORM */ + +static const struct bytecode_instruction ${pass_name}_derp[] = { +% for b in bytecode: + ${b}, +% endfor +}; + +static const union immediate_value ${pass_name}_immediates[] = { +% for i in immediates: + ${i}, +% endfor +}; + +% for (opcode, xform_list) in sorted(xform_dict.items()): + % for xform in xform_list: + ${xform.search.render()} + % endfor +% endfor + + +static const struct transform ${pass_name}_xforms[] = { +% for (name, first_bytecode, condition_index) in expression_bytecode_tuples: + { &${name}, ${first_bytecode}, ${condition_index} }, +% endfor +}; + +static bool +nir_emit_alu_${pass_name}(fs_visitor *v, const struct gen_device_info *devinfo, + const fs_builder &bld, nir_alu_instr *alu) +{ + const struct brw_wm_prog_key *const fs_key = (struct brw_wm_prog_key *) v->key; + + const bool condition_flags[${len(condition_list)}] = { + % for index, condition in enumerate(condition_list): + ${condition}, /* ${index} */ + % endfor + }; + + unsigned first = 0; + unsigned count = 0; + + switch (alu->op) { + % for opcode in opcode_dict.keys(): + case nir_op_${opcode}: + first = ${opcode_dict[opcode][0]}; + count = ${opcode_dict[opcode][1]}; + break; + + % endfor + % for opcode, reason in unsupported: + % if not isinstance(opcode, str): + % for o in opcode: + case nir_op_${o}: + % endfor + % else: + case nir_op_${opcode}: + % endif + unreachable("${reason}"); + % endfor + default: + /* unreachable("unhandled instruction"); */ + break; + } + + for (unsigned i = 0; i < count; i++) { + uint8_t swizzle[NIR_MAX_VEC_COMPONENTS]; + unsigned num_variables; + nir_alu_src variables[NIR_SEARCH_MAX_VARIABLES]; + uint8_t which_src[NIR_SEARCH_MAX_VARIABLES]; + + if (condition_flags[${pass_name}_xforms[first + i].condition_offset] && + nir_match_instr(alu, ${pass_name}_xforms[first + i].search, + swizzle, &num_variables, variables, which_src)) { + /* Deal with the destination of the instruction. */ + fs_reg result = v->get_nir_dest(alu->dest.dest); + result.type = brw_type_for_nir_type(devinfo, + (nir_alu_type)(nir_op_infos[alu->op].output_type | + nir_dest_bit_size(alu->dest.dest))); + + /* This code path should never see an instruction that operates on + * more than a single channel. Therefore, we can just adjust the + * source and destination registers for that channel and emit the + * instruction. + */ + unsigned channel = 0; + if (nir_op_infos[alu->op].output_size == 0) { + /* Since NIR is doing the scalarizing for us, we should only ever + * see vectorized operations with a single channel. + */ + assert(util_bitcount(alu->dest.write_mask) == 1); + channel = ffs(alu->dest.write_mask) - 1; + + result = offset(result, bld, channel); + } + + /* Deal with the sources of the "instruction." */ + fs_reg op[NIR_SEARCH_MAX_VARIABLES]; + for (unsigned i = 0; i < num_variables; i++) { + /* Instruction that originally used this variable. */ + assert(variables[i].src.parent_instr->type == nir_instr_type_alu); + nir_alu_instr *const orig_user = + nir_instr_as_alu(variables[i].src.parent_instr); + + op[i] = v->get_nir_src(variables[i].src); + op[i].type = brw_type_for_nir_type(devinfo, + (nir_alu_type)(nir_op_infos[orig_user->op].input_types[which_src[i]] | + nir_src_bit_size(variables[i].src))); + op[i].abs = variables[i].abs; + op[i].negate = variables[i].negate; + + assert(nir_op_infos[orig_user->op].input_sizes[i] < 2); + op[i] = offset(op[i], bld, orig_user->src[which_src[i]].swizzle[channel]); + } + + emit_instructions_from_bytecode(bld, + &${pass_name}_derp[${pass_name}_xforms[first + i].bytecode], + result, + op, + ${pass_name}_immediates, + alu->dest.saturate); + + return true; + } + } + + return false; +} +""") + +class CodeGeneratorGenerator(object): + def __init__(self, pass_name, transforms, unsupported): + self.opcode_xforms = defaultdict(lambda : []) + self.pass_name = pass_name + self.unsupported = unsupported + + error = False + + for xform in transforms: + if not isinstance(xform, SearchAndReplace): + try: + xform = SearchAndReplace(xform) + except: + print("Failed to parse transformation:", file=sys.stderr) + print(" " + str(xform), file=sys.stderr) + traceback.print_exc(file=sys.stderr) + print('', file=sys.stderr) + error = True + continue + + if xform.search.opcode not in self.opcode_xforms: + self.opcode_xforms[xform.search.opcode] = [] + + self.opcode_xforms[xform.search.opcode].append(xform) + + if error: + sys.exit(1) + + + def render(self): + # Generate two tables reprensenting the data related to the code + # transformations: + # + # 1. Table of bytecode instructions. + # 2. Table of <expression, bytecode> tuples. + + bytecode = [] + expression_bytecode_tuples = [] + opcode_dict = defaultdict(lambda : []) + + for (opcode, xform_list) in sorted(self.opcode_xforms.items()): + first = len(expression_bytecode_tuples) + + for xform in xform_list: + expression_bytecode_tuples.append((xform.search.name, len(bytecode), xform.condition_index)) + bytecode = bytecode + xform.replace.bytecode_instruction_list() + ["bytecode_instruction(end_of_stream)"] + + count = len(expression_bytecode_tuples) - first + opcode_dict[opcode] = (first, count) + + return _code_generator_template.render(pass_name=self.pass_name, + xform_dict=self.opcode_xforms, + bytecode=bytecode, + expression_bytecode_tuples=expression_bytecode_tuples, + opcode_dict=opcode_dict, + immediates=ConstantOperand.all_constants, + condition_list=condition_list, + unsupported=self.unsupported) + |