From 5cc19c36f773d18c937c6c606e1aa38968334a20 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 4 Sep 2026 18:08:54 +0000 Subject: [PATCH] Parallelize initial content wasm test conversion with ThreadPool - Convert initial wasm tests in memory using ThreadPool and stdin/stdout piping - Add add_bytes_to_tar helper and write initial/num.txt in memory without disk I/O - Stream initial content directly into the tar archive with in-place progress output Reduces test_cluster_fuzz test runtime from ~265s to ~76s (~3.5x faster), with bundling time dropping from ~125s to ~24s (~5x faster). --- scripts/bundle_clusterfuzz.py | 59 +++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/scripts/bundle_clusterfuzz.py b/scripts/bundle_clusterfuzz.py index 96940af3c47..6ad05f0401f 100755 --- a/scripts/bundle_clusterfuzz.py +++ b/scripts/bundle_clusterfuzz.py @@ -70,10 +70,13 @@ """ import glob +import io import os import subprocess import sys import tarfile +import time +from multiprocessing.pool import ThreadPool # Read the filenames first, as importing |shared| changes the directory. output_file = os.path.abspath(sys.argv[1]) @@ -115,6 +118,29 @@ '--disable-relaxed-atomics', ] + +def process_initial_test(test): + results = [] + if not fuzzing.is_fuzzable(test): + return results + for wast, _asserts in support.split_wast(test): + if not wast: + continue + input_bytes = wast.encode('utf-8') if isinstance(wast, str) else wast + cmd = shared.WASM_OPT + ['-q', '-', '-o', '-'] + features + proc = subprocess.run(cmd, input=input_bytes, capture_output=True) + if proc.returncode == 0 and proc.stdout: + results.append(proc.stdout) + return results + + +def add_bytes_to_tar(tar, name, data, mtime): + tarinfo = tarfile.TarInfo(name=name) + tarinfo.size = len(data) + tarinfo.mtime = mtime + tar.addfile(tarinfo, io.BytesIO(data)) + + # Use fast compression (level 1) to speed up bundling with only a modest size increase. with tarfile.open(output_file, 'w:gz', compresslevel=1) as tar: # run.py @@ -154,35 +180,22 @@ # Add tests we will use as initial content under initial/. We put all the # tests from the test suite there. print(' .. initial content: ') - temp_wasm = 'temp.wasm' index = 0 + mtime = int(time.time()) all_tests = shared.get_all_tests() - for i, test in enumerate(all_tests): - if not fuzzing.is_fuzzable(test): - continue - for wast, _asserts in support.split_wast(test): - if not wast: - continue - support.write_wast(temp_wasm, wast) - # If the file is not valid for our features, skip it. In the same - # operation, also convert to binary if this was text (binary is more - # compact). - cmd = shared.WASM_OPT + ['-q', temp_wasm, '-o', temp_wasm] + features - if subprocess.run(cmd, stderr=subprocess.PIPE).returncode: - continue - - # Looks good. - tar.add(temp_wasm, arcname=f'initial/{index}.wasm') - index += 1 - print(f'\r {100 * i / len(all_tests):.2f}%', end='', flush=True) + worker_count = os.cpu_count() or 1 + with ThreadPool(processes=worker_count) as pool: + for i, wasm_list in enumerate(pool.imap(process_initial_test, all_tests)): + for wasm_bytes in wasm_list: + add_bytes_to_tar(tar, f'initial/{index}.wasm', wasm_bytes, mtime) + index += 1 + if i % 20 == 0 or i == len(all_tests) - 1: + print(f'\r {100 * (i + 1) / len(all_tests):.2f}%', end='', flush=True) print(f' (num: {index})') # Write initial/num.txt which contains the number of testcases in that # directory (saves run.py from needing to listdir each time). - num_txt = 'num.txt' - with open(num_txt, 'w') as f: - f.write(f'{index}') - tar.add(num_txt, arcname='initial/num.txt') + add_bytes_to_tar(tar, 'initial/num.txt', str(index).encode(), mtime) print('Done.')