Skip to content

Commit 381317c

Browse files
authored
Merge pull request #2895 from GaijinEntertainment/bbatkin/linq-fold-emission-lanes-pr2
linq_fold: Phase F PR 2 — 4 array-side Terminator leaves migrate to TerminatorSpec
2 parents 619072b + 1104f84 commit 381317c

2 files changed

Lines changed: 64 additions & 50 deletions

File tree

daslib/linq_fold.das

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,23 +1073,26 @@ def private build_accumulator_perelement_stmts(opName : string;
10731073
def private emit_counter_lane(var top : Expression?; srcName, accName, itName : string; names : RangeStateNames;
10741074
var skipExpr, takeExpr, skipWhileCond : Expression?;
10751075
var loopBody : Expression?; at : LineInfo) : Expression? {
1076-
// Counter lane: `[skip/take init]; var acc = 0; for (it in src) { $loopBody }; return acc`
1077-
var topExpr = clone_expression(top)
1078-
topExpr.genFlags.alwaysSafe = true
1079-
var srcParamType = invoke_src_param_type(top)
1080-
var bodyStmts : array<Expression?>
1081-
append_ranges_prelude(bodyStmts, skipExpr, takeExpr, skipWhileCond, names)
1082-
bodyStmts |> push_from <| qmacro_block_to_array() {
1076+
// loopBody is caller-pre-wrapped (where/binds/post-take/in-loop ranges); spec only handles prelude+prologue+tail.
1077+
var prologue : array<Expression?>
1078+
append_ranges_prelude(prologue, skipExpr, takeExpr, skipWhileCond, names)
1079+
prologue |> push_from <| qmacro_block_to_array() {
10831080
var $i(accName) = 0
1084-
for ($i(itName) in $i(srcName)) {
1085-
$e(loopBody)
1086-
}
1081+
}
1082+
var perElementStmts : array<Expression?>
1083+
perElementStmts |> push(loopBody)
1084+
var tailStmts <- qmacro_block_to_array() {
10871085
return $i(accName)
10881086
}
1089-
var res = qmacro(invoke($($i(srcName) : $t(srcParamType)) {
1090-
$b(bodyStmts)
1091-
}, $e(topExpr)))
1092-
return finalize_invoke(res, at)
1087+
var laneTops : array<Expression?>
1088+
laneTops |> push(top)
1089+
var laneSrcs : array<string>
1090+
laneSrcs |> push(srcName)
1091+
return emit_terminator_lane(TerminatorSpec(
1092+
prologue <- prologue,
1093+
perElementStmts <- perElementStmts,
1094+
tailStmts <- tailStmts
1095+
), build_lane_adapter(laneTops, laneSrcs), at)
10931096
}
10941097

10951098
[clone(top), macro_function]
@@ -1155,6 +1158,8 @@ struct private TerminatorSpec {
11551158
takeWhileCond : Expression?
11561159
rangeNames : RangeStateNames
11571160
tailStmts : array<Expression?> // post-loop; INCLUDES the return
1161+
outElemType : TypeDeclPtr // invoke retType; null = infer
1162+
wrapIter : bool // adapter_wrap_invoke wraps emission with .to_sequence_move()
11581163
}
11591164

11601165
[macro_function]
@@ -1174,7 +1179,7 @@ def private emit_terminator_lane(var spec : TerminatorSpec; adapter : SourceAdap
11741179
bodyStmts |> push_from(spec.prologue)
11751180
bodyStmts |> push <| adapter_wrap_source_loop(adapter, loopBody, at)
11761181
bodyStmts |> push_from(spec.tailStmts)
1177-
return adapter_wrap_invoke(adapter, bodyStmts, null, false, at)
1182+
return adapter_wrap_invoke(adapter, bodyStmts, spec.outElemType, spec.wrapIter, at)
11781183
}
11791184

11801185
// Bridge for orchestrators that still take parallel (topExprs, srcNames) arrays. 1-source → Array;
@@ -2075,36 +2080,30 @@ def private emit_streaming_min(var c : Captures; var ctx : EmitCtx; at : LineInf
20752080
var elemType = clone_type(oc.orderElemType)
20762081
var lessTest = make_inline_less_call(oc.orderKey, oc.orderName,
20772082
qmacro($i(bindName)), qmacro($i(bestName)), at)
2078-
var perElement : Expression? = qmacro_expr() {
2083+
var perElementStmts : array<Expression?>
2084+
perElementStmts |> push <| qmacro_expr() {
20792085
if (!$i(seenName)) {
20802086
$i(bestName) := $i(bindName)
20812087
$i(seenName) = true
20822088
} elif ($e(lessTest)) {
20832089
$i(bestName) := $i(bindName)
20842090
}
20852091
}
2086-
if (oc.whereCond != null) {
2087-
perElement = qmacro_expr() {
2088-
if ($e(oc.whereCond)) {
2089-
$e(perElement)
2090-
}
2091-
}
2092-
}
2093-
let outElemType = (oc.selectLam != null) ? oc.selectElemType : elemType
2094-
var stmts : array<Expression?>
2092+
var outElemType = (oc.selectLam != null) ? oc.selectElemType : elemType
2093+
var prologue : array<Expression?>
2094+
var tailStmts : array<Expression?>
20952095
if (oc.firstName == "first") {
20962096
var firstRetExpr : Expression?
20972097
if (oc.selectLam != null) {
20982098
firstRetExpr = peel_lambda_replace_var(oc.selectLam, qmacro($i(bestName)))
20992099
} else {
21002100
firstRetExpr = qmacro($i(bestName))
21012101
}
2102-
stmts |> push_from <| qmacro_block_to_array() {
2102+
prologue <- qmacro_block_to_array() {
21032103
var $i(bestName) = default<$t(elemType)>
21042104
var $i(seenName) = false
21052105
}
2106-
stmts |> push <| adapter_wrap_source_loop(ctx.src, perElement, at)
2107-
stmts |> push_from <| qmacro_block_to_array() {
2106+
tailStmts <- qmacro_block_to_array() {
21082107
panic("sequence contains no elements") if (!$i(seenName))
21092108
return $e(firstRetExpr)
21102109
}
@@ -2119,18 +2118,23 @@ def private emit_streaming_min(var c : Captures; var ctx : EmitCtx; at : LineInf
21192118
bestRetExpr = qmacro($i(bestName))
21202119
dRetExpr = qmacro($i(dBindName))
21212120
}
2122-
stmts |> push_from <| qmacro_block_to_array() {
2121+
prologue <- qmacro_block_to_array() {
21232122
let $i(dBindName) = $e(oc.firstDefaultExpr)
21242123
var $i(bestName) = default<$t(elemType)>
21252124
var $i(seenName) = false
21262125
}
2127-
stmts |> push <| adapter_wrap_source_loop(ctx.src, perElement, at)
2128-
stmts |> push_from <| qmacro_block_to_array() {
2126+
tailStmts <- qmacro_block_to_array() {
21292127
return $e(bestRetExpr) if ($i(seenName))
21302128
return $e(dRetExpr)
21312129
}
21322130
}
2133-
return adapter_wrap_invoke(ctx.src, stmts, outElemType, false, at)
2131+
return emit_terminator_lane(TerminatorSpec(
2132+
prologue <- prologue,
2133+
whereCond = oc.whereCond,
2134+
perElementStmts <- perElementStmts,
2135+
tailStmts <- tailStmts,
2136+
outElemType = outElemType
2137+
), ctx.src, at)
21342138
}
21352139

21362140
// emit_bounded_heap — take(N) with inline-cmp key. Heap of size N during walk; distinct gate variant + terminal _select.
@@ -2798,27 +2802,30 @@ def private emit_reverse_counter(var c : Captures; var ctx : EmitCtx; at : LineI
27982802
if (c.single |> key_exists("proj")) {
27992803
projection = peel_lambda_rename_var(c.single["proj"].arguments[1], bindName)
28002804
}
2801-
var perElement : Expression?
2805+
var perElementStmts : array<Expression?>
28022806
if (projection != null && has_sideeffects(projection)) {
28032807
let vfinalName = qn("vfinal", at)
2804-
perElement = qmacro_block() {
2808+
perElementStmts |> push_from <| qmacro_block_to_array() {
28052809
var $i(vfinalName) = $e(projection)
28062810
$i(cntName) ++
28072811
}
28082812
} else {
2809-
perElement = qmacro_expr() {
2813+
perElementStmts |> push <| qmacro_expr() {
28102814
$i(cntName) ++
28112815
}
28122816
}
2813-
perElement = wrap_with_condition(perElement, whereCond)
2814-
var stmts <- qmacro_block_to_array() {
2817+
var prologue <- qmacro_block_to_array() {
28152818
var $i(cntName) = 0
28162819
}
2817-
stmts |> push <| adapter_wrap_source_loop(ctx.src, perElement, at)
2818-
stmts |> push_from <| qmacro_block_to_array() {
2820+
var tailStmts <- qmacro_block_to_array() {
28192821
return $i(cntName)
28202822
}
2821-
return adapter_wrap_invoke(ctx.src, stmts, null, false, at)
2823+
return emit_terminator_lane(TerminatorSpec(
2824+
prologue <- prologue,
2825+
whereCond = whereCond,
2826+
perElementStmts <- perElementStmts,
2827+
tailStmts <- tailStmts
2828+
), ctx.src, at)
28222829
}
28232830

28242831
// Rb — walk + overwrite-last scalar (terminator: first / first_or_default).
@@ -2858,7 +2865,7 @@ def private emit_reverse_walk_overwrite_scalar(var c : Captures; var ctx : EmitC
28582865
lastType = strip_const_ref(srcElemType)
28592866
}
28602867
// outElemType drives the invoke retType; terminal _select projects the survivor at return so the lane returns post-select shape.
2861-
let outElemType = (terminalSelectLam != null) ? terminalSelectElemType : lastType
2868+
var outElemType = (terminalSelectLam != null) ? terminalSelectElemType : lastType
28622869
var valueExpr : Expression?
28632870
if (projection != null) {
28642871
valueExpr = clone_expression(projection)
@@ -2867,11 +2874,10 @@ def private emit_reverse_walk_overwrite_scalar(var c : Captures; var ctx : EmitC
28672874
$i(bindName)
28682875
}
28692876
}
2870-
var matchBlock : Expression? = qmacro_block() {
2877+
var perElementStmts <- qmacro_block_to_array() {
28712878
$i(lastName) := $e(valueExpr)
28722879
$i(foundName) = true
28732880
}
2874-
var perElement = wrap_with_condition(matchBlock, whereCond)
28752881
var lastRetExpr : Expression?
28762882
if (terminalSelectLam != null) {
28772883
lastRetExpr = peel_lambda_replace_var(terminalSelectLam, qmacro($i(lastName)))
@@ -2880,30 +2886,36 @@ def private emit_reverse_walk_overwrite_scalar(var c : Captures; var ctx : EmitC
28802886
}
28812887
// first_or_default's user default is already at post-termsel type — re-projecting through `termsel` double-applies.
28822888
var dRetExpr : Expression? = qmacro($i(dBindName))
2883-
var stmts : array<Expression?>
2889+
var prologue : array<Expression?>
28842890
if (terminatorName == "first_or_default") {
2885-
stmts |> push_from <| qmacro_block_to_array() {
2891+
prologue |> push_from <| qmacro_block_to_array() {
28862892
let $i(dBindName) = $e(terminatorCall.arguments[1])
28872893
}
28882894
}
2889-
stmts |> push_from <| qmacro_block_to_array() {
2895+
prologue |> push_from <| qmacro_block_to_array() {
28902896
var $i(foundName) = false
28912897
var $i(lastName) : $t(lastType) = default<$t(lastType)>
28922898
}
2893-
stmts |> push <| adapter_wrap_source_loop(ctx.src, perElement, at)
2899+
var tailStmts : array<Expression?>
28942900
if (terminatorName == "first") {
2895-
stmts |> push_from <| qmacro_block_to_array() {
2901+
tailStmts <- qmacro_block_to_array() {
28962902
if (!$i(foundName)) {
28972903
panic("sequence contains no elements")
28982904
}
28992905
return $e(lastRetExpr)
29002906
}
29012907
} else {
2902-
stmts |> push_from <| qmacro_block_to_array() {
2908+
tailStmts <- qmacro_block_to_array() {
29032909
return $i(foundName) ? $e(lastRetExpr) : $e(dRetExpr)
29042910
}
29052911
}
2906-
return adapter_wrap_invoke(ctx.src, stmts, outElemType, false, at)
2912+
return emit_terminator_lane(TerminatorSpec(
2913+
prologue <- prologue,
2914+
whereCond = whereCond,
2915+
perElementStmts <- perElementStmts,
2916+
tailStmts <- tailStmts,
2917+
outElemType = outElemType
2918+
), ctx.src, at)
29072919
}
29082920

29092921
// R6 — backward index walk (bare reverse + take(N) + implicit to_array on array source).

daslib/linq_fold.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Inventory: **19 of 22 leaf emit fns fit one of two clean shapes** — 11 Termina
2121

2222
- [x] **PR F1** — Terminator lane + migrate the 2 array-side orchestrators (`emit_accumulator_lane` + `emit_early_exit_lane`). `TerminatorSpec` struct + `emit_terminator_lane` fn + `build_lane_adapter` helper. Orchestrators now build a `TerminatorSpec` (op-specific prologue + perElement + tail) and delegate scaffold to the lane fn. `finalize_lane_emission` orphaned + deleted (now via `adapter_wrap_invoke`). AST parity verified byte-identical on `test_linq_fold_loop_or_count.das` + `test_linq_aggregation.das`. Branch `bbatkin/linq-fold-emission-lanes-pr1`.
2323

24+
- [x] **PR F2** — Migrate 4 array-side Terminator leaves: `emit_counter_lane`, `emit_reverse_counter`, `emit_streaming_min`, `emit_reverse_walk_overwrite_scalar`. Spec extended with `outElemType` + `wrapIter` slots (threaded to `adapter_wrap_invoke`); previous orchestrators continue to pass null/false by struct default. `emit_counter_lane` keeps signature (still called from `emit_loop_or_count_lane`) but body collapses to a thin `TerminatorSpec` builder + `build_lane_adapter` call; range PRELUDE state-decls move into spec.prologue (caller already wraps in-loop range checks). AST parity verified byte-identical on counter / reverse-counter / streaming-min / reverse-walk chains. 3 decs leaves (`emit_decs_min_max_by`, `emit_decs_element_at`, `emit_decs_walk_lane`) deferred to PR F3 since they need the WalkMode (`for_each_archetype_find`) decs-adapter extension that PR F3's orchestrator migration introduces. Branch `bbatkin/linq-fold-emission-lanes-pr2`.
25+
2426
## Goal
2527

2628
Split `_fold` splice machinery into two layers:

0 commit comments

Comments
 (0)