#! /usr/bin/env python3

# Copyright 2021 syzkaller project authors. All rights reserved.
# Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

import re
import os
import sys
import pprint
import subprocess

pp = pprint.PrettyPrinter(indent = 0, compact = True, width = 300)
pe = pprint.PrettyPrinter(indent = 0, compact = True, width = 300, stream = sys.stderr)

def read_file(fname):
	if not os.access(fname, os.O_RDONLY):
		return []
	f = open(fname, 'r')
	ret = f.read()
	ret = ret.split("\n")[:-1]
	f.close()
	return ret

# Returns map<str, str>:  'layoutxxivbform': ([6, 5, 5, 5, 5, 2, 1, 1, 1, 1],
#	[None, 'T', A', 'B', 'C', None, 'CX', 'AX', 'BX'm 'TX'])
def get_layouts(layout_file):
	layout_content = read_file(layout_file)
	last_comment = ""
	layouts = {}
	for cur in layout_content:
		if len(cur) > 0 and cur[0] == '%':
			last_comment = re.sub(r'(^%|bits.*$)', "", cur).strip()
			continue
		l = re.match(r'\\newcommand{\\(layout\w+)}.*{', cur)
		if l:
			a = last_comment.split(" ")
			pos = []
			names = []
			for a1 in a:
				tmp = re.match(r'(\d+)<(\S+)>', a1)
				if tmp:
					pos += [int(tmp.group(1), 10)]
					names += [tmp.group(2)]
					continue
				pos += [int(a1, 10)]
				names += [None]
			layouts[l.group(1)] = (pos, names)
#	pe.pprint(layouts)
	return layouts

# Expands names of fields
# (list<n>, list<n>), string -> list<n>
def complete_layout(layout, insn_layout):
	bb = []
	b_ = 0
	b = re.findall(r'{([^}]*)}', insn_layout)
	for i in range(len(layout[0])):
		if layout[1][i]:
			bb += [layout[1][i]]
			continue

		# "pnop" is special: {any value\textsuperscript{*}
		tmpname = re.sub(r'([{}?]+|any value\\textsuperscript{\*)', "", b[b_])
		tmpname = re.sub(r'/+', '/', tmpname)
		bb += [tmpname]
		b_ += 1
	return bb

# Finds instructions in a latex file
# Returns map<str, list>:
# 'addc.': [([6, 5, 5, 5, 1, 9, 1], ['31', 'RT', 'RA', 'RB', 'OE', '10', 'Rc'], [-1, -1, -1, -1, 0, -1, 1])],
# layouts: array of tuples
def find_insns(tex_file, layouts):

	def add_insns(insn_list, layout):
		if len(layout) != 1 and len(layout) != 2:
			pe.pprint("!!!Error: broken layout {} for {}".format(layout, insn_list))
			sys.exit(-1)
		r = {}
		for ins in insn_list:
			tmp = ins.split(" ", 1)

			ll = []
			for l in layout:
				par = []
				for j in range(len(l[1])):
					defval = -1
					# This is dealing with OE/Rc from "addc. RT,RA,RB (OE=0 Rc=1)"
					if len(tmp) > 1 and ('?' not in l[1][j]):
						ptmp = re.match(r'.*{}=(\d).*'.format(l[1][j]), tmp[1])
						if ptmp:
							defval = int(ptmp.group(1), 10)
					par += [defval]
				ll += [(l[0], l[1], par)]
			pe.pprint("{}".format(tmp[0]))
			r[tmp[0]] = ll
		return r

	tex_content = read_file(tex_file)
	ret = {}
	layout = []
	insn_list = []
	for cur in tex_content:
		# \instrsyntax{pmxvf16ger2np AT,XA,XB,XMSK,YMSK,PMSK}
		l = re.match(r'\\instrsyntax{(.*)}', cur)
		if l:
			if insn_list != [] and layout != []:
				ret.update(add_insns(insn_list, layout))
				insn_list = []
				layout = []

			insn_list += [l.group(1)]
			continue
		if not insn_list:
			continue

		# \layoutxxiiidform{59}{AT}{//}{A}{B}{82}{AX}{BX}{/}
		l = re.match(r'\\(layout\w+)(.*)$', cur)
		if l:
			if len(layout) > 2:
				pe.pprint("! Wrong layout")
				sys.exit(-1)
			layout += [(layouts[l.group(1)][0],
				complete_layout(layouts[l.group(1)], l.group(2)))]

	if layout:
		ret.update(add_insns(insn_list, layout))

	return ret

