tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

prepare_compiler_kit.py (6439B)


      1 #!/usr/bin/env python3
      2 """Compile a source-mapped worker/adapter kit with an explicitly selected compiler.
      3 
      4 No retained flat support facade, inferred framing, fallback or generation claim.
      5 The map and output IO version are caller assertions; actual generation execution
      6 and independent acceptance remain separate gates.
      7 """
      8 import argparse
      9 import hashlib
     10 import json
     11 from pathlib import Path
     12 import re
     13 import time
     14 
     15 from bootstrap_generations import source_map
     16 from bootstrap_inputs import load_kit
     17 from generation_backend import BACKENDS, DEFAULT_BACKEND
     18 from generation_io import cli_version, compiler_flags
     19 from process_rss import run_guarded
     20 from rts_metrics import parse_rts_stats
     21 
     22 ROOT = Path(__file__).resolve().parent.parent
     23 
     24 
     25 def digest(data):
     26     return hashlib.sha256(data).hexdigest()
     27 
     28 
     29 def project(mapping, source_root, io_version, backend):
     30     text, entries, _ = source_map(mapping, source_root, 'source_map')
     31     if io_version != 2 or isinstance(io_version, bool):
     32         raise ValueError('current source kit requires IO protocol 2')
     33     io_alias = 'io'
     34     if io_alias not in entries:
     35         raise ValueError('selected map lacks adapter IO module: ' + io_alias)
     36     sources = {alias: (source_root / relative).read_bytes()
     37                for alias, (_, relative, _) in entries.items()}
     38     entry = BACKENDS[backend][0]
     39     worker = f'!import source_map as M\n!export {entry}\n{entry} = M.{entry}\n'
     40     adapter = f'!import {io_alias} as IO\n!export main\nmain build source overlay store compiler mapping root = IO.io (build source overlay store compiler mapping root)\n'
     41     return text, sources, worker, adapter
     42 
     43 
     44 def main():
     45     parser = argparse.ArgumentParser(description=__doc__)
     46     parser.add_argument('tricu', type=Path)
     47     parser.add_argument('output', type=Path)
     48     parser.add_argument('--compiler-bundle', type=Path, required=True)
     49     parser.add_argument('--compiler-io-version', type=cli_version, default=1)
     50     parser.add_argument('--io-version', type=cli_version, required=True, help='declared worker/adapter output framing')
     51     parser.add_argument('--map', type=Path, required=True)
     52     parser.add_argument('--source-root', type=Path, default=ROOT)
     53     parser.add_argument('--backend', choices=BACKENDS, default=DEFAULT_BACKEND)
     54     parser.add_argument('--memory-mib', type=int, default=8192)
     55     parser.add_argument('--seconds', type=float, default=1800)
     56     args = parser.parse_args()
     57     if not 0 < args.memory_mib <= 65536 or not 0 < args.seconds <= 7200:
     58         parser.error('limits must be within 64 GiB and two hours')
     59     start = time.monotonic()
     60     runtime = args.tricu.resolve()
     61     # Capture all source/compiler bytes before any output is created. Compilation
     62     # uses only these copies, never a later re-read of mutable selected inputs.
     63     text, sources, worker, adapter = project(args.map, args.source_root, args.io_version, args.backend)
     64     compiler_bytes = args.compiler_bundle.read_bytes()
     65     runtime_pin = digest(runtime.read_bytes())
     66     root = args.output.resolve(); root.mkdir(parents=True, exist_ok=False)
     67     projection = root/'projection'; (projection/'inputs').mkdir(parents=True)
     68     (root/'source-map.v1').write_text(text)
     69     workspace = ['tricu.workspace.v1']
     70     for alias, payload in sources.items():
     71         target = 'inputs/' + alias + '.tri'
     72         (projection/target).write_bytes(payload)
     73         workspace.append(alias + '\t' + target)
     74     (projection/'tricu.workspace').write_text('\n'.join(workspace)+'\n')
     75     (projection/'worker.tri').write_text(worker)
     76     (projection/'adapter.tri').write_text(adapter)
     77     (root/'compiler.arboricx').write_bytes(compiler_bytes)
     78     records = []
     79 
     80     def run(label, command):
     81         remaining = args.seconds - (time.monotonic()-start)
     82         if remaining <= 0:
     83             raise TimeoutError('kit preparation wall-time guard')
     84         command = [str(runtime), 'arboricx', *map(str, command), '+RTS', '-N1',
     85                    '-M' + str(args.memory_mib) + 'm', '-s', '-RTS']
     86         began = time.monotonic()
     87         reply, peak = run_guarded(command, memory_mib=args.memory_mib, seconds=remaining)
     88         (root/(label+'.log')).write_text(reply)
     89         records.append(dict(label=label, command=command, seconds=time.monotonic()-began,
     90                             peak_kib=peak, rts=parse_rts_stats(reply)))
     91         return reply
     92 
     93     reply = run('import-compiler', ['import', '--file', root/'compiler.arboricx',
     94                                   '--store', root/'tools', '--indexed'])
     95     compiler = re.search(r'Created module alias .* -> ([0-9a-f]{64})', reply)[1]
     96     kit = root/'kit'; kit.mkdir()
     97     metadata = dict(schema='tricu.bootstrap-kit.v2' if args.io_version == 2 else 'tricu.bootstrap-kit.v1',
     98                     backend=args.backend, io_version=args.io_version, artifacts={})
     99     for role, label, filename, export in [
    100             ('compiler', 'worker', 'rebuilt-compiler.arboricx', BACKENDS[args.backend][0]),
    101             ('adapter', 'adapter', 'io-adapter.arboricx', 'main')]:
    102         run('compile-'+label, ['compile', '--file', projection/(label+'.tri'),
    103             '--toolchain', compiler, '--tool-store', root/'tools',
    104             *compiler_flags(args.compiler_io_version), '--store', root/'store',
    105             '--output', kit/filename, '--compile-timeout', str(max(1, int(args.seconds-(time.monotonic()-start))))])
    106         metadata['artifacts'][role] = dict(file=filename, export=export,
    107                                           sha256=digest((kit/filename).read_bytes()))
    108     if digest(runtime.read_bytes()) != runtime_pin:
    109         raise ValueError('runtime changed during preparation')
    110     (kit/'bootstrap-kit.json').write_text(json.dumps(metadata, indent=2)+'\n')
    111     load_kit(kit, args.backend)
    112     (root/'report.json').write_text(json.dumps(dict(complete=True, generation_built=False,
    113         compiler_io_version=args.compiler_io_version, produced_io_version=args.io_version,
    114         runtime_sha256=runtime_pin, compiler_sha256=digest(compiler_bytes), compiler_manifest=compiler,
    115         map_sha256=digest(text.encode()), sources={a:digest(b) for a,b in sources.items()},
    116         worker_source_sha256=digest(worker.encode()), adapter_source_sha256=digest(adapter.encode()),
    117         kit=metadata, records=records, elapsed_seconds=time.monotonic()-start), indent=2)+'\n')
    118     print('Prepared explicit compiler kit:', kit, '(not generation acceptance)')
    119 
    120 
    121 if __name__ == '__main__':
    122     main()