1#!/usr/bin/python
2#
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18"""Generate assembler files out of the definition file."""
19
20import asm_defs
21import os
22import re
23import sys
24
25
26INDENT = '  '
27
28_imm_types = {
29    'Imm2': 'int8_t',
30    'Imm8': 'int8_t',
31    'Imm16': 'int16_t',
32    'Imm32': 'int32_t',
33    'Imm64': 'int64_t'
34}
35
36
37def _get_arg_type_name(arg):
38  cls = arg.get('class')
39  if asm_defs.is_x87reg(cls):
40    return 'X87Register'
41  if asm_defs.is_greg(cls):
42    return 'Register'
43  if asm_defs.is_xreg(cls):
44    return 'XMMRegister'
45  if asm_defs.is_imm(cls):
46    return _imm_types[cls]
47  if asm_defs.is_disp(cls):
48    return 'int32_t'
49  if asm_defs.is_label(cls):
50    return 'const Label&'
51  if asm_defs.is_cond(cls):
52    return 'Condition'
53  if asm_defs.is_mem_op(cls):
54    return 'const Operand&'
55  raise Exception('class %s is not supported' % (cls))
56
57
58def _get_immediate_type(insn):
59  imm_type = None
60  for arg in insn.get('args'):
61    cls = arg.get('class')
62    if asm_defs.is_imm(cls):
63      assert imm_type is None
64      imm_type = _imm_types[cls]
65  return imm_type
66
67
68def _get_params(insn):
69  result = []
70  arg_count = 0
71  for arg in insn.get('args'):
72    if asm_defs.is_implicit_reg(arg.get('class')):
73      continue
74    result.append("%s arg%d" % (_get_arg_type_name(arg), arg_count))
75    arg_count += 1
76  return ', '.join(result)
77
78
79def _contains_mem(insn):
80  return any(asm_defs.is_mem_op(arg['class']) for arg in insn.get('args'))
81
82
83def _get_template_name(insn):
84  name = insn.get('asm')
85  if '<' not in name:
86    return None, name
87  return 'template <%s>' % ', '.join(
88      'bool' if param.strip() in ('true', 'false') else
89      'typename' if re.search('[_a-zA-Z]', param) else 'int'
90      for param in name.split('<',1)[1][:-1].split(',')), name.split('<')[0]
91
92
93def _gen_generic_functions_h(f, insns, binary_assembler):
94  template_names = set()
95  for insn in insns:
96    template, name = _get_template_name(insn)
97    params = _get_params(insn)
98    imm_type = _get_immediate_type(insn)
99    if template:
100      # We could only describe each template function once, or that would be
101      # compilation error.  Yet functions with the same name but different
102      # arguments are different (e.g. MacroVTbl<15> and MacroVTbl<23>).
103      # But different types of arguments could map to the same C++ type.
104      # For example MacroCmpFloat<Float32> and MacroCmpFloat<Float64> have
105      # different IR arguments (FpReg32 vs FpReg64), but both map to the
106      # same C++ type: XMMRegister.
107      #
108      # Use function name + parameters (as described by _get_params) to get
109      # full description of template function.
110      template_name = str({
111          'name': name,
112          'params': _get_params(insn)
113      })
114      if template_name in template_names:
115        continue
116      template_names.add(template_name)
117      print(template, file=f)
118    # If this is binary assembler then we only generate header and then actual
119    # implementation is written manually.
120    #
121    # Text assembled passes "real" work down to GNU as, this works fine with
122    # just a simple generic implementation.
123    if binary_assembler:
124      if 'opcodes' in insn:
125        print('void %s(%s) {' % (name, params), file=f)
126        _gen_emit_shortcut(f, insn, insns)
127        _gen_emit_instruction(f, insn)
128        print('}', file=f)
129        # If we have a memory operand (there may be at most one) then we also
130        # have a special x86-64 exclusive form which accepts Label (it can be
131        # emulated on x86-32, too, if needed).
132        if 'const Operand&' in params:
133          print("", file=f)
134          print('void %s(%s) {' % (
135              name, params.replace('const Operand&', 'const LabelOperand')), file=f)
136          _gen_emit_shortcut(f, insn, insns)
137          _gen_emit_instruction(f, insn, rip_operand=True)
138          print('}\n', file=f)
139      else:
140        print('void %s(%s);' % (name, params), file=f)
141      if imm_type is not None:
142        if template:
143          print(template[:-1] + ", typename ImmType>", file=f)
144        else:
145          print('template<typename ImmType>', file=f)
146        print(('auto %s(%s) -> '
147                    'std::enable_if_t<std::is_integral_v<ImmType> && '
148                    'sizeof(%s) < sizeof(ImmType)> = delete;') % (
149                        name, params.replace(imm_type, 'ImmType'), imm_type), file=f)
150    else:
151      print('void %s(%s) {' % (name, params), file=f);
152      if 'feature' in insn:
153        print('  SetRequiredFeature%s();' % insn['feature'], file=f)
154      print('  Instruction(%s);' % ', '.join(
155          ['"%s"' % name] + list(_gen_instruction_args(insn))), file=f)
156      print('}', file=f)
157
158
159def _gen_instruction_args(insn):
160  arg_count = 0
161  for arg in insn.get('args'):
162    if asm_defs.is_implicit_reg(arg.get('class')):
163      continue
164    if _get_arg_type_name(arg) == 'Register':
165      yield 'typename Assembler::%s(arg%d)' % (
166          _ARGUMENT_FORMATS_TO_SIZES[arg['class']], arg_count)
167    else:
168      yield 'arg%d' % arg_count
169    arg_count += 1
170
171
172def _gen_emit_shortcut(f, insn, insns):
173  # If we have one 'Imm8' argument then it could be shift, try too see if
174  # ShiftByOne with the same arguments exist.
175  if asm_defs.exactly_one_of(arg['class'] == 'Imm8' for arg in insn['args']):
176    _gen_emit_shortcut_shift(f, insn, insns)
177  if asm_defs.exactly_one_of(arg['class'] in ('Imm16', 'Imm32') for arg in insn['args']):
178    if insn['asm'].endswith('Accumulator'):
179      _gen_emit_shortcut_accumulator_imm8(f, insn, insns)
180    else:
181      _gen_emit_shortcut_generic_imm8(f, insn, insns)
182  if len(insn['args']) > 1 and insn['args'][0]['class'].startswith('GeneralReg'):
183    _gen_emit_shortcut_accumulator(f, insn, insns)
184
185
186def _gen_emit_shortcut_shift(f, insn, insns):
187  # Replace Imm8 argument with '1' argument.
188  non_imm_args = [arg for arg in insn['args'] if arg['class'] != 'Imm8']
189  imm_arg_index = insn['args'].index({'class': 'Imm8'})
190  for maybe_shift_by_1_insn in insns:
191    if not _is_insn_match(maybe_shift_by_1_insn,
192                          insn['asm'] + 'ByOne',
193                          non_imm_args):
194      continue
195    # Now call that version if immediate is 1.
196    args = []
197    arg_count = 0
198    for arg in non_imm_args:
199      if asm_defs.is_implicit_reg(arg['class']):
200        continue
201      args.append('arg%d' % arg_count)
202      arg_count += 1
203    print('  if (arg%d == 1) return %sByOne(%s);' % (
204        imm_arg_index, insn['asm'], ', '.join(args)), file=f)
205
206
207def _gen_emit_shortcut_accumulator_imm8(f, insn, insns):
208  insn_name = insn['asm'][:-11]
209  args = insn['args']
210  assert len(args) == 3 and args[2]['class'] == 'FLAGS'
211  acc_class = args[0]['class']
212  # Note: AL is accumulator, too, but but imm is always 8-bit for it which means
213  # it shouldn't be encountered here and if it *does* appear here - it's an error
214  # and we should fail.
215  assert acc_class in ('AX', 'EAX', 'RAX')
216  greg_class = {
217      'AX': 'GeneralReg16',
218      'EAX': 'GeneralReg32',
219      'RAX': 'GeneralReg64'
220  }[acc_class]
221  maybe_8bit_imm_args = [
222    { 'class': greg_class, 'usage': args[0]['usage'] },
223    { 'class': 'Imm8' },
224    { 'class': 'FLAGS', 'usage': insn['args'][2]['usage'] }
225  ]
226  for maybe_imm8_insn in insns:
227    if not _is_insn_match(maybe_imm8_insn,
228                          insn_name + 'Imm8',
229                          maybe_8bit_imm_args):
230      continue
231    print('  if (IsInRange<int8_t>(arg0)) {', file=f)
232    print(('    return %s(Assembler::Accumulator(), '
233                 'static_cast<int8_t>(arg0));') % (
234                     maybe_imm8_insn['asm'],), file=f)
235    print('  }', file=f)
236
237def _gen_emit_shortcut_generic_imm8(f, insn, insns):
238  maybe_8bit_imm_args = [{ 'class': 'Imm8' } if arg['class'].startswith('Imm') else arg
239                         for arg in insn['args']]
240  imm_arg_index = maybe_8bit_imm_args.index({'class': 'Imm8'})
241  for maybe_imm8_insn in insns:
242    if not _is_insn_match(maybe_imm8_insn,
243                          insn['asm'] + 'Imm8',
244                          maybe_8bit_imm_args):
245      continue
246    # Now call that version if immediate fits into 8-bit.
247    arg_count = len(_get_params(insn).split(','))
248    print('  if (IsInRange<int8_t>(arg%d)) {' % (arg_count - 1), file=f)
249    print('   return %s(%s);' % (maybe_imm8_insn['asm'], ', '.join(
250        ('static_cast<int8_t>(arg%d)' if n == arg_count - 1 else 'arg%d') % n
251        for n in range(arg_count))), file=f)
252    print('  }', file=f)
253
254
255def _gen_emit_shortcut_accumulator(f, insn, insns):
256  accumulator_name = {
257      'GeneralReg8': 'AL',
258      'GeneralReg16': 'AX',
259      'GeneralReg32': 'EAX',
260      'GeneralReg64': 'RAX'
261  }[insn['args'][0]['class']]
262  maybe_accumulator_args = [
263      { 'class': accumulator_name, 'usage': insn['args'][0]['usage']}
264  ] + insn['args'][1:]
265  for maybe_accumulator_insn in insns:
266    if not _is_insn_match(maybe_accumulator_insn,
267                          insn['asm'] + 'Accumulator',
268                          maybe_accumulator_args):
269      continue
270    # Now call that version if register is an Accumulator.
271    arg_count = len(_get_params(insn).split(','))
272    print('  if (Assembler::IsAccumulator(arg0)) {', file=f)
273    print('  return %s(%s);' % (
274      maybe_accumulator_insn['asm'],
275      ', '.join('arg%d' % n for n in range(1, arg_count))), file=f)
276    print('}', file=f)
277
278
279def _is_insn_match(insn, expected_name, expected_args):
280  # Note: usually there are more than one instruction with the same name
281  # but different arguments because they could accept either GeneralReg
282  # or Memory or Immediate argument.
283  #   Instructions:
284  #     Addl %eax, $1
285  #     Addl (%eax), $1
286  #     Addl %eax, %eax
287  #     Addl (%eax), %eax
288  #   are all valid.
289  #
290  # Yet not all instruction have all kinds of optimizations: TEST only have
291  # version with accumulator (which is shorter than usual) - but does not
292  # have while version with short immediate. Imul have version with short
293  # immediate - but not version with accumulator.
294  #
295  # We want to ensure that we have the exact match - expected name plus
296  # expected arguments.
297
298  return insn['asm'] == expected_name and insn['args'] == expected_args
299
300
301_ARGUMENT_FORMATS_TO_SIZES = {
302  'X87Reg' : 'RegisterDefaultBit',
303  'Cond': '',
304  'FpReg32' : 'VectorRegister128Bit',
305  'FpReg64' : 'VectorRegister128Bit',
306  'GeneralReg' : 'RegisterDefaultBit',
307  'GeneralReg8' : 'Register8Bit',
308  'GeneralReg16' : 'Register16Bit',
309  'GeneralReg32' : 'Register32Bit',
310  'GeneralReg64' : 'Register64Bit',
311  'Imm2': '',
312  'Imm8': '',
313  'Imm16': '',
314  'Imm32': '',
315  'Imm64': '',
316  'Mem8' : 'Memory8Bit',
317  'Mem16' : 'Memory16Bit',
318  'Mem32' : 'Memory32Bit',
319  'Mem64' : 'Memory64Bit',
320  'Mem128' : 'Memory128Bit',
321  'MemX87': 'MemoryX87',
322  'MemX8716': 'MemoryX8716Bit',
323  'MemX8732': 'MemoryX8732Bit',
324  'MemX8764': 'MemoryX8764Bit',
325  'MemX8780': 'MemoryX8780Bit',
326  'RegX87': 'X87Register',
327  'XmmReg' : 'VectorRegister128Bit',
328  'VecMem32': 'VectorMemory32Bit',
329  'VecMem64': 'VectorMemory64Bit',
330  'VecMem128': 'VectorMemory128Bit',
331  'VecReg128' : 'VectorRegister128Bit'
332}
333
334
335# On x86-64 each instruction which accepts explicit memory operant (there may at most be one)
336# can also accept $rip-relative addressing (where distance to operand is specified by 32-bit
337# difference between end of instruction and operand address).
338#
339# We use it to support Label operands - only name of class is changed from MemoryXXX to LabelXXX,
340# e.g. VectorMemory32Bit becomes VectorLabel32Bit.
341#
342# Note: on x86-32 that mode can also be emulated using regular instruction form, if needed.
343def _gen_emit_instruction(f, insn, rip_operand=False):
344  result = []
345  arg_count = 0
346  for arg in insn['args']:
347    if asm_defs.is_implicit_reg(arg['class']):
348      continue
349    result.append('%s(arg%d)' % (_ARGUMENT_FORMATS_TO_SIZES[arg['class']], arg_count))
350    arg_count += 1
351  if insn.get('reg_to_rm', False):
352    result[0], result[1] = result[1], result[0]
353  if insn.get('rm_to_vex', False):
354    result[0], result[1] = result[1], result[0]
355  if insn.get('vex_imm_rm_to_reg', False):
356    result[0], result[1], result[2], result[3] = result[0], result[3], result[1], result[2]
357  if insn.get('vex_rm_imm_to_reg', False):
358    result[0], result[1], result[2], result[3] = result[0], result[2], result[1], result[3]
359  # If we want %rip--operand then we need to replace 'Memory' with 'Labal'
360  if rip_operand:
361    result = [arg.replace('Memory', 'Label') for arg in result]
362  # If vex operand is one of first 8 registers and rm operand is not then swapping these two
363  # operands produces more compact encoding.
364  # This only works with commutative instructions from first opcode map.
365  if ((insn.get('is_optimizable_using_commutation', False) and
366    # Note: we may only swap arguments if they have the same type.
367    # E.g. if one is memory and the other is register then we couldn't swap them.
368    result[0].split('(')[0] == result[2].split('(')[0])):
369    assert insn.get('vex_rm_to_reg', False)
370    print('  if (Assembler::IsSwapProfitable(%s, %s)) {' % (result[2], result[1]), file=f)
371    print('    return EmitInstruction<Opcodes<%s>>(%s);' % (
372        ', '.join('0x%02x' % int(opcode, 16) for opcode in insn['opcodes']),
373        ', '.join(result)), file=f)
374    print('  }', file=f)
375  if insn.get('vex_rm_to_reg', False):
376    result[0], result[1], result[2] = result[0], result[2], result[1]
377  print('  EmitInstruction<Opcodes<%s>>(%s);' % (
378      ', '.join('0x%02x' % int(opcode, 16) for opcode in insn['opcodes']),
379      ', '.join(result)), file=f)
380
381
382def _gen_memory_function_specializations_h(f, insns):
383  for insn in insns:
384    # Only build additional definitions needed for memory access in LIR if there
385    # are memory arguments and instruction is intended for use in LIR
386    if not _contains_mem(insn) or insn.get('skip_lir'):
387      continue
388    template, _ = _get_template_name(insn)
389    params = _get_params(insn)
390    for addr_mode in ('Absolute', 'BaseDisp', 'IndexDisp', 'BaseIndexDisp'):
391      # Generate a function to expand a macro and emit a corresponding
392      # assembly instruction with a memory operand.
393      macro_name = asm_defs.get_mem_macro_name(insn, addr_mode)
394      incoming_args = []
395      outgoing_args = []
396      for i, arg in enumerate(insn.get('args')):
397        if asm_defs.is_implicit_reg(arg.get('class')):
398          continue
399        arg_name = 'arg%d' % (i)
400        if asm_defs.is_mem_op(arg.get('class')):
401          if addr_mode == 'Absolute':
402            incoming_args.append('int32_t %s' % (arg_name))
403            outgoing_args.append('{.disp = %s}' % (arg_name))
404            continue
405          mem_args = []
406          if addr_mode in ('BaseDisp', 'BaseIndexDisp'):
407            mem_args.append(['Register', 'base', arg_name + '_base'])
408          if addr_mode in ('IndexDisp', 'BaseIndexDisp'):
409            mem_args.append(['Register', 'index', arg_name + '_index'])
410            mem_args.append(['ScaleFactor', 'scale', arg_name + '_scale'])
411          mem_args.append(['int32_t', 'disp', arg_name + '_disp'])
412          incoming_args.extend(['%s %s' % (pair[0], pair[2]) for pair in mem_args])
413          outgoing_args.append('{%s}' % (
414              ', '.join(['.%s = %s' % (pair[1], pair[2]) for pair in mem_args])))
415        else:
416          incoming_args.append('%s %s' % (_get_arg_type_name(arg), arg_name))
417          outgoing_args.append(arg_name)
418      if template:
419        print(template, file=f)
420      print('void %s(%s) {' % (macro_name, ', '.join(incoming_args)), file=f)
421      print('  %s(%s);' % (insn.get('asm'), ', '.join(outgoing_args)), file=f)
422      print('}', file=f)
423
424
425def _is_for_asm(insn):
426  if insn.get('skip_asm'):
427    return False
428  return True
429
430
431def _load_asm_defs(asm_def):
432  _, insns = asm_defs.load_asm_defs(asm_def)
433  # Filter out explicitly disabled instructions.
434  return [i for i in insns if _is_for_asm(i)]
435
436
437def main(argv):
438  # Usage: gen_asm.py --binary-assembler|--text_assembler
439  #                   <assembler_common-inl.h>
440  #                   <assembler_<arch>-inl.h>
441  #                   ...
442  #                   <def_common>
443  #                   <def_arch>
444  #                   ...
445
446  mode = argv[1]
447  assert len(argv) % 2 == 0
448  filenames = argv[2:]
449  filename_pairs = ((filenames[i], filenames[len(filenames)//2 + i])
450                    for i in range(0, len(filenames)//2))
451
452  if mode == '--binary-assembler':
453    binary_assembler = True
454  elif mode == '--text-assembler':
455    binary_assembler = False
456  else:
457    assert False, 'unknown option %s' % (mode)
458
459  for out_filename, input_filename in filename_pairs:
460    loaded_defs = _load_asm_defs(input_filename)
461    with open(out_filename, 'w') as out_file:
462      _gen_generic_functions_h(out_file, loaded_defs, binary_assembler)
463      if binary_assembler:
464        _gen_memory_function_specializations_h(out_file, loaded_defs)
465
466if __name__ == '__main__':
467  sys.exit(main(sys.argv))
468