Yuzhe's Blog

yuzhes

Don't execute Python early: split logical calls from physical work

Pysolate’s Prepare–Linearize–Materialize model, and why it is more general than execute-as-you-generate

A program specifies what should happen and at which logical location it should happen; it does not always require each physical piece of work to start only at that location.

What Pysolate wants to exploit is exactly the gap between these two.

In programs where Python is generated by a model, then calls remote tools, datasets, filesystems, or other Host capabilities, the most visible performance problem is usually not local a + b, but high-latency boundary operations: one model call may take dozens of seconds, one search may take several seconds, one data load may take even longer. The most intuitive optimization is “execute as we generate”: the model produces search(...), and the runtime immediately executes it; when the rest of the code is generated, continue from there.

This path is tempting, and dangerous.

An unfinished Python source is not yet a well-formed program. Later it may contain a syntax error, may place earlier calls inside an unclosed control flow, may change parameter sources, may redefine exception behavior inside try/finally, or may cause side effects that should not be published early. More fundamentally, once the Host starts executing any Python prefix, it must maintain partial interpreter state, partial control stack, exception boundaries, object identity, mutable heap, and the future continuation. The optimizer eventually becomes a second Python runtime, or quietly turns “generated code” into a new streaming language.

Pysolate chooses a different path:

  1. No Python execution during streaming stage.
  2. After full source seal, still perform exactly one true synchronous, fresh CPython execution.
  3. The Host can start some external physical work early, based on source facts already seen.
  4. When Python reaches the original call site, decide whether that early work can represent the current logical call.
  5. Only when the value is truly needed, synchronously obtain a normal Python value.

This is not “making Python asynchronous”; it is:

Separate logical execution from physical work\boxed{ \text{Separate logical execution from physical work} }

This is formalized as a three-phase model:

Prepare early;Linearize in place;Materialize late.\boxed{ \textbf{Prepare early;\quad Linearize in place;\quad Materialize late.} }

Abbreviated as PLM: Prepare–Linearize–Materialize.

First, a note: the following is a design model and implementable approach for Pysolate. It does not mean the current implementation already covers all optimizations described. Its value is primarily providing a unified and provable semantic framework for existing source-time pre-dispatch, Host-side pending work, prepared input, caching, and future AST scheduling.


1. Why we do not try “execute one line at a time”

1. Incomplete source is not a safe execution boundary

Assume the model has already emitted:

result = send_email(address, body)

But the rest is not generated yet. It might later be:

if dry_run:
    ...

or:

raise ValueError("do not send")

or the entire function may eventually contain a syntax error. From the prefix alone, you cannot know whether this call is reachable in the full program, whether it is protected by some exception boundary, or whether it should execute at all.

For read-only calls, this looks slightly lighter:

data = read_dataset("sales.csv")

But even if it does not write to the outside world, it may:

So, “no side effect” is not the same as “safe to execute at any time”.

2. Partial execution pushes the Host into an impure position

If the Host really executes a Python prefix, it must answer:

These are not small scheduling issues; they redefine Python execution semantics.

3. Better philosophy: do not move “what should happen”; move only the physical work needed to make it happen

Consider:

x = remote_read(key)

That single line contains at least two distinct things:

  1. Logical event: the program calls remote_read(key) here and gets a value or raises an exception here;
  2. Physical work: connection setup, authentication, queueing, remote computation, transport, deserialization.

Conventional synchronous execution binds both at the same location. Pysolate’s key judgment is:

The logical call site must stay, but some physical work does not need to wait to start there.

So optimization is not:

execute a piece of Python early

but:

generate a physical candidate result early, which may later be adopted by the logical call

This distinction is the central mental model.


2. Two time axes: logical order vs physical time

There are two fundamentally different kinds of “earlier” in a program.

1. Logical program points

Let:

ℓ∈L.\ell \in \mathbb L.

It denotes a position in source and CPython control flow. For example:

a = A()
x = f(a)
b = B(x)

Logically, we have:

ℓA<ℓf<ℓB.\ell_A < \ell_f < \ell_B.

This order defines Python semantics: whether B exists, what its arguments are, and how exceptions propagate are all determined on this logical axis.

2. Physical wall-clock time

Let:

p∈T.p \in \mathbb T.

It describes:

PLM allows an operation logically at ℓH\ell_H to start physical preparation earlier at time pPp_P:

pP<pL,p_P < p_L,

where pLp_L is the physical time when CPython reaches the original logical call site.

The key invariant is:

ℓH does not move; only physical work moves on the  p axis.\boxed{ \ell_H\ \text{does not move; only physical work moves on the }\ p \text{ axis.} }

We can draw it as:

Logical program axis

ℓ0 -------- ℓ1 -------- ℓ2 -------- ℓ3
             H_A                     H_B
              │                       │
              │ original semantic site │
              │                       │


Physical time axis

p0 -- prepare A -- prepare B -------- linearize A -- materialize A -- ...

This is the simplest mathematical version of “logical ownership / physical work separation.”


3. Hidden world state: why read-only still depends on time

A Host call is usually written as:

H(a),H(a),

where aa is an explicit argument. But semantically this is incomplete. The real operation also depends on global environment not explicit in source:

Θp=(Wp, Ap, Cp, Qp,…).\Theta_p = \left( W_p,\ A_p,\ C_p,\ Q_p,\ldots \right).

Where:

Therefore a Host operation should be written as:

H(a;Θp)\boxed{ H(a;\Theta_p) }

not just H(a)H(a).

1. “Read-only” is only an effect property

stock_price("AAPL") can be read-only because it does not mutate the market:

Wp′=Wp.W'_p = W_p.

But it may still satisfy:

H(a;Θp1)≠H(a;Θp2).H(a;\Theta_{p_1}) \ne H(a;\Theta_{p_2}).

So:

read-only⇏temporally invariant.\boxed{ \text{read-only} \not\Rightarrow \text{temporally invariant}. }

This distinction unifies caching, prefetch, version validation, and snapshots.

2. An operation usually depends on only part of the world

Define the semantic footprint of an operation:

DH(a)⊆Θ.D_H(a)\subseteq \Theta.

For example:

read_git_file(commit_hash, path)

