
#!/usr/bin/env python

# Auto-generates an exhaustive and repetitive test for correct bundle-locked
# alignment on x86.
# For every possible offset in an aligned bundle, a bundle-locked group of every
# size in the inclusive range [1, bundle_size] is inserted. An appropriate CHECK
# is added to verify that NOP padding occurred (or did not occur) as expected.
# Run with --align-to-end to generate a similar test with align_to_end for each
# .bundle_lock directive.

# This script runs with Python 2.7 and 3.2+

from __future__ import print_function
import argparse

BUNDLE_SIZE_POW2 = 4
BUNDLE_SIZE = 2 ** BUNDLE_SIZE_POW2

PREAMBLE = '''
# RUN: llvm-mc -filetype=obj -triple i386-pc-linux-gnu %s -o - \\
# RUN:   | llvm-objdump -triple i386 -disassemble -no-show-raw-insn - | FileCheck %s

# !!! This test is auto-generated from utils/testgen/mc-bundling-x86-gen.py !!!
#     It tests that bundle-aligned grouping works correctly in MC. Read the
#     source of the script for more details.

  .text
  .bundle_align_mode {0}
'''.format(BUNDLE_SIZE_POW2).lstrip()

ALIGNTO = '  .align {0}, 0x90'
NOPFILL = '  .fill {0}, 1, 0x90'

def print_bundle_locked_sequence(len, align_to_end=False):
  print('  .bundle_lock{0}'.format(' align_to_end' if align_to_end else ''))
  print('  .rept {0}'.format(len))
  print('  inc %eax')
  print('  .endr')
  print('  .bundle_unlock')

def generate(align_to_end=False):
  print(PREAMBLE)

  ntest = 0
  for instlen in range(1, BUNDLE_SIZE + 1):
    for offset in range(0, BUNDLE_SIZE):
      # Spread out all the instructions to not worry about cross-bundle
      # interference.
      print(ALIGNTO.format(2 * BUNDLE_SIZE))
      print('INSTRLEN_{0}_OFFSET_{1}:'.format(instlen, offset))
      if offset > 0:
        print(NOPFILL.format(offset))
      print_bundle_locked_sequence(instlen, align_to_end)

      # Now generate an appropriate CHECK line
      base_offset = ntest * 2 * BUNDLE_SIZE
      inst_orig_offset = base_offset + offset  # had it not been padded...

      def print_check(adjusted_offset=None, nop_split_offset=None):
        if adjusted_offset is not None:
          print('# CHECK: {0:x}: nop'.format(inst_orig_offset))
          if nop_split_offset is not None:
            print('# CHECK: {0:x}: nop'.format(nop_split_offset))
          print('# CHECK: {0:x}: incl'.format(adjusted_offset))
        else:
          print('# CHECK: {0:x}: incl'.format(inst_orig_offset))

      if align_to_end:
        if offset + instlen == BUNDLE_SIZE:
          # No padding needed
          print_check()
        elif offset + instlen < BUNDLE_SIZE:
          # Pad to end at nearest bundle boundary
          offset_to_end = base_offset + (BUNDLE_SIZE - instlen)
          print_check(offset_to_end)
        else: # offset + instlen > BUNDLE_SIZE
          # Pad to end at next bundle boundary, splitting the nop sequence
          # at the nearest bundle boundary
          offset_to_nearest_bundle = base_offset + BUNDLE_SIZE
          offset_to_end = base_offset + (BUNDLE_SIZE * 2 - instlen)
          if offset_to_nearest_bundle == offset_to_end:
            offset_to_nearest_bundle = None
          print_check(offset_to_end, offset_to_nearest_bundle)
      else:
        if offset + instlen > BUNDLE_SIZE:
          # Padding needed
          aligned_offset = (inst_orig_offset + instlen) & ~(BUNDLE_SIZE - 1)
          print_check(aligned_offset)
        else:
          # No padding needed
          print_check()

      print()
      ntest += 1

if __name__ == '__main__':
  argparser = argparse.ArgumentParser()
  argparser.add_argument('--align-to-end',
                         action='store_true',
                         help='generate .bundle_lock with align_to_end option')
  args = argparser.parse_args()
  generate(align_to_end=args.align_to_end)
