base_readable.py (9319B)
1 #!/usr/bin/env python3 2 """Bounded actual-base comparison for readable lazy-helper/effect rewrites. 3 4 Two sizes, three fresh-process samples, eight workload families; not a generation 5 acceptance gate. Reference source must be selected explicitly, never relabelled. 6 """ 7 import argparse 8 import hashlib 9 import json 10 from pathlib import Path 11 import random 12 import re 13 import time 14 15 from base_workloads import ROOT, encode, measurements, summarize 16 from bundle_dag import merge_bundles 17 from http_headers import expect, result 18 from fixture_sources import verify 19 from source_order import Runner 20 from stable_bundle import byte_tree 21 22 HELPERS = ['takeWhile', 'dropWhile', 'intercalate', 'joinSuffix', 'splitOnByte', 23 'guardC', 'checkContract', 'listOf', 'foldl', 'equal?', 'pair', 24 'pureM', 'bindM', 'exceptE', 'runM', 'handleM'] 25 DRIVER = '''takeDigits xs = takeWhile digit xs 26 dropSpaces xs = dropWhile (x : equal? x 32) xs 27 joinRecords xs = intercalate ", " xs 28 terminateRecords xs = joinSuffix "\\n" xs 29 splitRecords xs = splitOnByte 10 xs 30 validateBytes xs = checkContract (listOf (guardC "non-ascii" (x : inRange x 0 127))) xs 31 makeBinds xs = foldl (action x : bindM action (acc : pureM (natAdd acc x))) (pureM 0) xs 32 runBinds xs = runM (makeBinds xs) 33 makeEffects xs = foldl (action x : bindM action (acc : exceptE "resume" (natAdd acc x) (next : pureM next))) (pureM 0) xs 34 handleBinds xs = runM (handleM "resume" (value k : k value) (makeEffects xs)) 35 spin x = spin x 36 failedBind x = runM (bindM (exceptE "bad" x (spin x)) (_ : spin x)) 37 ignoredContinuation x = runM (handleM "resume" (value _ : pureM value) (exceptE "resume" x (spin x))) 38 takeTruthy xs = takeWhile (_ : pair t t) xs 39 dropTruthy xs = dropWhile (_ : pair t t) xs 40 guardTruthy x = guardC "bad" (_ : pair t t) x 7 41 guardIgnored x = guardC "bad" (_ : t) (spin x) 7 42 runRaw tree = runM tree 43 handleRaw tree = handleM "wanted" (_ _ : pureM 9) tree 44 ''' 45 EXPORTS = re.findall(r'^(\w+) .*=', DRIVER, re.M) 46 47 48 def cases(size): 49 data = (b'Header: VALUE42\n' * (size // 16 + 1))[:size] 50 rows = [list(data[i:i + 32]) for i in range(0, len(data), 32)] 51 values = list(range(1, 9 if size == 64 else 33)) 52 for name, argument, expected in [ 53 ('takeDigits', list(b'1234567890x' + data), encode(list(b'1234567890'))), 54 ('dropSpaces', list(b' ' * 16 + data), encode(list(data))), 55 ('joinRecords', rows, encode(list(b', '.join(bytes(r) for r in rows)))), 56 ('terminateRecords', rows, encode(list(b''.join(bytes(r) + b'\n' for r in rows)))), 57 ('splitRecords', list(data), encode([list(x) for x in data.split(b'\n')])), 58 ('validateBytes', list(data), result(True, encode(list(data)))), 59 ('runBinds', values, result(True, encode(sum(values)))), 60 ('handleBinds', values, result(True, encode(sum(values)))), 61 ]: 62 yield name, encode(argument), expected 63 64 65 def edges(): 66 for name in ['failedBind', 'ignoredContinuation']: 67 yield name, name, encode(42), result(name == 'ignoredContinuation', encode(42)) 68 yield 'truthy-take', 'takeTruthy', encode([1, 2]), encode([1, 2]) 69 yield 'truthy-drop', 'dropTruthy', encode([1, 2]), encode([]) 70 yield 'truthy-guard', 'guardTruthy', encode(42), result(True, encode(42), encode(7)) 71 yield 'ignored-guard', 'guardIgnored', encode(42), result(False, byte_tree(b'bad'), encode(7)) 72 yield 'failed-contract', 'validateBytes', encode([65, 255, 66]), result(False, byte_tree(b'non-ascii')) 73 for name in ['takeDigits', 'dropSpaces', 'joinRecords', 'terminateRecords', 'splitRecords']: 74 yield 'stem-' + name, name, b'\1\0', b'\2\0\0' 75 for name in ['runRaw', 'handleRaw']: 76 yield 'leaf-' + name, name, b'\0', b'\0' 77 yield 'stem-' + name, name, b'\1\0', b'\1\0' 78 # Every non-0/non-1 opcode has historically used the exception branch. 79 raw = b'\2' + encode(99) + b'\2' + byte_tree(b'other') + b'\2' + encode(42) + b'\0' 80 yield 'unknown-run', 'runRaw', raw, result(False, encode(42)) 81 yield 'unknown-unmatched', 'handleRaw', raw, raw 82 matched = raw.replace(byte_tree(b'other'), byte_tree(b'wanted')) 83 yield 'unknown-matched', 'handleRaw', matched, b'\2\0' + encode(9) 84 for raw in [b'\2\0', b'\2' + encode(1), b'\2' + encode(99)]: 85 # Malformed payloads preserve matchPair's historical Leaf/Stem results. 86 for payload in [b'\0', b'\1\0']: 87 tree = raw + payload 88 expected = result(True, payload) if raw == b'\2\0' else payload 89 yield 'malformed-' + tree.hex(), 'runRaw', tree, expected 90 for i, data in enumerate([b'', b'\n', b'\n\n', b'a\n', b'\na']): 91 yield 'split-edge-' + str(i), 'splitRecords', encode(list(data)), encode([list(x) for x in data.split(b'\n')]) 92 93 94 class BoundedRunner(Runner): 95 def run(self, label, args, seconds=20): 96 assert time.monotonic() - self.started < 300, 'five-minute batch guard' 97 return super().run(label, args, min(seconds, 60)) 98 99 100 def main(): 101 p = argparse.ArgumentParser(description=__doc__) 102 p.add_argument('tricu', type=Path) 103 p.add_argument('--compiler', type=Path, required=True) 104 p.add_argument('--reference-source', type=Path, required=True) 105 p.add_argument('--directory', type=Path, required=True) 106 args = p.parse_args() 107 root = args.directory.resolve();root.mkdir(parents=True, exist_ok=False) 108 verify(ROOT / 'test/fixtures') 109 (root / 'harness.py').write_bytes(Path(__file__).read_bytes()) 110 runner = BoundedRunner(args.tricu, root) 111 tool = runner.imported(args.compiler, 'compiler') 112 pins = {}; identities = { 113 'compiler_sha256': hashlib.sha256(args.compiler.read_bytes()).hexdigest(), 114 'runtime_sha256': hashlib.sha256(args.tricu.read_bytes()).hexdigest(), 115 'fixture_sha256': hashlib.sha256((ROOT / 'test/fixtures/compiler-library.arboricx').read_bytes()).hexdigest(), 116 'harness_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest()} 117 for variant, source in [('reference', args.reference_source), ('candidate', ROOT / 'lib/base.tri')]: 118 path = root / (variant + '.tri');path.write_bytes(source.read_bytes()) 119 bundle = root / (variant + '.arboricx') 120 runner.run(variant + '-compile', ['compile', '--file', path, '--toolchain', tool, 121 '--tool-store', runner.store, '--store', runner.store, '--output', bundle, '--compile-timeout', '60'], 60) 122 support = root / (variant + '-support.arboricx') 123 merge_bundles(support, [(bundle, [(n, n) for n in HELPERS]), 124 (ROOT / 'test/fixtures/compiler-library.arboricx', [('native_math.' + n, alias) 125 for n, alias in [('digit', 'digit'), ('inRange', 'inRange'), ('add', 'natAdd')]])], prune_unreachable=True) 126 reply = runner.run(variant + '-support', ['import', '--file', support, '--store', runner.store]) 127 dep = re.search(r'Created module alias .* -> ([0-9a-f]{64})', reply)[1] 128 driver = root / (variant + '-driver.tri') 129 driver.write_text('!import ' + dep + ' *\n' + ''.join('!export ' + n + '\n' for n in EXPORTS) + DRIVER) 130 output = root / (variant + '-driver.arboricx') 131 runner.run(variant + '-driver', ['compile', '--file', driver, '--toolchain', tool, 132 '--tool-store', runner.store, '--store', runner.store, '--output', output, '--compile-timeout', '60'], 60) 133 pins[variant] = runner.imported(output, variant + '-import') 134 identities[variant] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in [path, bundle, driver, output]} 135 (root / 'identities.json').write_text(json.dumps(identities, indent=2) + '\n') 136 edge_count = 0 137 for label, name, argument, expected in edges(): 138 path = root / (label + '.input');path.write_bytes(argument) 139 for variant, pin in pins.items(): 140 expect(runner.invoke(variant + '-' + label, pin, name, ['--arg-tree-file', path]), expected, label) 141 edge_count += 1 142 rows = []; randomizer = random.Random(1010) 143 for size in [64, 1024]: 144 for name, argument, expected in cases(size): 145 path = root / f'{name}-{size}.input';path.write_bytes(argument) 146 for repetition in range(4): 147 order = list(pins);randomizer.shuffle(order) 148 for variant in order: 149 label = f'{variant}-{name}-{size}-{repetition}' 150 expect(runner.invoke(label, pins[variant], name, ['--arg-tree-file', path]), expected, label) 151 rows.append(dict(variant=variant, workload=name, size=size, warmup=repetition == 0, 152 repetition=repetition, **measurements(root / (label + '.log')), 153 seconds=runner.records[-1]['seconds'], peak_kib=runner.records[-1]['peak_kib'])) 154 (root / 'results.json').write_text(json.dumps(rows, indent=2) + '\n') 155 # Same arity, wrong behavior must fail the independent prefix oracle. 156 path = root / 'negative.input';path.write_bytes(encode(list(b'123x'))) 157 assert runner.invoke('negative', pins['candidate'], 'dropSpaces', ['--arg-tree-file', path]) != encode(list(b'123')) 158 (root / 'summary.json').write_text(json.dumps(summarize(rows), indent=2) + '\n') 159 print(f'PASS {edge_count} edge checks, {len(rows)} workload calls, wrong-subject rejected') 160 161 162 if __name__ == '__main__': 163 main()