#!/usr/bin/env python

"""Generate skeletal functions with a variety .cfi_ directives.
The purpose is to produce object-file test inputs to lld with a
variety of compact unwind encodings.
"""
from __future__ import print_function
import random
import argparse
import string
from math import factorial
from itertools import permutations

lsda_n = 0
lsda_odds = 0.0
func_size_low = 0x10
func_size_high = 0x100
saved_regs = ["%r15", "%r14", "%r13", "%r12", "%rbx"]
saved_regs_combined = list(list(permutations(saved_regs, i))
                           for i in range(0,6))

def print_function(name):
  global lsda_odds
  have_lsda = (random.random() < lsda_odds)
  frame_size = random.randint(4, 64) * 16
  frame_offset = -random.randint(0, (frame_size/16 - 4)) * 16
  reg_count = random.randint(0, 5)
  reg_combo = random.randint(0, factorial(reg_count) - 1)
  regs_saved = saved_regs_combined[reg_count][reg_combo]
  global func_size_low, func_size_high
  func_size = random.randint(func_size_low, func_size_high) * 0x10
  func_size_high += 1
  if func_size_high % 0x10 == 0:
    func_size_low += 1

  print("""\
### %s regs=%d frame=%d lsda=%s size=%d
    .section __TEXT,__text,regular,pure_instructions
    .p2align 4, 0x90
    .globl %s
%s:
    .cfi_startproc""" % (
        name, reg_count, frame_size, have_lsda, func_size, name, name))
  if have_lsda:
    global lsda_n
    lsda_n += 1
    print("""\
    .cfi_personality 155, ___gxx_personality_v0
    .cfi_lsda 16, Lexception%d""" % lsda_n)
  print("""\
    pushq %%rbp
    .cfi_def_cfa_offset %d
    .cfi_offset %%rbp, %d
    movq %%rsp, %%rbp
    .cfi_def_cfa_register %%rbp""" % (frame_size, frame_offset + 6*8))
  for i in range(reg_count):
    print(".cfi_offset %s, %d" % (regs_saved[i], frame_offset+(i*8)))
  print("""\
    .fill %d
    popq %%rbp
    retq
    .cfi_endproc
""" % (func_size - 6))

  if have_lsda:
    print("""\
    .section __TEXT,__gcc_except_tab
    .p2align 2
Lexception%d:
    .space 0x10
""" % lsda_n)
  return func_size

def random_seed():
  """Generate a seed that can easily be passsed back in via --seed=STRING"""
  return ''.join(random.choice(string.ascii_lowercase) for i in range(10))

def main():
  parser = argparse.ArgumentParser(
    description=__doc__,
    epilog="""\
Function sizes begin small then monotonically increase.  The goal is
to produce early pages that are full and later pages that are less
than full, in order to test handling for both cases.  Full pages
contain the maximum of 1021 compact unwind entries for a total page
size = 4 KiB.

Use --pages=N or --functions=N to control the size of the output.
Default is --pages=2, meaning produce at least two full pages of
compact unwind entries, plus some more. The calculatation is sloppy.
""")
  parser.add_argument('--seed', type=str, default=random_seed(),
                      help='Seed the random number generator')
  parser.add_argument('--pages', type=int, default=2,
                      help='Number of compact-unwind pages')
  parser.add_argument('--functions', type=int, default=None,
                      help='Number of functions to generate')
  parser.add_argument('--encodings', type=int, default=127,
                      help='Maximum number of unique unwind encodings (default = 127)')
  parser.add_argument('--lsda', type=int, default=0,
                      help='Percentage of functions with personality & LSDA (default = 10')
  args = parser.parse_args()
  random.seed(args.seed)
  p2align = 14
  global lsda_odds
  lsda_odds = args.lsda / 100.0

  print("""\
### seed=%s lsda=%f p2align=%d
    .section __TEXT,__text,regular,pure_instructions
    .p2align %d, 0x90
""" % (args.seed, lsda_odds, p2align, p2align))

  size = 0
  base = (1 << p2align)
  if args.functions:
    for n in range(args.functions):
      size += print_function("x%08x" % (size+base))
  else:
    while size < (args.pages << 24):
      size += print_function("x%08x" % (size+base))

  print("""\
    .section __TEXT,__text,regular,pure_instructions
    .globl _main
    .p2align 4, 0x90
_main:
    retq

    .p2align 4, 0x90
___gxx_personality_v0:
    retq
""")


if __name__ == '__main__':
  main()