may depend only on the immutable Git object identified by commit_hash; market state, current cwd, and other file changes are irrelevant.

Define two contexts as equivalent for that call:

Θp1≡H,aΘp2\Theta_{p_1} \equiv_{H,a} \Theta_{p_2}

iff they are indistinguishable on DH(a)D_H(a).

If HH is deterministic, and:

Θp1≡H,aΘp2,\Theta_{p_1} \equiv_{H,a} \Theta_{p_2},

then:

H(a;Θp1)=H(a;Θp2).H(a;\Theta_{p_1}) = H(a;\Theta_{p_2}).

That is the core condition under which early results can be reused directly.


4. Start with the simplest example: move issue earlier, move collect later

Consider:

a = tool_A()       # 10s
b = tool_B()       # 20s

c = a + b

d = tool_D()       # 20s

return c + d

1. Ordinary synchronous execution

If the calls happen in sequence:

A: 10s
B: 20s
D: 20s

The approximate total time is:

Tsync=10+20+20=50s.T_{\mathrm{sync}} = 10+20+20 = 50\text{s}.

2. Minimal split-phase transformation

Use a temporary two-phase form:

_hA = issue(tool_A)
_hB = issue(tool_B)
_hD = issue(tool_D)

a = collect(_hA)
b = collect(_hB)

c = a + b

d = collect(_hD)

return c + d

Here:

Python has no await, and it never sees Future[int].

The three requests start almost simultaneously, so ideal total time is:

Tsplit≈max⁡(10,20,20)=20s.T_{\mathrm{split}} \approx \max(10,20,20) = 20\text{s}.

3. Remaining wait

For call ii, define:

The remaining blocking time is:

Wi=max⁡(0,piF−piM).W_i = \max(0,p_i^F-p_i^M).

If latency is LiL_i, then:

piF=piI+Li,p_i^F=p_i^I+L_i,

so:

Wi=max⁡(0,piI+Li−piM).\boxed{ W_i = \max(0,p_i^I+L_i-p_i^M). }

This directly shows:

But movement is still constrained by semantic dependencies.


5. Why two phases are not enough: add Linearize

If an operation depends on mutable Θp\Theta_p, writing only:

candidate = issue(H, args)
...
value = collect(candidate)

causes a problem:

does this call read the world at issue time, original call-site time, or collect time?

For example:

price = stock_price("AAPL")
pure_local_work()
return price

An early request may read 100 at pPp_P; original call site is at pLp_L; real collect happens at pMp_M. These times can differ.

So the cleaner normal form is three phases.

1. Prepare

candidate = prepare(H, frozen_args)

Formally:

PH(a,pP)→c.P_H(a,p_P)\rightarrow c.

It yields a candidate that may include:

pending request
candidate result
version / ETag
snapshot ID
lease
authority epoch
provider session

Prepare may perform physical work, but must be silent in abstract semantics:

Obs⁡(PH)=ϵ.\operatorname{Obs}(P_H)=\epsilon.

That is, it cannot raise to Python early, publish logical write effects, or change Guest-visible state.

2. Linearize

When CPython reaches the original call site:

job = linearize_or_start(candidate, H, actual_args)

Formally:

LH(c,a,ΘpL)→k.L_H(c,a,\Theta_{p_L})\rightarrow k.

It decides:

A typical rule:

