1 | #!/usr/bin/env python
|
---|
2 | # -*- coding: utf-8 -*-
|
---|
3 | # $Id: bsd-spec-analyze.py 108246 2025-02-17 00:18:01Z vboxsync $
|
---|
4 |
|
---|
5 | """
|
---|
6 | ARM BSD specification analyser.
|
---|
7 | """
|
---|
8 |
|
---|
9 | from __future__ import print_function;
|
---|
10 |
|
---|
11 | __copyright__ = \
|
---|
12 | """
|
---|
13 | Copyright (C) 2025 Oracle and/or its affiliates.
|
---|
14 |
|
---|
15 | This file is part of VirtualBox base platform packages, as
|
---|
16 | available from https://www.215389.xyz.
|
---|
17 |
|
---|
18 | This program is free software; you can redistribute it and/or
|
---|
19 | modify it under the terms of the GNU General Public License
|
---|
20 | as published by the Free Software Foundation, in version 3 of the
|
---|
21 | License.
|
---|
22 |
|
---|
23 | This program is distributed in the hope that it will be useful, but
|
---|
24 | WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
25 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
26 | General Public License for more details.
|
---|
27 |
|
---|
28 | You should have received a copy of the GNU General Public License
|
---|
29 | along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
30 |
|
---|
31 | SPDX-License-Identifier: GPL-3.0-only
|
---|
32 | """
|
---|
33 | __version__ = "$Revision: 108246 $"
|
---|
34 |
|
---|
35 | # Standard python imports.
|
---|
36 | import argparse;
|
---|
37 | import collections;
|
---|
38 | import json;
|
---|
39 | import os;
|
---|
40 | import sys;
|
---|
41 | import tarfile;
|
---|
42 |
|
---|
43 |
|
---|
44 | class ArmEncodesetField(object):
|
---|
45 | """
|
---|
46 | ARM Encodeset.Bits & Encodeset.Field.
|
---|
47 | """
|
---|
48 | def __init__(self, oJson, iFirstBit, cBitsWidth, fFixed, fValue, sName = None):
|
---|
49 | self.oJson = oJson;
|
---|
50 | self.iFirstBit = iFirstBit;
|
---|
51 | self.cBitsWidth = cBitsWidth;
|
---|
52 | self.fFixed = fFixed;
|
---|
53 | self.fValue = fValue;
|
---|
54 | self.sName = sName; ##< None if Encodeset.Bits.
|
---|
55 |
|
---|
56 | def __str__(self):
|
---|
57 | sRet = '[%2u:%-2u] = %#x/%#x/%#x' % (
|
---|
58 | self.iFirstBit + self.cBitsWidth - 1, self.iFirstBit, self.fValue, self.fFixed, self.getMask()
|
---|
59 | );
|
---|
60 | if self.sName:
|
---|
61 | sRet += ' # %s' % (self.sName,)
|
---|
62 | return sRet;
|
---|
63 |
|
---|
64 | def __repr__(self):
|
---|
65 | return self.__str__();
|
---|
66 |
|
---|
67 | def getMask(self):
|
---|
68 | """ Field mask (unshifted). """
|
---|
69 | return (1 << self.cBitsWidth) - 1;
|
---|
70 |
|
---|
71 | def getShiftedMask(self):
|
---|
72 | """ Field mask, shifted. """
|
---|
73 | return ((1 << self.cBitsWidth) - 1) << self.iFirstBit;
|
---|
74 |
|
---|
75 | @staticmethod
|
---|
76 | def fromJson(oJson):
|
---|
77 | """ """
|
---|
78 | assert oJson['_type'] in ('Instruction.Encodeset.Field', 'Instruction.Encodeset.Bits'), oJson['_type'];
|
---|
79 |
|
---|
80 | oRange = oJson['range'];
|
---|
81 | assert oRange['_type'] == 'Range';
|
---|
82 | iFirstBit = int(oRange['start']);
|
---|
83 | cBitsWidth = int(oRange['width']);
|
---|
84 |
|
---|
85 | sValue = oJson['value']['value'];
|
---|
86 | assert sValue[0] == '\'' and sValue[-1] == '\'', sValue;
|
---|
87 | sValue = sValue[1:-1];
|
---|
88 | assert len(sValue) == cBitsWidth, 'cBitsWidth=%s sValue=%s' % (cBitsWidth, sValue,);
|
---|
89 | fFixed = 0;
|
---|
90 | fValue = 0;
|
---|
91 | for ch in sValue:
|
---|
92 | assert ch in 'x10', 'ch=%s' % ch;
|
---|
93 | fFixed <<= 1;
|
---|
94 | fValue <<= 1;
|
---|
95 | if ch != 'x':
|
---|
96 | fFixed |= 1;
|
---|
97 | if ch == '1':
|
---|
98 | fValue |= 1;
|
---|
99 |
|
---|
100 | sName = oJson['name'] if oJson['_type'] == 'Instruction.Encodeset.Field' else None;
|
---|
101 | return ArmEncodesetField(oJson, iFirstBit, cBitsWidth, fFixed, fValue, sName);
|
---|
102 |
|
---|
103 | @staticmethod
|
---|
104 | def fromJsonEncodeset(oJson, aoSet, fCovered):
|
---|
105 | """ """
|
---|
106 | assert oJson['_type'] == 'Instruction.Encodeset.Encodeset', oJson['_type'];
|
---|
107 | for oJsonValue in oJson['values']:
|
---|
108 | oNewField = ArmEncodesetField.fromJson(oJsonValue);
|
---|
109 | fNewMask = oNewField.getShiftedMask();
|
---|
110 | if (fNewMask & fCovered) != fNewMask:
|
---|
111 | aoSet.append(oNewField)
|
---|
112 | fCovered |= fNewMask;
|
---|
113 | return (aoSet, fCovered);
|
---|
114 |
|
---|
115 |
|
---|
116 | class ArmInstruction(object):
|
---|
117 | """
|
---|
118 | ARM instruction
|
---|
119 | """
|
---|
120 | def __init__(self, oJson, sName, sMemonic, aoEncodesets):
|
---|
121 | self.oJson = oJson;
|
---|
122 | self.sName = sName;
|
---|
123 | self.sMnemonic = sMemonic;
|
---|
124 | self.aoEncodesets = aoEncodesets;
|
---|
125 | self.fFixedMask = 0;
|
---|
126 | self.fFixedValue = 0;
|
---|
127 | for oField in aoEncodesets:
|
---|
128 | self.fFixedMask |= oField.fFixed << oField.iFirstBit;
|
---|
129 | self.fFixedValue |= oField.fValue << oField.iFirstBit;
|
---|
130 |
|
---|
131 | def __str__(self):
|
---|
132 | sRet = 'sName=%s; sMnemonic=%s fFixedValue/Mask=%#x/%#x encoding=\n %s' % (
|
---|
133 | self.sName, self.sMnemonic, self.fFixedValue, self.fFixedMask,
|
---|
134 | ',\n '.join([str(s) for s in self.aoEncodesets]),
|
---|
135 | );
|
---|
136 | return sRet;
|
---|
137 |
|
---|
138 | def __repr__(self):
|
---|
139 | return self.__str__();
|
---|
140 |
|
---|
141 | ## All the instructions.
|
---|
142 | g_aoAllArmInstructions = [] # type: List[ArmInstruction]
|
---|
143 |
|
---|
144 | ## All the instructions by name (not mnemonic.
|
---|
145 | g_dAllArmInstructionsByName = {} # type: Dict[ArmInstruction]
|
---|
146 |
|
---|
147 |
|
---|
148 | def parseInstructions(aoStack, aoJson):
|
---|
149 | for oJson in aoJson:
|
---|
150 | if oJson['_type'] == "Instruction.InstructionSet":
|
---|
151 | parseInstructions([oJson,] + aoStack, oJson['children']);
|
---|
152 | elif oJson['_type'] == "Instruction.InstructionGroup":
|
---|
153 | parseInstructions([oJson,] + aoStack, oJson['children']);
|
---|
154 | elif oJson['_type'] == "Instruction.Instruction":
|
---|
155 | #aoJsonEncodings = [oJson['encoding'],];
|
---|
156 | (aoEncodesets, fCovered) = ArmEncodesetField.fromJsonEncodeset(oJson['encoding'], [], 0);
|
---|
157 | for oParent in aoStack:
|
---|
158 | if 'encoding' in oParent:
|
---|
159 | (aoEncodesets, fCovered) = ArmEncodesetField.fromJsonEncodeset(oParent['encoding'], aoEncodesets, fCovered);
|
---|
160 | oInstr = ArmInstruction(oJson, oJson['name'], oJson['name'], aoEncodesets);
|
---|
161 |
|
---|
162 | g_aoAllArmInstructions.append(oInstr);
|
---|
163 | assert oInstr.sName not in g_dAllArmInstructionsByName;
|
---|
164 | g_dAllArmInstructionsByName[oInstr.sName] = oInstr;
|
---|
165 |
|
---|
166 |
|
---|
167 | def bsdSpecAnalysis(asArgs):
|
---|
168 | """ Main function. """
|
---|
169 |
|
---|
170 | #
|
---|
171 | # Parse arguments.
|
---|
172 | #
|
---|
173 | oArgParser = argparse.ArgumentParser(add_help = False);
|
---|
174 | oArgParser.add_argument('--tar',
|
---|
175 | metavar = 'AARCHMRS_BSD_A_profile-2024-12.tar.gz',
|
---|
176 | dest = 'sTarFile',
|
---|
177 | action = 'store',
|
---|
178 | default = None,
|
---|
179 | help = 'Specification TAR file to get the files from.');
|
---|
180 | oArgParser.add_argument('--instructions',
|
---|
181 | metavar = 'Instructions.json',
|
---|
182 | dest = 'sFileInstructions',
|
---|
183 | action = 'store',
|
---|
184 | default = 'Instructions.json',
|
---|
185 | help = 'The path to the instruction specficiation file.');
|
---|
186 | oArgParser.add_argument('--features',
|
---|
187 | metavar = 'Features.json',
|
---|
188 | dest = 'sFileFeatures',
|
---|
189 | action = 'store',
|
---|
190 | default = 'Features.json',
|
---|
191 | help = 'The path to the features specficiation file.');
|
---|
192 | oArgParser.add_argument('--registers',
|
---|
193 | metavar = 'Registers.json',
|
---|
194 | dest = 'sFileRegisters',
|
---|
195 | action = 'store',
|
---|
196 | default = 'Registers.json',
|
---|
197 | help = 'The path to the registers specficiation file.');
|
---|
198 | oArgParser.add_argument('--spec-dir',
|
---|
199 | metavar = 'dir',
|
---|
200 | dest = 'sSpecDir',
|
---|
201 | action = 'store',
|
---|
202 | default = '',
|
---|
203 | help = 'Specification directory to prefix the specficiation files with.');
|
---|
204 | oOptions = oArgParser.parse_args(asArgs[1:]);
|
---|
205 |
|
---|
206 | #
|
---|
207 | # Load the files.
|
---|
208 | #
|
---|
209 | print("loading specs ...");
|
---|
210 | if oOptions.sTarFile:
|
---|
211 | with tarfile.open(oOptions.sTarFile, 'r') as oTarFile:
|
---|
212 | with oTarFile.extractfile(oOptions.sFileInstructions) as oFile:
|
---|
213 | dRawInstructions = json.load(oFile);
|
---|
214 | #with open(sFileFeatures, 'r', encoding = 'utf-8') as oFile:
|
---|
215 | # dRawFeatures = json.load(oFile);
|
---|
216 | #with open(sFileRegisters, 'r', encoding = 'utf-8') as oFile:
|
---|
217 | # dRawRegisters = json.load(oFile);
|
---|
218 | else:
|
---|
219 | if oOptions.sSpecDir:
|
---|
220 | if not os.path.isabs(oOptions.sFileInstructions):
|
---|
221 | oOptions.sFileInstructions = os.path.normpath(os.path.join(oOptions.sSpecDir, oOptions.sFileInstructions));
|
---|
222 | if not os.path.isabs(oOptions.sFileFeatures):
|
---|
223 | oOptions.sFileFeatures = os.path.normpath(os.path.join(oOptions.sSpecDir, oOptions.sFileFeatures));
|
---|
224 | if not os.path.isabs(oOptions.sFileRegisters):
|
---|
225 | oOptions.sFileRegisters = os.path.normpath(os.path.join(oOptions.sSpecDir, oOptions.sFileRegisters));
|
---|
226 |
|
---|
227 | with open(oOptions.sFileInstructions, 'r', encoding = 'utf-8') as oFile:
|
---|
228 | dRawInstructions = json.load(oFile);
|
---|
229 | #with open(oOptions.sFileFeatures, 'r', encoding = 'utf-8') as oFile:
|
---|
230 | # dRawFeatures = json.load(oFile);
|
---|
231 | #with open(oOptions.sFileRegisters, 'r', encoding = 'utf-8') as oFile:
|
---|
232 | # dRawRegisters = json.load(oFile);
|
---|
233 | print("... done loading.");
|
---|
234 |
|
---|
235 | #
|
---|
236 | # Parse the Instructions.
|
---|
237 | #
|
---|
238 | print("parsing instructions ...");
|
---|
239 | parseInstructions([], dRawInstructions['instructions']);
|
---|
240 | print("Found %u instructions." % (len(g_aoAllArmInstructions),));
|
---|
241 |
|
---|
242 | #oBrk = g_dAllArmInstructionsByName['BRK_EX_exception'];
|
---|
243 | #print("oBrk=%s" % (oBrk,))
|
---|
244 |
|
---|
245 | if False:
|
---|
246 | for oInstr in g_aoAllArmInstructions:
|
---|
247 | print('%08x/%08x %s' % (oInstr.fFixedMask, oInstr.fFixedValue, oInstr.sName));
|
---|
248 |
|
---|
249 | # Gather stats on fixed bits:
|
---|
250 | if True:
|
---|
251 | dCounts = collections.Counter();
|
---|
252 | for oInstr in g_aoAllArmInstructions:
|
---|
253 | cPopCount = bin(oInstr.fFixedMask).count('1');
|
---|
254 | dCounts[cPopCount] += 1;
|
---|
255 |
|
---|
256 | print('');
|
---|
257 | print('Fixed bit pop count distribution:');
|
---|
258 | for i in range(33):
|
---|
259 | if i in dCounts:
|
---|
260 | print(' %2u: %u' % (i, dCounts[i]));
|
---|
261 |
|
---|
262 | # Top 10 fixed masks.
|
---|
263 | if True:
|
---|
264 | dCounts = collections.Counter();
|
---|
265 | for oInstr in g_aoAllArmInstructions:
|
---|
266 | dCounts[oInstr.fFixedMask] += 1;
|
---|
267 |
|
---|
268 | print('');
|
---|
269 | print('Top 20 fixed masks:');
|
---|
270 | for fFixedMask, cHits in dCounts.most_common(20):
|
---|
271 | print(' %#x: %u times' % (fFixedMask, cHits,));
|
---|
272 |
|
---|
273 | return 0;
|
---|
274 |
|
---|
275 |
|
---|
276 | if __name__ == '__main__':
|
---|
277 | sys.exit(bsdSpecAnalysis(sys.argv));
|
---|
278 |
|
---|