tricu

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

numeric_batch.py (10505B)


      1 #!/usr/bin/env python3
      2 """Opt-in numeric candidates: independent Python oracles, no fixture publication."""
      3 import argparse
      4 import hashlib
      5 import json
      6 from pathlib import Path
      7 import random
      8 import re
      9 import time
     10 
     11 from collections_batch import ProbeRunner, audit_workspace, check_surface
     12 from base_workloads import encode as n
     13 from bundle_dag import merge_bundles
     14 from fixture_sources import verify
     15 from http_headers import LEAF, TRUE, pair, result, byte_tree, tree_list, expect
     16 from native_library_migration import digest
     17 from bootstrap_generations import source_map, snapshot
     18 
     19 ROOT = Path(__file__).resolve().parent.parent
     20 REFERENCE_FIXTURES = ROOT/'test/fixtures'
     21 SOURCES = dict(base='lib/base.tri', bytes='lib/bytes.tri', conversions='lib/conversions.tri',
     22                prelude='lib/prelude.tri', native_math='lib/math/native.tri')
     23 DRIVER = '''!import prelude as B
     24 !import native_math as N
     25 !export *
     26 spin x = spin x
     27 nativeBinary (a, b) = [(N.add a b) (N.subtract a b) (N.multiply a b)
     28   (N.compare a b) (N.equal a b) (N.lt a b) (N.lte a b) (N.gt a b) (N.gte a b)
     29   (N.minimum a b) (N.maximum a b) (N.divMod a b) (N.quotient a b) (N.remainder a b)]
     30 nativeBinary _ = t
     31 wrongBinary (a, b) = nativeBinary (t b a)
     32 wrongBinary _ = t
     33 checkedBinary (a, b) = [(B.add a b) (B.sub a b) (B.mul a b)
     34   (B.lt? a b) (B.lte? a b) (B.gt? a b) (B.gte? a b)
     35   (B.min a b) (B.max a b) (B.divMod a b) (B.div a b) (B.mod a b)]
     36 checkedBinary _ = t
     37 unary x = [(N.successor x) (N.predecessor x) (N.zero x) (N.odd x) (N.even x)
     38   (B.succ x) (B.pred x) (B.isZero? x) (B.odd? x) (B.even? x) (N.decimal x 7) (N.decimal x 0) (N.decimal x 9)]
     39 classes x = [(N.ascii x) (N.digit x) (N.letter x) (N.lowerHex x) (N.printable x) (N.inRange x 0 0) (N.inRange x 0 255) (N.inRange x 10 5)]
     40 powers (a, b) = [(N.power a b) (B.pow a b)]
     41 powers _ = t
     42 format x = B.showNumber x
     43 badFirst x = [(B.add x (spin x)) (B.div x (spin x)) (B.pow x (spin x)) (B.showNumber x)]
     44 badSecond x = [(B.add 2 x) (B.mul 2 x) (B.div 2 x) (B.mod 2 x) (B.divMod 2 x) (B.pow 2 x)]
     45 zeroPowerIgnoresBase x = N.power (spin x) 0
     46 zeroQuotientIgnoresDividend x = N.quotient (spin x) 0
     47 invalidExponent x = B.pow 2 x
     48 '''
     49 
     50 
     51 def boolean(value):
     52     return TRUE if value else LEAF
     53 
     54 
     55 def error(message):
     56     return result(False, byte_tree(message.encode()))
     57 
     58 
     59 def cases():
     60     rows = []
     61     def add(name, argument, expected):
     62         rows.append((f'{name}-{len(rows)}', name, argument, expected))
     63     rng = random.Random(83471)
     64     pairs = [(a,b) for a in range(10) for b in range(10)]
     65     for bits in [4, 8, 16, 32, 64, 128]:
     66         p = 1 << bits
     67         pairs += [(p-1, p), (p, p-1), (p+1, p), (p+1, 1), (p, 10), (p, p), (0, p)]
     68         pairs += [(rng.getrandbits(bits), rng.getrandbits(bits)) for _ in range(4)]
     69     for a,b in pairs:
     70         q,r = divmod(a,b) if b else (0,a)
     71         order = LEAF if a==b else TRUE if a<b else pair(LEAF,LEAF)
     72         arithmetic = [n(a+b),n(max(a-b,0)),n(a*b)]
     73         comparisons = [boolean(a<b),boolean(a<=b),boolean(a>b),boolean(a>=b)]
     74         limits = [n(min(a,b)),n(max(a,b))]
     75         qr = [pair(n(q),n(r)),n(q),n(r)]
     76         add('nativeBinary', pair(n(a),n(b)), tree_list(arithmetic+[order,boolean(a==b)]+comparisons+limits+qr))
     77         add('checkedBinary', pair(n(a),n(b)), tree_list(arithmetic+comparisons+limits+(qr if b else [error('non-zero')]*3)))
     78     for x in [0,1,2,3,9,10,99,100,255,256,1023,1024,2**32-1,2**64,2**127-1]:
     79         values = [n(x+1),n(max(0,x-1)),boolean(x==0),boolean(x%2),boolean(x%2==0)]
     80         add('unary', n(x), tree_list(values+values+[n(x*10+7),n(x*10),n(x*10+9)]))
     81     for x in range(258):
     82         add('classes', n(x), tree_list([boolean(p) for p in
     83             [x<=127,48<=x<=57,65<=x<=90 or 97<=x<=122,48<=x<=57 or 97<=x<=102,32<=x<=126,x==0,x<=255,False]]))
     84     for a,b in [(a,b) for a in range(6) for b in range(9)]+[(65537,16),(2,255),(0,2**64),(1,2**64)]:
     85         add('powers', pair(n(a),n(b)), n([a**b,a**b]))
     86     for x in [0,1,9,10,11,99,100,101,999,1000,1001,255,256,2**64-1,10**30,10**30-1,2**255-1]:
     87         add('format', n(x), byte_tree(str(x).encode()))
     88     for x in [TRUE,pair(LEAF,LEAF),pair(TRUE,pair(LEAF,LEAF)),pair(pair(LEAF,LEAF),LEAF)]:
     89         add('badFirst', x, tree_list([error('not a natural number')]*4))
     90         add('badSecond', x, tree_list([error('not a natural number')]*2+[error('non-zero')]*3+[error('not a natural number')]))
     91     add('zeroPowerIgnoresBase', LEAF, n(1))
     92     add('zeroQuotientIgnoresDividend', LEAF, n(0))
     93     return rows
     94 
     95 
     96 def screen(runner, root, modules):
     97     baseline = REFERENCE_FIXTURES/'compiler-library.arboricx'
     98     bundle = root/'baseline.arboricx'
     99     exports = [('base.add','add'),('base.div','div'),('conversions.showNumber','format'),
    100                ('native_math.add','nativeAdd'),('native_math.multiply','nativeMultiply'),('native_math.compare','nativeCompare')]
    101     merge_bundles(bundle, [(baseline, exports)], prune_unreachable=True)
    102     old = runner.imported(bundle,'baseline')
    103     samples = []
    104     jobs = [(name, values, expected, module, symbol) for size in [255,4095] for name,values,expected,module,symbol in
    105             [('add',[size,37],n(size+37),'base','add'),('div',[size,7],n(size//7),'base','div'),
    106              ('format',[size],byte_tree(str(size).encode()),'conversions','showNumber')]]
    107     for bits in [16,128]:
    108         a,b = (1<<bits)-1,1<<(bits-1)
    109         jobs += [('nativeAdd',[a,b],n(a+b),'native_math','add'),
    110                  ('nativeMultiply',[a,b],n(a*b),'native_math','multiply'),
    111                  ('nativeCompare',[a,b],pair(LEAF,LEAF),'native_math','compare')]
    112     for job,(name,values,expected,module,symbol) in enumerate(jobs):
    113         for repeat in range(3):
    114             for role in (['reference','candidate'] if repeat%2==0 else ['candidate','reference']):
    115                 pin = old if role=='reference' else modules[module]
    116                 export = name if role=='reference' else symbol
    117                 argv=[]
    118                 for i,v in enumerate(values):
    119                     f=root/f'screen-{i}.tree';f.write_bytes(n(v));argv += ['--arg-tree-file',f]
    120                 label=f'screen-{job}-{role}-{repeat}'
    121                 expect(runner.invoke(label,pin,export,argv),expected,label)
    122                 record=runner.records[-1]
    123                 samples.append(dict(operation=name,arguments=values,role=role,warmup=repeat==0,
    124                     seconds=record['invoke_seconds'],allocated_bytes=record['allocated_bytes']))
    125     for name in ['div']:
    126         argv=[]
    127         for i,v in enumerate([7,0]):
    128             f=root/f'negative-{i}.tree';f.write_bytes(n(v));argv += ['--arg-tree-file',f]
    129         assert runner.invoke('historical-zero-'+name,old,name,argv)!=error('non-zero')
    130 
    131     return samples
    132 
    133 
    134 def main():
    135     global REFERENCE_FIXTURES
    136     p=argparse.ArgumentParser(description=__doc__)
    137     p.add_argument('tricu',type=Path);p.add_argument('--compiler',type=Path,required=True)
    138     p.add_argument('--directory',type=Path,required=True);p.add_argument('--resume',action='store_true')
    139     p.add_argument('--seconds',type=int,default=900)
    140     p.add_argument('--reference-fixtures',type=Path,required=True,help='authenticated pre-rewrite fixtures')
    141     args=p.parse_args()
    142     REFERENCE_FIXTURES = args.reference_fixtures.resolve()
    143     import collections_batch
    144     collections_batch.REFERENCE_FIXTURES = REFERENCE_FIXTURES
    145     root=args.directory.resolve();root.mkdir(parents=True,exist_ok=args.resume)
    146     workspace=root/'workspace';workspace.mkdir(exist_ok=args.resume)
    147     verify(REFERENCE_FIXTURES)
    148     text,entries,required=source_map(ROOT/'bootstrap/compiler-source-map.v1',ROOT,'source_map')
    149     assert set(entries)==required
    150     report=dict(complete=False,map=snapshot(text,entries,ROOT),cases=[],
    151                 compiler_sha256=hashlib.sha256(args.compiler.read_bytes()).hexdigest(),
    152                 runtime_sha256=hashlib.sha256(args.tricu.read_bytes()).hexdigest(),
    153                 harness_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest())
    154     (root/'harness.py').write_bytes(Path(__file__).read_bytes())
    155     for alias,path in SOURCES.items():
    156         (workspace/(alias+'.tri')).write_bytes((ROOT/path).read_bytes())
    157     (workspace/'driver.tri').write_text(DRIVER)
    158     runner=ProbeRunner(args.tricu,root,args.seconds)
    159     try:
    160         tool=runner.imported(args.compiler,'tool');bundle=root/'driver.arboricx'
    161         runner.run('compile',['compile','--file',workspace/'driver.tri','--toolchain',tool,
    162             '--tool-store',runner.store,'--store',runner.store,'--output',bundle,'--compile-timeout','240'],270)
    163         pin=runner.imported(bundle,'driver')
    164         for label,name,argument,expected in cases():
    165             f=root/'argument.tree';f.write_bytes(argument)
    166             actual=runner.invoke(label,pin,name,['--arg-tree-file',f]);expect(actual,expected,label)
    167             report['cases'].append(dict(label=label,input_sha256=hashlib.sha256(argument).hexdigest(),result_sha256=hashlib.sha256(actual).hexdigest()))
    168         f=root/'argument.tree';f.write_bytes(pair(n(2),n(3)))
    169         expected = next(expected for _,name,arg,expected in cases() if name=='nativeBinary' and arg==pair(n(2),n(3)))
    170         assert runner.invoke('wrong-subject',pin,'wrongBinary',['--arg-tree-file',f])!=expected
    171         tool_payload=(runner.store/'objects'/tool[:3]/tool).read_bytes()
    172         assert digest('arboricx.module-manifest.v1',tool_payload)==tool
    173         compiler=next(row.split('\t')[3] for row in tool_payload.decode().splitlines()[1:] if row.split('\t')[1]=='sourceBuildGraph')
    174         report['modules']=audit_workspace(workspace,runner.store,compiler)
    175         native=runner.run('native-driver',['import','--file',bundle,'--store',runner.store])
    176         assert re.search(r'Created module alias .* -> ([0-9a-f]{64})',native)[1]==report['modules']['driver']['manifest']
    177         report['bundle_sha256']=hashlib.sha256(bundle.read_bytes()).hexdigest()
    178         report['removed_exports']=check_surface(report['modules'])
    179         report['screen']=screen(runner,root,{name:row['manifest'] for name,row in report['modules'].items()})
    180         for alias,path in SOURCES.items():
    181             assert (workspace/(alias+'.tri')).read_bytes()==(ROOT/path).read_bytes()
    182         report.update(complete=True,wrong_subject_rejected=True,seconds=time.monotonic()-runner.started)
    183         print(f'PASS: {len(report["cases"])} numeric/format/domain cases; {report["seconds"]:.2f}s')
    184     finally:
    185         (root/'report.json').write_text(json.dumps(report,indent=2)+'\n')
    186 
    187 if __name__=='__main__':
    188     main()