fix(codegen): a procedure never emits a value return
Unannotated functions were typed by scanning the body, which inferred i32, so falling off
the end emitted `llvm.return 0 : i32` inside a function declared void. The MLIR verifier
rejects that ("expected no operands"), which is what broke Scheduler._yield.
- an unannotated `def` is void in both the forward declare and the body, so the implicit
epilogue emits `ret void`
- translate_return treats a procedure (no annotation, `-> None`, or a void-typed function) as
void: the expression is evaluated for its side effects and `ret void` is emitted. Bare
`return`, `return f()` where f is void, and `return <None value>` are legal and never carry
a value
- body_has_value_return accepts `return f()` and `return <None define>` in a procedure
- `define X = None` is a named None: registered as the string sentinel None, materialized as
a None value at use sites, and exported through the vpi
This commit is contained in:
1 parent
ffe31cc6e3
commit
3b3cd42d1c
7 files changed
+76
-24
No files matched your search
@@ -2329,6 +2329,9 @@ def _vpi_register_typedef_closure(pool: memhub.MemBuddy*, import_mods: str, temp
|
||||
# 位宽与定义方同一套裁决(注解 → 右侧强转 → 值的最小可存宽度),
|
||||
# 否则使用方拿 bits=0 只能兜底,跨模块位宽会与定义方不一致
|
||||
GenApi.register_cdefine_constant(pool, cdef_t.id, cdef_val, GenApi.cdefine_decl_bits(pool, aa_r.annotation, vnode_r), sha1_r)
|
||||
elif cn_v.const_kind == ast.CONST_NONE:
|
||||
# `define X = None`: a named None (see _register_cdefine_from_value).
|
||||
GenApi.register_cdefine_string(pool, cdef_t.id, "None", sha1_r)
|
||||
# recurse into child imports (cycle guarded by the visited marks)
|
||||
if sub_imports[0] != '\0': _vpi_register_typedef_closure(pool, sub_imports, temp_dir, depth + 1)
|
||||
stdlib.free(sub_imports)
|
||||
@@ -3339,15 +3342,20 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v
|
||||
cdef_neg_f = 1
|
||||
cdef_is_str: int = 0
|
||||
cdef_is_float: int = 0
|
||||
cdef_is_none: int = 0
|
||||
cdef_float_val: double = 0.0
|
||||
if vnode.kind() == ast.ASTKind.Constant:
|
||||
cn_v: ast.Constant* = ast.Constant*(vnode)
|
||||
if cn_v.const_kind == ast.CONST_STR: cdef_is_str = 1
|
||||
elif cn_v.const_kind == ast.CONST_NONE: cdef_is_none = 1
|
||||
elif cn_v.const_kind == ast.CONST_FLOAT:
|
||||
cdef_is_float = 1
|
||||
cdef_float_val = cn_v.float_val
|
||||
if cdef_neg_f != 0: cdef_float_val = -cdef_float_val
|
||||
if cdef_is_str == 0 and cdef_is_float != 0:
|
||||
if cdef_is_none != 0:
|
||||
# `define X = None`: a named None (see _register_cdefine_from_value).
|
||||
GenApi.register_cdefine_string(pool, cdef_tgt.id, "None", def_mod)
|
||||
elif cdef_is_str == 0 and cdef_is_float != 0:
|
||||
# Float DefineT (e.g. FRAGMENTATION_THRESHOLD: t.DefineT = 0.7): register via
|
||||
# register_cdefine_double (integer extraction would fail fast).
|
||||
# 注解声明 float 时带上 f32 种类(与定义方同一套裁决)。
|
||||
|
||||
@@ -909,6 +909,12 @@ novirtual class AssignGenMixin:
|
||||
return
|
||||
if val_node is not None and val_node.kind() == ast.ASTKind.Constant:
|
||||
cn_fp: ast.Constant* = ast.Constant*(val_node)
|
||||
if cn_fp.const_kind == ast.CONST_NONE:
|
||||
# `define X = None` is a named None: usable wherever None is (e.g. `return X`
|
||||
# from a procedure must emit `ret void`, never a value). Stored as the string
|
||||
# constant "None" and materialized as a None value at use sites.
|
||||
self.Gen.TypeGenCDef.register_cdefine_string(pool, name, "None")
|
||||
return
|
||||
if cn_fp.const_kind == ast.CONST_STR:
|
||||
# String DefineT: register as a string constant (reader returns a global string)
|
||||
self.Gen.TypeGenCDef.register_cdefine_string(pool, name, cn_fp.str_val)
|
||||
|
||||
@@ -52,7 +52,11 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
|
||||
# String DefineT (e.g. ZLIB_VERSION: t.DefineT = "1.3.2"): return a global
|
||||
# string (i8*) for `return ZLIB_VERSION`.
|
||||
cdef_sv: str = self.Gen.TypeGenCDef.lookup_cdefine_string(nm_id)
|
||||
if cdef_sv is not None: return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, cdef_sv, trans)
|
||||
if cdef_sv is not None:
|
||||
# `define X = None`: a named None, materialized like the None literal so a
|
||||
# procedure's `return X` becomes `ret void` (never a value).
|
||||
if string.strcmp(cdef_sv, "None") == 0: return self._NoneValueAsTarget(builder, target_ty)
|
||||
return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, cdef_sv, trans)
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"DefineT string value missing: {nm_id}", "InternalCompilerError")
|
||||
# 浮点常量:声明为 32 位 float(`define PI: float = ...`)时直接出 f32 常量,
|
||||
# 否则出 f64 —— 出 f64 会把后续 f32 字面量整体提升成 double(fpext/cvtss2sd)。
|
||||
|
||||
@@ -516,10 +516,11 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
if ret_ty is None: self.Gen.FuncGenBase.RejectCReturnDecorator(fd, fd.decorator_list)
|
||||
if ret_ty is None and fd.returns is not None:
|
||||
# Return annotation present but unresolvable: compile-time error, no silent i32 fallback
|
||||
# (that forced return i32 and misaligned cross-module return types). No annotation keeps i32.
|
||||
# (that forced return i32 and misaligned cross-module return types). No annotation is a
|
||||
# procedure (void), matching the definition side and the vpi `def f():` form.
|
||||
self.Gen.TypeGenErr.fatal_error(fd, "function return type resolution failed: annotation is not resolvable", "TypeError")
|
||||
return 0
|
||||
if ret_ty is None: ret_ty = i32_ty
|
||||
if ret_ty is None: ret_ty = mlirlite.Void(pool)
|
||||
|
||||
# Detect ExternT/State/ExportT
|
||||
has_extern: int = 0
|
||||
@@ -748,8 +749,12 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
return 0
|
||||
self.Gen.TypeGenErr.fatal_error(fd, "function has no return type annotation but returns a non-None value", "TypeError")
|
||||
return 0
|
||||
param_types_str: str = self.Gen.TypeGenErr.build_param_types_str(pool, fd.args)
|
||||
ret_ty = self.Gen.TypeGenErr.infer_return_type(pool, fd.children, param_types_str)
|
||||
# A procedure: void, so the implicit epilogue emits `ret void` instead of a value
|
||||
# (a value return inside a void function is rejected by the MLIR verifier).
|
||||
ret_ty = mlirlite.Void(pool)
|
||||
else:
|
||||
param_types_str: str = self.Gen.TypeGenErr.build_param_types_str(pool, fd.args)
|
||||
ret_ty = self.Gen.TypeGenErr.infer_return_type(pool, fd.children, param_types_str)
|
||||
# Detect an extern declaration (t.ExternT or t.State): t.ExternT ignores the body and emits only a declare;
|
||||
# t.State = t.ExternT + t.ExportT. t.ExternT/t.State/t.ExportT get no SHA1 prefix and map to C library names.
|
||||
is_extern_decl: int = 0
|
||||
@@ -1406,15 +1411,10 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
# branches return, no br to merge) -> LLVM unreachable. Reachability uses only `_ctrl_merge_reachable`.
|
||||
mlirlite.build_unreachable(func_builder)
|
||||
elif fd.returns is None:
|
||||
# No return annotation -> a no-value procedure function, return a zero default
|
||||
if ret_ty is not None:
|
||||
zero_val: mlirlite.Value* = mlirlite.ConstZero(pool, ret_ty)
|
||||
if zero_val is not None:
|
||||
# M3:隐式收尾出口,先释放 slife 再返回
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
|
||||
mlirlite.build_ret(func_builder, zero_val)
|
||||
else: self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
|
||||
else: self.Gen.TypeGenErr.fatal_error(node, "function flow falls off the end without an explicit return", "TypeError")
|
||||
# No return annotation -> a procedure: the epilogue must never carry a value
|
||||
# (`llvm.return <val>` in a void function is rejected by the verifier).
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, func_builder, mod, trans)
|
||||
mlirlite.build_ret_void(func_builder)
|
||||
else:
|
||||
# fail-fast: has a -> annotation but the flow falls off the end (pointing at the function definition
|
||||
# node; fatal_error prints a traceback-style error).
|
||||
|
||||
@@ -210,6 +210,12 @@ def register_cdefine_double(pool: memhub.MemBuddy*, name: str, value: double, is
|
||||
if _g_gen is not None: _g_gen.TypeGenCDef.register_cdefine_double(pool, name, value, is_f32, module)
|
||||
|
||||
|
||||
# ---------------- register_cdefine_string (delegates to TypeGenCDef.register_cdefine_string) ----------------
|
||||
def register_cdefine_string(pool: memhub.MemBuddy*, name: str, value: str, module: str = None):
|
||||
"""Delegate to gen.register_cdefine_string (a string DefineT, or the "None" sentinel for `define X = None`)"""
|
||||
if _g_gen is not None: _g_gen.TypeGenCDef.register_cdefine_string(pool, name, value, module)
|
||||
|
||||
|
||||
# ---------------- cdefine_decl_float_bits (delegates to AssignGenMixin.cdefine_decl_float_bits) ----------------
|
||||
def cdefine_decl_float_bits(pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
|
||||
"""define 声明的**浮点**宽度裁决:注解显式写了 float → 1(f32),double/未写 → 0。
|
||||
|
||||
@@ -586,17 +586,34 @@ novirtual class StmtGenCifMixin:
|
||||
cur_func_rt: mlirlite.Function* = trans._cur_func
|
||||
ret_ty_rt: mlirlite.LLVMType* = None
|
||||
if cur_func_rt is not None and val is not None and val.Ty is not None: ret_ty_rt = mlirlite.function_get_ret_ty(cur_func_rt)
|
||||
# void function with `return <expr>`: drop the value and emit ret void (valid Python);
|
||||
# returning the value would emit ret i32 and conflict with define void.
|
||||
if ret_ty_rt is not None and val is not None:
|
||||
rv_void: int = 0
|
||||
# A procedure (`def f():`, no -> annotation) or a void-typed function never returns a value:
|
||||
# `return <expr>` evaluates the expression for its side effects and emits `ret void`.
|
||||
# Emitting the value would produce `llvm.return %v : i32` inside a void function, which the
|
||||
# MLIR verifier rejects ("expected no operands"). Legal forms: bare `return`,
|
||||
# `return f()` where f is void, and `return <None value / define>`.
|
||||
proc_ret: int = 0
|
||||
if ret_ty_rt is None:
|
||||
if trans.CurFuncRetAnnot is None: proc_ret = 1
|
||||
else:
|
||||
match ret_ty_rt:
|
||||
case mlirlite.LLVMType.Void():
|
||||
rv_void = 1
|
||||
if rv_void != 0:
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||||
mlirlite.build_ret_void(builder)
|
||||
return 0
|
||||
proc_ret = 1
|
||||
case _:
|
||||
pass
|
||||
if proc_ret == 0 and trans.CurFuncRetAnnot is not None:
|
||||
# `def f() -> None:` is a procedure too.
|
||||
if trans.CurFuncRetAnnot.kind() == ast.ASTKind.Constant:
|
||||
cn_rt: ast.Constant* = ast.Constant*(trans.CurFuncRetAnnot)
|
||||
if cn_rt.const_kind == ast.CONST_NONE: proc_ret = 1
|
||||
if proc_ret != 0:
|
||||
self.Gen.VarGen._SlifeEmitFrees(pool, builder, mod, trans)
|
||||
mlirlite.build_ret_void(builder)
|
||||
return 0
|
||||
if ret_ty_rt is None:
|
||||
# fail-fast: a value return with an unresolvable function return type must not emit a
|
||||
# guessed type (no i32 fallback).
|
||||
self.Gen.TypeGenErr.fatal_error(rt, "cannot determine the function return type", "TypeError")
|
||||
return 0
|
||||
if ret_ty_rt is not None:
|
||||
# str/bytes is no generic-pointer wildcard: a bare str/bytes return annotation with a
|
||||
# pointer-to-aggregate value → fail-fast, before coerce_to_type silently bitcasts ptr→ptr.
|
||||
|
||||
@@ -257,6 +257,17 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
|
||||
if rt.value.kind() == ast.ASTKind.Constant:
|
||||
cn: ast.Constant* = ast.Constant*(rt.value)
|
||||
if cn.const_kind == ast.CONST_NONE: continue
|
||||
# `return f()`: a procedure may return another procedure's result; a callee that
|
||||
# does return a value has it dropped by translate_return (a void function never
|
||||
# emits a value return), so this is not a "value return" shape.
|
||||
if rt.value.kind() == ast.ASTKind.Call: continue
|
||||
# `return <define>` where the define is the None sentinel (`define X = None`)
|
||||
if rt.value.kind() == ast.ASTKind.Name:
|
||||
nm_rv: ast.Name* = ast.Name*(rt.value)
|
||||
if nm_rv.id is not None:
|
||||
sv_rv: str = self.Gen.TypeGenCDef.lookup_cdefine_string(nm_rv.id)
|
||||
if self.Gen.TypeGenCDef.is_cdefine_str() != 0 and sv_rv is not None:
|
||||
if string.strcmp(sv_rv, "None") == 0: continue
|
||||
return 1
|
||||
elif k == ast.ASTKind.FunctionDef or k == ast.ASTKind.ClassDef: continue
|
||||
elif k == ast.ASTKind.If or k == ast.ASTKind.While or k == ast.ASTKind.For or k == ast.ASTKind.Try or k == ast.ASTKind.With or k == ast.ASTKind.Match:
|
||||
|
||||
Reference in new issue
Block a user