Skip to content

Commit 95f2055

Browse files
committed
fix(hir): lua5.1部分情况生成goto
1 parent 6c6575c commit 95f2055

19 files changed

Lines changed: 1870 additions & 147 deletions

File tree

docs/design/4.structure.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,17 @@ LoweredProto + Cfg + GraphFacts + DataflowFacts
7171
|---|---|
7272
| `helpers.rs` | region entry/exit edge、reducible 判定、branch region block 收集、forward region 扫描 |
7373
| `phi_facts.rs` | phi/def/incoming 翻译成结构候选,避免在 loops / branch_values / short_circuit 各处散落 |
74-
| `short_circuit/` | 短路条件出口识别 + 值合流提取,不跑到 HIR 再重新解释 DAG |
74+
| `short_circuit/` | 短路条件出口识别 + 值合流提取;包含 `if a or b then ... else ... end` 这类共享 then/else body 的条件 DAG,不跑到 HIR 再重新解释 DAG |
75+
76+
### 短路条件出口补充
77+
78+
`short_circuit/branch_exit.rs` 会同时提取三类条件出口 DAG:
79+
80+
- 线性 guard / if-then 链,例如 `if a and b then body end`
81+
- if-else 共享 body 链,例如 `if a or b then shared else gated end`,其中后续 header 可能把一臂接到共享 body,另一臂接到 loop continue / merge。
82+
- 嵌套 if-else 节点组成的 guard DAG。遇到多前驱目标时会先把目标视为出口,避免把共享 continuation 或 body header 继续 follow 进后面的 merge。
83+
84+
在 loop 内,单纯的全图 `can_reach` 会穿过回边,不能作为“两个出口属于自然 fallthrough”的唯一依据。这里优先结合后支配关系判断出口方向;对同一共享出口在不同节点上极性不同的链,允许 relaxed linear inference 只要求最终收敛到两个出口。
7585

7686
## 维护规范
7787

@@ -91,6 +101,6 @@ HIR 消费的所有结构候选,见"关键类型"一节。
91101
|---|---|
92102
| branch 合流 block 识别错误 | `helpers.rs` region exit 逻辑 |
93103
| loop 类型判断错误(while vs repeat) | `loops.rs` WhileLike 识别 |
94-
| 短路候选丢失 | `short_circuit/branch_exit.rs` |
104+
| 短路候选丢失 | `short_circuit/branch_exit.rs`,尤其检查 guard/if-then 链和 if-else 共享 body 两类条件出口 |
95105
| phi 归属错误(branch vs loop vs generic) | `phi_facts.rs` incoming 分类 |
96106
| 想看结构候选 | `unluac --debug --target-stage structure-facts <file>` |

docs/design/5.hir.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ LoweredChunk + CfgGraph + GraphFacts + DataflowFacts + StructureFacts
121121
7. **locals pass 边界**:只物化真正"局部、一次性"的 temp;loop/branch-carried update(`t1 = t1 * t0`)不应被提升成新 local。
122122
8. **dialect-neutral canonicalization**:Lua 5.4/5.5 的 `>> -1` / `<< -2` 应在 HIR 收回 `<< 1` / `>> 2`
123123
9. **短路 branch 的 single-eval 只覆盖未物化节点**`if A and B then` 这类 branch-exit 短路在入口 header 会先通过 `lower_block_prefix` 物化前缀语句;若为了后续被吞掉的 header 需要 single-eval 展开,只能展开那些不会单独 lower 的节点,入口 header 仍应引用已物化 temp,避免 `local x = f(); if f() and ...` 这类重复求值。
124+
10. **试探式 lowering 必须可回滚**:conditional reassign、value merge、loop continue/break、cross escape 等 branch 快捷路径,以及 numeric/generic for preheader 这种会先试降 loop body 的路径,如果失败都必须恢复 visited / override / active loop / 输出语句状态,再交给下一种结构化策略。
125+
11. **shared continuation 不是普通 fallthrough**`if cond then gated end; shared` 这类结构只有在 gated 的所有非终止路径都到 shared,或在当前 active loop 边界上 break/continue,才可恢复成共享 continuation;loop escape 必须显式留在 gated block 内。
126+
12. **loop exit pad 可以是小型结构**:numeric/generic/while 的 break exit pad 不只可能是线性 cleanup,也可能是 `if cleanup_cond then cleanup end` 后再到 post-loop。loop context 需要记录 pad 消费的所有 block,避免 coverage 阶段误以为 pad body 未被结构化。
127+
13. **非空 continue target 不能简单继续**:generic/numeric for 的 tail header 往往是非空 continue target。短路 branch 若 `truthy` 正好跳到这个 target,应先取反并交换出口,恢复成 `if cond then body end`;若 `falsy` 跳到 target,则可以按 if-then guard 处理。repeat/while 仍保持保守,避免把循环条件边界吞进短路表达式。
128+
14. **被短路吞掉的 header prefix 要先重写**:扩展 branch-exit short-circuit 后,后续被消费 header 的 prefix temp 不会再单独 lower。HIR 需要用已有 prefix temp override 把这些 temp 内联进合成条件;仍残留不可解析 prefix temp 时才回退,避免生成引用未定义 temp 的条件。
129+
15. **loop state 初值允许使用 preheader 出口表达式**:generic/numeric loop 可能紧跟在一串已结构化 branch-value merge 后面,preheader 边上的同一寄存器会保留多个原始 def。只要这是进入 loop 前的 block 出口值,就可以用 `expr_for_reg_at_block_exit(preheader, reg)` 作为 state 初值,而不是在 loop state 里重新拆这些 branch merge。
124130

