Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion examples/polonius_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from __future__ import annotations

from srdatalog.dsl import SPLIT, Filter, Program, Relation, Var
from srdatalog.ir.dialects.parallel.atomic_ws.pragmas.work_stealing import (
WorkStealing,
)

# ----- Relations ----------------------------------------------

Expand Down Expand Up @@ -396,7 +399,8 @@ def build_poloniusdb_program() -> Program:
)
)
.named('subset_trans')
.with_plan(delta=0, work_stealing=True),
.with_plan(delta=0)
.with_pragma(WorkStealing()),
(
subset(origin1, origin2, point2)
<= subset(origin1, origin2, point1)
Expand Down
18 changes: 10 additions & 8 deletions src/srdatalog/dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,11 @@ class PlanEntry:

The pragma flags flow through to HirRuleVariant so codegen sees them:
- fanout -> fan-out work-stealing for Cartesian products
- work_stealing -> mid-level work-stealing (task queue + steal loop)
- block_group -> block-group work partitioning
- dedup_hash -> GPU hash table for in-kernel existential dedup
Mid-level work-stealing is no longer a `with_plan(...)` keyword:
it migrated to `Rule.with_pragma(WorkStealing())` in C4 and the
shadow bool field was retired in A3-2.
`balanced_root` / `balanced_sources` drive balanced partitioning for
skewed joins (not yet lowered in Python).

Expand All @@ -360,7 +362,6 @@ class PlanEntry:
var_order: tuple[str, ...] = ()
clause_order: tuple[int, ...] = ()
fanout: bool = False
work_stealing: bool = False
block_group: bool = False
dedup_hash: bool = False
balanced_root: tuple[str, ...] = ()
Expand Down Expand Up @@ -414,21 +415,22 @@ def with_plan(
var_order: tuple[str, ...] | list[str] | None = None,
clause_order: tuple[int, ...] | list[int] | None = None,
fanout: bool = False,
work_stealing: bool = False,
block_group: bool = False,
dedup_hash: bool = False,
balanced_root: tuple[str, ...] | list[str] | None = None,
balanced_sources: tuple[str, ...] | list[str] | None = None,
) -> Rule:
'''Append a single PlanEntry. Can be called multiple times to add
entries for different deltas (or use .with_plans(entries) to replace).

Note: the legacy `work_stealing=True` keyword was retired in
A3-2 — use `.with_pragma(WorkStealing())` instead.
'''
entry = PlanEntry(
delta=delta,
var_order=tuple(var_order) if var_order is not None else (),
clause_order=tuple(clause_order) if clause_order is not None else (),
fanout=fanout,
work_stealing=work_stealing,
block_group=block_group,
dedup_hash=dedup_hash,
balanced_root=tuple(balanced_root) if balanced_root is not None else (),
Expand Down Expand Up @@ -750,10 +752,10 @@ def _validate_pragma_registered(pragma: Pragma) -> None:
'srdatalog.ir.dialects.relation.sorted_array.pragmas.dedup_hash.DedupHash',
'dedup_hash',
),
(
'srdatalog.ir.dialects.parallel.atomic_ws.pragmas.work_stealing.WorkStealing',
'work_stealing',
),
# A3-2: `WorkStealing` shadow entry removed alongside the
# `PlanEntry.work_stealing` / `mir.ExecutePipeline.work_stealing`
# field deletion. The typed pragma is now the sole driver; see
# `docs/phase_a3_remove_deprecated_bool_fields.md` §3.2.
# C6: `FanOut` shadows `PlanEntry.fanout` -> `variant.fanout` ->
# `ep.use_fan_out`. Same dual-write contract as `DedupHash`:
# `with_pragma(FanOut())` sets both `fanout=True` on each plan
Expand Down
5 changes: 3 additions & 2 deletions src/srdatalog/ir/codegen/cuda/batchfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
)
from srdatalog.ir.codegen.cuda.schema import SchemaDefinition
from srdatalog.ir.hir.types import Version
from srdatalog.ir.mir.passes import ep_has_work_stealing