LH(c,a,ΘpL)={adopt⁡(c),Valid⁡H(c,a,ΘpL)start⁡(H,a,ΘpL),otherwise.L_H(c,a,\Theta_{p_L}) = \begin{cases} \operatorname{adopt}(c), & \operatorname{Valid}_H(c,a,\Theta_{p_L}) \\[4pt] \operatorname{start}(H,a,\Theta_{p_L}), & \text{otherwise}. \end{cases}

Linearize is fixed at the original logical call site.

3. Materialize

value = materialize(job)

Formally:

MH(k,pM)→r.M_H(k,p_M)\rightarrow r.

It:

So the complete principle is:

P as early as possible,L in place,M as late as possible.\boxed{ P\ \text{as early as possible,}\quad L\ \text{in place,}\quad M\ \text{as late as possible.} }

6. Core correctness contract

For a baseline synchronous operation:

H(a;ΘpL),H(a;\Theta_{p_L}),

the PLM protocol must satisfy:

MH(LH(PH(a,pP),a,ΘpL),pM)≡H(a;ΘpL).\boxed{ M_H \left( L_H \left( P_H(a,p_P), a, \Theta_{p_L} \right), p_M \right) \equiv H(a;\Theta_{p_L}). }

where:

pP≤pL≤pM.p_P\le p_L\le p_M.

Intuitively:

No matter how early physical preparation starts, as long as the system validates, adopts, or restarts the right operation at the original logical point and only returns the result at a legal position, Guest-observable behavior should be equivalent to a normal synchronous call at that point.

If HH is a nondeterministic relation, uniqueness is not required; instead:

Outcome⁡PLM∈⟦H⟧(a,ΘpL),\operatorname{Outcome}_{PLM} \in \llbracket H\rrbracket(a,\Theta_{p_L}),

meaning the PLM outcome must be one of the results allowed by the original operation in that context.

Candidate validator soundness

Let candidate be:

c=(r^,ν),c=(\hat r,\nu),

where ν\nu is version, lease, or snapshot evidence. The Validator must satisfy:

Valid⁡H(c,a,ΘpL)=true⇒r^∈⟦H⟧(a,ΘpL).\boxed{ \operatorname{Valid}_H(c,a,\Theta_{p_L})=\text{true} \Rightarrow \hat r \in \llbracket H\rrbracket(a,\Theta_{p_L}). }

It may be incomplete: some actually valid candidates can be conservatively rejected, which only loses performance. It must not be unsound: it cannot treat an expired candidate as the current logical result.


7. stock price: full example under hidden temporal variables

Consider:

price = stock_price("AAPL")

Case 1: provider only supports “current price”

Prepare at pPp_P gets:

r^=100.\hat r=100.

At pLp_L there is no version, snapshot, or verifiable evidence. Then:

Valid⁡=false.\operatorname{Valid}=\text{false}.

The system cannot treat 100 as the result of the current logical call and must re-query at pLp_L.

But Prepare can still pre-complete:

DNS
TLS
connection pool setup
authentication
provider route
request object allocation

This shows physical phases can be further split:

PH0→PH1→⋯→LH→MH.P_H^0\rightarrow P_H^1\rightarrow\cdots\rightarrow L_H\rightarrow M_H.

The less this phase depends on current world state, the farther left it can move.

Case 2: provider returns a version number

Prepare gets:

(r^=100,ν=42).(\hat r=100,\nu=42).

At pLp_L, the system performs a linearizable current-version check. If still 42, the candidate can be adopted at this logical point; if it is already 43, it reissues.

The version check is proof that ties candidate result to current world state.

Case 3: call bound to snapshot

price = stock_price("AAPL", snapshot=s)

Then semantics are:

H("AAPL";s),H("AAPL";s),

rather than a current read that changes with wall-clock. If snapshot is immutable, the result can be:

This also shows:

Cache and early issue are semantically the same thing: both produce “candidate results at another physical time,” and linearize decides whether a candidate can represent the current logical call.


8. Complex control flow: Host does not choose branches; CPython remains the sole semantic executor

Consider:

user = get_user("alice")

if user["premium"]:
    score = normalize(user["score"])       # regular Python, 3s
    recs = search(user["topic"])           # 15s

    if score > 10 and len(recs) > 3:
        quote = price(recs[0]["sku"])      # 10s
    else:
        quote = fallback("premium")        # 6s
else:
    quote = fallback("basic")              # 6s

tax = tax_table("UK")                      # 20s
return render(quote, tax)

Assume:

1. Source streaming stage

The Host reads:

user = get_user("alice")

Since arguments are source-closed:

prepare get_user("alice")

It sees:

search(user["topic"])

At this point it cannot execute user["topic"], because that requires a real Python object and __getitem__ semantics. The Host only records the call site, no request is sent.

It sees:

fallback("premium")
fallback("basic")

If contract allows control speculation, it can prepare both candidates.

It sees:

tax_table("UK")

Arguments are source-closed, so prepare immediately.

The whole streaming stage:

2. Conceptual AST transform after sealing

_p_user = prepare_or_reuse(GET_USER, "alice")
_p_tax = prepare_or_reuse(TAX, "UK")
_p_fp = prepare_or_reuse(FALLBACK_PREMIUM, "premium")
_p_fb = prepare_or_reuse(FALLBACK_BASIC, "basic")

_j_user = linearize_or_start(
    _p_user, GET_USER, "alice"
)
user = materialize(_j_user)

if user["premium"]:
    # user is now a real Python dict.
    _p_search = prepare(SEARCH, user["topic"])
    _j_search = linearize_or_start(
        _p_search, SEARCH, user["topic"]
    )

    # search runs in the Host background; CPython performs local computation normally.
    score = normalize(user["score"])

    recs = materialize(_j_search)

    if score > 10 and len(recs) > 3:
        _p_price = prepare(PRICE, recs[0]["sku"])
        _j_price = linearize_or_start(
            _p_price, PRICE, recs[0]["sku"]
        )
        quote = materialize(_j_price)
    else:
        _j_fp = linearize_or_start(
            _p_fp, FALLBACK, "premium"
        )
        quote = materialize(_j_fp)
        discard(_p_fb)
else:
    _j_fb = linearize_or_start(
        _p_fb, FALLBACK, "basic"
    )
    quote = materialize(_j_fb)
    discard(_p_fp)

_j_tax = linearize_or_start(
    _p_tax, TAX_TABLE, "UK"
)
tax = materialize(_j_tax)

return render(quote, tax)

3. What exactly happens there

The resulting execution naturally forms multiple waves:

source-time prepare wave
        ↓
materialize user
        ↓
CPython decides outer branch
        ↓
runtime prepare search
        ↓
CPython does independent local work
        ↓
materialize search
        ↓
CPython decides inner branch
        ↓
prepare / adopt price or fallback
        ↓
materialize

There is no Python scheduler, and no Host-side Python graph evaluator.


9. Data dependencies: why some critical paths cannot be eliminated

Consider:

a = tool_A()       # 10s
x = a + 1
b = tool_B(x)      # 20s

d = tool_D()       # 20s
return b + d

Pysolate can prepare early:

A
D

But B depends on the argument:

x = a + 1

By design, the Host does not execute this Python. Therefore:

  1. materialize A;
  2. CPython computes x;
  3. only then prepare/linearize B.

The critical path remains:

A(10)→Python (+1)→B(20)=30s.A(10) \rightarrow \text{Python }(+1) \rightarrow B(20) = 30\text{s}.

D can run in parallel with this chain, so total time is near:

max⁡(30,20)=30s.\max(30,20)=30\text{s}.

This is not an optimizer failure, but an explicit semantic staging barrier:

Any intermediate Python computation is not executed early by the Host.\boxed{ \text{Any intermediate Python computation is not executed early by the Host.} }

If in the future we want to automatically compute x and start B the instant A completes, even while main Python thread is doing other work, at least one extra mechanism is needed:

These are beyond the minimal and clean PLM model.

10. Side effects: not just whether it can be early, but which phase can be.

Side effects are not a boolean label. A logical operation can often be split into several physical phases, some silent and some not advanceable.

1. Four classes of operation contract

Immutable / snapshot read

Read content hash
Read fixed Git commit
Read fixed dataset version

Result does not depend on a changing current world, so full Prepare is possible.

Validated read

Read object with ETag/version
Read database result with snapshot token

Prepare produces candidate and evidence; Linearize validates, and if not valid it re-executes.

Prepare–commit effect

For example:

send_email(to, body)

Can prepare:

parse recipients
generate MIME
upload temporary attachments
establish connection
complete spam / policy checks

But actual “send” must commit at the original logical point.

Formally:

PH(a)→prepared effectP_H(a)\rightarrow \text{prepared effect} LH→claimL_H\rightarrow \text{claim} CH→publish/commit.C_H\rightarrow \text{publish/commit}.

Here commit generally cannot move right of other logical effects.

Non-stageable

If a call itself:

then it cannot be advanced and degrades to a normal synchronous call.

2. The real condition for speculation

If a call is hidden in an unknown branch:

if cond:
    x = H()

To prepare while cond is unknown, we must preserve:

Prepare⁡(H)\operatorname{Prepare}(H)

with the same logical effect as not executing.

If the branch is not taken:

discard(candidate)

must satisfy:

Obs⁡(discard⁡)=ϵ.\operatorname{Obs}(\operatorname{discard})=\epsilon.

So “read-only” is still not enough. A read can consume quota, hold an unique lease, trigger audit, or affect provider future behavior. Any of these that the program can observe are part of the effect footprint.

3. Authority is also semantic state

Advance work cannot bypass authority.

A candidate should at least bind:

logical run
capability identity
authority epoch
tool identity
normalised argument digest
source seal
dynamic occurrence

For write operations, Linearize or Commit usually re-checks authority. If capability is revoked after Prepare, candidate results must not be automatically published.

This means:

Physical work can be started, reused, or shared early; logical authorization, publishing, and mutable state remain per logical run.

11. Exceptions: Materialize cannot be deferred unconditionally

Consider:

a = H()          # may fail
send_email()
return a

If transformed to:

_p = prepare(H)
_j = linearize_or_start(_p, H)

send_email()

a = materialize(_j)
return a

If H fails:

So they are not equivalent.

1. Materialize sinking commutativity condition

Let:

M=materialize⁡(j)M=\operatorname{materialize}(j)

and SS is the immediately following statement. We can move MM across SS only when:

Obs⁡(M;S)=Obs⁡(S;M)\operatorname{Obs}(M;S) = \operatorname{Obs}(S;M)

We write:

M⋈S.M\bowtie S.

If:

M⋈S1,…,M⋈Sn,M\bowtie S_1,\ldots,M\bowtie S_n,

we can obtain by adjacent swaps:

M;S1;⋯ ;Sn≡S1;⋯ ;Sn;M.M;S_1;\cdots;S_n \equiv S_1;\cdots;S_n;M.

2. What a conservative implementation allows to cross

Typical statements that can cross:

proved pure
non-raising
does not read result variable
does not observe local variable binding
does not alter temporal validity of operation
does not alter authority / transaction / cwd

Barrier statements that must stop:

observable I/O or external effects
unknown call that may raise
try / except / finally
with / transaction
return / yield
time and random reads
locals / frame introspection
thread or signal boundaries
first strict use of result

Hence a first implementation can safely keep:

only Prepare moving left, with Materialize kept at the original call site.

Most concurrency gains already exist. Materialize sinking is a stronger, more constrained later optimization.

12. How to relate this to other work

Pysolate does not appear in a vacuum. It is related to futures, dataflow, lazy evaluation, async/await, partial evaluation, speculation, and caching, but places the boundary differently.

1. Futures / Promises: bring “not-yet-available values” into the language

Multilisp future allows an expression to be evaluated in parallel, with an implicit or explicit wait when its concrete value is needed; Liskov and Shrira’s promises separate request issuance from return retrieval for asynchronous remote procedure calls.[1][2]

A typical mental model is:

a: Future[int] = async_A()
b: Future[int] = async_B()
c = touch(a) + touch(b)

The key object is:

Future[T].Future[T].

Pysolate differs in that:

Pysolate borrows “separate invocation and wait,” but keeps Future as an internal compiler/Host handle instead of extending the Guest value domain.

2. Python async / await: explicit coroutines and event loop

Python async model requires a coroutine to suspend at await, yielding control to event loop; the event loop schedules other tasks while waiting on a Future.[3]

This suits applications that intentionally write async code:

a_task = asyncio.create_task(A())
b_task = asyncio.create_task(B())
a, b = await asyncio.gather(a_task, b_task)

Pysolate deliberately does not adopt this user model:

In other words, asyncio makes programmers explicitly manage concurrency; PLM lets the compiler move boundary work physical phases.

3. APPL: future proxy and demand-driven synchronization

APPL is a close cousin. It integrates Python and LLM calls via AST transpilation and uses StringFuture, BooleanFuture, etc. to defer synchronization: string concatenation can continue across futures, while strict contexts like len, str, or boolean control flow trigger materialize. APPL explicitly grounds automatic parallelization in async semantics and Future objects.[4]

Its model can be summarized as:

Call returns Future-like Python object
Future participates in limited Python operations
synchronize on first strict use

Pysolate’s key differences are:

  1. Streaming phase does not execute Python.
  2. Future proxy does not leak into normal Python value semantics.
  3. Complex control flow is still decided by a single post-seal synchronous CPython execution.
  4. Early work must be adopted at the original logical point with temporal, authority, and effect contracts.
  5. Scope is not limited to LLM-generation results; it applies to any Host-owned capability.

So APPL is closer to “Python with transparent Futures,” while Pysolate is closer to “a sync Python outer sync plus split-phase physical-work overlay.”

4. Dataflow: making the whole program a dependence graph

Dataflow runtime often models computation as a graph: nodes trigger when input dependencies are ready. Program Dependence Graph makes data and control dependencies explicit and supports optimization and code motion.[5]

If fully dataflow-ized, the example becomes:

A ─┐
   ├─ add ─┐
B ─┘       ├─ add -> result
D ─────────┘

A scheduler can execute directly along critical path.

Pysolate does not implement full dataflow runtime:

So a more accurate structure is:

ordinary Python program
+
Host physical-work overlay

rather than:

Python -> full computation graph -> Host execution graph

5. Lazy evaluation: deferred computation, but PLM does early physical work

Call-by-need delays evaluation until needed and uses sharing to avoid repeated computation. Launchbury’s semantics models this sharing and demand-driven values explicitly via heap bindings.[6]

PLM is a mirror image:

Lazy evaluation:
    start as late as possible
    evaluate when needed

PLM:
    start physical work early
    deliver/materialize only when needed

You can call Pysolate:

physically eager, logically strict, materially deferred

Physical work is aggressive, logical execution remains original synchronous order, and materialization can be deferred.

6. Partial evaluation and multi-stage programming: executing static parts in advance

Partial evaluation computes parts of a program in advance given known inputs and emits a residual program; multi-stage programming explicitly separates code generation and execution stages.[7]

The distinction from Pysolate is important:

Prepared dataset folding or replacing validated inputs with constants overlaps with partial evaluation, but those are additional source transformations, not required parts of PLM correctness.

7. Speculative execution and prefetch: doing potentially needed work early

CPU branch speculation, compiler prefetch, and database prefetch all try to start work before demand is certain. PLM shares this:

start early
possibly waste work
discard wrong path
shorten critical path

The difference is PLM’s speculation is bounded by effect and authority contracts: a candidate cannot produce logical effects just because “it is only an optimization.”

8. Linearizability: giving asynchronous physical process a logical activation point

Linearizability requires each concurrent operation to appear to take effect atomically at some instant between invocation and response.[8]

PLM borrows this mental model, but the domain is not identical:

This intermediate point re-anchors “early physical result” into the original Python semantics.

9. Effect systems: code motion must know what it touches

Effect systems use static information on possible effects to support safe parallelism, reordering, and isolation reasoning.[9]

PLM metadata is essentially a Host-boundary effect/temporal system:

what is read
what is written
whether immutable
whether verifiable
whether speculatable
when authority is checked
whether exception behavior is stable
whether prepare/commit is supported

It does not need a full effect system for all Python; unknowns remain barriers, and conservative correctness is still possible.

10. Leases, versioning, and cache consistency

Distributed-system leases provide bounded guarantees for consistency protocols.[10] In PLM, a lease can become candidate validity evidence:

value
version
valid_until
authority_epoch

This turns “time t” from a vague risk into a verifiable protocol condition.

11. LLM program runtimes

LMQL compiles prompt, constraints, and control flow into efficient inference procedures; SGLang provides a structured LLM-program frontend with runtime parallel control and KV-cache reuse.[11][12]

These systems mainly optimize LLM program semantics or serving substrate. PLM abstraction is lower and broader:


13. Why this design is broadly general

PLM applicability is not determined by tool type, but by a factorizable contract:

∃PH,LH,MHs.t.MH(LH(PH(⋅)))≡H(⋅).\exists P_H,L_H,M_H \quad\text{s.t.} \quad M_H(L_H(P_H(\cdot)))\equiv H(\cdot).

As long as such a factorization exists, optimization applies.

1. It unifies pre-dispatch and cache

Source-time request:

start now, adopt later

Cache:

prepared earlier, adopt now

Both are candidates:

c=(physical outcome,validity evidence).c=(\text{physical outcome},\text{validity evidence}).

Linearize does not care whether it has just started, already finished, or came from prior cache; it only cares whether it can represent the current logical operation.

2. It unifies warm preparation and request execution

An operation can be split into:

runtime / interpreter preparation
connection preparation
argument-independent provider setup
argument-dependent request
time-sensitive read
validation
deserialisation
publication

Each layer has different dependency footprint, so each can move to a different location.

3. It does not depend on a full static graph

Even when branch, loop, or params are only known at runtime, CPython can issue the next batch of work as new frontier is reached. No complete DAG must be known upfront.

4. It allows conservative fallback

Any proof failure:

no Prepare
no speculation
no sinking materialize

falls back directly to:

value = synchronous_host_call(...)

So correctness does not depend on optimization success.

5. It fits Host-owned authority boundaries

If Guest cannot directly access network, Git, file, or provider directly and must go through typed Host capability, Host boundary naturally provides:

uniform interception point
tool identity
argument serialization
effect metadata
authority check
trace / replay
cancellation
result ownership

That makes PLM more realistic than guessing arbitrary Python library calls.


14. Formalization: a minimal core language

No need to attempt a full CPython proof. Define a small language and map implementation to an admitted subset.

1. Baseline language

Expressions:

e::=v∣x∣f(e1,…,en)e ::= v \mid x \mid f(e_1,\ldots,e_n)

Statements:

s::=skip⁡∣x:=e∣x:=H(e)∣s1;s2∣if⁡ e s1 s2∣return⁡ e∣raise⁡ e.s ::= \operatorname{skip} \mid x:=e \mid x:=H(e) \mid s_1;s_2 \mid \operatorname{if}\ e\ s_1\ s_2 \mid \operatorname{return}\ e \mid \operatorname{raise}\ e.

where:

Program state:

⟨s,σ,Θ,π⟩\langle s,\sigma,\Theta,\pi\rangle

includes:

2. PLM extension

Add:

c:=PH(e)c:=P_H(e) k:=LH(c,e)k:=L_H(c,e) x:=MH(k)x:=M_H(k) DH(c).D_H(c).

where candidate cc and job kk are Guest-unforgeable internal tokens.

3. Labelled transitions

Separate internal events:

τint={prepare,complete,validate,cacheHit,discard}\tau_{\mathrm{int}}= \{ prepare, complete, validate, cacheHit, discard \}

and visible events:

τvis={logicalCall,return,exception,commit,output}.\tau_{\mathrm{vis}}= \{ logicalCall, return, exception, commit, output \}.

Define:

hide⁡int(τ)\operatorname{hide}_{int}(\tau)

as deleting physical internal events.

4. Trace refinement theorem

For admitted program PP, if:

  1. Prepare is silent on logical state;
  2. validator is sound;
  3. invalid candidates fallback in Linearize;
  4. untaken candidates can be silently discarded;
  5. Materialize returns corresponding job result or exception;
  6. phase movement respects data/control/effect/exception/temporal dependencies;
  7. any site that cannot be proved is kept as baseline call;

then:

hide⁡int(Traces⁡(T(P)))⊆Traces⁡(P).\boxed{ \operatorname{hide}_{int} \left( \operatorname{Traces}(T(P)) \right) \subseteq \operatorname{Traces}(P). }

This says every visible behavior of transformed program is explained by the original synchronous program.

Under stronger deterministic, snapshot-fixed, and no real-time observation assumptions, this can be strengthened to trace equality:

hide⁡int(Traces⁡(T(P)))=Traces⁡(P).\operatorname{hide}_{int} \left( \operatorname{Traces}(T(P)) \right) = \operatorname{Traces}(P).

5. Why refinement, not same wall-clock

If program reads real time:

start = time.time()
...

any optimization that makes it faster changes the time it observes.

Likewise, if stock_price() truly reads a live market, after optimization the program reaches the original call site earlier, so it may naturally observe different market state.

So we cannot claim lockstep equivalence for arbitrary wall-clock-observing Python. A more precise theorem is:

For the same external history, after hiding internal Prepare events, PLM logical operations can be legally linearized at the original call site and produce a trace allowed by baseline semantics.

If strict replay is required, then you must:


15. Proof structure

Global proof is composed of local lemmas.

Lemma 1: Silent Prepare insertion

If:

PHP_H

only changes Host private state π\pi, does not change σ,Θ\sigma,\Theta, and produces no visible label, inserting Prepare is a stuttering step:

A;B≈A;PH;B.A;B \approx A;P_H;B.

Lemma 2: Valid adoption

By validator soundness:

ValidH(c,a,Θ)=true⇒Outcome(c)∈⟦H⟧(a,Θ).Valid_H(c,a,\Theta)=true \Rightarrow Outcome(c)\in\llbracket H\rrbracket(a,\Theta).

So adopting a candidate does not produce a result not allowed by baseline.

Lemma 3: Invalid fallback

If candidate is invalid, Linearize starts canonical HH from current logical context, directly restoring baseline semantics.

Lemma 4: Untaken speculation

If Prepare and Discard are both silent, an unentered-branch candidate only changes π\pi, and is unobservable after hiding internal events.

Lemma 5: Materialize commutation

If:

M⋈S,M\bowtie S,

then:

M;S≈S;M.M;S\approx S;M.

By induction, Materialize can be safely moved rightward to the end of any contiguous commutable interval.

Composition

AST transformation decomposes into finitely many steps:

insert Prepare
replace each original call with Linearize + Materialize
move Prepare
move Materialize
insert Discard

Each step preserves trace refinement; transitivity yields whole-program property.


16. Turn a program into a PLM dependence graph

The optimizer does not need a full Python dataflow graph; for each Host call hh it only creates:

Ph→Lh→Mh.P_h\rightarrow L_h\rightarrow M_h.

1. Basic edges

param definitions ───────────────> P_h or L_h
P_h ───────────────────> L_h
L_h ───────────────────> M_h
M_h ───────────────────> result consumers

If parameter is source-time known, definitions can target PhP_h; if parameter is produced only by CPython, preparation can only occur before LhL_h.

2. Control edge

A logical call must be controlled by original branch:

condition→Lh.condition\rightarrow L_h.

If speculation is not allowed:

condition→Ph.condition\rightarrow P_h.

If silent speculation is allowed, the control edge to PhP_h can be removed, but the edge to LhL_h cannot be removed.

This precisely expresses:

physical preparation can cross unknown control flow; logical occurrence cannot.

3. Effect edge

If a statement can change operation world footprint, authority, or validity:

effect→Lh.effect\rightarrow L_h.

If even Prepare is affected, the edge targets PhP_h as well.

4. Exception edge

If failure of a previous operation prevents a later logical call:

Mi→Lj.M_i\rightarrow L_j.

You can prepare jj early, but cannot let jj produce logical effects early.

5. Temporal order edge

Two reads are not always swappable:

a = current_price()
b = current_price()

If they lack a shared snapshot, original program requires logical order:

La→Lb.L_a\rightarrow L_b.

That is, classic read/read independence is insufficient in temporal worlds.


17. Formalized optimization objective

In an ideal world with infinite resources, with fixed dependency graph latency and total work, the lower bound is set by critical path; work/span models characterize parallel computation by total work and longest dependency chain.[13]

PLM’s simplest scheduling policy is:

Ph→ASAPP_h\rightarrow ASAP Lh→fixed at original logical pointL_h\rightarrow \text{fixed at original logical point} Mh→ALAPM_h\rightarrow ALAP

That is:

Prepare early, Linearize in-place, Materialize as late as dependency allows.

But real systems also have:

branch hit probability
candidate expiration probability
provider cost
rate limit
memory
concurrent quota
cancel cost
unknown latency

So optimal scheduling is a policy, not a fixed topological order.

You can define:

π∗=arg⁡min⁡πE[Treturn+λCprovider+μCwaste+νCcontention].\pi^* = \arg\min_\pi \mathbb E[ T_{\mathrm{return}} + \lambda C_{\mathrm{provider}} + \mu C_{\mathrm{waste}} + \nu C_{\mathrm{contention}} ].

Global optimum is unnecessary. The following simple optimizations are already effective.


18. Practical optimization 1: Source-closed Prepare

If the streaming analyzer sees:

tax = tax_table("UK")

and proves:

tool identity known
parameters are literal / sealed constant
authority known
Prepare silent

it creates immediately:

candidate = prepare(site, TAX_TABLE, ("UK",))

At final runtime, at the original site:

job = linearize_or_start(
    candidate,
    TAX_TABLE,
    actual_args=("UK",),
)
tax = materialize(job)

It must dynamically check:

source seal
site fingerprint
tool identity
actual args digest
authority epoch
logical run
temporal evidence

Fallback if mismatch.

This is a high-value, low-complexity optimization because runtime guards turn many static proofs into “mistakes only lose performance.”


19. Practical optimization 2: Prepare hoisting within a basic block

Original code:

a = A()
x = pure_local_1()
b = B()
y = pure_local_2()
c = C()
return combine(a, b, c, x, y)

If A/B/C parameters are known, it becomes:

_pA = prepare(A)
_pB = prepare(B)
_pC = prepare(C)

_jA = linearize_or_start(_pA, A)
a = materialize(_jA)

x = pure_local_1()

_jB = linearize_or_start(_pB, B)
b = materialize(_jB)

y = pure_local_2()

_jC = linearize_or_start(_pC, C)
c = materialize(_jC)

return combine(a, b, c, x, y)

Even with no Materialize movement, all three calls can overlap.

The compiler only needs to:

  1. keep Python A-normalized left-to-right evaluation;
  2. scan forward inside basic block;
  3. hoist Prepare across statements known not to affect parameters, authority, or effects;
  4. stop on unknown calls, exception boundaries, or mutation.

20. Practical optimization 3: Local Materialize sinking

Original code:

a = read_immutable()
x = pure_noexcept()
y = another_pure_noexcept(x)
return use(a, y)

If read_immutable is:

total
noexcept
immutable result

then:

_p = prepare(read_immutable)
_j = linearize_or_start(_p, read_immutable)

x = pure_noexcept()
y = another_pure_noexcept(x)

a = materialize(_j)
return use(a, y)

Materialize can overlap with local computation.

Implementation uses a conservative commutation predicate:

can_sink(M, statement) =
    statement.pure
    and statement.noexcept
    and not reads_result
    and not observes_locals
    and not changes_context

Unknown means false.

21. Practical optimization 4: Branch speculation

Original code:

if cond:
    x = fallback("premium")
else:
    x = fallback("basic")

If both calls satisfy:

Prepare silent
result immutable
low cost
cancelable/discardable

they may be prepared while cond is unknown, and only one path is linearized.

Expected benefit can be roughly estimated as:

Uh=Preach(h)⋅Pvalid(h)⋅hiddenLatency⁡(h)−λCh−μCcontention.U_h = P_{\mathrm{reach}}(h) \cdot P_{\mathrm{valid}}(h) \cdot \operatorname{hiddenLatency}(h) - \lambda C_h - \mu C_{\mathrm{contention}}.

Only speculate when:

Uh>0U_h>0

No complex ML scheduler needed. A simple first version can use:

at most N speculative jobs per run
at most M jobs per tool
allow only low-cost immutable reads

22. Practical optimization 5: Just-in-time Prepare

With time validity, “earlier is always better” is no longer universally correct.

Suppose:

We want:

pP+L≤p^Lp_P+L\le \hat p_L

so it completes before the call, and:

p^L≤pP+L+δ\hat p_L\le p_P+L+\delta

so it is still valid when called.

So a suitable issue window is:

p^L−L−δ≤pP≤p^L−L.\boxed{ \hat p_L-L-\delta \le p_P \le \hat p_L-L. }

Examples:

This is a simple but highly discriminative temporal scheduling optimization.

23. Practical optimization 6: Demand promotion and cancellation

Host job priority:

waiting to be materialized
    >
entered real control path
    >
same-path source-time prepare
    >
unknown branch speculation

When Python calls:

materialize(job)

Host promotes the job to highest priority.

After branch is determined, discard unchosen candidates:

discard(candidate)

and cancel quickly to avoid speculative work consuming critical-path provider quota.

24. Practical optimization 7: Singleflight and cache unification

If multiple logical runs request:

same immutable operation
same parameters
same authority-compatible context

Host can share one physical work:

one physical candidate
    ├── private adoption for run A
    ├── private adoption for run B
    └── cache backing

But must maintain:

logical handle per run
private result ownership
mutable data private copy / COW
cancellation per run
authority checked independently

This is the generalization of “shared physical production, separate sandboxes”:

Shared physical work, separate logical ownership.\boxed{ \text{Shared physical work, separate logical ownership.} }

25. Implementation sketch

1. Compiler pipeline

sealed source
    ↓
parse AST
    ↓
mark Host capability calls
    ↓
A-normalisation
    ↓
CFG / control dependence / exception region
    ↓
def-use / mutation / effect facts
    ↓
create P-L-M nodes
    ↓
Prepare hoisting
    ↓
Materialize sinking
    ↓
AST rewrite
    ↓
verifier
    ↓
synchronous CPython

Streaming analyzer uses more conservative prefix facts and handles only source-closed sites.

2. Minimal ABI

candidate = __pysolate_prepare__(
    site_token,
    tool_id,
    frozen_args,
    contract_token,
)

job = __pysolate_linearize__(
    site_token,
    tool_id,
    actual_args,
    candidate,
)

value = __pysolate_materialize__(job)

__pysolate_discard__(candidate)

Actual fusions are possible:

3. Host job state

CREATED
  ↓
PREPARING
  ├── PENDING
  ├── CANDIDATE_READY
  ├── TENTATIVE_FAILED
  └── CANCELLED

LINEARIZED
  ├── ADOPTED
  ├── VALIDATING
  └── CANONICAL_RUNNING

MATERIALIZED
  ├── RETURNED
  └── RAISED

4. Tool contract

temporal:
    IMMUTABLE
    SNAPSHOT
    VERSIONED
    LEASED
    CURRENT
    WALLCLOCK_OBSERVING

effect:
    SILENT_READ
    PREPARE_COMMIT
    NON_STAGEABLE

speculation:
    NEVER
    SAME_PATH
    BUDGETED

failure:
    STABLE
    RETRY_AT_LINEARIZE
    VALIDATE_AT_LINEARIZE
authority:
    BIND_AT_PREPARE
    RECHECK_AT_LINEARIZE
    RECHECK_AT_COMMIT

5. Verifier

At minimum, verify:

each candidate/job created only by internal ABI
Materialize definition dominates all uses
speculation is used only under allowed contract
Prepare parameters and runtime actual args are checkable
does not cross forbidden exception/effect/authority region
dynamic occurrence reuse across loops is not incorrect
untaken candidate has discard/cancellation path
traceback/source mapping preserved

If verifier fails, fallback to original synchronous execution.


26. What PLM explicitly does not cover

A useful abstraction must say what it does not do.

1. Not arbitrary local Python constant folding

For example:

x = expensive_pure_python(a)

PLM does not execute this during streaming, and does not auto partial-evaluate it.

The following belong to traditional compiler / partial evaluation:

constant folding
function specialization
loop unrolling
dead local computation elimination
NumPy graph fusion
local pure function memoization

These can combine with PLM, but are not the same mechanism.

2. Not full future propagation

PLM does not turn:

a + b
a["id"]
obj.method()

into Future graph, nor auto-generate Future[Future[T]].

Therefore, after any local Python computation, any subsequent Host call must wait for CPython to compute the argument.

3. Not full local CPU computation parallel scheduling

If:

a = A()          # 1s
cpu_work()       # 10s
x = a + 1
b = B(x)      # 20s

A finishes in 1 second, but the main Python thread is doing cpu_work() so B starts at 10 seconds.

To automatically start B at the 1-second mark, we would need:

continuation scheduler
second Python context
thread/process parallelism
safe code motion
Host expression IR

This is beyond minimal PLM.

4. Not arbitrary loop-iteration parallelization

for x in xs:
    y = tool(x)

Per-iteration splitting is easy; fan-out of all iterations in batch would change:

failure timing
resource peak
variable bindings
partial-effect ordering
break/continue
iterator behavior

It needs separate loop transformations and proofs.

5. Not unsafe current reads

If operation semantics are “true state at call time” and provider does not offer:

snapshot
version
lease
validation

then final value cannot be safely pre-read. At best, one can pre-prepare connection or authentication stages that are time-insensitive.

6. Not arbitrary non-reversible side effects

Real effects:

sending email
a charge
deleting resource
pushing commit

cannot happen early under branch speculation unless the provider supports explicit prepare/commit or compensation.

7. Not all CPython reflective semantics

These capabilities observe AST transforms or variable-binding timing:

locals()
inspect.currentframe()
sys.settrace()
eval()
exec()

along with:

guest threads
signals
real wall-clock
a mutable object with identity sensitivity
arbitrary native extension

They must be blocked, virtualized, or treated as optimization barriers.

8. Not guaranteed globally optimal schedule

Real latency, branch probabilities, provider contention, and temporal invalidation are unknown. Global optimum is an online stochastic scheduling problem.

PLM provides:

normalized valid scheduling space
safe local transformations
a conservative greedy policy

not a universal optimizer oracle.


27. Optimizations not yet feasible, but worth studying

1. Restricted Python continuation

Allow a limited set of proven pure Python operators to run after Host Future completion:

A result
  ↓
pure projection / arithmetic
  ↓
B request

This can shorten data-dependent chains but introduces a small continuation runtime. The key research question is: how to bound it without reimplementing Python.

2. Typed projection IR

Support only:

JSON field
tuple index
primitive arithmetic
comparison
formatting

This allows:

B(a["id"])

to be auto-started by Host after A completes. But Python’s __getitem__ is not always equivalent to JSON projection, so it must apply only to typed Host results.

3. Cross-basic-block global scheduling

Using full PDG, dominance, post-dominance, and effect summary, prepare can be hoisted further and materialize sinked further. Yields potentially better performance, but exception and reflection boundaries significantly increase proof complexity.

4. Profile-guided temporal scheduling

Learn from historical traces:

call-site arrival time
branch reach probability
provider latency distribution
candidate invalidation probability

Then choose just-in-time Prepare timing.

5. Speculative branch portfolio

Under a cost budget choose branches worth pre-starting instead of starting everything:

max⁡S∑h∈SPreach(h)Pvalid(h)benefit⁡(h)\max_{S} \sum_{h\in S} P_{\mathrm{reach}}(h) P_{\mathrm{valid}}(h) \operatorname{benefit}(h)

subject to:

∑h∈Scost(h)≤B.\sum_{h\in S} cost(h)\le B.

6. Automatic derive prepare/commit

For some side-effecting APIs, automatically decompose request into:

construct
upload temporary
validate
commit

This typically requires provider-specific protocol and cannot usually be inferred from Python AST alone.

7. Combine with local partial evaluation

If future work adds pure-function proof, typed arrays, or restricted numerical sublanguage, PLM could in parallel:

That would extend PLM into a fuller staged optimizer, but should be separate layers rather than folded into the minimal semantic core.


Conclusion: not execute earlier, but schedule work more intelligently

Pysolate is often misread as:

“As the model generates one line, we execute that line.”

A more accurate and stronger statement is:

We never execute Python during source streaming. We only use facts already exposed by source to prepare physical work early that may later be adopted by logical calls.

Control flow, object semantics, exceptions, and intermediate state are still decided by one synchronous CPython run after sealing. The Host only owns:

physical candidate
validity evidence
pending job
authority contract
effect protocol

The three-phase normal form compresses this idea into:

Prepare at the earliest useful physical time;\boxed{ \textbf{Prepare at the earliest useful physical time;} } Linearize at the original semantic point;\boxed{ \textbf{Linearize at the original semantic point;} } Materialize at the latest observationally safe point.\boxed{ \textbf{Materialize at the latest observationally safe point.} }

Or shorter:

Physical work early, logical effects in place, observation late.\boxed{ \text{Physical work early, logical effects in place, observation late.} }

Its generality comes from a simple fact: more of today’s latency happens not in local arithmetic but at controlled system boundaries. As long as that boundary can separate prepare, activate, and deliver, a program need not change its synchronous mental model to gain parallelism.

This may be Pysolate’s most important design philosophy:

Do not execute an incomplete program early; complete early the work that the full program may need later.\boxed{ \text{Do not execute an incomplete program early; complete early the work that the full program may need later.} }

References

[1] Barbara Liskov and Liuba Shrira. Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems. PLDI, 1988.

[2] Robert H. Halstead Jr. Multilisp: A Language for Concurrent Symbolic Computation. ACM TOPLAS, 1985.

[3] Yury Selivanov. PEP 492 — Coroutines with async and await syntax; Python asyncio official documentation.

[4] Honghua Dong et al. APPL: A Prompt Programming Language for Harmonious Integration of Programs and Large Language Model Prompts. ACL, 2025; arXiv preprint: 2406.13161.

[5] Jeanne Ferrante, Karl J. Ottenstein, and Joe D. Warren. The Program Dependence Graph and Its Use in Optimization. ACM TOPLAS, 1987.

[6] John Launchbury. A Natural Semantics for Lazy Evaluation. POPL, 1993.

[7] Neil D. Jones, Carsten K. Gomard, and Peter Sestoft. Partial Evaluation and Automatic Program Generation. 1993; Walid Taha and Tim Sheard. Multi-stage Programming with Explicit Annotations. 1997.

[8] Maurice P. Herlihy and Jeannette M. Wing. Linearizability: A Correctness Condition for Concurrent Objects. ACM TOPLAS, 1990.

[9] John M. Lucassen and David K. Gifford. Polymorphic Effect Systems. POPL, 1988.

[10] Cary G. Gray and David R. Cheriton. Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency. SOSP, 1989.

[11] Luca Beurer-Kellner, Marc Fischer, and Martin Vechev. Prompting Is Programming: A Query Language for Large Language Models. PLDI, 2023.

[12] Lianmin Zheng et al. SGLang: Efficient Execution of Structured Language Model Programs. NeurIPS, 2024.

[13] Robert D. Blumofe and Charles E. Leiserson. Scheduling Multithreaded Computations by Work Stealing. JACM, 1999.