VirtualBox

source: vbox/trunk/src/VBox/VMM/VMMAll/IEMAllThreadedPython.py@ 99220

Last change on this file since 99220 was 98969, checked in by vboxsync, 15 months ago

VMM/IEM: More work on processing MC blocks, mainly related to reworking common functions for binary operations into body macros. bugref:10369

  • Property svn:eol-style set to LF
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 48.3 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# $Id: IEMAllThreadedPython.py 98969 2023-03-15 00:24:47Z vboxsync $
4# pylint: disable=invalid-name
5
6"""
7Annotates and generates threaded functions from IEMAllInstructions*.cpp.h.
8"""
9
10from __future__ import print_function;
11
12__copyright__ = \
13"""
14Copyright (C) 2023 Oracle and/or its affiliates.
15
16This file is part of VirtualBox base platform packages, as
17available from https://www.virtualbox.org.
18
19This program is free software; you can redistribute it and/or
20modify it under the terms of the GNU General Public License
21as published by the Free Software Foundation, in version 3 of the
22License.
23
24This program is distributed in the hope that it will be useful, but
25WITHOUT ANY WARRANTY; without even the implied warranty of
26MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27General Public License for more details.
28
29You should have received a copy of the GNU General Public License
30along with this program; if not, see <https://www.gnu.org/licenses>.
31
32SPDX-License-Identifier: GPL-3.0-only
33"""
34__version__ = "$Revision: 98969 $"
35
36# Standard python imports.
37import copy;
38import datetime;
39import os;
40import sys;
41import argparse;
42
43import IEMAllInstructionsPython as iai;
44
45
46# Python 3 hacks:
47if sys.version_info[0] >= 3:
48 long = int; # pylint: disable=redefined-builtin,invalid-name
49
50## Number of generic parameters for the thread functions.
51g_kcThreadedParams = 3;
52
53g_kdTypeInfo = {
54 # type name: (cBits, fSigned, C-type )
55 'int8_t': ( 8, True, 'uint8_t', ),
56 'int16_t': ( 16, True, 'int16_t', ),
57 'int32_t': ( 32, True, 'int32_t', ),
58 'int64_t': ( 64, True, 'int64_t', ),
59 'uint4_t': ( 4, False, 'uint8_t', ),
60 'uint8_t': ( 8, False, 'uint8_t', ),
61 'uint16_t': ( 16, False, 'uint16_t', ),
62 'uint32_t': ( 32, False, 'uint32_t', ),
63 'uint64_t': ( 64, False, 'uint64_t', ),
64 'uintptr_t': ( 64, False, 'uintptr_t', ), # ASSUMES 64-bit host pointer size.
65 'bool': ( 1, False, 'bool', ),
66 'IEMMODE': ( 2, False, 'IEMMODE', ),
67};
68
69g_kdIemFieldToType = {
70 # Illegal ones:
71 'offInstrNextByte': ( None, ),
72 'cbInstrBuf': ( None, ),
73 'pbInstrBuf': ( None, ),
74 'uInstrBufPc': ( None, ),
75 'cbInstrBufTotal': ( None, ),
76 'offCurInstrStart': ( None, ),
77 'cbOpcode': ( None, ),
78 'offOpcode': ( None, ),
79 'offModRm': ( None, ),
80 # Okay ones.
81 'fPrefixes': ( 'uint32_t', ),
82 'uRexReg': ( 'uint8_t', ),
83 'uRexB': ( 'uint8_t', ),
84 'uRexIndex': ( 'uint8_t', ),
85 'iEffSeg': ( 'uint8_t', ),
86 'enmEffOpSize': ( 'IEMMODE', ),
87 'enmDefAddrMode': ( 'IEMMODE', ),
88 'enmEffAddrMode': ( 'IEMMODE', ),
89 'enmDefOpSize': ( 'IEMMODE', ),
90 'idxPrefix': ( 'uint8_t', ),
91 'uVex3rdReg': ( 'uint8_t', ),
92 'uVexLength': ( 'uint8_t', ),
93 'fEvexStuff': ( 'uint8_t', ),
94 'uFpuOpcode': ( 'uint16_t', ),
95};
96
97class ThreadedParamRef(object):
98 """
99 A parameter reference for a threaded function.
100 """
101
102 def __init__(self, sOrgRef, sType, oStmt, iParam = None, offParam = 0):
103 self.sOrgRef = sOrgRef; ##< The name / reference in the original code.
104 self.sStdRef = ''.join(sOrgRef.split()); ##< Normalized name to deal with spaces in macro invocations and such.
105 self.sType = sType; ##< The type (typically derived).
106 self.oStmt = oStmt; ##< The statement making the reference.
107 self.iParam = iParam; ##< The parameter containing the references. None if implicit.
108 self.offParam = offParam; ##< The offset in the parameter of the reference.
109
110 self.sNewName = 'x'; ##< The variable name in the threaded function.
111 self.iNewParam = 99; ##< The this is packed into.
112 self.offNewParam = 1024 ##< The bit offset in iNewParam.
113
114
115class ThreadedFunctionVariation(object):
116 """ Threaded function variation. """
117
118 ## @name Variations.
119 ## These variations will match translation block selection/distinctions as well.
120 ## @note Effective operand size is generally handled in the decoder, at present
121 ## we only do variations on addressing and memory accessing.
122 ## @{
123 ksVariation_Default = ''; ##< No variations.
124 ksVariation_Addr16 = '_Addr16'; ##< 16-bit addressing mode.
125 ksVariation_Addr32 = '_Addr32'; ##< 32-bit addressing mode.
126 ksVariation_Addr32Flat = '_Addr32Flat'; ##< 32-bit addressing mode with CS, DS, ES and SS flat and 4GB wide.
127 ksVariation_Addr64 = '_Addr64'; ##< 64-bit addressing mode.
128 ksVariation_Addr64_32 = '_Addr6432'; ##< 32-bit addressing in 64-bit mode.
129 kasVariations_EffAddr = (
130 ksVariation_Addr16, ksVariation_Addr32, ksVariation_Addr32Flat, ksVariation_Addr64, ksVariation_Addr64_32
131 );
132 ## @}
133
134 def __init__(self, oThreadedFunction, sVariation = ksVariation_Default):
135 self.oParent = oThreadedFunction # type: ThreadedFunction
136 ##< ksVariation_Xxxx.
137 self.sVariation = sVariation
138
139 ## Threaded function parameter references.
140 self.aoParamRefs = [] # type: list(ThreadedParamRef)
141 ## Unique parameter references.
142 self.dParamRefs = {} # type: dict(str,list(ThreadedParamRef))
143 ## Minimum number of parameters to the threaded function.
144 self.cMinParams = 0;
145
146 ## List/tree of statements for the threaded function.
147 self.aoStmtsForThreadedFunction = [] # type list(McStmt)
148
149 def getIndexName(self):
150 sName = self.oParent.oMcBlock.sFunction;
151 if sName.startswith('iemOp_'):
152 sName = sName[len('iemOp_'):];
153 if self.oParent.oMcBlock.iInFunction == 0:
154 return 'kIemThreadedFunc_%s%s' % ( sName, self.sVariation, );
155 return 'kIemThreadedFunc_%s_%s%s' % ( sName, self.oParent.oMcBlock.iInFunction, self.sVariation, );
156
157 def getFunctionName(self):
158 sName = self.oParent.oMcBlock.sFunction;
159 if sName.startswith('iemOp_'):
160 sName = sName[len('iemOp_'):];
161 if self.oParent.oMcBlock.iInFunction == 0:
162 return 'iemThreadedFunc_%s%s' % ( sName, self.sVariation, );
163 return 'iemThreadedFunc_%s_%s%s' % ( sName, self.oParent.oMcBlock.iInFunction, self.sVariation, );
164
165 #
166 # Analysis and code morphing.
167 #
168
169 def raiseProblem(self, sMessage):
170 """ Raises a problem. """
171 self.oParent.raiseProblem(sMessage);
172
173 def analyzeReferenceToType(self, sRef):
174 """
175 Translates a variable or structure reference to a type.
176 Returns type name.
177 Raises exception if unable to figure it out.
178 """
179 ch0 = sRef[0];
180 if ch0 == 'u':
181 if sRef.startswith('u32'):
182 return 'uint32_t';
183 if sRef.startswith('u8') or sRef == 'uReg':
184 return 'uint8_t';
185 if sRef.startswith('u64'):
186 return 'uint64_t';
187 if sRef.startswith('u16'):
188 return 'uint16_t';
189 elif ch0 == 'b':
190 return 'uint8_t';
191 elif ch0 == 'f':
192 return 'bool';
193 elif ch0 == 'i':
194 if sRef.startswith('i8'):
195 return 'int8_t';
196 if sRef.startswith('i16'):
197 return 'int32_t';
198 if sRef.startswith('i32'):
199 return 'int32_t';
200 if sRef.startswith('i64'):
201 return 'int64_t';
202 if sRef in ('iReg', 'iGReg', 'iSegReg', 'iSrcReg', 'iDstReg'):
203 return 'uint8_t';
204 elif ch0 == 'p':
205 if sRef.find('-') < 0:
206 return 'uintptr_t';
207 if sRef.startswith('pVCpu->iem.s.'):
208 sField = sRef[len('pVCpu->iem.s.') : ];
209 if sField in g_kdIemFieldToType:
210 if g_kdIemFieldToType[sField][0]:
211 return g_kdIemFieldToType[sField][0];
212 self.raiseProblem('Reference out-of-bounds decoder field: %s' % (sRef,));
213 elif ch0 == 'G' and sRef.startswith('GCPtr'):
214 return 'uint64_t';
215 elif ch0 == 'e':
216 if sRef == 'enmEffOpSize':
217 return 'IEMMODE';
218 elif sRef == 'cShift': ## @todo risky
219 return 'uint8_t';
220
221 self.raiseProblem('Unknown reference: %s' % (sRef,));
222 return None; # Shut up pylint 2.16.2.
223
224 def analyzeMorphStmtForThreaded(self, aoStmts, iParamRef = 0):
225 """
226 Transforms (copy) the statements into those for the threaded function.
227
228 Returns list/tree of statements (aoStmts is not modified) and the new
229 iParamRef value.
230 """
231 #
232 # We'll be traversing aoParamRefs in parallel to the statements, so we
233 # must match the traversal in analyzeFindThreadedParamRefs exactly.
234 #
235 #print('McBlock at %s:%s' % (os.path.split(self.oMcBlock.sSrcFile)[1], self.oMcBlock.iBeginLine,));
236 aoThreadedStmts = [];
237 for oStmt in aoStmts:
238 # Skip C++ statements that is purely related to decoding.
239 if not oStmt.isCppStmt() or not oStmt.fDecode:
240 # Copy the statement. Make a deep copy to make sure we've got our own
241 # copies of all instance variables, even if a bit overkill at the moment.
242 oNewStmt = copy.deepcopy(oStmt);
243 aoThreadedStmts.append(oNewStmt);
244 #print('oNewStmt %s %s' % (oNewStmt.sName, len(oNewStmt.asParams),));
245
246 # If the statement has parameter references, process the relevant parameters.
247 # We grab the references relevant to this statement and apply them in reserve order.
248 if iParamRef < len(self.aoParamRefs) and self.aoParamRefs[iParamRef].oStmt == oStmt:
249 iParamRefFirst = iParamRef;
250 while True:
251 iParamRef += 1;
252 if iParamRef >= len(self.aoParamRefs) or self.aoParamRefs[iParamRef].oStmt != oStmt:
253 break;
254
255 #print('iParamRefFirst=%s iParamRef=%s' % (iParamRefFirst, iParamRef));
256 for iCurRef in range(iParamRef - 1, iParamRefFirst - 1, -1):
257 oCurRef = self.aoParamRefs[iCurRef];
258 if oCurRef.iParam is not None:
259 assert oCurRef.oStmt == oStmt;
260 #print('iCurRef=%s iParam=%s sOrgRef=%s' % (iCurRef, oCurRef.iParam, oCurRef.sOrgRef));
261 sSrcParam = oNewStmt.asParams[oCurRef.iParam];
262 assert sSrcParam[oCurRef.offParam : oCurRef.offParam + len(oCurRef.sOrgRef)] == oCurRef.sOrgRef, \
263 'offParam=%s sOrgRef=%s sSrcParam=%s<eos>' % (oCurRef.offParam, oCurRef.sOrgRef, sSrcParam);
264 oNewStmt.asParams[oCurRef.iParam] = sSrcParam[0 : oCurRef.offParam] \
265 + oCurRef.sNewName \
266 + sSrcParam[oCurRef.offParam + len(oCurRef.sOrgRef) : ];
267
268 # Morph IEM_MC_CALC_RM_EFF_ADDR into IEM_MC_CALC_RM_EFF_ADDR_THREADED ...
269 if oNewStmt.sName == 'IEM_MC_CALC_RM_EFF_ADDR':
270 assert self.sVariation != self.ksVariation_Default;
271 oNewStmt.sName = 'IEM_MC_CALC_RM_EFF_ADDR_THREADED' + self.sVariation.upper();
272 assert len(oNewStmt.asParams) == 3;
273 if self.sVariation == self.ksVariation_Addr16:
274 oNewStmt.asParams = [
275 oNewStmt.asParams[0], oNewStmt.asParams[1], self.dParamRefs['u16Disp'][0].sNewName,
276 ];
277 elif self.sVariation in (self.ksVariation_Addr32, self.ksVariation_Addr32Flat):
278 oNewStmt.asParams = [
279 oNewStmt.asParams[0], oNewStmt.asParams[1], self.dParamRefs['bSib'][0].sNewName,
280 self.dParamRefs['u32Disp'][0].sNewName,
281 ];
282 else:
283 oNewStmt.asParams = [
284 oNewStmt.asParams[0], self.dParamRefs['bRmEx'][0].sNewName, self.dParamRefs['bSib'][0].sNewName,
285 self.dParamRefs['u32Disp'][0].sNewName, self.dParamRefs['cbInstr'][0].sNewName,
286 ];
287 # ... and IEM_MC_ADVANCE_RIP_AND_FINISH into *_THREADED ...
288 elif oNewStmt.sName in ('IEM_MC_ADVANCE_RIP_AND_FINISH', 'IEM_MC_REL_JMP_S8_AND_FINISH',
289 'IEM_MC_REL_JMP_S16_AND_FINISH', 'IEM_MC_REL_JMP_S32_AND_FINISH'):
290 oNewStmt.sName += '_THREADED';
291 oNewStmt.asParams.append(self.dParamRefs['cbInstr'][0].sNewName);
292 # ... and IEM_MC_CALL_CIMPL_[0-5] into *_THREADED ...
293 elif oNewStmt.sName.startswith('IEM_MC_CALL_CIMPL_'):
294 oNewStmt.sName += '_THREADED';
295 oNewStmt.asParams.insert(0, self.dParamRefs['cbInstr'][0].sNewName);
296
297 # Process branches of conditionals recursively.
298 if isinstance(oStmt, iai.McStmtCond):
299 (oNewStmt.aoIfBranch, iParamRef) = self.analyzeMorphStmtForThreaded(oStmt.aoIfBranch, iParamRef);
300 if oStmt.aoElseBranch:
301 (oNewStmt.aoElseBranch, iParamRef) = self.analyzeMorphStmtForThreaded(oStmt.aoElseBranch, iParamRef);
302
303 return (aoThreadedStmts, iParamRef);
304
305 def analyzeConsolidateThreadedParamRefs(self):
306 """
307 Consolidate threaded function parameter references into a dictionary
308 with lists of the references to each variable/field.
309 """
310 # Gather unique parameters.
311 self.dParamRefs = {};
312 for oRef in self.aoParamRefs:
313 if oRef.sStdRef not in self.dParamRefs:
314 self.dParamRefs[oRef.sStdRef] = [oRef,];
315 else:
316 self.dParamRefs[oRef.sStdRef].append(oRef);
317
318 # Generate names for them for use in the threaded function.
319 dParamNames = {};
320 for sName, aoRefs in self.dParamRefs.items():
321 # Morph the reference expression into a name.
322 if sName.startswith('IEM_GET_MODRM_REG'): sName = 'bModRmRegP';
323 elif sName.startswith('IEM_GET_MODRM_RM'): sName = 'bModRmRmP';
324 elif sName.startswith('IEM_GET_MODRM_REG_8'): sName = 'bModRmReg8P';
325 elif sName.startswith('IEM_GET_MODRM_RM_8'): sName = 'bModRmRm8P';
326 elif sName.startswith('IEM_GET_EFFECTIVE_VVVV'): sName = 'bEffVvvvP';
327 elif sName.find('.') >= 0 or sName.find('->') >= 0:
328 sName = sName[max(sName.rfind('.'), sName.rfind('>')) + 1 : ] + 'P';
329 else:
330 sName += 'P';
331
332 # Ensure it's unique.
333 if sName in dParamNames:
334 for i in range(10):
335 if sName + str(i) not in dParamNames:
336 sName += str(i);
337 break;
338 dParamNames[sName] = True;
339
340 # Update all the references.
341 for oRef in aoRefs:
342 oRef.sNewName = sName;
343
344 # Organize them by size too for the purpose of optimize them.
345 dBySize = {} # type: dict(str,str)
346 for sStdRef, aoRefs in self.dParamRefs.items():
347 if aoRefs[0].sType[0] != 'P':
348 cBits = g_kdTypeInfo[aoRefs[0].sType][0];
349 assert(cBits <= 64);
350 else:
351 cBits = 64;
352
353 if cBits not in dBySize:
354 dBySize[cBits] = [sStdRef,]
355 else:
356 dBySize[cBits].append(sStdRef);
357
358 # Pack the parameters as best as we can, starting with the largest ones
359 # and ASSUMING a 64-bit parameter size.
360 self.cMinParams = 0;
361 offNewParam = 0;
362 for cBits in sorted(dBySize.keys(), reverse = True):
363 for sStdRef in dBySize[cBits]:
364 if offNewParam < 64:
365 offNewParam += cBits;
366 else:
367 self.cMinParams += 1;
368 offNewParam = cBits;
369 assert(offNewParam <= 64);
370
371 for oRef in self.dParamRefs[sStdRef]:
372 oRef.iNewParam = self.cMinParams;
373 oRef.offNewParam = offNewParam - cBits;
374
375 if offNewParam > 0:
376 self.cMinParams += 1;
377
378 # Currently there are a few that requires 4 parameters, list these so we can figure out why:
379 if self.cMinParams >= 4:
380 print('debug: cMinParams=%s cRawParams=%s - %s:%d'
381 % (self.cMinParams, len(self.dParamRefs), self.oParent.oMcBlock.sSrcFile, self.oParent.oMcBlock.iBeginLine,));
382
383 return True;
384
385 ksHexDigits = '0123456789abcdefABCDEF';
386
387 def analyzeFindThreadedParamRefs(self, aoStmts):
388 """
389 Scans the statements for things that have to passed on to the threaded
390 function (populates self.aoParamRefs).
391 """
392 for oStmt in aoStmts:
393 # Some statements we can skip alltogether.
394 if isinstance(oStmt, iai.McCppPreProc):
395 continue;
396 if oStmt.isCppStmt() and oStmt.fDecode:
397 continue;
398
399 if isinstance(oStmt, iai.McStmtVar):
400 if oStmt.sConstValue is None:
401 continue;
402 aiSkipParams = { 0: True, 1: True, 3: True };
403 else:
404 aiSkipParams = {};
405
406 # Several statements have implicit parameters.
407 if oStmt.sName in ('IEM_MC_ADVANCE_RIP_AND_FINISH', 'IEM_MC_REL_JMP_S8_AND_FINISH', 'IEM_MC_REL_JMP_S16_AND_FINISH',
408 'IEM_MC_REL_JMP_S32_AND_FINISH', 'IEM_MC_CALL_CIMPL_0', 'IEM_MC_CALL_CIMPL_1',
409 'IEM_MC_CALL_CIMPL_2', 'IEM_MC_CALL_CIMPL_3', 'IEM_MC_CALL_CIMPL_4', 'IEM_MC_CALL_CIMPL_5', ):
410 self.aoParamRefs.append(ThreadedParamRef('cbInstr', 'uint4_t', oStmt));
411
412 if oStmt.sName == 'IEM_MC_CALC_RM_EFF_ADDR':
413 ## @todo figure out how to do this in the input part...
414 if self.sVariation == self.ksVariation_Addr16:
415 self.aoParamRefs.append(ThreadedParamRef('bRm', 'uint8_t', oStmt));
416 self.aoParamRefs.append(ThreadedParamRef('u16Disp', 'uint16_t', oStmt));
417 elif self.sVariation in (self.ksVariation_Addr32, self.ksVariation_Addr32Flat):
418 self.aoParamRefs.append(ThreadedParamRef('bRm', 'uint8_t', oStmt));
419 self.aoParamRefs.append(ThreadedParamRef('bSib', 'uint8_t', oStmt));
420 self.aoParamRefs.append(ThreadedParamRef('u32Disp', 'uint32_t', oStmt));
421 else:
422 assert self.sVariation in (self.ksVariation_Addr64, self.ksVariation_Addr64_32);
423 self.aoParamRefs.append(ThreadedParamRef('bRmEx', 'uint8_t', oStmt));
424 self.aoParamRefs.append(ThreadedParamRef('bSib', 'uint8_t', oStmt));
425 self.aoParamRefs.append(ThreadedParamRef('u32Disp', 'uint32_t', oStmt));
426 self.aoParamRefs.append(ThreadedParamRef('cbInstr', 'uint4_t', oStmt));
427
428 # Inspect the target of calls to see if we need to pass down a
429 # function pointer or function table pointer for it to work.
430 if isinstance(oStmt, iai.McStmtCall):
431 if oStmt.sFn[0] == 'p':
432 self.aoParamRefs.append(ThreadedParamRef(oStmt.sFn, 'uintptr_t', oStmt, oStmt.idxFn));
433 elif ( oStmt.sFn[0] != 'i'
434 and not oStmt.sFn.startswith('IEMTARGETCPU_EFL_BEHAVIOR_SELECT')
435 and not oStmt.sFn.startswith('IEM_SELECT_HOST_OR_FALLBACK') ):
436 self.raiseProblem('Bogus function name in %s: %s' % (oStmt.sName, oStmt.sFn,));
437 aiSkipParams[oStmt.idxFn] = True;
438
439 # Check all the parameters for bogus references.
440 for iParam, sParam in enumerate(oStmt.asParams):
441 if iParam not in aiSkipParams and sParam not in self.oParent.dVariables:
442 # The parameter may contain a C expression, so we have to try
443 # extract the relevant bits, i.e. variables and fields while
444 # ignoring operators and parentheses.
445 offParam = 0;
446 while offParam < len(sParam):
447 # Is it the start of an C identifier? If so, find the end, but don't stop on field separators (->, .).
448 ch = sParam[offParam];
449 if ch.isalpha() or ch == '_':
450 offStart = offParam;
451 offParam += 1;
452 while offParam < len(sParam):
453 ch = sParam[offParam];
454 if not ch.isalnum() and ch != '_' and ch != '.':
455 if ch != '-' or sParam[offParam + 1] != '>':
456 # Special hack for the 'CTX_SUFF(pVM)' bit in pVCpu->CTX_SUFF(pVM)->xxxx:
457 if ( ch == '('
458 and sParam[offStart : offParam + len('(pVM)->')] == 'pVCpu->CTX_SUFF(pVM)->'):
459 offParam += len('(pVM)->') - 1;
460 else:
461 break;
462 offParam += 1;
463 offParam += 1;
464 sRef = sParam[offStart : offParam];
465
466 # For register references, we pass the full register indexes instead as macros
467 # like IEM_GET_MODRM_REG implicitly references pVCpu->iem.s.uRexReg and the
468 # threaded function will be more efficient if we just pass the register index
469 # as a 4-bit param.
470 if ( sRef.startswith('IEM_GET_MODRM')
471 or sRef.startswith('IEM_GET_EFFECTIVE_VVVV') ):
472 offParam = iai.McBlock.skipSpacesAt(sParam, offParam, len(sParam));
473 if sParam[offParam] != '(':
474 self.raiseProblem('Expected "(" following %s in "%s"' % (sRef, oStmt.renderCode(),));
475 (asMacroParams, offCloseParam) = iai.McBlock.extractParams(sParam, offParam);
476 if asMacroParams is None:
477 self.raiseProblem('Unable to find ")" for %s in "%s"' % (sRef, oStmt.renderCode(),));
478 offParam = offCloseParam + 1;
479 self.aoParamRefs.append(ThreadedParamRef(sParam[offStart : offParam], 'uint8_t',
480 oStmt, iParam, offStart));
481
482 # We can skip known variables.
483 elif sRef in self.oParent.dVariables:
484 pass;
485
486 # Skip certain macro invocations.
487 elif sRef in ('IEM_GET_HOST_CPU_FEATURES',
488 'IEM_GET_GUEST_CPU_FEATURES',
489 'IEM_IS_GUEST_CPU_AMD'):
490 offParam = iai.McBlock.skipSpacesAt(sParam, offParam, len(sParam));
491 if sParam[offParam] != '(':
492 self.raiseProblem('Expected "(" following %s in "%s"' % (sRef, oStmt.renderCode(),));
493 (asMacroParams, offCloseParam) = iai.McBlock.extractParams(sParam, offParam);
494 if asMacroParams is None:
495 self.raiseProblem('Unable to find ")" for %s in "%s"' % (sRef, oStmt.renderCode(),));
496 offParam = offCloseParam + 1;
497 while offParam < len(sParam) and (sParam[offParam].isalnum() or sParam[offParam] in '_.'):
498 offParam += 1;
499
500 # Skip constants, globals, types (casts), sizeof and macros.
501 elif ( sRef.startswith('IEM_OP_PRF_')
502 or sRef.startswith('IEM_ACCESS_')
503 or sRef.startswith('IEMINT_')
504 or sRef.startswith('X86_GREG_')
505 or sRef.startswith('X86_SREG_')
506 or sRef.startswith('X86_EFL_')
507 or sRef.startswith('X86_FSW_')
508 or sRef.startswith('X86_FCW_')
509 or sRef.startswith('X86_XCPT_')
510 or sRef.startswith('IEMMODE_')
511 or sRef.startswith('g_')
512 or sRef in ( 'int8_t', 'int16_t', 'int32_t',
513 'INT8_C', 'INT16_C', 'INT32_C', 'INT64_C',
514 'UINT8_C', 'UINT16_C', 'UINT32_C', 'UINT64_C',
515 'UINT8_MAX', 'UINT16_MAX', 'UINT32_MAX', 'UINT64_MAX',
516 'INT8_MAX', 'INT16_MAX', 'INT32_MAX', 'INT64_MAX',
517 'INT8_MIN', 'INT16_MIN', 'INT32_MIN', 'INT64_MIN',
518 'sizeof', 'NOREF', 'RT_NOREF', 'IEMMODE_64BIT',
519 'NIL_RTGCPTR' ) ):
520 pass;
521
522 # Skip certain macro invocations.
523 # Any variable (non-field) and decoder fields in IEMCPU will need to be parameterized.
524 elif ( ( '.' not in sRef
525 and '-' not in sRef
526 and sRef not in ('pVCpu', ) )
527 or iai.McBlock.koReIemDecoderVars.search(sRef) is not None):
528 self.aoParamRefs.append(ThreadedParamRef(sRef, self.analyzeReferenceToType(sRef),
529 oStmt, iParam, offStart));
530 # Number.
531 elif ch.isdigit():
532 if ( ch == '0'
533 and offParam + 2 <= len(sParam)
534 and sParam[offParam + 1] in 'xX'
535 and sParam[offParam + 2] in self.ksHexDigits ):
536 offParam += 2;
537 while offParam < len(sParam) and sParam[offParam] in self.ksHexDigits:
538 offParam += 1;
539 else:
540 while offParam < len(sParam) and sParam[offParam].isdigit():
541 offParam += 1;
542 # Whatever else.
543 else:
544 offParam += 1;
545
546 # Traverse the branches of conditionals.
547 if isinstance(oStmt, iai.McStmtCond):
548 self.analyzeFindThreadedParamRefs(oStmt.aoIfBranch);
549 self.analyzeFindThreadedParamRefs(oStmt.aoElseBranch);
550 return True;
551
552 def analyzeVariation(self, aoStmts):
553 """
554 2nd part of the analysis, done on each variation.
555
556 The variations may differ in parameter requirements and will end up with
557 slightly different MC sequences. Thus this is done on each individually.
558
559 Returns dummy True - raises exception on trouble.
560 """
561 # Now scan the code for variables and field references that needs to
562 # be passed to the threaded function because they are related to the
563 # instruction decoding.
564 self.analyzeFindThreadedParamRefs(aoStmts);
565 self.analyzeConsolidateThreadedParamRefs();
566
567 # Morph the statement stream for the block into what we'll be using in the threaded function.
568 (self.aoStmtsForThreadedFunction, iParamRef) = self.analyzeMorphStmtForThreaded(aoStmts);
569 if iParamRef != len(self.aoParamRefs):
570 raise Exception('iParamRef=%s, expected %s!' % (iParamRef, len(self.aoParamRefs),));
571
572 return True;
573
574
575class ThreadedFunction(object):
576 """
577 A threaded function.
578 """
579
580 def __init__(self, oMcBlock):
581 self.oMcBlock = oMcBlock # type: IEMAllInstructionsPython.McBlock
582 ## Variations for this block. There is at least one.
583 self.aoVariations = [] # type: list(ThreadedFunctionVariation)
584 ## Dictionary of local variables (IEM_MC_LOCAL[_CONST]) and call arguments (IEM_MC_ARG*).
585 self.dVariables = {} # type: dict(str,McStmtVar)
586
587 @staticmethod
588 def dummyInstance():
589 """ Gets a dummy instance. """
590 return ThreadedFunction(iai.McBlock('null', 999999999, 999999999, 'nil', 999999999));
591
592 def raiseProblem(self, sMessage):
593 """ Raises a problem. """
594 raise Exception('%s:%s: error: %s' % (self.oMcBlock.sSrcFile, self.oMcBlock.iBeginLine, sMessage, ));
595
596 def analyzeFindVariablesAndCallArgs(self, aoStmts):
597 """ Scans the statements for MC variables and call arguments. """
598 for oStmt in aoStmts:
599 if isinstance(oStmt, iai.McStmtVar):
600 if oStmt.sVarName in self.dVariables:
601 raise Exception('Variable %s is defined more than once!' % (oStmt.sVarName,));
602 self.dVariables[oStmt.sVarName] = oStmt.sVarName;
603
604 # There shouldn't be any variables or arguments declared inside if/
605 # else blocks, but scan them too to be on the safe side.
606 if isinstance(oStmt, iai.McStmtCond):
607 cBefore = len(self.dVariables);
608 self.analyzeFindVariablesAndCallArgs(oStmt.aoIfBranch);
609 self.analyzeFindVariablesAndCallArgs(oStmt.aoElseBranch);
610 if len(self.dVariables) != cBefore:
611 raise Exception('Variables/arguments defined in conditional branches!');
612 return True;
613
614 def analyze(self):
615 """
616 Analyzes the code, identifying the number of parameters it requires and such.
617
618 Returns dummy True - raises exception on trouble.
619 """
620
621 # Decode the block into a list/tree of McStmt objects.
622 aoStmts = self.oMcBlock.decode();
623
624 # Scan the statements for local variables and call arguments (self.dVariables).
625 self.analyzeFindVariablesAndCallArgs(aoStmts);
626
627 # Create variations if needed.
628 if iai.McStmt.findStmtByNames(aoStmts, {'IEM_MC_CALC_RM_EFF_ADDR' : True,}):
629 self.aoVariations = [ThreadedFunctionVariation(self, sVar)
630 for sVar in ThreadedFunctionVariation.kasVariations_EffAddr];
631 else:
632 self.aoVariations = [ThreadedFunctionVariation(self),];
633
634 # Continue the analysis on each variation.
635 for oVariation in self.aoVariations:
636 oVariation.analyzeVariation(aoStmts);
637
638 return True;
639
640 def generateInputCode(self):
641 """
642 Modifies the input code.
643 """
644 assert len(self.oMcBlock.asLines) > 2, "asLines=%s" % (self.oMcBlock.asLines,);
645 cchIndent = (self.oMcBlock.cchIndent + 3) // 4 * 4;
646 return iai.McStmt.renderCodeForList(self.oMcBlock.aoStmts, cchIndent = cchIndent).replace('\n', ' /* gen */\n', 1);
647
648
649class IEMThreadedGenerator(object):
650 """
651 The threaded code generator & annotator.
652 """
653
654 def __init__(self):
655 self.aoThreadedFuncs = [] # type: list(ThreadedFunction)
656 self.oOptions = None # type: argparse.Namespace
657 self.aoParsers = [] # type: list(IEMAllInstructionsPython.SimpleParser)
658
659 #
660 # Processing.
661 #
662
663 def processInputFiles(self):
664 """
665 Process the input files.
666 """
667
668 # Parse the files.
669 self.aoParsers = iai.parseFiles(self.oOptions.asInFiles);
670
671 # Create threaded functions for the MC blocks.
672 self.aoThreadedFuncs = [ThreadedFunction(oMcBlock) for oMcBlock in iai.g_aoMcBlocks];
673
674 # Analyze the threaded functions.
675 dRawParamCounts = {};
676 dMinParamCounts = {};
677 for oThreadedFunction in self.aoThreadedFuncs:
678 oThreadedFunction.analyze();
679 for oVariation in oThreadedFunction.aoVariations:
680 dRawParamCounts[len(oVariation.dParamRefs)] = dRawParamCounts.get(len(oVariation.dParamRefs), 0) + 1;
681 dMinParamCounts[oVariation.cMinParams] = dMinParamCounts.get(oVariation.cMinParams, 0) + 1;
682 print('debug: param count distribution, raw and optimized:', file = sys.stderr);
683 for cCount in sorted({cBits: True for cBits in list(dRawParamCounts.keys()) + list(dMinParamCounts.keys())}.keys()):
684 print('debug: %s params: %4s raw, %4s min'
685 % (cCount, dRawParamCounts.get(cCount, 0), dMinParamCounts.get(cCount, 0)),
686 file = sys.stderr);
687
688 return True;
689
690 #
691 # Output
692 #
693
694 def generateLicenseHeader(self):
695 """
696 Returns the lines for a license header.
697 """
698 return [
699 '/*',
700 ' * Autogenerated by $Id: IEMAllThreadedPython.py 98969 2023-03-15 00:24:47Z vboxsync $ ',
701 ' * Do not edit!',
702 ' */',
703 '',
704 '/*',
705 ' * Copyright (C) 2023-' + str(datetime.date.today().year) + ' Oracle and/or its affiliates.',
706 ' *',
707 ' * This file is part of VirtualBox base platform packages, as',
708 ' * available from https://www.virtualbox.org.',
709 ' *',
710 ' * This program is free software; you can redistribute it and/or',
711 ' * modify it under the terms of the GNU General Public License',
712 ' * as published by the Free Software Foundation, in version 3 of the',
713 ' * License.',
714 ' *',
715 ' * This program is distributed in the hope that it will be useful, but',
716 ' * WITHOUT ANY WARRANTY; without even the implied warranty of',
717 ' * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU',
718 ' * General Public License for more details.',
719 ' *',
720 ' * You should have received a copy of the GNU General Public License',
721 ' * along with this program; if not, see <https://www.gnu.org/licenses>.',
722 ' *',
723 ' * The contents of this file may alternatively be used under the terms',
724 ' * of the Common Development and Distribution License Version 1.0',
725 ' * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included',
726 ' * in the VirtualBox distribution, in which case the provisions of the',
727 ' * CDDL are applicable instead of those of the GPL.',
728 ' *',
729 ' * You may elect to license modified versions of this file under the',
730 ' * terms and conditions of either the GPL or the CDDL or both.',
731 ' *',
732 ' * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0',
733 ' */',
734 '',
735 '',
736 '',
737 ];
738
739
740 def generateThreadedFunctionsHeader(self, oOut):
741 """
742 Generates the threaded functions header file.
743 Returns success indicator.
744 """
745
746 asLines = self.generateLicenseHeader();
747
748 # Generate the threaded function table indexes.
749 asLines += [
750 'typedef enum IEMTHREADEDFUNCS',
751 '{',
752 ' kIemThreadedFunc_Invalid = 0,',
753 ];
754 for oThreadedFunction in self.aoThreadedFuncs:
755 for oVariation in oThreadedFunction.aoVariations:
756 asLines.append(' ' + oVariation.getIndexName() + ',');
757 asLines += [
758 ' kIemThreadedFunc_End',
759 '} IEMTHREADEDFUNCS;',
760 '',
761 ];
762
763 # Prototype the function table.
764 sFnType = 'typedef IEM_DECL_IMPL_TYPE(VBOXSTRICTRC, FNIEMTHREADEDFUNC, (PVMCPU pVCpu';
765 for iParam in range(g_kcThreadedParams):
766 sFnType += ', uint64_t uParam' + str(iParam);
767 sFnType += '));'
768
769 asLines += [
770 sFnType,
771 'typedef FNIEMTHREADEDFUNC *PFNIEMTHREADEDFUNC;',
772 '',
773 'extern const PFNIEMTHREADEDFUNC g_apfnIemThreadedFunctions[kIemThreadedFunc_End];',
774 ];
775
776 oOut.write('\n'.join(asLines));
777 return True;
778
779 ksBitsToIntMask = {
780 1: "UINT64_C(0x1)",
781 2: "UINT64_C(0x3)",
782 4: "UINT64_C(0xf)",
783 8: "UINT64_C(0xff)",
784 16: "UINT64_C(0xffff)",
785 32: "UINT64_C(0xffffffff)",
786 };
787 def generateThreadedFunctionsSource(self, oOut):
788 """
789 Generates the threaded functions source file.
790 Returns success indicator.
791 """
792
793 asLines = self.generateLicenseHeader();
794 oOut.write('\n'.join(asLines));
795
796 # Prepare the fixed bits.
797 sParamList = '(PVMCPU pVCpu';
798 for iParam in range(g_kcThreadedParams):
799 sParamList += ', uint64_t uParam' + str(iParam);
800 sParamList += '))\n';
801
802 #
803 # Emit the function definitions.
804 #
805 for oThreadedFunction in self.aoThreadedFuncs:
806 oMcBlock = oThreadedFunction.oMcBlock;
807 for oVariation in oThreadedFunction.aoVariations:
808 # Function header
809 oOut.write( '\n'
810 + '\n'
811 + '/**\n'
812 + ' * %s at line %s offset %s in %s%s\n'
813 % (oMcBlock.sFunction, oMcBlock.iBeginLine, oMcBlock.offBeginLine,
814 os.path.split(oMcBlock.sSrcFile)[1],
815 ' (macro expansion)' if oMcBlock.iBeginLine == oMcBlock.iEndLine else '')
816 + ' */\n'
817 + 'static IEM_DECL_IMPL_DEF(VBOXSTRICTRC, ' + oVariation.getFunctionName() + ',\n'
818 + ' ' + sParamList
819 + '{\n');
820
821 aasVars = [];
822 for aoRefs in oVariation.dParamRefs.values():
823 oRef = aoRefs[0];
824 if oRef.sType[0] != 'P':
825 cBits = g_kdTypeInfo[oRef.sType][0];
826 sType = g_kdTypeInfo[oRef.sType][2];
827 else:
828 cBits = 64;
829 sType = oRef.sType;
830
831 sTypeDecl = sType + ' const';
832
833 if cBits == 64:
834 assert oRef.offNewParam == 0;
835 if sType == 'uint64_t':
836 sUnpack = 'uParam%s;' % (oRef.iNewParam,);
837 else:
838 sUnpack = '(%s)uParam%s;' % (sType, oRef.iNewParam,);
839 elif oRef.offNewParam == 0:
840 sUnpack = '(%s)(uParam%s & %s);' % (sType, oRef.iNewParam, self.ksBitsToIntMask[cBits]);
841 else:
842 sUnpack = '(%s)((uParam%s >> %s) & %s);' \
843 % (sType, oRef.iNewParam, oRef.offNewParam, self.ksBitsToIntMask[cBits]);
844
845 sComment = '/* %s - %s ref%s */' % (oRef.sOrgRef, len(aoRefs), 's' if len(aoRefs) != 1 else '',);
846
847 aasVars.append([ '%s:%02u' % (oRef.iNewParam, oRef.offNewParam), sTypeDecl, oRef.sNewName, sUnpack, sComment ]);
848 acchVars = [0, 0, 0, 0, 0];
849 for asVar in aasVars:
850 for iCol, sStr in enumerate(asVar):
851 acchVars[iCol] = max(acchVars[iCol], len(sStr));
852 sFmt = ' %%-%ss %%-%ss = %%-%ss %%s\n' % (acchVars[1], acchVars[2], acchVars[3]);
853 for asVar in sorted(aasVars):
854 oOut.write(sFmt % (asVar[1], asVar[2], asVar[3], asVar[4],));
855
856 # RT_NOREF for unused parameters.
857 if oVariation.cMinParams < g_kcThreadedParams:
858 oOut.write(' RT_NOREF('
859 + ', '.join(['uParam%u' % (i,) for i in range(oVariation.cMinParams, g_kcThreadedParams)])
860 + ');\n');
861
862 # Now for the actual statements.
863 oOut.write(iai.McStmt.renderCodeForList(oVariation.aoStmtsForThreadedFunction, cchIndent = 4));
864
865 oOut.write('}\n');
866
867
868 #
869 # Emit the function table.
870 #
871 oOut.write( '\n'
872 + '\n'
873 + '/**\n'
874 + ' * Function table.\n'
875 + ' */\n'
876 + 'const PFNIEMTHREADEDFUNC g_apfnIemThreadedFunctions[kIemThreadedFunc_End] =\n'
877 + '{\n'
878 + ' /*Invalid*/ NULL, \n');
879 iThreadedFunction = 0;
880 for oThreadedFunction in self.aoThreadedFuncs:
881 for oVariation in oThreadedFunction.aoVariations:
882 iThreadedFunction += 1;
883 oOut.write(' /*%4u*/ %s,\n' % (iThreadedFunction, oVariation.getFunctionName(),));
884 oOut.write('};\n');
885
886 return True;
887
888 def getThreadedFunctionByIndex(self, idx):
889 """
890 Returns a ThreadedFunction object for the given index. If the index is
891 out of bounds, a dummy is returned.
892 """
893 if idx < len(self.aoThreadedFuncs):
894 return self.aoThreadedFuncs[idx];
895 return ThreadedFunction.dummyInstance();
896
897 def findEndOfMcEndStmt(self, sLine, offEndStmt):
898 """
899 Helper that returns the line offset following the 'IEM_MC_END();'.
900 """
901 assert sLine[offEndStmt:].startswith('IEM_MC_END');
902 off = sLine.find(';', offEndStmt + len('IEM_MC_END'));
903 assert off > 0, 'sLine="%s"' % (sLine, );
904 return off + 1 if off > 0 else 99999998;
905
906 def generateModifiedInput(self, oOut):
907 """
908 Generates the combined modified input source/header file.
909 Returns success indicator.
910 """
911 #
912 # File header.
913 #
914 oOut.write('\n'.join(self.generateLicenseHeader()));
915
916 #
917 # ASSUMING that g_aoMcBlocks/self.aoThreadedFuncs are in self.aoParsers
918 # order, we iterate aoThreadedFuncs in parallel to the lines from the
919 # parsers and apply modifications where required.
920 #
921 iThreadedFunction = 0;
922 oThreadedFunction = self.getThreadedFunctionByIndex(0);
923 for oParser in self.aoParsers: # IEMAllInstructionsPython.SimpleParser
924 oOut.write("\n\n/* ****** BEGIN %s ******* */\n" % (oParser.sSrcFile,));
925
926 iLine = 0;
927 while iLine < len(oParser.asLines):
928 sLine = oParser.asLines[iLine];
929 iLine += 1; # iBeginLine and iEndLine are 1-based.
930
931 # Can we pass it thru?
932 if ( iLine not in [oThreadedFunction.oMcBlock.iBeginLine, oThreadedFunction.oMcBlock.iEndLine]
933 or oThreadedFunction.oMcBlock.sSrcFile != oParser.sSrcFile):
934 oOut.write(sLine);
935 #
936 # Single MC block. Just extract it and insert the replacement.
937 #
938 elif oThreadedFunction.oMcBlock.iBeginLine != oThreadedFunction.oMcBlock.iEndLine:
939 assert sLine.count('IEM_MC_') == 1;
940 oOut.write(sLine[:oThreadedFunction.oMcBlock.offBeginLine]);
941 sModified = oThreadedFunction.generateInputCode().strip();
942 oOut.write(sModified);
943
944 iLine = oThreadedFunction.oMcBlock.iEndLine;
945 sLine = oParser.asLines[iLine - 1];
946 assert sLine.count('IEM_MC_') == 1;
947 oOut.write(sLine[self.findEndOfMcEndStmt(sLine, oThreadedFunction.oMcBlock.offEndLine) : ]);
948
949 # Advance
950 iThreadedFunction += 1;
951 oThreadedFunction = self.getThreadedFunctionByIndex(iThreadedFunction);
952 #
953 # Macro expansion line that have sublines and may contain multiple MC blocks.
954 #
955 else:
956 offLine = 0;
957 while iLine == oThreadedFunction.oMcBlock.iBeginLine:
958 oOut.write(sLine[offLine : oThreadedFunction.oMcBlock.offBeginLine]);
959
960 sModified = oThreadedFunction.generateInputCode().strip();
961 assert sModified.startswith('IEM_MC_BEGIN'), 'sModified="%s"' % (sModified,);
962 oOut.write(sModified);
963
964 offLine = self.findEndOfMcEndStmt(sLine, oThreadedFunction.oMcBlock.offEndLine);
965
966 # Advance
967 iThreadedFunction += 1;
968 oThreadedFunction = self.getThreadedFunctionByIndex(iThreadedFunction);
969
970 # Last line segment.
971 if offLine < len(sLine):
972 oOut.write(sLine[offLine : ]);
973
974 oOut.write("/* ****** END %s ******* */\n" % (oParser.sSrcFile,));
975
976 return True;
977
978 #
979 # Main
980 #
981
982 def main(self, asArgs):
983 """
984 C-like main function.
985 Returns exit code.
986 """
987
988 #
989 # Parse arguments
990 #
991 sScriptDir = os.path.dirname(__file__);
992 oParser = argparse.ArgumentParser(add_help = False);
993 oParser.add_argument('asInFiles', metavar = 'input.cpp.h', nargs = '*',
994 default = [os.path.join(sScriptDir, asFM[0]) for asFM in iai.g_aasAllInstrFilesAndDefaultMap],
995 help = "Selection of VMMAll/IEMAllInstructions*.cpp.h files to use as input.");
996 oParser.add_argument('--out-funcs-hdr', metavar = 'file-funcs.h', dest = 'sOutFileFuncsHdr', action = 'store',
997 default = '-', help = 'The output header file for the functions.');
998 oParser.add_argument('--out-funcs-cpp', metavar = 'file-funcs.cpp', dest = 'sOutFileFuncsCpp', action = 'store',
999 default = '-', help = 'The output C++ file for the functions.');
1000 oParser.add_argument('--out-mod-input', metavar = 'file-instr.cpp.h', dest = 'sOutFileModInput', action = 'store',
1001 default = '-', help = 'The output C++/header file for the modified input instruction files.');
1002 oParser.add_argument('--help', '-h', '-?', action = 'help', help = 'Display help and exit.');
1003 oParser.add_argument('--version', '-V', action = 'version',
1004 version = 'r%s (IEMAllThreadedPython.py), r%s (IEMAllInstructionsPython.py)'
1005 % (__version__.split()[1], iai.__version__.split()[1],),
1006 help = 'Displays the version/revision of the script and exit.');
1007 self.oOptions = oParser.parse_args(asArgs[1:]);
1008 print("oOptions=%s" % (self.oOptions,));
1009
1010 #
1011 # Process the instructions specified in the IEM sources.
1012 #
1013 if self.processInputFiles():
1014 #
1015 # Generate the output files.
1016 #
1017 aaoOutputFiles = (
1018 ( self.oOptions.sOutFileFuncsHdr, self.generateThreadedFunctionsHeader ),
1019 ( self.oOptions.sOutFileFuncsCpp, self.generateThreadedFunctionsSource ),
1020 ( self.oOptions.sOutFileModInput, self.generateModifiedInput ),
1021 );
1022 fRc = True;
1023 for sOutFile, fnGenMethod in aaoOutputFiles:
1024 if sOutFile == '-':
1025 fRc = fnGenMethod(sys.stdout) and fRc;
1026 else:
1027 try:
1028 oOut = open(sOutFile, 'w'); # pylint: disable=consider-using-with,unspecified-encoding
1029 except Exception as oXcpt:
1030 print('error! Failed open "%s" for writing: %s' % (sOutFile, oXcpt,), file = sys.stderr);
1031 return 1;
1032 fRc = fnGenMethod(oOut) and fRc;
1033 oOut.close();
1034 if fRc:
1035 return 0;
1036
1037 return 1;
1038
1039
1040if __name__ == '__main__':
1041 sys.exit(IEMThreadedGenerator().main(sys.argv));
1042
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use