4848
4949LOG_LIMIT = 125
5050
51- WASM2JS = False
52-
5351
5452# utilities
5553
@@ -85,10 +83,10 @@ def randomize_pass_debug():
8583IGNORE = '[binaryen-fuzzer-ignore]'
8684
8785
88- def compare (x , y , comment ):
86+ def compare (x , y , context ):
8987 if x != y and x != IGNORE and y != IGNORE :
90- message = '' .join ([a . rstrip () + '\n ' for a in difflib .unified_diff (x .split ( ' \n ' ), y .split ( ' \n ' ), fromfile = 'expected' , tofile = 'actual' )])
91- raise Exception (str ( comment ) + ": Expected to have '%s' == '%s', diff:\n \n %s" % (
88+ message = '' .join ([a + '\n ' for a in difflib .unified_diff (x .splitlines ( ), y .splitlines ( ), fromfile = 'expected' , tofile = 'actual' )])
89+ raise Exception (context + " comparison error, expected to have '%s' == '%s', diff:\n \n %s" % (
9290 x , y ,
9391 message
9492 ))
@@ -111,15 +109,15 @@ def fix_double(x):
111109
112110 # exceptions may differ when optimizing, but an exception should occur. so ignore their types
113111 # also js engines print them out slightly differently
114- return '\n ' .join (map (lambda x : ' *exception*' if 'exception' in x else x , out .split ( ' \n ' )))
112+ return '\n ' .join (map (lambda x : ' *exception*' if 'exception' in x else x , out .splitlines ( )))
115113
116114
117115def fix_spec_output (out ):
118116 out = fix_output (out )
119117 # spec shows a pointer when it traps, remove that
120- out = '\n ' .join (map (lambda x : x if 'runtime trap' not in x else x [x .find ('runtime trap' ):], out .split ( ' \n ' )))
118+ out = '\n ' .join (map (lambda x : x if 'runtime trap' not in x else x [x .find ('runtime trap' ):], out .splitlines ( )))
121119 # https://github.com/WebAssembly/spec/issues/543 , float consts are messed up
122- out = '\n ' .join (map (lambda x : x if 'f32' not in x and 'f64' not in x else '' , out .split ( ' \n ' )))
120+ out = '\n ' .join (map (lambda x : x if 'f32' not in x and 'f64' not in x else '' , out .splitlines ( )))
123121 return out
124122
125123
@@ -133,7 +131,7 @@ def run_vm(cmd):
133131 ]
134132 try :
135133 return run (cmd )
136- except :
134+ except subprocess . CalledProcessError :
137135 output = run_unchecked (cmd )
138136 for issue in known_issues :
139137 if issue in output :
@@ -145,83 +143,152 @@ def run_bynterp(wasm):
145143 return fix_output (run_vm ([in_bin ('wasm-opt' ), wasm , '--fuzz-exec-before' ] + FEATURE_OPTS ))
146144
147145
148- def run_wasm2js (wasm ):
149- wrapper = run ([in_bin ('wasm-opt' ), wasm , '--emit-js-wrapper=/dev/stdout' ] + FEATURE_OPTS )
150- cmd = [in_bin ('wasm2js' ), wasm , '--emscripten' ]
151- if random .random () < 0.5 :
152- cmd += ['-O' ]
153- main = run (cmd + FEATURE_OPTS )
154- with open (os .path .join (options .binaryen_root , 'scripts' , 'wasm2js.js' )) as f :
155- glue = f .read ()
156- with open ('js.js' , 'w' ) as f :
157- f .write (glue )
158- f .write (main )
159- f .write (wrapper )
160- out = fix_output (run_vm ([NODEJS , 'js.js' , 'a.wasm' ]))
161- if 'exception' in out :
162- # exception, so ignoring - wasm2js does not have normal wasm trapping, so opts can eliminate a trap
163- out = IGNORE
164- return out
165-
166-
167- def run_vms (prefix ):
168- wasm = prefix + 'wasm'
169- results = []
170- results .append (run_bynterp (wasm ))
171- results .append (fix_output (run_vm ([os .path .expanduser ('d8' ), prefix + 'js' ] + V8_OPTS + ['--' , wasm ])))
172- if WASM2JS :
173- results .append (run_wasm2js (wasm ))
174-
175- # append to add results from VMs
176- # results += [fix_output(run_vm([os.path.expanduser('d8'), prefix + 'js'] + V8_OPTS + ['--', prefix + 'wasm']))]
177- # results += [fix_output(run_vm([os.path.expanduser('~/.jsvu/jsc'), prefix + 'js', '--', prefix + 'wasm']))]
178- # spec has no mechanism to not halt on a trap. so we just check until the first trap, basically
179- # run(['../spec/interpreter/wasm', prefix + 'wasm'])
180- # results += [fix_spec_output(run_unchecked(['../spec/interpreter/wasm', prefix + 'wasm', '-e', open(prefix + 'wat').read()]))]
181-
182- if len (results ) == 0 :
183- results = [0 ]
184-
185- # NaNs are a source of nondeterminism between VMs; don't compare them
186- if not NANS :
187- first = results [0 ]
188- for i in range (len (results )):
189- compare (first , results [i ], 'comparing between vms at ' + str (i ))
190-
191- return results
146+ # Each test case handler receives two wasm files, one before and one after some changes
147+ # that should have kept it equivalent. It also receives the optimizations that the
148+ # fuzzer chose to run.
149+ class TestCaseHandler :
150+ # If the core handle_pair() method is not overridden, it calls handle_single()
151+ # on each of the pair. That is useful if you just want the two wasms, and don't
152+ # care about their relationship
153+ def handle_pair (self , before_wasm , after_wasm , opts ):
154+ self .handle (before_wasm )
155+ self .handle (after_wasm )
156+
157+
158+ # Run VMs and compare results
159+ class CompareVMs (TestCaseHandler ):
160+ def handle_pair (self , before_wasm , after_wasm , opts ):
161+ run ([in_bin ('wasm-opt' ), before_wasm , '--emit-js-wrapper=a.js' , '--emit-spec-wrapper=a.wat' ])
162+ run ([in_bin ('wasm-opt' ), after_wasm , '--emit-js-wrapper=b.js' , '--emit-spec-wrapper=b.wat' ])
163+ before = self .run_vms ('a.js' , before_wasm )
164+ after = self .run_vms ('b.js' , after_wasm )
165+ self .compare_vs (before , after )
166+
167+ def run_vms (self , js , wasm ):
168+ results = []
169+ results .append (run_bynterp (wasm ))
170+ results .append (fix_output (run_vm (['d8' , js ] + V8_OPTS + ['--' , wasm ])))
171+
172+ # append to add results from VMs
173+ # results += [fix_output(run_vm(['d8', js] + V8_OPTS + ['--', wasm]))]
174+ # results += [fix_output(run_vm([os.path.expanduser('~/.jsvu/jsc'), js, '--', wasm]))]
175+ # spec has no mechanism to not halt on a trap. so we just check until the first trap, basically
176+ # run(['../spec/interpreter/wasm', wasm])
177+ # results += [fix_spec_output(run_unchecked(['../spec/interpreter/wasm', wasm, '-e', open(prefix + 'wat').read()]))]
178+
179+ if len (results ) == 0 :
180+ results = [0 ]
181+
182+ # NaNs are a source of nondeterminism between VMs; don't compare them
183+ if not NANS :
184+ first = results [0 ]
185+ for i in range (len (results )):
186+ compare (first , results [i ], 'CompareVMs at ' + str (i ))
187+
188+ return results
189+
190+ def compare_vs (self , before , after ):
191+ for i in range (len (before )):
192+ compare (before [i ], after [i ], 'CompareVMs at ' + str (i ))
193+ # with nans, we can only compare the binaryen interpreter to itself
194+ if NANS :
195+ break
196+
197+
198+ # Fuzz the interpreter with --fuzz-exec. This tests everything in a single command (no
199+ # two separate binaries) so it's easy to reproduce.
200+ class FuzzExec (TestCaseHandler ):
201+ def handle_pair (self , before_wasm , after_wasm , opts ):
202+ # fuzz binaryen interpreter itself. separate invocation so result is easily fuzzable
203+ run ([in_bin ('wasm-opt' ), before_wasm , '--fuzz-exec' , '--fuzz-binary' ] + opts )
204+
205+
206+ # Check for determinism - the same command must have the same output
207+ class CheckDeterminism (TestCaseHandler ):
208+ def handle_pair (self , before_wasm , after_wasm , opts ):
209+ # check for determinism
210+ run ([in_bin ('wasm-opt' ), before_wasm , '-o' , 'b1.wasm' ] + opts )
211+ run ([in_bin ('wasm-opt' ), before_wasm , '-o' , 'b2.wasm' ] + opts )
212+ assert open ('b1.wasm' ).read () == open ('b2.wasm' ).read (), 'output must be deterministic'
213+
214+
215+ class Wasm2JS (TestCaseHandler ):
216+ def handle_pair (self , before_wasm , after_wasm , opts ):
217+ compare (self .run (before_wasm ), self .run (after_wasm ), 'Wasm2JS' )
218+
219+ def run (self , wasm ):
220+ # TODO: wasm2js does not handle nans precisely, and does not
221+ # handle oob loads etc. with traps, should we use
222+ # FUZZ_OPTS += ['--no-fuzz-nans']
223+ # FUZZ_OPTS += ['--no-fuzz-oob']
224+ # ?
225+ wrapper = run ([in_bin ('wasm-opt' ), wasm , '--emit-js-wrapper=/dev/stdout' ] + FEATURE_OPTS )
226+ cmd = [in_bin ('wasm2js' ), wasm , '--emscripten' ]
227+ if random .random () < 0.5 :
228+ cmd += ['-O' ]
229+ main = run (cmd + FEATURE_OPTS )
230+ with open (os .path .join (options .binaryen_root , 'scripts' , 'wasm2js.js' )) as f :
231+ glue = f .read ()
232+ with open ('js.js' , 'w' ) as f :
233+ f .write (glue )
234+ f .write (main )
235+ f .write (wrapper )
236+ out = fix_output (run_vm ([NODEJS , 'js.js' , 'a.wasm' ]))
237+ if 'exception' in out :
238+ # exception, so ignoring - wasm2js does not have normal wasm trapping, so opts can eliminate a trap
239+ out = IGNORE
240+ return out
241+
242+
243+ class Bysyncify (TestCaseHandler ):
244+ def handle (self , wasm ):
245+ # run normally and run in an async manner, and compare
246+ before = run ([in_bin ('wasm-opt' ), wasm , '--fuzz-exec' ])
247+ # TODO: also something that actually does async sleeps in the code, say
248+ # on the logging commands?
249+ # --remove-unused-module-elements removes the bysyncify intrinsics, which are not valid to call
250+ cmd = [in_bin ('wasm-opt' ), wasm , '--bysyncify' , '--remove-unused-module-elements' , '-o' , 'by.wasm' ]
251+ if random .random () < 0.5 :
252+ cmd += ['--optimize-level=3' ] # TODO: more
253+ run (cmd )
254+ after = run ([in_bin ('wasm-opt' ), 'by.wasm' , '--fuzz-exec' ])
255+ after = '\n ' .join ([line for line in after .splitlines () if '[fuzz-exec] calling $bysyncify' not in line ])
256+ compare (before , after , 'Bysyncify' )
257+
258+
259+ # The global list of all test case handlers
260+ testcase_handlers = [
261+ CompareVMs (),
262+ FuzzExec (),
263+ CheckDeterminism (),
264+ Wasm2JS (),
265+ # TODO Bysyncify(),
266+ ]
192267
193268
269+ # Do one test, given an input file for -ttf and some optimizations to run
194270def test_one (infile , opts ):
195271 randomize_pass_debug ()
196272
197273 bytes = 0
198274
199275 # fuzz vms
200276 # gather VM outputs on input file
201- run ([in_bin ('wasm-opt' ), infile , '-ttf' , '--emit-js-wrapper=a.js' , '--emit-spec-wrapper=a.wat' , '- o' , 'a.wasm' ] + FUZZ_OPTS + FEATURE_OPTS )
277+ run ([in_bin ('wasm-opt' ), infile , '-ttf' , '-o' , 'a.wasm' ] + FUZZ_OPTS + FEATURE_OPTS )
202278 wasm_size = os .stat ('a.wasm' ).st_size
203279 bytes += wasm_size
204280 print ('pre js size :' , os .stat ('a.js' ).st_size , ' wasm size:' , wasm_size )
205- before = run_vms ('a.' )
206281 print ('----------------' )
282+
207283 # gather VM outputs on processed file
208284 run ([in_bin ('wasm-opt' ), 'a.wasm' , '-o' , 'b.wasm' ] + opts + FUZZ_OPTS + FEATURE_OPTS )
209285 wasm_size = os .stat ('b.wasm' ).st_size
210286 bytes += wasm_size
211287 print ('post js size:' , os .stat ('a.js' ).st_size , ' wasm size:' , wasm_size )
212288 shutil .copyfile ('a.js' , 'b.js' )
213- after = run_vms ('b.' )
214- for i in range (len (before )):
215- compare (before [i ], after [i ], 'comparing between builds at ' + str (i ))
216- # with nans, we can only compare the binaryen interpreter to itself
217- if NANS :
218- break
219- # fuzz binaryen interpreter itself. separate invocation so result is easily fuzzable
220- run ([in_bin ('wasm-opt' ), 'a.wasm' , '--fuzz-exec' , '--fuzz-binary' ] + opts + FUZZ_OPTS + FEATURE_OPTS )
221- # check for determinism
222- run ([in_bin ('wasm-opt' ), 'a.wasm' , '-o' , 'b.wasm' ] + opts + FUZZ_OPTS + FEATURE_OPTS )
223- run ([in_bin ('wasm-opt' ), 'a.wasm' , '-o' , 'c.wasm' ] + opts + FUZZ_OPTS + FEATURE_OPTS )
224- assert open ('b.wasm' ).read () == open ('c.wasm' ).read (), 'output must be deterministic'
289+
290+ for testcase_handler in testcase_handlers :
291+ testcase_handler .handle_pair (before_wasm = 'a.wasm' , after_wasm = 'b.wasm' , opts = opts + FUZZ_OPTS + FEATURE_OPTS )
225292
226293 return bytes
227294
@@ -298,12 +365,6 @@ def get_multiple_opt_choices():
298365if not NANS :
299366 FUZZ_OPTS += ['--no-fuzz-nans' ]
300367
301- if WASM2JS :
302- # wasm2js does not handle nans precisely, and does not
303- # handle oob loads etc. with traps
304- FUZZ_OPTS += ['--no-fuzz-nans' ]
305- FUZZ_OPTS += ['--no-fuzz-oob' ]
306-
307368if __name__ == '__main__' :
308369 print ('checking infinite random inputs' )
309370 random .seed (time .time () * os .getpid ())
0 commit comments