tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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()