125131
## 向后提供的事实
126132

docs/design/7.readability.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ src/ast/readability/
8080
| `walk.rs` | rewrite pass,默认先完整递归所有子节点再执行当前 hook,不短路 |
8181
| `visit.rs` | 只读 collector |
8282
| `traverse.rs` | 共享子节点递归骨架,不要为单个 pass 复制一整套递归样板 |
83-
| `binding_flow.rs` | binding use 统计 / 替换 / 可见性分析 |
83+
| `binding_flow.rs` | binding use 统计 / mention 集合 / 替换 / 可见性分析 |
8484
| `binding_tree.rs` | binding 树结构分析 |
8585
| `expr_analysis.rs` | 复杂度 / 内联安全性 / 屏障判断 |
8686
| `inline_exprs/use_sites.rs` / `candidate.rs` | use-site 判定共享逻辑 |
@@ -90,7 +90,7 @@ src/ast/readability/
9090
## 维护规范
9191

9292
1. **新 pass 默认先接共享 walker/visitor**:不复制专用递归框架。
93-
2. **binding / 表达式约束复用共享 helper**:binding use 统计、替换、access-base 安全判断、单值判定等,补到共享 helper 而不是分散在 pass 内。
93+
2. **binding / 表达式约束复用共享 helper**:binding use 统计、mention 集合、替换、access-base 安全判断、单值判定等,补到共享 helper 而不是分散在 pass 内。
9494
3. **`materialize_temps` 是边界 pass**:Readability 结束后不应再把原生 `TempId` 留给 Naming。依赖原生 temp 的新 pass 应放在 `materialize_temps` 之前。
9595
4. **branch-local 值壳回 HIR**`local x; if cond then x=a else x=b end` 这类,如果前层已经足以证明是"给同一 binding 选值",应回 HIR 用 `Decision->Expr` 处理,不在 AST 层重新发明。
9696
5. **`statement_merge` 尊重 goto/label 边界**:不能把 `local` 下沉到 forward goto 之后,否则生成"跳进 local 作用域"的非法 Lua。

src/ast/readability/binding_flow.rs

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! 这些 pass 经常需要回答同一类问题:
44
//! - 某个 binding 在一段语句里还会不会再被读取?
5+
//! - 某个语句实际提到了哪些 binding(包括赋值目标这种 mention,而不只是读取)?
56
//! - 某个语句/块会不会提前引用一组待下沉的 hoisted local?
67
//! - 某个 binding 在当前函数体里一共被用了几次?
78
//!
@@ -11,7 +12,7 @@
1112
//! 但 `FunctionExpr.captured_bindings` 是闭包创建时对当前词法 binding 的显式引用,
1213
//! 必须按当前语句的一次使用统计,否则后续 pass 可能误删仍被闭包持有的局部。
1314
14-
use std::collections::BTreeMap;
15+
use std::collections::{BTreeMap, BTreeSet};
1516

1617
use super::super::common::{
1718
AstBindingRef, AstBlock, AstCallKind, AstExpr, AstLValue, AstLocalBinding, AstNameRef, AstStmt,
@@ -173,6 +174,12 @@ pub(super) fn count_binding_mentions_in_block(block: &AstBlock, binding: AstBind
173174
.sum()
174175
}
175176

177+
pub(super) fn binding_mentions_in_stmt(stmt: &AstStmt) -> BTreeSet<AstBindingRef> {
178+
let mut mentions = BTreeSet::new();
179+
collect_binding_mentions_in_stmt(stmt, &mut mentions);
180+
mentions
181+
}
182+
176183
pub(super) fn count_binding_uses_in_stmt(stmt: &AstStmt, binding: AstBindingRef) -> usize {
177184
count_binding_uses_in_stmt_with_scope(stmt, binding, BindingUseScope::CurrentFunctionOnly)
178185
}
@@ -451,6 +458,193 @@ fn count_binding_mentions_in_stmt(stmt: &AstStmt, binding: AstBindingRef) -> usi
451458
}
452459
}
453460

