The mwcc engine from Python: a client of mwcc-tools serve, with the standard library only. One Engine is one
server and its session: the projects, units, targets, compiled objects and traces it loads stay loaded while it
runs, so later calls are fast. The methods, their parameters and their results are in docs/api.md.
The installers (the repository's README, Installation) make it importable, from the installation's python directory; in
a virtual environment, pip install -e ~/.local/share/mwcc-tools/python ($env:LOCALAPPDATA\mwcc-tools\python on
Windows). It also
installs with pip install "git+https://github.com/PrimeDecomp/mwcc-tools@vVERSION#subdirectory=python" (the
release of your mwcc-tools), or put this directory on sys.path (or PYTHONPATH). The engine runs MWCC_TOOLS
if it is set, else the bin/mwcc-tools of the installation this package is in, or the release build of the
checkout it is in (target/release/mwcc-tools), which match it, else mwcc-tools on the path. The server finds the mwcc checkout from MWCC_ORACLE_ROOT, else from where the binary was built.
from mwcc_tools import Engine
with Engine(project='/path/to/echoes') as engine:
print(engine.version, len(engine.methods))
for result in engine.call('unit.residuals', below=100.0):
print(result['summary'])
one = engine.call_one('oracle.analyze', trace_path='saved.trace', version='GC_2_7')
print(engine.last_timing)Engine(project=None, binary=None, command=None, env=None)starts the server;projectis the one a call names none of. It checks the server's schema version, and refuses one this package does not speak.engine.call(method, **params)iterates over a method's results (a dict each). Stopping early cancels the request; the results so far stand.engine.call_one(method, **params)gives a method's only result.- A failed method raises
EngineErrorwith the server's message. Calls may come from several threads at once. engine.last_timingis the last call's time by stage (milliseconds).engine.close()ends the server (withdoes it).
from mwcc_tools import Engine, Unit
with Engine(project='/path/to/echoes') as engine:
unit = Unit(engine, 'main/MetroidPrime/CControlMapper')
print(unit.info['source'], unit.report['measures']['fuzzy_match_percent'])
source = open('/path/to/echoes/' + unit.info['source'], encoding='utf-8').read()
edited = source.replace('int i = 0;', 's32 i = 0;')
compiled = unit.compile({unit.info['source']: edited})
print(compiled['status'], compiled['object']['sha1'])
score = unit.score({unit.info['source']: edited}, symbols=['GetMapping__14CControlMapperCFQ214CControlMapper9ECommands'])
print(score['score']['fuzzy'])
print(unit.score(built=True)['score']['fuzzy'])Unit(engine, name, project=None)loads the unit:unit.handle,unit.info(its source, flags, compiler, target and build object) andunit.report(its report.json entry, when the build has written one).unit.compile(files=None, **options): the unit compiled withfiles({path relative to the project: text, or None for absent}) in place of the disk's. Nothing is written to the tree. Options areunit.compile's:inline_max_size,trace,trace_path,object_path,timeout_ms,cache.unit.score(files=None, built=False, symbols=None, rows=False, **options): the object compiled withfiles, or the build's (built), orobject=PATH, scored against the target as objdiff-cli 3.8.2 scores it.unit.experiment(output, space, stages=None, strategy=None, budget=None, **plan): a plan run inoutput(docs/oracle/experiments.md). It yields each finished candidate's row; stopping early cancels the run, and the rows written stay. The summary is thenunit.last_summary.
variants = '''### const
@@@ old
f32 x = GetAxis(0);
@@@ new
const f32 x = GetAxis(0);
'''
for row in unit.experiment('build/experiments/mapper', {'variants': {'text': variants}},
budget={'seconds': 600}):
print(row['name'], row['status'], row['fuzzy'], row['scores'])
print(unit.last_summary['control']['identical_to_build'])The methods without a wrapper take the unit's handle:
engine.call_one('source.variants', handle=unit.handle, variants_path='mapper.variants')
engine.call_one('unit.inline_sweep', handle=unit.handle, sizes=[16, 32, 64])
engine.call_one('unit.sinit_profile', handle=unit.handle)
engine.call_one('oracle.regdiff', handle=unit.handle, function='GetMapping', text=True)['text']
engine.call_one('inline.diff', a={'handle': unit.handle}, target=True, text=True)['text']
engine.call_one('tu.data_diff', handle=unit.handle, text=True)['text']from mwcc_tools import Engine, Trace, Unit
with Engine(project='/path/to/echoes') as engine:
unit = Unit(engine, 'main/MetroidPrime/CControlMapper')
trace = Trace(engine, unit, function='GetDigitalInput')
function = trace.functions[0]['function']
report = trace.target(function)
print('\n'.join(trace.target(function, text=True)['text']))
wrong = report['result']['wrong']
replayed = trace.replay(function, [['degree', wrong[0], 1], ['drop', wrong[0]]])
many = trace.replay_many(function, [[['degree', v, 1]] for v in wrong])
best = trace.search(function, iterations=2000, restarts=2)Trace(engine, unit=None, **params)loads a trace (trace.load): aUnit(or a unit's name) compiled with a trace, throughfilesand withfunctionkeeping one function's records; or a saved trace,load='PREFIX'(PREFIX.trace, with PREFIX.o, PREFIX.source and PREFIX.manifest.json when there) and itsversion.save='PREFIX'saves a unit's trace. It givestrace.handle,trace.functionsandtrace.version.- Its colouring methods take the function and
class_(GPR, FPR, VR or CR; default GPR):verify,target,forceandreplay(withwants, registers the target has by vreg, andtext),replay_many(each edit list replayed on the same graph: one result per list),search(iterations,seed,restarts,top,fixed,nodes,kinds) andrealize(function, edits, limit=None): one solution's edits as source variants, whosecandidatesare an experiment's space (unit.experiment(output, {'list': result['candidates']})), each labelledsuggested(docs/oracle/colouring.md). - Edits are lists:
['degree', 35, 1],['move', 40, 'after', 37],['drop', 35],['link', 35, 40],['unlink', 35, 40],['neighbours', 35, 1]and['order', [35, 36, 37]]. They apply in the order given.
trace.remap compares two traces' graphs: engine.call_one('trace.remap', first=a.handle, second=b.handle, function=...).
The decomp kits' decomp_experiment and decomp_matrix modules are these functions with root set to the
checkout. Candidates are complete files rendered in Python and compiled through the engine; the working tree is
never written.
from pathlib import Path
from mwcc_tools import run_variants, run_matrix, checked_replace
root = Path('/path/to/echoes')
source = 'src/Echoes/MetroidPrime/CControlMapper.cpp'
text = (root / source).read_text(encoding='utf-8')
rows = run_variants([('two', {source: checked_replace(text, 'mControlScheme == 1', 'mControlScheme == 2')})],
files=[source], build=['ninja', 'build/G2ME01/src/MetroidPrime/CControlMapper.o'],
output_dir='build/experiments/scheme', root=root, symbols=['GetDigitalInput'])
coverage = run_matrix({'scheme': ['1', '2']},
lambda c: {source: text.replace('mControlScheme == 1', 'mControlScheme == ' + c['scheme'])},
files=[source], build=['ninja', 'build/G2ME01/src/MetroidPrime/CControlMapper.o'],
output_dir='build/experiments/matrix', root=root, symbols=['GetDigitalInput'])run_variants(variants, *, files, build, output_dir, root, unit=None, symbols=(), baseline=None, evaluate=None, engine=None, on_row=None, quiet=False, **plan):variantsare(name, {path: text, bytes or None});filesevery path they may change (Nonemakes one absent);buildthe unit's ninja target, which names the unit (an edge that is no unit's object is refused before anything compiles);output_dira new run directory, or one to resume. Each row scoressymbols(asscore_symbolsgives them), or is whatevaluate(candidate)returns:candidatehasname,directory,files(the candidate's files there),objectanddiff. An evaluated row is written to the run'sevaluations.jsonl(the engine's own rows are inresults.jsonl); a duplicate candidate gets its original's evaluation, and a resumed run first evaluates the rows an interrupted one left.plantakes the rest of the plan (budget,jobs,timeout_ms,keep_objects,diff).score_symbols(unit, symbols, *, root, engine=None): each symbol'spercent,original,emitted,differing,exact_instructionsandstrict(the strict level's verdict:exact, or its first difference) for the build's object.run_matrix(axes, render, *, files, build, output_dir, root, baseline=None, skip=None, context=None, max_cases=256, **options): every combination ofaxesrendered byrender(choices)(andskip(choices), a reason orNone) before any compiles, written tomatrix.json, then run withrun_variantsinrun/;coverage.jsonis refreshed after each row, from the evaluated rows when there is anevaluate.matrix_cases(axes),refresh_coverage(output_dir)andchecked_replace(text, old, new, count=1)are its parts.
mwcc_tools.reports gives the kit scripts' printed reports from the methods' results: try_variants
(source.variants), inline_sweep, residuals and sinit_profile (the unit.* methods), each a list of lines.
python -m unittest discover -s python/tests
They run against a fake server; with MWCC_TEST_ECHOES set to an Echoes checkout, they also run against the real
engine on it.