mirror of
https://github.com/RPCSX/xed.git
synced 2024-11-23 11:39:40 +00:00
e7d734962c
Change-Id: I166833daaa56c33eca01bdf7b9aa6e74a490ba9a (cherry picked from commit 1212ba962dff6dfbfa0bd2469327ff447ce59058)
6440 lines
220 KiB
Python
Executable File
6440 lines
220 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- python -*-
|
|
#BEGIN_LEGAL
|
|
#
|
|
#Copyright (c) 2017 Intel Corporation
|
|
#
|
|
# 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
|
|
#
|
|
# http://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.
|
|
#
|
|
#END_LEGAL
|
|
|
|
|
|
############################################################################
|
|
## this is the main generator and the decoder generator.
|
|
## the main data structures are:
|
|
##
|
|
## class all_generator_info_t(object):
|
|
## the catch-all for all state -- at least it tries to be.
|
|
##
|
|
## class generator_common_t(object):
|
|
## class generator_info_t(generator_common_t):
|
|
## each generator has a parser
|
|
##
|
|
## class parser_t(object):
|
|
##
|
|
## which contains:
|
|
##
|
|
## class partitionable_info_t(object):
|
|
## class instruction_info_t(partitionable_info_t):
|
|
##
|
|
## class nonterminal_info_t(object):
|
|
## class nonterminal_dict_t(object):
|
|
|
|
## class bits_list_t(object):
|
|
##
|
|
## contains a list of:
|
|
##
|
|
## class bit_info_t(object):
|
|
|
|
## class state_info_t(object):
|
|
## class prebinding_t(object):
|
|
## class opnds.operand_info_t(object):
|
|
## class graph_node(object):
|
|
|
|
## class code_gen_dec_args_t(object):
|
|
## class table_init_object_t(object):
|
|
## class bit_group_info_t(object):
|
|
## class reg_info_t(object):
|
|
## class width_info_t(object):
|
|
############################################################################
|
|
from __future__ import print_function
|
|
import os
|
|
import sys
|
|
import copy
|
|
import types
|
|
import glob
|
|
import re
|
|
import optparse
|
|
|
|
def find_dir(d):
|
|
dir = os.getcwd()
|
|
last = ''
|
|
while dir != last:
|
|
target_dir = os.path.join(dir,d)
|
|
if os.path.exists(target_dir):
|
|
return target_dir
|
|
last = dir
|
|
(dir,tail) = os.path.split(dir)
|
|
return None
|
|
|
|
mbuild_install_path = os.path.join(os.path.dirname(sys.argv[0]), '..', 'mbuild')
|
|
if not os.path.exists(mbuild_install_path):
|
|
mbuild_install_path = find_dir('mbuild')
|
|
sys.path= [mbuild_install_path] + sys.path
|
|
try:
|
|
import mbuild
|
|
except:
|
|
sys.stderr.write("\nERROR(generator.py): Could not find mbuild. " +
|
|
"Should be a sibling of the xed2 directory.\n\n")
|
|
sys.exit(1)
|
|
|
|
xed2_src_path = os.path.join(os.path.dirname(sys.argv[0]))
|
|
if not os.path.exists(xed2_src_path):
|
|
xed2_src_path = find_dir('xed2')
|
|
sys.path= [ xed2_src_path ] + sys.path
|
|
sys.path= [ os.path.join(xed2_src_path,'pysrc') ] + sys.path
|
|
|
|
from genutil import *
|
|
import operand_storage
|
|
import slash_expand, flag_gen
|
|
from verbosity import *
|
|
import opnds
|
|
import opnd_types
|
|
import genutil
|
|
|
|
send_stdout_message_to_file = False
|
|
if send_stdout_message_to_file:
|
|
fn = "out"
|
|
set_msgs(open(fn,"w"))
|
|
sys.stderr.write("Writing messages to file: [" + fn + "]\n")
|
|
|
|
check_python_version(2,4)
|
|
|
|
from codegen import *
|
|
import metaenum
|
|
import enum_txt_writer
|
|
import chipmodel
|
|
import ctables
|
|
import ild
|
|
import refine_regs
|
|
|
|
#####################################################################
|
|
## OPTIONS
|
|
#####################################################################
|
|
def setup_arg_parser():
|
|
arg_parser = optparse.OptionParser()
|
|
arg_parser.add_option('--limit-enum-strings',
|
|
action='store_true',
|
|
dest='limit_enum_strings',
|
|
default=False,
|
|
help='Save space by limiting the enum strings')
|
|
arg_parser.add_option('--gendir',
|
|
action='store',
|
|
dest='gendir',
|
|
default='gen',
|
|
help='Directory for generated files')
|
|
arg_parser.add_option('--xeddir',
|
|
action='store',
|
|
dest='xeddir',
|
|
default='',
|
|
help='Directory for generated files')
|
|
arg_parser.add_option('--input-regs',
|
|
action='store',
|
|
dest='input_regs',
|
|
default='',
|
|
help='Register input file')
|
|
arg_parser.add_option('--input-widths',
|
|
action='store',
|
|
dest='input_widths',
|
|
default='',
|
|
help='Widths input file')
|
|
arg_parser.add_option('--input-extra-widths',
|
|
action='store',
|
|
dest='input_extra_widths',
|
|
default='',
|
|
help='Extra widths input file')
|
|
arg_parser.add_option('--input-element-types',
|
|
action='store',
|
|
dest='input_element_types',
|
|
default='',
|
|
help='File with mappings from type names to' +
|
|
' widths and base element types')
|
|
arg_parser.add_option('--input-element-type-base',
|
|
action='store',
|
|
dest='input_element_type_base',
|
|
default='',
|
|
help='new chunk for element type enum')
|
|
arg_parser.add_option('--input-pointer-names',
|
|
action='store',
|
|
dest='input_pointer_names',
|
|
default='',
|
|
help='Pointer names input file for disassembly')
|
|
arg_parser.add_option('--input-fields',
|
|
action='store',
|
|
dest='input_fields',
|
|
default='',
|
|
help='Operand storage description input file')
|
|
arg_parser.add_option('--input',
|
|
action='store',
|
|
dest='input',
|
|
default='',
|
|
help='Input file')
|
|
arg_parser.add_option('--input-state',
|
|
action='store',
|
|
dest='input_state',
|
|
default='xed-state-bits.txt',
|
|
help='state input file')
|
|
arg_parser.add_option('--inst',
|
|
action='store',
|
|
dest='inst_init_file',
|
|
default='xed-init-inst-table.c',
|
|
help='Instruction table init file')
|
|
arg_parser.add_option('--sout',
|
|
action='store',
|
|
dest='structured_output_fn',
|
|
default='xed-sout.txt',
|
|
help='Emit structured output file')
|
|
arg_parser.add_option('--patterns',
|
|
action='store',
|
|
dest='structured_input_fn',
|
|
default='',
|
|
help='Read structured input file')
|
|
arg_parser.add_option('--chip-models',
|
|
action='store',
|
|
dest='chip_models_input_fn',
|
|
default='',
|
|
help='Chip models input file name')
|
|
arg_parser.add_option('--ctables',
|
|
action='store',
|
|
dest='ctables_input_fn',
|
|
default='',
|
|
help='Conversion tables input file name')
|
|
arg_parser.add_option('--isa',
|
|
action='store',
|
|
dest='isa_input_file',
|
|
default='',
|
|
help='Read structured input file containing' +
|
|
' the ISA INSTRUCTIONS() nonterminal')
|
|
arg_parser.add_option('--spine',
|
|
action='store',
|
|
dest='spine',
|
|
default='',
|
|
help='Read the spine file containing the' +
|
|
' top-most decoder nonterminal')
|
|
arg_parser.add_option('--print-graph',
|
|
action='store_true',
|
|
dest='print_graph',
|
|
default=False,
|
|
help='Print the graph for each nonterminal (big)')
|
|
|
|
arg_parser.add_option('--verbosity', '--verbose', '-v',
|
|
action='append',
|
|
dest='verbosity',
|
|
default=[],
|
|
help='Level of verbosity, repeatable. ' +
|
|
' Values=1..7, enc,merge')
|
|
arg_parser.add_option('--no-imm-suffix',
|
|
action='store_false',
|
|
dest='add_suffix_to_imm',
|
|
default=True,
|
|
help='Omit width suffixes from iforms')
|
|
arg_parser.add_option('--ild-scanners',
|
|
action='store',
|
|
dest='ild_scanners_input_fn',
|
|
default='',
|
|
help='ILD scanners input file')
|
|
arg_parser.add_option('--ild-getters',
|
|
action='store',
|
|
dest='ild_getters_input_fn',
|
|
default='',
|
|
help='ILD getters input file')
|
|
arg_parser.add_option('--cpuid',
|
|
action='store',
|
|
dest='cpuid_input_fn',
|
|
default='',
|
|
help='isa-set to cpuid map input file')
|
|
arg_parser.add_option('--gen-ild-storage',
|
|
action='store_true',
|
|
dest='gen_ild_storage',
|
|
default=False,
|
|
help='Dump the ILD storage file.')
|
|
arg_parser.add_option("--compress-operands",
|
|
action="store_true",
|
|
dest="compress_operands",
|
|
default=False,
|
|
help="use bit-fields to compress the "+
|
|
"operand storage.")
|
|
return arg_parser
|
|
|
|
#####################################################################
|
|
|
|
header_pattern = re.compile(r'[.][Hh]$')
|
|
def is_header(fn):
|
|
global header_pattern
|
|
if header_pattern.search(fn):
|
|
return True
|
|
return False
|
|
|
|
|
|
############################################################################
|
|
# Compiled patterns used in this program
|
|
############################################################################
|
|
delete_iclass_pattern = re.compile('^DELETE')
|
|
delete_iclass_full_pattern = \
|
|
re.compile(r'^DELETE[ ]*[:][ ]*(?P<iclass>[A-Za-z_0-9]+)')
|
|
|
|
udelete_pattern = re.compile('^UDELETE')
|
|
udelete_full_pattern = \
|
|
re.compile(r'^UDELETE[ ]*[:][ ]*(?P<uname>[A-Za-z_0-9]+)')
|
|
|
|
operand_token_pattern = re.compile('OPERAND')
|
|
underscore_pattern = re.compile(r'_')
|
|
invert_pattern = re.compile(r'[!]')
|
|
|
|
instructions_pattern = re.compile(r'INSTRUCTIONS')
|
|
equals_pattern = re.compile(r'(?P<lhs>[^!]+)=(?P<rhs>.+)')
|
|
not_equals_pattern = re.compile(r'(?P<lhs>[^!]+)!=(?P<rhs>.+)')
|
|
|
|
quick_equals_pattern= re.compile(r'=')
|
|
colon_pattern= re.compile(r'[:]')
|
|
|
|
bits_and_letters_underscore_pattern = re.compile(r'^[10a-z_]+$')
|
|
|
|
hex_pattern = re.compile(r'0[xX][0-9A-Fa-f]+')
|
|
|
|
slash_macro_pattern = re.compile(r'([a-z][/][0-9]{1,2})')
|
|
nonterminal_string = r'([A-Z][a-zA-Z0-9_]*)[(][)]'
|
|
|
|
parens_to_end_of_line = re.compile(r'[(][)].*::.*$') # with double colon
|
|
lookupfn_w_args_pattern = re.compile(r'[[][a-z]+]')
|
|
#nonterminal_start_pattern=re.compile(r'^' + nonterminal_string + r'\s*::')
|
|
nonterminal_start_pattern=re.compile(r'::')
|
|
nonterminal_pattern=re.compile(nonterminal_string)
|
|
nonterminal_parens_pattern = re.compile(r'[(][^)]*[)]')
|
|
|
|
binary_pattern = re.compile(r'^[01_]+$') # only 1's and 0's
|
|
formal_binary_pattern = re.compile(r'^0b[01_]+$') # only 1's and 0's leading 0b
|
|
one_zero_pattern = re.compile(r'^[01]') # just a leading 0 or 1
|
|
completely_numeric = re.compile(r'^[0-9]+$') # only numbers
|
|
|
|
# things identified by the restriction_pattern are the operand deciders:
|
|
restriction_pattern = re.compile(r'([A-Z0-9_]+)(!=|=)([bx0-9A-Z_]+)')
|
|
all_caps_pattern = re.compile(r'^[A-Z_0-9]+$')
|
|
|
|
not11_pattern = re.compile(r'NOT11[(]([a-z]{2})[)]')
|
|
letter_basis_pattern = re.compile(r'[a-z]')
|
|
|
|
all_zeros_pattern = re.compile(r'^[0]+$')
|
|
type_ending_pattern = re.compile(r'_t$')
|
|
uniq_pattern = re.compile(r'_uniq(.*)$')
|
|
ntwidth_pattern = re.compile('NTWIDTH')
|
|
paren_underscore_pattern = re.compile(r'[(][)][_]+')
|
|
|
|
all_lower_case_pattern = re.compile(r'^[a-z]+$')
|
|
|
|
|
|
pattern_binding_pattern = re.compile(
|
|
r'(?P<name>[A-Za-z_0-9]+)[[](?P<bits>[A-Za-z01_]+)]')
|
|
uppercase_pattern = re.compile(r'[A-Z]')
|
|
|
|
|
|
reg_operand_name_pattern = re.compile("^REG(?P<regno>[0-9]+)$")
|
|
############################################################################
|
|
|
|
def comment(s):
|
|
return '/* ' + s + ' */'
|
|
|
|
def all_the_same(lst):
|
|
"return True if all the elements of the list are the same"
|
|
first = lst[0]
|
|
for x in lst:
|
|
if x != first:
|
|
return False
|
|
return True
|
|
|
|
def pad_to_multiple_of_8bits(x):
|
|
ilen = len(x)
|
|
frac = ilen & 7
|
|
if frac == 0:
|
|
return x
|
|
t = []
|
|
while frac < 8:
|
|
t.append('0')
|
|
frac = frac + 1
|
|
t.extend(x)
|
|
return t
|
|
|
|
############################################################################
|
|
# $$ nonterminal_info_t
|
|
class nonterminal_info_t(object):
|
|
def __init__(self,name, type=None):
|
|
self.name = name
|
|
self.type = type
|
|
self.start_node = None
|
|
|
|
def set_start_node(self,n):
|
|
self.start_node = n
|
|
|
|
def is_lookup_function(self):
|
|
if self.type != None:
|
|
return True
|
|
return False
|
|
|
|
# $$ nonterminal_dict_t
|
|
class nonterminal_dict_t(object):
|
|
"""dictionary holding nonterminal information for code generation"""
|
|
|
|
def __init__(self):
|
|
# dictionary of nonterminal_info_t's by short name.
|
|
# nonterminal_info_t has {name, type, start_node}
|
|
self.nonterminal_info = {}
|
|
|
|
def keys(self):
|
|
return list(self.nonterminal_info.keys())
|
|
|
|
def add_graph_node(self, nt_name, node_id):
|
|
"""set the node id in the graph node"""
|
|
if nt_name not in self.nonterminal_info:
|
|
self.add_to_dict(nt_name)
|
|
n = self.nonterminal_info[nt_name]
|
|
n.start_node = node_id
|
|
|
|
def get_node(self,nt_name):
|
|
if nt_name in self.nonterminal_info:
|
|
return self.nonterminal_info[nt_name]
|
|
die("Did not find " + nt_name + " in the nonterminal dictionary.")
|
|
|
|
def add_to_dict(self,short_nt_name, nt_type):
|
|
msge("Adding " + short_nt_name + " to nonterminal dict")
|
|
#nonterminal_info_t has {name, type, start_node, encode, decoder}
|
|
new_nt = nonterminal_info_t(short_nt_name, nt_type)
|
|
self.nonterminal_info[short_nt_name] = new_nt
|
|
|
|
def record_nonterminal(self,nt_name, nt_type):
|
|
if nt_name:
|
|
if nt_name not in self.nonterminal_info:
|
|
#msge("Adding NT: " + nt_name)
|
|
self.add_to_dict(nt_name, nt_type)
|
|
else:
|
|
die("Bad nonterminal name")
|
|
|
|
|
|
############################################################################
|
|
# $$ bit_info_t
|
|
class bit_info_t(object):
|
|
"""The patterns are built up of bits of various kinds. Normal 1/0
|
|
bits are type bit. The other kinds of bits are dontcares which are
|
|
letter names, state bits, operand tests and nonterminals.
|
|
"""
|
|
|
|
bit_types = [ 'bit', 'dontcare', 'operand', 'nonterminal' ]
|
|
def __init__(self, value, btype='bit', pbit=-1):
|
|
self.btype = btype # See bit_info_t.bit_types
|
|
self.value = value
|
|
|
|
# Physical bits are bits that are real. They are offsets from
|
|
# the beginnning of this nonterminal or the last nonterminal.
|
|
|
|
self.pbit = pbit
|
|
|
|
self.token = None # operand decider
|
|
self.test = None # eq or ne
|
|
self.requirement = None # the value the od must have (or not have)
|
|
|
|
if btype == 'operand':
|
|
# for operands, we split them in to a token name and a required value.
|
|
#search for FOO=233 or FOO!=233
|
|
m = restriction_pattern.search(value)
|
|
if not m:
|
|
die("bad operand decider: "+ value)
|
|
(token,test,requirement) = m.groups([0,1,2])
|
|
if vod():
|
|
msge("OperandDecider Token= " + token +
|
|
" Test= " + test + " Requirement= " + requirement)
|
|
self.token = token
|
|
self.requirement = make_numeric(requirement, value)
|
|
if test == '=':
|
|
self.test='eq'
|
|
else:
|
|
self.test='ne'
|
|
|
|
|
|
def __eq__(self,other):
|
|
if other == None:
|
|
return False
|
|
if self.value == other.value:
|
|
if self.btype == other.btype:
|
|
return True
|
|
return False
|
|
|
|
def __ne__(self,other):
|
|
if other == None:
|
|
return True
|
|
if self.value != other.value:
|
|
return True
|
|
if self.btype != other.btype:
|
|
return True
|
|
return False
|
|
|
|
def __str__(self):
|
|
s = self.btype + '/' + str(self.value)
|
|
if self.pbit != -1:
|
|
s += '/PBIT' + str(self.pbit)
|
|
return s
|
|
|
|
def just_bits(self):
|
|
return self.value
|
|
|
|
def is_nonterminal(self):
|
|
if self.btype == 'nonterminal':
|
|
return True
|
|
return False
|
|
|
|
def is_operand_decider(self):
|
|
if self.btype == 'operand':
|
|
return True
|
|
return False
|
|
|
|
def is_dont_care(self):
|
|
if self.btype == 'dontcare':
|
|
return True
|
|
return False
|
|
|
|
def is_real_bit(self):
|
|
if self.btype == 'dontcare' or self.btype == 'bit':
|
|
return True
|
|
return False
|
|
def is_one_or_zero(self):
|
|
if self.btype == 'bit':
|
|
return True
|
|
return False
|
|
|
|
def nonterminal_name(self):
|
|
if self.is_nonterminal():
|
|
g = nonterminal_pattern.search(self.value)
|
|
if g:
|
|
nt_name = g.group(1)
|
|
return nt_name
|
|
else:
|
|
die("Error finding NT name for " + self.value)
|
|
return None
|
|
|
|
# $$ bits_list_t
|
|
class bits_list_t(object):
|
|
""" list of bit_info_t """
|
|
def __init__(self):
|
|
self.bits = []
|
|
def append(self,x):
|
|
self.bits.append(x)
|
|
|
|
def __str__(self):
|
|
return self.just_bits()
|
|
|
|
def just_bits(self):
|
|
""" return a string of just the bits"""
|
|
s = [ x.just_bits() for x in self.bits]
|
|
o = []
|
|
i = 0
|
|
for b in s:
|
|
o.append(b)
|
|
i = i + 1
|
|
if i == 4:
|
|
i = 0
|
|
o.append(' ')
|
|
return ' '.join(o)
|
|
|
|
# $$ state_info_t
|
|
class state_info_t(object):
|
|
"""This is really just a big dictionary for the state (operand
|
|
decider) macro expansion"""
|
|
def __init__(self, name, list_of_str):
|
|
""" takes a name and a string containing our bit strings"""
|
|
self.name = name
|
|
self.list_of_str = []
|
|
# bust up bit strings that come in via the states file.
|
|
# make individual bits and add them to the list of strings.
|
|
for x in list_of_str:
|
|
if formal_binary_pattern.match(x):
|
|
t = re.sub('0b','',x)
|
|
t = re.sub('_','',t)
|
|
self.list_of_str.extend(list(t))
|
|
else:
|
|
self.list_of_str.append(x)
|
|
|
|
|
|
def dump_str(self):
|
|
s = self.name + ' '
|
|
for w in self.list_of_str:
|
|
s += w + ' '
|
|
return s
|
|
|
|
############################################################################
|
|
|
|
def blank_line(line):
|
|
if line == '':
|
|
return False
|
|
return True
|
|
|
|
def pad_pattern(pattern):
|
|
"pad it to a multiple of 8 bits"
|
|
plen = len(pattern)
|
|
if (plen & 7) != 0:
|
|
rem = 8-(plen & 7)
|
|
pattern += '-' * rem
|
|
return pattern
|
|
|
|
def read_dict_spec(fn):
|
|
"""Read a file with expected format of a form
|
|
{KEY VALUE\n}, return a dict of dict[KEY] == VALUE """
|
|
res_dict = {}
|
|
if not os.path.exists(fn):
|
|
die("Could not read file: " + fn)
|
|
lines = open(fn,'r').readlines()
|
|
lines = map(no_comments, lines)
|
|
lines = list(filter(blank_line, lines))
|
|
for line in lines:
|
|
wrds = line.split()
|
|
key = wrds[0]
|
|
value = wrds[1]
|
|
#convert straight to int
|
|
res_dict[key] = int(value)
|
|
return res_dict
|
|
|
|
def read_ild_scanners_def(ild_scanners_fn):
|
|
scanners_dict = read_dict_spec(ild_scanners_fn)
|
|
return scanners_dict
|
|
|
|
def read_ild_getters_def(ild_getters_fn):
|
|
getters_dict = read_dict_spec(ild_getters_fn)
|
|
return getters_dict
|
|
|
|
def read_state_spec(fn):
|
|
"Return dictionary of state bits"
|
|
state_bits = {}
|
|
if not os.path.exists(fn):
|
|
die("Could not read file: " + fn)
|
|
lines = open(fn,'r').readlines()
|
|
lines = map(no_comments, lines)
|
|
lines = list(filter(blank_line, lines))
|
|
for line in lines:
|
|
## remove comment lines
|
|
#line = no_comments(line)
|
|
#if line == '':
|
|
# continue
|
|
#msge("STATELINE: [" + line + ']')
|
|
|
|
wrds = line.split()
|
|
tag = wrds[0]
|
|
spattern = wrds[1:]
|
|
|
|
si = state_info_t(tag,spattern)
|
|
|
|
state_bits[tag] = si
|
|
|
|
return state_bits
|
|
|
|
def compute_state_space(state_dict):
|
|
"""Figure out all the values for each token, return a dictionary
|
|
indexed by token name"""
|
|
|
|
# a dictionary of the values of a each operand_decider
|
|
state_values = {}
|
|
|
|
for k in list(state_dict.keys()):
|
|
vals = state_dict[k]
|
|
for wrd in vals.list_of_str:
|
|
m = restriction_pattern.search(wrd)
|
|
if m:
|
|
(token,test,requirement) = m.groups([0,1,2])
|
|
if requirement == 'XED_ERROR_GENERAL_ERROR':
|
|
continue
|
|
#if type(requirement) == types.IntType:
|
|
# die("Already an integer")
|
|
requirement_base10 = make_numeric(requirement,wrd)
|
|
#msge("STATE RESTRICTION PATTERN " + token + " : " +
|
|
# str(requirement) + " -> " + str(requirement_base10))
|
|
if token in state_values:
|
|
if requirement_base10 not in state_values[token]:
|
|
state_values[token].append(requirement_base10)
|
|
else:
|
|
state_values[token] = [ requirement_base10 ]
|
|
elif formal_binary_pattern.match(wrd):
|
|
pass # ignore these
|
|
elif nonterminal_pattern.match(wrd):
|
|
pass # ignore these
|
|
elif hex_pattern.match(wrd):
|
|
pass # ignore these
|
|
else:
|
|
die("Unhandled state pattern: %s" % wrd)
|
|
|
|
return state_values
|
|
|
|
|
|
############################################################################
|
|
|
|
def validate_field_width(agi, field_name, bits):
|
|
b=make_binary(bits)
|
|
n = len(b)
|
|
opnd = agi.operand_storage.get_operand(field_name)
|
|
if n > int(opnd.bitwidth):
|
|
s = ("length of captured field %s[%s] is %d and that exceeds " +
|
|
" operand storage field width %s\n")
|
|
die(s % (field_name, bits, n, opnd.bitwidth))
|
|
|
|
# $$ prebinding_t
|
|
class prebinding_t(object):
|
|
"""This is for fields mentioned in the decode pattern. We want to
|
|
bind the bits to the operand storage fields before calling any
|
|
NT/NTLUFs that require these bindings.
|
|
"""
|
|
def __init__(self, name):
|
|
self.field_name = name
|
|
self.bit_info_list = [] # list of bit_info_t's
|
|
def add_bit(self, b):
|
|
self.bit_info_list.append(b)
|
|
|
|
def is_constant(self):
|
|
for bi in self.bit_info_list:
|
|
if bi.is_dont_care():
|
|
return False #dontcare in prebinding
|
|
return True
|
|
|
|
def get_value(self):
|
|
value = ''
|
|
for bi in self.bit_info_list:
|
|
value += bi.just_bits()
|
|
return value
|
|
|
|
def __str__(self):
|
|
s = []
|
|
s.append(self.field_name)
|
|
s.extend( [str(x) for x in self.bit_info_list ] )
|
|
return ', '.join(s)
|
|
|
|
def get_btype(b):
|
|
if b == '1' or b == '0':
|
|
return 'bit'
|
|
return 'dontcare'
|
|
|
|
def parse_opcode_spec(agi, line, state_dict):
|
|
"""Given a string of bits, spaces and hex codes, canonicalize it
|
|
to useful binary, return a list of single char bits or letters, or
|
|
nonterminals.
|
|
|
|
@rtype: tuple
|
|
@return: (list of bits, -- everything is raw bits at this level
|
|
list of operand binding tuples,-- same info as the prebindings
|
|
list bit_info_t, -- interpreted bits with types and positions
|
|
dict of prebinding_t, -- dictionary of the captured fields
|
|
pointing to bits
|
|
xed_bool_t otherwise_ok)
|
|
"""
|
|
# if there are any trailing underscores after a nonterminal paren,
|
|
# convert them to spaces.
|
|
b = paren_underscore_pattern.sub('() ', line)
|
|
# if there are any leading underscores before, convert them to spaces
|
|
|
|
extra_bindings = [] # the inline captures become "extra" operands later
|
|
if vparse():
|
|
msgb("PARSE OPCODE SPEC " + line)
|
|
# expand things from the state dictionary
|
|
wrds = []
|
|
for w in b.split():
|
|
if w in state_dict:
|
|
wrds.extend(copy.deepcopy(state_dict[w].list_of_str))
|
|
else:
|
|
wrds.append(w)
|
|
all_bits = []
|
|
#
|
|
# 1. hex byte
|
|
# 2. immediate capture IMM(a-z,0-9) ??? IS THIS USED???
|
|
# IMM(a,9) -- old form of slash
|
|
# 3. slash pattern (just more letter bits)
|
|
# 4. pattern binding eg: MOD[mm] or MOD[11_]
|
|
# 5. nonterminal
|
|
# Then EXPAND
|
|
all_bit_infos = []
|
|
all_prebindings = {}
|
|
bcount = 0 # bit count
|
|
for w in wrds:
|
|
if w == 'otherwise':
|
|
return (None,None,None,None,True)
|
|
|
|
if hex_pattern.match(w):
|
|
bits = pad_to_multiple_of_8bits(hex_to_binary(w))
|
|
for b in bits:
|
|
all_bit_infos.append(bit_info_t(b,'bit',bcount))
|
|
bcount += 1
|
|
all_bits.extend(bits)
|
|
continue
|
|
|
|
# inline captures MOD[mm] REG[rrr] RM[nnn] or REG[111] etc. --
|
|
# can be constant
|
|
pb = pattern_binding_pattern.match(w)
|
|
if pb:
|
|
#msgb("PATTERN BINDING", w)
|
|
(field_name,bits) = pb.group('name','bits')
|
|
if uppercase_pattern.search(bits):
|
|
die("\n\nUpper case letters in capture pattern" +
|
|
": %s in line\n\n %s\n\n" % ( w, line))
|
|
|
|
validate_field_width(agi, field_name, bits)
|
|
extra_bindings.append( (field_name,bits) )
|
|
prebinding = prebinding_t(field_name)
|
|
bits_str = make_binary(bits)
|
|
bits_list = list(bits_str)
|
|
#print "BITS %s -> %s" % ( bits, bits_str)
|
|
for b in bits_list:
|
|
#btype is either bit or dontcare
|
|
btype = get_btype(b)
|
|
bi = bit_info_t(b,btype,bcount)
|
|
bcount += 1
|
|
prebinding.add_bit(bi)
|
|
all_bit_infos.append(bi)
|
|
all_prebindings[field_name] = prebinding
|
|
all_bits.extend(bits_list)
|
|
continue
|
|
if nonterminal_pattern.search(w):
|
|
# got a nonterminal
|
|
bits = [ w ]
|
|
all_bit_infos.append(bit_info_t(w,'nonterminal', bcount))
|
|
bcount += 1
|
|
all_bits.extend(bits)
|
|
continue
|
|
|
|
|
|
m = restriction_pattern.search(w)
|
|
if m:
|
|
(token,test,requirement) = m.groups([0,1,2])
|
|
# got an operand-decider (requirement)
|
|
#msge("RESTRICTION PATTERN " + str(w))
|
|
# we skip some field bindings that are only for the encoder.
|
|
# They are denoted DS in the fields data-files.
|
|
if agi.operand_storage.decoder_skip(token):
|
|
#msge("SKIPPING RESTRICTION PATTERN " + str(w))
|
|
continue
|
|
bits = [ w ]
|
|
all_bit_infos.append(bit_info_t(w,'operand', bcount))
|
|
bcount += 1
|
|
all_bits.extend(bits)
|
|
continue
|
|
|
|
if formal_binary_pattern.search(w):
|
|
bits = make_binary(w)
|
|
all_bits.extend(bits)
|
|
for b in list(bits):
|
|
btype = get_btype(b)
|
|
all_bit_infos.append(bit_info_t(b,btype,bcount))
|
|
bcount += 1
|
|
continue
|
|
|
|
# remove the underscores now that we know it is a pattern
|
|
w = w.replace('_','')
|
|
# some binary value or letter
|
|
bits = [ str(x) for x in list(w) ]
|
|
all_bits.extend(bits)
|
|
for b in list(bits):
|
|
btype = get_btype(b)
|
|
all_bit_infos.append(bit_info_t(b,btype,bcount))
|
|
bcount += 1
|
|
|
|
|
|
# We now also have a a list of bit_info_t's in all_bit_infos and a
|
|
# dictionary of prebinding_t's in all_prebindings.
|
|
|
|
return (all_bits,extra_bindings,all_bit_infos, all_prebindings,False)
|
|
|
|
def add_str(s, name, value):
|
|
t = s + '%-15s' % (name) + ': '
|
|
if type(value) == list:
|
|
for q in value:
|
|
t += q + ' '
|
|
else:
|
|
t += value
|
|
t += '\n'
|
|
return t
|
|
|
|
def add_str_list(s, name, values):
|
|
s = s + '%-15s' % (name) + ': '
|
|
for v in values:
|
|
s = s + v + ' '
|
|
s = s + '\n'
|
|
return s
|
|
|
|
|
|
#for the first not commented, non-empty line from lines,
|
|
#return if regexp.search succeeds
|
|
def accept(regexp, lines):
|
|
#msge("In accept!")
|
|
line = ''
|
|
while line == '':
|
|
if lines == []:
|
|
return None
|
|
line = no_comments(lines[0])
|
|
line = line.strip()
|
|
lines.pop(0)
|
|
#msge("Testing line :" + line)
|
|
if re.search(regexp,line):
|
|
return True
|
|
return False
|
|
|
|
|
|
def read_str(lines,name):
|
|
"Read a line emitted by add_str() above. Split on 1st colon"
|
|
line = ''
|
|
while line == '':
|
|
if lines == []:
|
|
return None
|
|
line = no_comments(lines[0])
|
|
line = line.strip()
|
|
lines.pop(0)
|
|
#msge("Reading line: " + line)
|
|
(iname, rest) = line.split(':',1)
|
|
iname = iname.strip()
|
|
rest = rest.strip()
|
|
if iname != name:
|
|
die('Misparsed structured input file. Expecting: ['
|
|
+ name + '] Observed: [' + iname + ']')
|
|
return rest
|
|
|
|
def read_str_simple(lines):
|
|
"Read a line emitted by add_str() above. Split on 1st colon"
|
|
line = ''
|
|
while line == '':
|
|
if lines == []:
|
|
return None
|
|
line = no_comments(lines[0])
|
|
line = line.strip()
|
|
lines.pop(0)
|
|
return line
|
|
|
|
############################################################################
|
|
def parse_extra_operand_bindings(agi, extra_bindings):
|
|
"""Add the captures as operands"""
|
|
operands = []
|
|
operand_storage_dict = agi.operand_storage.get_operands()
|
|
for (name,bits) in extra_bindings:
|
|
# FIXME handle something other than bits
|
|
bits_str = make_binary(bits)
|
|
# FIXME: add "i#" width codes for the captured operands!
|
|
try:
|
|
bits = operand_storage_dict[name].bitwidth
|
|
oc2= "i%s" % (bits)
|
|
except:
|
|
die("Could not find field width for %s" % (name))
|
|
|
|
new_operand = opnds.operand_info_t(name,
|
|
'imm',
|
|
list(bits_str),
|
|
vis='SUPP',
|
|
oc2=oc2)
|
|
# DENOTE THESE AS INLINE TO ALLOW EARLY CAPTURING
|
|
if vbind():
|
|
msge("INLINE OPERAND %s" % (name))
|
|
new_operand.inline = True
|
|
operands.append(new_operand)
|
|
return operands
|
|
|
|
#NOTE: class attributes are not like static member data in C++. They
|
|
#are per object type. So if you derive a new class bar from class foo,
|
|
#then the instance (attribute) variables of class foo and class bar
|
|
#are disjoint.
|
|
global_inum = 0
|
|
|
|
# $$ partitionable
|
|
class partitionable_info_t(object):
|
|
def new_inum(self):
|
|
global global_inum
|
|
self.inum = global_inum
|
|
global_inum += 1
|
|
|
|
def __init__(self, name='', ipattern_input='', operands_input=None):
|
|
|
|
self.new_inum()
|
|
self.name = name
|
|
self.input_str = ''
|
|
|
|
self.ipattern_input = ipattern_input
|
|
self.ipattern = None # bits_list_t()
|
|
self.prebindings = None # dictionary
|
|
|
|
if operands_input:
|
|
self.operands_input = operands_input
|
|
else:
|
|
self.operands_input = []
|
|
|
|
self.operands = [] # list of opnds.operand_info_t's
|
|
|
|
# FOR HIERARCHICAL RECORDS -- THESE GET SPLIT OFF AFTER RECORD-READ
|
|
self.extra_ipatterns = []
|
|
self.extra_operands = []
|
|
self.extra_iforms_input = []
|
|
|
|
# When we consume a prefix, we must apply state modifications
|
|
# and that might cause us to jump to a different part of the
|
|
# graph, so we must retraverse the state-portion of the graph,
|
|
# but remember what byte we were processing and pick up at the
|
|
# next byte which may or may not be a prefix.
|
|
self.reset_for_prefix = False
|
|
|
|
|
|
self.encoder_func_obj = None # an instance of a class function_object_t
|
|
|
|
self.encoder_operands = None
|
|
|
|
self.otherwise_ok = False
|
|
|
|
# simple nonterminals: either all nonterminals or all operand deciders.
|
|
self.all_nonterminals = None
|
|
self.all_operand_deciders = None
|
|
|
|
def get_iclass(self):
|
|
if field_check(self,'iclass'):
|
|
return self.iclass
|
|
return '*NO-ICLASS*'
|
|
|
|
def refine_parsed_line(self, agi, state_dict):
|
|
"""Refine the ipattern_input to ipattern, parse up operands"""
|
|
(simple_pattern,
|
|
extra_bindings,
|
|
all_bit_infos,
|
|
all_prebindings,
|
|
otherwise_ok) = parse_opcode_spec(agi,self.ipattern_input, state_dict)
|
|
|
|
if otherwise_ok: # FIXME: 2008-09-25 - need to remove this for more
|
|
# general "otherwise" handling
|
|
self.otherwise_ok = True
|
|
return
|
|
|
|
self.ipattern = bits_list_t()
|
|
self.ipattern.bits = all_bit_infos
|
|
self.prebindings = all_prebindings
|
|
|
|
(self.operands, self.reset_for_prefix) = \
|
|
parse_operand_spec(agi, self.operands_input)
|
|
if extra_bindings:
|
|
extra_operands = parse_extra_operand_bindings(agi,extra_bindings)
|
|
self.operands.extend(extra_operands)
|
|
|
|
self.check_for_simple_nts()
|
|
|
|
def check_for_simple_nts(self):
|
|
"""Check for NTs that do not accept bits. We'll make them in to
|
|
fast functions"""
|
|
all_operand_deciders = True
|
|
all_nonterminals = True
|
|
for bi in self.ipattern.bits:
|
|
if not bi.is_operand_decider():
|
|
all_operand_deciders = False
|
|
if not bi.is_nonterminal():
|
|
all_nonterminals = False
|
|
|
|
self.all_nonterminals = all_nonterminals
|
|
self.all_operand_deciders = all_operand_deciders
|
|
|
|
|
|
def __str__(self):
|
|
return self.dump_str()
|
|
|
|
def dump_str(self, pad=''):
|
|
return self.input_str
|
|
|
|
def dump_structured(self,pad=''):
|
|
lst = []
|
|
s = pad
|
|
s += self.ipattern_input + ' | '
|
|
s += ' '.join(self.operands_input)
|
|
lst.append( s )
|
|
return lst
|
|
|
|
def dump(self, pad=''):
|
|
for s in self.dump_structured(pad):
|
|
msge(s)
|
|
|
|
s = ' ipatternbits:' + str(len(self.ipattern.bits))
|
|
msge("BITLENGTHS: " + s)
|
|
s = ''
|
|
for b in self.ipattern.bits:
|
|
s += ' ' + b.value
|
|
|
|
msge("GRAPHBITS: " + s)
|
|
|
|
############################################################################
|
|
|
|
# indicates which fields are required in the input parsing
|
|
structured_input_tags = {'ICLASS': True,
|
|
'UNAME': False,
|
|
'CATEGORY': True,
|
|
'EXTENSION': True,
|
|
'ISA_SET': False,
|
|
'STATE': False,
|
|
'PATTERN': True,
|
|
'ATTRIBUTES': False,
|
|
'OPERANDS': False,
|
|
'UCODE': False,
|
|
'FLAGS': False,
|
|
'VERSION': False,
|
|
'CPL': False,
|
|
'COMMENT': False,
|
|
'EXCEPTIONS': False,
|
|
'DISASM': False,
|
|
'DISASM_INTEL': False,
|
|
'DISASM_ATTSV': False,
|
|
'IFORM': False
|
|
}
|
|
|
|
|
|
# $$ instruction_info_t
|
|
class instruction_info_t(partitionable_info_t):
|
|
def __init__(self,
|
|
iclass='',
|
|
ipattern_input='',
|
|
operands_input=None,
|
|
category='DEFAULT',
|
|
extension='DEFAULT',
|
|
version = 0,
|
|
isa_set = None):
|
|
partitionable_info_t.__init__(self, iclass,ipattern_input, operands_input)
|
|
self.iclass = iclass
|
|
self.uname = None
|
|
self.ucode = None
|
|
self.comment = None
|
|
self.exceptions = None
|
|
|
|
# Default version. Newer versions replace older versions
|
|
self.version = version
|
|
|
|
self.category = category
|
|
self.extension = extension
|
|
self.isa_set = isa_set
|
|
self.cpl = None
|
|
self.attributes = None
|
|
self.flags_input = None
|
|
self.flags_info = None # flag_gen.flags_info_t class
|
|
|
|
self.iform_input = None
|
|
self.iform_num = None
|
|
self.iform_enum = None
|
|
|
|
def add_attribute(self,s):
|
|
if self.attributes:
|
|
self.attributes.append(s)
|
|
else:
|
|
self.attributes = [s]
|
|
|
|
def add_stack_attribute(self, memop_index):
|
|
for op in self.operands:
|
|
if op.bits == 'XED_REG_STACKPUSH':
|
|
self.add_attribute('STACKPUSH%d' % (memop_index))
|
|
return
|
|
elif op.bits == 'XED_REG_STACKPOP':
|
|
self.add_attribute('STACKPOP%d' % (memop_index))
|
|
return
|
|
die("Did not find stack push/pop operand")
|
|
|
|
def dump_structured(self):
|
|
"""Return a list of strings representing the instruction in a
|
|
structured way"""
|
|
|
|
slist = []
|
|
|
|
slist.append('{\n')
|
|
|
|
s = add_str('', 'ICLASS', self.iclass)
|
|
slist.append(s)
|
|
|
|
if self.uname:
|
|
s = add_str('', 'UNAME', self.uname)
|
|
slist.append(s)
|
|
|
|
if self.version != 0:
|
|
s = add_str('','VERSION', str(self.version))
|
|
slist.append(s)
|
|
|
|
s = add_str('','CATEGORY', self.category)
|
|
slist.append(s)
|
|
|
|
s = add_str('','EXTENSION', self.extension)
|
|
slist.append(s)
|
|
s = add_str('','ISA_SET', self.isa_set)
|
|
slist.append(s)
|
|
s = add_str('','PATTERN', self.ipattern_input)
|
|
slist.append(s)
|
|
if self.cpl:
|
|
s = add_str('','CPL', self.cpl)
|
|
slist.append(s)
|
|
|
|
|
|
if self.attributes:
|
|
s = add_str('','ATTRIBUTES', self.attributes)
|
|
slist.append(s)
|
|
if self.ucode:
|
|
s = add_str('','UCODE', self.ucode)
|
|
slist.append(s)
|
|
if self.comment:
|
|
s = add_str('','COMMENT', self.comment)
|
|
slist.append(s)
|
|
if self.exceptions:
|
|
s = add_str('','EXCEPTIONS', self.exceptions)
|
|
slist.append(s)
|
|
if self.exceptions:
|
|
s = add_str('','DISASM_INTEL', self.disasm_intel)
|
|
slist.append(s)
|
|
if self.exceptions:
|
|
s = add_str('','DISASM_ATTSV', self.disasm_att)
|
|
slist.append(s)
|
|
if self.iform_input:
|
|
s = add_str('','IFORM_INPUT', self.iform_input)
|
|
slist.append(s)
|
|
if self.iform:
|
|
s = add_str('','IFORM', self.iform)
|
|
slist.append(s)
|
|
|
|
if self.flags_input:
|
|
s = add_str('','FLAGS', self.flags_input)
|
|
slist.append(s)
|
|
|
|
t = ''
|
|
for op in self.operands_input:
|
|
t = t + op + ' '
|
|
s = add_str('','OPERANDS', t)
|
|
slist.append(s)
|
|
|
|
slist.append('}\n')
|
|
return slist
|
|
|
|
def read_structured_flexible(self,lines):
|
|
debug = False
|
|
accept(r'[{]', lines)
|
|
reached_closing_bracket = False
|
|
# FIXME add more error checking
|
|
structured_input_dict = dict(zip(list(structured_input_tags.keys()),
|
|
len(structured_input_tags)*[False]))
|
|
found_operands = False
|
|
filling_extra = False
|
|
while 1:
|
|
line = read_str_simple(lines)
|
|
if debug:
|
|
msge("Reading: " + line)
|
|
if not line:
|
|
if debug:
|
|
msge("Dead line - ending")
|
|
break
|
|
if line == '}':
|
|
if debug:
|
|
msge("Hit bracket")
|
|
reached_closing_bracket = True
|
|
break
|
|
#print "READING [%s]" % (line)
|
|
if colon_pattern.search(line):
|
|
(token, rest ) = line.split(':',1)
|
|
token = token.strip()
|
|
rest = rest.strip()
|
|
|
|
# Certain tokens can be duplicated. We allow for triples
|
|
# of (pattern,operands,iform). The iform is optional. If
|
|
# we see "pattern, operand, pattern" without an
|
|
# intervening iform, the iform is assumed to be
|
|
# auto-generated. But we must have an operand line for
|
|
# each pattern line.
|
|
#msge("LINE: %s" % (line))
|
|
if token in structured_input_dict:
|
|
if structured_input_dict[token] == True:
|
|
if token in [ 'PATTERN', 'OPERANDS', 'IFORM']:
|
|
filling_extra = True
|
|
else:
|
|
die("Duplicate token %s in entry:\n\t%s\n" % (token, line))
|
|
structured_input_dict[token] =True
|
|
#msge("FILLING EXTRA = %s" %( str(filling_extra)))
|
|
|
|
if token == 'ICLASS':
|
|
self.iclass = rest
|
|
if viclass():
|
|
msgb("ICLASS", rest)
|
|
|
|
elif token == 'CATEGORY':
|
|
self.category = rest
|
|
elif token == 'CPL':
|
|
self.cpl = rest
|
|
elif token == 'EXTENSION':
|
|
self.extension = rest
|
|
|
|
# the isa set defaults to the extension. We can override
|
|
# the isa set with the ISA_SET token.
|
|
if self.isa_set == None:
|
|
self.isa_set = self.extension
|
|
|
|
elif token == 'ISA_SET':
|
|
self.isa_set = rest
|
|
elif token == 'ATTRIBUTES':
|
|
self.attributes = rest.upper().split()
|
|
elif token == 'VERSION':
|
|
self.version = int(rest)
|
|
elif token == 'FLAGS':
|
|
self.flags_input = rest
|
|
self.flags_info = flag_gen.flags_info_t(self.flags_input)
|
|
if vflag():
|
|
msge("FLAGS parsed = %s" % str(self.flags_info))
|
|
elif token == 'PATTERN':
|
|
if filling_extra:
|
|
self.extra_ipatterns.append(rest)
|
|
#msge("APPENDING None TO IFORMS INPUT")
|
|
self.extra_iforms_input.append(None)
|
|
self.extra_operands.append(None)
|
|
else:
|
|
self.ipattern_input = rest
|
|
found_operands=False
|
|
elif token == 'OPERANDS':
|
|
if filling_extra:
|
|
# overwrite the one that was added when we had an
|
|
# extra pattern.
|
|
if len(self.extra_operands) == 0:
|
|
die("Need to have a PATTERN line before the " +
|
|
"OPERANDS line for " + ii.iclass)
|
|
self.extra_operands[-1] = rest.split()
|
|
else:
|
|
self.operands_input = rest.split()
|
|
found_operands=True
|
|
elif token == 'UCODE':
|
|
self.ucode = rest
|
|
elif token == 'COMMENT':
|
|
self.comment = rest
|
|
elif token == 'EXCEPTIONS':
|
|
self.exceptions = rest
|
|
elif token == 'DISASM':
|
|
self.disasm_intel = rest
|
|
self.disasm_att = rest
|
|
elif token == 'DISASM_INTEL':
|
|
self.disasm_intel = rest
|
|
elif token == 'DISASM_ATTSV': # AT&T System V
|
|
self.disasm_att = rest
|
|
elif token == 'UNAME':
|
|
self.uname = rest
|
|
if viclass():
|
|
msgb("UNAME", rest)
|
|
|
|
elif token == 'IFORM':
|
|
if filling_extra:
|
|
if len(self.extra_iforms_input) == 0:
|
|
die("Need to have a PATTERN line before " +
|
|
"the IFORM line for " + ii.iclass)
|
|
self.extra_iforms_input[-1] = rest
|
|
else:
|
|
self.iform_input = rest
|
|
else:
|
|
setattr(self,token,rest.strip())
|
|
# die("Unhandled token in line: " + line)
|
|
else:
|
|
print("NEXT FEW LINES: ")
|
|
for x in lines[0:20]:
|
|
print("INPUT LINE: %s" % (x.strip()))
|
|
die("Missing colon in line: " + line)
|
|
|
|
if reached_closing_bracket:
|
|
if found_operands == False:
|
|
die("Did not find operands for " + self.iclass)
|
|
for k in list(structured_input_dict.keys()):
|
|
if structured_input_dict[k] == False:
|
|
if structured_input_tags[k]:
|
|
die("Required token missing: "+ k)
|
|
|
|
if debug:
|
|
msge("\tReturning...")
|
|
return True
|
|
return False
|
|
|
|
def add_scalable_attribute(self, scalable_widths, agi):
|
|
"""Look for operations that have width codes that are scalable
|
|
width codes (z,v,a,p,p2,s,spw8,spw,spw3,spw2,
|
|
etc. (auto-derived) , and add an attribute SCALABLE"""
|
|
|
|
scalable = False
|
|
|
|
for op in self.operands:
|
|
if op.oc2:
|
|
s= op.oc2.upper()
|
|
#msge("RRR Checking for %s in %s" % (s, str(scalable_widths)))
|
|
if s in scalable_widths:
|
|
scalable=True
|
|
break
|
|
|
|
if op.lookupfn_name:
|
|
#msge("OPNAME: " + op.lookupfn_name)
|
|
scalable = look_for_scalable_nt(agi, op.lookupfn_name)
|
|
if scalable:
|
|
break
|
|
|
|
if scalable:
|
|
s = "SCALABLE"
|
|
self.add_attribute(s)
|
|
|
|
|
|
|
|
def add_fixed_base_attribute(self):
|
|
"""Look for STACKPUSH/STACKPOP operands and then add an
|
|
attribute that says fixed_base0 or fixed_base1 depending on
|
|
which base reg has the SrSP operand."""
|
|
stack_memop_indx = -1
|
|
if vattr():
|
|
msgb("ATTRIBUTE-FOR-STACKOP: CHECKING", self.iclass)
|
|
for op in self.operands:
|
|
if op.is_ntluf():
|
|
if vattr():
|
|
msgb("ATTRIBUTE-NTLUF", "%s = %s" % (op.name,op.lookupfn_name))
|
|
if op.lookupfn_name == 'SrSP':
|
|
if op.name == 'BASE0':
|
|
stack_memop_indx = 0
|
|
elif op.name == 'BASE1':
|
|
stack_memop_indx = 1
|
|
else:
|
|
pass # skip other fields
|
|
if stack_memop_indx != -1:
|
|
if vattr():
|
|
msgb("ATTRIBUTE-FOR-STACKOP",
|
|
"%s memop index %s" % (self.iclass, stack_memop_indx))
|
|
s = "FIXED_BASE%d" % stack_memop_indx
|
|
self.add_attribute(s)
|
|
self.add_stack_attribute(stack_memop_indx)
|
|
|
|
|
|
def __str__(self):
|
|
return self.dump_str()
|
|
|
|
def dump_str(self, pad='', brief=False):
|
|
s = []
|
|
s.append(pad)
|
|
s.append(self.iclass)
|
|
if self.uname:
|
|
s.append(" uname=%s" % str(self.uname))
|
|
s.append(" inum=%s " % str(self.inum))
|
|
if field_check(self,'iform') and self.iform:
|
|
s.append(" iform=%s " % str(self.iform))
|
|
if field_check(self,'iform_input') and self.iform_input:
|
|
s.append(" iform_input=%s " % str(self.iform_input))
|
|
s.append("pattern len=%d\n" % len(self.ipattern.bits))
|
|
s.append(" %s ipattern: %s\n" % (pad,self.ipattern.just_bits()) )
|
|
|
|
if brief:
|
|
return ''.join(s)
|
|
if self.prebindings:
|
|
s.append('prebindings: \n\t' +
|
|
'\n\t'.join( [str(x) for x in list(self.prebindings.values())]) + '\n')
|
|
for op in self.operands:
|
|
s.append(pad)
|
|
s.append(" ")
|
|
s.append(op.dump_str(pad))
|
|
s.append("\n")
|
|
return ''.join(s)
|
|
|
|
|
|
def look_for_scalable_nt(agi, nt_name):
|
|
"""Look for a nonterminal that is sensitive to EOSZ. It looks
|
|
recursively at NTs in the patterns, but that does not occur in x86."""
|
|
try:
|
|
gi = agi.generator_dict[nt_name]
|
|
except:
|
|
die("Generator not found for nt_name: %s" % (nt_name))
|
|
|
|
for rule in gi.parser_output.instructions:
|
|
for b in rule.ipattern.bits:
|
|
if b.token == 'EOSZ':
|
|
return True
|
|
elif b.is_nonterminal():
|
|
r_nt_name = b.nonterminal_name()
|
|
if look_for_scalable_nt(agi, r_nt_name):
|
|
return True
|
|
return False
|
|
|
|
|
|
|
|
def mk_opnd(agi, s, default_vis='DEFAULT'):
|
|
op = opnds.parse_one_operand(s,
|
|
default_vis,
|
|
agi.xtypes,
|
|
agi.widths_dict)
|
|
return op
|
|
|
|
def add_flags_register_operand(agi,ii):
|
|
"""If the instruction has flags, then add a flag register operand."""
|
|
if field_check(ii,'flags_info') and \
|
|
ii.flags_info and ii.flags_info.x86_flags():
|
|
rw = ii.flags_info.rw_action()
|
|
(memidx_dummy,regidx) = find_max_memidx_and_regidx(ii.operands)
|
|
s = "REG%d=rFLAGS():%s:SUPP" % ( regidx, rw )
|
|
if vflag():
|
|
msgb("RFLAGS-APPEND", "%s <-- %s" % ( ii.iclass, s))
|
|
op = mk_opnd(agi,s)
|
|
if op:
|
|
ii.operands.append(op)
|
|
|
|
def add_flags_register_operand_all(agi,parser):
|
|
for ii in parser.instructions:
|
|
add_flags_register_operand(agi,ii)
|
|
|
|
|
|
def rewrite_stack_push(op,memidx,regidx):
|
|
s = []
|
|
#s.append("REG%d=SrSP():rw:SUPP" % (regidx))
|
|
s.append("MEM%d:w:%s:SUPP" % (memidx, op.oc2))
|
|
s.append("BASE%d=SrSP():rw:SUPP" % (memidx))
|
|
if memidx == 0:
|
|
s.append("SEG0=FINAL_SSEG0():r:SUPP") # note FINAL_SSEG0()
|
|
else:
|
|
s.append("SEG1=FINAL_SSEG1():r:SUPP") # note FINAL_SSEG1() ***
|
|
return s
|
|
|
|
def rewrite_stack_pop(op,memidx,regidx):
|
|
s = []
|
|
#s.append("REG%d=SrSP():rw:SUPP" % (regidx))
|
|
s.append("MEM%d:r:%s:SUPP" % (memidx, op.oc2))
|
|
s.append("BASE%d=SrSP():rw:SUPP" % (memidx))
|
|
if memidx == 0:
|
|
s.append("SEG0=FINAL_SSEG0():r:SUPP") # note FINAL_SSEG()
|
|
else:
|
|
s.append("SEG1=FINAL_SSEG1():r:SUPP") # note FINAL_SSEG1() ***
|
|
return s
|
|
|
|
|
|
|
|
def expand_stack_operand(op, memidx, regidx):
|
|
"""Replace the STACKPUSH and STACKPOP operands by a sequence of operands
|
|
@type op: opnds.operand_info_t
|
|
@param op: input operand that is a stack push or pop
|
|
|
|
@type memidx: integer
|
|
@param memidx: index of the memop we should use, either 0 or 1.
|
|
|
|
@type regidx: integer
|
|
@param regidx: index of the first register we should use for
|
|
the rSP() operand
|
|
|
|
@rtype: [ strings ]
|
|
@return: additional text of operands (to be processed) for the
|
|
stack pointer access, memop, base, & seg.
|
|
"""
|
|
if vstack():
|
|
msgb("EXPANDING STACK OP", "%s memidx %d regidx %d"
|
|
% (op.bits, memidx, regidx))
|
|
if op.bits == 'XED_REG_STACKPUSH':
|
|
out = rewrite_stack_push(op,memidx,regidx)
|
|
elif op.bits == 'XED_REG_STACKPOP':
|
|
out = rewrite_stack_pop(op,memidx,regidx)
|
|
else:
|
|
out = None
|
|
if vstack():
|
|
msgb("STACKOPS", str(out))
|
|
return out
|
|
|
|
|
|
|
|
|
|
def find_max_memidx_and_regidx(operands):
|
|
"find the maximum memidx and regidx"
|
|
memidx = 0
|
|
regidx = 0
|
|
verbose = False
|
|
for op in operands:
|
|
if verbose:
|
|
msgb("OPNAME", op.name)
|
|
if op.name == 'MEM0':
|
|
memidx = 1
|
|
elif op.name == 'MEM1':
|
|
memidx = 2 # this should cause an error if it is ever used
|
|
rnm = reg_operand_name_pattern.match(op.name)
|
|
if rnm:
|
|
current_regidx = int(rnm.group('regno'))
|
|
if verbose:
|
|
msgb("COMPARE REGS", "current %d max %d" %
|
|
( current_regidx, regidx))
|
|
if current_regidx >= regidx:
|
|
if verbose:
|
|
msgb("BUMPING regidx")
|
|
regidx = current_regidx+1
|
|
return (memidx, regidx)
|
|
|
|
def parse_operand_spec(agi,operand_spec):
|
|
"""build a list classes holding operand info"""
|
|
#print str(operand_spec)
|
|
operands = []
|
|
reset_any = False
|
|
for w in operand_spec:
|
|
op = mk_opnd(agi,w)
|
|
if op:
|
|
if op.type == 'xed_reset':
|
|
reset_any = True
|
|
else:
|
|
operands.append(op)
|
|
##############################################################
|
|
# expand stack operands
|
|
#
|
|
found_stackop = None
|
|
for op in operands:
|
|
# msgb("BITS", str(op.bits))
|
|
if op.bits == 'XED_REG_STACKPUSH' or op.bits == 'XED_REG_STACKPOP':
|
|
found_stackop = op
|
|
break
|
|
if found_stackop:
|
|
(memidx, regidx) = find_max_memidx_and_regidx(operands)
|
|
new_strings = expand_stack_operand(found_stackop, memidx, regidx)
|
|
# make new operands based on these strings.
|
|
if new_strings:
|
|
for s in new_strings:
|
|
new_op = mk_opnd(agi,s)
|
|
if new_op:
|
|
operands.append(new_op)
|
|
#
|
|
##############################################################
|
|
return (operands, reset_any)
|
|
|
|
|
|
##################################################################
|
|
# Structured input / output of instructions
|
|
##################################################################
|
|
|
|
|
|
def is_nonterminal_line(s):
|
|
g = nonterminal_start_pattern.search(s)
|
|
if g:
|
|
# remove everything from the parens to the end of the line
|
|
# including two colons
|
|
t = parens_to_end_of_line.sub('', s)
|
|
wrds = t.split()
|
|
short_nt_name = wrds[-1]
|
|
if len(wrds) == 1:
|
|
type = None
|
|
msge("NONTERMINAL: " + short_nt_name + " notype")
|
|
else:
|
|
type = wrds[0]
|
|
msge("NONTERMINAL: " + short_nt_name + " type= " + type)
|
|
return (short_nt_name, type)
|
|
return (None,None)
|
|
|
|
def remove_instructions(agi):
|
|
for g in agi.generator_list:
|
|
ii = g.parser_output.instructions[0]
|
|
if field_check(ii,'iclass'):
|
|
g.parser_output = remove_overridden_versions(g.parser_output)
|
|
|
|
def remove_overridden_versions(parser):
|
|
"""Remove instructions that have newer versions using a dictionary
|
|
of lists."""
|
|
d = {}
|
|
for ii in parser.instructions:
|
|
if ii.iclass in parser.deleted_instructions:
|
|
continue # drop this record
|
|
if ii.uname in parser.deleted_unames:
|
|
msge("DROPPING UNAME %s" % (ii.uname))
|
|
continue # drop this record
|
|
if ii.iclass in d:
|
|
if ii.version == d[ii.iclass][0].version:
|
|
d[ii.iclass].append(ii)
|
|
elif ii.version > d[ii.iclass][0].version:
|
|
# we have an updated version. drop the old stuff and start over
|
|
del d[ii.iclass]
|
|
d[ii.iclass] = [ii]
|
|
else:
|
|
pass # drop this record
|
|
else:
|
|
# add first element of this iclass
|
|
d[ii.iclass] = [ii]
|
|
|
|
iis = []
|
|
for ilist in list(d.values()):
|
|
iis.extend(ilist)
|
|
parser.instructions = iis
|
|
return parser
|
|
|
|
def read_input(agi, lines):
|
|
"""Read the input from a flat token-per-line file or a structured
|
|
ISA input file"""
|
|
msge("read_input " + str(global_inum))
|
|
# first line must be a nonterminal
|
|
(nt_name, nt_type) = is_nonterminal_line(lines[0])
|
|
if not nt_name:
|
|
die("Did not find a nonterminal: " + lines[0])
|
|
|
|
parser = None
|
|
# see if we have encountered this nonterminal before
|
|
try:
|
|
gi = agi.generator_dict[nt_name]
|
|
# we have a re-occurrence of an earlier nonterminal. We extend it now.
|
|
msge("FOUND OLD PARSER FOR " + nt_name)
|
|
parser = gi.parser_output
|
|
except:
|
|
# need to make a new generator & parser
|
|
parser = parser_t()
|
|
parser.nonterminal_line = lines[0].strip()
|
|
parser.nonterminal_name = nt_name
|
|
parser.nonterminal_type = nt_type
|
|
gi = agi.make_generator(nt_name)
|
|
gi.parser_output = parser
|
|
agi.nonterminal_dict.record_nonterminal(nt_name, nt_type)
|
|
|
|
msge("Nonterminal " + parser.nonterminal_line)
|
|
msge("Nonterminal name " + parser.nonterminal_name)
|
|
lines.pop(0)
|
|
|
|
# The {...} defined patterns must have the substring "INSTRUCTIONS" in them
|
|
if instructions_pattern.search(parser.nonterminal_name):
|
|
nlines = read_structured_input(agi,
|
|
agi.common.options,
|
|
parser,
|
|
lines,
|
|
agi.common.state_bits)
|
|
return nlines
|
|
else:
|
|
return read_flat_input(agi,
|
|
agi.common.options,parser,
|
|
lines,
|
|
agi.common.state_bits)
|
|
|
|
def read_structured_input(agi, options, parser, lines, state_dict):
|
|
msge("read_structured_input")
|
|
while len(lines) != 0:
|
|
if verb4():
|
|
msge("NEXTLINE " + lines[0])
|
|
first_line = no_comments(lines[0])
|
|
if first_line == '':
|
|
lines.pop(0)
|
|
continue
|
|
first_line = slash_expand.expand_all_slashes(first_line)
|
|
|
|
if udelete_pattern.search(first_line):
|
|
m = udelete_full_pattern.search(first_line)
|
|
uname = m.group('uname')
|
|
msge("REGISTERING UDELETE %s" % (uname))
|
|
parser.deleted_unames[uname] = True
|
|
lines.pop(0)
|
|
elif delete_iclass_pattern.search(first_line):
|
|
m = delete_iclass_full_pattern.search(first_line)
|
|
iclass = m.group('iclass')
|
|
parser.deleted_instructions[iclass] = True
|
|
lines.pop(0)
|
|
|
|
|
|
elif nonterminal_start_pattern.search(first_line):
|
|
msge("Hit a nonterminal, returning at: " + first_line )
|
|
break
|
|
else:
|
|
ii = instruction_info_t()
|
|
okay = ii.read_structured_flexible(lines)
|
|
if okay:
|
|
#mbuild.msgb("PATTERN:", ii.ipattern_input)
|
|
# when there are multiple patterns/operands in the
|
|
# structured input, we flatten them out here, making
|
|
# multiple complete records, one per
|
|
# pattern/set-of-operands.
|
|
flat_ii_recs = expand_hierarchical_records(ii)
|
|
|
|
for flat_ii in flat_ii_recs:
|
|
flat_ii.refine_parsed_line(agi,state_dict)
|
|
flat_ii.add_fixed_base_attribute()
|
|
flat_ii.add_scalable_attribute(agi.scalable_widths, agi)
|
|
if flat_ii.otherwise_ok:
|
|
parser.otherwise_ok = True # FIXME 2008-09-25: is this used?
|
|
else:
|
|
|
|
parser.instructions.append(flat_ii)
|
|
|
|
|
|
msge("parser returning with " + str(len(lines)) + ' lines remaining.')
|
|
return lines
|
|
|
|
##################################################################
|
|
|
|
def junk_line(line):
|
|
if len(line) == 0:
|
|
return True
|
|
if line[0] == '#':
|
|
return True
|
|
return False
|
|
|
|
# $$ parse_t
|
|
class parser_t(object):
|
|
def __init__(self):
|
|
self.nonterminal_line = ''
|
|
self.nonterminal_name = ''
|
|
# the actual nonterminal_type is NOW IGNORED 2008-07-22 I take
|
|
# the value from the operand storage fields type spec. I still
|
|
# use the existence of the nonterminal return type to indicate
|
|
# that an NT is a NTLUF.. FIXME!!
|
|
self.nonterminal_type = None # for lookup functions only
|
|
|
|
# list of partitionable_info_t or instruction_info_t, which is a
|
|
# subclass of partitionable_info_t.
|
|
self.instructions = []
|
|
|
|
self.deleted_instructions = {}
|
|
self.deleted_unames = {}
|
|
|
|
# if epsilon actions result in errors, otherwise_ok is False. If
|
|
# epsilon actions result in no-error, then otherwise_ok should
|
|
# be set to true.
|
|
self.otherwise_ok = False
|
|
|
|
|
|
def is_lookup_function(self):
|
|
if self.nonterminal_type != None:
|
|
return True
|
|
return False
|
|
|
|
def dump_structured(self,fp):
|
|
"Print out the expanded records."
|
|
for ii in self.instructions:
|
|
slist = ii.dump_structured()
|
|
for s in slist:
|
|
fp.write(s)
|
|
fp.write('\n')
|
|
|
|
|
|
def print_structured_output(self,fp):
|
|
"Print the input in a structuctured token-per-line fashion"
|
|
fp.write(self.nonterminal_line + "\n")
|
|
fp.write("\n")
|
|
self.dump_structured(fp)
|
|
|
|
|
|
|
|
def read_flat_input(agi, options, parser,lines,state_dict):
|
|
"""These are the simple format records, one per line. Used for
|
|
non-instruction things to make partitionable objects"""
|
|
msge("read_flat_input " + str(global_inum))
|
|
while len(lines) > 0:
|
|
if verb4():
|
|
msge("NEXTLINE " + lines[0])
|
|
first_line = no_comments(lines[0])
|
|
if first_line == '':
|
|
lines.pop(0)
|
|
continue
|
|
first_line = slash_expand.expand_all_slashes(first_line)
|
|
if nonterminal_start_pattern.search(first_line):
|
|
msge("Hit a nonterminal, returning at: " + first_line )
|
|
break
|
|
|
|
try:
|
|
(new_bits, bindings) = first_line.split('|')
|
|
except ValueError:
|
|
die('Could not split line in to 2 pieces based on vertical bar: [' +
|
|
first_line + ']')
|
|
|
|
(opcode_spec,
|
|
extra_bindings,
|
|
all_bit_infos,
|
|
all_prebindings,
|
|
otherwise_ok) = parse_opcode_spec( agi, new_bits, state_dict)
|
|
|
|
if otherwise_ok:
|
|
parser.otherwise_ok = True # FIXME 2008-09-25 need to change this
|
|
# if 'otherwise' node have RHS support.
|
|
lines.pop(0)
|
|
continue
|
|
|
|
operands_input = bindings.split()
|
|
(operands, reset_for_prefix) = parse_operand_spec(agi, operands_input)
|
|
if extra_bindings:
|
|
extra_operands = parse_extra_operand_bindings(agi, extra_bindings)
|
|
operands.extend(extra_operands)
|
|
|
|
# now put opcode_spec, and operands in to a data structure
|
|
|
|
## FIXME add a table and line number for the name?
|
|
pi = partitionable_info_t('',new_bits, operands_input)
|
|
pi.input_str = first_line
|
|
|
|
pi.ipattern = bits_list_t()
|
|
pi.ipattern.bits = all_bit_infos
|
|
|
|
pi.prebindings = all_prebindings
|
|
|
|
pi.operands = operands # list of opnds.operand_info_t
|
|
pi.reset_for_prefix = reset_for_prefix
|
|
|
|
parser.instructions.append(pi) # FIXME: instruction is a misnomer here
|
|
|
|
lines.pop(0)
|
|
|
|
return lines
|
|
|
|
|
|
############################################################################
|
|
## $$ Graph build
|
|
############################################################################
|
|
|
|
# $$ graph_node_t
|
|
class graph_node(object):
|
|
|
|
global_node_num = 0
|
|
|
|
def __init__(self, token,bitpos):
|
|
self.id = self.__class__.global_node_num
|
|
#msge("CREATE NODE %d" % (self.id))
|
|
self.__class__.global_node_num += 1
|
|
self.token = token
|
|
self.instructions = [] # a list of instruction_info_t class items
|
|
|
|
# the relative bit position, mod 8, assuming nonterminals return bytes
|
|
self.bitpos_mod8 = bitpos & 7
|
|
|
|
# number of bits we use to decide on the next node.
|
|
self.decider_bits = 0
|
|
|
|
# number of bits we accept and skip over to get to the next
|
|
# decider-group-of-bits
|
|
self.skipped_bits = 0
|
|
|
|
# a nonterminal that follows this node
|
|
self.nonterminal = None
|
|
|
|
# an operand decision point
|
|
self.operand_decider = None
|
|
|
|
# When we have to go back and pick up a bit that was ignored
|
|
# earlier, we list it here.
|
|
self.back_split_pos = None
|
|
|
|
# Usually we want the epsilon actions to result in decode
|
|
# errors. When we want to permit epsilon action for prefix-type
|
|
# nonterminals, then we set self.otherwise_ok to True.
|
|
self.otherwise_ok = False
|
|
|
|
self.next = {}
|
|
|
|
# The capture function_object_t for the operands we need to
|
|
# capture at this node.
|
|
self.capture_function = None
|
|
|
|
def is_nonterminal(self):
|
|
if self.nonterminal != None:
|
|
return True
|
|
return False
|
|
|
|
def is_operand_decider(self):
|
|
if self.operand_decider != None:
|
|
return True
|
|
return False
|
|
def value_test_node(self):
|
|
"""returns (value_test, value_to_test) """
|
|
if self.is_operand_decider():
|
|
if len(self.next) == 2:
|
|
found_value = False
|
|
found_other = False
|
|
value = None
|
|
for k,nxt in self.next.items():
|
|
if k == 'other' and found_other==False:
|
|
found_other = True
|
|
elif found_value == False:
|
|
found_value = True
|
|
value = k
|
|
else:
|
|
# multiple values
|
|
return (False, None)
|
|
if found_value and found_other:
|
|
return (True, value)
|
|
return (False, None)
|
|
|
|
def leaf(self):
|
|
if len(self.next) == 0:
|
|
return True
|
|
return False
|
|
|
|
def dump_str(self,pad=''):
|
|
eol = "\n"
|
|
s = pad + 'id: ' + str(self.id)
|
|
s += ' token: ' + str(self.token)
|
|
s += ' next nodes: %d' % (len(self.next))
|
|
s += ' insts: %d' % (len(self.instructions))
|
|
s += eol
|
|
s += pad + 'skipped_bits: ' + str(self.skipped_bits)
|
|
s += ' decider_bits: ' + str(self.decider_bits)
|
|
if self.otherwise_ok:
|
|
s += ' otherwise_ok: ' + str(self.otherwise_ok)
|
|
if self.back_split_pos:
|
|
s += ' back_split_pos: ' + str(self.back_split_pos)
|
|
if self.nonterminal != None:
|
|
s += " NONTERMINAL:" + str(self.nonterminal)
|
|
if self.operand_decider:
|
|
s += ' OPERAND-DECIDER:' + str(self.operand_decider)
|
|
s += eol
|
|
# only print instructions for leaf nodes
|
|
if len(self.next) == 0:
|
|
s += pad + 'insts: ' + eol
|
|
for ii in self.instructions:
|
|
s += ii.dump_str(pad + ' ') + eol
|
|
return s
|
|
def dump(self,pad=''):
|
|
msge(self.dump_str(pad))
|
|
|
|
|
|
|
|
|
|
def new_node(graphnode, token, bitpos):
|
|
node = graph_node(token,bitpos)
|
|
graphnode.next[token] = node
|
|
return node
|
|
|
|
def get_bit(ii,bitpos):
|
|
if bitpos >= len(ii.ipattern.bits):
|
|
return 'badbit'
|
|
return ii.ipattern.bits[bitpos]
|
|
|
|
|
|
def collect_required_values(instructions, bitpos):
|
|
"""Return a list of the required values for a list of operand
|
|
deciders."""
|
|
d = []
|
|
for ii in instructions:
|
|
if not ii.ipattern.bits[bitpos].is_operand_decider():
|
|
die("Died looking for an operand decider")
|
|
operand_decider = ii.ipattern.bits[bitpos]
|
|
if operand_decider.test == 'eq':
|
|
if operand_decider.requirement not in d:
|
|
d.append(operand_decider.requirement)
|
|
return d
|
|
|
|
|
|
def partition_by_required_values(options, instructions, bitpos, token,
|
|
required_values, state_space, splatter=True,
|
|
operand_storage_dict=None):
|
|
"""Return a dictionary of lists of instructions, split by the
|
|
elements of the required_values list"""
|
|
#msge("\n\n\nNEW PARTITION:" )
|
|
d = {}
|
|
all_values = {}
|
|
for ii in instructions:
|
|
if not ii.ipattern.bits[bitpos].is_operand_decider():
|
|
die("THIS DOES NOT HAPPEN")
|
|
# we have an operand decider
|
|
operand_decider = ii.ipattern.bits[bitpos]
|
|
if vod():
|
|
msge("PARTITIONING OPERAND DECIDER TOKEN: " +
|
|
str(operand_decider.token))
|
|
if operand_decider.token != token:
|
|
die("Mixed partitionings of operand_deciders: splitting on " + token +
|
|
" but encountered " + operand_decider.token)
|
|
if operand_decider.test == 'eq':
|
|
# just one instruction needs to be placed
|
|
if vod():
|
|
msge("Setting EQ OD %s" % operand_decider.requirement)
|
|
if operand_decider.requirement not in d:
|
|
d[ operand_decider.requirement ] = [ ii ]
|
|
else:
|
|
d[ operand_decider.requirement ].append(ii)
|
|
all_values[operand_decider.requirement]=True
|
|
elif operand_decider.test == 'ne':
|
|
# add to trimmed list of req'd vals ( excluding this value)
|
|
# THIS DUPLICATES (references to) NODES if there is more than
|
|
# one choice.
|
|
if operand_decider.token in state_space:
|
|
all_values_for_this_od = state_space[operand_decider.token]
|
|
if vod():
|
|
msge("NE OD: all values from state space %s" %
|
|
(str(all_values_for_this_od)))
|
|
else:
|
|
try:
|
|
osf = operand_storage_dict[operand_decider.token]
|
|
if osf.ctype.find('enum') == -1:
|
|
nvalues = 1 << int(osf.bitwidth)
|
|
#all_values_for_this_od = [ str(x) for x in range(0,nvalues) ]
|
|
all_values_for_this_od = range(0,nvalues)
|
|
if vod():
|
|
msge("Synthesized values for %s: %s" %
|
|
( operand_decider.token,
|
|
", ".join( [ str(x) for x in all_values_for_this_od])))
|
|
except:
|
|
die("could not find %s in state space dictionary" %
|
|
(operand_decider.token))
|
|
|
|
if vod():
|
|
msge("All values for OD: %s" %
|
|
", ".join( [ str(x) for x in all_values_for_this_od] ))
|
|
for a in all_values_for_this_od:
|
|
all_values[a]=True
|
|
trimmed_vals = list(filter(lambda x: x != operand_decider.requirement,
|
|
all_values_for_this_od))
|
|
if len(trimmed_vals) == 0:
|
|
die("We had a not-equals requirement but did" +
|
|
" not have any other values to bin against.")
|
|
|
|
# DO NOT MAKE ONE NODE PER trimmed_vals element - that
|
|
# explodes the graph size.
|
|
#msge("\tAdding other with values " + str(trimmed_vals))
|
|
other = 'other'
|
|
if other not in d:
|
|
d[ other ] = [ (trimmed_vals,ii) ]
|
|
else:
|
|
d[ other ].append((trimmed_vals,ii) )
|
|
|
|
#msge("RETURNING FROM PARTITION: %s" % ( str(d.keys())))
|
|
return (d, list(all_values.keys()) )
|
|
|
|
|
|
def all_same_operand_decider(ilist,bitpos):
|
|
"""Return false if not all of the next bits are the same
|
|
operand-decider, also return operand decider if True"""
|
|
last = None
|
|
for i in ilist:
|
|
plen = len(i.ipattern.bits)
|
|
if bitpos >= plen:
|
|
#msge("Fell off end of bits")
|
|
return (False,None)
|
|
|
|
# They can have different required values, but they must be the
|
|
# same deciding token.
|
|
|
|
if i.ipattern.bits[bitpos].is_operand_decider():
|
|
if last == None:
|
|
last = i.ipattern.bits[bitpos]
|
|
elif last.token != i.ipattern.bits[bitpos].token:
|
|
return (False, None)
|
|
else:
|
|
return (False, None) # not an operand decider
|
|
if last:
|
|
return (True, last)
|
|
return (False, None)
|
|
|
|
|
|
|
|
def all_same_nonterminal(ilist,bitpos):
|
|
"""Return false if not all of the next bits are the same
|
|
nonterminal, also return nonterminal if True"""
|
|
last_nonterminal = None
|
|
for i in ilist:
|
|
|
|
plen = len(i.ipattern.bits)
|
|
if bitpos >= plen:
|
|
#msge("Fell off end of bits")
|
|
return (False,None)
|
|
if i.ipattern.bits[bitpos].is_nonterminal():
|
|
if last_nonterminal == None:
|
|
last_nonterminal = i.ipattern.bits[bitpos]
|
|
elif last_nonterminal != i.ipattern.bits[bitpos]:
|
|
#msge("Differing NTs: [" + str(last_nonterminal)+ "] vs ["
|
|
#+ str(i.ipattern.bits[bitpos]) + ']')
|
|
return (False, None)
|
|
else:
|
|
#msge("Not a nonterminal")
|
|
return (False, None) # not a nonterminal
|
|
if last_nonterminal:
|
|
return (True, last_nonterminal)
|
|
return (False, None)
|
|
|
|
|
|
def move_candidate_od_to_front(bitpos, candidate_od, bit_info_t_list):
|
|
"""Move a speicific od names candidate_od from wherever it is in
|
|
the list (after bitpos) to the location bitpos"""
|
|
|
|
found = False
|
|
for i,b in enumerate(bit_info_t_list[bitpos+1:]):
|
|
if b.is_operand_decider():
|
|
if b.token == candidate_od:
|
|
found = True
|
|
badpos = i + bitpos + 1
|
|
if found:
|
|
# move bit_info_t_list[badpos] to just before bitpos
|
|
if vrearrange():
|
|
msge("BEFORE REARRANGE: bitpos = %d and badpos = %d" %
|
|
(bitpos, badpos))
|
|
for b in bit_info_t_list:
|
|
msge( "\t" + str(b) )
|
|
z = bit_info_t_list[badpos]
|
|
del bit_info_t_list[badpos]
|
|
bit_info_t_list.insert(bitpos,z)
|
|
if vrearrange():
|
|
msge("AFTER REARRANGE:")
|
|
for b in bit_info_t_list:
|
|
msge( "\t" +str(b) )
|
|
|
|
return found
|
|
|
|
def renumber_one_ipattern(i):
|
|
bitpos = 0
|
|
for p in i.ipattern.bits:
|
|
p.pbit = bitpos
|
|
bitpos = bitpos + 1
|
|
|
|
def renumber_bitpos(ilist):
|
|
for i in ilist:
|
|
renumber_one_ipattern(i)
|
|
|
|
def rearrange_at_conflict(ilist,bitpos):
|
|
"""Try to rearrange ODs at a conflict"""
|
|
|
|
# build up a list of candidate ods
|
|
|
|
# FIXME 2008-11-12 Mark Charney: could search for all sequential
|
|
# ODs rather than just one neighboring OD.
|
|
|
|
candidate_ods = []
|
|
for i in ilist:
|
|
if bitpos >= len(i.ipattern.bits):
|
|
return False
|
|
if i.ipattern.bits[bitpos].is_operand_decider():
|
|
t = i.ipattern.bits[bitpos].token
|
|
if t not in candidate_ods:
|
|
candidate_ods.append(t)
|
|
|
|
# look ahead one spot too...
|
|
nbitpos = bitpos+1
|
|
if nbitpos < len(i.ipattern.bits):
|
|
if i.ipattern.bits[nbitpos].is_operand_decider():
|
|
t = i.ipattern.bits[nbitpos].token
|
|
if t not in candidate_ods:
|
|
candidate_ods.append(t)
|
|
|
|
if len(candidate_ods) == 0:
|
|
msge("REARRANGE: NO CANDIDATE OD")
|
|
return False
|
|
|
|
retry = True
|
|
for candidate_od in candidate_ods:
|
|
if retry == False:
|
|
break
|
|
msge("REARRANGE ATTEMPT using %s" % (candidate_od))
|
|
retry = False
|
|
for i in ilist:
|
|
if i.ipattern.bits[bitpos].is_operand_decider():
|
|
if candidate_od == i.ipattern.bits[bitpos].token:
|
|
msge("\tSKIPPING %s inum %d -- already fine" %
|
|
( i.get_iclass(), i.inum))
|
|
else:
|
|
msge("\tREARRANGE needs to juggle: %s inum %d" %
|
|
( i.get_iclass(), i.inum))
|
|
# attempt to juggle ODs in i.ipattern.bits to get
|
|
# candidate_od in to bitpos
|
|
if move_candidate_od_to_front(bitpos,
|
|
candidate_od,
|
|
i.ipattern.bits):
|
|
msge("\tREARRANGE one pattern worked for %s inum %d" %
|
|
( i.get_iclass(), i.inum))
|
|
else:
|
|
retry = True
|
|
msge("\tREARRANGE FAILED for %s. Trying again..." %
|
|
(candidate_od))
|
|
break # hit the outer loop
|
|
|
|
if retry == True:
|
|
msge("REARRANGE FAILED for all ODs")
|
|
return False
|
|
|
|
# make sure they are all the same OD at this bitpos now
|
|
candidate_od = None
|
|
for i in ilist:
|
|
if i.ipattern.bits[bitpos].is_operand_decider():
|
|
if candidate_od == None:
|
|
candidate_od = i.ipattern.bits[bitpos].token
|
|
elif candidate_od != i.ipattern.bits[bitpos].token:
|
|
msge("REARRANGE FAILED AT END(1)! bitpos = %d" % (bitpos))
|
|
msge( i.dump_str() )
|
|
return False
|
|
else:
|
|
print_node(ilist)
|
|
msge("REARRANGE FAILED AT END(2)!")
|
|
return False
|
|
msge("REARRANGE: FIXED OD CONFLICT!")
|
|
|
|
# since we rearranged, we need to renumber the pbits or the
|
|
# extraction will not work properly.
|
|
renumber_bitpos(ilist)
|
|
return True
|
|
|
|
|
|
def some_funky_spot(ilist,bitpos):
|
|
"""Return true if some pattern has a nonterminal or operand decider"""
|
|
for i in ilist:
|
|
if bitpos < len(i.ipattern.bits):
|
|
if i.ipattern.bits[bitpos].is_nonterminal():
|
|
return True
|
|
if i.ipattern.bits[bitpos].is_operand_decider():
|
|
return True
|
|
return False
|
|
|
|
def print_split(others,ones,zeros,brief=False):
|
|
for s,lst in [('Others',others),
|
|
('Ones', ones),
|
|
('Zeros', zeros)]:
|
|
msge(s +': ')
|
|
for ii in lst:
|
|
try:
|
|
msge( ii.dump_str(brief=brief))
|
|
except:
|
|
msge( "XUNKNOWN: " + str(ii) )
|
|
|
|
|
|
def partition_nodes(ilist,bitpos):
|
|
"""return a tuple of lists of nodes whose next bit is zero, one or
|
|
a letter (others)"""
|
|
zeros = []
|
|
ones = []
|
|
others = []
|
|
zero = '0'
|
|
one= '1'
|
|
for inst in ilist:
|
|
bit = inst.ipattern.bits[bitpos]
|
|
if bit.value == zero:
|
|
zeros.append(inst)
|
|
elif bit.value == one:
|
|
ones.append(inst)
|
|
else:
|
|
others.append(inst)
|
|
return (ones,zeros,others)
|
|
|
|
def print_node(ilist):
|
|
for ii in ilist:
|
|
msge("\t" + ii.dump_str())
|
|
|
|
def at_end_of_instructions(ilist, bitpos):
|
|
"""If all instructions are done with their bits, return 1
|
|
None). Check for length problems too. If not done, return 0.
|
|
If there is an error, return -1"""
|
|
done = False
|
|
notdone = False
|
|
for ii in ilist:
|
|
if isinstance(ii,tuple):
|
|
die("Bad tuple where instruction expected: "+ str(ii))
|
|
if bitpos >= len(ii.ipattern.bits):
|
|
done = True
|
|
else:
|
|
notdone = True
|
|
if done:
|
|
if notdone:
|
|
msge("Length error: some instructions done and some" +
|
|
" are not done simultaneously")
|
|
msge("ilist len = " + str(len(ilist)))
|
|
msge("\n\nILIST:")
|
|
for ii in ilist:
|
|
msge( 'bitpos:' + str(bitpos) +
|
|
' len-pattern:' + str( len(ii.ipattern.bits)))
|
|
if (len(ii.ipattern.bits)) == 0:
|
|
msge("BAD INST: %s" % ( str(ii)))
|
|
msge("\n\nNODE:")
|
|
print_node(ilist)
|
|
#die("Dying")
|
|
return -1 # problem: some done, some not done
|
|
else:
|
|
return 1 # all is well, all done
|
|
return 0 # not done yet
|
|
|
|
def no_dont_cares(instructions, bitpos):
|
|
"Return True if there are no don't cares"
|
|
for i in instructions:
|
|
if i.ipattern.bits[bitpos].is_dont_care():
|
|
return False
|
|
return True
|
|
|
|
def some_different(instructions,bitpos):
|
|
"""Return True if there are ones and zeros and no don't cares,
|
|
nonterminals or operand deciders"""
|
|
zero = '0'
|
|
one= '1'
|
|
|
|
zeros = 0
|
|
ones = 0
|
|
for i in instructions:
|
|
if i.ipattern.bits[bitpos].value == zero:
|
|
zeros += 1
|
|
elif i.ipattern.bits[bitpos].value == one:
|
|
ones += 1
|
|
if zeros > 0 and ones > 0:
|
|
return True
|
|
return False
|
|
|
|
def scan_backwards_for_distinguishing_bit(instructions,bitpos):
|
|
"""Return a tuple (t/f, bitpos) that says where we can partition
|
|
this node further (when possible)."""
|
|
|
|
b = bitpos - 1
|
|
while b >= 0:
|
|
if no_dont_cares(instructions,b):
|
|
if some_different(instructions,b):
|
|
msge("FALLBACK: we can parition on the 1s and 0s at bitpos " +
|
|
str(b))
|
|
return (True, b)
|
|
b = b - 1
|
|
msge("FALLBACK: No bits left to examine: at bit %d" % (bitpos))
|
|
return (False, None)
|
|
|
|
def convert_splitpos_to_bit_index(graph,splitpos):
|
|
"""Convert the fake bitposition in splitpos in to a real bit
|
|
position by skipping leading operand deciders. Intervening
|
|
nonterminals might mess this up??? FIXME
|
|
"""
|
|
i = graph.instructions[0]
|
|
real_bits = 0
|
|
for b in i.ipattern.bits[0:splitpos]:
|
|
if not b.is_operand_decider():
|
|
real_bits += 1
|
|
msge("BACKSPLIT fake bitpos: %d real bitpos: %d\n" % (splitpos, real_bits))
|
|
return real_bits
|
|
|
|
|
|
|
|
|
|
def back_split_graph(common, graph, bitpos, skipped_bits, splitpos):
|
|
"""Partition based on splitpos and then recur in to build_sub_graph
|
|
for the partitions."""
|
|
|
|
options = common.options
|
|
msge("back_split_graph: based on " + str(splitpos))
|
|
(ones,zeros,others) = partition_nodes(graph.instructions,splitpos)
|
|
if vbuild():
|
|
s = "ones %d zeros %d others %d" % (len(ones), len(zeros), len(others))
|
|
msge('back_split_graph: ' + s )
|
|
if len(others) > 0:
|
|
die("We encountered some junk on a back-split")
|
|
|
|
if len(zeros) == 0:
|
|
die("We didn't have any zeros in the back-split partition")
|
|
if len(ones) == 0:
|
|
die("We didn't have any ones in the back-split partition")
|
|
|
|
graph.skipped_bits = skipped_bits
|
|
graph.decider_bits = 1
|
|
graph.back_split_pos = convert_splitpos_to_bit_index(graph,splitpos)
|
|
|
|
# zero child node
|
|
znode = new_node(graph,'0',bitpos)
|
|
znode.instructions.extend(zeros)
|
|
build_sub_graph(common,znode,bitpos, 0) # RECUR
|
|
|
|
# one child node
|
|
onode = new_node(graph,'1',bitpos)
|
|
onode.instructions.extend(ones)
|
|
build_sub_graph(common,onode,bitpos, 0) # RECUR
|
|
|
|
|
|
|
|
# global hack -- FIXME: 2007-07-10 to get the operand storage
|
|
# dictionary in to
|
|
# partition_by_required_values.
|
|
g_operand_storage_dict = None
|
|
|
|
def build_sub_graph(common, graph, bitpos, skipped_bits):
|
|
"""Recursively partition instructions based on 1s, 0s and
|
|
placeholder letters"""
|
|
global g_operand_storage_dict
|
|
options = common.options
|
|
|
|
# expand_dont_cares is an important control for the graph
|
|
# building. If expand_dont_cares is false, whenever we see a
|
|
# don't-care in some thing at the next bit position, then we skip
|
|
# that bit in the graph formation. This leads to problems when
|
|
# skipped 1s and 0s are required to disambiguate the tree
|
|
# downstream. When expand_dont_cares is true, then whenever we have
|
|
# some 1s or 0s that happen to collide with a don't-care in some
|
|
# thing at the next bit positiona, then we copy all the don't-care
|
|
# ("others") on both zero and one successor nodes. Something down
|
|
# stream will disambiguate them necessarily. The nice thing about
|
|
# expand_dont_cares being true is that (a) one does not have the
|
|
# problem alluded to above (which I've only seen when processing
|
|
# the SIB BASE table, and hacked around by swapping the nonterminal
|
|
# argument order). And (b), we don't have to confirm that bits we
|
|
# skipped have required values when we get down to just one
|
|
# decoding option.
|
|
#
|
|
# I only expand the don't cares if they happen to line up with some
|
|
# nodes that have 1s or 0s in them. This attempts to prevent the
|
|
# immediates from exploding the graph. But if immediates line up
|
|
# with some 1s and 0s, they'll get expanded. The 1B PUSH does not
|
|
# get expanded because the 0101_0xxx is unique once you get to the
|
|
# 0101_0 part. So that would have to be coalesced in graph merging
|
|
# later on.
|
|
#
|
|
#
|
|
# WARNING: When expand_dont_cares is true the graph currently
|
|
# explodes in size.
|
|
expand_dont_cares = False
|
|
|
|
# skip_constants is an important control for the graph building. If
|
|
# skip_constants is false, then when the next bit for all
|
|
# instructions is 1 or the next bit for all instructions is 0, then
|
|
# we pretend it is decider bit and make a new node. This node is
|
|
# trivial because it only has one out-edge, but it enabls the graph
|
|
# merger to merge it in to the parent node when merging is
|
|
# done. This results in a smaller graph. When skip_constants is
|
|
# True, then we "skip" bits where the next bit is always 1 or 0 for
|
|
# all instructions. In this case, we still need to confirm that we
|
|
# have a 1 or 0 at decode time, but it doesn't contribute new
|
|
# information. After node merging, the graph is smaller when
|
|
# skip_constants is False at graph build time, so that is the
|
|
# preferred setting.
|
|
skip_constants = False
|
|
|
|
# stop_early is another important control. If stop_early is True,
|
|
# then we do not continue building the graph when we are down to
|
|
# one instruction. When stop_early is False, we keep going even
|
|
# when there is only one instruction. Setting stop_early to False
|
|
# is required to get the nonterminals that may be down stream built
|
|
# in to the graph.
|
|
stop_early = False
|
|
|
|
if vbuild():
|
|
msge("[SUBGRAPH BUILD] Token %s ninst %d" %
|
|
(str(graph.token), len(graph.instructions)))
|
|
for ii in graph.instructions:
|
|
msge(ii.dump_str(' '))
|
|
|
|
if stop_early and len(graph.instructions) == 1:
|
|
# we are down to one option. We must verify when decoding, but
|
|
# the path is now determined fully.
|
|
return
|
|
|
|
# Go to the next bit. Note: we initialize the bitpos to -1 so that
|
|
# when we advance it here the first time, we start with bit zero.
|
|
|
|
bitpos += 1
|
|
if vbuild():
|
|
msge("Token " + str(graph.token) + " Reached bit " + str(bitpos))
|
|
|
|
at_end = at_end_of_instructions(graph.instructions,bitpos)
|
|
if at_end == 1:
|
|
if vbuild():
|
|
msge("Hit end of instructions -- skipped bits " + str(skipped_bits))
|
|
if len(graph.instructions) > 1:
|
|
msge("\nBUILD ERROR: more than one leaf when ran out of bits:")
|
|
for ii in graph.instructions:
|
|
msge(ii.dump_str(' '))
|
|
msge("\n\n")
|
|
(okay, splitpos) = scan_backwards_for_distinguishing_bit(
|
|
graph.instructions,bitpos)
|
|
if okay:
|
|
msge("NEED TO BACKSPLIT AT POSITION %d" % (splitpos))
|
|
# redo this bit (hence bitpos-1) once we split based on splitpos
|
|
back_split_graph(common, graph, bitpos-1, skipped_bits, splitpos)
|
|
return
|
|
else:
|
|
msge("Back-split failed to solve the problem")
|
|
die("BUILD ERROR: more than one leaf when ran out of bits." +
|
|
" See stdout.")
|
|
graph.skipped_bits = skipped_bits
|
|
return
|
|
elif at_end == -1:
|
|
if vbuild():
|
|
msge("Hit end of SOME instructions -- try back_split")
|
|
(okay, splitpos) = \
|
|
scan_backwards_for_distinguishing_bit(graph.instructions,bitpos)
|
|
if not okay:
|
|
die("Back-split failed to solve ending problem")
|
|
else:
|
|
# redo this bit (hence bitpos-1) once we split based on splitpos
|
|
back_split_graph(common, graph, bitpos-1, skipped_bits, splitpos)
|
|
return
|
|
|
|
if vpart():
|
|
msge("Here is what we are considering, bitpos" + str(bitpos) + ":")
|
|
for ii in graph.instructions:
|
|
msge(ii.dump_str(' ') + '\n')
|
|
|
|
#####################################################################
|
|
|
|
|
|
iterations = 0
|
|
splitting = True
|
|
while splitting:
|
|
#msge("SPLIT ITERATION: %d" % (iterations))
|
|
if iterations > 1:
|
|
die("We should not be trying to resplit things more than once")
|
|
iterations += 1
|
|
|
|
# Check for identical operand deciders
|
|
(all_same_decider, operand_decider) = \
|
|
all_same_operand_decider(graph.instructions,bitpos)
|
|
if all_same_decider:
|
|
if vbuild():
|
|
msge("All same operand decider: %s" % operand_decider)
|
|
# hit an operand decider
|
|
|
|
# tell current node that it is an operand decider
|
|
graph.operand_decider = operand_decider # a special kind of bit_info_t
|
|
graph.skipped_bits = skipped_bits
|
|
|
|
# Collect up the required values...
|
|
required_values = collect_required_values(graph.instructions, bitpos)
|
|
if vpart():
|
|
msge("Required values for operand decider " + str(required_values))
|
|
|
|
# When we have things that ignore a particular decider we need
|
|
# to copy everything else to all the options. NOTE: this must be
|
|
# false because when set to true, it messes up the subsequent bitpos
|
|
# ordering for things that were skipped.
|
|
splatter_dont_cares = False
|
|
|
|
# split up the nodes based on the different values observed.
|
|
(node_partition,values) = \
|
|
partition_by_required_values(options, graph.instructions, bitpos,
|
|
operand_decider.token,
|
|
required_values, common.state_space,
|
|
splatter_dont_cares,
|
|
g_operand_storage_dict)
|
|
|
|
graph.child_od_key_values = values
|
|
|
|
|
|
# check to see if all the 'other' conditions are the same.
|
|
# if not, we must splatter them.
|
|
need_to_splatter = False
|
|
previous_trimmed_values = None
|
|
scalar_values = set()
|
|
for k,partition in node_partition.items():
|
|
if vpart():
|
|
msge("SPATTER SCAN: Operand decider partition key= " + str(k))
|
|
if isinstance(partition[0],tuple):
|
|
for trimmed_values, ii in partition:
|
|
s = set(trimmed_values)
|
|
if previous_trimmed_values == None:
|
|
previous_trimmed_values = s
|
|
if s != previous_trimmed_values:
|
|
# need to splatter!
|
|
msge("X9 need to splatter based on differing " +
|
|
"other conditions")
|
|
need_to_splatter = True
|
|
break
|
|
else:
|
|
scalar_values.add(k)
|
|
|
|
# if there is an overlap between the 'other' values and
|
|
# values referenced by non "other" nodes (scalar ODs), then
|
|
# we need to splatter. MOD=3 and MOD!=3 will not get splattered.
|
|
# But X=0, X=1, X!=2 will get splattered since X!=2 -> X= 0 or 1.
|
|
# and that overlaps with existing scalar values.
|
|
|
|
if not need_to_splatter and previous_trimmed_values:
|
|
if scalar_values.intersection(previous_trimmed_values):
|
|
msge("X9 need to splatter based on cases overlapping " +
|
|
"with scalar dispatch")
|
|
need_to_splatter = True
|
|
|
|
# fix up the 'other' conditions so that they are partitionable.
|
|
if need_to_splatter:
|
|
msge("Splattering because of conflicting 'other' conditions")
|
|
new_node_partition = {}
|
|
for k,partition in node_partition.items():
|
|
if isinstance(partition[0],tuple):
|
|
for trimmed_values, ii in partition:
|
|
for tv in trimmed_values:
|
|
try:
|
|
new_node_partition[tv].append(ii)
|
|
except:
|
|
new_node_partition[tv]=[ii]
|
|
else:
|
|
try:
|
|
new_node_partition[k].extend(partition)
|
|
except:
|
|
new_node_partition[k]=partition
|
|
# replace old partition with splattered partition
|
|
node_partition = new_node_partition
|
|
|
|
|
|
# set up the next nodes and give them their instructions.
|
|
|
|
for k,partition in node_partition.items():
|
|
if vpart():
|
|
msge("PARTITIION: Operand decider partition key= " + str(k))
|
|
next_node = new_node(graph,k,bitpos)
|
|
|
|
if isinstance(partition[0],tuple):
|
|
# store the key choices in the node for later graph building
|
|
for trimmed_values, ii in partition:
|
|
next_node.trimmed_values = trimmed_values
|
|
next_node.instructions.append(ii)
|
|
else:
|
|
if k == 'XED': # FIXME: why is this test here? / 2009-02-06
|
|
die("Bad operand decider: " + k )
|
|
next_node.instructions.extend(partition)
|
|
|
|
# build the subgraphs for the children
|
|
for child in graph.next.values():
|
|
# RECUR for operand-decider
|
|
build_sub_graph(common, child, bitpos, 0)
|
|
return
|
|
|
|
####################################################################
|
|
# Check for identical nonterminals
|
|
# nt is the bit_info_t for the nonterminal
|
|
(all_same_nt, nt) = all_same_nonterminal(graph.instructions,bitpos)
|
|
if all_same_nt:
|
|
if vbuild():
|
|
msge("All same nt")
|
|
# hit a nonterminal.
|
|
|
|
# tell current node that it is a nonterminal
|
|
graph.nonterminal = nt
|
|
graph.skipped_bits = skipped_bits
|
|
if vbuild():
|
|
msge("GRAPHBLD: Nonterminal: " +
|
|
str(nt) + " skipped_bits: " + str(skipped_bits))
|
|
# build a new node that follows the nonterminal and give it
|
|
# all the instructions. The '-' denotes we go there when the
|
|
# nonterminal is done parsing.
|
|
nt_next_node = new_node(graph,'-',bitpos)
|
|
|
|
nt_next_node.instructions.extend(graph.instructions)
|
|
|
|
# carry on build the graph from the nt_next_node
|
|
build_sub_graph(common, nt_next_node, bitpos, 0)
|
|
return
|
|
else:
|
|
if vbuild():
|
|
msge("Not all the same nonterminal.")
|
|
|
|
#####################################################################
|
|
# *AFTER* we advance the bit, we partition the nodes based on
|
|
# *the current bit
|
|
|
|
(ones,zeros,others) = partition_nodes(graph.instructions,bitpos)
|
|
if vbuild():
|
|
s = "ones %d zeros %d others %d" % (len(ones),
|
|
len(zeros), len(others))
|
|
msge('build_sub_graph ' + s )
|
|
|
|
# make sure we do not have some things that hit the Nonterminal
|
|
# and some that do not
|
|
if some_funky_spot(graph.instructions,bitpos):
|
|
msge('FUNKY SPOT: bitpos %d' % (bitpos))
|
|
print_split(others,ones,zeros,brief=True)
|
|
if rearrange_at_conflict(graph.instructions, bitpos):
|
|
msge("REARRANGED ODs TO BYPASS PROBLEM at bitpos %d" % bitpos )
|
|
# try resplitting the nodes now that we've juggled stuff
|
|
continue
|
|
|
|
else:
|
|
(okay, splitpos) = \
|
|
scan_backwards_for_distinguishing_bit(graph.instructions,
|
|
bitpos)
|
|
if not okay:
|
|
die("Look-ahead error - only some are nonterminal " +
|
|
"at next bit position")
|
|
else:
|
|
# redo this bit (hence bitpos-1) once we split based on
|
|
# splitpos.
|
|
back_split_graph(common, graph,
|
|
bitpos-1, skipped_bits, splitpos)
|
|
return
|
|
else:
|
|
splitting = False # we are good to exit
|
|
|
|
if verb7():
|
|
print_split(others,ones,zeros)
|
|
|
|
|
|
# FIXME: enabling either of these lines causes a python error at
|
|
# this point we never need this nodes instructions again. (Turns
|
|
# out we need them anyway for generating args for the nonterminals,
|
|
# so all is not lost, just confused.)
|
|
#del graph.instructions
|
|
#graph.instructions = []
|
|
|
|
# if there are any others in then, we cannot split on this bit, so
|
|
# just keep going. Similarly, if there are all 1s or all 0s then we
|
|
# just keep going (when skip_constants is True). Only split the
|
|
# node if it is a mix of 1s and 0s.
|
|
if len(others) > 0:
|
|
|
|
####OLD STUFF build_sub_graph(common,graph, skipped_bits+1) # RECUR
|
|
|
|
if expand_dont_cares and (len(ones)>0 or len(zeros)>0):
|
|
# we only do the expansion if ones/zeros are floating around
|
|
# at this point. Build two nodes. Put all the ones in the
|
|
# "one" node, all hte zeros in the "zero" node and the others
|
|
# in both nodes! Then recur on both nodes
|
|
if vbuild():
|
|
msge("Duplicating dontcares")
|
|
graph.skipped_bits = skipped_bits
|
|
graph.decider_bits = 1
|
|
|
|
# zero child node
|
|
znode = new_node(graph,'0',bitpos)
|
|
if len(zeros) > 0:
|
|
znode.instructions.extend(zeros)
|
|
# Add the "don't-care others" to the zeros
|
|
znode.instructions.extend(others)
|
|
build_sub_graph(common,znode,bitpos, 0) # RECUR
|
|
|
|
# one child node
|
|
onode = new_node(graph,'1',bitpos)
|
|
if len(ones) > 0:
|
|
onode.instructions.extend(ones)
|
|
# Add the "don't-care others" to the ones
|
|
onode.instructions.extend(others)
|
|
build_sub_graph(common,onode,bitpos, 0) # RECUR
|
|
|
|
else:
|
|
build_sub_graph(common,graph,bitpos, skipped_bits+1) # RECUR
|
|
|
|
elif len(ones) > 0 and len(zeros) == 0:
|
|
# Some one's but no zeros, no others
|
|
if vbuild():
|
|
msge("some ones, no zeros no others")
|
|
if skip_constants:
|
|
build_sub_graph(common,graph, bitpos, skipped_bits+1) # RECUR
|
|
else:
|
|
graph.skipped_bits = skipped_bits
|
|
graph.decider_bits = 1
|
|
|
|
onode = new_node(graph,'1',bitpos)
|
|
onode.instructions.extend(ones)
|
|
build_sub_graph(common,onode,bitpos, 0) # RECUR
|
|
elif len(ones) == 0 and len(zeros) > 0:
|
|
# Some zeros's but no ones, no others
|
|
if vbuild():
|
|
msge("some zeros, no ones no others")
|
|
if skip_constants:
|
|
build_sub_graph(common,graph,bitpos, skipped_bits+1)
|
|
else:
|
|
graph.skipped_bits = skipped_bits
|
|
graph.decider_bits = 1
|
|
|
|
znode = new_node(graph,'0',bitpos)
|
|
znode.instructions.extend(zeros)
|
|
build_sub_graph(common,znode, bitpos, 0) # RECUR
|
|
else:
|
|
# some zeros, some ones -> split it
|
|
if vbuild():
|
|
msge("Just 0s and 1s, splitting, building a subgraph")
|
|
graph.skipped_bits = skipped_bits
|
|
graph.decider_bits = 1
|
|
|
|
# zero child node
|
|
znode = new_node(graph,'0',bitpos)
|
|
znode.instructions.extend(zeros)
|
|
build_sub_graph(common,znode, bitpos, 0) # RECUR
|
|
|
|
# one child node
|
|
onode = new_node(graph,'1',bitpos)
|
|
onode.instructions.extend(ones)
|
|
build_sub_graph(common,onode,bitpos, 0) # RECUR
|
|
|
|
|
|
|
|
def build_graph(common, parser_output, operand_storage_dict):
|
|
"""Build a graph of the parsed instructions. Return the root"""
|
|
if vgraph_res():
|
|
print_resource_usage('build_graph.0')
|
|
global g_operand_storage_dict
|
|
g_operand_storage_dict = operand_storage_dict
|
|
if verb4():
|
|
msge("Building graph:")
|
|
print_node(parser_output.instructions)
|
|
if parser_output.otherwise_ok:
|
|
msge("Otherwise-ok is set to true for this nonterminal")
|
|
nt_name = parser_output.nonterminal_name
|
|
graph = graph_node(nt_name,0)
|
|
#msge("Building new graph: %d" % (graph.id))
|
|
|
|
#THIS LINE IS THE HUGE BIG MEMORY HOG. IS IT REALLY NEEDED??? NO!!
|
|
# Removing it cut the memory usage in half and the execution time in half!
|
|
#ilist = copy.deepcopy(parser_output.instructions)
|
|
ilist = parser_output.instructions
|
|
if vgraph_res():
|
|
print_resource_usage('build_graph.1')
|
|
graph.instructions.extend(ilist)
|
|
if vgraph_res():
|
|
print_resource_usage('build_graph.2')
|
|
bitpos = -1
|
|
skipped_bits = 0
|
|
build_sub_graph(common,graph, bitpos, skipped_bits)
|
|
if vgraph_res():
|
|
print_resource_usage('build_graph.3')
|
|
return graph
|
|
|
|
|
|
def print_graph(options, node, pad =''):
|
|
s = node.dump_str(pad)
|
|
msge(s)
|
|
for k,nxt in node.next.items(): # PRINTING
|
|
s = pad + ' key: ' + str(k)
|
|
msge(s)
|
|
print_graph(options, nxt, pad + ' ')
|
|
|
|
############################################################################
|
|
## $$ OPCAP capturing operands
|
|
############################################################################
|
|
|
|
def collect_immediate_operand_bit_positions(options, opnd, ii):
|
|
"""Fill in the opnd.bit_positions list with index of each bit in
|
|
the immediate operand."""
|
|
limit = len(ii.ipattern.bits)
|
|
last_bit_pos = {} # record the next starting point for each letter
|
|
# we encounter
|
|
for b in opnd.bits: # try to find each bit.
|
|
if b in last_bit_pos:
|
|
start_at = last_bit_pos[b]
|
|
else:
|
|
start_at = 0
|
|
found = False
|
|
# look at the bits in the ipattern
|
|
for p in range(start_at,limit):
|
|
if ii.ipattern.bits[p].value == b:
|
|
opnd.bit_positions.append( p )
|
|
# bump our new starting point to after the position we just found
|
|
last_bit_pos[b] = p+1
|
|
found = True
|
|
break
|
|
|
|
if not found:
|
|
die("Did not find bit %s of operand %s in instruction %s " %
|
|
(str(b), str(opnd), ii.dump_str()))
|
|
|
|
################################
|
|
|
|
uninteresting_operand_types_list = ['imm_const', 'reg', 'relbr', 'ptr', 'error',
|
|
'nt_lookup_fn', 'mem', 'xed_reset',
|
|
'flag', 'agen']
|
|
|
|
uninteresting_operand_types_dict = \
|
|
dict(zip(uninteresting_operand_types_list,
|
|
[True]*len(uninteresting_operand_types_list)))
|
|
|
|
|
|
def decorate_operand(options,opnd,ii):
|
|
"""Set opnd.bit_positions list and opnd.rightmost_bitpos for this
|
|
operand in this instruction"""
|
|
|
|
global uninteresting_operand_types_dict
|
|
|
|
if opnd.type in uninteresting_operand_types_dict:
|
|
pass
|
|
elif opnd.type == 'imm':
|
|
if ii.prebindings and opnd.name in ii.prebindings:
|
|
opnd.bit_positions = [ x.pbit for x in
|
|
ii.prebindings[opnd.name].bit_info_list ]
|
|
else:
|
|
collect_immediate_operand_bit_positions(options,opnd, ii)
|
|
opnd.rightmost_bitpos = max(opnd.bit_positions)
|
|
else:
|
|
die("Unhandled operand type: " + str(opnd))
|
|
|
|
|
|
|
|
def decorate_operands(options,agi):
|
|
for gi in agi.generator_list:
|
|
for ii in gi.parser_output.instructions:
|
|
for opnd in ii.operands:
|
|
decorate_operand(options,opnd,ii)
|
|
|
|
|
|
def find_all_operands(options, node):
|
|
"""Return a set of operand names for this graph node. Just look at
|
|
all the instructions."""
|
|
operands = set()
|
|
|
|
if vcapture():
|
|
for ii in node.instructions:
|
|
for opnd in ii.operands:
|
|
msge("FINDALL: " + opnd.name + " type= [" + opnd.type + ']')
|
|
|
|
# Get the operands from the first instruction
|
|
# Only look at the imm-type operands
|
|
ii = node.instructions[0]
|
|
for opnd in ii.operands:
|
|
if vcapture():
|
|
msge("FINDALL Operand " + opnd.name + " type= [" + opnd.type + ']')
|
|
# at leaves, include all operands. at internal nodes, just the
|
|
# reg, imm and imm_const operands.
|
|
if node.leaf():
|
|
operands.add(opnd.name)
|
|
elif opnd.type == 'imm' or opnd.type == 'imm_const' or opnd.type == 'reg':
|
|
operands.add(opnd.name)
|
|
|
|
# Remove any operands that are not in every instruction.
|
|
# (We do not reprocess the first element.)
|
|
for ii in node.instructions[1:]:
|
|
op2set = set()
|
|
for opnd in ii.operands:
|
|
if node.leaf():
|
|
# FIXME: this *was* operands.add(opnd.name)
|
|
# 2007-06-26. Not sure if it was wrong or equivalent.
|
|
op2set.add(opnd.name)
|
|
elif opnd.type == 'imm' or opnd.type == 'imm_const' or \
|
|
opnd.type == 'reg':
|
|
op2set.add(opnd.name)
|
|
operands = operands.intersection(op2set)
|
|
return operands
|
|
|
|
|
|
def collect_instruction_types(agi, master_list):
|
|
"""Collect the iclass / category /extension"""
|
|
need_to_die = False
|
|
for generator in agi.generator_list:
|
|
for ii in generator.parser_output.instructions:
|
|
if field_check(ii, 'iclass'):
|
|
plist = []
|
|
if field_check(ii, 'attributes'):
|
|
plist = ii.attributes
|
|
|
|
if field_check(ii, 'iclass_string_index'):
|
|
iclass_string_index = ii.iclass_string_index
|
|
else:
|
|
iclass_string_index = 0
|
|
|
|
t = (ii.iclass, ii.extension, ii.category, ii.isa_set,
|
|
plist,
|
|
iclass_string_index)
|
|
if ii.iform_enum in master_list:
|
|
# duplicate iform - check extension and isa-set
|
|
(oldi, olde, oldc,
|
|
olds, oldp, oldisi) = master_list[ii.iform_enum]
|
|
if olde != ii.extension:
|
|
need_to_die = True
|
|
msgb("ERROR:EXTENSION ALIASING IN IFORM TABLE", ii.iform_enum)
|
|
if olds != ii.isa_set:
|
|
msgb("ERROR: ISA_SET ALIASING IN IFORM TABLE", ii.iform_enum)
|
|
need_to_die = True
|
|
msgb("DUPLICATE IFORM", ii.iform_enum)
|
|
master_list[ii.iform_enum] = t
|
|
if need_to_die:
|
|
mbuild.die("Dieing due to iform aliasing")
|
|
|
|
def collect_isa_sets(agi):
|
|
"""Collect the isaset info"""
|
|
s = set()
|
|
for generator in agi.generator_list:
|
|
for ii in generator.parser_output.instructions:
|
|
if field_check(ii, 'iclass'):
|
|
s.add(ii.isa_set.upper())
|
|
return s
|
|
|
|
|
|
|
|
def collect_tree_depth(node, depths={}, depth=0):
|
|
"""Collect instruction field data for enumerations"""
|
|
|
|
cdepth = depth + 1
|
|
if len(node.next) == 0:
|
|
try:
|
|
depths[cdepth] += 1
|
|
except:
|
|
depths[cdepth] = 1
|
|
else:
|
|
for child in node.next.values():
|
|
collect_tree_depth(child, depths, cdepth)
|
|
return depths
|
|
|
|
def collect_ifield(options, node, field, master_list):
|
|
"""Collect instruction field data for enumerations"""
|
|
for ii in node.instructions:
|
|
if field_check(ii, field):
|
|
s = getattr(ii,field)
|
|
if s not in master_list:
|
|
master_list.append(s)
|
|
for child in node.next.values():
|
|
# FIXME: sloppy return value handling???
|
|
collect_ifield(options,child, field,master_list)
|
|
return master_list
|
|
|
|
|
|
def collect_ofield(options, node, field, master_list):
|
|
"""Collect operand field data for enumerations"""
|
|
for ii in node.instructions:
|
|
for opnd in ii.operands:
|
|
if field_check(opnd, field):
|
|
s = getattr(opnd,field)
|
|
if s != None and s not in master_list:
|
|
master_list[s] = True
|
|
for child in node.next.values():
|
|
collect_ofield(options,child, field,master_list)
|
|
|
|
def collect_ofield_operand_type(options, node, field, master_list):
|
|
"""Collect operand type enumeration data"""
|
|
for ii in node.instructions:
|
|
for opnd in ii.operands:
|
|
if field_check(opnd, field):
|
|
s = opnd.get_type_for_emit()
|
|
#s = getattr(opnd,field)
|
|
if s != None and s not in master_list:
|
|
master_list[s] = True
|
|
for child in node.next.values():
|
|
collect_ofield_operand_type(options,child, field,master_list)
|
|
|
|
|
|
def collect_ofield_name_type(options, node, field, master_list):
|
|
"""Collect operand field data for enumerations"""
|
|
for ii in node.instructions:
|
|
for opnd in ii.operands:
|
|
if field_check(opnd, field):
|
|
s = getattr(opnd,field)
|
|
type = getattr(opnd,'type')
|
|
if s not in master_list:
|
|
master_list[s]=type
|
|
for child in node.next.values():
|
|
collect_ofield_name_type(options,child, field,master_list)
|
|
|
|
|
|
|
|
def collect_attributes_pre(options, node, master_list):
|
|
collect_attributes(options, node, master_list)
|
|
# add always-available attributes. These facilitate writing
|
|
# unconditional property-checking code in XED.
|
|
for attr in [ 'MASKOP_EVEX', 'MASK_AS_CONTROL' ]:
|
|
if attr not in master_list:
|
|
master_list.append(attr)
|
|
|
|
|
|
def collect_attributes(options, node, master_list):
|
|
"""Collect all attributes"""
|
|
for ii in node.instructions:
|
|
if field_check(ii, 'attributes'):
|
|
s = getattr(ii,'attributes')
|
|
if isinstance(s, list):
|
|
for x in s:
|
|
if x not in master_list:
|
|
master_list.append(x)
|
|
elif s != None and s not in master_list:
|
|
master_list.append(s)
|
|
for nxt in node.next.values():
|
|
collect_attributes(options,nxt, master_list)
|
|
|
|
|
|
idata_files = 0
|
|
def write_instruction_data(odir,idata_dict):
|
|
"""Write a file containing the content of the idata_dict. The keys
|
|
are iclass:extension the values are (iclass, extension, category). This
|
|
appends to the file if we've already opened this."""
|
|
global idata_files
|
|
fn = 'idata.txt'
|
|
if idata_files == 0:
|
|
open_mode = "w"
|
|
else:
|
|
open_mode = "a"
|
|
idata_files += 1
|
|
f = open(os.path.join(odir,fn),open_mode)
|
|
kys = list(idata_dict.keys())
|
|
kys.sort()
|
|
s = "#%-19s %-15s %-15s %-30s %-20s %s\n" % ("iclass",
|
|
"extension",
|
|
"category",
|
|
"iform",
|
|
"isa_set",
|
|
'attributes')
|
|
f.write(s)
|
|
for iform in kys:
|
|
(iclass,extension,category,isa_set, plist,
|
|
iclass_string_index) = idata_dict[iform]
|
|
if plist:
|
|
attributes = ":".join(plist)
|
|
else:
|
|
attributes = 'INVALID'
|
|
s = "%-19s %-15s %-15s %-30s %-20s %s\n" % (iclass,
|
|
extension,
|
|
category,
|
|
iform,
|
|
isa_set,
|
|
attributes)
|
|
f.write(s)
|
|
f.close()
|
|
|
|
def attr_dict_keyfn(a):
|
|
return a[0]
|
|
|
|
def attr_dict_cmp(a,b): # FIXME:2017-06-10:PY3 port, now unused
|
|
av = a[0]
|
|
bv = b[0]
|
|
if av == bv:
|
|
return 0
|
|
if av > bv:
|
|
return 1
|
|
return -1
|
|
|
|
def write_attributes_table(agi, odir):
|
|
fn = 'xed-attributes-init.c'
|
|
if vattr():
|
|
msgb("Writing attributes file", fn)
|
|
f = agi.common.open_file(fn, start=False)
|
|
f.add_misc_header("#include \"xed-attributes.h\"")
|
|
f.add_misc_header("#include \"xed-gen-table-defs.h\"")
|
|
f.start()
|
|
f.write("\nconst xed_attributes_t ")
|
|
f.write("xed_attributes[XED_MAX_REQUIRED_ATTRIBUTES] = {\n")
|
|
|
|
if vattr():
|
|
msgb("Unique attributes", len(agi.attributes_dict))
|
|
t = []
|
|
for s,v in agi.attributes_dict.items():
|
|
t.append((v,s))
|
|
t.sort(key=attr_dict_keyfn)
|
|
if vattr():
|
|
msgb("Sorted Unique attributes", len(t))
|
|
agi.attributes_ordered = t
|
|
|
|
# agi.attributes_ordered has tuple (i,s) where s is a comma
|
|
# separated list of attributes that we'll use to manufacture the
|
|
# initialization equations.
|
|
if len(agi.attributes_ordered) >= 65536:
|
|
die("Too many attributes combinations for the 16b index used" +
|
|
" in the xed_inst_t data structure." +
|
|
" Please report this to the SDE/XED team.")
|
|
|
|
for i,s in agi.attributes_ordered:
|
|
if s:
|
|
v = s.split(',')
|
|
struct_init = make_attributes_structure_init(agi,v)
|
|
else:
|
|
struct_init = make_attributes_structure_init(agi,None)
|
|
f.write("/* %5d */ %s,\n" % (i,struct_init))
|
|
f.write("\n};\n")
|
|
f.close()
|
|
|
|
def write_quick_iform_map(agi,odir,idata_dict):
|
|
fn = 'xed-iform-map-init.c'
|
|
f = agi.common.open_file(fn, start=False)
|
|
f.add_misc_header("#include \"xed-iform-map.h\"")
|
|
f.start()
|
|
|
|
# FIXME: declare this type
|
|
f.write("\nconst xed_iform_info_t xed_iform_db[XED_IFORM_LAST] = {\n")
|
|
first = True
|
|
for (iclass,iform_num,iform) in agi.iform_tuples:
|
|
try:
|
|
(x_iclass,extension,category,isa_set,
|
|
plist,
|
|
iclass_string_index) = idata_dict[iform]
|
|
except:
|
|
(x_iclass,extension,category,isa_set,
|
|
plist,
|
|
iclass_string_index) = ('INVALID',
|
|
'INVALID',
|
|
'INVALID',
|
|
'INVALID',
|
|
None,
|
|
0) # FIXME BADNESS
|
|
|
|
if first:
|
|
first = False
|
|
else:
|
|
f.write(",\n")
|
|
qual_iclass = "XED_ICLASS_%s" % (iclass.upper())
|
|
qual_category = "XED_CATEGORY_%s" % (category.upper())
|
|
qual_extension = "XED_EXTENSION_%s" % (extension.upper())
|
|
qual_isa_set = "XED_ISA_SET_%s" % (isa_set.upper())
|
|
t = '/* %29s */ { (xed_uint16_t) %25s, (xed_uint8_t) %22s, (xed_uint8_t)%20s, (xed_uint8_t)%25s, (xed_uint16_t)%4d }' % \
|
|
(iform,
|
|
qual_iclass,
|
|
qual_category,
|
|
qual_extension,
|
|
qual_isa_set,
|
|
iclass_string_index)
|
|
f.write(t)
|
|
f.write("\n};\n")
|
|
|
|
f.close()
|
|
|
|
def collect_graph_enum_info(agi,graph):
|
|
# we ignore the return values because we don't need them. The agi
|
|
# fields get written by the collect*() functions.
|
|
|
|
# operand fields
|
|
collect_ofield_operand_type(agi.common.options,
|
|
graph,
|
|
'type',
|
|
agi.operand_types)
|
|
collect_ofield(agi.common.options,graph, 'oc2', agi.operand_widths)
|
|
collect_ofield_name_type(agi.common.options,graph, 'name',
|
|
agi.operand_names)
|
|
|
|
collect_ifield(agi.common.options,graph, 'iclass',agi.iclasses)
|
|
collect_ifield(agi.common.options,graph, 'category', agi.categories)
|
|
collect_ifield(agi.common.options,graph, 'extension', agi.extensions)
|
|
|
|
collect_attributes_pre(agi.common.options,graph, agi.attributes)
|
|
|
|
def add_invalid(lst):
|
|
if 'INVALID' not in lst:
|
|
lst[0:0] = ['INVALID']
|
|
|
|
############################################################################
|
|
def key_invalid_first(x):
|
|
# make 'INVALID' sort to be first.
|
|
if x == 'INVALID':
|
|
# space is first printable character in ascii table and should
|
|
# not show up in our usage.
|
|
return ' '
|
|
return x
|
|
|
|
def cmp_invalid(t1,t2): # FIXME:2017-06-10:PY3 port, no longer used
|
|
"""Special sort-comparison function that makes sure the INVALID
|
|
entry is first"""
|
|
if t1 == t2:
|
|
return 0
|
|
if t1 == 'INVALID':
|
|
return -1
|
|
if t2 == 'INVALID':
|
|
return 1
|
|
if t1 > t2:
|
|
return 1
|
|
return -1
|
|
|
|
|
|
def key_invalid_tuple_element_0(x):
|
|
return key_invalid_first(x[0])
|
|
def key_tuple_element_1(x):
|
|
return x[1]
|
|
|
|
def cmp_invalid_vtuple(vt1,vt2): #FIXME:2017-06-10:PY3 port. No longer used
|
|
"""Special sort-comparison function that makes sure the INVALID
|
|
entry is first"""
|
|
t1 = vt1[0]
|
|
t2 = vt2[0]
|
|
if t1 == t2:
|
|
v1 = vt1[1]
|
|
v2 = vt2[1]
|
|
if v1 == v2:
|
|
return 0
|
|
elif v1 > v2:
|
|
return 1
|
|
else:
|
|
return -1
|
|
if t1 == 'INVALID':
|
|
return -1
|
|
if t2 == 'INVALID':
|
|
return 1
|
|
if t1 > t2:
|
|
return 1
|
|
return -1
|
|
|
|
|
|
class rep_obj_t(object):
|
|
def __init__(self, iclass, indx, repkind):
|
|
self.iclass = iclass
|
|
self.indx = indx
|
|
self.repkind = repkind
|
|
self.no_rep_iclass = None
|
|
self.no_rep_indx = None
|
|
|
|
|
|
|
|
def repmap_emit_code(agi, plist, kind, hash_fn):
|
|
"""Emit table that implements the required mapping of iclasses. plist
|
|
is an array of (key,value) pairs. kind is one of repe, repne, rep
|
|
or norep. The hash function maps from the keys to a unique
|
|
value. """
|
|
|
|
fo = function_object_t(name='xed_' + kind + '_map',
|
|
return_type='xed_iclass_enum_t',
|
|
dll_export=True)
|
|
fo.add_arg('xed_iclass_enum_t iclass')
|
|
t = {}
|
|
mx = 0
|
|
for (k,v) in plist:
|
|
h = hash_fn.apply(k)
|
|
t[h] = (k,v)
|
|
mx = max(mx, h)
|
|
|
|
# For nonlinear hashes, add hash key input validation so that we
|
|
# check if the input matches the thing we expect to get on the
|
|
# output of the hash. Then the functions won't return undefined
|
|
# results for unexpected inputs.
|
|
|
|
if hash_fn.kind() == 'linear':
|
|
array_limit = mx+1 # no extra room required for validation.
|
|
else:
|
|
array_limit = 2*(mx+1) # make room for input key validation
|
|
fo.add_code('const xed_uint16_t lu_table[{}] = {{'.format(array_limit))
|
|
|
|
hashes = list(t.keys())
|
|
hashes.sort()
|
|
|
|
# fill in the rows of the array
|
|
for h in range(0,mx+1):
|
|
if h in t:
|
|
(k,v) = t[h]
|
|
else:
|
|
k = "0xFFFF"
|
|
v = 0 # XED_ICLASS_INVALID
|
|
if hash_fn.kind() == 'linear':
|
|
fo.add_code( '/* {} -> {} */ {},'.format(k,h,v))
|
|
else:
|
|
fo.add_code( '/* {} -> {} */ {}, {},'.format(k,h, k,v))
|
|
|
|
fo.add_code_eol('}')
|
|
fo.add_code_eol('const xed_uint_t key = (xed_uint_t)iclass')
|
|
fo.add_code_eol('const xed_uint_t hash = {}'.format(hash_fn.emit_cexpr()))
|
|
fo.add_code( 'if (hash <= {}) {{'.format(mx))
|
|
if hash_fn.kind() == 'linear':
|
|
fo.add_code_eol(' const xed_uint_t v = lu_table[hash]')
|
|
fo.add_code_eol(' return (xed_iclass_enum_t) v')
|
|
else:
|
|
# validate the correct input mapped to the output
|
|
fo.add_code_eol(' const xed_uint_t ek = lu_table[2*hash]')
|
|
fo.add_code( ' if (ek == key) {')
|
|
fo.add_code_eol(' const xed_uint_t v = lu_table[2*hash+1]')
|
|
fo.add_code_eol(' return (xed_iclass_enum_t) v')
|
|
fo.add_code( ' }')
|
|
fo.add_code( '}')
|
|
fo.add_code_eol('return XED_ICLASS_INVALID')
|
|
return fo
|
|
|
|
def emit_iclass_rep_ops(agi):
|
|
|
|
"""We want to make several functions that map (1) norep -> rep, (2)
|
|
norep -> repe, (3) norep ->repne, and (4) rep/repe/repne -> norep.
|
|
To do that, we need 2 hash functions. One hash function maps from
|
|
rep/repe/repne keys and and another one mapping from norep keys.
|
|
"""
|
|
import hashfks
|
|
import hashmul
|
|
import hashlin
|
|
|
|
# collect the iclasses of interest by name.
|
|
keys = []
|
|
repobjs = []
|
|
for i,iclass in enumerate(agi.iclasses_enum_order):
|
|
#msge("TTX-ICLASS: {}".format(str(iclass)))
|
|
if 'REPE_' in iclass:
|
|
keys.append(i)
|
|
repobjs.append(rep_obj_t(iclass,i,'repe'))
|
|
if 'REPNE_' in iclass:
|
|
keys.append(i)
|
|
repobjs.append(rep_obj_t(iclass,i,'repne'))
|
|
if 'REP_' in iclass:
|
|
keys.append(i)
|
|
repobjs.append(rep_obj_t(iclass,i,'rep'))
|
|
|
|
# fill in the no-rep info for each object
|
|
for o in repobjs:
|
|
o.no_rep_iclass = re.sub(r'REP(E|NE)?_', '', o.iclass)
|
|
o.no_rep_indx = agi.iclasses_enum_order.index(o.no_rep_iclass)
|
|
|
|
# make a list of keys for the norep-to-whatever hash functions
|
|
no_rep_keys = uniqueify( [x.no_rep_indx for x in repobjs])
|
|
no_rep_keys.sort()
|
|
|
|
msge("NOREP KEYS: {}".format(str(no_rep_keys)))
|
|
msge("REP KEYS: {}".format(str(keys)))
|
|
|
|
# find the two required hash functions
|
|
all_fn = { 'repinst':None, 'norepinst':None }
|
|
for kind, kl in [('repinst',keys), ('norepinst',no_rep_keys)]:
|
|
hashfn = hashlin.get_linear_hash_function(kl)
|
|
if not hashfn:
|
|
hashfn = hashmul.find_perfect(kl)
|
|
if not hashfn:
|
|
hashfn = hashfks.find_fks_perfect(kl)
|
|
|
|
if hashfn:
|
|
msge('{}'.format(hashfn.emit_cexpr()))
|
|
msge('{}'.format(str(hashfn)))
|
|
msge('FOUND PERFECT HASH FUNCTION FOR {}'.format(kind))
|
|
all_fn[kind]=hashfn
|
|
else:
|
|
# If this ever happens, it is seriously bad news. We'll
|
|
# have to upgrade the perfect hash function generation so
|
|
# that this succeeds or make a fallback code path that either
|
|
# large or slow. Or one could generate a 2-level perfect hash
|
|
# but that seems like overkill for this.
|
|
die('DID NOT FIND PERFECT HASH FUNCTION FOR {}'.format(kind))
|
|
|
|
functions = []
|
|
# emit the 3 functions that map from norep -> various kinds of
|
|
# rep/repe/repne prefixes
|
|
for kind in ['repe', 'repne', 'rep']:
|
|
plist = []
|
|
for r in repobjs:
|
|
if r.repkind == kind:
|
|
plist.append((r.no_rep_indx, r.indx))
|
|
fo = repmap_emit_code(agi, plist, kind, all_fn['norepinst'])
|
|
functions.append(fo)
|
|
|
|
# emit the 1 function that maps from rep/repe/repne -> norep version
|
|
plist = []
|
|
for r in repobjs:
|
|
plist.append((r.indx, r.no_rep_indx))
|
|
fo = repmap_emit_code(agi, plist, "norep", all_fn['repinst'])
|
|
functions.append(fo)
|
|
|
|
cfp = agi.open_file('xed-rep-map.c')
|
|
for fn in functions:
|
|
cfp.write(fn.emit())
|
|
cfp.close()
|
|
|
|
##############################################################################
|
|
|
|
def emit_iclass_enum_info(agi):
|
|
"""Emit major enumerations based on stuff we collected from the
|
|
graph."""
|
|
msge('emit_iclass_enum_info')
|
|
iclasses = [s.upper() for s in agi.iclasses]
|
|
add_invalid(iclasses)
|
|
|
|
# 2...9 # omitting NOP1
|
|
iclasses.extend( [ "NOP%s" % (str(x)) for x in range(2,10)])
|
|
|
|
iclasses = uniqueify(iclasses)
|
|
# sort each to make sure INVALID is first
|
|
iclasses.sort(key=key_invalid_first)
|
|
gendir = agi.common.options.gendir
|
|
xeddir = agi.common.options.xeddir
|
|
agi.iclasses_enum_order = iclasses
|
|
i_enum = enum_txt_writer.enum_info_t(iclasses, xeddir, gendir,
|
|
'xed-iclass',
|
|
'xed_iclass_enum_t',
|
|
'XED_ICLASS_',
|
|
cplusplus=False)
|
|
|
|
i_enum.print_enum()
|
|
i_enum.run_enumer()
|
|
agi.add_file_name(i_enum.src_full_file_name)
|
|
agi.add_file_name(i_enum.hdr_full_file_name, header=True)
|
|
agi.all_enums['xed_iclass_enum_t'] = iclasses
|
|
|
|
def power2(x):
|
|
"""Return a list of the powers of 2 from 2^0... 2^x"""
|
|
if x == 0:
|
|
return None
|
|
ret = []
|
|
for p in range(0,x):
|
|
ret.append(2**p)
|
|
return ret
|
|
|
|
max_attributes=0
|
|
|
|
def emit_attributes_table(agi, attributes):
|
|
"""Print a global initializer list of attributes to the
|
|
xed_attributes_table[XED_MAX_ATTRIBUTE_COUNT]"""
|
|
cfp = agi.open_file('xed-attributes-list.c')
|
|
cfp.write('const xed_attribute_enum_t ' +
|
|
'xed_attributes_table[XED_MAX_ATTRIBUTE_COUNT] = {\n')
|
|
first = True
|
|
for attr in attributes:
|
|
if first:
|
|
first = False
|
|
else:
|
|
cfp.write(',\n')
|
|
cfp.write(' XED_ATTRIBUTE_%s' % (attr))
|
|
cfp.write('\n};\n')
|
|
cfp.close()
|
|
|
|
|
|
|
|
def emit_enum_info(agi):
|
|
"""Emit major enumerations based on stuff we collected from the
|
|
graph."""
|
|
msge('emit_enum_info')
|
|
# make everything uppercase
|
|
nonterminals = [ s.upper() for s in list(agi.nonterminal_dict.keys())]
|
|
operand_types = [ s.upper() for s in list(agi.operand_types.keys())]
|
|
operand_widths = [ s.upper() for s in list(agi.operand_widths.keys())]
|
|
|
|
operand_names = [ s.upper() for s in
|
|
list(agi.operand_storage.get_operands().keys()) ]
|
|
msge("OPERAND-NAMES " + " ".join(operand_names))
|
|
|
|
|
|
extensions = [ s.upper() for s in agi.extensions]
|
|
categories = [ s.upper() for s in agi.categories]
|
|
attributes = [ s.upper() for s in agi.attributes]
|
|
# remove the things with equals signs
|
|
attributes = list(filter(lambda s: s.find('=') == -1 ,attributes))
|
|
|
|
|
|
# add an invalid entry to each in the first spot if it is not
|
|
# already in the list. Sometimes it is there already, so we must
|
|
# sort to make INVALID the 0th entry.
|
|
add_invalid(nonterminals)
|
|
add_invalid(operand_types)
|
|
add_invalid(operand_widths)
|
|
add_invalid(extensions)
|
|
add_invalid(categories)
|
|
gendir = agi.common.options.gendir
|
|
xeddir = agi.common.options.xeddir
|
|
|
|
|
|
nonterminals.sort(key=key_invalid_first)
|
|
nt_enum = enum_txt_writer.enum_info_t(nonterminals, xeddir, gendir,
|
|
'xed-nonterminal',
|
|
'xed_nonterminal_enum_t',
|
|
'XED_NONTERMINAL_',
|
|
cplusplus=False)
|
|
|
|
#For xed3 we want to dump a C mapping nt_enum -> nt_capture_function
|
|
#for that matter we want a mapping:
|
|
#nt_enum_numeric_value -> nt_name
|
|
xed3_nt_enum_val_map = {}
|
|
upper_dict = {}
|
|
for nt_name in list(agi.nonterminal_dict.keys()):
|
|
nt_name_upper = nt_name.upper()
|
|
upper_dict[nt_name_upper] = nt_name
|
|
for i,upper_nt in enumerate(nonterminals):
|
|
if i == 0:
|
|
continue #no nt_name for invalid guy
|
|
xed3_nt_enum_val_map[i] = upper_dict[upper_nt]
|
|
agi.xed3_nt_enum_val_map = xed3_nt_enum_val_map
|
|
|
|
operand_names.sort()
|
|
add_invalid(operand_names)
|
|
on_enum = enum_txt_writer.enum_info_t(operand_names, xeddir, gendir,
|
|
'xed-operand',
|
|
'xed_operand_enum_t',
|
|
'XED_OPERAND_',
|
|
cplusplus=False)
|
|
#for xed3 we want to create xed3_operand_struct_t
|
|
#and it would be nice to order struct members in the
|
|
#operand_enum order
|
|
agi.xed3_operand_names = operand_names
|
|
|
|
operand_types.sort(key=key_invalid_first)
|
|
ot_enum = enum_txt_writer.enum_info_t(operand_types, xeddir, gendir,
|
|
'xed-operand-type',
|
|
'xed_operand_type_enum_t',
|
|
'XED_OPERAND_TYPE_',
|
|
cplusplus=False)
|
|
|
|
attributes.sort(key=key_invalid_first)
|
|
lena = len(attributes)
|
|
attributes_list = ['INVALID']
|
|
if lena > 0:
|
|
attributes_list.extend(attributes)
|
|
if lena > 128:
|
|
die("Exceeded 128 attributes. " +
|
|
" The SDE/XED team needs to add support for more." +
|
|
" Please report this error.")
|
|
global max_attributes
|
|
max_attributes= lena
|
|
|
|
emit_attributes_table(agi, attributes)
|
|
|
|
for i,a in enumerate(attributes_list):
|
|
agi.sorted_attributes_dict[a] = i
|
|
|
|
at_enum = enum_txt_writer.enum_info_t(attributes_list, xeddir, gendir,
|
|
'xed-attribute',
|
|
'xed_attribute_enum_t',
|
|
'XED_ATTRIBUTE_',
|
|
cplusplus=False)
|
|
|
|
|
|
categories.sort(key=key_invalid_first)
|
|
c_enum = enum_txt_writer.enum_info_t(categories, xeddir, gendir,
|
|
'xed-category',
|
|
'xed_category_enum_t',
|
|
'XED_CATEGORY_',
|
|
cplusplus=False)
|
|
|
|
extensions.sort(key=key_invalid_first)
|
|
e_enum = enum_txt_writer.enum_info_t(extensions, xeddir, gendir,
|
|
'xed-extension',
|
|
'xed_extension_enum_t',
|
|
'XED_EXTENSION_',
|
|
cplusplus=False)
|
|
|
|
enums = [ nt_enum, on_enum, ot_enum, at_enum,
|
|
# ow_enum,
|
|
c_enum, e_enum ]
|
|
|
|
|
|
for e in enums:
|
|
e.print_enum()
|
|
e.run_enumer()
|
|
agi.add_file_name(e.src_full_file_name)
|
|
agi.add_file_name(e.hdr_full_file_name,header=True)
|
|
|
|
|
|
############################################################################
|
|
|
|
def emit_code(f,s):
|
|
'A simple function that tacks on a semicolon and a newline'
|
|
f.write(s + ';\n')
|
|
|
|
def pick_arg_type(arg):
|
|
"""Arg is a bit string whose name length determines what type we
|
|
should use for passing it"""
|
|
return 'xed_uint32_t'
|
|
#if arg == None or len(arg) <= 32:
|
|
# utype = "xed_uint32_t"
|
|
#else:
|
|
# utype = "xed_uint64_t"
|
|
#return utype
|
|
|
|
def create_basis(arg):
|
|
"return an bit string with the values of the 1s in arg, and zeros elsewhere"
|
|
basis = letter_basis_pattern.sub('0',arg)
|
|
# squish strings of all zeros down to just a single zero.
|
|
if all_zeros_pattern.match(basis):
|
|
return '0'
|
|
return basis
|
|
|
|
def get_inst_from_node(node):
|
|
if len(node.instructions) == 0:
|
|
die("no instructions when doing cg for nonterminal. graph build error.")
|
|
# grab the first instruction since they are all the same when the
|
|
# get to a nonterminal
|
|
ii = node.instructions[0]
|
|
|
|
#FIXME: confirm that the arg bits are in the same positions for all
|
|
#the instructions of this node. Otherwise erroneous bits will be
|
|
#extracted.
|
|
|
|
return ii
|
|
|
|
############################################################################
|
|
def compute_iform(options,ii, operand_storage_dict):
|
|
"""These are really the iforms."""
|
|
iform = []
|
|
if viform():
|
|
msge("IFORM ICLASS: %s" % (ii.iclass))
|
|
iform.append(ii.iclass)
|
|
for operand in ii.operands:
|
|
if operand.internal:
|
|
if viform():
|
|
msge("IFORM SKIPPING INTERNAL %s" % (operand.name))
|
|
pass
|
|
elif operand.visibility == 'SUPPRESSED':
|
|
if viform():
|
|
msge("IFORM SKIPPING SUPPRESSED %s" % (operand.name))
|
|
pass
|
|
elif operand.type == 'nt_lookup_fn':
|
|
s = operand.lookupfn_name # .upper()
|
|
s = re.sub(r'_SB','',s)
|
|
s = re.sub(r'_SR','',s)
|
|
s = re.sub(r'_EB','',s) # order counts _EB before _B
|
|
s = re.sub(r'_[RBNEI].*','',s)
|
|
s = re.sub(r'_DREX','',s) # AMD SSE5
|
|
s = re.sub(r'_SE','',s)
|
|
if operand.oc2 and s not in ['X87'] :
|
|
if operand.oc2 == 'v' and s[-1] == 'v':
|
|
pass # avoid duplicate v's
|
|
else:
|
|
s += operand.oc2
|
|
iform.append( s )
|
|
elif operand.type == 'reg':
|
|
s = operand.bits.upper()
|
|
s= re.sub('XED_REG_','',s)
|
|
if operand.oc2 and operand.oc2 not in ['f80']:
|
|
s += operand.oc2
|
|
iform.append( s )
|
|
elif operand.type == 'imm_const':
|
|
s = operand.name.upper()
|
|
s=re.sub('IMM[01]','IMM',s)
|
|
s=re.sub('MEM[01]','MEM',s)
|
|
add_suffix = True
|
|
if s == 'IMM':
|
|
add_suffix = options.add_suffix_to_imm
|
|
if add_suffix:
|
|
if operand.oc2:
|
|
s += operand.oc2
|
|
iform.append( s )
|
|
else: # this skips MOD/REG/RMp
|
|
if viform():
|
|
msge("IFORM SKIPPING %s" % (operand.name))
|
|
if len(iform) == 0:
|
|
iform = ['default']
|
|
ii.iform = iform
|
|
if viform():
|
|
msgb("IFORMX", "%s: %s" % (ii.iclass, "_".join(iform)))
|
|
return tuple(iform)
|
|
|
|
|
|
def compute_iforms(options, gi, operand_storage_dict):
|
|
"""Classify the operand patterns"""
|
|
|
|
# look at the first parser record to see if it contains actual
|
|
# instructions.
|
|
ii = gi.parser_output.instructions[0]
|
|
if not field_check(ii,'iclass'):
|
|
return None
|
|
|
|
iforms = {} # dict by iform pointing instructions recs
|
|
ii_iforms = {} # dict by iclass of iform names
|
|
for ii in gi.parser_output.instructions:
|
|
iform = compute_iform(options,ii,operand_storage_dict)
|
|
if viform():
|
|
msge("IFORM %s %s" % (ii.iclass, str(iform)))
|
|
s = "_".join(iform)
|
|
if ii.iform_input: # override from grammar input
|
|
s = ii.iform_input
|
|
ii.iform_enum = s
|
|
|
|
if viform():
|
|
try:
|
|
iforms[s].append(ii)
|
|
except:
|
|
iforms[s]=[ii]
|
|
try:
|
|
ii_iforms[ii.iclass].append(s)
|
|
except:
|
|
ii_iforms[ii.iclass]=[s]
|
|
|
|
# printing various ways
|
|
if viform():
|
|
for iform,iilist in iforms.items():
|
|
msge("IFORM %s: %s" % (iform,
|
|
" ".join([x.iclass for x in iilist] )))
|
|
|
|
for iclass,iformlist in ii_iforms.items():
|
|
str_iforms = {}
|
|
dups = []
|
|
for iform in iformlist:
|
|
if iform in str_iforms:
|
|
dups.append(iform)
|
|
else:
|
|
str_iforms[iform]=True
|
|
|
|
|
|
msge("II_IFORM %s: %s" % (iclass, " ".join(list(str_iforms.keys()))))
|
|
if len(dups)!=0:
|
|
msge("\tDUPS: %s: %s" % (iclass," ".join(dups)))
|
|
|
|
############################################################################
|
|
## CG code generation
|
|
############################################################################
|
|
|
|
# $$ code_gen_dec_arg_t
|
|
class code_gen_dec_args_t(object):
|
|
"""Empty class that I fill in as I pass arguments"""
|
|
pass
|
|
|
|
|
|
operand_max=0
|
|
|
|
def code_gen_itable_operand(agi,
|
|
data_table_file,
|
|
operand):
|
|
"""Emit code for one opnds.operand_info_t operand"""
|
|
|
|
global operand_max
|
|
|
|
if operand.type == 'error':
|
|
return False
|
|
if operand.internal:
|
|
return False
|
|
|
|
this_operand = operand_max
|
|
oprefix = 'xed_operand+%s' % str(operand_max)
|
|
operand_max += 1
|
|
|
|
x_name = None
|
|
x_vis = None
|
|
x_rw = None
|
|
x_oc2 = None
|
|
x_type = None
|
|
x_xtype = None
|
|
x_imm_nt_reg = '0'
|
|
|
|
x_name = 'XED_OPERAND_%s' % operand.name.upper()
|
|
|
|
if operand.type == 'nt_lookup_fn':
|
|
x_imm_nt_reg = 'XED_NONTERMINAL_' + operand.lookupfn_name.upper()
|
|
elif operand.type == 'imm_const':
|
|
x_imm_nt_reg = operand.bits
|
|
elif operand.type == 'reg':
|
|
x_imm_nt_reg = operand.bits
|
|
elif operand.type == 'flag': # FIXME: not used
|
|
x_imm_nt_reg = operand.bits
|
|
|
|
try:
|
|
x_vis = 'XED_OPVIS_%s' % operand.visibility.upper()
|
|
x_type = 'XED_OPERAND_TYPE_%s' % operand.get_type_for_emit()
|
|
|
|
#
|
|
# Some "PUBLIC" operands captured in the pattern do not have
|
|
# xtypes specified. I just make them int types.
|
|
#
|
|
if operand.xtype == None:
|
|
operand.xtype = 'int'
|
|
x_xtype ='XED_OPERAND_XTYPE_%s' % operand.xtype.upper()
|
|
|
|
x_rw = 'XED_OPERAND_ACTION_%s' % operand.rw.upper()
|
|
x_cvt_index = str(operand.cvt_index)
|
|
except:
|
|
mbuild.die("ERROR processing operand %s" % (str(operand)))
|
|
|
|
if operand.oc2:
|
|
x_oc2 ='XED_OPERAND_WIDTH_%s' % (operand.oc2.upper())
|
|
else:
|
|
try:
|
|
if operand.type == 'nt_lookup_fn':
|
|
x_oc2 ='XED_OPERAND_WIDTH_%s' % (
|
|
agi.extra_widths_nt[operand.lookupfn_name].upper() )
|
|
elif operand.type == 'reg':
|
|
tname = re.sub('XED_REG_', '', operand.bits)
|
|
x_oc2 ='XED_OPERAND_WIDTH_%s' % (
|
|
agi.extra_widths_reg[tname].upper() )
|
|
elif operand.type == 'imm_const':
|
|
x_oc2 ='XED_OPERAND_WIDTH_%s' % (
|
|
agi.extra_widths_imm_const[operand.name].upper() )
|
|
else:
|
|
mbuild.msgb("INVALID WIDTH CODE", str(operand))
|
|
x_oc2 ='XED_OPERAND_WIDTH_INVALID'
|
|
except:
|
|
mbuild.msgb("INVALID WIDTH CODE", str(operand))
|
|
x_oc2 ='XED_OPERAND_WIDTH_INVALID'
|
|
|
|
if operand.type == 'nt_lookup_fn':
|
|
nt = '1'
|
|
else:
|
|
nt = '0'
|
|
args = [ x_name, x_vis, x_rw, x_oc2, x_type, x_xtype,
|
|
x_cvt_index, x_imm_nt_reg, nt ]
|
|
|
|
try:
|
|
#msgb("X_NAME", x_name)
|
|
s_args = ",".join(args)
|
|
data_table_file.add_code( '/*%4d*/ XED_DEF_OPND(%s),' %
|
|
(this_operand, s_args) )
|
|
except:
|
|
die("Bad token in list: %s" % (str(args)))
|
|
|
|
return True
|
|
|
|
def memorize_attributes_equation(agi, attr_string_or):
|
|
try:
|
|
return agi.attributes_dict[attr_string_or]
|
|
except:
|
|
p = agi.attr_next_pos
|
|
if vattr():
|
|
msgb("Memorizing attribute",
|
|
"%d -> %s" % (p, attr_string_or))
|
|
|
|
agi.attributes_dict[attr_string_or] = p
|
|
agi.attr_next_pos = p + 1
|
|
return p
|
|
|
|
|
|
|
|
def make_one_attribute_equation(attr_grp,basis):
|
|
one = '((xed_uint64_t)1)'
|
|
attr_string_or = None
|
|
for a in attr_grp:
|
|
if vattr():
|
|
msgb("ADDING ATTRIBUTE", "%s for %s" % ( a, ii.iclass))
|
|
|
|
if basis:
|
|
rebase = "(%s<<(XED_ATTRIBUTE_%s-%d))" % (one, a, basis)
|
|
else:
|
|
rebase = "(%s<<XED_ATTRIBUTE_%s)" % (one, a)
|
|
|
|
if attr_string_or:
|
|
attr_string_or = "%s|%s" % (attr_string_or, rebase)
|
|
else:
|
|
attr_string_or = rebase
|
|
|
|
return attr_string_or
|
|
|
|
def lookup_attr(agi, attr):
|
|
try:
|
|
return agi.sorted_attributes_dict[attr]
|
|
except:
|
|
die("Failed to find attribute [%s] in attributes dictionary" % (attr))
|
|
|
|
def partition_attributes(agi, attr):
|
|
"""Partition the attributes in to groups of 64 by their
|
|
ordinality. Return a list of groups. 0..63 are in one group,
|
|
64...127 in the next, etc.
|
|
"""
|
|
|
|
d = { 0:[], 1:[] }
|
|
#msgb("PARTITIONING ATTRIBUTES", '[%s]' % (",".join(attr)))
|
|
for a in attr:
|
|
i = lookup_attr(agi,a)
|
|
b = i // 64
|
|
try:
|
|
d[b].append(a)
|
|
except:
|
|
d[b] = [a]
|
|
return d
|
|
|
|
|
|
def make_attributes_equation(agi,ii):
|
|
"""Make a unique key representing the attributes of this instruction"""
|
|
key = ''
|
|
if field_check(ii,'attributes'):
|
|
if ii.attributes:
|
|
trimmed_attributes = \
|
|
list(filter(lambda s: s.find('=') == -1 ,ii.attributes))
|
|
|
|
if len(trimmed_attributes) > 0:
|
|
trimmed_attributes.sort()
|
|
key = ",".join(trimmed_attributes)
|
|
|
|
n = memorize_attributes_equation(agi,key)
|
|
return n
|
|
|
|
def make_attributes_structure_init(agi,v):
|
|
eqns = {0:'0',1:'0'}
|
|
|
|
if v:
|
|
groups = partition_attributes(agi, v)
|
|
n = len(groups)
|
|
for i in range(0,n):
|
|
g = groups[i]
|
|
eqns[i] = make_one_attribute_equation(g,i*64)
|
|
|
|
el = []
|
|
n = len(eqns)
|
|
for i in range(0,n):
|
|
if eqns[i]:
|
|
el.append(eqns[i])
|
|
else:
|
|
el.append('0')
|
|
|
|
s = '{ %s }' % (",".join(el))
|
|
return s
|
|
|
|
############################################################################
|
|
global_operand_table = {}
|
|
global_operand_table_id = 0
|
|
global_id_to_operand = {}
|
|
global_oid_sequences = {}
|
|
global_max_operand_sequences = 0
|
|
global_oid_sequence_id_to_oid_list = {}
|
|
def remember_operand(xop):
|
|
"""Call this from wherever operands are created. It assigns unique
|
|
IDs to each operand."""
|
|
|
|
global global_operand_table
|
|
global global_operand_table_id
|
|
global global_id_to_operand
|
|
|
|
try:
|
|
xop.unique_id = global_operand_table[xop]
|
|
msgb("A9: Found existing operand ID {} for {}".format(xop.unique_id,
|
|
str(xop)))
|
|
except:
|
|
global_operand_table[xop] = global_operand_table_id
|
|
xop.unique_id = global_operand_table_id
|
|
global_id_to_operand[xop.unique_id] = xop
|
|
global_operand_table_id = global_operand_table_id + 1
|
|
|
|
import hlist
|
|
def find_common_operand_sequences(agi):
|
|
"""Label each instruction with an oid_sequence number that
|
|
corresponds to its operand sequence. The operands get their
|
|
unique_ids first. """
|
|
|
|
global global_operand_table_id # counter of # of operands
|
|
global global_oid_sequences
|
|
global global_max_operand_sequences
|
|
global global_oid_sequence_id_to_oid_list
|
|
next_oid_seqeuence = 0
|
|
reused = 0
|
|
n_operands = 0
|
|
for gi in agi.generator_list:
|
|
for ii in gi.parser_output.instructions:
|
|
# build up a list of operand unique indices
|
|
ii.oid_list = []
|
|
for op in ii.operands:
|
|
# skip the internal operands
|
|
if op.internal:
|
|
continue
|
|
remember_operand(op)
|
|
ii.oid_list.append(op.unique_id)
|
|
|
|
# then find out if other instructions share that operand sequence
|
|
hl = hlist.hlist_t(ii.oid_list)
|
|
try:
|
|
(ii.oid_sequence, ii.oid_sequence_start) = \
|
|
global_oid_sequences[hl]
|
|
reused = reused + 1
|
|
except:
|
|
ii.oid_sequence = next_oid_seqeuence
|
|
ii.oid_sequence_start = n_operands
|
|
global_oid_sequences[hl] = (next_oid_seqeuence, n_operands)
|
|
global_oid_sequence_id_to_oid_list[next_oid_seqeuence] = hl
|
|
next_oid_seqeuence = next_oid_seqeuence + 1
|
|
n_operands = n_operands + len(ii.oid_list)
|
|
|
|
msgb("Unique Operand Sequences", str(next_oid_seqeuence))
|
|
n = 0
|
|
for k in list(global_oid_sequences.keys()):
|
|
n = n + len(k.lst)
|
|
global_max_operand_sequences = n
|
|
msgb("Number of required operand sequence pointers",
|
|
str(global_max_operand_sequences))
|
|
msgb("Number of reused operand sequence pointers", str(reused))
|
|
msgb("Number of required operands", str(global_operand_table_id))
|
|
|
|
|
|
def code_gen_operand_sequences(agi):
|
|
global global_oid_sequences
|
|
global global_oid_sequence_id_to_oid_list
|
|
|
|
m = len(global_oid_sequences)
|
|
k = 0
|
|
for i in range(0, m):
|
|
hl = global_oid_sequence_id_to_oid_list[i]
|
|
for oi,n in enumerate(hl.lst):
|
|
s = '/* %4d %4d.%1d */ %6d,' % (k, i, oi, n)
|
|
agi.operand_sequence_file.add_code(s)
|
|
k = k + 1
|
|
|
|
def code_gen_unique_operands(agi):
|
|
global global_operand_table_id
|
|
global global_id_to_operand
|
|
|
|
for i in range(0,global_operand_table_id):
|
|
operand = global_id_to_operand[i]
|
|
okay = code_gen_itable_operand(agi, agi.data_table_file, operand)
|
|
if not okay:
|
|
die("operand code gen failed")
|
|
|
|
############################################################################
|
|
max_operand_count = 0
|
|
global_final_inum = 0
|
|
global_emitted_zero_inum = False
|
|
def code_gen_instruction(agi, options, ii, state_dict, fo,
|
|
nonterminal_dict, operand_storage_dict):
|
|
"""Emit code for one instruction entry"""
|
|
global max_operand_count
|
|
fp = agi.inst_fp
|
|
|
|
itable = 'xed_inst_table[' + str(ii.inum) + ']'
|
|
global global_final_inum
|
|
if ii.inum > global_final_inum:
|
|
global_final_inum = ii.inum
|
|
|
|
global global_emitted_zero_inum
|
|
if ii.inum == 0:
|
|
if global_emitted_zero_inum:
|
|
return
|
|
global_emitted_zero_inum = True
|
|
|
|
has_iclass = field_check(ii,'iclass')
|
|
if verb1():
|
|
s = "code_gen_instruction - inum: " + str(ii.inum) + ' '
|
|
if has_iclass:
|
|
s += ii.iclass
|
|
else:
|
|
s += 'no-iclass'
|
|
msge( s)
|
|
|
|
# print the operands - separate table with 'index & count" pointers
|
|
# in this table
|
|
operand_count = 0
|
|
for operand in ii.operands:
|
|
if operand.type == 'error':
|
|
continue
|
|
if operand.internal:
|
|
continue
|
|
|
|
operand_count = operand_count + 1
|
|
if operand_count != len(ii.oid_list):
|
|
die("Mismatch on operand list for %s" % (str(ii)))
|
|
|
|
# print the flags - separate table with "index & count" pointers in
|
|
# this table
|
|
flgrec=0
|
|
complex =False
|
|
if field_check(ii,'flags_info'):
|
|
if ii.flags_info:
|
|
# emit the rflags info
|
|
#FIXME: OLD (flgrec, complex) = ii.flags_info.code_gen(itable, fo)
|
|
(flgrec, complex) = ii.flags_info.emit_data_record(agi.flag_simple_file,
|
|
agi.flag_complex_file,
|
|
agi.flag_action_file)
|
|
|
|
|
|
# not using "1ULL" because that does not work with VC6 (!!!)
|
|
one = '((xed_uint64_t)1)'
|
|
# emit attributes
|
|
attributes_index = make_attributes_equation(agi,ii)
|
|
|
|
operand_names = [ x.name.upper() for x in ii.operands]
|
|
|
|
# THE NEW WAY - DATA INITIALIZATION -- see include/private/xed-inst-defs.h
|
|
cpl = '3'
|
|
if has_iclass:
|
|
args = [ 'XED_ICLASS_%s' % (ii.iclass.upper()),
|
|
'XED_CATEGORY_%s' % (ii.category.upper()),
|
|
'XED_EXTENSION_%s' % (ii.extension.upper()) ]
|
|
if ii.cpl:
|
|
cpl = str(ii.cpl)
|
|
args.append(cpl)
|
|
args.append('XED_IFORM_%s' %( ii.iform_enum))
|
|
|
|
else:
|
|
args = [ 'XED_ICLASS_INVALID',
|
|
'XED_CATEGORY_INVALID',
|
|
'XED_EXTENSION_INVALID']
|
|
args.append(cpl)
|
|
args.append('XED_IFORM_INVALID')
|
|
|
|
|
|
#if field_check(ii,'ucode') and ii.ucode:
|
|
# args.append(str(ii.ucode))
|
|
#else:
|
|
# args.append('0')
|
|
|
|
|
|
args.append(str(ii.oid_sequence_start))
|
|
args.append(str(operand_count))
|
|
if operand_count > max_operand_count:
|
|
max_operand_count = operand_count
|
|
|
|
args.append(str(flgrec))
|
|
if complex:
|
|
flagtype = '1'
|
|
else:
|
|
flagtype = '0'
|
|
args.append(flagtype)
|
|
|
|
args.append(str(attributes_index))
|
|
if field_check(ii,'exceptions') and ii.exceptions:
|
|
args.append('XED_EXCEPTION_' + ii.exceptions)
|
|
else:
|
|
args.append('XED_EXCEPTION_INVALID')
|
|
|
|
s_args = ",".join(args)
|
|
|
|
fp.add_code( '/*%4d*/ XED_DEF_INST(%s),' % (ii.inum, s_args) )
|
|
|
|
|
|
|
|
|
|
#$$ table_init_object
|
|
class table_init_object_t(object):
|
|
def __init__(self, file_name, function_name):
|
|
self.file_name_prefix = file_name
|
|
self.function_name_prefix = function_name
|
|
|
|
self.fp = None # file pointer
|
|
self.fo = None # function_object_t
|
|
self.init_functions = []
|
|
self.max_lines_per_file = 3000
|
|
|
|
def get_init_functions(self):
|
|
return self.init_functions
|
|
|
|
def get_fo(self,gi):
|
|
if not self.fp:
|
|
# make a new output file and new function obj if we don't
|
|
# already have one
|
|
n = str(len(self.init_functions))
|
|
self.fp = gi.common.open_file(self.file_name_prefix + n + '.c',
|
|
start=False)
|
|
self.fp.start()
|
|
|
|
full_function_name = self.function_name_prefix + n
|
|
self.fo = function_object_t(full_function_name,"void")
|
|
self.init_functions.append(self.fo)
|
|
return self.fo
|
|
|
|
def check_file(self):
|
|
if self.fo:
|
|
if self.fo.lines() >= self.max_lines_per_file:
|
|
|
|
self.fp.write(self.fo.emit())
|
|
self.fp.close()
|
|
del self.fp
|
|
|
|
self.fo = None
|
|
self.fp = None
|
|
|
|
def finish_fp(self):
|
|
# write anything that didn't get emitted already
|
|
if self.fp:
|
|
self.fp.write(self.fo.emit())
|
|
self.fp.close()
|
|
del self.fp
|
|
|
|
self.fo = None
|
|
self.fp = None
|
|
|
|
|
|
|
|
|
|
|
|
def code_gen_instruction_table(agi, gi, itable_init, nonterminal_dict,
|
|
operand_storage_dict):
|
|
"""Emit a table of all instructions. itable_init is a
|
|
table_init_object_t with a list of function_object_ts to which we add
|
|
those that we create."""
|
|
if vtrace():
|
|
msge("code_gen_instruction_table")
|
|
|
|
for ii in gi.parser_output.instructions:
|
|
fo = itable_init.get_fo(gi)
|
|
code_gen_instruction(agi,
|
|
gi.common.options,
|
|
ii,
|
|
gi.common.state_bits,
|
|
fo,
|
|
nonterminal_dict,
|
|
operand_storage_dict)
|
|
|
|
itable_init.check_file()
|
|
|
|
|
|
def rewrite_default_operand_visibilities(generator,
|
|
operand_field_dict):
|
|
"""Change the default visibilty of any operand to the visibilty
|
|
indicated by the operand_field_t in the dictionary."""
|
|
|
|
if not generator.parser_output.is_lookup_function():
|
|
for ii in generator.parser_output.instructions:
|
|
#if field_check(ii,'iclass'):
|
|
# mbuild.msgb("Processing", ii.iclass)
|
|
for opnd in ii.operands:
|
|
#mbuild.msgb("Operand", "\t%s" % (str(opnd)))
|
|
if opnd.visibility == 'DEFAULT':
|
|
new_vis = operand_field_dict[opnd.name].default_visibility
|
|
if vopvis():
|
|
msge("OPVIS-DELTA: " + opnd.name + " to " + new_vis )
|
|
opnd.visibility = new_vis
|
|
|
|
#################################################################
|
|
def emit_string_table(agi, iclass_strings):
|
|
|
|
f = agi.common.open_file('xed-iclass-string.c', start=False)
|
|
f.add_misc_header('#include "xed-gen-table-defs.h"')
|
|
f.add_misc_header('#include "xed-tables-extern.h"')
|
|
f.start()
|
|
s = 'char const* const xed_iclass_string[XED_ICLASS_NAME_STR_MAX] = {\n'
|
|
f.write(s)
|
|
for i in iclass_strings:
|
|
f.write('"%s",\n' % (i))
|
|
f.write('};\n')
|
|
f.close()
|
|
|
|
|
|
def collect_iclass_strings(agi):
|
|
"""We collect the disasm strings in pairs. One for Intel, One for
|
|
ATT SYSV syntax"""
|
|
iclass_strings = ['invalid','invalid']
|
|
|
|
# string table indexed by intel syntax dotted with the att syntax
|
|
st = { 'invalid.invalid': 0 }
|
|
n = 2
|
|
for generator in agi.generator_list:
|
|
ii = generator.parser_output.instructions[0]
|
|
if not field_check(ii,'iclass'):
|
|
continue
|
|
for ii in generator.parser_output.instructions:
|
|
if field_check(ii,'disasm_intel'):
|
|
if not field_check(ii,'disasm_att'):
|
|
die("Missing att syntax when intel sytnax" +
|
|
" is provided for %s" % (ii.iclass))
|
|
if field_check(ii,'disasm_att'):
|
|
if not field_check(ii,'disasm_intel'):
|
|
die("Missing intel syntax when att sytnax " +
|
|
" is provided for %s" % (ii.iclass))
|
|
if field_check(ii,'disasm_att'):
|
|
k = '%s.%s' % (ii.disasm_intel, ii.disasm_att)
|
|
if k in st:
|
|
ii.iclass_string_index = st[k]
|
|
else:
|
|
st[k] = n
|
|
ii.iclass_string_index = n
|
|
iclass_strings.append(ii.disasm_intel)
|
|
iclass_strings.append(ii.disasm_att)
|
|
n = n + 2
|
|
|
|
agi.max_iclass_strings = n
|
|
emit_string_table(agi, iclass_strings)
|
|
|
|
def compress_iform_strings(values):
|
|
# values are a list of 3 tuples (iform string, index, comment) and
|
|
# the comments are generally empty strings.
|
|
bases = {}
|
|
operand_sigs = { '':0 }
|
|
o_indx = 1
|
|
b_indx = 0
|
|
h = {} # map index to base, operand indices
|
|
|
|
# split the bases (iclass name, mostly) and operand sigs.
|
|
# assign ids to the bases and to the operand sigs
|
|
for iform,index,comment in values:
|
|
try:
|
|
s,rest = iform.split("_",1)
|
|
if s not in bases:
|
|
bases[s]=b_indx
|
|
b = b_indx
|
|
b_indx += 1
|
|
if rest not in operand_sigs:
|
|
operand_sigs[rest] = o_indx
|
|
o = o_indx
|
|
o_indx += 1
|
|
except:
|
|
if iform not in bases:
|
|
bases[iform]=b_indx
|
|
b = b_indx
|
|
o = 0
|
|
b_indx += 1
|
|
# store the base,operand_sig pair
|
|
h[int(index)] = (b,o)
|
|
|
|
print("XZ: NTUPLES {} BASES {} OPERAND_SIGS {}".format(len(values),
|
|
len(bases),
|
|
len(operand_sigs)))
|
|
|
|
if len(h) != (max( [ int(x) for x in list(h.keys())] )+1):
|
|
print("PROBLEM IN h LENGTH")
|
|
# make an numerically indexed version of the bases table
|
|
bi = {}
|
|
for k,v in bases.items():
|
|
bi[v] = k
|
|
# make an numerically indexed version of the operand_sig table
|
|
oi = {}
|
|
for k,v in operand_sigs.items():
|
|
oi[v] = k
|
|
|
|
f = sys.stdout
|
|
|
|
f.write('static const char* base[] = {\n')
|
|
for i in range(0,len(bases)):
|
|
f.write( '/* {} */ "{}",\n'.format(i,bi[i]) )
|
|
f.write('};\n')
|
|
|
|
f.write('static const char* operands[] = {\n')
|
|
for i in range(0,len(operand_sigs)):
|
|
f.write('/* {} */ "{}",\n'.format(i,oi[i]))
|
|
f.write('};\n')
|
|
|
|
f.write('static const iform_name_chunks[] = {\n')
|
|
for i in range(0,len(h)):
|
|
a,b = h[i]
|
|
f.write( '/* {} */ {{ {},{} }},\n'.format(i,a,b))
|
|
f.write('};\n')
|
|
|
|
|
|
def generate_iform_enum(agi,options,values):
|
|
# values are a list of 3 tuples (iform string, index, comment) and
|
|
# the comments are generally empty strings.
|
|
string_convert = 1
|
|
if options.limit_enum_strings:
|
|
string_convert = 0
|
|
enum = enum_txt_writer.enum_info_t(values,
|
|
options.xeddir, options.gendir,
|
|
'xed-iform',
|
|
'xed_iform_enum_t', 'XED_IFORM_',
|
|
cplusplus=False,
|
|
extra_header = ['xed-common-hdrs.h',
|
|
'xed-iclass-enum.h'],
|
|
upper_case=False,
|
|
string_convert=string_convert)
|
|
enum.print_enum()
|
|
enum.run_enumer()
|
|
agi.add_file_name(enum.src_full_file_name)
|
|
agi.add_file_name(enum.hdr_full_file_name,header=True)
|
|
|
|
def generate_iform_first_last_enum(agi,options,values):
|
|
enum = enum_txt_writer.enum_info_t(values,
|
|
options.xeddir, options.gendir,
|
|
'xed-iformfl',
|
|
'xed_iformfl_enum_t',
|
|
'XED_IFORMFL_',
|
|
cplusplus=False,
|
|
extra_header = ['xed-common-hdrs.h',
|
|
'xed-iclass-enum.h'],
|
|
upper_case=False,
|
|
string_convert=-1)
|
|
enum.print_enum()
|
|
enum.run_enumer()
|
|
agi.add_file_name(enum.src_full_file_name)
|
|
agi.add_file_name(enum.hdr_full_file_name,header=True)
|
|
|
|
|
|
|
|
global_max_iforms_per_iclass = 0
|
|
|
|
def collect_and_emit_iforms(agi,options):
|
|
iform_dict = {} # build dictionary by iclass of [iform,...]
|
|
for generator in agi.generator_list:
|
|
ii = generator.parser_output.instructions[0]
|
|
if not field_check(ii,'iclass'):
|
|
continue
|
|
for ii in generator.parser_output.instructions:
|
|
try:
|
|
iform_dict[ii.iclass].append(ii.iform_enum)
|
|
except:
|
|
iform_dict[ii.iclass] = [ii.iform_enum]
|
|
|
|
# number them from zero, per iclass
|
|
vtuples = [('INVALID', 0, 'INVALID') ]
|
|
imax = {} # maximum number of iforms per iclass
|
|
for ic,ol in iform_dict.items():
|
|
ol = uniqueify(ol)
|
|
sz= len(ol)
|
|
vsub = zip([ic.upper()]*sz, # the iclass
|
|
range(0,sz), # number the iforms
|
|
ol) # the list of iform names
|
|
imax[ic] = sz
|
|
vtuples.extend(vsub)
|
|
|
|
#msge("VTUPLES %s" % (str(vtuples)))
|
|
# Relying on stable sorting. sort first by 2nd field (#1), then
|
|
# sort by iclass, making sure "INVALID" is first.
|
|
vtuples.sort(key=key_tuple_element_1)
|
|
vtuples.sort(key=key_invalid_tuple_element_0)
|
|
|
|
|
|
agi.iform_tuples = vtuples
|
|
|
|
# number the tuples from 0
|
|
ntuples = []
|
|
for i,v in enumerate(vtuples):
|
|
lv = list(v)
|
|
lv.extend([str(i),''])
|
|
t = tuple(lv)
|
|
ntuples.append(t)
|
|
|
|
#msge("NTUPLES %s" % (str(ntuples)))
|
|
# add a first and last element for each group of iforms (per iclass)
|
|
first_last_tuples = []
|
|
last_tuple = None
|
|
ifirst = {}
|
|
for v in ntuples:
|
|
if last_tuple and last_tuple[0] != v[0]:
|
|
if last_tuple[0] != 'INVALID':
|
|
t = ( 'INVALID', 0, last_tuple[0] + "_LAST", last_tuple[3], '')
|
|
first_last_tuples.append(t)
|
|
t = ( 'INVALID', 0, v[0] + "_FIRST", v[3], '')
|
|
ifirst[v[0]] = int(v[3])
|
|
first_last_tuples.append(t)
|
|
last_tuple = v
|
|
if last_tuple and last_tuple[0] != 'INVALID':
|
|
t = ( 'INVALID', 0, last_tuple[0] + "_LAST", last_tuple[3], '')
|
|
first_last_tuples.append(t)
|
|
|
|
|
|
#msge("NTUPLES %s" % (str(ntuples)))
|
|
# rip off first two fields of vtuples
|
|
vtuples = [ x[2:] for x in ntuples]
|
|
|
|
#for t in vtuples:
|
|
# msge("TUPLE " + str(t))
|
|
generate_iform_enum(agi,options,vtuples)
|
|
# compress_iform_strings(vtuples)
|
|
|
|
# rip off first two fields of vtuples
|
|
first_last_tuples = [ x[2:] for x in first_last_tuples]
|
|
generate_iform_first_last_enum(agi,options,first_last_tuples)
|
|
|
|
#emit imax in global iclass order for data-initialization!
|
|
cfp = agi.open_file('xed-iform-max.c')
|
|
cfp.write('const xed_uint32_t ' +
|
|
'xed_iform_max_per_iclass_table[XED_ICLASS_LAST] = {\n')
|
|
first = True
|
|
gmax = 0 # maximum number of iforms for any iclass
|
|
niform = 0 # total number of iforms
|
|
for ic in agi.iclasses_enum_order:
|
|
if first:
|
|
first = False
|
|
else:
|
|
cfp.write(',\n')
|
|
try:
|
|
mx = imax[ic]
|
|
except:
|
|
mx = 0 # for the INVALID entry
|
|
if mx > gmax:
|
|
gmax = mx
|
|
niform = niform + mx
|
|
cfp.write(' /* %25s */ %2d' % (ic,mx))
|
|
cfp.write('\n};\n')
|
|
|
|
|
|
cfp.write('const xed_uint32_t' +
|
|
' xed_iform_first_per_iclass_table[XED_ICLASS_LAST] = {\n')
|
|
first = True
|
|
niform = 0 # total number of iforms
|
|
for ic in agi.iclasses_enum_order:
|
|
if first:
|
|
first = False
|
|
else:
|
|
cfp.write(',\n')
|
|
try:
|
|
firstiform = ifirst[ic]
|
|
except:
|
|
firstiform = 0 # for the INVALID entry
|
|
|
|
cfp.write(' /* %25s */ %2d' % (ic,firstiform))
|
|
cfp.write('\n};\n')
|
|
|
|
cfp.close()
|
|
|
|
global global_max_iforms_per_iclass
|
|
global_max_iforms_per_iclass = gmax
|
|
|
|
|
|
############################################################################
|
|
|
|
def relabel_itable(agi):
|
|
"""Renumber the itable so that it is sequential."""
|
|
global global_inum
|
|
inum = 1
|
|
for gi in agi.generator_list:
|
|
if not gi.parser_output.is_lookup_function():
|
|
for ii in gi.parser_output.instructions:
|
|
has_iclass = field_check(ii,'iclass')
|
|
if has_iclass:
|
|
ii.inum = inum
|
|
inum += 1
|
|
else:
|
|
# make all the non-instruction leaves point to node zero
|
|
ii.inum = 0
|
|
global_inum = inum
|
|
|
|
|
|
############################################################################
|
|
# The renum_node_id is global because we renumber each graph so that
|
|
# we have contiguous numbers for graph code-gen for the distinct subgraphs.
|
|
renum_node_id = -1
|
|
def renumber_nodes(options,node):
|
|
"""renumber the nodes now that we've deleted some"""
|
|
#msge("renumbering graph nodes..")
|
|
renumber_nodes_sub(options,node)
|
|
global renum_node_id
|
|
#msge(" ...last node id = %d" % (renum_node_id))
|
|
|
|
def renumber_nodes_sub(options,node):
|
|
"""renumber the nodes now that we've deleted some"""
|
|
# bump the 'global' node counter
|
|
global renum_node_id
|
|
renum_node_id = renum_node_id + 1
|
|
# update the current node
|
|
#msge("RENUMBER NODE %d becomes %d" % ( node.id, renum_node_id))
|
|
node.id = renum_node_id
|
|
# recur
|
|
for nxt in node.next.values():
|
|
node_id = renumber_nodes_sub(options,nxt)
|
|
|
|
|
|
|
|
def merge_child_nodes(options,node):
|
|
"""Merge the children and grandchildren of this node."""
|
|
candidates = len(node.next)
|
|
if vmerge():
|
|
msge(str(candidates) + " merge candidate")
|
|
# merge tokens??
|
|
# should not need to merge instructions
|
|
# bit_pos* becomes a bigger range
|
|
# more "next" nodes.
|
|
tnode = {}
|
|
for k,child in node.next.items(): # children # MERGING
|
|
for j in list(child.next.keys()): # grandchildren
|
|
bigkey = str(k) + str(j)
|
|
if vmerge():
|
|
msge("Bigkey= %s" % (bigkey))
|
|
child.next[j].token = bigkey
|
|
tnode[bigkey] = child.next[j]
|
|
# overwrite the current nodes next pointers:
|
|
node.next = tnode
|
|
|
|
# increment number of decider bits
|
|
node.decider_bits = node.decider_bits + 1
|
|
if vmerge():
|
|
msge("Decider bits after merging = " + str(node.decider_bits))
|
|
|
|
|
|
|
|
def merge_nodes(options,node):
|
|
"""Merge compatible nodes, deleting some nodes and increasing the
|
|
arity of others"""
|
|
# If nodes are sequential in their bit positions and the next one
|
|
# is not a leaf, consider merging them.
|
|
|
|
#FIXME: must not merge across state bits.
|
|
if (not node.is_nonterminal() and
|
|
not node.leaf() and
|
|
not node.is_operand_decider()):
|
|
merging = True
|
|
while merging:
|
|
all_match = True
|
|
decider_bits = [ node.next[k].decider_bits for k in
|
|
list(node.next.keys()) ]
|
|
if not all_the_same(decider_bits):
|
|
if vmerge():
|
|
msge("Not merging because unequal numbers of decider" +
|
|
" bits follow:" + str(decider_bits))
|
|
for nxt in node.next.values():
|
|
msge("\tChildNode:\n" +nxt.dump_str('\t\t'))
|
|
all_match = False
|
|
break
|
|
|
|
|
|
# stop at byte boundaries. All the children have the same
|
|
# number of decider bits at this point. Look at the first
|
|
# one.
|
|
if decider_bits[0] == 8:
|
|
msge("Stopping child nodes with 8 decider bits")
|
|
break
|
|
if vmerge():
|
|
msge("PREMRG node decider " +
|
|
"bits= %d child decider bits= %d bitpos_mod8= %d\n" %
|
|
( node.decider_bits, decider_bits[0], node.bitpos_mod8))
|
|
|
|
# FIXME: the following is not right. We want the bitpos_mod8
|
|
# of the child because that is what we are merging with the
|
|
# grandchild. We also don't care about the decider its of the parent.
|
|
|
|
# FIXME: we are not updating the bitpos_mod8 of the children
|
|
# when we merge them.
|
|
|
|
# NOTE: IT IS BETTER NOT DO DO THIS TEST AT ALL. THE GRAPH IS
|
|
# MUCH SMALLER. but more 'next' nodes, which are much
|
|
# smaller. so that is good!
|
|
|
|
# Do not want to merge across byte boundaries.
|
|
#if node.decider_bits + decider_bits[0] + node.bitpos_mod8 > 8:
|
|
#if node.decider_bits + decider_bits[0] + node.bitpos_mod8 > 8:
|
|
# msge("Stopping child node merging at a byte boundary")
|
|
# break
|
|
|
|
|
|
# look at all the next nodes
|
|
for child in node.next.values():
|
|
if child.back_split_pos != None:
|
|
if vmerge():
|
|
msge("Not merging because a child is back-split")
|
|
all_match = False
|
|
break
|
|
if child.is_nonterminal():
|
|
if vmerge():
|
|
msge("Not merging because a child is a nonterminal")
|
|
all_match = False
|
|
break
|
|
if child.decider_bits == 0: # FIXME: WHY WOULD THIS HAPPEN?
|
|
if vmerge():
|
|
msge("Not merging because zero decider bits follow: " +
|
|
str(child.decider_bits))
|
|
msge("\tChildNode:\n" + child.dump_str('\t'))
|
|
all_match = False
|
|
break
|
|
if child.skipped_bits != 0:
|
|
if vmerge():
|
|
msge("Not merging because skipped bits at child level: " +
|
|
str(child.skipped_bits))
|
|
all_match = False
|
|
break
|
|
|
|
|
|
if all_match:
|
|
merge_child_nodes(options,node)
|
|
|
|
else:
|
|
merging = False
|
|
|
|
# recur
|
|
for child in node.next.values():
|
|
merge_nodes(options,child)
|
|
|
|
def optimize_graph(options, node):
|
|
"""return an optimized graph. Merge compatible nodes."""
|
|
if vgraph_res():
|
|
print_resource_usage('optimize-graph.0')
|
|
merge_nodes(options,node)
|
|
if vgraph_res():
|
|
print_resource_usage('optimize-graph.1')
|
|
renumber_nodes(options,node)
|
|
if vgraph_res():
|
|
print_resource_usage('optimize-graph.2')
|
|
|
|
|
|
def epsilon_label_graph(options, node):
|
|
node.otherwise_ok = True
|
|
# recur
|
|
for child in node.next.values():
|
|
epsilon_label_graph(options,child)
|
|
|
|
############################################################################
|
|
## Packers and extractors
|
|
############################################################################
|
|
# $$ bit_group_info_t
|
|
class bit_group_info_t(object):
|
|
"""Tell us where physical bits are symbolically. Each bit_group_info_t has:
|
|
|
|
a bit name
|
|
a bit instance - the i'th copy of the named bit
|
|
|
|
a length - number of bits in this group. So this group is bit i
|
|
though bit i+length-1.
|
|
|
|
a position - not counting NONTERMINALS or OPERAND DECIDERS.
|
|
|
|
a nonterminal adder - a string describing all previous
|
|
nonterminals encountered)
|
|
|
|
a nonterminal instance - counting any and all kinds of
|
|
nonterminals in this pattern
|
|
"""
|
|
def __init__(self,
|
|
bit_name,
|
|
instance,
|
|
position_count,
|
|
nonterminal_adder,
|
|
nonterminal_instance=0):
|
|
self.bit_name = bit_name
|
|
# number of the first bit of this run
|
|
self.bit_instance = instance
|
|
# length of this run of bits
|
|
self.length = 1
|
|
self.position_count = position_count
|
|
self.position_nonterminal_adders = nonterminal_adder
|
|
|
|
# for nonterminals, the nonterminal_instance says the numeric id
|
|
# of this sub-nonterminal in the current nonterminal. If there
|
|
# are 4 sub-nonterminals in a nonterminal, they are numbered 0
|
|
# to 3. This index is used to index in to the nonterminal storage
|
|
# associated with the current nonterminal.
|
|
self.nonterminal_instance = 0
|
|
|
|
def emit(self):
|
|
"return a string"
|
|
lst = [self.bit_name ]
|
|
if self.bit_instance != 0:
|
|
lst.append('instnc:'+str(self.bit_instance))
|
|
lst.append( 'len:'+str(self.length) )
|
|
lst.append( 'pos:'+str(self.position_count) )
|
|
if self.position_nonterminal_adders != '':
|
|
lst.append('ntadders:'+self.position_nonterminal_adders)
|
|
s = '/'.join(lst)
|
|
return s
|
|
|
|
def code_gen_extract_sub_runs_old(sub_runs, vname, start_clean = True):
|
|
"""Write code that assigns bits to vname based on the sub runs. If
|
|
start_clean is false, we OR in our first stuff. Otherwise we do an
|
|
assignment for the very first bits extracted."""
|
|
|
|
# position is the position of the start of the bit run, treated as
|
|
# a string, so shifts must be adjusted for the width of the run.
|
|
|
|
eol = ';\n'
|
|
nl = '\n'
|
|
if start_clean:
|
|
first = True
|
|
else:
|
|
first = False
|
|
s = ''
|
|
|
|
for (bit,count,position_str, nonterminal_addr) in sub_runs:
|
|
print("PROCESSING SUBRUN (%s, %d, %s, %s)" % (
|
|
bit, count ,position_str, nonterminal_addr))
|
|
# control whether or not we do an assignment or and |= in to our
|
|
# dest var c.
|
|
if first:
|
|
bar = ''
|
|
first = False
|
|
else:
|
|
bar = '|'
|
|
# must shift last "c" by the amount we are or-ing in on this iteration
|
|
t += "%s=%s<<%s%s" % (vname, vname, str(count), eol)
|
|
s += t
|
|
print("ADDING SHIFT OF PREV STUFF: %s" % t)
|
|
|
|
sindex = str(position_str)
|
|
if nonterminal_addr != '':
|
|
sindex += nonterminal_addr
|
|
sindex += '+xed_decoded_inst_nonterminal_bitpos_start(xds)'
|
|
s += "%s %s=xed_decoded_inst_read_any_bits(xds,%s,%s)%s" % (
|
|
vname, bar, sindex, str(count), eol )
|
|
return s
|
|
|
|
|
|
def print_bit_groups(bit_groups, s=''):
|
|
q = "BITGRP:"
|
|
for b in bit_groups:
|
|
q = q + b.emit() + ' '
|
|
msge(s + " " + q)
|
|
|
|
############################################################################
|
|
|
|
def emit_function_headers(fp, fo_dict):
|
|
"""For each function in the fo_dict dictionary, emit the function
|
|
prototype to the fp file emitter object."""
|
|
for fname in list(fo_dict.keys()):
|
|
fo = fo_dict[fname]
|
|
fp.write(fo.emit_header())
|
|
|
|
############################################################################
|
|
def mark_operands_internal(agi, parser_output):
|
|
"""Go through all the operands in the parser and mark each
|
|
internal or not. They have already been expanded and cleaned
|
|
up."""
|
|
|
|
for ii in parser_output.instructions:
|
|
for op in ii.operands: # opnds.operand_info_t list
|
|
ip = agi.operand_storage.get_operand(op.name).internal_or_public
|
|
if ip == "INTERNAL":
|
|
op.internal = True
|
|
|
|
|
|
def rewrite_state_operands(agi, state_bits, parser_output):
|
|
"""For each operand in the parser output, make sure we denote state
|
|
modifcations as operands and not flags"""
|
|
for pi in parser_output.instructions:
|
|
expand_operands(agi, pi, state_bits)
|
|
|
|
def expand_operands(agi, pi, state_bits):
|
|
"""make opnds.operand_info_t's for any un-expanded operands based on the
|
|
strings stored in the state_bits."""
|
|
new_list = []
|
|
for x in pi.operands: # opnds.operand_info_t list
|
|
found = None
|
|
if x.name in state_bits:
|
|
found = x.name
|
|
else:
|
|
lwr = x.name.lower()
|
|
if lwr in state_bits:
|
|
found = lwr
|
|
|
|
# the state name we found might expand in to more than one operand.
|
|
if found:
|
|
for v in state_bits[found].list_of_str:
|
|
if vmacro():
|
|
msge("Expanding %s to %s" % (found,v))
|
|
eqp = equals_pattern.match(v)
|
|
if eqp:
|
|
new_operand = mk_opnd(agi, v, default_vis='SUPP')
|
|
if new_operand:
|
|
new_operand.set_suppressed()
|
|
new_list.append(new_operand)
|
|
else:
|
|
die("Could not find equals sign in state macro definition of " +
|
|
x.name)
|
|
elif x.type == 'flag':
|
|
die("THIS SHOULD NOT HAPPEN - FLAG: %s" % (x.name))
|
|
else:
|
|
new_list.append(x)
|
|
pi.operands = new_list
|
|
|
|
|
|
|
|
def expand_hierarchical_records(ii):
|
|
"""Return a list of new records splitting the extra_ipatterns and
|
|
extra_operands in to new stuff"""
|
|
new_lines = []
|
|
|
|
# FIXME: perf: 2007-08-05 mjc could skip this expansion when not
|
|
# needed and save the copying.
|
|
|
|
extra_operands = ii.extra_operands
|
|
extra_ipatterns = ii.extra_ipatterns
|
|
extra_iforms_input = ii.extra_iforms_input
|
|
ii.extra_operands = None
|
|
ii.extra_ipatterns = None
|
|
ii.extra_iforms_input = None
|
|
|
|
# start with the first instruction, then expand the "extra" ones
|
|
new_lines.append(ii)
|
|
|
|
if len(extra_ipatterns) != len(extra_operands) or \
|
|
len(extra_ipatterns) != len(extra_iforms_input):
|
|
die("Missing some patterns, operands or iforms for " + ii.iclass)
|
|
|
|
for (ipattern, operands, iform) in zip(extra_ipatterns,
|
|
extra_operands,
|
|
extra_iforms_input):
|
|
new_rec = copy.deepcopy(ii)
|
|
new_rec.new_inum()
|
|
new_rec.extra_operands = None
|
|
new_rec.extra_ipatterns = None
|
|
new_rec.extra_iforms_input = None
|
|
new_rec.ipattern_input = ipattern
|
|
new_rec.operands_input = operands
|
|
new_rec.iform_input = iform
|
|
#msge("ISET2: %s -- %s" % (iform, str(operands)))
|
|
new_lines.append(new_rec)
|
|
|
|
del extra_ipatterns
|
|
del extra_operands
|
|
return new_lines
|
|
|
|
|
|
|
|
# $$ generator_common_t
|
|
class generator_common_t(object):
|
|
"""This is stuff that is common to every geneator and the
|
|
agi. Basically all the globals that are needed by most generator
|
|
specific processing."""
|
|
|
|
def __init__(self):
|
|
self.options = None
|
|
self.state_bits = None # dictionary of state_info_t's
|
|
self.state_space = None # dictionary of all values of each state
|
|
# restriction (operand_decider)
|
|
|
|
self.enc_file = None
|
|
self.inst_file = None
|
|
self.operand_storage_hdr_file = None
|
|
self.operand_storage_src_file = None
|
|
|
|
self.header_file_names = []
|
|
self.source_file_names = []
|
|
self.file_pointers = []
|
|
|
|
self.inst_table_file_names = []
|
|
|
|
|
|
def open_file(self,fn, arg_shell_file=False, start=True):
|
|
'open and record the file pointers'
|
|
if True: #MJC2006-10-10
|
|
fp = xed_file_emitter_t(self.options.xeddir,
|
|
self.options.gendir,
|
|
fn,
|
|
shell_file=arg_shell_file)
|
|
if is_header(fn):
|
|
self.header_file_names.append(fp.full_file_name)
|
|
else:
|
|
self.source_file_names.append(fp.full_file_name)
|
|
|
|
if start:
|
|
fp.start()
|
|
else:
|
|
fp = base_open_file(fn,'w')
|
|
self.file_pointers.append(fp)
|
|
return fp
|
|
|
|
def build_fn(self,tail,header=False):
|
|
'build and record the file names'
|
|
if True: # MJC2006-10-10
|
|
fn = tail
|
|
else:
|
|
fn = os.path.join(self.options.gendir,tail)
|
|
if header:
|
|
self.header_file_names.append(fn)
|
|
else:
|
|
self.source_file_names.append(fn)
|
|
return fn
|
|
|
|
def open_all_files(self):
|
|
"Open the major output files"
|
|
msge("Opening output files")
|
|
|
|
header = True
|
|
|
|
|
|
self.inst_file = self.open_file(self.build_fn(
|
|
self.options.inst_init_file))
|
|
|
|
def open_new_inst_table_file(self):
|
|
i = len(self.inst_table_file_names)
|
|
base_fn = 'xed-inst-table-init-'
|
|
fn = self.build_fn(base_fn + str(i) + ".c")
|
|
self.inst_table_file_names.append(fn)
|
|
fp = self.open_file(fn)
|
|
return fp
|
|
|
|
|
|
def close_output_files(self):
|
|
"Close the major output files"
|
|
for f in self.file_pointers:
|
|
f.close()
|
|
|
|
# $$ generator_info_t
|
|
class generator_info_t(generator_common_t):
|
|
"""All the information that we collect and generate"""
|
|
def __init__(self, common):
|
|
super(generator_info_t,self).__init__()
|
|
self.common = common
|
|
|
|
if self.common.options == None:
|
|
die("Bad init")
|
|
#old style generator_common_t.__init__(self,generator_common)
|
|
self.parser_output = None # class parser_t
|
|
self.graph = None
|
|
# unique list of iclasses
|
|
self.iclasses = {}
|
|
|
|
# list of tuples of (nonterminal names, max count of how many
|
|
# there are of this one per instruction)
|
|
self.nonterminals = []
|
|
|
|
# list of opnds.operand_info_t's
|
|
self.operands = None
|
|
|
|
self.storage_class = None
|
|
|
|
#For thing that are directly translateable in to tables, we
|
|
#generate a table here.
|
|
self.luf_arrays = []
|
|
self.marshalling_function = None
|
|
|
|
def nonterminal_name(self):
|
|
"""The name of this subtree"""
|
|
s = self.parser_output.nonterminal_name
|
|
return nonterminal_parens_pattern.sub('', s)
|
|
|
|
def build_unique_iclass_list(self):
|
|
"build a unique list of iclasses"
|
|
self.iclasses = {}
|
|
for ii in self.parser_output.instructions:
|
|
if field_check(ii,'iclass'):
|
|
if ii.iclass not in self.iclasses:
|
|
self.iclasses[ii.iclass] = True
|
|
|
|
def cmp_tuple_first(a,b):
|
|
(a1,a2)=a
|
|
(b1,b2)=b
|
|
if a1==b1:
|
|
return 0
|
|
if a1>b1:
|
|
return 1
|
|
return -1
|
|
|
|
|
|
# $$ all_generator_info_t
|
|
class all_generator_info_t(object):
|
|
"""List of generators, each with its own graph"""
|
|
def __init__(self,options):
|
|
#common has mostly input and output files and names
|
|
self.common = generator_common_t()
|
|
self.common.options = options
|
|
self.common.open_all_files()
|
|
|
|
self.generator_list = []
|
|
self.generator_dict = {} # access by NT name
|
|
self.nonterminal_dict = nonterminal_dict_t()
|
|
|
|
self.src_files=[]
|
|
self.hdr_files=[]
|
|
|
|
# enum lists
|
|
self.operand_types = {} # typename -> True
|
|
self.operand_widths = {} # width -> True # oc2
|
|
self.operand_names = {} # name -> Type
|
|
self.iclasses = []
|
|
self.categories = []
|
|
self.extensions = []
|
|
self.attributes = []
|
|
|
|
# this is the iclasses in the order of the enumeration for us in
|
|
# initializing other structures.
|
|
self.iclasses_enum_order = None
|
|
|
|
# function_object_ts
|
|
self.itable_init_functions = table_init_object_t('xed-init-inst-table-',
|
|
'xed_init_inst_table_')
|
|
self.encode_init_function_objects = []
|
|
|
|
# dictionaries of code snippets that map to function names
|
|
self.extractors = {}
|
|
self.packers = {}
|
|
|
|
self.operand_storage = None # operand_storage_t
|
|
|
|
|
|
# function_object_t
|
|
self.overall_lookup_init = None
|
|
|
|
# functions called during decode traverals to capture required operands.
|
|
self.all_node_capture_functions = []
|
|
|
|
# data for instruction table
|
|
self.inst_fp = None
|
|
|
|
# list of (index, initializer) tuples for all the entire decode graph
|
|
self.all_decode_graph_nodes=[]
|
|
|
|
self.data_table_file=None
|
|
self.operand_sequence_file=None
|
|
|
|
# dict "iclass:extension" -> ( iclass,extension,
|
|
# category, iform_enum, properties-list)
|
|
self.iform_info = {}
|
|
|
|
self.attributes_dict = {}
|
|
self.attr_next_pos = 0
|
|
self.attributes_ordered = None
|
|
self.sorted_attributes_dict = {}
|
|
# a dict of all the enum names to their values.
|
|
# passed to operand storage in order to calculate
|
|
# the number of required bits
|
|
self.all_enums = {}
|
|
|
|
# these are xed_file_emitter_t objects
|
|
self.flag_simple_file = self.common.open_file("xed-flags-simple.c", start=False)
|
|
self.flag_complex_file = self.common.open_file("xed-flags-complex.c", start=False)
|
|
self.flag_action_file = self.common.open_file("xed-flags-actions.c", start=False)
|
|
self.flag_simple_file.add_header('xed-flags.h')
|
|
self.flag_complex_file.add_header('xed-flags.h')
|
|
self.flag_complex_file.add_header('xed-flags-private.h')
|
|
self.flag_action_file.add_header('xed-flags.h')
|
|
|
|
self.flag_simple_file.start()
|
|
self.flag_complex_file.start()
|
|
self.flag_action_file.start()
|
|
|
|
self.emit_flag_simple_decl()
|
|
self.emit_flag_complex_decl()
|
|
self.emit_flag_action_decl()
|
|
|
|
def close_flags_files(self):
|
|
self.emit_close_array(self.flag_simple_file)
|
|
self.emit_close_array(self.flag_complex_file)
|
|
self.emit_close_array(self.flag_action_file)
|
|
|
|
def emit_flag_simple_decl(self):
|
|
self.flag_simple_file.add_code("const xed_simple_flag_t xed_flags_simple_table[] = {")
|
|
self.flag_simple_file.add_code("/* 0 */ {0,0,0,{0},{0},{0},0}, /* invalid */")
|
|
|
|
def emit_flag_action_decl(self):
|
|
self.flag_action_file.add_code("const xed_flag_action_t xed_flag_action_table[] = {")
|
|
|
|
def emit_flag_complex_decl(self):
|
|
self.flag_complex_file.add_code("const xed_complex_flag_t xed_flags_complex_table[] = {")
|
|
self.flag_complex_file.add_code("/* 0 */ {0,0,{0,0,0,0,0},}, /* invalid */")
|
|
|
|
def emit_close_array(self,f):
|
|
f.add_code_eol("}")
|
|
|
|
|
|
def open_operand_data_file(self):
|
|
self.data_table_file=self.open_file('xed-init-operand-data.c',
|
|
start=False)
|
|
self.data_table_file.add_header('xed-inst-defs.h')
|
|
self.data_table_file.start()
|
|
s = 'XED_DLL_EXPORT const xed_operand_t ' + \
|
|
'xed_operand[XED_MAX_OPERAND_TABLE_NODES] = {\n'
|
|
self.data_table_file.write(s)
|
|
|
|
def close_operand_data_file(self):
|
|
self.data_table_file.write('};\n')
|
|
self.data_table_file.close()
|
|
|
|
|
|
|
|
|
|
def open_operand_sequence_file(self):
|
|
self.operand_sequence_file = \
|
|
self.open_file('xed-init-operand-sequences.c',
|
|
start=False)
|
|
self.operand_sequence_file.add_header('xed-inst-defs.h')
|
|
self.operand_sequence_file.start()
|
|
s = 'XED_DLL_EXPORT const xed_uint16_t ' + \
|
|
'xed_operand_sequences[XED_MAX_OPERAND_SEQUENCES] = {\n'
|
|
self.operand_sequence_file.write(s)
|
|
|
|
def close_operand_sequence_file(self):
|
|
self.operand_sequence_file.write('};\n')
|
|
self.operand_sequence_file.close()
|
|
|
|
|
|
def add_file_name(self,fn,header=False):
|
|
if type(fn) in [bytes,str]:
|
|
fns = [fn]
|
|
elif type(fn) == list:
|
|
fns = fn
|
|
else:
|
|
die("Need string or list")
|
|
|
|
for f in fns:
|
|
if header:
|
|
self.hdr_files.append(f)
|
|
else:
|
|
self.src_files.append(f)
|
|
|
|
def dump_generated_files(self):
|
|
output_file_list = mbuild.join(self.common.options.gendir,
|
|
"DECGEN-OUTPUT-FILES.txt")
|
|
f = base_open_file(output_file_list,"w")
|
|
for fn in self.hdr_files + self.src_files:
|
|
f.write(fn+"\n")
|
|
f.close()
|
|
|
|
def mk_fn(self,fn):
|
|
if True: #MJC2006-10-10
|
|
return fn
|
|
else:
|
|
return self.real_mk_fn(fn)
|
|
|
|
def real_mk_fn(self,fn):
|
|
return os.path.join(self.common.options.gendir,fn)
|
|
|
|
def close_output_files(self):
|
|
"Close the major output files"
|
|
self.common.close_output_files()
|
|
|
|
def make_generator(self, nt_name):
|
|
g = generator_info_t(self.common)
|
|
self.generator_list.append(g)
|
|
self.generator_dict[nt_name] = g
|
|
return g
|
|
|
|
|
|
def open_file(self, fn, keeper=True, arg_shell_file=False, start=True):
|
|
'open and record the file pointers'
|
|
if True: #MJC2006-10-10
|
|
fp = xed_file_emitter_t(self.common.options.xeddir,
|
|
self.common.options.gendir,
|
|
fn,
|
|
shell_file=arg_shell_file)
|
|
if keeper:
|
|
self.add_file_name(fp.full_file_name, is_header(fn))
|
|
|
|
if start:
|
|
fp.start()
|
|
else:
|
|
fp = base_open_file(fn,'w')
|
|
return fp
|
|
|
|
|
|
|
|
def code_gen_table_sizes(self):
|
|
"""Write the file that has the declarations of the tables that we
|
|
fill in in the generator"""
|
|
fn = "xed-gen-table-defs.h"
|
|
# we do not put this in a namespace because it is included while
|
|
# in the XED namespace.
|
|
fi = xed_file_emitter_t(self.common.options.xeddir,
|
|
self.common.options.gendir,
|
|
fn,
|
|
namespace=None)
|
|
|
|
self.add_file_name(fi.full_file_name,header=True)
|
|
fi.replace_headers([]) # no headers
|
|
fi.start()
|
|
|
|
global global_final_inum
|
|
irecs = global_final_inum + 1 # 7000
|
|
|
|
global global_max_iforms_per_iclass
|
|
|
|
global operand_max
|
|
orecs = operand_max+1
|
|
|
|
fi.add_code("#define XED_ICLASS_NAME_STR_MAX %d" %
|
|
(self.max_iclass_strings))
|
|
|
|
global max_attributes
|
|
fi.add_code("#define XED_MAX_ATTRIBUTE_COUNT %d" % (max_attributes))
|
|
|
|
fi.add_code("#define XED_MAX_INST_TABLE_NODES %d" % (irecs))
|
|
|
|
global global_operand_table_id
|
|
fi.add_code("#define XED_MAX_OPERAND_TABLE_NODES %d" %
|
|
(global_operand_table_id))
|
|
|
|
global global_max_operand_sequences
|
|
fi.add_code("#define XED_MAX_OPERAND_SEQUENCES %d" %
|
|
(global_max_operand_sequences))
|
|
|
|
# flags
|
|
fi.add_code("#define XED_MAX_REQUIRED_SIMPLE_FLAGS_ENTRIES %d" %
|
|
(flag_gen.flags_info_t._flag_simple_rec))
|
|
fi.add_code("#define XED_MAX_REQUIRED_COMPLEX_FLAGS_ENTRIES %d" %
|
|
(flag_gen.flags_info_t._flag_complex_rec))
|
|
fi.add_code("#define XED_MAX_GLOBAL_FLAG_ACTIONS %d" %
|
|
(flag_gen.flags_info_t._max_flag_actions))
|
|
|
|
|
|
fi.add_code("#define XED_MAX_IFORMS_PER_ICLASS %d" %
|
|
(global_max_iforms_per_iclass))
|
|
|
|
fi.add_code("#define XED_MAX_REQUIRED_ATTRIBUTES %d" %
|
|
(len(self.attributes_dict)))
|
|
|
|
|
|
fi.add_code("#define XED_MAX_CONVERT_PATTERNS %d" %
|
|
(self.max_convert_patterns))
|
|
fi.add_code("#define XED_MAX_DECORATIONS_PER_OPERAND %d" %
|
|
(self.max_decorations_per_operand))
|
|
|
|
fi.close()
|
|
|
|
|
|
def handle_prefab_enum(self,enum_fn):
|
|
# parse the enum file and get the c and h file names
|
|
gendir = self.common.options.gendir
|
|
m=metaenum.metaenum_t(enum_fn,gendir)
|
|
m.run_enumer()
|
|
# remember the c & h file names
|
|
self.add_file_name(m.src_full_file_name)
|
|
self.add_file_name(m.hdr_full_file_name,header=True)
|
|
all_values = [ x.name for x in m.tuples ]
|
|
return all_values
|
|
|
|
|
|
|
|
|
|
def handle_prefab_enums(self):
|
|
"""Gather up all the enum.txt files in the datafiles directory"""
|
|
prefab_enum_shell_pattern = os.path.join(self.common.options.xeddir,
|
|
"datafiles/*enum.txt")
|
|
prefab_enum_files = glob.glob( prefab_enum_shell_pattern )
|
|
for fn in prefab_enum_files:
|
|
msge("PREFAB-ENUM: " + fn)
|
|
self.handle_prefab_enum( fn )
|
|
|
|
def extend_operand_names_with_input_states(self):
|
|
type ='xed_uint32_t'
|
|
for operand_decider in list(self.common.state_space.keys()):
|
|
#msge("STATESPACE: considering " + operand_decider)
|
|
if operand_decider not in self.operand_names:
|
|
self.operand_names[operand_decider] = type
|
|
|
|
|
|
|
|
def init_functions_for_table(agi, fp, function_name, init_object):
|
|
"""emit, to the file pointer fp, headers and calls to each init
|
|
function for the init_object. The function we build is named
|
|
function_name."""
|
|
print_resource_usage('init.0')
|
|
# emit prototype for each subgraph init function
|
|
for dfo in init_object.get_init_functions():
|
|
#print_resource_usage('init.1')
|
|
fp.write(dfo.emit_header())
|
|
|
|
#print_resource_usage('init.2')
|
|
# a function that calls each init function
|
|
init_fo = function_object_t(function_name,'void')
|
|
for dfo in init_object.get_init_functions():
|
|
init_fo.add_code_eol(dfo.function_name + '()')
|
|
fp.write(init_fo.emit())
|
|
fp.close()
|
|
del fp
|
|
#print_resource_usage('init.3')
|
|
|
|
############################################################################
|
|
|
|
def generator_emit_function_list(fo_list, file_emitter):
|
|
"""Emit the function_object_t-s in the fo_list list via the file_emitter"""
|
|
for fo in fo_list:
|
|
fo.emit_file_emitter(file_emitter)
|
|
|
|
def generator_emit_function_header_list(fo_list, file_emitter):
|
|
"""Emit the function headers for the function_object_t-s in the
|
|
fo_list list via the file_emitter"""
|
|
for fo in fo_list:
|
|
file_emitter.add_code(fo.emit_header())
|
|
|
|
def make_cvt_key(lst):
|
|
return ",".join(lst)
|
|
def make_cvt_values(s,n):
|
|
if s == '':
|
|
return ['INVALID']*n
|
|
t = s.split(",")
|
|
len_t = len(t)
|
|
if len_t < n:
|
|
t.extend(['INVALID']*(n-len_t))
|
|
return t
|
|
|
|
def collect_convert_decorations(agi):
|
|
"""Find all instruction operands. Each operand has 0...N where N=3
|
|
currently conversion decorations. Number each combination of
|
|
convert decorations. Assign that number to the instruction. Emit a
|
|
initialized array of convert decoration enumeration names, N-wide.
|
|
Element 0 is special: That means no convert decorations.
|
|
"""
|
|
cvt_dict = {'0':'INVALID'}
|
|
cvt_list = ['INVALID']
|
|
n = 1
|
|
for gi in agi.generator_list:
|
|
for ii in gi.parser_output.instructions:
|
|
for op in ii.operands:
|
|
if op.cvt:
|
|
key = make_cvt_key(op.cvt)
|
|
try:
|
|
op.cvt_index = cvt_dict[key]
|
|
except:
|
|
cvt_dict[key] = n
|
|
cvt_list.append(key)
|
|
op.cvt_index = n
|
|
n = n + 1
|
|
else:
|
|
op.cvt_index = 0
|
|
if n >= 256:
|
|
die("NOTIFY XED DEVELOPERS: NEED MORE BITS IN operand cvt_idx field")
|
|
msgb("NUMBER OF CONVERT PATTERNS", str(n))
|
|
agi.max_convert_patterns = n
|
|
agi.max_decorations_per_operand = 3
|
|
fn = 'xed-operand-convert-init.c'
|
|
f = agi.common.open_file(fn, start=False)
|
|
f.add_misc_header("#include \"xed-operand-convert-enum.h\"")
|
|
f.add_misc_header("#include \"xed-gen-table-defs.h\"")
|
|
f.start()
|
|
f.write("\nconst xed_operand_convert_enum_t ")
|
|
f.write("xed_operand_convert[XED_MAX_CONVERT_PATTERNS][%s] = {\n" %
|
|
('XED_MAX_DECORATIONS_PER_OPERAND'))
|
|
|
|
|
|
for i,cvt_key in enumerate(cvt_list):
|
|
cvals = make_cvt_values(cvt_key,agi.max_decorations_per_operand)
|
|
s = ("{ XED_OPERAND_CONVERT_%s, " +
|
|
"XED_OPERAND_CONVERT_%s, " +
|
|
"XED_OPERAND_CONVERT_%s }, ") % tuple(cvals)
|
|
f.write("/* %d */ %s\n" % (i,s))
|
|
f.write("\n};\n")
|
|
f.close()
|
|
|
|
|
|
|
|
############################################################################
|
|
# Generate the graph and most tables
|
|
############################################################################
|
|
|
|
|
|
def gen_everything_else(agi):
|
|
"""This is the major work function of the generator. We read the
|
|
main input files and build the decoder graph and then the decoder"""
|
|
|
|
msge("Reading state bits")
|
|
if agi.common.options.input_state != '':
|
|
#parse the xed-state-bits.txt (or something similar) file and return
|
|
#a dictionary from a token_name to an object of
|
|
#{token_name, [token_expansion]}
|
|
#for example for "no_refining_prefix REFINING=0 OSZ=0" line we will
|
|
#have an entry no_refining_prefix:
|
|
#{no_refning_prefix, [REFINING=0, OSZ=0]}
|
|
agi.common.state_bits = read_state_spec(agi.common.options.input_state)
|
|
else:
|
|
die("Could not find state bits file in options")
|
|
msge("Done reading state bits")
|
|
|
|
#for each of the requirement statements (eg EOSZ=1), found in the state
|
|
#file, save for each token (eg EOSZ) all its possible values
|
|
#(eg [0,1,2,3]), return a dictionary from token to its possible values
|
|
#eg EOSZ: [0,1,2,3]
|
|
agi.common.state_space = compute_state_space(agi.common.state_bits)
|
|
|
|
lines = []
|
|
spine = base_open_file(agi.common.options.spine,"r").readlines()
|
|
lines.extend(spine)
|
|
|
|
msge("Reading structured input")
|
|
misc = base_open_file(
|
|
agi.common.options.structured_input_fn,"r").readlines()
|
|
lines.extend(misc)
|
|
|
|
msge("Reading Instructions (ISA) input")
|
|
isa_lines = base_open_file(
|
|
agi.common.options.isa_input_file,"r").readlines()
|
|
lines.extend(isa_lines)
|
|
del isa_lines
|
|
|
|
lines = process_continuations(lines)
|
|
|
|
# Open structured output file
|
|
if agi.common.options.structured_output_fn.startswith(os.path.sep):
|
|
fn = agi.common.options.structured_output_fn
|
|
else:
|
|
fn = os.path.join(agi.common.options.gendir,
|
|
agi.common.options.structured_output_fn)
|
|
print_structured_output = False
|
|
if print_structured_output:
|
|
sout = open(fn,"w")
|
|
print_resource_usage('everything.0')
|
|
|
|
# read all the input
|
|
while len(lines) != 0:
|
|
msge("=============================================")
|
|
msge("Creating a generator " + str(len(agi.generator_list)))
|
|
msge("=============================================")
|
|
print_resource_usage('everything.1')
|
|
msge("ALines (lines before reading input) = " + str(len(lines)))
|
|
lines = read_input(agi, lines)
|
|
msge("BLines (lines remaining after reading input) = " + str(len(lines)))
|
|
|
|
#after this we will have all deleted and udeleted instructions
|
|
#removed for all parsers, that have instructions.
|
|
#Also all instructions with old versions will be dropped.
|
|
remove_instructions(agi)
|
|
|
|
# first pass on the input, build the graph, collect information
|
|
for gi in agi.generator_list:
|
|
# if anything has flags, then add a flags register
|
|
add_flags_register_operand_all(agi,gi.parser_output)
|
|
|
|
if agi.common.state_bits == None:
|
|
die("Bad agi state bits")
|
|
|
|
if gi.common.state_bits == None:
|
|
die("Bad state bits")
|
|
|
|
rewrite_state_operands(agi, gi.common.state_bits, gi.parser_output)
|
|
mark_operands_internal(agi, gi.parser_output)
|
|
if print_structured_output:
|
|
gi.parser_output.print_structured_output(sout)
|
|
###############################################
|
|
# BUILD THE GRAPH BY RECURSIVE PARTITIONING
|
|
###############################################
|
|
gi.graph = build_graph(agi.common,
|
|
gi.parser_output,
|
|
agi.operand_storage.get_operands())
|
|
|
|
if not gi.parser_output.is_lookup_function():
|
|
optimize_graph(agi.common.options, gi.graph)
|
|
nt_name = gi.graph.token
|
|
#msge("GRAPHROOT: " + nt_name)
|
|
agi.nonterminal_dict.add_graph_node(nt_name, gi.graph.id)
|
|
|
|
# For epsilon nodes, where errors are allowed, we label all
|
|
# nodes in the subgraph with "otherwise_ok".
|
|
if gi.parser_output.otherwise_ok:
|
|
epsilon_label_graph(agi.common.options, gi.graph)
|
|
|
|
# do not collect operands from nonterminals that are lookup functions:
|
|
if not gi.parser_output.is_lookup_function():
|
|
#msge("Collecting graph enum info")
|
|
collect_graph_enum_info(agi,gi.graph)
|
|
d = {}
|
|
d =collect_tree_depth(gi.graph, d)
|
|
#msge("DEPTHS: "+ str(d))
|
|
if agi.common.options.print_graph:
|
|
print_graph(agi.common.options,gi.graph)
|
|
|
|
print_resource_usage('everything.2')
|
|
if print_structured_output:
|
|
sout.close()
|
|
del sout
|
|
|
|
print_resource_usage('everything.3')
|
|
# Renumber the itable nodes so that they are sequential, skipping
|
|
# over the lookup function itable entries.
|
|
relabel_itable(agi)
|
|
|
|
print_resource_usage('everything.3a')
|
|
|
|
# some stuff needs to be created first so that the pass2 stuff can
|
|
# refer to it.
|
|
for generator in agi.generator_list:
|
|
print_resource_usage('everything.4')
|
|
rewrite_default_operand_visibilities(generator,
|
|
agi.operand_storage.get_operands())
|
|
|
|
compute_iforms(generator.common.options,
|
|
generator,
|
|
agi.operand_storage.get_operands())
|
|
|
|
collect_convert_decorations(agi)
|
|
|
|
# We emit the iform enum here so that we can use the ordering for
|
|
# initializing other structures.
|
|
emit_iclass_enum_info(agi)
|
|
emit_iclass_rep_ops(agi)
|
|
|
|
collect_and_emit_iforms(agi,agi.common.options)
|
|
collect_iclass_strings(agi)
|
|
collect_instruction_types(agi, agi.iform_info)
|
|
agi.isa_sets = collect_isa_sets(agi)
|
|
|
|
# idata.txt file write
|
|
write_instruction_data(agi.common.options.gendir,agi.iform_info)
|
|
write_quick_iform_map(agi,agi.common.options.gendir,agi.iform_info)
|
|
|
|
print_resource_usage('everything.4b')
|
|
# mark bit positions in each "instruction"
|
|
decorate_operands(agi.common.options,agi)
|
|
print_resource_usage('everything.4c')
|
|
|
|
decorate_instructions_with_exception_types(agi)
|
|
|
|
agi.inst_fp = agi.open_file('xed-init-inst-table-data.c', start=False)
|
|
agi.inst_fp.add_header('xed-inst-defs.h')
|
|
agi.inst_fp.start()
|
|
agi.inst_fp.write('const xed_inst_t ' +
|
|
'xed_inst_table[XED_MAX_INST_TABLE_NODES] = {\n')
|
|
|
|
agi.open_operand_data_file()
|
|
agi.open_operand_sequence_file()
|
|
|
|
cg_args = code_gen_dec_args_t()
|
|
|
|
agi.encode_init_function_objects.append(
|
|
function_object_t('xed_encode_init', 'void'))
|
|
print_resource_usage('everything.5')
|
|
|
|
find_common_operand_sequences(agi)
|
|
|
|
for generator in agi.generator_list:
|
|
print_resource_usage('everything.6')
|
|
if generator.parser_output.is_lookup_function():
|
|
pass
|
|
else:
|
|
cg_args.gi = generator
|
|
cg_args.options = generator.common.options
|
|
cg_args.node = generator.graph
|
|
cg_args.nonterminal_dict = agi.nonterminal_dict
|
|
cg_args.state_bits = agi.common.state_bits
|
|
cg_args.itable_init_functions = agi.itable_init_functions
|
|
|
|
cg_args.encode_init_function_object = \
|
|
agi.encode_init_function_objects[0]
|
|
cg_args.operand_storage_dict = agi.operand_storage.get_operands()
|
|
|
|
# generate the itable
|
|
code_gen_instruction_table(agi,
|
|
cg_args.gi,
|
|
cg_args.itable_init_functions,
|
|
cg_args.nonterminal_dict,
|
|
cg_args.operand_storage_dict)
|
|
|
|
print_resource_usage('everything.7')
|
|
|
|
global max_operand_count
|
|
msgb("MAX OPERAND COUNT {}".format(max_operand_count))
|
|
|
|
code_gen_unique_operands(agi)
|
|
code_gen_operand_sequences(agi)
|
|
agi.close_operand_data_file()
|
|
agi.close_operand_sequence_file()
|
|
agi.inst_fp.write('};\n')
|
|
agi.inst_fp.close()
|
|
|
|
# finish emitting the last function for the itable and the decode graph
|
|
agi.itable_init_functions.finish_fp()
|
|
|
|
print_resource_usage('everything.10')
|
|
|
|
# THIS NEXT FUNCTION IS THE BIGGEST TIME HOG
|
|
init_functions_for_table(agi,
|
|
agi.common.inst_file,
|
|
'xed_init_inst_table',
|
|
agi.itable_init_functions)
|
|
|
|
print_resource_usage('everything.12')
|
|
# some states are not assigned to in the graph and we must reserve
|
|
# storage for them anyway. MODE is one example.
|
|
agi.extend_operand_names_with_input_states()
|
|
|
|
emit_enum_info(agi)
|
|
agi.handle_prefab_enums()
|
|
|
|
agi.add_file_name(agi.common.source_file_names)
|
|
agi.add_file_name(agi.common.header_file_names, header=True)
|
|
|
|
write_attributes_table(agi,agi.common.options.gendir)
|
|
|
|
# defines for emitted tables
|
|
agi.code_gen_table_sizes()
|
|
agi.close_flags_files()
|
|
print_resource_usage('everything.16')
|
|
|
|
call_chipmodel(agi)
|
|
call_ctables(agi)
|
|
emit_operand_storage(agi)
|
|
|
|
################################################
|
|
def emit_operand_storage(agi):
|
|
agi.operand_storage.emit(agi)
|
|
|
|
def call_ctables(agi):
|
|
"""Conversion tables for operands"""
|
|
lines = open(agi.common.options.ctables_input_fn,'r').readlines()
|
|
srcs = ctables.work(lines,
|
|
xeddir=agi.common.options.xeddir,
|
|
gendir=agi.common.options.gendir)
|
|
|
|
def call_chipmodel(agi):
|
|
args = chipmodel.args_t()
|
|
args.input_file_name = agi.common.options.chip_models_input_fn
|
|
args.xeddir = agi.common.options.xeddir
|
|
args.gendir = agi.common.options.gendir
|
|
# isaset is a list of the ISA_SETs mentioned in the chip hierarchy.
|
|
# we need to check that all of those are used/mentioned by some chip.
|
|
files_created,chips,isaset = chipmodel.work(args)
|
|
agi.all_enums['xed_chip_enum_t'] = chips
|
|
agi.all_enums['xed_isa_set_enum_t'] = isaset
|
|
print("Created files: %s" % (" ".join(files_created)))
|
|
for f in files_created:
|
|
agi.add_file_name(f,is_header(f))
|
|
|
|
genutil.msgb("FROM CHIP MODEL", isaset)
|
|
genutil.msgb("FROM INSTRUCTIONS ", agi.isa_sets)
|
|
for v in isaset: # stuff from the chip hierarchy model
|
|
v = v.upper()
|
|
if v in ['INVALID']:
|
|
continue
|
|
if v not in agi.isa_sets: # stuff from the instructions
|
|
genutil.warn("isa_set referenced by chip model hierarchy," +
|
|
"but not used by any instructions: {}".format(v))
|
|
|
|
################################################
|
|
def read_cpuid_mappings(fn):
|
|
lines = open(fn,'r').readlines()
|
|
lines = map(no_comments, lines)
|
|
lines = list(filter(blank_line, lines))
|
|
d = {} # isa-set to list of cpuid records
|
|
for line in lines:
|
|
wrds = line.split(':')
|
|
isa_set = wrds[0].strip()
|
|
#cpuid_bits = re.sub('[.]','_',wrds[1].upper()).split()
|
|
cpuid_bits = wrds[1].upper().split()
|
|
if isa_set in d:
|
|
die("Duplicate cpuid definition for isa set. isa-set={} old ={} new={}".format(
|
|
isa_set, d[isa_set], cpuid_bits))
|
|
d[isa_set] = cpuid_bits
|
|
return d
|
|
|
|
def make_cpuid_mappings(agi,mappings):
|
|
|
|
# 'mappings' is a dict of isa_set -> list of cpuid_bit_names
|
|
|
|
# collect all unique list of cpuid bit names
|
|
cpuid_bits = {}
|
|
for vlist in mappings.values():
|
|
for bit in vlist:
|
|
if bit == 'N/A':
|
|
data = bitname = 'INVALID'
|
|
else:
|
|
try:
|
|
bitname,orgdata = bit.split('.',1)
|
|
data = re.sub('[.]','_',orgdata)
|
|
except:
|
|
die("splitting problem with {}".format(bit))
|
|
if bitname in cpuid_bits:
|
|
if cpuid_bits[bitname] != data:
|
|
die("Mismatch on cpuid bit specification for bit {}: {} vs {}".format(
|
|
bitname, cpuid_bits[bitname], data))
|
|
cpuid_bits[bitname]=data
|
|
|
|
|
|
cpuid_bit_string_names = sorted(cpuid_bits.keys())
|
|
|
|
# move INVALID to 0th element:
|
|
p = cpuid_bit_string_names.index('INVALID')
|
|
del cpuid_bit_string_names[p]
|
|
cpuid_bit_string_names = ['INVALID'] + cpuid_bit_string_names
|
|
|
|
# emit enum for cpuid bit names
|
|
cpuid_bit_enum = enum_txt_writer.enum_info_t(cpuid_bit_string_names,
|
|
agi.common.options.xeddir,
|
|
agi.common.options.gendir,
|
|
'xed-cpuid-bit',
|
|
'xed_cpuid_bit_enum_t',
|
|
'XED_CPUID_BIT_',
|
|
cplusplus=False)
|
|
cpuid_bit_enum.print_enum()
|
|
cpuid_bit_enum.run_enumer()
|
|
agi.add_file_name(cpuid_bit_enum.src_full_file_name)
|
|
agi.add_file_name(cpuid_bit_enum.hdr_full_file_name,header=True)
|
|
|
|
fp = agi.open_file('xed-cpuid-tables.c')
|
|
|
|
fp.add_code('const xed_cpuid_rec_t xed_cpuid_info[] = {')
|
|
# emit initialized structure mapping cpuid enum values to descriptive structures
|
|
for bitname in cpuid_bit_string_names:
|
|
cpuid_bit_data = cpuid_bits[bitname]
|
|
if bitname == 'INVALID':
|
|
leaf = subleaf = bit = 0
|
|
reg = 'INVALID'
|
|
else:
|
|
(leaf,subleaf,reg,bit) = cpuid_bit_data.split('_')
|
|
|
|
s = "/* {:18s} */ {{ 0x{}, {}, {}, XED_REG_{} }},".format(
|
|
bitname, leaf,subleaf, bit, reg)
|
|
fp.add_code(s)
|
|
fp.add_code('};')
|
|
|
|
# check that each isa set in the cpuid files has a corresponding XED_ISA_SET_ value
|
|
fail = False
|
|
for cisa in list(mappings.keys()):
|
|
t = re.sub('XED_ISA_SET_','',cisa)
|
|
if t not in agi.all_enums['xed_isa_set_enum_t']:
|
|
fail = True
|
|
genutil.warn("bad isa_set referenced cpuid file: {}".format(cisa))
|
|
if fail:
|
|
die("Found bad isa_sets in cpuid input files.")
|
|
|
|
|
|
|
|
|
|
# emit initialized structure of isa-set mapping to array of cpuid bit string enum.
|
|
n = 4
|
|
fp.add_code('const xed_cpuid_bit_enum_t xed_isa_set_to_cpuid_mapping[][XED_MAX_CPUID_BITS_PER_ISA_SET] = {')
|
|
|
|
for isaset in agi.all_enums['xed_isa_set_enum_t']:
|
|
print("ISASET: ", isaset)
|
|
x = 'XED_ISA_SET_' + isaset
|
|
raw = n*['XED_CPUID_BIT_INVALID']
|
|
if x in mappings:
|
|
for i,v in enumerate(mappings[x]):
|
|
if v == 'N/A':
|
|
bit_symbolic_name = 'INVALID'
|
|
else:
|
|
(bit_symbolic_name,leaf,subleaf,reg,bit) = v.split('.')
|
|
|
|
if i >= n:
|
|
die("Make XED_MAX_CPUID_BITS_PER_ISA_SET bigger")
|
|
raw[i] = 'XED_CPUID_BIT_' + bit_symbolic_name
|
|
bits = ", ".join(raw)
|
|
s = '/* {} */ {{ {} }} ,'.format(isaset, bits)
|
|
fp.add_code(s)
|
|
fp.add_code('};')
|
|
fp.close()
|
|
|
|
def gen_cpuid_map(agi):
|
|
fn = agi.common.options.cpuid_input_fn
|
|
if fn:
|
|
if os.path.exists(fn):
|
|
mappings = read_cpuid_mappings(fn)
|
|
make_cpuid_mappings(agi, mappings)
|
|
return
|
|
die("Could not read cpuid input file: {}".format(str(fn)))
|
|
|
|
################################################
|
|
|
|
def gen_ild(agi):
|
|
#do the ild things
|
|
if agi.common.options.ild_scanners_input_fn != '':
|
|
agi.common.ild_scanners_dict = \
|
|
read_ild_scanners_def(agi.common.options.ild_scanners_input_fn)
|
|
else:
|
|
die("Could not find scanners file in options")
|
|
#getters are optional
|
|
if agi.common.options.ild_getters_input_fn != '':
|
|
agi.common.ild_getters_dict = \
|
|
read_ild_getters_def(agi.common.options.ild_getters_input_fn)
|
|
else:
|
|
agi.common.ild_getters_dict = None
|
|
|
|
ild.work(agi)
|
|
|
|
|
|
def emit_regs_enum(options, regs_list):
|
|
|
|
#FIXME: sort the register names by their type. Collect all the
|
|
#types-and-widths, sort them by their ordinals. Special handling
|
|
#for the AH/BH/CH/DH registers is required.
|
|
|
|
enumvals = refine_regs.rearrange_regs(regs_list)
|
|
|
|
reg_enum = enum_txt_writer.enum_info_t(enumvals,
|
|
options.xeddir, options.gendir,
|
|
'xed-reg', 'xed_reg_enum_t',
|
|
'XED_REG_', cplusplus=False)
|
|
reg_enum.print_enum()
|
|
reg_enum.run_enumer()
|
|
return (reg_enum.src_full_file_name,reg_enum.hdr_full_file_name)
|
|
|
|
def emit_reg_class_enum(options, regs_list):
|
|
rclasses = {}
|
|
for ri in regs_list:
|
|
if ri.type not in rclasses:
|
|
rclasses[ri.type]=True
|
|
|
|
#Add GPR8,16,32,64 as reg classes
|
|
if ri.type == 'GPR':
|
|
fine_rclass = 'GPR' + ri.width
|
|
if fine_rclass not in rclasses:
|
|
rclasses[fine_rclass]=True
|
|
|
|
del rclasses['INVALID']
|
|
just_rclass_names = list(rclasses.keys())
|
|
# FIXME: would really prefer alphanumeric sort (low priority)
|
|
just_rclass_names.sort()
|
|
|
|
just_rclass_names[0:0] = ['INVALID'] # put INVALID at the start of the list
|
|
reg_enum = enum_txt_writer.enum_info_t(just_rclass_names,
|
|
options.xeddir,
|
|
options.gendir,
|
|
'xed-reg-class',
|
|
'xed_reg_class_enum_t',
|
|
'XED_REG_CLASS_',
|
|
cplusplus=False)
|
|
reg_enum.print_enum()
|
|
reg_enum.run_enumer()
|
|
return (reg_enum.src_full_file_name,reg_enum.hdr_full_file_name)
|
|
|
|
def emit_reg_class_mappings(options, regs_list):
|
|
"""Emit code to map any reg to its regclass. Also emit code to map
|
|
GPRs to a more specific GPR regclass (GPR8,16,32,64)"""
|
|
|
|
fo = function_object_t('xed_init_reg_mappings', 'void')
|
|
for ri in regs_list:
|
|
s = 'xed_reg_class_array[XED_REG_%s]= XED_REG_CLASS_%s' % (ri.name,
|
|
ri.type)
|
|
fo.add_code_eol(s)
|
|
|
|
for ri in regs_list:
|
|
s = 'xed_largest_enclosing_register_array[XED_REG_%s]= XED_REG_%s' % (
|
|
ri.name, ri.max_enclosing_reg)
|
|
fo.add_code_eol(s)
|
|
|
|
if ri.max_enclosing_reg_32:
|
|
m32 = ri.max_enclosing_reg_32
|
|
else:
|
|
m32 = ri.max_enclosing_reg
|
|
|
|
s = 'xed_largest_enclosing_register_array_32[XED_REG_%s]= XED_REG_%s' % (
|
|
ri.name, m32)
|
|
fo.add_code_eol(s)
|
|
|
|
for ri in regs_list:
|
|
if ri.type == 'GPR':
|
|
s = 'xed_gpr_reg_class_array[XED_REG_%s]= XED_REG_CLASS_%s%s' % (
|
|
ri.name, ri.type, ri.width)
|
|
fo.add_code_eol(s)
|
|
|
|
|
|
for ri in regs_list:
|
|
if 'NA' == ri.width:
|
|
width = '0'
|
|
width64 = '0'
|
|
elif '/' in ri.width:
|
|
chunks = ri.width.split('/')
|
|
width = chunks[0]
|
|
width64 = chunks[1]
|
|
else:
|
|
width = ri.width
|
|
width64 = ri.width
|
|
|
|
s = 'xed_reg_width_bits[XED_REG_%s][0] = %s' % (ri.name, width)
|
|
fo.add_code_eol(s)
|
|
s = 'xed_reg_width_bits[XED_REG_%s][1] = %s' % (ri.name, width64)
|
|
fo.add_code_eol(s)
|
|
|
|
# write the file in our customized way
|
|
fp = xed_file_emitter_t(options.xeddir,
|
|
options.gendir,
|
|
'xed-init-reg-class.c')
|
|
fp.start()
|
|
fp.write(fo.emit())
|
|
fp.close()
|
|
return fp.full_file_name
|
|
|
|
|
|
def gen_regs(options,agi):
|
|
"""Generate the register enumeration & reg class mapping functions"""
|
|
|
|
lines = base_open_file(options.input_regs,"r","registers input").readlines()
|
|
|
|
# remove comments and blank lines
|
|
# regs_list is a list of reg_info_t's
|
|
regs_list = refine_regs.refine_regs_input(lines)
|
|
regs = [ x.name for x in regs_list]
|
|
agi.all_enums['xed_reg_enum_t'] = regs
|
|
|
|
(cfn, hfn) = emit_regs_enum(options, regs_list)
|
|
agi.add_file_name(cfn)
|
|
agi.add_file_name(hfn,header=True)
|
|
|
|
(cfn, hfn) = emit_reg_class_enum(options, regs_list)
|
|
agi.add_file_name(cfn)
|
|
agi.add_file_name(hfn,header=True)
|
|
|
|
cfn_map = emit_reg_class_mappings(options, regs_list)
|
|
agi.add_file_name(cfn_map)
|
|
|
|
agi.regs_info = regs_list
|
|
|
|
|
|
############################################################################
|
|
# $$ width_info_t
|
|
class width_info_t(object):
|
|
def __init__(self, name, dtype, widths):
|
|
""" a name and a list of widths, 8, 16,32, and 64b"""
|
|
self.name = name.upper()
|
|
self.dtype = dtype
|
|
self.widths = widths
|
|
|
|
def is_bits(val):
|
|
"""Return a number if the value is in explicit bits form:
|
|
[0-9]+bits, or None"""
|
|
length = len(val)
|
|
if length > 4:
|
|
if val[-4:] == "bits":
|
|
number_string = val[0:-4]
|
|
if completely_numeric.match(number_string):
|
|
return number_string
|
|
return None
|
|
|
|
def refine_widths_input(lines):
|
|
"""Return a list of width_info_t. Skip comments and blank lines"""
|
|
global comment_pattern
|
|
widths_list = []
|
|
for line in lines:
|
|
pline = comment_pattern.sub('',line).strip()
|
|
if pline == '':
|
|
continue
|
|
wrds = pline.split()
|
|
ntokens = len(wrds)
|
|
if ntokens == 3:
|
|
(name, dtype, all_width) = wrds
|
|
width8 = all_width
|
|
width16 = all_width
|
|
width32 = all_width
|
|
width64 = all_width
|
|
elif ntokens == 5:
|
|
width8='0'
|
|
(name, dtype, width16, width32, width64) = wrds
|
|
else:
|
|
die("Bad number of tokens on line: " + line)
|
|
|
|
# convert from bytes to bits, unless in explicit bits form "b'[0-9]+"
|
|
bit_widths = []
|
|
for val in [width8, width16, width32, width64]:
|
|
number_string = is_bits(val)
|
|
if number_string:
|
|
bit_widths.append(number_string)
|
|
else:
|
|
bit_widths.append(str(int(val)*8))
|
|
widths_list.append(width_info_t(name, dtype, bit_widths))
|
|
return widths_list
|
|
|
|
def emit_widths_enum(options, widths_list):
|
|
just_width_names = [ x.name for x in widths_list]
|
|
width_enum = enum_txt_writer.enum_info_t(just_width_names,
|
|
options.xeddir, options.gendir,
|
|
'xed-operand-width',
|
|
'xed_operand_width_enum_t',
|
|
'XED_OPERAND_WIDTH_',
|
|
cplusplus=False)
|
|
width_enum.print_enum()
|
|
width_enum.run_enumer()
|
|
return (width_enum.src_full_file_name,width_enum.hdr_full_file_name)
|
|
|
|
|
|
def emit_width_lookup(options, widths_list):
|
|
"""Emit code to map XED_OPERAND_WIDTH_* and an effective operand size to a
|
|
number of bytes. """
|
|
|
|
fo = function_object_t('xed_init_width_mappings', 'void')
|
|
for ri in widths_list:
|
|
for i,w in enumerate(ri.widths):
|
|
s = 'xed_width_bits[XED_OPERAND_WIDTH_%s][%d] = %s' % (ri.name, i, w)
|
|
fo.add_code_eol(s)
|
|
|
|
if 0: # DISABLED!!!
|
|
if int(w) % 8 == 0:
|
|
multiple = '1'
|
|
else:
|
|
multiple = '0'
|
|
s = 'xed_width_is_bytes[XED_OPERAND_WIDTH_%s][%d] = %s' % (
|
|
ri.name, i, multiple)
|
|
fo.add_code_eol(s)
|
|
|
|
# write the file in our customized way
|
|
fp = xed_file_emitter_t(options.xeddir,
|
|
options.gendir,
|
|
'xed-init-width.c')
|
|
fp.start()
|
|
fp.write(fo.emit())
|
|
fp.close()
|
|
return fp.full_file_name
|
|
|
|
|
|
def gen_element_types_base(agi):
|
|
"""Read in the information about element base types"""
|
|
fn = agi.common.options.input_element_type_base
|
|
msge("MAKING ELEMENT BASE TYPE ENUM")
|
|
all_values = agi.handle_prefab_enum(fn)
|
|
agi.all_enums['xed_operand_element_type_enum_t'] = all_values
|
|
|
|
def gen_element_types(agi):
|
|
"""Read in the information about element types"""
|
|
lines = base_open_file(agi.common.options.input_element_types,
|
|
"r","element types").readlines()
|
|
agi.xtypes_dict = opnd_types.read_operand_types(lines)
|
|
agi.xtypes = set(agi.xtypes_dict.keys())
|
|
|
|
(cfn,hfn) = opnd_types.write_enum(agi,agi.xtypes_dict)
|
|
agi.add_file_name(cfn)
|
|
agi.add_file_name(hfn,header=True)
|
|
cfn = opnd_types.write_table(agi,agi.xtypes_dict)
|
|
agi.add_file_name(cfn)
|
|
|
|
def gen_extra_widths(agi):
|
|
"""Read the extra decorations for NTs and REGs that lack width
|
|
information"""
|
|
lines = base_open_file(agi.common.options.input_extra_widths,
|
|
"r", "extra widths input").readlines()
|
|
agi.extra_widths_reg = {}
|
|
agi.extra_widths_nt = {}
|
|
agi.extra_widths_imm_const = {}
|
|
for line in lines:
|
|
pline = comment_pattern.sub('',line).strip()
|
|
if pline == '':
|
|
continue
|
|
wrds = pline.split()
|
|
ntokens = len(wrds)
|
|
if ntokens != 3:
|
|
die("Bad number of tokens on line: " + line)
|
|
(nt_or_reg, name, oc2) = wrds
|
|
if nt_or_reg == 'nt':
|
|
agi.extra_widths_nt[name] = oc2
|
|
elif nt_or_reg == 'reg':
|
|
agi.extra_widths_reg[name] = oc2
|
|
elif nt_or_reg == 'imm_const':
|
|
agi.extra_widths_imm_const[name] = oc2
|
|
else:
|
|
die("Bad NT/REG on line: " + line)
|
|
|
|
|
|
|
|
def gen_widths(options,agi):
|
|
"""Generate the oc2 operand width enumeration & width lookup function"""
|
|
|
|
lines = base_open_file(options.input_widths,"r","widths input").readlines()
|
|
|
|
# remove comments and blank lines
|
|
# widths_list is a list of width_info_t's
|
|
widths_list = refine_widths_input(lines)
|
|
|
|
(cfn, hfn) = emit_widths_enum(options, widths_list)
|
|
agi.add_file_name(cfn)
|
|
agi.add_file_name(hfn,header=True)
|
|
|
|
cfn_map = emit_width_lookup(options, widths_list)
|
|
agi.add_file_name(cfn_map)
|
|
|
|
agi.widths_list = widths_list
|
|
|
|
# sets the default data type for each width
|
|
agi.widths_dict = {}
|
|
for w in widths_list:
|
|
agi.widths_dict[w.name] = w.dtype
|
|
|
|
# compute the scalable widths
|
|
agi.scalable_widths = set()
|
|
for w in widths_list:
|
|
(w8,w16,w32,w64) = w.widths
|
|
if w16 != w32 or w16 != w64 or w32 != w64:
|
|
msge("Adding scalable width: " + w.name)
|
|
agi.scalable_widths.add(w.name)
|
|
|
|
|
|
############################################################################
|
|
def emit_pointer_name_lookup(options, widths_list):
|
|
"""Emit code to map integers representing a number of bytes accessed to a
|
|
pointer name for disassembly."""
|
|
|
|
max_width = 0
|
|
for bbytes, name, suffix in widths_list:
|
|
if int(bbytes) > max_width:
|
|
max_width = int(bbytes)+1
|
|
|
|
hfp = xed_file_emitter_t(options.xeddir,
|
|
options.gendir,
|
|
'xed-init-pointer-names.h')
|
|
hfp.start()
|
|
hfp.write("#define XED_MAX_POINTER_NAMES %d\n" % max_width)
|
|
hfp.close()
|
|
|
|
|
|
fo = function_object_t('xed_init_pointer_names', 'void')
|
|
fo.add_code_eol("memset((void*)xed_pointer_name,0," +
|
|
"sizeof(const char*)*XED_MAX_POINTER_NAMES)")
|
|
for bbytes, name, suffix in widths_list:
|
|
# add a trailing space to the name for formatting.
|
|
s = 'xed_pointer_name[%s] = \"%s \"' % (bbytes, name)
|
|
fo.add_code_eol(s)
|
|
|
|
fo.add_code_eol("memset((void*)xed_pointer_name_suffix,0,"+
|
|
"sizeof(const char*)*XED_MAX_POINTER_NAMES)")
|
|
for bbytes, name, suffix in widths_list:
|
|
# add a trailing space to the name for formatting.
|
|
s = 'xed_pointer_name_suffix[%s] = \"%s \"' % (bbytes, suffix)
|
|
fo.add_code_eol(s)
|
|
|
|
# write the file in our customized way
|
|
fp = xed_file_emitter_t(options.xeddir,
|
|
options.gendir,
|
|
'xed-init-pointer-names.c')
|
|
fp.start()
|
|
fp.write("#include \"xed-init-pointer-names.h\"\n")
|
|
fp.write("#include <string.h>\n") # for memset
|
|
fp.write("const char* xed_pointer_name[XED_MAX_POINTER_NAMES];\n")
|
|
fp.write("const char* xed_pointer_name_suffix[XED_MAX_POINTER_NAMES];\n")
|
|
fp.write(fo.emit())
|
|
fp.close()
|
|
return [fp.full_file_name, hfp.full_file_name]
|
|
|
|
def refine_pointer_names_input(lines):
|
|
"""Return a list of width_info_t. Skip comments and blank lines"""
|
|
global comment_pattern
|
|
widths_list = []
|
|
for line in lines:
|
|
pline = comment_pattern.sub('',line).strip()
|
|
if pline == '':
|
|
continue
|
|
wrds = pline.split()
|
|
ntokens = len(wrds)
|
|
if ntokens == 3:
|
|
(bbytes, name, suffix) = wrds
|
|
else:
|
|
die("Bad number of tokens on line: " + line)
|
|
widths_list.append((bbytes,name,suffix))
|
|
return widths_list
|
|
|
|
def gen_pointer_names(options,agi):
|
|
"""Generate the pointer name lookup function"""
|
|
lines = base_open_file(options.input_pointer_names,"r",
|
|
"pointer names input").readlines()
|
|
widths_list = refine_pointer_names_input(lines)
|
|
(cfn, hfn) = emit_pointer_name_lookup(options, widths_list)
|
|
agi.add_file_name(cfn)
|
|
agi.add_file_name(hfn,header=True)
|
|
|
|
|
|
def emit_exception_enum(agi):
|
|
if 'INVALID' not in agi.exception_types:
|
|
agi.exception_types.append('INVALID')
|
|
agi.exception_types = uniqueify(agi.exception_types)
|
|
agi.exception_types.sort(key=key_invalid_first)
|
|
enum = enum_txt_writer.enum_info_t( agi.exception_types,
|
|
agi.common.options.xeddir,
|
|
agi.common.options.gendir,
|
|
'xed-exception',
|
|
'xed_exception_enum_t',
|
|
'XED_EXCEPTION_',
|
|
cplusplus=False)
|
|
enum.print_enum()
|
|
enum.run_enumer()
|
|
agi.add_file_name(enum.src_full_file_name)
|
|
agi.add_file_name(enum.hdr_full_file_name,header=True)
|
|
|
|
|
|
def decorate_instructions_with_exception_types(agi):
|
|
"""Put a default exception on instructions that lack a specific
|
|
exception."""
|
|
agi.exception_types = []
|
|
for generator in agi.generator_list:
|
|
ii = generator.parser_output.instructions[0]
|
|
if not field_check(ii,'iclass'):
|
|
continue
|
|
for ii in generator.parser_output.instructions:
|
|
if field_check(ii,'exceptions') and ii.exceptions:
|
|
# clean it up a little
|
|
ii.exceptions = re.sub('-','_',ii.exceptions)
|
|
ii.exceptions = ii.exceptions.upper()
|
|
agi.exception_types.append(ii.exceptions)
|
|
else:
|
|
ii.exceptions = 'INVALID'
|
|
# writes agi.exception_types list of exceptions
|
|
emit_exception_enum(agi)
|
|
|
|
|
|
|
|
############################################################################
|
|
|
|
def emit_ctypes_enum(options, ctypes_dict):
|
|
ctypes_dict['INVALID']=True
|
|
type_names = list(ctypes_dict.keys())
|
|
type_names.sort(key=key_invalid_first)
|
|
ctypes_enum = enum_txt_writer.enum_info_t(type_names,
|
|
options.xeddir, options.gendir,
|
|
'xed-operand-ctype',
|
|
'xed_operand_ctype_enum_t',
|
|
'XED_OPERAND_CTYPE_',
|
|
cplusplus=False)
|
|
ctypes_enum.print_enum()
|
|
ctypes_enum.run_enumer()
|
|
return (ctypes_enum.src_full_file_name,ctypes_enum.hdr_full_file_name)
|
|
|
|
def emit_ctypes_mapping(options, operand_ctype_map, operand_bits_map):
|
|
"""Map operand names to ctypes and bits. Return c and h filenames"""
|
|
fn = 'xed-operand-ctype-map'
|
|
cf = xed_file_emitter_t(options.xeddir, options.gendir, fn + '.c')
|
|
hf = xed_file_emitter_t(options.xeddir, options.gendir, fn + '.h')
|
|
cf.start()
|
|
hf.start()
|
|
cf.write("#include \"%s\"\n" % (hf.file_name))
|
|
|
|
mfo = function_object_t('xed_operand_get_ctype', 'xed_operand_ctype_enum_t')
|
|
mfo.add_arg("xed_operand_enum_t opname")
|
|
mfo.add_code_eol(" xed_assert(opname <XED_OPERAND_LAST)")
|
|
mfo.add_code_eol(" return xed_operand_ctype[opname]")
|
|
|
|
lfo = function_object_t('xed_operand_decider_get_width', 'unsigned int')
|
|
lfo.add_arg("xed_operand_enum_t opname")
|
|
lfo.add_code_eol(" xed_assert(opname <XED_OPERAND_LAST)")
|
|
lfo.add_code_eol(" return xed_operand_bits[opname]")
|
|
|
|
ifo = function_object_t('xed_init_operand_ctypes', 'void')
|
|
|
|
for o,c in operand_ctype_map.items():
|
|
ifo.add_code_eol(
|
|
"xed_operand_ctype[XED_OPERAND_%s]=XED_OPERAND_CTYPE_%s" % (
|
|
o.upper(),c.upper()))
|
|
|
|
for o,c in operand_bits_map.items():
|
|
ifo.add_code_eol("xed_operand_bits[XED_OPERAND_%s]=%s" % (o.upper(), c))
|
|
|
|
cf.write("static xed_operand_ctype_enum_t"+
|
|
" xed_operand_ctype[XED_OPERAND_LAST];\n")
|
|
cf.write("static unsigned int xed_operand_bits[XED_OPERAND_LAST];\n")
|
|
cf.write(ifo.emit())
|
|
cf.write(mfo.emit())
|
|
hf.write(mfo.emit_header())
|
|
cf.write(lfo.emit())
|
|
hf.write(lfo.emit_header())
|
|
hf.close()
|
|
cf.close()
|
|
return (cf.full_file_name, hf.full_file_name)
|
|
|
|
|
|
def gen_operand_storage_fields(options,agi):
|
|
"""Read the register names and type specifiers. Build some classes, enum"""
|
|
lines = base_open_file(options.input_fields,"r",
|
|
"operand fields input").readlines()
|
|
|
|
compress_operands = agi.common.options.compress_operands
|
|
agi.operand_storage = operand_storage.operands_storage_t(lines,
|
|
compress_operands)
|
|
|
|
operand_fields = agi.operand_storage.get_operands()
|
|
ctypes = {} # ctypes -> True
|
|
for of in list(operand_fields.values()):
|
|
ctypes[of.ctype]=True
|
|
|
|
|
|
operand_ctype_map = {}
|
|
operand_bits_map = {}
|
|
for of in operand_fields.values():
|
|
operand_ctype_map[of.name] = of.ctype
|
|
operand_bits_map[of.name] = of.bitwidth
|
|
|
|
|
|
#msge("OPERAND STORAGE: %s" %(agi.operand_storage.operand_field.keys()))
|
|
|
|
# make an enumeration of the ctypes used for passing operands around.
|
|
(cfn, hfn) = emit_ctypes_enum(options, ctypes)
|
|
agi.add_file_name(cfn)
|
|
agi.add_file_name(hfn,header=True)
|
|
|
|
(cfn, hfn) = emit_ctypes_mapping(options,
|
|
operand_ctype_map, operand_bits_map)
|
|
agi.add_file_name(cfn)
|
|
agi.add_file_name(hfn,header=True)
|
|
|
|
|
|
|
|
############################################################################
|
|
# MAIN
|
|
############################################################################
|
|
|
|
def main():
|
|
arg_parser = setup_arg_parser()
|
|
(options, args ) = arg_parser.parse_args()
|
|
set_verbosity_options(options.verbosity)
|
|
if options.xeddir == '':
|
|
path_to_generator = sys.argv[0]
|
|
(path_to_src, configure) = os.path.split(path_to_generator)
|
|
options.xeddir = path_to_src
|
|
msge("[ASSUMING PATH TO XED SRC] " + options.xeddir)
|
|
|
|
agi = all_generator_info_t(options)
|
|
|
|
if not os.path.exists(agi.common.options.gendir):
|
|
die("Need a subdirectory called " + agi.common.options.gendir)
|
|
|
|
print_resource_usage('main.1')
|
|
gen_operand_storage_fields(options,agi)
|
|
|
|
print_resource_usage('main.2')
|
|
gen_regs(options,agi)
|
|
|
|
print_resource_usage('main.2.5')
|
|
gen_widths(options,agi) # writes agi.widths_list and agi.widths_dict
|
|
gen_extra_widths(agi) # writes agi.extra_widths_nt and agi.exta_widths_reg
|
|
gen_element_types_base(agi)
|
|
gen_element_types(agi) # write agi.xtypes dict, agi.xtypes
|
|
gen_pointer_names(options,agi)
|
|
|
|
print_resource_usage('main.3')
|
|
|
|
# this reads the pattern input, builds a graph, emits the decoder
|
|
# graph and the itable, emits the extractor functions, computes the
|
|
# iforms, writes map using iforms, computes capture
|
|
# functions, gathers and emits enums. (That part should move out).
|
|
gen_everything_else(agi)
|
|
|
|
print_resource_usage('main.4')
|
|
gen_ild(agi)
|
|
gen_cpuid_map(agi)
|
|
|
|
print_resource_usage('main.5')
|
|
agi.close_output_files()
|
|
print_resource_usage('main.6')
|
|
agi.dump_generated_files()
|
|
|
|
################################################
|
|
|
|
if __name__ == '__main__':
|
|
_profile = False
|
|
if _profile:
|
|
# profiling takes A REAL LONG TIME
|
|
import profile
|
|
profile.run('main()','profile.out')
|
|
else:
|
|
main()
|
|
sys.exit(0)
|
|
#eof
|