1414import argparse , re , subprocess , struct , uuid , os , instparse , reginfo , multiprocessing , itertools
1515from tqdm import tqdm
1616
17+ COLRED = '\033 [91m'
18+ COLGREEN = '\033 [92m'
19+ COLRESET = '\033 [0m'
20+
1721parser = argparse .ArgumentParser (prog = 'asm-tester' )
1822parser .add_argument ('--reference' , dest = 'reference' , required = True , help = 'Path (or executable within PATH) to invoke reference `as`' )
1923parser .add_argument ('--undertest' , dest = 'undertest' , required = True , help = 'Path (or executable within PATH) to invoke for `as`' )
2024parser .add_argument ('--objcopy' , dest = 'objcopy' , required = True , help = 'Path (or executable within PATH) to invoke for `objcopy`' )
21- parser .add_argument ('--chunksize' , dest = 'chunksize' , type = int , default = 128 * 1024 , help = 'Block size (instruction count)' )
2225parser .add_argument ('--instr' , dest = 'instregex' , default = ".*" , help = 'Instructions to emit (a regular expression)' )
2326parser .add_argument ('--threads' , dest = 'nthreads' , type = int , default = 8 , help = 'Number of threads to use' )
2427
@@ -62,6 +65,26 @@ def run_sidebyside(asmfile):
6265
6366 return (same , None , None )
6467
68+ def run_errors (expected_insts , asmfile ):
69+ # These files should produce a ton of errors, each line should be invalid.
70+ objf1 = tmpfile (name = "obj" )
71+
72+ p1 = subprocess .Popen ([args .reference , "-march=allegrex" , "-o" , objf1 , asmfile ],
73+ stdin = subprocess .DEVNULL , stdout = subprocess .DEVNULL , stderr = subprocess .PIPE )
74+
75+ outp1 = p1 .communicate ()
76+ p1 .wait ()
77+ exit_code1 = p1 .poll ()
78+
79+ if exit_code1 != 0 :
80+ # Validate there's error lines
81+ errlog = outp1 [1 ].decode ("ascii" ).strip ()
82+ numerrs = errlog .count ("\n " )
83+ if numerrs == expected_insts :
84+ return (True , None )
85+
86+ return (False , asmfile )
87+
6588def dict_product (indict ):
6689 return (dict (zip (indict .keys (), values )) for values in itertools .product (* indict .values ()))
6790
@@ -94,12 +117,16 @@ def gencombs(instname, variables, elemcnt):
94117 return (dict_product (combos ), subreginfo )
95118
96119# Given a list of immediates generate all their possible values and combinations
97- def genimms (imms ):
120+ def genimms (imms , numel ):
98121 combos = {}
99122 for v , iinfo in imms .items ():
100123 combos [v ] = []
101124 if iinfo .get ("type" , None ) == "enum" :
102- combos [v ] = iinfo ["enum" ]
125+ cs = iinfo ["enum" ]
126+ if isinstance (cs , dict ):
127+ combos [v ] = cs ["0sptq" [numel ]]
128+ else :
129+ combos [v ] = cs
103130 else :
104131 for val in range (iinfo ["minval" ], iinfo ["maxval" ] + 1 ):
105132 combos [v ].append (str (val ))
@@ -133,18 +160,14 @@ def check_overlap(iobj, regcomb, subreginfo):
133160# Aggregate all bits toghether to get a number of instructions to generate
134161print ("Testing %d different instructions!" % len (allinsts ))
135162
136- def process_block (instname , iobj ):
137- if any (k for k , v in iobj .inputs ().items () if v .split (":" )[0 ] not in ["single" , "vector" , "matrix" , "vfpucc" , "gpr" ]):
138- # TODO Support other reg types!
139- print ("Instruction" , instname , "has some unsupported inputs" , iobj .raw_syntax ())
140- return (True , instname , 0 )
163+ def process_block (instname , iobj , validinsts ):
164+ regs = iobj .inputs () | iobj .outputs ()
141165
142- if any (k for k , v in iobj . outputs () .items () if v .split (":" )[0 ] not in ["single" , "vector" , "matrix" , "vfpucc" , "gpr" ]):
166+ if any (k for k , v in regs .items () if v .split (":" )[0 ] not in ["single" , "vector" , "matrix" , "vfpucc" , "gpr" ]):
143167 # TODO Support other reg types!
144- print ("Instruction" , instname , "has some unsupported outputs" , iobj .raw_syntax ())
168+ print ("Instruction" , instname , "has some unsupported inputs/ outputs" , iobj .raw_syntax ())
145169 return (True , instname , 0 )
146170
147- regs = iobj .inputs () | iobj .outputs ()
148171 # No need to allocate CC registers :D
149172 regs = {k :v for k , v in regs .items () if v != "vfpucc" }
150173
@@ -155,50 +178,61 @@ def process_block(instname, iobj):
155178 with open (asmfile , "w" ) as fd :
156179 regit , subreginfo = gencombs (instname , regs , iobj .numelems ())
157180 for varcomb in regit :
158- # Validate that this combination of registers is even valid
159- if not check_overlap (iobj , varcomb , subreginfo ):
160- continue
161-
162181 # Fake one immediate if there are none. Something nicer would be better tho.
163182 imms = iobj .immediates () or {'dummyimm' : {'type' : 'interger' , 'minval' : 0 , 'maxval' : 0 }}
164183
165- for immcomb in genimms (imms ):
184+ for immcomb in genimms (imms , iobj . numelems () ):
166185 istr = iobj .raw_syntax ()
167186 for vname , vval in varcomb .items ():
168187 istr = istr .replace ("%" + vname , vval )
169188 for iname , ival in immcomb .items ():
170189 istr = istr .replace ("%" + iname , ival )
171- fd .write (istr + "\n " )
172- numinsts += 1
173190
174- # Run the disassemblers now!
175- success , ec1 , ec2 = run_sidebyside (asmfile )
176- if not success :
177- return (False , instname , ec1 , ec2 , asmfile )
191+ # Validate that this combination of registers is even valid
192+ if check_overlap (iobj , varcomb , subreginfo ) == validinsts :
193+ fd .write (istr + "\n " )
194+ numinsts += 1
195+
196+ if numinsts > 0 :
197+ # Run the disassemblers now!
198+ if validinsts :
199+ success , ec1 , ec2 = run_sidebyside (asmfile )
200+ if not success :
201+ return (False , instname , ec1 , ec2 , asmfile )
202+ else :
203+ success , outp = run_errors (numinsts , asmfile )
204+ if not success :
205+ return (False , instname , asmfile )
178206
179- # os.unlink(asmfile)
207+ os .unlink (asmfile )
180208 return (True , instname , numinsts )
181209
182210res = []
183211finfo = []
184212with multiprocessing .Pool (processes = args .nthreads ) as executor :
185213 for instname , iobj in allinsts :
186- r = executor .apply_async (process_block , (instname , iobj ))
187- res .append (r )
214+ res . append (( executor .apply_async (process_block , (instname , iobj , True )), True ))
215+ res .append (( executor . apply_async ( process_block , ( instname , iobj , False )), False ) )
188216
189217 executor .close ()
190218
191- totalinsts = 0
192- for r in tqdm (res ):
219+ totalinsts , badinsts = 0 , 0
220+ for r , t in tqdm (res ):
193221 succ , * info = r .get ()
194222 if succ is False :
195223 print (info )
196224 else :
197- totalinsts += info [1 ]
198- finfo .append ("%s : %d instructions" % (info [0 ], info [1 ]))
225+ if t :
226+ totalinsts += info [1 ]
227+ finfo .append (("%s : %d " + COLGREEN + "good instructions" + COLRESET ) % (info [0 ], info [1 ]))
228+ else :
229+ if info [1 ]:
230+ badinsts += info [1 ]
231+ finfo .append (("%s : %d " + COLRED + "bad instructions" + COLRESET ) % (info [0 ], info [1 ]))
199232
200233print ("\n " .join (finfo ))
201234print ("--------------" )
202235print ("Tested a total of %d instructions" % totalinsts )
236+ print ("Validated a total of %d bad instructions" % badinsts )
203237
204238
0 commit comments