feat(match): case 内 break 跳出 match,并落地 fallthrough

- Translator:新增 _in_match_case(FallThrough 合法性判定用);_break_bb 注释补充
  case 内 break 语义
- StmtGenDeclMixin:新增 translate_fallthrough(本身不产出 IR;非 case 体内 fail-fast)
- StmtGenMixin:FallThrough 语句分派
- MatchGenMixin:进出 case 体(含 default 臂)时暂存/覆盖 _break_bb = match end_bb,
  使 case 内 break 跳出 match 而非外层循环;_has_no_break 兼容 FallThrough 标记;
  REnum / simple 两条 switch 路径的 fallthrough 由"只留无终结符块"改为显式 br
  下一个 case body(原实现产出非法 IR)
This commit is contained in:
TermiNexus committed 2026-10-02 22:33:15 +08:00
1 parent 0b7f7a50c7
commit 43d25e58b9
4 files changed
+106 -9

No files matched your search

+82 -8
View File
@@ -215,9 +215,11 @@ novirtual class MatchGenMixin:
return self.Gen.ClassGenEnum._lookup_renum_variant(variant_name)
# ============================================================
# _has_no_break - 检测 case body 是否含 c.NoBreak() 调用
# _has_no_break - 检测 case body 是否要求 fallthrough(落到下一个 case)
#
# 遍历 case body 顶层语句,查找 Expr(Call(Attribute(Name('c'), 'NoBreak')))
# 两种写法等价:
# ① 软关键字语句 `fallthrough`(ASTKind.FallThrough)
# ② 兼容写法 `c.NoBreak()`:Expr(Call(Attribute(Name('c'), 'NoBreak')))
# ============================================================
def _has_no_break(self, body_list: list[ast.AST*]*) -> int:
if body_list is None:
@@ -226,6 +228,8 @@ novirtual class MatchGenMixin:
i: int = 0
while i < n:
stmt: ast.AST* = body_list.get(i)
if stmt is not None and stmt.kind() == ast.ASTKind.FallThrough:
return 1
if stmt is not None and stmt.kind() == ast.ASTKind.Expr:
expr_node: rawptr[ast.Expr] = rawptr[ast.Expr](stmt)
val: ast.AST* = expr_node.value
@@ -560,6 +564,13 @@ novirtual class MatchGenMixin:
# 定位到 case BB
llvmlite.position_at_end(builder, case_bb)
# case 体内 break = 跳出 match(C switch 语义):暂存外层循环的
# break 目标并覆盖为 match 的 end_bb,出 case 时恢复。
rmb_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
trans._break_bb = end_bb
rmb_old_inmc: int = trans._in_match_case
trans._in_match_case = 1
# 进入 case 作用域(变体字段绑定隔离)
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
@@ -606,15 +617,29 @@ novirtual class MatchGenMixin:
self.Gen.StmtGen.translate_stmt(trans, stmt)
bi = bi + 1
# 退出 case 作用域
# 退出 case 作用域,并恢复 break 目标 / match-case 标记
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._break_bb = rmb_old_break
trans._in_match_case = rmb_old_inmc
# 若未终止且无 NoBreak,跳到 end
# 若未终止:默认跳 end;含 fallthrough(`fallthrough` 标记或兼容的
# c.NoBreak())则落到下一个 case 的 body(跳过其模式检查)。
# 末句控制流全 return(结构性不可达)时补 unreachable,不 br end_bb
if llvmlite.builder_cur_block_is_terminated(builder) == 0 \
and nb == 0 \
and self._case_tail_unreachable(trans, builder, body_list) == 0:
llvmlite.build_br(builder, end_bb)
if nb == 0:
llvmlite.build_br(builder, end_bb)
else:
rmb_ft_nj: int = i + 1
while rmb_ft_nj < collected and def_arr[rmb_ft_nj] != 0:
rmb_ft_nj = rmb_ft_nj + 1
if rmb_ft_nj < collected:
rmb_ft_bb: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](bb_arr[rmb_ft_nj])
llvmlite.build_br(builder, rmb_ft_bb)
elif has_default != 0:
llvmlite.build_br(builder, default_bb)
else:
llvmlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
rm_merge_reach = 1
@@ -633,6 +658,11 @@ novirtual class MatchGenMixin:
# (不再经 def_arr 槽位反查 cases_list:collected 下标与原始下标
# 在"有臂被跳过"时会错位,default_bb 随之空转)
dmc_d: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](default_case_node)
# default 臂同样按 match-case 语义:break = 跳出 match
rmd_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
trans._break_bb = end_bb
rmd_old_inmc: int = trans._in_match_case
trans._in_match_case = 1
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
# dbody 预声明+重赋值会使 .get 误分派成 list[int].get
# (i32→inttoptr 截断指针 → translate_stmt 读垃圾节点崩溃,gen1 IR 证据)。
@@ -647,6 +677,8 @@ novirtual class MatchGenMixin:
self.Gen.StmtGen.translate_stmt(trans, stmt3)
dj3 = dj3 + 1
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._break_bb = rmd_old_break
trans._in_match_case = rmd_old_inmc
# 末句控制流全 return → 补 unreachable(不 br end_bb)
if llvmlite.builder_cur_block_is_terminated(builder) == 0 \
and self._case_tail_unreachable(trans, builder, dbody3) == 0:
@@ -1024,6 +1056,11 @@ novirtual class MatchGenMixin:
llvmlite.position_at_end(builder, bbb3)
cnode2: ast.AST* = cases_list.get(ci)
mc3: rawptr[ast.MatchCase] = (rawptr[ast.MatchCase])(cnode2)
# case 体内 break = 跳出 match(C switch 语义):暂存/覆盖 _break_bb
sqb_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
trans._break_bb = end_bb
sqb_old_inmc: int = trans._in_match_case
trans._in_match_case = 1
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
body_list2: list[ast.AST*]* = mc3.children
if body_list2 is not None:
@@ -1035,6 +1072,8 @@ novirtual class MatchGenMixin:
self.Gen.StmtGen.translate_stmt(trans, stmt)
bi = bi + 1
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._break_bb = sqb_old_break
trans._in_match_case = sqb_old_inmc
# 末句控制流全 return → 补 unreachable(不 br end,无 fallthrough)
if llvmlite.builder_cur_block_is_terminated(builder) == 0 \
and self._case_tail_unreachable(trans, builder, body_list2) == 0:
@@ -1063,6 +1102,11 @@ novirtual class MatchGenMixin:
# 直接翻译 pass1 记录的 `case _` 臂(不再经 def_arr 槽位反查 cases_list:
# collected 下标与原始下标在"有臂被跳过"时会错位,default_bb 随之空转)
dmc_d: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](default_case_node)
# default 臂同样按 match-case 语义:break = 跳出 match
sqd_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
trans._break_bb = end_bb
sqd_old_inmc: int = trans._in_match_case
trans._in_match_case = 1
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
# dbody 预声明+重赋值会使 .get 误分派成 list[int].get
# (i32→inttoptr 截断指针 → translate_stmt 读垃圾节点崩溃,gen1 IR 证据)。
@@ -1078,6 +1122,8 @@ novirtual class MatchGenMixin:
self.Gen.StmtGen.translate_stmt(trans, stmt3)
dj3 = dj3 + 1
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._break_bb = sqd_old_break
trans._in_match_case = sqd_old_inmc
# 末句控制流全 return → 补 unreachable(不 br end_bb)
if llvmlite.builder_cur_block_is_terminated(builder) == 0 \
and self._case_tail_unreachable(trans, builder, dbody) == 0:
@@ -1267,6 +1313,12 @@ novirtual class MatchGenMixin:
llvmlite.position_at_end(builder, case_bb)
# case 体内 break = 跳出 match(C switch 语义):暂存/覆盖 _break_bb
smb_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
trans._break_bb = end_bb
smb_old_inmc: int = trans._in_match_case
trans._in_match_case = 1
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
body_list: list[ast.AST*]* = mc.children
@@ -1280,12 +1332,27 @@ novirtual class MatchGenMixin:
bi = bi + 1
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._break_bb = smb_old_break
trans._in_match_case = smb_old_inmc
# 若未终止:默认跳 end;含 fallthrough(`fallthrough` 标记或兼容的
# c.NoBreak())则落到下一个 case 的 body(跳过其模式检查)。
# 末句控制流全 return → 补 unreachable(不 br end_bb)
if llvmlite.builder_cur_block_is_terminated(builder) == 0 \
and nb == 0 \
and self._case_tail_unreachable(trans, builder, body_list) == 0:
llvmlite.build_br(builder, end_bb)
if nb == 0:
llvmlite.build_br(builder, end_bb)
else:
sm_ft_nj: int = i + 1
while sm_ft_nj < collected and def_arr[sm_ft_nj] != 0:
sm_ft_nj = sm_ft_nj + 1
if sm_ft_nj < collected:
sm_ft_bb: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](bb_arr[sm_ft_nj])
llvmlite.build_br(builder, sm_ft_bb)
elif has_default != 0:
llvmlite.build_br(builder, default_bb)
else:
llvmlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
sm_merge_reach = 1
@@ -1303,6 +1370,11 @@ novirtual class MatchGenMixin:
# 直接翻译 pass1 记录的 `case _` 臂(不再经 def_arr 槽位反查 cases_list:
# collected 下标与原始下标在"有臂被跳过"时会错位,default_bb 随之空转)
dmc_d: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](default_case_node)
# default 臂同样按 match-case 语义:break = 跳出 match
smd_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
trans._break_bb = end_bb
smd_old_inmc: int = trans._in_match_case
trans._in_match_case = 1
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
# dbody 预声明+重赋值会使 .get 误分派成 list[int].get
# (i32→inttoptr 截断指针 → translate_stmt 读垃圾节点崩溃,gen1 IR 证据)。
@@ -1317,6 +1389,8 @@ novirtual class MatchGenMixin:
self.Gen.StmtGen.translate_stmt(trans, stmt3)
dj3 = dj3 + 1
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._break_bb = smd_old_break
trans._in_match_case = smd_old_inmc
# 末句控制流全 return → 补 unreachable(不 br end_bb)
if llvmlite.builder_cur_block_is_terminated(builder) == 0 \
and self._case_tail_unreachable(trans, builder, dbody3) == 0:
+14
View File
@@ -280,6 +280,20 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
llvmlite.position_at_end(builder, dead_bb)
return 0
def translate_fallthrough(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""翻译 `fallthrough` 标记语句:本身不产出任何 IR
语义由 match 生成侧消费——case 体含该标记时末尾不自动 break,直接落到
下一个 case 体(对齐 C 的 [[fallthrough]])。合法位置只有 match-case
语句体;出现在别处只会静默无效果,故 fail-fast 暴露。
"""
if trans is None:
return 0
if trans._in_match_case == 0:
self.Gen.TypeGenErr.fatal_error(node,
"fallthrough 只能用在 match-case 语句体内(语义是落到下一个 case)", "SyntaxError")
return 0
def translate_continue(self, trans: HT.Translator*) -> int:
"""翻译 continue 语句:跳转到循环 cond/incr 块"""
builder: llvmlite.IRBuilder* = trans._cur_builder
+2
View File
@@ -90,6 +90,8 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
return self.translate_break(trans)
elif k == ast.ASTKind.Continue:
return self.translate_continue(trans)
elif k == ast.ASTKind.FallThrough:
return self.translate_fallthrough(trans, node)
elif k == ast.ASTKind.Match:
return self.Gen.MatchGen.translate_match(trans, node)
elif k == ast.ASTKind.Delete:
+8 -1
View File
@@ -170,10 +170,16 @@ novirtual class Translator:
# 的直接值」时才用 CurFuncRetAnnot(嵌套调用如 return f(Stack()) 不推断)。
CurRetNode: ast.AST*
# 循环控制流目标(break/continue)
# 循环控制流目标(break/continue)。注意 match-case 体内的 break 语义是
# "跳出 match":MatchGenMixin 进出 case 体时暂存并把 _break_bb 覆盖为 match
# 的 end_bb(出 case 恢复),故 case 内 break 不会误跳外层循环。
_break_bb: rawptr[llvmlite.BasicBlock]
_continue_bb: rawptr[llvmlite.BasicBlock]
# 是否正处于 match-case 语句体内(1=是)。供 `fallthrough` 语句判定合法性
# (只在 case 体内合法,别处 fail-fast),由 MatchGenMixin 进出 case 时置位。
_in_match_case: int
# 独立标签计数器(不与 builder.Counter 共享,避免 SSA 编号非单调)
_label_counter: int
@@ -291,6 +297,7 @@ novirtual class Translator:
self._cur_builder = None
self._break_bb = None
self._continue_bb = None
self._in_match_case = 0
self._label_counter = 0
self._imported_modules = None
self._from_imports = None