# -----------------------------------------------------------------------------
# Prelude
Expand Down Expand Up @@ -360,7 +361,7 @@ def generate_runner(
ctx.output_name = output_var
full += generate_pipeline(mutable_pipeline, ctx)

if pipeline.work_stealing:
if ep_has_work_stealing(pipeline):
full += " // TODO: Implement work-stealing logic (jit_kernel.nim 1084-1589)\n"

# -- LaunchParams struct --
Expand All @@ -386,7 +387,7 @@ def generate_runner(
uint64_t work_per_warp = 0;'''
for i, _ in enumerate(pipeline.dest_specs):
full += f"\n uint32_t old_size_{i} = 0;"
if pipeline.work_stealing:
if ep_has_work_stealing(pipeline):
full += "" # jitWSLaunchParamsFields() TODO
full += "\n };\n\n"

Expand Down
5 changes: 3 additions & 2 deletions src/srdatalog/ir/codegen/cuda/complete_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
has_tiled_cartesian_eligible,
)
from srdatalog.ir.codegen.cuda.plugin import plugin_gen_host_view_setup, plugin_view_count
from srdatalog.ir.mir.passes import ep_has_work_stealing

# Pure-template phase emitters now live in the dialect's runner module.
# Local aliases preserve the legacy call sites until the rest of this
Expand Down Expand Up @@ -922,7 +923,7 @@ def gen_complete_runner(
)

# Feature-flag guards: not covered in Phase 2 baseline port.
if node.work_stealing:
if ep_has_work_stealing(node):
raise NotImplementedError("gen_complete_runner: work_stealing not yet ported")
if has_balanced_scan(node.pipeline):
raise NotImplementedError("gen_complete_runner: balanced-scan runner not yet ported")
Expand Down Expand Up @@ -958,7 +959,7 @@ def gen_complete_runner(
# can't interleave safely with concurrent kernels into the same region.
tiled_cartesian_eligible = (
has_tiled_cartesian_eligible(mutable_pipe)
and not node.work_stealing
and not ep_has_work_stealing(node)
and not node.block_group
and not node.dedup_hash
and not is_count
Expand Down
1 change: 0 additions & 1 deletion src/srdatalog/ir/codegen/cuda/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ class RunnerGenState:
dest_arities: list[int] = field(default_factory=list)
total_view_count: int = 0
is_balanced: bool = False
is_work_stealing: bool = False
is_block_group: bool = False
is_dedup_hash: bool = False
is_count: bool = False
Expand Down
12 changes: 8 additions & 4 deletions src/srdatalog/ir/codegen/cuda/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

import srdatalog.ir.mir.types as m
from srdatalog.ir.hir.types import Version
from srdatalog.ir.mir.passes import ep_has_work_stealing

# -----------------------------------------------------------------------------
# C++ type expression generators
Expand Down Expand Up @@ -173,7 +174,7 @@ def _gen_execute_pipeline(

has_fused = (
not instr.dedup_hash
and not instr.work_stealing
and not ep_has_work_stealing(instr)
and not instr.block_group
and len(instr.dest_specs) == 1
)
Expand Down Expand Up @@ -243,7 +244,10 @@ def _gen_parallel_group(
i2 = indent + " "

all_fused_eligible = all(
not op.dedup_hash and not op.work_stealing and not op.block_group and len(op.dest_specs) == 1
not op.dedup_hash
and not ep_has_work_stealing(op)
and not op.block_group
and len(op.dest_specs) == 1
for op in exec_ops
)

Expand Down Expand Up @@ -277,7 +281,7 @@ def _gen_parallel_group(
single_dest_indices: list[int] = []
multi_head_indices: list[int] = []
for idx, op in enumerate(exec_ops):
if len(op.dest_specs) == 1 and not op.work_stealing and not op.dedup_hash:
if len(op.dest_specs) == 1 and not ep_has_work_stealing(op) and not op.dedup_hash:
single_dest_indices.append(idx)
else:
multi_head_indices.append(idx)
Expand Down Expand Up @@ -406,7 +410,7 @@ def _gen_parallel_group(
multi_in_dest: list[int] = []
for rule_idx in rule_indices:
op = exec_ops[rule_idx]
if len(op.dest_specs) == 1 and not op.work_stealing and not op.dedup_hash:
if len(op.dest_specs) == 1 and not ep_has_work_stealing(op) and not op.dedup_hash:
single_in_dest.append(rule_idx)
else:
multi_in_dest.append(rule_idx)
Expand Down
10 changes: 7 additions & 3 deletions src/srdatalog/ir/dialects/parallel/atomic_ws/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@

C4 scope (per spec §5, PR row C4): only the kernel-functor-level
WS emit variants migrate to typed pragma + wrap op. The runner-side
scaffolding (WCOJTask queue construction, steal-loop emission) stays
on the legacy `ExecutePipeline.work_stealing: bool` path; A3 will
drop the bool field and the C4 lowering will become the sole driver.
scaffolding (WCOJTask queue construction, steal-loop emission) was
historically gated by the deprecated `ExecutePipeline.work_stealing:
bool` field; A3-2 retired that field, leaving the C4 lowering as
the sole driver for the kernel-functor-level emit. Runner-side
WS scaffolding integration is a separate follow-up that consumes
the typed `WorkStealing` pragma (or its post-pass `WSScope` wrap
op) directly.

Ops currently registered: none — the only WS-specific op today is
the MIR wrap op `mir.WSScope`, which lives next to `DedupGate` in
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
'''Pragma: `work_stealing` — atomic work-stealing kernel-functor emit.

Trigger: `Rule(...).with_pragma(WorkStealing())` (preferred typed
form) or the legacy `with_plan(..., work_stealing=True)` keyword
(back-compat, slated for removal in A3).
Trigger: `Rule(...).with_pragma(WorkStealing())` (the sole typed
form post-A3-2; the legacy `with_plan(..., work_stealing=True)`
keyword and the `mir.ExecutePipeline.work_stealing: bool` field
have been deleted).

Materialization (this module): wrap each `mir.InsertInto` at the
tail of an `ExecutePipeline.pipeline` with `mir.WSScope(inner=
...)` during `MirPragmaPass`, then strip the `WorkStealing`
instance from the EP's `pragmas` tuple.
instance from the EP's `pragmas` tuple. Unconditional after A3-2
— there is no dual-write bool-shadow to short-circuit on.

Lowering (this module): a `@lowering(target=iir.cf, source=mir.
WSScope)` rule (registered via the parent dialect's
Expand All @@ -23,18 +25,20 @@
The lowering body delegates to the legacy `_lower_insert_into`
with `ctx.ws_enabled` flipped True for the duration of the call,
so the emission is literally the same code path the legacy
bool-field flow produces (byte-equivalence by construction).
bool-field flow produced (byte-equivalence by construction).

C4 scope (per `docs/phase_c_pragma_materialization.md` §5, PR row
C4 + §4.2 sub-dialect description): only the kernel-functor-level
WS emit variants migrate. The runner-level WS scaffolding (WCOJTask
queue, steal-loop emission, etc.) stays on the legacy
`ExecutePipeline.work_stealing: bool` field path; A3 drops the bool
and the wrap op becomes the sole driver.
queue, steal-loop emission, etc.) was historically gated by the
deleted `ExecutePipeline.work_stealing: bool` field — A3-2 retires
that field; future runner-side scheduling integration is a
separate work item that consumes the typed pragma directly.

Spec: `docs/phase_c_pragma_materialization.md` §4.2, §5 (C4 PR
row), §5.1 (per-PR acceptance gate); `docs/pragma_as_typed_object.
md` §2 (Pragma base class), §3 (`@pragma_handler` decorator).
md` §2 (Pragma base class), §3 (`@pragma_handler` decorator);
`docs/phase_a3_remove_deprecated_bool_fields.md` §3.2 (A3-2 cut).
'''

from __future__ import annotations
Expand Down Expand Up @@ -99,7 +103,7 @@ def materialize_work_stealing(
op: Any, # ExecutePipeline at runtime — typed as Any to match the registry's
# Callable[[Any, Pragma, PragmaCtx], Any] signature (see
# core/pragma.py:PragmaRegistration). Body re-narrows via the typed
# `op.pragmas` / `op.work_stealing` accesses below.
# `op.pragmas` access below.
pragma: Any, # WorkStealing at runtime; same Any reason.
ctx: Any, # PragmaCtx, unused for this no-config pragma.
) -> Any:
Expand All @@ -108,32 +112,19 @@ def materialize_work_stealing(

Returns a new `ExecutePipeline` with:
- `pipeline` rewritten so every `InsertInto` is replaced by
`WSScope(inner=that_insert)` — **except** in the C4
dual-write transition state (see below),
`WSScope(inner=that_insert)`,
- `pragmas` filtered to drop the consumed `WorkStealing`
instance (per the `MirPragmaPass` post-flight invariant; see
`docs/pragma_as_typed_object.md` §3).

C4 dual-write transition: the DSL `Rule.with_pragma(WorkStealing
())` sets BOTH the typed pragma AND the legacy `work_stealing:
bool` field on the resulting `ExecutePipeline`, so the legacy
emitter (`compile_kernel_body` -> `_lower_insert_into`'s
`if ctx.ws_enabled:` branch, plus the runner-side WS scaffolding
in `codegen/cuda/batchfile.py` / `orchestrator.py`) keeps
producing byte-equivalent output without seeing the new `WSScope`
wrap op (which the monolith doesn't know how to traverse). When
`ep.work_stealing` is True we therefore SKIP the wrap step:
stripping the pragma is enough to satisfy `MirPragmaPass`'s
post-flight invariant, and the bool field continues to drive
emission via the legacy path.

When `ep.work_stealing` is False (the post-A3 pure-typed state,
or test fixtures that exercise only the typed surface), the
handler produces the wrap op and the registered
`@lowering(target=iir.cf, source=WSScope)` rule lowers it. The
resulting IIR is byte-equivalent to the WS branch of
`_lower_insert_into` by construction (the lowering delegates to
that helper with `ctx.ws_enabled` flipped True for the call).
Unconditional after A3-2: the legacy `ExecutePipeline.
work_stealing: bool` field has been retired (per
`docs/phase_a3_remove_deprecated_bool_fields.md` §3.2), so the
pragma is the sole emission driver. The registered `@lowering(
target=iir.cf, source=WSScope)` rule delegates back to
`_lower_insert_into` with `ctx.ws_enabled` flipped True for the
call, so the resulting IIR is byte-equivalent to the WS branch
of the legacy lowering by construction.

Idempotency: the EP carries at most one `WorkStealing` (the DSL
constructs at most one per `with_pragma(WorkStealing())` call;
Expand All @@ -147,10 +138,6 @@ def materialize_work_stealing(
trailing `InsertInto` run is exactly the leaf position.
'''
new_pragmas = tuple(p for p in op.pragmas if not isinstance(p, WorkStealing))
# Dual-write transition: skip the wrap if the legacy bool is set
# (the legacy lowering will handle emission). See docstring above.
if op.work_stealing:
return dataclasses.replace(op, pragmas=new_pragmas)
new_pipeline = _wrap_inserts_in_ws_scope(list(op.pipeline))
return dataclasses.replace(op, pipeline=new_pipeline, pragmas=new_pragmas)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,24 @@ def lower_scan_pipeline(
registered `@lowering`) because the wrap op only carries the
root; the trailing `rest = ops[1:]` lives on the enclosing
pipeline and is naturally accessible at this dispatch site.

A3-2: trailing `WSScope(InsertInto)` wrap ops (inserted by
`materialize_work_stealing`) are unwrapped in place + the
enclosing call is wrapped in a `ctx.ws_enabled=True` save/restore.
Mirrors the legacy bool-driven path: the same `_lower_insert_into`
body fires with `ctx.ws_enabled` set, byte-equivalent by
construction.
'''
has_ws_scope = any(isinstance(o, mir.WSScope) for o in ops)
if has_ws_scope:
unwrapped: list[mir.MirNode] = [o.inner if isinstance(o, mir.WSScope) else o for o in ops]
prev_ws = ctx.ws_enabled
try:
ctx.ws_enabled = True
return lower_scan_pipeline(unwrapped, ctx)
finally:
ctx.ws_enabled = prev_ws

head = ops[0] if ops else None
if isinstance(head, mir.BlockGroupRoot):
inner = head.inner
Expand Down Expand Up @@ -2620,21 +2637,17 @@ def _lower_insert_into(node: mir.InsertInto, ctx: LoweringCtx) -> list[Op]:
lowering out of this monolith — until then they are the
byte-equivalence anchor for the dual-write transition.

DEAD CODE NOTE (C4): the `if ctx.ws_enabled:` branch below
(count-phase WS, search for `ws_enabled`) is superseded by the
Post-A3-2: the `if ctx.ws_enabled:` branch below (count-phase WS,
search for `ws_enabled`) is reached only via the
`@lowering(target=iir.cf, source=mir.WSScope)` rule registered by
`srdatalog.ir.dialects.parallel.atomic_ws.pragmas.work_stealing`.
Same dual-write contract as C2: the new lowering delegates back
into this helper with `ctx.ws_enabled` forced True, so the branch
remains functionally LIVE during the C4 transition (the
`with_pragma(WorkStealing())` DSL dual-writes both the bool field
AND the typed pragma; see `pragmas/work_stealing.py:
materialize_work_stealing`). The branch will be removable once
Phase A3 drops the `work_stealing: bool` field on
`mir.ExecutePipeline`. The `ws_cartesian_valid_var` branch
further below is unrelated to the C4 migration — it's a separate
legacy plumbing path (batched-Cartesian WS materialize) not
driven by `ctx.ws_enabled` and not in C4 scope.
`srdatalog.ir.dialects.parallel.atomic_ws.pragmas.work_stealing`,
which delegates back into this helper with `ctx.ws_enabled`
forced True. The deprecated `ExecutePipeline.work_stealing: bool`
shadow field that previously dual-drove this branch has been
retired (per `docs/phase_a3_remove_deprecated_bool_fields.md`
§3.2). The `ws_cartesian_valid_var` branch further below is
unrelated — it's a separate legacy plumbing path (batched-
Cartesian WS materialize) not driven by `ctx.ws_enabled`.

DEAD CODE NOTE (C6 - count): the `if ctx.is_counting:` branches
in this function (and elsewhere in this monolith) are superseded
Expand Down
Loading
Loading