#! /usr/bin/python

# Copyright 2018 Google LLC
#
# 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
#
#     https://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.

import random
import re
import subprocess

def call(args):
  p = subprocess.Popen(args, stdout=subprocess.PIPE)
  o, e = p.communicate('')
  return o

def arm64_assemble(instruction):
  output = ''
  
  with open('/tmp/test.asm', 'w') as tmp:
    tmp.write('{}\n'.format(instruction))
    tmp.write('\n\n')

  call(['aarch64-linux-gnu-as', '/tmp/test.asm', '-o', '/tmp/test'])
  objdump = call(['aarch64-linux-gnu-objdump', '-d', '/tmp/test'])

  match = re.search('[0-9a-f]+\:\s+([0-9a-f]+)\s+([^\n]+)', objdump)
  code = match.group(1)
  print code
  while len(code) >= 2:
    output += '{}'.format(code[:2])
    code = code[2:]

  call(['rm', '/tmp/test.asm', '/tmp/test'])

  return output

def write_header(file):
  file.write('''// Copyright 2018 Google LLC
//
// 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
//
//     https://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.

#include <cstdio>
#include <random>

#include "gtest/gtest.h"

#include "reil/aarch64.h"

namespace reil {
namespace test {

// NOTE: This file is generated by aarch64_decoder_test_generator.py - please 
// add new tests there and regenerate this file instead of modifying this file 
// directly.

''')

def write_footer(file):
  file.write('''}  // namespace test
}  // namespace reil

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
''')

def write_testcase(file, testcase):
  print '[*]', testcase['name']

  testcase['instruction_dword'] = arm64_assemble(testcase['instruction'])

  file.write('''TEST(AArch64Decoder, {name}) {{
  uint32_t opcode = 0x{instruction_dword};
  aarch64::decoder::Instruction insn = aarch64::decoder::DecodeInstruction(0x1000, opcode);

  std::cerr << insn << std::endl;

'''.format(**testcase))

  for check in testcase['checks']:
    file.write('  ASSERT_EQ({}, {});\n'.format(check[0], check[1]))

  file.write('}\n\n')


def test(name, instruction, checks=[]):
  return {
    'name': name,
    'instruction': instruction,
    'checks': checks
  }

testcases = [
  test('Adr', 'adr  x0, c', [
      ('insn.opcode', 'aarch64::decoder::kAdr'),
    ]),
  test('AddImmediate', 'add x29, sp, #0x23', [
      ('insn.opcode', 'aarch64::decoder::kAddImmediate'),
    ]),
  test('LdaxrBug1', 'ldaxr x8, [x20]', [
      ('insn.opcode', 'aarch64::decoder::kLdaxr'),
    ]),
  test('SimdLdrLiteral', 'ldr q0, #0', [
      ('insn.opcode', 'aarch64::decoder::kSimdLdrLiteral'),
    ]),
  test('SimdLdp', 'ldp q0, q1, [x0]', [
      ('insn.opcode', 'aarch64::decoder::kSimdLdp'),
    ]),
  test('SimdLdnp', 'ldnp q0, q1, [x0]', [
      ('insn.opcode', 'aarch64::decoder::kSimdLdnp'),
    ]),
  test('SimdStp', 'stp q0, q1, [x0]', [
      ('insn.opcode', 'aarch64::decoder::kSimdStp'),
    ]),
  test('SimdStnp', 'stnp q0, q1, [x0]', [
      ('insn.opcode', 'aarch64::decoder::kSimdStnp'),
    ]),
  test('SimdLdrUnscaledImmediatePostindex', 'ldr b0, [x0], #1', [
      ('insn.opcode', 'aarch64::decoder::kSimdLdr'),
      ('insn.operands.size()', '2'),
      ('absl::get<aarch64::decoder::Register>(insn.operands[0]).size', '8'),
      ('absl::get<aarch64::decoder::Register>(insn.operands[0]).name', 'aarch64::decoder::Register::kV0'),
    ]),
  test('SimdStrUnscaledImmediatePostindex', 'str q31, [x0], #1', [
      ('insn.opcode', 'aarch64::decoder::kSimdStr'),
      ('insn.operands.size()', '2'),
      ('absl::get<aarch64::decoder::Register>(insn.operands[0]).size', '128'),
      ('absl::get<aarch64::decoder::Register>(insn.operands[0]).name', 'aarch64::decoder::Register::kV31'),
  ]),
]

def main():
  with open('aarch64/decoder_test.cpp', 'w') as tmp:
    write_header(tmp)
    for testcase in testcases:
      write_testcase(tmp, testcase)
    write_footer(tmp)
  call(['clang-format', '-i', '-style=Google', 'aarch64/decoder_test.cpp'])


if __name__ == '__main__':
  main()