461+
fn collect_binding_mentions_in_block(block: &AstBlock, mentions: &mut BTreeSet<AstBindingRef>) {
462+
for stmt in &block.stmts {
463+
collect_binding_mentions_in_stmt(stmt, mentions);
464+
}
465+
}
466+
467+
fn collect_binding_mentions_in_stmt(stmt: &AstStmt, mentions: &mut BTreeSet<AstBindingRef>) {
468+
match stmt {
469+
AstStmt::LocalDecl(local_decl) => {
470+
mentions.extend(local_decl.bindings.iter().map(|binding| binding.id));
471+
for value in &local_decl.values {
472+
collect_binding_mentions_in_expr(value, mentions);
473+
}
474+
}
475+
AstStmt::GlobalDecl(global_decl) => {
476+
for value in &global_decl.values {
477+
collect_binding_mentions_in_expr(value, mentions);
478+
}
479+
}
480+
AstStmt::Assign(assign) => {
481+
for target in &assign.targets {
482+
collect_binding_mentions_in_lvalue(target, mentions);
483+
}
484+
for value in &assign.values {
485+
collect_binding_mentions_in_expr(value, mentions);
486+
}
487+
}
488+
AstStmt::CallStmt(call_stmt) => collect_binding_mentions_in_call(&call_stmt.call, mentions),
489+
AstStmt::Return(ret) => {
490+
for value in &ret.values {
491+
collect_binding_mentions_in_expr(value, mentions);
492+
}
493+
}
494+
AstStmt::If(if_stmt) => {
495+
collect_binding_mentions_in_expr(&if_stmt.cond, mentions);
496+
collect_binding_mentions_in_block(&if_stmt.then_block, mentions);
497+
if let Some(else_block) = &if_stmt.else_block {
498+
collect_binding_mentions_in_block(else_block, mentions);
499+
}
500+
}
501+
AstStmt::While(while_stmt) => {
502+
collect_binding_mentions_in_expr(&while_stmt.cond, mentions);
503+
collect_binding_mentions_in_block(&while_stmt.body, mentions);
504+
}
505+
AstStmt::Repeat(repeat_stmt) => {
506+
collect_binding_mentions_in_block(&repeat_stmt.body, mentions);
507+
collect_binding_mentions_in_expr(&repeat_stmt.cond, mentions);
508+
}
509+
AstStmt::NumericFor(numeric_for) => {
510+
mentions.insert(numeric_for.binding);
511+
collect_binding_mentions_in_expr(&numeric_for.start, mentions);
512+
collect_binding_mentions_in_expr(&numeric_for.limit, mentions);
513+
collect_binding_mentions_in_expr(&numeric_for.step, mentions);
514+
collect_binding_mentions_in_block(&numeric_for.body, mentions);
515+
}
516+
AstStmt::GenericFor(generic_for) => {
517+
mentions.extend(generic_for.bindings.iter().copied());
518+
for expr in &generic_for.iterator {
519+
collect_binding_mentions_in_expr(expr, mentions);
520+
}
521+
collect_binding_mentions_in_block(&generic_for.body, mentions);
522+
}
523+
AstStmt::DoBlock(block) => collect_binding_mentions_in_block(block, mentions),
524+
AstStmt::FunctionDecl(function_decl) => {
525+
collect_function_name_mentions(&function_decl.target, mentions);
526+
}
527+
AstStmt::LocalFunctionDecl(function_decl) => {
528+
mentions.insert(function_decl.name);
529+
}
530+
AstStmt::Break
531+
| AstStmt::Continue
532+
| AstStmt::Goto(_)
533+
| AstStmt::Label(_)
534+
| AstStmt::Error(_) => {}
535+
}
536+
}
537+
538+
fn collect_binding_mentions_in_call(call: &AstCallKind, mentions: &mut BTreeSet<AstBindingRef>) {
539+
match call {
540+
AstCallKind::Call(call) => {
541+
collect_binding_mentions_in_expr(&call.callee, mentions);
542+
for arg in &call.args {
543+
collect_binding_mentions_in_expr(arg, mentions);
544+
}
545+
}
546+
AstCallKind::MethodCall(call) => {
547+
collect_binding_mentions_in_expr(&call.receiver, mentions);
548+
for arg in &call.args {
549+
collect_binding_mentions_in_expr(arg, mentions);
550+
}
551+
}
552+
}
553+
}
554+
555+
fn collect_binding_mentions_in_lvalue(target: &AstLValue, mentions: &mut BTreeSet<AstBindingRef>) {
556+
match target {
557+
AstLValue::Name(name) => {
558+
if let Some(binding) = binding_from_name_ref(name) {
559+
mentions.insert(binding);
560+
}
561+
}
562+
AstLValue::FieldAccess(access) => {
563+
collect_binding_mentions_in_expr(&access.base, mentions);
564+
}
565+
AstLValue::IndexAccess(access) => {
566+
collect_binding_mentions_in_expr(&access.base, mentions);
567+
collect_binding_mentions_in_expr(&access.index, mentions);
568+
}
569+
}
570+
}
571+
572+
fn collect_binding_mentions_in_expr(expr: &AstExpr, mentions: &mut BTreeSet<AstBindingRef>) {
573+
match expr {
574+
AstExpr::Var(name) => {
575+
if let Some(binding) = binding_from_name_ref(name) {
576+
mentions.insert(binding);
577+
}
578+
}
579+
AstExpr::FieldAccess(access) => collect_binding_mentions_in_expr(&access.base, mentions),
580+
AstExpr::IndexAccess(access) => {
581+
collect_binding_mentions_in_expr(&access.base, mentions);
582+
collect_binding_mentions_in_expr(&access.index, mentions);
583+
}
584+
AstExpr::Unary(unary) => collect_binding_mentions_in_expr(&unary.expr, mentions),
585+
AstExpr::Binary(binary) => {
586+
collect_binding_mentions_in_expr(&binary.lhs, mentions);
587+
collect_binding_mentions_in_expr(&binary.rhs, mentions);
588+
}
589+
AstExpr::LogicalAnd(logical) | AstExpr::LogicalOr(logical) => {
590+
collect_binding_mentions_in_expr(&logical.lhs, mentions);
591+
collect_binding_mentions_in_expr(&logical.rhs, mentions);
592+
}
593+
AstExpr::Call(call) => {
594+
collect_binding_mentions_in_expr(&call.callee, mentions);
595+
for arg in &call.args {
596+
collect_binding_mentions_in_expr(arg, mentions);
597+
}
598+
}
599+
AstExpr::MethodCall(call) => {
600+
collect_binding_mentions_in_expr(&call.receiver, mentions);
601+
for arg in &call.args {
602+
collect_binding_mentions_in_expr(arg, mentions);
603+
}
604+
}
605+
AstExpr::SingleValue(expr) => collect_binding_mentions_in_expr(expr, mentions),
606+
AstExpr::TableConstructor(table) => {
607+
for field in &table.fields {
608+
match field {
609+
AstTableField::Array(value) => {
610+
collect_binding_mentions_in_expr(value, mentions);
611+
}
612+
AstTableField::Record(record) => {
613+
if let AstTableKey::Expr(key) = &record.key {
614+
collect_binding_mentions_in_expr(key, mentions);
615+
}
616+
collect_binding_mentions_in_expr(&record.value, mentions);
617+
}
618+
}
619+
}
620+
}
621+
AstExpr::FunctionExpr(_) => {}
622+
AstExpr::Nil
623+
| AstExpr::Boolean(_)
624+
| AstExpr::Integer(_)
625+
| AstExpr::Number(_)
626+
| AstExpr::String(_)
627+
| AstExpr::Int64(_)
628+
| AstExpr::UInt64(_)
629+
| AstExpr::Complex { .. }
630+
| AstExpr::VarArg
631+
| AstExpr::Error(_) => {}
632+
}
633+
}
634+
635+
fn collect_function_name_mentions(
636+
target: &super::super::common::AstFunctionName,
637+
mentions: &mut BTreeSet<AstBindingRef>,
638+
) {
639+
let path = match target {
640+
super::super::common::AstFunctionName::Plain(path) => path,
641+
super::super::common::AstFunctionName::Method(path, _) => path,
642+
};
643+
if let Some(binding) = binding_from_name_ref(&path.root) {
644+
mentions.insert(binding);
645+
}
646+
}
647+
454648
fn count_binding_uses_in_call(call: &AstCallKind, binding: AstBindingRef) -> usize {
455649
count_binding_uses_in_call_with_scope(call, binding, BindingUseScope::CurrentFunctionOnly)
456650
}

0 commit comments

Comments
 (0)