# Extracts priv. flag from Table H.1: Power ISA Instruction Set Sorted by Mnemonic
# Returns priv insns list
def collect_priv(tex_file, insns):
	tex_cont = read_file(tex_file)
	ret = []
	cur = ""
	for tcur in tex_cont:
		if tcur != '\hline':
			cur += tcur
			continue
		# Merge all lines between \hline and split by tab (which '&' in latex)
		cur = re.sub(r'\\&', 'AND', cur) # but '&' may occur in the instruction name
		tmp = cur.split('&')
		if len(tmp) == 11:
			ins = re.sub(r'.*{([^{}]+)}$', r'\1', tmp[5])
			if ins in insns:
				if re.match(r'.+{(P|H|HV|HV\/P|UV|64)}$', tmp[7]):
					ret += [ins]
			cur = ""
	return ret

def generate_go(insns, priv):
	def ppcmask(val, start, len):
		return (val & ((1 << len) - 1)) << (31 - (start + len - 1))

	def generate_opcode(ins, layout):
		pos_, names_, defval_ = layout[0], layout[1], layout[2]
		opcode = 0
		opmask = 0
		pos = 0
		bits = 0
		fields = {}
		for i in range(len(pos_)):
			pos += bits
			bits = pos_[i]

			# Fields marked `/` must be 0
			if names_[i] == '/':
				opmask |= ppcmask(0xffffffff, pos, pos_[i])
				continue

			if names_[i] == '':
				continue

			try:
				num = int(names_[i], 10)
				opcode |= ppcmask(num, pos, pos_[i])
				opmask |= ppcmask(0xffffffff, pos, pos_[i])
				continue
			except:
				pass

			if defval_[i] >= 0:
				opcode |= ppcmask(defval_[i], pos, pos_[i])
				opmask |= ppcmask(0xffffffff, pos, pos_[i])
				continue

			fval = [(pos, pos_[i])]
			if (ins in ['rldcl', 'rldcl.', 'rldic', 'rldic.', 'rldicl', 'rldicl.',
				'rldimi', 'rldimi.', 'rldcr', 'rldcr.', 'rldicr', 'rldicr.'] and
				names_[i] in ["me", "mb"] and fval == [(21, 6)]):
				fval = [(21, 5), (26, 1)]
			elif ins in ['mfspr', 'mtspr'] and names_[i] == "spr" and fval == [(11, 10)]:
				fval = [(16, 5), (11, 5)]

			if names_[i] not in fields:
				fields[names_[i]] = []
			fields[names_[i]] += fval


		# Fix up fields
		fields_str = ""
		for f, fval in sorted(fields.items()):
			fields_str += '{'
			fields_str += 'Name: "{}", Bits: []powerpc.InsnBits'.format(f)
			fields_str += '{'
			for ff in fval:
				fields_str += '{{{}, {}}}, '.format(ff[0], ff[1])
				if ff[1] == 0:
					pe.pprint("!Wrong length!")
					sys.exit(-1)
			fields_str = fields_str[:-2] + '}}, '

		return opcode, opmask, fields_str[:-2]

	for ins, ival in sorted(insns.items()):
		tmp = '\t{Name: "'
		tmp += ins
		tmp += '", '
		if len(ival) >= 1:
			opcode, opmask, fields = generate_opcode(ins, ival[0])
			tmp += 'Opcode: 0x{:08x}, Mask: 0x{:08x}, Fields: []powerpc.InsnField{{{}}}'.format(opcode, opmask, fields)
			if ins in priv:
				tmp += ', Priv: true'
		if len(ival) == 2:
			opcode, opmask, fields = generate_opcode(ins, ival[1])
			tmp += ',\n\t\tOpcodeSuffix: 0x{:08x}, MaskSuffix: 0x{:08x}, FieldsSuffix: []powerpc.InsnField{{{}}}'.format(opcode, opmask, fields)

		tmp += "},"
		print(tmp)

isa_dir = sys.argv[1]
layouts = get_layouts(isa_dir + '/ilayouts.tex')
texfiles = subprocess.check_output(["find", isa_dir, "-iname", "*.tex"]).decode("utf-8").split("\n")[:-1]

insns = {}
for tex in texfiles:
	insns.update(find_insns(tex, layouts))

print('// Code generated by {}. DO NOT EDIT.'.format(sys.argv[0]))
print('')
print('//go:build !codeanalysis')
print('// +build !codeanalysis')
print('')
print('package generated')
print('')
print('import "github.com/google/syzkaller/pkg/ifuzz/powerpc"')
print('')
print('func init() {')
print('\tpowerpc.Register(insns)')
print('}')
print('')
print('var insns = []*powerpc.Insn{')
generate_go(insns, collect_priv(isa_dir + "/Appendices/inst-mnem.tex", insns))
print("}")

pe.pprint("Processed {} instructions".format(len(insns)))
