Compiler & artifacts¶
This page explains what happens between your .py file and the compiled program file, .dbc, that d/OS runs. After it you will know which parts of the compiler are Python-specific, why two builds of the same source are always identical, what each dpython command does, and how far the compiler's type inference goes today.
What is Python's and what is shared¶
The parser is RustPython's, at release 0.4.0, used under its MIT notice. Semantic analysis is d/Python's own: Python's scoping, name binding, type inference and the translation to d/OS bytecode. It is kept apart from the d/BASIC compiler's analysis (d/BASIC is d/OS's other language and shares the same runtime) so that sharing the later stages cannot let BASIC assumptions leak into Python. The code generator, optimizer, relocation and validation are shared. They were separated out of the d/BASIC compiler when a second language needed them; the d/BASIC compiler kept working unchanged and its whole test suite kept passing. The container format is the authority on opcodes, typed operands, headers, versions, program kinds and permissions, and the intent definitions, generated from the official list of intents, supply exact signatures.
The compiler today emits ordinary bytecode: objects, fields, iterator steps and typed procedure calls. It adds no new opcode, no private channel for Python, and no native Python object. New Python behavior arrives as shared support libraries or as versioned additions to the container format that leave every existing d/BASIC program working.
Determinism¶
The same source, the same library bytes, the same target and the same options produce the same bytes every time, with no host paths or timestamps inside. Every measured corpus program has identical debug and release output, and the browser build's fully optimized program and both of its libraries match the native command line byte for byte for the playground's test program. The header of a compiled program records exactly what its code and libraries need, and the d/OS runtime, the program that runs your compiled code, declines a program that needs something it cannot supply before executing it.
The commands¶
dpython check program.py # validate the emitted artifact; not an execution claim
dpython build program.py -o out/app.dbc # write the image and lib/*.dbl beside it
dpython run program.py # compile and execute; GUI source opens the native host
dpython run out/app.dbc # execute a built artifact
dpython run --native program.py # select the window face of a dual-face image
dpython inspect program.py # JSON: code, pool, static, frames, capabilities, libraries
dpython trace program.py # execute against the deterministic scripted host
dpython bindings # print the callable registry the compiler reads
dpython package program.py --metadata app.json -o app.dapp
inspect reads the compiled program with an independent reader of the container format. For fibonacci.py it reports a program of 1,561 bytes: 734 bytes of code, 763 of constant pool and 56 of static data, 15 value-stack slots, 24 call frames, a console program with no window, no permissions required, and one library, dpython.integers, of 2,791 bytes.
Optimization¶
The native command line always uses the ALL optimization profile; the browser API also offers NONE. For the playground's test program, NONE produced 2,615 bytes and ALL 2,082, with the same two libraries. Sharing method bodies as cached typed procedures took the tic-tac-toe game from 67,275 bytes to 17,968 without changing a rule of the game or a reviewed pixel. d/Python's promise for every optimization is simple: correct behavior, isolation and a responsive interface are requirements, and a smaller program that quietly changes what Python code does is not an improvement.
Type inference, today¶
The compiler specializes the code it emits on the types it can infer: one element type per list, dictionary or set; a fixed shape per tuple; one concrete result type per function, found from a base-case return before any recursive call; the same type on both sides of a conditional expression or an and/or. Where inference cannot decide, the compiler stops and tells you rather than guessing, and its message names the missing piece the compatibility table is waiting on: tagged values for mixed types, whole-scope inference for empty containers. These are limits of today's implementation, not of the language.
Scheduling and budgets¶
Every loop, arithmetic operation and copy is charged against the runtime's instruction and memory budgets. The playground runs programs in slices of 5,000 instructions; the test runner uses explicit finite budgets and reports when one runs out. A group of fused instructions can never run past the end of its slice: that was a defect found by the tic-tac-toe game, fixed in the shared runtime, and proven for every budget from 1 to 32 with fusion on and off. Event handlers run within the same budgets, and the user interface never waits on a program.
Provenance¶
The RustPython parser is at release 0.4.0 under its MIT notice. The reference CPython build (3.9.6) that every test is checked against is a test dependency only; no CPython runtime is embedded, and no native Python code takes part in running a compiled program. See Conformance.