From a1afe47ed4c3f05bb531bf957a8d7cca978c5feb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 27 Feb 2024 13:58:34 -0800 Subject: [PATCH] Fuzzer: Separate arguments used to make the fuzz wasm from the opts we run on it (#6357) Before FUZZ_OPTS was used both when doing --translate-to-fuzz/-ttf to generate the wasm from the random bytes and also when later running optimizations to generate a second wasm file for comparison. That is, we ended up doing this, if the opts were -O3: wasm-opt random.input -ttf -o a.wasm -O3 wasm-opt a.wasm -O3 -o b.wasm Now we have a pair a.wasm,b.wasm which we can test. However, we have run -O3 on both which is a little silly - the second -O3 might not actually have anything left to do, which would mean we compare the same wasm to itself. Worse, this is incorrect, as there are things we need to do only during the generation phase, like --denan. We need that in order to generate a valid wasm to test on, but it is "destructive" in itself: when removing NaNs (to avoid nondeterminism) if replaces them with 0, which is different. As a result, running --denan when generating the second wasm from the first could lead to different execution in them. This was always a problem, but became more noticable recently now that DeNaN modifies SIMD operations, as one optimization we do is to replace a memory.copy with v128.load + v128.store, and --denan will make sure the loaded value has no NaNs... To fix this, separate the generation and optimization phase. Instead of wasm-opt random.input -ttf -o a.wasm --denan -O3 wasm-opt a.wasm --denan -O3 -o b.wasm (note how --denan -O3 appears twice), do this: wasm-opt random.input -ttf -o a.wasm --denan wasm-opt a.wasm -O3 -o b.wasm (note how --denan appears in generation, and -O3 in optimization). --- scripts/fuzz_opt.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 6705d79526c..bc12bdbe08b 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -174,8 +174,13 @@ def update_feature_opts(wasm): def randomize_fuzz_settings(): + # a list of the arguments to pass to wasm-opt -ttf when generating the wasm + global GEN_ARGS + GEN_ARGS = [] + # a list of the optimizations to run on the wasm global FUZZ_OPTS + FUZZ_OPTS = [] # a boolean whether NaN values are allowed, or we de-NaN them global NANS @@ -186,20 +191,19 @@ def randomize_fuzz_settings(): # a boolean whether we legalize the wasm for JS global LEGALIZE - FUZZ_OPTS = [] if random.random() < 0.5: NANS = True else: NANS = False - FUZZ_OPTS += ['--denan'] + GEN_ARGS += ['--denan'] if random.random() < 0.5: OOB = True else: OOB = False - FUZZ_OPTS += ['--no-fuzz-oob'] + GEN_ARGS += ['--no-fuzz-oob'] if random.random() < 0.5: LEGALIZE = True - FUZZ_OPTS += ['--legalize-and-prune-js-interface'] + GEN_ARGS += ['--legalize-and-prune-js-interface'] else: LEGALIZE = False @@ -209,6 +213,10 @@ def randomize_fuzz_settings(): # https://github.com/WebAssembly/binaryen/pull/5665 # https://github.com/WebAssembly/binaryen/issues/5599 if '--disable-gc' not in FEATURE_OPTS: + GEN_ARGS += ['--dce'] + + # Add --dce not only when generating the original wasm but to the + # optimizations we use to create any other wasm file. FUZZ_OPTS += ['--dce'] print('randomized settings (NaNs, OOB, legalize):', NANS, OOB, LEGALIZE) @@ -1267,7 +1275,7 @@ def handle(self, wasm): second_input = abspath('second_input.dat') make_random_input(second_size, second_input) second_wasm = abspath('second.wasm') - run([in_bin('wasm-opt'), second_input, '-ttf', '-o', second_wasm] + FUZZ_OPTS + FEATURE_OPTS) + run([in_bin('wasm-opt'), second_input, '-ttf', '-o', second_wasm] + GEN_ARGS + FEATURE_OPTS) # sometimes also optimize the second module if random.random() < 0.5: @@ -1364,14 +1372,14 @@ def test_one(random_input, given_wasm): # wasm had applied. that is, we need to preserve properties like not # having nans through reduction. try: - run([in_bin('wasm-opt'), given_wasm, '-o', abspath('a.wasm')] + FUZZ_OPTS + FEATURE_OPTS) + run([in_bin('wasm-opt'), given_wasm, '-o', abspath('a.wasm')] + GEN_ARGS + FEATURE_OPTS) except Exception as e: print("Internal error in fuzzer! Could not run given wasm") raise e else: # emit the target features section so that reduction can work later, # without needing to specify the features - generate_command = [in_bin('wasm-opt'), random_input, '-ttf', '-o', abspath('a.wasm')] + FUZZ_OPTS + FEATURE_OPTS + generate_command = [in_bin('wasm-opt'), random_input, '-ttf', '-o', abspath('a.wasm')] + GEN_ARGS + FEATURE_OPTS if INITIAL_CONTENTS: generate_command += ['--initial-fuzz=' + INITIAL_CONTENTS] if PRINT_WATS: @@ -1385,7 +1393,7 @@ def test_one(random_input, given_wasm): print('pre wasm size:', wasm_size) update_feature_opts('a.wasm') - # create a second wasm for handlers that want to look at pairs. + # create a second (optimized) wasm for handlers that want to look at pairs. generate_command = [in_bin('wasm-opt'), abspath('a.wasm'), '-o', abspath('b.wasm')] + opts + FUZZ_OPTS + FEATURE_OPTS if PRINT_WATS: printed = run(generate_command + ['--print'])