xed/pysrc/ins_emit.py
Mark Charney 9bf8c93ad8 ins_emit.py: fix typo in key fn name
Change-Id: Iabfd28f74381c9123f6a1511e59148d57f0e2bcb
(cherry picked from commit 2f0f348612c6bc6827d207cdd16bf1cfe454a0a8)
2017-06-12 14:41:23 -04:00

787 lines
30 KiB
Python
Executable File

#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
from __future__ import print_function
import sys
import os
import codegen
import encutil
import genutil
import actions
import verbosity
max_in_byte = 256 #max unsigned int per byte
emit_function_prefix = 'xed_encode_instruction_emit_pattern'
bind_function_prefix = 'xed_encode_instruction_fb_pattern'
get_field_value = 'xed_encoder_get_start_field_value'
legacy_maps = {'map0':'XED_ILD_MAP0',
'map1':'XED_ILD_MAP1',
'map2':'XED_ILD_MAP2',
'map3':'XED_ILD_MAP3',
'3dnow':'XED_ILD_MAPAMD' }
def key_field_binding_lower(x):
return x.field_name.lower()
def sort_field_bindings(a,b):
''' sort action_t of type emit '''
if a.field_name.lower() > b.field_name.lower():
return 1
elif a.field_name.lower() < b.field_name.lower():
return -1
return 0
def key_iform_by_bind_ptrn(x):
return x.bind_ptrn
def cmp_iforms_by_bind_ptrn(a,b): # FIXME:2017-06-10: PY3 port, no longer used
if a.bind_ptrn > b.bind_ptrn:
return 1
elif a.bind_ptrn < b.bind_ptrn:
return -1
return 0
def key_priority(x):
return x.priority
def key_rule_length(x):
return len(x.rule.get_all_emits() + x.rule.get_all_nts())
def cmp_iform_len(a,b): # FIXME:2017-06-10: PY3 port, no longer used
if a.priority > b.priority:
return 1
elif a.priority < b.priority:
return -1
alen = len(a.rule.get_all_emits() + a.rule.get_all_nts())
blen = len(b.rule.get_all_emits() + b.rule.get_all_nts())
if alen > blen:
return 1
elif alen < blen:
return -1
return cmp_iforms_by_bind_ptrn(a,b)
class instructions_group_t(object):
''' each encoding iform has:
1. conditions ( e.g.REG1=GPRv_r() )
2. actions, one type of the actions is a nonterminal (nt)
the conditions and the nt's are called "bind patterns".
if two different iclasses have the same bind patterns
for all of their iforms,
we can put those iclasses in the same group.
this is what we are doing in _join_iclasses_to_groups
'''
def __init__(self,iarray,log_dir):
self.groups = []
self.iclass2group = {}
self.log_name = 'groups_log.txt'
self._join_iclasses_to_groups(iarray,log_dir)
def _group_good_for_iclass(self,group,iforms):
''' Check if the incoming group represents the list of iforms.
A group represents a list of iforms if:
1. it has same number of iforms.
2. for each iform there is an iform in the group that has the same
bind pattern
@param group: ins_group_t object
@param iforms: a list of iform_t
@return: True if group represents the the ifoms list '''
if len(group.iforms) != len(iforms):
return False
for iform,group_iform in zip(iforms,group.iforms):
if iform.bind_ptrn != group_iform.bind_ptrn:
return False
return True
def _put_iclass_in_group(self,groups,iclass,instruction_iforms):
''' tries to find a group that represents the incoming iclass.
'represents' means that all the iforms have exactly the same
bind patterns.
if no group was found, then create new group for the iclass.
@param groups: a list of ins_group_t object
@param iclass: the iclass name
@param instruction_iforms: a list of iform_t, the
iforms of the iclass
@return: function_object_t '''
for group in groups:
# check if group represents the iclass
if self._group_good_for_iclass(group,instruction_iforms):
group.add_iclass(iclass,instruction_iforms)
return
#no matching groups
#create new one
group = ins_group_t()
group.add_iclass(iclass,instruction_iforms)
groups.append(group)
return
def _join_iclasses_to_groups(self,iarray,log_dir):
'''
1. dividing the iclasses into groups.
2. creating a mapping from iclass to its group Id.
3. generating a log
return: a list of ins_group_t objects '''
groups = []
#1. generate the groups
for iclass,iforms in list(iarray.items()):
iforms.sort(key=key_iform_by_bind_ptrn)
self._put_iclass_in_group(groups,iclass,iforms)
# 2. generate the iclass to group Id mapping
self.iclass2group = {}
for i,group in enumerate(groups):
for iclass in group.get_iclasses():
self.iclass2group[iclass] = i
# 3. print the log
if verbosity.vencode():
log_file = os.path.join(log_dir,self.log_name)
df = open(log_file,'w') #debug file
df.write("number of iclasses: %d\n" % len(iarray))
df.write("number of groups: %d\n" % len(groups))
for i,group in enumerate(groups):
df.write("GROUP Id: %d\n" % i)
df.write("ICLASSES: %s\n" % str(group.get_iclasses()))
for iform in group.iforms:
df.write("%s: %s\n" % ('BIND PATTERN: ', iform.bind_ptrn ))
df.write("\n\n")
df.close()
self.groups = groups
def get_groups(self):
''' return the groups list '''
return self.groups
def num_groups(self):
''' return the number of groups '''
return len(self.groups)
def get_iclass2group(self):
''' return a dic of iclass to it group Id'''
return self.iclass2group
def get_iclass2index_in_group(self):
''' return a dictionary of iclass to its index in the group'''
d = {}
for group in self.groups:
iclasses = sorted(group.get_iclasses())
for i,iclass in enumerate(iclasses):
d[iclass] = i
return d
class ins_group_t(object):
''' This class represents one group.
it holds the list of iclasses that have the same bind patterns.
'''
def __init__(self):
''' params:
1. iclass2iforms: mapping from iclass to a list of iforms
2.iforms: list of iform_t objects
'''
self.iclass2iforms = {}
self.iforms = []
def add_iclass(self,iclass,iforms):
''' add the iclass and iforms list to the group '''
self.iclass2iforms[iclass] = iforms
if not self.iforms:
self.iforms = iforms
def get_iclasses(self):
''' return a list of iclasses in the group'''
return list(self.iclass2iforms.keys())
def get_iform_ids_table(self):
''' generate C style table of iform Id's.
the table is 2D. one row per iclass.
the columns are the different iform Ids '''
table = []
iclasses = sorted(self.get_iclasses())
for iclass in iclasses:
values = ''
iforms_sorted_by_length = self.iclass2iforms[iclass]
iforms_sorted_by_length.sort(key=key_iform_by_bind_ptrn)
iforms_sorted_by_length.sort(key=key_rule_length)
iforms_sorted_by_length.sort(key=key_priority)
for iform in iforms_sorted_by_length:
values += '%4d,' % iform.rule.iform_id
line = "/*%10s*/ {%s}," % (iclass,values)
table.extend([ line ])
return table
class instruction_codegen_t():
def __init__(self,iform_list,iarray,logs_dir, amd_enabled=True):
self.amd_enabled = amd_enabled
self.iform_list = iform_list
self.iarray = iarray
self.logs_dir = logs_dir #directory for the log file
#list of field binding function_object_t
self.fb_ptrs_fo_list = None
#list of emit patterns function_object_t
self.emit_ptrs_fo_list = None
# number of field binding patterns
self.max_fb_ptrns = None
# number of emit patterns
self.max_emit_ptrns = None
# a list of all values been set to field ordered sequentially
self.fb_values_list = None
# the length of fb_values_list
self.fb_values_table_size = None
# list of groups (instructions_group_t)
self.instruction_groups = None
def get_values(self,encoder_config):
''' copy the necessary fields to encoder_confing object '''
encoder_config.fb_values_list = self.fb_values_list
encoder_config.fb_values_table_size = self.fb_values_table_size
encoder_config.emit_ptrs_fo_list = self.emit_ptrs_fo_list
encoder_config.max_emit_ptrns = self.max_emit_ptrns
encoder_config.fb_ptrs_fo_list = self.fb_ptrs_fo_list
encoder_config.max_fb_ptrns = self.max_fb_ptrns
encoder_config.ins_groups = self.instruction_groups
def _make_emit_fo(self, iform, i):
''' create the function object for this emit pattern
@param iform: iform_t object
@param i: index of the pattern function
@return: function_object_t
'''
fname = "%s_%d" % (emit_function_prefix,i)
fo = codegen.function_object_t(fname,
return_type='void')
# obj_str is the function parameters for the emit function
obj_str = encutil.enc_strings['obj_str']
enc_arg = "%s* %s" % (encutil.enc_strings['obj_type'],
obj_str)
fo.add_arg(enc_arg)
for action in iform.rule.actions:
if action.field_name and action.field_name == 'MAP':
emit_map = 'xed_encoder_request_emit_legacy_map'
code = " %s(%s)" % (emit_map,obj_str)
fo.add_code_eol(code)
elif action.field_name and action.field_name == 'NOM_OPCODE':
code = ''
get_opcode = 'xed_encoder_get_nominal_opcode(%s)' % obj_str
if action.nbits == 8:
emit_func = 'xed_encoder_request_emit_bytes'
else:
emit_func = 'xed_encoder_request_encode_emit'
code = ' '*4
code += '%s(%s,%d,%s)' % (emit_func,obj_str,
action.nbits,get_opcode)
fo.add_code_eol(code)
else:
code = action.emit_code('EMIT')
for c in code:
fo.add_code(c)
return fo
def _make_fb_setter_fo(self, iform, i):
''' create the function object for pattern of fields bindings
@param iform: iform_t object
@param i: index of the pattern function
@return: function_object_t
'''
fname = "%s_%d" % (bind_function_prefix,i)
fo = codegen.function_object_t(fname,
return_type='void')
obj_name = encutil.enc_strings['obj_str']
enc_arg = "%s* %s" % (encutil.enc_strings['obj_type'],
obj_name)
fo.add_arg(enc_arg)
if not iform.fbs:
#no field binding we need to set, pacify the compiler
fo.add_code_eol('(void)%s' % obj_name)
return fo
fo.add_code_eol(' const xed_uint8_t* val')
fo.add_code_eol(' val = %s(%s)' % (get_field_value, obj_name))
for i,fb_action in enumerate(iform.fbs):
value_from_lu_table = '*(val+%d)' % i
operand_setter = "%s_set_%s" % (encutil.enc_strings['op_accessor'],
fb_action.field_name.lower())
code = ' %s(%s,%s);' % (operand_setter,
obj_name, value_from_lu_table)
fo.add_code(code)
return fo
def _verify_naked_bits_in_unique_pattern(self):
''' calculate how many references we have per each full
instruction emit pattern.
naked bits are bits in the pattern without a field name
like 0x0F or 0b110. earlier functions decorated
opcode/legacy map.
If the naked bits just show up once, then we can hardcode
those bits in the emit function. This is a test for that.
Current design relies on the naked bits being the same in
similar instruction patterns. If two patterns differ in
any naked bits, they cannot share emit functions and we die.
The workaround would be to capture the bits in some field to
allow the emit function to be shared & generic.
The current inputs to XED have no such conflicts.
'''
refs_per_ptrn ={}
for iform in self.iform_list:
if iform.emit_actions not in refs_per_ptrn:
refs_per_ptrn[iform.emit_actions] = 1
else:
refs_per_ptrn[iform.emit_actions] += 1
if iform.rule.has_naked_bit_action():
err = 'emit pattern: %s has more than one reference ' +\
'use of naked bits is not allowed'%iform.emit_actions
genutil.die(err)
def _make_emit_pattern_fos(self):
''' collect all the different patterns for emit phase.
for each pattern create a function representing it.
adds to each rule in iform_t the index of the pattern function
@return: list of emit pattern function name to function object
'''
emit_patterns = {}
fo_list = []
i = 0
for iform in self.iform_list:
if iform.emit_actions not in emit_patterns:
fo = self._make_emit_fo(iform,i)
emit_patterns[iform.emit_actions] = (fo,i)
fo_list.append(fo)
iform.emit_func_index = i
i += 1
else:
fo, index = emit_patterns[iform.emit_actions]
iform.emit_func_index = index
return fo_list
def _make_fb_pattern_fos(self):
''' collect all the different patterns for bind phase.
for each pattern create a function representing it.
adds to each rule in iform_t the index of the pattern function
@return: list of emit pattern function name to function object
'''
bind_ptterns = {}
fo_list = []
i = 0
for iform in self.iform_list:
if iform.fb_ptrn not in bind_ptterns:
fo = self._make_fb_setter_fo(iform,i)
bind_ptterns[iform.fb_ptrn] = (fo,i)
fo_list.append(fo)
iform.bind_func_index = i
i += 1
else:
fo,index = bind_ptterns[iform.fb_ptrn]
iform.bind_func_index = index
return fo_list
def _identify_map_and_nominal_opcode(self,iform):
''' scan the list of actions and identify the nominal opcode and
the legacy map.
replace the actions that describe the bytes of the nom opcode
and map with dummy action as place holders.
'''
#list of all prefixes
prefixes = [0x66,0x67,0xf2,0xf3,0xf0,0x64,0x65,0x2e,0x3e,0x26,0x36]
vv = 0
first_naked_bits_index = None
for i,action in enumerate(iform.rule.actions):
if action.is_field_binding() and action.field_name == 'VEXVALID':
# we are in vex valid 1/2/3
vv = 1
if action.naked_bits():
if vv == 0 and action.int_value in prefixes:
#we are in legacy space and this byte is
#a prefix, don't care
continue
else:
#this byte represents the nominal opcode or the legacy map
first_naked_bits_index = i
break
if first_naked_bits_index == None:
err = "did not find nominal opcode for iform: %s" % str(iform)
genutil.die(err)
last_index = len(iform.rule.actions) - 1
first = iform.rule.actions[first_naked_bits_index]
if first.int_value != 0x0F or vv:
#this action represents the opcode
iform.nominal_opcode = first.int_value
iform.nom_opcode_bits = first.nbits
# FIXME: mjc: 2017-05-19: this ultimately gets dumped in
# to the xed-encoder-iforms-init.c file and all the
# VEX/EVEX/XOP encoded stuff are listed as XED_ILD_MAP0
# which is wrong. This is only used for encoding the legacy maps.
# The actual map comes from the field-bindings collected later
iform.map = legacy_maps['map0'] # legacy only, Not all vexvalid instr are map0.
#replacing with place holder
iform.rule.actions[i] = actions.dummy_emit(first,'NOM_OPCODE')
else: #first byte == 0x0F and we are legacy space
#check that we have at least one more byte to read
if first_naked_bits_index+1 > last_index:
genutil.die("not enough actions")
second = iform.rule.actions[first_naked_bits_index+1]
if not second.naked_bits():
genutil.die("expecting map/nominal opcode after 0x0F byte")
if self.amd_enabled and second.int_value == 0x0F: #3DNow
# the nominal opcode in 3DNow is in the last action.
# FIXME: it is best to not reference directly the last action
# but rather add a meaningful field name to the action
amd3dnow_opcode_action = iform.rule.actions[-1]
iform.nominal_opcode = amd3dnow_opcode_action.int_value
iform.nom_opcode_bits = 8
iform.map = legacy_maps['3dnow']
iform.rule.actions[-1] = actions.dummy_emit(
amd3dnow_opcode_action,'NOM_OPCODE')
iform.rule.actions[i] = actions.dummy_emit(first,'MAP')
#the second byte that describes the map
#is not needed, remove it
iform.rule.actions.remove(second)
elif second.int_value == 0x38 or second.int_value == 0x3A:
#check that we have at least one more byte to read
if first_naked_bits_index+2 > last_index:
genutil.die("not enough actions")
third = iform.rule.actions[first_naked_bits_index+2]
if not third.naked_bits():
genutil.die("expecting map/nominal opcode after 0x0F byte")
iform.nominal_opcode = third.int_value
iform.nom_opcode_bits = third.nbits
if second.int_value == 0x38:
iform.map = legacy_maps['map2']
else: #0x3A
iform.map = legacy_maps['map3']
iform.rule.actions[i+1] = actions.dummy_emit(second,'MAP')
iform.rule.actions[i+2] = actions.dummy_emit(third,
'NOM_OPCODE')
iform.rule.actions.remove(first)
else:
iform.nominal_opcode = second.int_value
iform.nom_opcode_bits = second.nbits
iform.map = legacy_maps['map1']
iform.rule.actions[i] = actions.dummy_emit(first,'MAP')
iform.rule.actions[i+1] = actions.dummy_emit(second,
'NOM_OPCODE')
def _find_sub_list(self,all_fbs_values, fbs_values):
''' find the the sub list: fbs_values
in the list: all_fbs_values.
if not found return -1
if found return the fist index of the recurrence '''
elems = len(fbs_values)
indices_to_scan = len(all_fbs_values) - elems + 1
for i in range(indices_to_scan):
if fbs_values == all_fbs_values[i:i+elems]:
return i
return -1
def _find_fb_occurrence(self,all_fbs_values, fbs_values):
''' find the the sub list: fbs_values
in the list: all_fbs_values.
if fbs_values is not a sub list to all_fbs_values
concatenate it.
return: the first index of fbs_values occurrence
in all_fbs_values.
'''
if not fbs_values:
return 0
if not all_fbs_values:
all_fbs_values.extend(fbs_values)
return 0
index = self._find_sub_list(all_fbs_values,fbs_values)
if index >= 0:
# found sub list
return index
# did not found sub list concatenate to the end
last_index = len(all_fbs_values)
all_fbs_values.extend(fbs_values)
return last_index
def _make_fb_values_list(self):
''' generate a list of the values being set by the FB actions.
for each iform find the start index of the values list.
All the field bindings get put in to a linear array.
This is finds the index in to that array.
This is a quick compression technique for sharing trailing
subsequences.
e.g.: iform1 sets the values: 0 1 2 (3 fields)
iform2 sets the values: 3 4 (2 fields)
iform3 sets the values: 1 2
iform4 sets the values: 2 3
the ordered list of unique sequence values across
all iforms is: 0 1 2 3 4.
start index of iform1: 0 (which picks up 0, 1 2)
start index of iform2: 3 (which picks up 3, 4)
start index of iform3: 1 (which picks up 1, 2)
start index of iform4: 2 (which picks up 2, 3)
Note: because of ordering, if iform3 happens to show
up before iform1, they won't share iform1's
subsequence 1,2.
'''
fbs_list = []
for iform in self.iform_list:
# collect all the actions that set fields
iform.fbs = iform.rule.get_all_fbs()
iform.fbs.sort(key=key_field_binding_lower)
# create a list of int values
fbs_values = [ x.int_value for x in iform.fbs]
#find the start index of this list of values in the general list
#and update the general list as needed
iform.fb_index = self._find_fb_occurrence(fbs_list, fbs_values)
fbs_list = [ str(x) for x in fbs_list]
return fbs_list
def _make_field_bindings_pattern(self,iform):
''' create the string that represents the field bindings pattern. '''
bind_actions = []
for action in iform.rule.actions:
if action.type == 'nt':
pass
elif action.type == 'FB':
bind_actions.append(action.field_name)
elif action.type == 'emit':
if action.emit_type == 'numeric' and action.field_name:
bind_actions.append(action.field_name)
else:
pass
else:
genutil.die("unexpected action type: %s" % action.type)
fb_ptrn = ''
if bind_actions:
fb_ptrn = ', '.join(sorted(bind_actions))
iform.fb_ptrn = fb_ptrn
def _make_emit_pattern(self,iform):
''' create the string that represents the action for the emit phase.
using this string we will classify all
the emit actions into patterns
'''
emit_pattern = []
for action in iform.rule.actions:
if action.type == 'emit':
emit_pattern.append("emit %s nbits=%d" % (action.field_name,
action.nbits))
elif action.type == 'nt':
emit_pattern.append(str(action))
elif action.type == 'FB':
# FB are not used in emit phase so we do not factor them
# in to the string that represents the pattern
pass
else:
genutil.die("unexpected action type: %s" % action.type)
iform.emit_actions = ', '.join(emit_pattern)
def _make_bind_pattern(self,iform):
''' create the string that represents the field bindings pattern. '''
bind_ptrn = [ str(iform.rule.conditions) ]
for action in iform.rule.actions:
if action.type == 'nt':
bind_ptrn.append(str(action))
iform.bind_ptrn = ''
if bind_ptrn:
iform.bind_ptrn = ', '.join(bind_ptrn)
def _print_log(self):
print("---- encoder log ----")
for i,iform in enumerate(self.iform_list):
print("%d\n" % i)
print("IFORM: %s" % str(iform))
print("iform index: %d" % iform.rule.iform_id)
bind_index = iform.bind_func_index
bind_fo = self.fb_ptrs_fo_list[bind_index]
print("BIND function: %d, %s" % (bind_index,
bind_fo.function_name))
emit_index = iform.emit_func_index
emit_fo = self.emit_ptrs_fo_list[emit_index]
print("EMIT function: %d, %s" % (emit_index,
emit_fo.function_name))
print("NOM_OPCODE: %d" % iform.nominal_opcode)
print("MAP: %s" % iform.map)
fbs_values = [ x.int_value for x in iform.fbs]
print("FB values: %s" % fbs_values)
print("\n\n")
print("-"*20)
def work(self): # main entry point
'''
Each instruction has
1) conditions (iclass, user registers, user inputs) and
2) actions. 3 types:
2a) field bindings,
2b) nonterminals,
2c) bit-emit of operand fields
(hard-coded or from NT output)).
fos = function output object (plural)
generate the following:
1) list of emit patterns fos (2c)
2) list of field bindings patterns fos (2a)
3) list of all field bindings values (values from prev step)
4) max number of emit patterns
5) max number of field binding patterns
6) max number of field bindings values
7) list of groups fos (see explanation in instructions_group_t)
'''
for iform in self.iform_list:
self._identify_map_and_nominal_opcode(iform)
self._make_field_bindings_pattern(iform)
self._make_emit_pattern(iform)
#see explanation about bind patterns in instructions_group_t
self._make_bind_pattern(iform)
self._verify_naked_bits_in_unique_pattern()
self.fb_values_list = self._make_fb_values_list() # step 3
self.fb_values_table_size = len(self.fb_values_list)
self.emit_ptrs_fo_list = self._make_emit_pattern_fos()
self.max_emit_ptrns = len(self.emit_ptrs_fo_list)
if self.max_emit_ptrns > max_in_byte:
# we are using uint8to hold the number of patterns,
# we need to make sure we don't exceeds
error = "total number of emit patterns(%d) exceeds 8 bits"
genutil.die(error % self.max_emit_ptrns)
self.instruction_groups = instructions_group_t(self.iarray,
self.logs_dir)
self.fb_ptrs_fo_list = self._make_fb_pattern_fos()
self.max_fb_ptrns = len(self.fb_ptrs_fo_list)
if self.max_fb_ptrns > max_in_byte:
# we are using uint8to hold the number of patterns,
# we need to make sure we don't exceeds
error = "total number of field binding patterns(%d) exceeds 8 bits"
genutil.die(error % self.max_fb_ptrns)
if verbosity.vencode():
self._print_log()