| #!/usr/bin/python |
| # |
| # Copyright (C) 2014 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| """Generate assembler files out of the definition file.""" |
| |
| import asm_defs |
| import os |
| import re |
| import sys |
| |
| |
| INDENT = ' ' |
| |
| _imm_types = { |
| # x86 immediates |
| 'Imm2': 'int8_t', |
| 'Imm8': 'int8_t', |
| 'Imm16': 'int16_t', |
| 'Imm32': 'int32_t', |
| 'Imm64': 'int64_t', |
| # Official RISC-V immediates |
| 'B-Imm': 'BImmediate', |
| 'I-Imm': 'IImmediate', |
| 'J-Imm': 'JImmediate', |
| 'P-Imm': 'PImmediate', |
| 'S-Imm': 'SImmediate', |
| 'U-Imm': 'UImmediate', |
| # Extra RISC-V immediates |
| 'Csr-Imm' : 'CsrImmediate', |
| 'Shift32-Imm': 'Shift32Immediate', |
| 'Shift64-Imm': 'Shift64Immediate' |
| } |
| |
| |
| def _get_arg_type_name(arg, insn_type): |
| cls = arg.get('class') |
| if asm_defs.is_x87reg(cls): |
| return 'X87Register' |
| if asm_defs.is_greg(cls): |
| return 'Register' |
| if asm_defs.is_freg(cls): |
| return 'FpRegister' |
| if asm_defs.is_xreg(cls): |
| return 'XMMRegister' |
| if asm_defs.is_imm(cls): |
| return _imm_types[cls] |
| if asm_defs.is_disp(cls): |
| return 'int32_t' |
| if asm_defs.is_label(cls): |
| return 'const Label&' |
| if asm_defs.is_cond(cls): |
| return 'Condition' |
| if asm_defs.is_csr(cls): |
| return 'Csr' |
| if asm_defs.is_rm(cls): |
| return 'Rounding' |
| if asm_defs.is_mem_op(cls): |
| if insn_type is not None and insn_type.endswith('-type'): |
| return 'const Operand<Register, %sImmediate>&' % insn_type[:-5] |
| return 'const Operand&' |
| raise Exception('class %s is not supported' % (cls)) |
| |
| |
| def _get_immediate_type(insn): |
| imm_type = None |
| for arg in insn.get('args'): |
| cls = arg.get('class') |
| if asm_defs.is_imm(cls): |
| assert imm_type is None |
| imm_type = _imm_types[cls] |
| return imm_type |
| |
| |
| def _get_params(insn, filter=None): |
| result = [] |
| arg_count = 0 |
| for arg in insn.get('args'): |
| if asm_defs.is_implicit_reg(arg.get('class')): |
| continue |
| if filter is not None and filter(arg): |
| continue |
| result.append("%s arg%d" % ( |
| _get_arg_type_name(arg, insn.get('type', None)), arg_count)) |
| arg_count += 1 |
| return ', '.join(result) |
| |
| |
| def _contains_mem(insn): |
| return any(asm_defs.is_mem_op(arg['class']) for arg in insn.get('args')) |
| |
| |
| def _get_template_name(insn): |
| name = insn.get('asm') |
| if '<' not in name: |
| return None, name |
| return 'template <%s>' % ', '.join( |
| 'bool' if param.strip() in ('true', 'false') else |
| 'typename' if re.search('[_a-zA-Z]', param) else 'int' |
| for param in name.split('<',1)[1][:-1].split(',')), name.split('<')[0] |
| |
| |
| def _gen_generic_functions_h(f, insns, binary_assembler, arch): |
| template_names = set() |
| for insn in insns: |
| template, name = _get_template_name(insn) |
| params = _get_params(insn) |
| imm_type = _get_immediate_type(insn) |
| if template: |
| # We could only describe each template function once, or that would be |
| # compilation error. Yet functions with the same name but different |
| # arguments are different (e.g. MacroVTbl<15> and MacroVTbl<23>). |
| # But different types of arguments could map to the same C++ type. |
| # For example MacroCmpFloat<Float32> and MacroCmpFloat<Float64> have |
| # different IR arguments (FpReg32 vs FpReg64), but both map to the |
| # same C++ type: XMMRegister. |
| # |
| # Use function name + parameters (as described by _get_params) to get |
| # full description of template function. |
| template_name = str({ |
| 'name': name, |
| 'params': params |
| }) |
| if template_name in template_names: |
| continue |
| template_names.add(template_name) |
| print(template, file=f) |
| # If this is binary assembler then we only generate header and then actual |
| # implementation is written manually. |
| # |
| # Text assembled passes "real" work down to GNU as, this works fine with |
| # just a simple generic implementation. |
| if binary_assembler: |
| if 'opcode' in insn: |
| assert '' not in insn |
| insn['opcodes'] = [insn['opcode']] |
| if 'opcodes' in insn: |
| opcodes = [] |
| for opcode in insn['opcodes']: |
| if re.match('^[0-9a-fA-F]{2}$', opcode): |
| opcodes.append('uint8_t{0x%s}' % opcode) |
| elif re.match('^[0-9a-fA-F]{4}$', opcode): |
| opcodes.append('uint16_t{0x%s}' % opcode) |
| elif re.match('^[0-9a-fA-F]{8}$', opcode): |
| opcodes.append('uint32_t{0x%s}' % opcode) |
| elif re.match('^[0-9a-fA-F]{4}_[0-9a-fA-F]{4}$', opcode): |
| opcodes.append('uint32_t{0x%s}' % re.sub('_', '\'', opcode)) |
| elif re.match('^[0-7]$', opcode): |
| opcodes.append('uint8_t{%s}' % opcode) |
| else: |
| assert False |
| insn['processed_opcodes'] = opcodes |
| print('void %s(%s) {' % (name, params), file=f) |
| if 'x86' in arch: |
| _gen_emit_shortcut(f, insn, insns) |
| _gen_emit_instruction(f, insn, arch) |
| print('}', file=f) |
| # If we have a memory operand (there may be at most one) then we also |
| # have a special x86-64 exclusive form which accepts Label (it can be |
| # emulated on x86-32, too, if needed). |
| if 'const Operand&' in params and 'x86' in arch: |
| print("", file=f) |
| print('void %s(%s) {' % ( |
| name, params.replace('const Operand&', 'const LabelOperand')), file=f) |
| _gen_emit_shortcut(f, insn, insns) |
| _gen_emit_instruction(f, insn, arch, rip_operand=True) |
| print('}\n', file=f) |
| if 'Rounding' in params: |
| print("", file=f) |
| print('void %s(%s) {' % ( |
| name, _get_params(insn, lambda arg: arg.get('class', '') == 'Rm')), file=f) |
| _gen_emit_instruction(f, insn, arch, dyn_rm=True) |
| print('}\n', file=f) |
| else: |
| print('void %s(%s);' % (name, params), file=f) |
| # If immediate type is integer then we want to prevent automatic |
| # conversions from integers of larger sizes. |
| if imm_type is not None and "int" in imm_type: |
| if template: |
| print(template[:-1] + ", typename ImmType>", file=f) |
| else: |
| print('template<typename ImmType>', file=f) |
| print(('auto %s(%s) -> ' |
| 'std::enable_if_t<std::is_integral_v<ImmType> && ' |
| 'sizeof(%s) < sizeof(ImmType)> = delete;') % ( |
| name, params.replace(imm_type, 'ImmType'), imm_type), file=f) |
| else: |
| print('void %s(%s) {' % (name, params), file=f); |
| if 'feature' in insn: |
| print(' SetRequiredFeature%s();' % insn['feature'], file=f) |
| print(' Instruction(%s);' % ', '.join( |
| ['"%s"' % insn.get('native-asm', name)] + |
| list(_gen_instruction_args(insn, arch))), file=f) |
| print('}', file=f) |
| |
| |
| def _gen_instruction_args(insn, arch): |
| arg_count = 0 |
| for arg in insn.get('args'): |
| if asm_defs.is_implicit_reg(arg.get('class')): |
| continue |
| if (_get_arg_type_name(arg, insn.get('type', None)) == 'Register' |
| and 'x86' in arch): |
| yield 'typename DerivedAssemblerType::%s(arg%d)' % ( |
| _ARGUMENT_FORMATS_TO_SIZES[arg['class']], arg_count) |
| else: |
| yield 'arg%d' % arg_count |
| arg_count += 1 |
| |
| |
| def _gen_emit_shortcut(f, insn, insns): |
| # If we have one 'Imm8' argument then it could be shift, try too see if |
| # ShiftByOne with the same arguments exist. |
| if asm_defs.exactly_one_of(arg['class'] == 'Imm8' for arg in insn['args']): |
| _gen_emit_shortcut_shift(f, insn, insns) |
| if asm_defs.exactly_one_of(arg['class'] in ('Imm16', 'Imm32') for arg in insn['args']): |
| if insn['asm'].endswith('Accumulator'): |
| _gen_emit_shortcut_accumulator_imm8(f, insn, insns) |
| else: |
| _gen_emit_shortcut_generic_imm8(f, insn, insns) |
| if len(insn['args']) > 1 and insn['args'][0]['class'].startswith('GeneralReg'): |
| _gen_emit_shortcut_accumulator(f, insn, insns) |
| |
| |
| def _gen_emit_shortcut_shift(f, insn, insns): |
| # Replace Imm8 argument with '1' argument. |
| non_imm_args = [arg for arg in insn['args'] if arg['class'] != 'Imm8'] |
| imm_arg_index = insn['args'].index({'class': 'Imm8'}) |
| for maybe_shift_by_1_insn in insns: |
| if not _is_insn_match(maybe_shift_by_1_insn, |
| insn['asm'] + 'ByOne', |
| non_imm_args): |
| continue |
| # Now call that version if immediate is 1. |
| args = [] |
| arg_count = 0 |
| for arg in non_imm_args: |
| if asm_defs.is_implicit_reg(arg['class']): |
| continue |
| args.append('arg%d' % arg_count) |
| arg_count += 1 |
| print(' if (arg%d == 1) return %sByOne(%s);' % ( |
| imm_arg_index, insn['asm'], ', '.join(args)), file=f) |
| |
| |
| def _gen_emit_shortcut_accumulator_imm8(f, insn, insns): |
| insn_name = insn['asm'][:-11] |
| args = insn['args'] |
| assert len(args) == 3 and args[2]['class'] == 'FLAGS' |
| acc_class = args[0]['class'] |
| # Note: AL is accumulator, too, but but imm is always 8-bit for it which means |
| # it shouldn't be encountered here and if it *does* appear here - it's an error |
| # and we should fail. |
| assert acc_class in ('AX', 'EAX', 'RAX') |
| greg_class = { |
| 'AX': 'GeneralReg16', |
| 'EAX': 'GeneralReg32', |
| 'RAX': 'GeneralReg64' |
| }[acc_class] |
| maybe_8bit_imm_args = [ |
| { 'class': greg_class, 'usage': args[0]['usage'] }, |
| { 'class': 'Imm8' }, |
| { 'class': 'FLAGS', 'usage': insn['args'][2]['usage'] } |
| ] |
| for maybe_imm8_insn in insns: |
| if not _is_insn_match(maybe_imm8_insn, |
| insn_name + 'Imm8', |
| maybe_8bit_imm_args): |
| continue |
| print(' if (IsInRange<int8_t>(arg0)) {', file=f) |
| print((' return %s(DerivedAssemblerType::Accumulator(), ' |
| 'static_cast<int8_t>(arg0));') % ( |
| maybe_imm8_insn['asm'],), file=f) |
| print(' }', file=f) |
| |
| def _gen_emit_shortcut_generic_imm8(f, insn, insns): |
| maybe_8bit_imm_args = [{ 'class': 'Imm8' } if arg['class'].startswith('Imm') else arg |
| for arg in insn['args']] |
| imm_arg_index = maybe_8bit_imm_args.index({'class': 'Imm8'}) |
| for maybe_imm8_insn in insns: |
| if not _is_insn_match(maybe_imm8_insn, |
| insn['asm'] + 'Imm8', |
| maybe_8bit_imm_args): |
| continue |
| # Now call that version if immediate fits into 8-bit. |
| arg_count = len(_get_params(insn).split(',')) |
| print(' if (IsInRange<int8_t>(arg%d)) {' % (arg_count - 1), file=f) |
| print(' return %s(%s);' % (maybe_imm8_insn['asm'], ', '.join( |
| ('static_cast<int8_t>(arg%d)' if n == arg_count - 1 else 'arg%d') % n |
| for n in range(arg_count))), file=f) |
| print(' }', file=f) |
| |
| |
| def _gen_emit_shortcut_accumulator(f, insn, insns): |
| accumulator_name = { |
| 'GeneralReg8': 'AL', |
| 'GeneralReg16': 'AX', |
| 'GeneralReg32': 'EAX', |
| 'GeneralReg64': 'RAX' |
| }[insn['args'][0]['class']] |
| maybe_accumulator_args = [ |
| { 'class': accumulator_name, 'usage': insn['args'][0]['usage']} |
| ] + insn['args'][1:] |
| for maybe_accumulator_insn in insns: |
| if not _is_insn_match(maybe_accumulator_insn, |
| insn['asm'] + 'Accumulator', |
| maybe_accumulator_args): |
| continue |
| # Now call that version if register is an Accumulator. |
| arg_count = len(_get_params(insn).split(',')) |
| print(' if (DerivedAssemblerType::IsAccumulator(arg0)) {', file=f) |
| print(' return %s(%s);' % ( |
| maybe_accumulator_insn['asm'], |
| ', '.join('arg%d' % n for n in range(1, arg_count))), file=f) |
| print('}', file=f) |
| |
| |
| def _is_insn_match(insn, expected_name, expected_args): |
| # Note: usually there are more than one instruction with the same name |
| # but different arguments because they could accept either GeneralReg |
| # or Memory or Immediate argument. |
| # Instructions: |
| # Addl %eax, $1 |
| # Addl (%eax), $1 |
| # Addl %eax, %eax |
| # Addl (%eax), %eax |
| # are all valid. |
| # |
| # Yet not all instruction have all kinds of optimizations: TEST only have |
| # version with accumulator (which is shorter than usual) - but does not |
| # have while version with short immediate. Imul have version with short |
| # immediate - but not version with accumulator. |
| # |
| # We want to ensure that we have the exact match - expected name plus |
| # expected arguments. |
| |
| return insn['asm'] == expected_name and insn['args'] == expected_args |
| |
| |
| _ARGUMENT_FORMATS_TO_SIZES = { |
| 'X87Reg' : 'RegisterDefaultBit', |
| 'Cond': '', |
| 'FpReg32' : 'VectorRegister128Bit', |
| 'FpReg64' : 'VectorRegister128Bit', |
| 'GeneralReg' : 'RegisterDefaultBit', |
| 'GeneralReg8' : 'Register8Bit', |
| 'GeneralReg16' : 'Register16Bit', |
| 'GeneralReg32' : 'Register32Bit', |
| 'GeneralReg64' : 'Register64Bit', |
| 'Imm2': '', |
| 'Imm8': '', |
| 'Imm16': '', |
| 'Imm32': '', |
| 'Imm64': '', |
| 'Mem': 'MemoryDefaultBit', |
| 'Mem8' : 'Memory8Bit', |
| 'Mem16' : 'Memory16Bit', |
| 'Mem32' : 'Memory32Bit', |
| 'Mem64' : 'Memory64Bit', |
| 'Mem128' : 'Memory128Bit', |
| 'MemX87': 'MemoryX87', |
| 'MemX8716': 'MemoryX8716Bit', |
| 'MemX8732': 'MemoryX8732Bit', |
| 'MemX8764': 'MemoryX8764Bit', |
| 'MemX8780': 'MemoryX8780Bit', |
| 'RegX87': 'X87Register', |
| 'XmmReg' : 'VectorRegister128Bit', |
| 'VecMem32': 'VectorMemory32Bit', |
| 'VecMem64': 'VectorMemory64Bit', |
| 'VecMem128': 'VectorMemory128Bit', |
| 'VecReg128' : 'VectorRegister128Bit' |
| } |
| |
| |
| # On x86-64 each instruction which accepts explicit memory operant (there may at most be one) |
| # can also accept $rip-relative addressing (where distance to operand is specified by 32-bit |
| # difference between end of instruction and operand address). |
| # |
| # We use it to support Label operands - only name of class is changed from MemoryXXX to LabelXXX, |
| # e.g. VectorMemory32Bit becomes VectorLabel32Bit. |
| # |
| # Note: on x86-32 that mode can also be emulated using regular instruction form, if needed. |
| def _gen_emit_instruction(f, insn, arch, rip_operand=False, dyn_rm=False): |
| result = [] |
| arg_count = 0 |
| for arg in insn['args']: |
| if asm_defs.is_implicit_reg(arg['class']): |
| continue |
| # Note: in RISC-V there is never any ambiguity about whether full register or its part is used. |
| # Instead size of operand is always encoded in the name, e.g. addw vs add or fadd.s vs fadd.d |
| if arch in ['common_riscv', 'rv32', 'rv64']: |
| if dyn_rm and arg['class'] == 'Rm': |
| result.append('Rounding::kDyn') |
| else: |
| result.append('arg%d' % arg_count) |
| else: |
| result.append('%s(arg%d)' % (_ARGUMENT_FORMATS_TO_SIZES[arg['class']], arg_count)) |
| arg_count += 1 |
| # If we want %rip--operand then we need to replace 'Memory' with 'Labal' |
| if rip_operand: |
| result = [arg.replace('Memory', 'Label') for arg in result] |
| print(' Emit%sInstruction<%s>(%s);' % ( |
| asm_defs._get_cxx_name(insn.get('type', '')), |
| ', '.join(insn['processed_opcodes']), |
| ', '.join(result)), file=f) |
| |
| |
| def _gen_memory_function_specializations_h(f, insns, arch): |
| for insn in insns: |
| # Only build additional definitions needed for memory access in LIR if there |
| # are memory arguments and instruction is intended for use in LIR |
| if not _contains_mem(insn) or insn.get('skip_lir'): |
| continue |
| template, _ = _get_template_name(insn) |
| params = _get_params(insn) |
| for addr_mode in ('Absolute', 'BaseDisp', 'IndexDisp', 'BaseIndexDisp'): |
| # Generate a function to expand a macro and emit a corresponding |
| # assembly instruction with a memory operand. |
| macro_name = asm_defs.get_mem_macro_name(insn, addr_mode) |
| incoming_args = [] |
| outgoing_args = [] |
| for i, arg in enumerate(insn.get('args')): |
| if asm_defs.is_implicit_reg(arg.get('class')): |
| continue |
| arg_name = 'arg%d' % (i) |
| if asm_defs.is_mem_op(arg.get('class')): |
| if addr_mode == 'Absolute': |
| incoming_args.append('int32_t %s' % (arg_name)) |
| outgoing_args.append('{.disp = %s}' % (arg_name)) |
| continue |
| mem_args = [] |
| if addr_mode in ('BaseDisp', 'BaseIndexDisp'): |
| mem_args.append(['Register', 'base', arg_name + '_base']) |
| if addr_mode in ('IndexDisp', 'BaseIndexDisp'): |
| mem_args.append(['Register', 'index', arg_name + '_index']) |
| mem_args.append(['ScaleFactor', 'scale', arg_name + '_scale']) |
| mem_args.append(['int32_t', 'disp', arg_name + '_disp']) |
| incoming_args.extend(['%s %s' % (pair[0], pair[2]) for pair in mem_args]) |
| outgoing_args.append('{%s}' % ( |
| ', '.join(['.%s = %s' % (pair[1], pair[2]) for pair in mem_args]))) |
| else: |
| incoming_args.append('%s %s' % (_get_arg_type_name(arg, None), arg_name)) |
| outgoing_args.append(arg_name) |
| if template: |
| print(template, file=f) |
| print('void %s(%s) {' % (macro_name, ', '.join(incoming_args)), file=f) |
| print(' %s(%s);' % (insn.get('asm'), ', '.join(outgoing_args)), file=f) |
| print('}', file=f) |
| |
| |
| def _is_for_asm(insn): |
| if insn.get('skip_asm'): |
| return False |
| return True |
| |
| |
| def _load_asm_defs(asm_def): |
| arch, insns = asm_defs.load_asm_defs(asm_def) |
| # Filter out explicitly disabled instructions. |
| return arch, [i for i in insns if _is_for_asm(i)] |
| |
| |
| def main(argv): |
| # Usage: gen_asm.py --binary-assembler|--text_assembler |
| # <assembler_common-inl.h> |
| # <assembler_<arch>-inl.h> |
| # ... |
| # <def_common> |
| # <def_arch> |
| # ... |
| |
| mode = argv[1] |
| assert len(argv) % 2 == 0 |
| filenames = argv[2:] |
| filename_pairs = ((filenames[i], filenames[len(filenames)//2 + i]) |
| for i in range(0, len(filenames)//2)) |
| |
| if mode == '--binary-assembler': |
| binary_assembler = True |
| elif mode == '--text-assembler': |
| binary_assembler = False |
| else: |
| assert False, 'unknown option %s' % (mode) |
| |
| for out_filename, input_filename in filename_pairs: |
| arch, loaded_defs = _load_asm_defs(input_filename) |
| with open(out_filename, 'w') as out_file: |
| _gen_generic_functions_h(out_file, loaded_defs, binary_assembler, arch) |
| if binary_assembler and arch is not None and 'x86' in arch: |
| _gen_memory_function_specializations_h(out_file, loaded_defs, arch) |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv)) |