diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 45e406a..de8bb80 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -51,7 +51,7 @@ novirtual class AssignGenMixin: """编译期常量表达式求值:返回值,并把**位宽**写入 bits_out[0](0=未知)。 位宽规则(供 define 物化用): - - 显式强转(Call 的 callee 是整型/typedef,如 WORD / int / t.CUInt16T)→ 该位宽; + - 显式强转(Call 的 callee 是整型/typedef,如 WORD / int / uint16)→ 该位宽; - Name 引用其它 define → 沿用其位宽; - BinOp:操作数的**声明宽度**保持最小(不提升),基准取较宽者;`+ - * <<` 的结果若装不进 基准宽度,就**按结果值加宽**(向上取到 8/16/32/64)—— 静态计算不允许回绕; @@ -154,7 +154,7 @@ novirtual class AssignGenMixin: # Unregistered reference: fail fast self.Gen.TypeGenErr.fatal_error(val_node, "DefineT value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") - # Case 4: Call - 整型/typedef 强转(如 WORD(0x0080) / t.CUnsignedLong(-11)): + # Case 4: Call - 整型/typedef 强转(如 WORD(0x0080) / uint32(-11)): # 取实参值,并按**目标类型的位宽**规范化(这赋予常量"已知宽度")。 if k == ast.ASTKind.Call: cl: ast.Call* = ast.Call*(val_node) @@ -208,7 +208,7 @@ novirtual class AssignGenMixin: if rhs_bits != 0: return rhs_bits return self.define_min_width(val) - # define / t.DefineT integer constant storable and width checks + # define / DefineT integer constant storable and width checks # Auto-derived => 64-bit pattern; explicit type T => T's width range. def define_min_width(self, value: int64) -> int: """Return the minimal storable width (8/16/32/64) for an integer value.""" @@ -320,13 +320,13 @@ novirtual class AssignGenMixin: def _int_bits_of_type_name(self, pool: memhub.MemBuddy*, nm: str, depth: int) -> int: """类型名 → 整数位宽(0=未知/非整数)。depth 防 typedef 环。 - 覆盖:内建名(int/bool/int8../uint64/sizet)、`t.CXxx`(map_builtin_type)、 - 以及**用户 typedef**(如 stdint 的 `WORD: t.TypedefT = uint16` —— 经 lookup_ctypedef + 覆盖:内建名(int/bool/int8../uint64/sizet)、builtin names(map_builtin_type)、 + 以及**用户 typedef**(如 stdint 的 `WORD: TypedefT = uint16` —— 经 lookup_ctypedef 取其底层类型文本再递归定宽)。此前用户 typedef 一律返回 0,导致 `define X: WORD = ...` 的声明宽度不参与校验/物化。 """ if nm is None or depth > 10: return 0 - # 去掉模块/类限定前缀:`t.CUInt16T` → `CUInt16T`;`stdint.uint16` → `uint16` + # 去掉模块/类限定前缀:`uint16` → `CUInt16T`;`stdint.uint16` → `uint16` dot: str = string.strrchr(nm, 46) if dot is not None and dot[1] != '\0': return self._int_bits_of_type_name(pool, dot + 1, depth + 1) @@ -346,7 +346,7 @@ novirtual class AssignGenMixin: return 0 case _: return 0 - # 用户/include typedef(如 stdint.vp 的 `WORD: t.TypedefT = uint16`): + # 用户/include typedef(如 stdint.vp 的 `WORD: TypedefT = uint16`): # 表内值可能是模块限定 ref(stdint.uint16),先用 _expand_typedef_ref 展开; # 本地表未命中再走 builtins 源扫描兜底。 underlying: str = self.Gen.TypeGenCType.lookup_ctypedef(nm) @@ -392,10 +392,10 @@ novirtual class AssignGenMixin: def _float_kind_of_type_name(self, pool: memhub.MemBuddy*, nm: str, depth: int) -> int: """类型名 → 1 表示它是 32 位 float(f32),0 表示 double 或非浮点/未知。depth 防 typedef 环。 - 覆盖:内建名 float、`t.CFloat` / `t.CFloat32T`(map_builtin_type)、用户/include typedef(递归展开)。 + 覆盖:内建名 float、`float` / `float32`(map_builtin_type)、用户/include typedef(递归展开)。 """ if nm is None or depth > 10: return 0 - # 去掉模块/类限定前缀:`t.CFloat` → `CFloat`;`stdint.float32` → `float32` + # 去掉模块/类限定前缀:`float` → `CFloat`;`stdint.float32` → `float32` dot: str = string.strrchr(nm, 46) if dot is not None and dot[1] != '\0': return self._float_kind_of_type_name(pool, dot + 1, depth + 1) @@ -409,7 +409,7 @@ novirtual class AssignGenMixin: return 0 case _: return 0 - # 用户/include typedef(如 `MYF: t.TypedefT = float`):展开后再递归判定 + # 用户/include typedef(如 `MYF: TypedefT = float`):展开后再递归判定 underlying: str = self.Gen.TypeGenCType.lookup_ctypedef(nm) if underlying is None: underlying = self.Gen.TypeGenResolve._lookup_builtins_typedef(pool, nm) @@ -591,7 +591,7 @@ novirtual class AssignGenMixin: self.Gen.TypeGenErr.fatal_error(value_node, f"[VPC] define constant use-site width insufficient: constant '{rn.id}' is {dw} bits wide " f"(declared width; actual value {cdef_val}), but the use site (variable declaration annotation) provides only {bits} bits. " f"Silent truncation/downgrade is forbidden; compilation aborted. " f"Fix: widen the variable declaration annotation to at least {dw} bits (such as int{dw}T / CUInt{dw}T), " f"or narrow the constant at its define with an explicit cast.", "TypeError") # eval_cdefine_float_expr - 编译期**浮点**常量表达式求值(DefineT 值为浮点表达式时用)。 - # 支持 Constant(float/int)、UnaryOp(+/-)、Call 强转包裹(float(x)/double(x)/t.CDouble(x))、 + # 支持 Constant(float/int)、UnaryOp(+/-)、Call 强转包裹(float(x)/double(x)/double(x))、 # BinOp(+ - * /)。成功写 out[0] 并返回 1;不是纯浮点常量表达式时返回 0(交回整型路径)。 # 与 extract_cdefine_int_value_bits 的分工:后者只服务整型 DefineT,遇到 float 字面量会 fail-fast。 def eval_cdefine_float_expr(self, node: ast.AST*, out: double*, pool: memhub.MemBuddy*) -> int: @@ -618,7 +618,7 @@ novirtual class AssignGenMixin: return 1 return 0 if k == ast.ASTKind.Call: - # 强转包裹(float(3.14) / double(x) / t.CDouble(x)):取首实参的值 + # 强转包裹(float(3.14) / double(x) / double(x)):取首实参的值 cl: ast.Call* = ast.Call*(node) if cl.args is not None and len(cl.args) > 0: return self.eval_cdefine_float_expr(cl.args.get(0), out, pool) @@ -831,9 +831,9 @@ novirtual class AssignGenMixin: # which would hide the float cast when deciding the constant kind. float_src: ast.AST* = val_node # 浮点宽度种类(f32 与否)必须**在拆掉右侧包裹前**判定: - # ① 注解有显式类型(`define PI: float = ...` / `PI: float | t.DefineT = ...`)→ 注解说了算 + # ① 注解有显式类型(`define PI: float = ...` / `PI: float | DefineT = ...`)→ 注解说了算 # (`define X: double = float(1.5)` 按 double 走); - # ② 注解没有显式类型(`PI: t.DefineT = float(3.14)`)→ 看右侧强转包裹 float(...) / double(...)。 + # ② 注解没有显式类型(`PI: DefineT = float(3.14)`)→ 看右侧强转包裹 float(...) / double(...)。 f32_kind: int = 0 if self._define_explicit_annot(annot) is not None: f32_kind = self.cdefine_decl_float_bits(pool, annot) @@ -974,16 +974,8 @@ novirtual class AssignGenMixin: if sep >= line_start + line_len: continue if buf[sep] != ':' and buf[sep] != '=': continue - # DefineT path requires t.DefineT in the line, or the define keyword form - if require_cdefine != 0 and is_define_line == 0: - has_cd: int = 0 - ci: sizet = line_start + name_len + 1 - while ci + 8 <= line_start + line_len: - if (buf[ci] == 't' and buf[ci + 1] == '.' and buf[ci + 2] == 'C' and buf[ci + 3] == 'D' and buf[ci + 4] == 'e' and buf[ci + 5] == 'f' and buf[ci + 6] == 'i' and buf[ci + 7] == 'n' and buf[ci + 8] == 'e'): - has_cd = 1 - break - ci += 1 - if has_cd == 0: continue + # DefineT path requires the define keyword form. + if require_cdefine != 0 and is_define_line == 0: continue # Find '=' eq: sizet = line_start @@ -1023,7 +1015,7 @@ novirtual class AssignGenMixin: return 0 def _recover_cdefine_float(self, name: str, out: double*) -> int: - """Recover a DefineT float value (requiring a t.DefineT line).""" + """Recover a DefineT float value from its define line.""" return self._recover_float_literal(name, out, 1) # _annotation_contains_name - recursively check whether an annotation contains a name @@ -1854,9 +1846,6 @@ novirtual class AssignGenMixin: at64: ast.Attribute* = ast.Attribute*(node) ty_nm = at64.attr if ty_nm is None: return 0 - if string.strcmp(ty_nm, "CUInt64T") == 0: return 1 - if string.strcmp(ty_nm, "CInt64T") == 0: return 1 - if string.strcmp(ty_nm, "CSizeT") == 0: return 1 # builtin 64-bit short names if string.strcmp(ty_nm, "sizet") == 0: return 1 if string.strcmp(ty_nm, "size_t") == 0: return 1 @@ -1923,25 +1912,6 @@ novirtual class AssignGenMixin: if string.strcmp(ty_name, "float") == 0: return 1 if string.strcmp(ty_name, "double") == 0: return 1 if string.strcmp(ty_name, "bool") == 0: return 1 - if string.strcmp(ty_name, "CInt") == 0: return 1 - if string.strcmp(ty_name, "CUnsignedInt") == 0: return 1 - if string.strcmp(ty_name, "CUnsigned") == 0: return 1 - if string.strcmp(ty_name, "CChar") == 0: return 1 - if string.strcmp(ty_name, "CUnsignedChar") == 0: return 1 - if string.strcmp(ty_name, "CSignedChar") == 0: return 1 - if string.strcmp(ty_name, "CShort") == 0: return 1 - if string.strcmp(ty_name, "CUnsignedShort") == 0: return 1 - if string.strcmp(ty_name, "CLong") == 0: return 1 - if string.strcmp(ty_name, "CUnsignedLong") == 0: return 1 - if string.strcmp(ty_name, "CLongLong") == 0: return 1 - if string.strcmp(ty_name, "CUnsignedLongLong") == 0: return 1 - if string.strcmp(ty_name, "CSizeT") == 0: return 1 - if string.strcmp(ty_name, "CBool") == 0: return 1 - # Character width family: widths 8/16/32 are not 8-byte pointer slots - if string.strcmp(ty_name, "CWCharT") == 0: return 1 - if string.strcmp(ty_name, "CChar8T") == 0: return 1 - if string.strcmp(ty_name, "CChar16T") == 0: return 1 - if string.strcmp(ty_name, "CChar32T") == 0: return 1 if string.strcmp(ty_name, "BOOL") == 0: return 1 if string.strcmp(ty_name, "BYTE") == 0: return 1 if string.strcmp(ty_name, "UCHAR") == 0: return 1 @@ -2241,9 +2211,9 @@ novirtual class AssignGenMixin: # translate_augassign - translate augmented assignment statements def const_assign_msg(self, kind: str, name: str, src_name: str) -> str: """Build the const-assignment error message used by const_assign_fail.""" - decl: str = "(annotation contains t.ConstT; the source name at the declaration was not recorded)" + decl: str = "(annotation contains const; the source name at the declaration was not recorded)" if src_name is not None: decl = f"ConstT | {src_name}" - return GenApi.escape_fstring_copy(f"cannot assign to const variable '{name}' ({kind}); " f"declaration type (including const): {decl}" f"; fix: remove the const descriptor / t.ConstT from the declaration, or assign once at the declaration " f"and afterwards use only non-const copies/temporaries; a const object forbids assignment, " f"augmented assignment and element assignment within its scope, and this never degrades to a warning") + return GenApi.escape_fstring_copy(f"cannot assign to const variable '{name}' ({kind}); " f"declaration type (including const): {decl}" f"; fix: remove the const descriptor / const from the declaration, or assign once at the declaration " f"and afterwards use only non-const copies/temporaries; a const object forbids assignment, " f"augmented assignment and element assignment within its scope, and this never degrades to a warning") def const_assign_fail(self, node: ast.AST*, kind: str, name: str, src_name: str) -> int: """Fail fast when a const target is written.""" @@ -2286,12 +2256,12 @@ novirtual class AssignGenMixin: if target_ty is None: # fail-fast: field type missing, no i32 fallback self.Gen.TypeGenErr.fatal_error(target, "field type information missing", "TypeError") - # t.VolatileT field: both load and store carry the volatile qualifier + # volatile field: both load and store carry the volatile qualifier vol_fa: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target) # BigEndian field: the memory slot holds the byte-swapped pattern, so the current value must # be un-swapped before it takes part in the arithmetic, and the result re-swapped on store. bo_fa: int = self.Gen.ExprGenAttr.attr_field_byte_order(pool, trans, target) - # rawptr[t.VolatileT|T] base: field load/store also carry volatile + # rawptr[volatile|T] base: field load/store also carry volatile at_aug: ast.Attribute* = ast.Attribute*(target) if at_aug is not None: if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_aug.value) != 0: vol_fa = 1 @@ -2359,7 +2329,7 @@ novirtual class AssignGenMixin: if target_ty is None: # fail-fast: element type missing, no i32 fallback self.Gen.TypeGenErr.fatal_error(target, "element type information missing", "TypeError") - # rawptr[t.VolatileT|T] base: p[i] load/store carry volatile when the base is resolvable + # rawptr[volatile|T] base: p[i] load/store carry volatile when the base is resolvable vol_sa: int = 0 sub_aug: ast.Subscript* = ast.Subscript*(target) if sub_aug is not None: vol_sa = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_aug.value) @@ -2452,7 +2422,7 @@ novirtual class AssignGenMixin: if GenApi.is_global_name(trans, nm.id) != 0: scope_type = 1 elif GenApi.is_nonlocal_name(trans, nm.id) != 0: scope_type = 2 - # t.VolatileT variable: both load and store carry the volatile qualifier + # volatile variable: both load and store carry the volatile qualifier vol_aa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) # BigEndian local variable: un-swap on load / re-swap on store so the arithmetic sees the native # value (local scope only; global/nonlocal are not registered in the byte-order table, like TPC). @@ -2560,9 +2530,9 @@ novirtual class AssignGenMixin: return 0 - # is_ctypedef_annotation - check whether an annotation is t.TypedefT + # is_ctypedef_annotation - check whether an annotation is TypedefT def is_ctypedef_annotation(self, annot: ast.AST*) -> int: - """Return 1 if the annotation is t.TypedefT, else 0.""" + """Return 1 if the annotation is TypedefT, else 0.""" return self.Gen.TypeGenCType._is_ctypedef_annotation(annot) # _unwrap_value_type_markers - strip value-only markers around a declaration's type node. @@ -2769,17 +2739,22 @@ novirtual class AssignGenMixin: if o_ref is not None: owner_b = o_ref if owner_b is not None: self.Gen.StmtGen._BorrowAdd(trans, owner_b, nm.id) + # A call returning `own T` transfers ownership to this binding. + if GenApi.annotation_is_own(annot) != 0 and aa.value is not None \ + and aa.value.kind() == ast.ASTKind.Call: + sh_c_own: mlirlite.Value* = self.Gen.VarGen._SlifeEnsureOwned(pool, builder, alloca) + if sh_c_own is not None: self.Gen.VarGen._SlifeNoteStore(pool, builder, sh_c_own, 1) # Store the annotation's class name; pass pool so generic Subscript yields the mangled name if annot is not None: cls_nm_ps: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(annot, trans._imported_modules, pool, trans._from_imports) if cls_nm_ps is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_nm_ps) # Check for bytes* or str* (i8** semantics, 8-byte stride) if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element(trans.SymTab, nm.id) - # const object (annotation has t.ConstT and the type is not a pointer): no writes in scope + # const object (annotation has const and the type is not a pointer): no writes in scope if self.Gen.TypeGenErr.is_const_object_marker(annot, var_ty) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, nm.id) - # t.VolatileT: mark the variable at declaration; its load/store carry volatile + # volatile: mark the variable at declaration; its load/store carry volatile if self.Gen.TypeGenErr.has_decorator_marker(annot, "VolatileT", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id) - # rawptr[t.VolatileT | T]: dereference load/store carry volatile + # rawptr[volatile | T]: dereference load/store carry volatile if self.Gen.TypeGenErr.has_pointee_volatile_marker(annot) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id) return 0 return 0 @@ -2949,7 +2924,7 @@ novirtual class AssignGenMixin: if var_ty.kind() == mlirlite.LLVMType.Struct: self.Gen.TypeGenErr.fatal_error(aa, f"type annotation of '{nm.id}' is a class value type but the initializer is an address ('&...'): did you forget '*'? Write the pointer type ('T*') for a pointer target, or initialize with a value (e.g. 'T(...)')", "TypeError") - # t.Callable function pointer: register the return-type signature + # Callable function pointer: register the return-type signature callable_ret: mlirlite.LLVMType* = self.Gen.TypeGenErr.try_extract_callable_ret(pool, annot, trans._imported_modules, trans._from_imports, trans) if callable_ret is not None: # Nonzero return already reported inside register_func_ptr_sig; this branch is defensive @@ -3016,7 +2991,7 @@ novirtual class AssignGenMixin: # str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node) rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans) - # t.VolatileT global: the initializing store carries the volatile qualifier + # volatile global: the initializing store carries the volatile qualifier vol_gaa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gaa) # Clear the assignment context (all four branches must stay in sync) @@ -3098,7 +3073,7 @@ novirtual class AssignGenMixin: # str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val_mg, aa.annotation, node) rhs_val_mg = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val_mg, target_ty_mg, node, trans) - # t.VolatileT module-level global: the initializing store carries volatile + # volatile module-level global: the initializing store carries volatile vol_mga: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) mlirlite.build_store(builder, rhs_val_mg, mod_glob, vol_mga) # Clear the assignment context (all four branches must stay in sync) @@ -3158,9 +3133,9 @@ novirtual class AssignGenMixin: if cls_nm_hd is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_nm_hd) # Check for bytes* or str* (i8** semantics, 8-byte stride) if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element(trans.SymTab, nm.id) - # Byte-order marker (`T | t.BigEndian` / `T | t.LittleEndian`): register the variable's byte + # Byte-order marker (`T | bigend` / `T | littlend`): register the variable's byte # order so the local load/store paths bswap (mirrors ClassGenDef's field-level detection). - # A bare `t.BigEndian` annotation resolves to i32 upstream, so registration is unconditional. + # A bare `bigend` annotation resolves to i32 upstream, so registration is unconditional. if self.Gen.TypeGenErr.has_decorator_marker(annot, "BigEndian", 0) != 0: self.Gen.VarGen.set_var_byte_order(trans.SymTab, nm.id, 1) elif self.Gen.TypeGenErr.has_decorator_marker(annot, "LittleEndian", 0) != 0: self.Gen.VarGen.set_var_byte_order(trans.SymTab, nm.id, 2) @@ -3177,7 +3152,7 @@ novirtual class AssignGenMixin: val_nm: ast.Name* = ast.Name*(val_cl.func) if val_nm.id is not None: if self.Gen.AsyncGen.is_async_func(trans, val_nm.id) != 0: self.Gen.AsyncGen.register_coro_var(trans, nm.id, val_nm.id) - # c.Addr(async_call): interpret the tt: Coroutine = c.Addr(task()) form in place + # &async_call: interpret the tt: Coroutine = &task() form in place elif val_cl.func is not None and val_cl.func.kind() == ast.ASTKind.Attribute: val_at: ast.Attribute* = ast.Attribute*(val_cl.func) if val_at.attr is not None and string.strcmp(val_at.attr, "Addr") == 0 and val_cl.args is not None and len(val_cl.args) >= 1: @@ -3319,7 +3294,7 @@ novirtual class AssignGenMixin: # str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node) rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans) - # t.VolatileT local variable: mark the VarEntry and store with volatile + # volatile local variable: mark the VarEntry and store with volatile vol_laa: int = 0 if aa.annotation is not None and nm.id is not None: # const object local variable: mark VarEntry.IsConst (idempotent with prescan) @@ -3327,7 +3302,7 @@ novirtual class AssignGenMixin: if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "VolatileT", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id) vol_laa = 1 - # rawptr[t.VolatileT | T]: mark PointeeVolatile (this store writes the pointer itself) + # rawptr[volatile | T]: mark PointeeVolatile (this store writes the pointer itself) if self.Gen.TypeGenErr.has_pointee_volatile_marker(aa.annotation) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id) # `T?`: remember the declaration so later plain `a = None` is accepted self.Gen.VarGen.set_var_nullable(trans.SymTab, nm.id, GenApi.annotation_is_nullable(aa.annotation)) @@ -3454,7 +3429,7 @@ novirtual class AssignGenMixin: sub_nm_lv: ast.Name* = ast.Name*(sub_lv_b.value) if sub_nm_lv.id is not None: bo_lv = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, sub_nm_lv.id)) store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_lv) - # t.VolatileT 字段 / rawptr[t.VolatileT|T] 基址:写出同样带 volatile。 + # volatile 字段 / rawptr[volatile|T] 基址:写出同样带 volatile。 vol_lv: int = 0 if is_attr != 0: vol_lv = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target) @@ -3871,7 +3846,7 @@ novirtual class AssignGenMixin: sub_nm_sw: ast.Name* = ast.Name*(sub_wr.value) if sub_nm_sw.id is not None: bo_sw = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, sub_nm_sw.id)) store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_sw) - # rawptr[t.VolatileT|T] base: p[i] = v stores with volatile when the base is resolvable + # rawptr[volatile|T] base: p[i] = v stores with volatile when the base is resolvable vol_sw: int = 0 if sub_wr is not None: vol_sw = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_wr.value) mlirlite.build_store(builder, store_val, elem_ptr, vol_sw) @@ -3975,7 +3950,7 @@ novirtual class AssignGenMixin: pointee_w: mlirlite.LLVMType* = ptr_w.Ty.Pointee if pointee_w is None: self.Gen.TypeGenErr.fatal_error(target, "deref write target type missing (pointee is empty)", "TypeError") - # `*a = *b` 是 c.Load 语义的**位拷贝**(type-pun):把源内存按**源自身类型**读出来, + # `*a = *b` 是 `*` 语义的**位拷贝**(type-pun):把源内存按**源自身类型**读出来, # 再按位重解释(bitcast)成目的类型存回去 —— **绝不做数值转换**。缺了它 # `*&u = *&xv`(double → uint64)会退化成 fptosi(现场 `cvttsd2si %xmm0, %rax`) # ⇒ IEEE 位型丢失(isnan/isinf、sin/cos 的 ef/neg 全错)。 @@ -4050,9 +4025,9 @@ novirtual class AssignGenMixin: # (pairs the un-swap done by ExprGenAttr.translate_attribute on the load path). bo_fw: int = self.Gen.ExprGenAttr.attr_field_byte_order(pool, trans, target) store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_fw) - # t.VolatileT field: store with the volatile qualifier + # volatile field: store with the volatile qualifier vol_fw: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target) - # rawptr[t.VolatileT|T] base: p.field = v also stores with volatile + # rawptr[volatile|T] base: p.field = v also stores with volatile at_wr: ast.Attribute* = ast.Attribute*(target) if at_wr is not None: if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_wr.value) != 0: vol_fw = 1 @@ -4070,6 +4045,15 @@ novirtual class AssignGenMixin: if nm_rf.id is not None and self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_rf.id) != 0: ref_ok = 1 if ref_ok == 0: self.Gen.TypeGenErr.fatal_error(asgn.value, f"field '.{at_tg.attr}' is a reference: assign a borrow (`&value`), not an owner", "TypeError") + else: + # Reference field: remember the borrow so the owner cannot be del-ed + # while the owning object is in scope. + own_bf: str = self._BorrowOwnerName(asgn.value) + brw_bf: str = None + if at_tg.value is not None and at_tg.value.kind() == ast.ASTKind.Name: + brw_bf = (ast.Name*)(at_tg.value).id + if own_bf is not None and brw_bf is not None: + self.Gen.StmtGen._BorrowAdd(trans, own_bf, brw_bf) mlirlite.build_store(builder, store_val, field_ptr, vol_fw) self._move_out_assigned_source(trans, asgn.value, pool, builder) continue @@ -4125,7 +4109,7 @@ novirtual class AssignGenMixin: target_ty: mlirlite.LLVMType* = None if mod_alloca.Ty is not None: target_ty = mod_alloca.Ty.Pointee if target_ty is not None: rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans) - # t.VolatileT global: store with the volatile qualifier + # volatile global: store with the volatile qualifier vol_gas: int = 0 if nm.id is not None: vol_gas = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gas) @@ -4178,7 +4162,7 @@ novirtual class AssignGenMixin: # memory (pairs the un-swap done by ExprGenName.translate_name_value on the read path). bo_las: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_las) - # t.VolatileT local variable: store with the volatile qualifier + # volatile local variable: store with the volatile qualifier vol_las: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) # M3 所有权记账:必须先记账再写值(覆盖释放要读到旧值); # `p = new heap slife …` → 释放旧对象 + 影子置 1;`q = p` / 外来指针 → 交还所有权 diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 7377a3b..d825e8c 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -106,6 +106,10 @@ novirtual class CallGenBase: # self included for methods. Registered when a callee definition is translated. NullableParams: hashtable.HashTable* = None + # --- owned parameter flags (explicit `own T` params / OBS move-in): key = callee symbol name + # -> byte-per-parameter '0'/'1' (1 = the callee takes ownership and releases it). + OwnParams: hashtable.HashTable* = None + def InitCallGen(self) -> int: """Initialize the call mixin fields (inlined by subclass constructors since NoVTable does not forward inherited symbols)""" if self is None: return 1 @@ -145,6 +149,42 @@ novirtual class CallGenBase: if code == 50: return 2 # '2' = temp return 0 + # _register_own_params - store per-parameter `own` flags for a callee symbol name. + def _register_own_params(self, name: char*, flags: char*, count: int) -> int: + """Store per-parameter '0'/'1' owned flags (PermPool-persistent) keyed by callee symbol name.""" + if name is None or flags is None or count <= 0: return 0 + perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool + if perm is None: return 0 + if self.OwnParams is None: + self.OwnParams = hashtable.HashTable(perm) + if self.OwnParams is None: return 0 + key: char* = perm.alloc(string.strlen(name) + 1) + if key is None: return 0 + string.strcpy(key, name) + val: char* = perm.alloc(sizet(count) + 1) + if val is None: return 0 + string.memcpy(val, flags, sizet(count)) + val[count] = char(0) + self.OwnParams[key] = val + return 1 + + # _param_own_known - 1 when an `own` mask was registered for the callee symbol. + def _param_own_known(self, name: char*) -> int: + if self.OwnParams is None or name is None: return 0 + v: rawptr = self.OwnParams.__getitem__(name) + return 0 if v is None else 1 + + # _param_is_own - 1 when parameter idx of the callee symbol takes ownership. + def _param_is_own(self, name: char*, idx: int) -> int: + if self.OwnParams is None or name is None or idx < 0: return 0 + v: rawptr = self.OwnParams.__getitem__(name) + if v is None: return 0 + flags: char* = rawptr(v) + i: int = 0 + while i < idx and flags[i] != char(0): i += 1 + if i != idx: return 0 + return 1 if int(flags[idx]) == 49 else 0 + # _register_nullable_params - store per-parameter nullability flags for a callee symbol name. def _register_nullable_params(self, name: char*, flags: char*, count: int) -> int: """Store per-parameter '0'/'1' nullable flags (PermPool-persistent) keyed by callee symbol name.""" diff --git a/App/lib/core/Gen/Call/CallGenLib.vp b/App/lib/core/Gen/Call/CallGenLib.vp index fe25e4e..b15c309 100644 --- a/App/lib/core/Gen/Call/CallGenLib.vp +++ b/App/lib/core/Gen/Call/CallGenLib.vp @@ -179,6 +179,29 @@ novirtual class CallGenLib(CallGenBase.CallGenBase): # ensure-load + direct cache lookup (not _vpi_method_param_annot, which misses module funcs) if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.Gen.CallGenBase.TempDir) prm_annots: char* = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm) + # Ownership: an `own` parameter takes the argument's ownership. A local `own` binding is + # moved in; `new` / calls are already owned; a borrowed value is rejected. + if trans is not None and cargs is not None and self.Gen.CallGenBase._param_own_known(call_name) != 0: + arg_n_o: int = int(len(cargs)) + ai_o: int = 0 + while ai_o < arg_n_o: + an_o: ast.AST* = cargs.get(sizet(ai_o)) + ai_o += 1 + if an_o is None: continue + pidx_o: int = ai_o - 1 + self_off + if self.Gen.CallGenBase._param_is_own(call_name, pidx_o) == 0: continue + if an_o.kind() == ast.ASTKind.Name: + nm_o: ast.Name* = ast.Name*(an_o) + if nm_o.id is None: continue + src_o: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_o.id) + if src_o is not None and self.Gen.VarGen._SlifeOwned(src_o) != 0: + sh_o: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(src_o) + if sh_o is not None: self.Gen.VarGen._SlifeNoteStore(pool, builder, sh_o, 0) + self.Gen.VarGen._FreedMark(src_o) + continue + self.Gen.TypeGenErr.fatal_error(an_o, f"cannot pass '{nm_o.id}' to an `own` parameter: it is not owned here; move an `own` binding or pass `new ...`", "LifetimeError") + elif an_o.kind() == ast.ASTKind.UnaryOp: + self.Gen.TypeGenErr.fatal_error(an_o, "cannot pass a borrow to an `own` parameter: the callee takes ownership", "LifetimeError") # Strict OBS: a nullable binding/field may be None and must not flow into a non-nullable # parameter. Same-module callees use the nullable registry, cross-module ones use their # vpi param annotations; an unknown callee signature is left alone. diff --git a/App/lib/core/Gen/Class/ClassGenDef.vp b/App/lib/core/Gen/Class/ClassGenDef.vp index 758e3f5..0528ba0 100644 --- a/App/lib/core/Gen/Class/ClassGenDef.vp +++ b/App/lib/core/Gen/Class/ClassGenDef.vp @@ -8,6 +8,7 @@ import stdio import viperlib import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HT +import lib.core.Gen.GenApi as GenApi import lib.core.CompilerFactory as CompilerFactory from . import ClassGenBase @@ -152,27 +153,29 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): field_types_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) field_defaults_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) field_annot_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) - # Field volatile flag buffer (1 = annotation union contains t.VolatileT), parallel + # Field volatile flag buffer (1 = annotation union contains volatile), parallel # to field_annot_buf with values 0/1. field_volatile_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) - # Field pointee-volatile flag buffer (1 = rawptr[t.VolatileT | T], i.e. volatile T*), + # Field pointee-volatile flag buffer (1 = rawptr[volatile | T], i.e. volatile T*), # parallel to field_volatile_buf with values 0/1. field_pointee_volatile_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) - # Field const-object flag buffer (1 = annotation has t.ConstT and the type is not + # Field const-object flag buffer (1 = annotation has const and the type is not # a pointer), parallel to field_volatile_buf with values 0/1. field_const_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) - # Field byte-order buffer (0 = native, 1 = t.BigEndian, 2 = t.LittleEndian), parallel - # to field_volatile_buf; from the `X | t.BigEndian` annotation union. + # Field byte-order buffer (0 = native, 1 = bigend, 2 = littlend), parallel + # to field_volatile_buf; from the `X | bigend` annotation union. field_byteorder_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) # Field nullable flag buffer (1 = `own T?` / `T?`), parallel to field_volatile_buf. field_nullable_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) # Field reference kind buffer (0=none, 1=`&T`, 2=`&mut T` in __obs__ files). field_refkind_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) + # Field `own T` flag buffer (1 = the containing object owns the pointee). + field_own_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) if field_names_buf is None or field_types_buf is None or field_defaults_buf is None: return 0 if field_annot_buf is None or field_volatile_buf is None: return 0 if field_pointee_volatile_buf is None or field_const_buf is None: return 0 if field_byteorder_buf is None or field_nullable_buf is None: return 0 - if field_refkind_buf is None: return 0 + if field_refkind_buf is None or field_own_buf is None: return 0 string.memset(field_names_buf, 0, 8 * GenTypes.FIELD_MAX) string.memset(field_types_buf, 0, 8 * GenTypes.FIELD_MAX) string.memset(field_defaults_buf, 0, 8 * GenTypes.FIELD_MAX) @@ -183,6 +186,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): string.memset(field_byteorder_buf, 0, 8 * GenTypes.FIELD_MAX) string.memset(field_nullable_buf, 0, 8 * GenTypes.FIELD_MAX) string.memset(field_refkind_buf, 0, 8 * GenTypes.FIELD_MAX) + string.memset(field_own_buf, 0, 8 * GenTypes.FIELD_MAX) field_count: int = 0 @@ -237,6 +241,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): # Inherit the nullable flag (unchanged after flattening) field_nullable_buf[field_count] = sizet(pfe.Nullable) field_refkind_buf[field_count] = sizet(pfe.RefKind) + field_own_buf[field_count] = sizet(pfe.IsOwn) field_count += 1 children: list[ast.AST*]* = cd.children @@ -344,26 +349,27 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): diag_cn_val: sizet = sizet(annot_cn) field_annot_buf[field_count] = diag_cn_val else: field_annot_buf[field_count] = 0 - # t.VolatileT storage-class flag: loads/stores through this field are - # volatile (e.g. Spinlock.locked: t.VolatileT | t.CInt). + # volatile storage-class flag: loads/stores through this field are + # volatile (e.g. Spinlock.locked: volatile | int). if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "VolatileT", 0) != 0: field_volatile_buf[field_count] = 1 else: field_volatile_buf[field_count] = 0 - # rawptr[t.VolatileT | T] (volatile pointee, i.e. C volatile T*): derefs + # rawptr[volatile | T] (volatile pointee, i.e. C volatile T*): derefs # through this field (self.ptr[i]/self.ptr.field) are volatile. if self.Gen.TypeGenErr.has_pointee_volatile_marker(aa.annotation) != 0: field_pointee_volatile_buf[field_count] = 1 else: field_pointee_volatile_buf[field_count] = 0 - # const object field (annotation has t.ConstT, non-pointer type): assigning + # const object field (annotation has const, non-pointer type): assigning # through the object fail-fasts, matching TPC's Gen.field_const_flags. if self.Gen.TypeGenErr.is_const_object_marker(aa.annotation, field_ty) != 0: field_const_buf[field_count] = 1 else: field_const_buf[field_count] = 0 - # Byte-order marker (t.BigEndian / t.LittleEndian) -> 1 / 2; annotation `|` - # means "and", so `X | t.BigEndian` is a BigEndian-encoded X field. + # Byte-order marker (bigend / littlend) -> 1 / 2; annotation `|` + # means "and", so `X | bigend` is a BigEndian-encoded X field. if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "BigEndian", 0) != 0: field_byteorder_buf[field_count] = 1 elif self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "LittleEndian", 0) != 0: field_byteorder_buf[field_count] = 2 else: field_byteorder_buf[field_count] = 0 # `own T?` / `T?`: direct `.` access is rejected; use `?.` or a None check. field_nullable_buf[field_count] = sizet(self._ann_nullable(aa.annotation)) field_refkind_buf[field_count] = sizet(self.Gen.TypeGenResolve.ref_kind(aa.annotation)) + field_own_buf[field_count] = sizet(GenApi.annotation_is_own(aa.annotation)) else: field_annot_buf[field_count] = 0 field_volatile_buf[field_count] = 0 @@ -372,6 +378,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): field_byteorder_buf[field_count] = 0 field_nullable_buf[field_count] = 0 field_refkind_buf[field_count] = 0 + field_own_buf[field_count] = 0 field_count += 1 if field_count == 0: @@ -536,11 +543,11 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): if fannot_addr != 0: fannot = str(fannot_addr) if fname is not None and fty2 is not None: fidx_add: int = self.Gen.StructGen.add_field(pool, entry, fname, fty2, fdef, fannot) - # t.VolatileT field flag (field_volatile_buf[fi] corresponds 1:1 with add_field order) + # volatile field flag (field_volatile_buf[fi] corresponds 1:1 with add_field order) if fidx_add >= 0 and field_volatile_buf[fi] != 0: fe_vol: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add) if fe_vol is not None: fe_vol.IsVolatile = 1 - # rawptr[t.VolatileT | T] field flag (field_pointee_volatile_buf[fi] corresponds + # rawptr[volatile | T] field flag (field_pointee_volatile_buf[fi] corresponds # 1:1 with add_field): derefs through it load/store with volatile. if fidx_add >= 0 and field_pointee_volatile_buf[fi] != 0: fe_pv: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add) @@ -561,6 +568,9 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): if fidx_add >= 0 and field_refkind_buf[fi] != 0: fe_rk: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add) if fe_rk is not None: fe_rk.RefKind = int(field_refkind_buf[fi]) + if fidx_add >= 0 and field_own_buf[fi] != 0: + fe_ow: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add) + if fe_ow is not None: fe_ow.IsOwn = 1 # Phase 1a declaration mode: only register the struct and OOP flags, no method bodies if trans._declare_only == 1: diff --git a/App/lib/core/Gen/Class/ClassGenGeneric.vp b/App/lib/core/Gen/Class/ClassGenGeneric.vp index 3183b87..b7e159a 100644 --- a/App/lib/core/Gen/Class/ClassGenGeneric.vp +++ b/App/lib/core/Gen/Class/ClassGenGeneric.vp @@ -523,7 +523,7 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase): if pa is None or pa.kind() != ast.ASTKind.Arg: continue arg: ast.Arg* = ast.Arg*(pa) if arg.arg is None: continue - # One shared parameter mapping (CVoid skipped, class -> Ptr, array -> element pointer). + # One shared parameter mapping (void skipped, class -> Ptr, array -> element pointer). param_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.param_llvm_ty(trans, arg, gg_ctx, 0) if param_ty is None: continue pname: str = pool.alloc(32) @@ -563,6 +563,20 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase): nc_g += 1 nul_buf_g_[nc_g] = char(0) self.Gen.CallGenBase._register_nullable_params(spec_name, nul_buf_g_, nc_g) + own_buf_g_: str = pool.alloc(an + 1) + if own_buf_g_ is not None: + oc_g: int = 0 + for ai_og in range(int(an)): + pa_og: ast.AST* = alist.get(ai_og) + f_og: char = char(48) + if pa_og is not None and pa_og.kind() == ast.ASTKind.Arg: + arg_og: ast.Arg* = ast.Arg*(pa_og) + if arg_og.annotation is not None and GenApi.annotation_is_own(arg_og.annotation) != 0: + f_og = char(49) + own_buf_g_[oc_g] = f_og + oc_g += 1 + own_buf_g_[oc_g] = char(0) + self.Gen.CallGenBase._register_own_params(spec_name, own_buf_g_, oc_g) if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(saved_cur) trans.ModuleSha1 = saved_ms trans._imported_modules = saved_imp diff --git a/App/lib/core/Gen/Class/ClassGenVTable.vp b/App/lib/core/Gen/Class/ClassGenVTable.vp index 038eb1a..a8fd423 100644 --- a/App/lib/core/Gen/Class/ClassGenVTable.vp +++ b/App/lib/core/Gen/Class/ClassGenVTable.vp @@ -715,7 +715,7 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): arg_m: ast.Arg* = ast.Arg*(alist.get(ai_m)) mm: int = 0 if arg_m is not None and arg_m.annotation is not None: - if self.Gen.TypeGenResolve._is_builtin_marker(arg_m.annotation, "CVoid", imported_modules) == 0: + if self.Gen.TypeGenResolve._is_bare_void(arg_m.annotation) == 0: mm = self.Gen.TypeGenResolve.ref_param_mode(arg_m.annotation, imported_modules) if mm == 1: mode_buf[mode_cnt] = char(49) elif mm == 2: mode_buf[mode_cnt] = char(50) @@ -741,6 +741,23 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase): nc_m += 1 nul_buf_m[nc_m] = char(0) self.Gen.CallGenBase._register_nullable_params(mangled_name, nul_buf_m, nc_m) + # Per-parameter ownership (`own T` params), same indexing. + own_buf_m: str = pool.alloc(an + 1) + if own_buf_m is not None: + oc_m: int = 0 + if is_static_m == 0: + own_buf_m[oc_m] = char(48) + oc_m += 1 + for ai_o in range(int(start_idx), int(an)): + arg_o: ast.Arg* = ast.Arg*(alist.get(ai_o)) + f_o: char = char(48) + if arg_o is not None and arg_o.annotation is not None \ + and GenApi.annotation_is_own(arg_o.annotation) != 0: + f_o = char(49) + own_buf_m[oc_m] = f_o + oc_m += 1 + own_buf_m[oc_m] = char(0) + self.Gen.CallGenBase._register_own_params(mangled_name, own_buf_m, oc_m) # *args variadic: set IsVarArg=1 so the signature carries the ... suffix (the body # pipeline allocates the va_list buffer, same as a module-level function). diff --git a/App/lib/core/Gen/Func/FuncGenBase.vp b/App/lib/core/Gen/Func/FuncGenBase.vp index 947169a..f78cb70 100644 --- a/App/lib/core/Gen/Func/FuncGenBase.vp +++ b/App/lib/core/Gen/Func/FuncGenBase.vp @@ -38,7 +38,7 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): if deco is None: continue dk: int = deco.kind() # Direct form `@t.attr.` (same as ClassGenBase._has_decorator): `inline def f` - # synthesizes it and does NOT require `import c`; unsupported names are ignored. + # synthesizes it and does NOT require an import; unsupported names are ignored. if dk == ast.ASTKind.Attribute: dan: str = self._get_attr_name_from_node(deco) if dan is not None and string.strcmp(dan, "section") != 0: @@ -230,26 +230,6 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): if ecn_f.id is not None and string.strcmp(ecn_f.id, "StopIteration") == 0: is_stop_f = 1 if is_stop_f == 0: return 1 return 0 - # RejectCReturnDecorator - @c.CReturn was removed; fail at compile time if seen. - # Multi-return goes only through `-> tuple[...]`; any CReturn form is fail-fast, shared with FuncGenDef. - def RejectCReturnDecorator(self, node: ast.AST*, decorators: list[ast.AST*]*) -> int: - """Fail if @c.CReturn appears in the decorators; 0=none found.""" - if decorators is None: return 0 - rcr_dn: sizet = len(decorators) - for rcr_i in range(int(rcr_dn)): - rcr_deco: ast.AST* = decorators.get(rcr_i) - if rcr_deco is None: continue - # Handle both @c.CReturn(...) (Call(func=Attribute)) and bare @c.CReturn (Attribute) - rcr_attr: ast.Attribute* = None - if rcr_deco.kind() == ast.ASTKind.Call: - rcr_call: ast.Call* = ast.Call*(rcr_deco) - if rcr_call.func is not None and rcr_call.func.kind() == ast.ASTKind.Attribute: rcr_attr = ast.Attribute*(rcr_call.func) - elif rcr_deco.kind() == ast.ASTKind.Attribute: rcr_attr = ast.Attribute*(rcr_deco) - if rcr_attr is None or rcr_attr.attr is None: continue - if string.strcmp(rcr_attr.attr, "CReturn") == 0: - self.Gen.TypeGenErr.fatal_error(node, "`@c.CReturn` removed: use a return annotation `-> tuple[ty1, ty2, ...]` instead (multiple return values are packed into a struct return)", "TypeError") - return 1 - return 0 # Create LLVM function (simple version, declaration only) def create_function(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, name: str, args_node: ast.AST*, ret_ty: mlirlite.LLVMType*) -> mlirlite.Function*: """Create an LLVM function and add its parameters.""" @@ -267,11 +247,11 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): for ai in range(int(an)): arg: ast.Arg* = ast.Arg*(alist.get(ai)) if arg is not None and arg.arg is not None: - # CVoid / void means no args: skip + # void means no args: skip if arg.annotation is not None: if arg.annotation.kind() == ast.ASTKind.Name: at_cn: ast.Name* = ast.Name*(arg.annotation) - if at_cn.id is not None and (string.strcmp(at_cn.id, "CVoid") == 0 or string.strcmp(at_cn.id, "void") == 0): continue + if at_cn.id is not None and string.strcmp(at_cn.id, "void") == 0: continue param_ty: mlirlite.LLVMType* = i32_ty if arg.annotation is not None: resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, None, None, None, 0) @@ -339,9 +319,6 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): elif self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExportT", 0) != 0: ret_ty = mlirlite.Void(pool) elif self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExternT", 0) != 0: ret_ty = mlirlite.Void(pool) - # @c.CReturn was removed: encountering it is a compile-time error (use -> tuple[...]). - if ret_ty is None: self.RejectCReturnDecorator(fd, fd.decorator_list) - if ret_ty is not None: return ret_ty if fd.returns is not None: @@ -362,7 +339,7 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): return mlirlite.Void(pool) # param_llvm_ty - the single parameter-type mapping (signature and alloca must agree). - # Returns None for a `CVoid` placeholder (the parameter is omitted), else the LLVM type: + # Returns None for a `void` placeholder (the parameter is omitted), else the LLVM type: # bare class -> Ptr(struct), ArrayT -> element pointer (C array decay), others as resolved. def param_llvm_ty(self, trans: HT.Translator*, arg: ast.Arg*, ctx_label: str, pure_mode: int) -> mlirlite.LLVMType*: """Map one parameter annotation to its LLVM type; None means the parameter is omitted.""" @@ -373,8 +350,8 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool) if arg.annotation is None: return i32_ty - # CVoid means empty param: skip, do not add to the signature or allocate it. - if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: return None + # void means empty param: skip, do not add to the signature or allocate it. + if self.Gen.TypeGenResolve._is_bare_void(arg.annotation) != 0: return None resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0) if resolved is None: @@ -436,6 +413,11 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): alloca: mlirlite.Value* = mlirlite.build_alloca(builder, param_ty2) if alloca is None: continue self.Gen.VarGen.define_var(trans.SymTab, arg2.arg, alloca) + if GenApi.annotation_is_own(arg2.annotation) != 0: + # An `own` parameter takes ownership: the callee releases it at exit. + self.Gen.VarGen.set_var_own(trans.SymTab, arg2.arg, 1) + sh_own_p: mlirlite.Value* = self.Gen.VarGen._SlifeEnsureOwned(pool, builder, alloca) + if sh_own_p is not None: self.Gen.VarGen._SlifeNoteStore(pool, builder, sh_own_p, 1) if GenApi.strict_obs() != 0 and arg2.annotation is not None: rk_p: int = self.Gen.TypeGenResolve.ref_kind(arg2.annotation) if rk_p != 0: self.Gen.VarGen.set_var_ref_kind(trans.SymTab, arg2.arg, rk_p) @@ -448,7 +430,7 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): if pure_mode != 0: if string.strcmp(arg2.arg, "self") == 0: self.Gen.VarGen.set_var_life(trans.SymTab, arg2.arg, GenTypes.LIFE_FRAME) elif self.Gen.ExprGenBase._pure_str_struct_ty(pool, param_ty2) is not None: self.Gen.VarGen.set_var_life(trans.SymTab, arg2.arg, GenTypes.LIFE_FRAME) - # t.ConstT / t.VolatileT / volatile pointee markers are per-parameter properties. + # const / volatile / volatile pointee markers are per-parameter properties. if arg2.annotation is not None: if self.Gen.TypeGenErr.has_decorator_marker(arg2.annotation, "ConstT", 0) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, arg2.arg) if self.Gen.TypeGenErr.has_decorator_marker(arg2.annotation, "VolatileT", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, arg2.arg) @@ -526,11 +508,11 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): if k3 == ast.ASTKind.If or k3 == ast.ASTKind.While or k3 == ast.ASTKind.For or k3 == ast.ASTKind.Try or k3 == ast.ASTKind.With or k3 == ast.ASTKind.Match: last_is_ctrl = 1 # A procedure never returns a value: explicit type, plus the annotation-level checks - # (`-> None` / t.CVoid) for the case where the REnum class-pattern match degrades. + # (`-> None` / `void`) for the case where the REnum class-pattern match degrades. is_void: int = self.ty_is_void(ret_ty) if is_void == 0 and unannotated_void != 0: if fd.returns is None: is_void = 1 - elif self.Gen.TypeGenResolve._is_cvoid_marker(fd.returns, trans._imported_modules) != 0: is_void = 1 + elif self.Gen.TypeGenResolve._is_void_marker(fd.returns) != 0: is_void = 1 elif fd.returns.kind() == ast.ASTKind.Constant: ret_none_cn: ast.Constant* = (ast.Constant*)(fd.returns) if ret_none_cn.const_kind == ast.CONST_NONE: is_void = 1 diff --git a/App/lib/core/Gen/Func/FuncGenDef.vp b/App/lib/core/Gen/Func/FuncGenDef.vp index abe8bf7..3d84850 100644 --- a/App/lib/core/Gen/Func/FuncGenDef.vp +++ b/App/lib/core/Gen/Func/FuncGenDef.vp @@ -44,8 +44,8 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): for i in range(int(an)): arg: ast.Arg* = ast.Arg*(alist.get(i)) if arg is None or arg.arg is None: continue - # CVoid params are not emitted into the signature, so they must not occupy a mode slot. - if arg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imp) != 0: continue + # void params are not emitted into the signature, so they must not occupy a mode slot. + if arg.annotation is not None and self.Gen.TypeGenResolve._is_bare_void(arg.annotation) != 0: continue m: int = 0 if arg.annotation is not None: m = self.Gen.TypeGenResolve.ref_param_mode(arg.annotation, imp) if m == 1: buf[cnt] = char(49) @@ -63,7 +63,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): for i2 in range(int(an)): arg2: ast.Arg* = ast.Arg*(alist.get(i2)) if arg2 is None or arg2.arg is None: continue - if arg2.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg2.annotation, "CVoid", imp) != 0: continue + if arg2.annotation is not None and self.Gen.TypeGenResolve._is_bare_void(arg2.annotation) != 0: continue f2: char = char(48) if arg2.annotation is not None and GenApi.annotation_is_nullable(arg2.annotation) != 0: f2 = char(49) nul_buf[nc] = f2 @@ -71,6 +71,21 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): nul_buf[nc] = char(0) self.Gen.CallGenBase._register_nullable_params(name, nul_buf, nc) stdlib.free(nul_buf) + # Per-parameter ownership (`own T` params). + own_buf: bytes = stdlib.malloc(an + 1) + if own_buf is not None: + oc: int = 0 + for i3 in range(int(an)): + arg3: ast.Arg* = ast.Arg*(alist.get(i3)) + if arg3 is None or arg3.arg is None: continue + if arg3.annotation is not None and self.Gen.TypeGenResolve._is_bare_void(arg3.annotation) != 0: continue + f3: char = char(48) + if arg3.annotation is not None and GenApi.annotation_is_own(arg3.annotation) != 0: f3 = char(49) + own_buf[oc] = f3 + oc += 1 + own_buf[oc] = char(0) + self.Gen.CallGenBase._register_own_params(name, own_buf, oc) + stdlib.free(own_buf) return 1 # ============================================================ @@ -160,8 +175,8 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): if arg is None or arg.arg is None: continue ty: mlirlite.LLVMType* = None if arg.annotation is not None: - # CVoid 是"空形参"占位,不参与签名 - if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: continue + # void 是"空形参"占位,不参与签名 + if self.Gen.TypeGenResolve._is_bare_void(arg.annotation) != 0: continue ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0) if ty is None: stdlib.free(buf) @@ -331,7 +346,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): if arg is None or arg.arg is None: continue pty: mlirlite.LLVMType* = None if arg.annotation is not None: - if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", trans._imported_modules) != 0: continue + if self.Gen.TypeGenResolve._is_bare_void(arg.annotation) != 0: continue pty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, trans._imported_modules, trans._from_imports, trans, 0) if pty is None: self.Gen.TypeGenErr.fatal_error(arg, f"runtime CDLL extern '{fd.name}' parameter '{arg.arg}' type annotation failed to resolve", "NameError") @@ -644,9 +659,9 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): for ai in range(int(an)): arg: ast.Arg* = ast.Arg*(alist.get(ai)) if arg is None or arg.arg is None: continue - # CVoid means empty param: skip + # void means empty param: skip if arg.annotation is not None: - if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: continue + if self.Gen.TypeGenResolve._is_bare_void(arg.annotation) != 0: continue param_ty: mlirlite.LLVMType* = i32_ty if arg.annotation is not None: resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0) @@ -760,7 +775,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): for pai in range(int(len(pure_ags.args))): parg: ast.Arg* = ast.Arg*(pure_ags.args.get(pai)) if parg is None or parg.arg is None or parg.annotation is None: continue - if self.Gen.TypeGenResolve._is_builtin_marker(parg.annotation, "CVoid", imported_modules) != 0: continue + if self.Gen.TypeGenResolve._is_bare_void(parg.annotation) != 0: continue pty_pa: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, parg.annotation, imported_modules, from_imports, trans, 0) if pty_pa is not None and (self.Gen.ExprGenBase.is_ptr_type(pty_pa) != 0 or pty_pa.kind() == mlirlite.LLVMType.Array): self.Gen.TypeGenErr.fatal_error(parg, f"pure function '{fd.name}' parameter '{parg.arg}' cannot be a pointer or array: pure parameters are immutable value copies (use str/bytes for a pure_str value, or a pure class value)", "TypeError") @@ -796,7 +811,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): # Skip builtin decorators if string.strcmp(dname, "staticmethod") == 0 or string.strcmp(dname, "property") == 0 or string.strcmp(dname, "classmethod") == 0: continue # Skip t-module storage markers (@t.ExportT/@t.ExternT/@t.State/@t.TLS/@t.StaticT/@t.InlineT/@t.VTableT/ - # @t.NoVTable/@t.Object/@t.EnumBase/@t.UnionBase/@t.REnum/@t.DefineT). Attribute-form @t.xxx is not handled here. + # @t.NoVTable/@t.Object/@t.EnumBase/@t.UnionBase/@t.REnum/@DefineT). Attribute-form @t.xxx is not handled here. # 另跳过**编译期生命期标注**(P2:规范形式 @life(willFree(...)) 与兼容叠加形式 @willFree(...)): # 它们是纯标注、没有运行时装饰器函数。漏在这里会让 VPC 生成 __decor_wrap_ wrapper, # 去调用并不存在的 @life/@willFree,并把被装饰的 extern def(如 stdlib.free)变成 internal 声明 @@ -878,8 +893,8 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): for ai_e in range(int(an_e)): arg_e: ast.Arg* = ast.Arg*(alist_e.get(ai_e)) if arg_e is not None and arg_e.arg is not None: - # CVoid means empty param: skip, do not add to the function signature - if arg_e.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg_e.annotation, "CVoid", imported_modules) != 0: continue + # void means empty param: skip, do not add to the function signature + if arg_e.annotation is not None and self.Gen.TypeGenResolve._is_bare_void(arg_e.annotation) != 0: continue param_ty_e: mlirlite.LLVMType* = i32_ty if arg_e.annotation is not None: resolved_e: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg_e.annotation, imported_modules, from_imports, trans, 0) @@ -945,8 +960,8 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase): for reuse_i in range(int(reuse_n)): rearg: ast.Arg* = ast.Arg*(reuse_list.get(reuse_i)) if rearg is None or rearg.arg is None: continue - # CVoid means empty param: not part of the signature (mirrors the add_param loops above) - if rearg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(rearg.annotation, "CVoid", imported_modules) != 0: continue + # void means empty param: not part of the signature (mirrors the add_param loops above) + if rearg.annotation is not None and self.Gen.TypeGenResolve._is_bare_void(rearg.annotation) != 0: continue if reused_p is None: # 复用声明形参少于定义:签名与函数体必然错配,禁止静默,fail-fast self.Gen.TypeGenErr.fatal_error(rearg, f"reused declaration for '{fd.name}' has fewer parameters than the definition", "TypeError") diff --git a/App/lib/core/Gen/GenTypes.vp b/App/lib/core/Gen/GenTypes.vp index a5c7256..9f6cf1e 100644 --- a/App/lib/core/Gen/GenTypes.vp +++ b/App/lib/core/Gen/GenTypes.vp @@ -67,19 +67,19 @@ class VarEntry: # TypeSpec (sole type carrier since S3): filled by add_var via SpecFromLLVMType from # alloca.Ty's pointee (PermPool, cross-file safe); annotation side augments sign/source/elem. Spec: TSpec.TypeSpec* - # 1=load/store of this variable carries LLVM volatile (annotation union has t.VolatileT). + # 1=load/store of this variable carries LLVM volatile (annotation union has volatile). # Appended at the end so existing field offsets are unchanged. IsVolatile: int - # 1=this variable is rawptr[t.VolatileT | T] (volatile pointee, like C's volatile T*): + # 1=this variable is rawptr[volatile | T] (volatile pointee, like C's volatile T*): # its dereferences carry volatile. Unlike IsVolatile, which limits the pointer variable itself. PointeeVolatile: int - # 1=this variable is a const object (annotation has t.ConstT and the type is not a + # 1=this variable is a const object (annotation has const and the type is not a # pointer): assignment to it anywhere in scope fails fast. Pointer ConstT only limits the pointee. IsConst: int # 生命期标签(LifeTag,见文件头 define LIFE_*):该变量**当前值**的生命期。 # 追加在末尾以保持既有字段偏移不变。默认 LIFE_UNKNOWN(未登记 = 保守放行)。 Life: int - # Byte order of this variable, from the `T | t.BigEndian` / `T | t.LittleEndian` annotation union: + # Byte order of this variable, from the `T | bigend` / `T | littlend` annotation union: # 0=native (no marker), 1=BigEndian, 2=LittleEndian. Appended at the end so existing field offsets # are unchanged; local loads/stores bswap when 1 (16/32/64-bit ints only). Mirrors FieldEntry.ByteOrder. ByteOrder: int @@ -120,15 +120,15 @@ class FieldEntry: # field_ty (PermPool, safe across 1b reset); annotation side adds sign/source. Spec: TSpec.TypeSpec* # 1=load/store of this field via an object carries LLVM volatile (field annotation union - # has t.VolatileT, e.g. Spinlock.locked). Appended at the end so offsets are unchanged. + # has volatile, e.g. Spinlock.locked). Appended at the end so offsets are unchanged. IsVolatile: int - # 1=this field is rawptr[t.VolatileT | T] (volatile pointee): dereferences via it + # 1=this field is rawptr[volatile | T] (volatile pointee): dereferences via it # (self.ptr[i] / self.ptr.field) carry volatile. Same distinction as VarEntry.PointeeVolatile. PointeeVolatile: int - # 1=this field is a const object (annotation has t.ConstT and the type is not a pointer): + # 1=this field is a const object (annotation has const and the type is not a pointer): # assigning it via an object fails fast, same as VarEntry.IsConst. IsConst: int - # Byte order of this field, from the `X | t.BigEndian` / `X | t.LittleEndian` annotation + # Byte order of this field, from the `X | bigend` / `X | littlend` annotation # union: 0=native (no marker), 1=BigEndian, 2=LittleEndian. Appended at the end so offsets # are unchanged; consumers bswap on field load/store when 1 (16/32/64-bit ints only). ByteOrder: int @@ -137,6 +137,9 @@ class FieldEntry: Nullable: int # Reference kind in an `__obs__` file: 0=not a reference, 1=`&T` (read-only), 2=`&mut T`. RefKind: int + # 1 when the field is declared `own T`: the containing object owns the pointee and releases + # it when the object is deleted. + IsOwn: int # ============================================================ diff --git a/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp b/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp index 357bd89..c1222fa 100644 --- a/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp @@ -44,7 +44,7 @@ novirtual class StmtGenCifMixin: """Check the internal cif marker attribute name produced by the parser (c.@CIf family). The '@' prefix is internal-only spelling (a leading '@' cannot be lexed as an identifier), so a - user-written c.CIf/c.CIfdef/c.CIfndef never matches here and is reported as removed instead. + user-written CIf spellings never match here. """ if attr is None: return 0 if string.strcmp(attr, "@CIf") == 0: return 1 @@ -448,6 +448,8 @@ novirtual class StmtGenCifMixin: self.Gen.TypeGenErr.fatal_error(rt.value, "the function's return type is not nullable: a nullable value may be None; return `T?` or unwrap it with `match` first", "TypeError") return 0 + # Ownership: returning a local `own` binding transfers it to the caller (no release here). + # Handled after the value is translated below. val: mlirlite.Value* = None if rt.value is not None: # f-string escape (return f"..."): its buffer lives on this frame's stack and dies on @@ -529,15 +531,15 @@ novirtual class StmtGenCifMixin: ret_val_ty: mlirlite.LLVMType* = None cur_func_rv: mlirlite.Function* = trans._cur_func if cur_func_rv is not None: ret_val_ty = mlirlite.function_get_ret_ty(cur_func_rv) - # `return <闭包变量>` 且返回注解是 t.Callable:闭包对象是 {fn_ptr, env_ptr}, - # 不是裸函数指针。调用方把结果存进 t.Callable 变量后 `f()` 会按裸函数指针直调, + # `return <闭包变量>` 且返回注解是 Callable:闭包对象是 {fn_ptr, env_ptr}, + # 不是裸函数指针。调用方把结果存进 Callable 变量后 `f()` 会按裸函数指针直调, # 跳到堆地址崩溃(ClosureCallTest 现场)。这里明确 fail-fast,要求用 rawptr。 if rt.value.kind() == ast.ASTKind.Name and trans is not None and trans.CurFuncRetAnnot is not None: clo_nm_rv: ast.Name* = ast.Name*(rt.value) if clo_nm_rv.id is not None and self.Gen.CallGenLeafKit.is_closure_name(clo_nm_rv.id) != 0: clo_ret_rv: mlirlite.LLVMType* = self.Gen.TypeGenErr.try_extract_callable_ret(pool, trans.CurFuncRetAnnot, trans._imported_modules, trans._from_imports, trans) if clo_ret_rv is not None: - self.Gen.TypeGenErr.fatal_error(rt, f"the return annotation is t.Callable (a bare function pointer) but the returned value is the closure variable '{clo_nm_rv.id}'. A closure is a (fn_ptr, env_ptr) object; storing it where a t.Callable is expected makes the caller jump into heap data. Annotate the return as rawptr instead", "TypeError") + self.Gen.TypeGenErr.fatal_error(rt, f"the return annotation is Callable (a bare function pointer) but the returned value is the closure variable '{clo_nm_rv.id}'. A closure is a (fn_ptr, env_ptr) object; storing it where a Callable is expected makes the caller jump into heap data. Annotate the return as rawptr instead", "TypeError") # [C1] return-annotation context: record this return's direct value node so a bare # generic ctor (`return Stack()` in `-> Stack[int]`) specializes via CurFuncRetAnnot. trans.CurRetNode = rt.value @@ -599,6 +601,12 @@ novirtual class StmtGenCifMixin: if rt_slot_p is not None and self.Gen.AssignGen._pure_str_slot(trans.Pool, rt_slot_p) != 0: rt_sh_p: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(rt_slot_p) if rt_sh_p is not None: self.Gen.VarGen._SlifeNoteStore(trans.Pool, builder, rt_sh_p, 0) + # `return ` in a `-> own T` function: ownership goes to the caller. + fd_own: ast.FunctionDef* = ast.FunctionDef*(self.Gen.StructGen.NllCurFunc) + if fd_own is not None and GenApi.annotation_is_own(fd_own.returns) != 0 \ + and rt_slot_p is not None and self.Gen.VarGen._SlifeOwned(rt_slot_p) != 0: + rt_sh_o: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(rt_slot_p) + if rt_sh_o is not None: self.Gen.VarGen._SlifeNoteStore(trans.Pool, builder, rt_sh_o, 0) 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) @@ -1120,7 +1128,7 @@ novirtual class StmtGenCifMixin: cn_ik: ast.Constant* = ast.Constant*(iter_node) if cn_ik.const_kind != ast.CONST_STR: self.Gen.TypeGenErr.fatal_error(iter_node, "for iteration does not support this constant (only strings, arrays, list literals, range())") elif ik == ast.ASTKind.Dict or ik == ast.ASTKind.Set or ik == ast.ASTKind.Tuple or ik == ast.ASTKind.SetComp or ik == ast.ASTKind.DictComp: - self.Gen.TypeGenErr.fatal_error(iter_node, "for iteration does not support dict/set/tuple literals; use range(), a t.ArrayT array, or a list literal") + self.Gen.TypeGenErr.fatal_error(iter_node, "for iteration does not support dict/set/tuple literals; use range(), a fixed-size array, or a list literal") return self._handle_ptr_iter(trans, for_node, var_name) call: ast.Call* = ast.Call*(iter_node) diff --git a/App/lib/core/Gen/Stmt/StmtGenMixin.vp b/App/lib/core/Gen/Stmt/StmtGenMixin.vp index 80ba5ee..e221b8b 100644 --- a/App/lib/core/Gen/Stmt/StmtGenMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenMixin.vp @@ -244,6 +244,8 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): self.Gen.TypeGenErr.fatal_error(tgt, f"cannot `del {nm_del.id}`: the pointer value could not be loaded", "TypeError") continue self._emit_del_dtor(pool, builder, mod, trans, del_cur, del_var_ty, del_struct_ty) + # `own` fields belong to this object: release them before freeing it. + self._release_own_fields(pool, builder, mod, trans, del_cur, del_struct_ty) if self.Gen.CallGenBase.find_func_in_module(mod, "free") is None: self.Gen.TypeGenErr.fatal_error(tgt, "cannot `del`: the module has no declaration of the C library function 'free'", "TypeError") continue @@ -330,6 +332,37 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): # translate_assert - translate `assert cond[, msg]` into a single testcheck.check(...) call: # a true condition is silent (ok_msg=None), a false one reports FAIL via the standard path # (no printf/exit/exception; the checker prints nothing when the condition holds). + # _release_own_fields - free the `own` fields of a heap object before the object itself is freed. + def _release_own_fields(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, + mod: mlirlite.LLVMModule*, trans: HT.Translator*, + cur: mlirlite.Value*, struct_ty: mlirlite.LLVMType*, depth: int = 0) -> int: + if pool is None or builder is None or mod is None or cur is None or struct_ty is None: return 0 + if depth > 8: return 0 + entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(struct_ty) + if entry is None: return 0 + if self.Gen.CallGenBase.find_func_in_module(mod, "free") is None: return 0 + i8p: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) + fi: int = 0 + while fi < entry.FieldCount: + fe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fi) + fi += 1 + if fe is None or fe.IsOwn == 0: continue + if fe.Ty is None or fe.Ty.Pointee is None: continue + if self.Gen.ExprGenBase.is_ptr_type(fe.Ty) == 0: continue + fp: mlirlite.Value* = mlirlite.build_gep_struct(builder, struct_ty, fe.Ty, cur, fe.Index) + if fp is None: continue + fv: mlirlite.Value* = mlirlite.build_load(builder, fe.Ty, fp) + if fv is None: continue + inner: mlirlite.LLVMType* = fe.Ty.Pointee + self._emit_del_dtor(pool, builder, mod, trans, fv, fe.Ty, inner) + self._release_own_fields(pool, builder, mod, trans, fv, inner, depth + 1) + arg_f: mlirlite.Value* = fv + if fv.Ty is not None and uint64(fv.Ty) != uint64(i8p): + arg_f = mlirlite.build_bitcast(builder, fv, i8p) + if arg_f is not None: + mlirlite.build_call(builder, "free", arg_f, 1, mlirlite.Void(pool), 0) + return 1 + def translate_assert(self, trans: HT.Translator*, node: ast.AST*) -> int: """Translate an assert statement; returns 0""" if trans is None or node is None: return 0 diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index 68e4a38..3f2dbe5 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -418,9 +418,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if _out_s is not None: return _out_s # ---- 2. already normalized (SHA1 prefix) ---- if self._is_sha1_prefixed(type_name) != 0: return type_name - # ---- 3. t.XXX / c.XXX builtin namespace ---- - if (type_name[0] == 't' or type_name[0] == 'c') and type_name[1] == '.': return type_name - # ---- 4. "mod.Class" Attribute ---- + # ---- 3. "mod.Class" Attribute ---- dot_pos: str = string.strchr(type_name, '.') if dot_pos is not None: mod_len: sizet = sizet(uint64(dot_pos) - uint64(type_name)) @@ -607,7 +605,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): # `T1 | T2` unions were retired: an annotation string containing '|' is invalid. if string.strchr(type_str, '|') is not None: return None - # Check for '[' (Subscript) like t.Callable[[int], int]; must precede the '.' + # Check for '[' (Subscript) like Callable[[int], int]; must precede the '.' # check since it is a Subscript. Parse fully via ast.parse_expression. bracket_idx: sizet = seg_len for bi in range(int(seg_len)): @@ -655,7 +653,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): expr_body: ast.AST* = expr_node.body if expr_body is not None: return expr_body # Fallback: file-level ast.parse; the single-expression entry fails on complex - # slices like "t.Callable[[int], int]" but the file-level one can parse them. + # slices like "Callable[[int], int]" but the file-level one can parse them. tree_s: ast.AST* = ast.parse(type_str, pool) if tree_s is not None: ch_s: list[ast.AST*]* = tree_s.children @@ -984,7 +982,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): stdlib.free(b_buf) return None if b_read < LONG(BUF_B): b_buf[sizet(b_read)] = 0 - # Scan line-by-line and register all builtins "NAME: t.TypedefT = TYPE" entries at + # Scan line-by-line and register all builtins "NAME: TypedefT = TYPE" entries at # once; existing entries (module/import-injected) take priority and are not overwritten. b_total: sizet = string.strlen(b_buf) # Pre-scan import lines to register aliases (import stdint as __stdint), so later @@ -1037,7 +1035,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if b_i == b_total or b_buf[b_i] == '\n': b_line_len: sizet = b_i - b_line_start if b_line_len > 0 and b_td_buf is not None: - # Parse both equivalent forms (`typedef N = V` and `N: t.TypedefT = V`); + # Parse both equivalent forms (`typedef N = V` and `N: TypedefT = V`); # builtins.vp now uses the sugar form, so accept both. if self._parse_typedef_line(b_td_buf, b_buf, b_line_start, b_line_len) != 0: b_nb: str = b_td_buf @@ -1124,7 +1122,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if v is None: return None return rawptr(v) def _lookup_library_typedef(self, pool: memhub.MemBuddy*, mod: str, attr: str) -> str: - """Look up "attr: t.TypedefT = TYPE" in a library module source, returning TYPE or None.""" + """Look up "attr: TypedefT = TYPE" in a library module source, returning TYPE or None.""" if mod is None or attr is None: return None # Restore the real module name from a builtins import alias (__stdint -> stdint); # without this, SHA1 lookup and path resolution all miss. @@ -1168,7 +1166,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if m_i == m_total or m_buf[m_i] == '\n': m_line_len: sizet = m_i - m_ls if m_line_len > 0 and m_td_buf is not None: - # Parse both equivalent forms (`typedef N = V` and `N: t.TypedefT = V`), + # Parse both equivalent forms (`typedef N = V` and `N: TypedefT = V`), # both of which occur in library module sources. if self._parse_typedef_line(m_td_buf, m_buf, m_ls, m_line_len) != 0: m_nb: str = m_td_buf @@ -1198,7 +1196,6 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): mod_len: sizet = sizet(uint64(dot_pos) - uint64(type_str)) attr: str = dot_pos + 1 if mod_len == 0 or attr is None or attr[0] == '\0': return type_str - if mod_len == 1 and (type_str[0] == 't' or type_str[0] == 'c'): return type_str mod_buf: str = pool.alloc(mod_len + 1) if mod_buf is None: return type_str string.strncpy(mod_buf, type_str, mod_len) @@ -1210,7 +1207,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if ts_x is not None and self.Gen.TypeGenCType.CTypedefFound != 0 and string.strcmp(ts_x, type_str) != 0: return self._expand_typedef_ref(pool, ts_x, depth + 1) return type_str # _IsCallableSubscriptValue - whether a Subscript value is a function-pointer type - # constructor (t.Callable / callable / Callable); used to allow the unsigned forms. + # constructor (Callable / callable / Callable); used to allow the unsigned forms. def _IsCallableSubscriptValue(self, value_node: ast.AST*) -> int: if value_node is None: return 0 vk: int = value_node.kind() @@ -1330,7 +1327,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): # 存疑 - # Lowercase alias callable: equivalent to t.Callable in annotations. + # Lowercase alias callable: equivalent to Callable in annotations. elif string.strcmp(type_name, "callable") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) # stdint.vp aliases (INT/BYTE/DWORD/int8_t/size_t/_Bool ...) now come from its @@ -1339,21 +1336,13 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): elif string.strcmp(type_name, "float") == 0: return mlirlite.Float(pool) elif string.strcmp(type_name, "double") == 0: return mlirlite.Double(pool) return None - def _is_builtin_marker(self, node: ast.AST*, attr_name: str, imported_modules: str) -> int: - """Whether a node is the bare builtin marker name (e.g. PtrT / RefT).""" - if node is None or attr_name is None: return 0 - if node.kind() != ast.ASTKind.Name: return 0 - nm: ast.Name* = ast.Name*(node) - if nm.id is None: return 0 - if string.strcmp(nm.id, attr_name) != 0: return 0 - return 1 def target_is_big_endian(self) -> int: """Whether the compilation target is big-endian (1) or little-endian (0). The configured data layout wins: LLVM's first data-layout field is the endianness marker ('e' = little, 'E' = big; omitted means little). Without a data layout, fall back to the triple's `_be` arch suffix (e.g. aarch64_be-...); otherwise little, which - is the default target. Both byte-order markers are judged against this: a `t.BigEndian` - field on a big-endian target needs no swap, and a `t.LittleEndian` field on a + is the default target. Both byte-order markers are judged against this: a `bigend` + field on a big-endian target needs no swap, and a `littlend` field on a big-endian target does.""" dl: str = Config.TargetDataLayout if dl is not None and dl[0] != '\0': @@ -1434,12 +1423,15 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): rn: ast.Name* = ast.Name*(at.value) if rn.id is None or string.strcmp(rn.id, "attr") != 0: return 0 return 1 - def _is_cvoid_marker(self, node: ast.AST*, imported_modules: str) -> int: - """Whether a node is a CVoid / void marker: 1=yes / 0=no.""" - if self._is_builtin_marker(node, "CVoid", imported_modules) != 0: return 1 + def _is_bare_void(self, node: ast.AST*) -> int: + """Whether a node is exactly the bare `void` name: 1=yes / 0=no.""" + if node is None or node.kind() != ast.ASTKind.Name: return 0 + nm: ast.Name* = ast.Name*(node) + return 1 if nm.id is not None and string.strcmp(nm.id, "void") == 0 else 0 + def _is_void_marker(self, node: ast.AST*) -> int: + """Whether a node is a void marker: 1=yes / 0=no.""" if node is not None and node.kind() == ast.ASTKind.Name: nm: ast.Name* = ast.Name*(node) - # Bare CVoid is synonymous with the builtin alias void if nm.id is not None and string.strcmp(nm.id, "void") == 0: return 1 # Storage / descriptor wrappers keep the wrapped type's void-ness (a pointer wrapper does not). if node is not None and node.kind() == ast.ASTKind.Subscript: @@ -1453,7 +1445,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): or string.strcmp(sw_nm.id, "DefineT") == 0 or string.strcmp(sw_nm.id, "RefT") == 0 \ or string.strcmp(sw_nm.id, "TempRefT") == 0 or string.strcmp(sw_nm.id, "MutRefT") == 0 \ or string.strcmp(sw_nm.id, "AutoPtrT") == 0): - if sub_v.slice is not None: return self._is_cvoid_marker(sub_v.slice, imported_modules) + if sub_v.slice is not None: return self._is_void_marker(sub_v.slice) return 0 def annotation_to_str(self, annot: ast.AST*) -> str: """Render a type-annotation AST to a readable string; None means not extractable.""" @@ -1472,7 +1464,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if nm.id is not None: return nm.id return "" - # Attribute node: t.CBool, memhub.MemBuddy + # Attribute node: bool, memhub.MemBuddy if k == ast.ASTKind.Attribute: at: ast.Attribute* = ast.Attribute*(annot) if at.attr is None: return "" @@ -1593,8 +1585,8 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if k == ast.ASTKind.Attribute: at: ast.Attribute* = ast.Attribute*(node) if at.attr is None: return None - # t.XXX builtin type: return XXX (e.g. uint32 -> "CUInt32T") so AnnotClassName - # can record it; consumers degrade gracefully for non-struct names. + # Qualified type: return "module.Type" so later resolution can record it; + # consumers degrade gracefully for non-struct names. if at.value is not None and at.value.kind() == ast.ASTKind.Name: val_nm2: ast.Name* = ast.Name*(at.value) if val_nm2.id is not None and string.strcmp(val_nm2.id, "attr") == 0: return at.attr @@ -1716,7 +1708,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): # named Option is never shadowed in files that do not use Option[...]. if string.strcmp(gen_cls_name, "Option") == 0: self.Gen.ClassGenGeneric._ensure_builtin_option(None) # tuple[T1, T2, ...] -> anonymous struct {T1, T2, ...} (multi-value packing, matching - # TPC LLVMTypeMapper._resolve_subscript), for `-> tuple[str, t.CUnsignedShort]` and unpacking. + # TPC LLVMTypeMapper._resolve_subscript), for `-> tuple[str, ushort]` and unpacking. if string.strcmp(gen_cls_name, "tuple") == 0: tp_elts: list[ast.AST*]* = None tp_count: sizet = 0 @@ -1981,9 +1973,9 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): return mlirlite.Int8(pool) elif string.strcmp(nm.id, "bytes") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) elif string.strcmp(nm.id, "bool") == 0: - # bool = t.CBool = i1 (consistent with CBool in map_builtin_type) + # bool = bool = i1 (consistent with CBool in map_builtin_type) return mlirlite.Int1(pool) - # Bare float types: float = t.CFloat = f32, double = t.CDouble = f64 + # Bare float types: float = float = f32, double = double = f64 elif string.strcmp(nm.id, "float") == 0: return mlirlite.Float(pool) elif string.strcmp(nm.id, "double") == 0: return mlirlite.Double(pool) # Enum base type, needed by the struct branches below (forward-placeholder guard). @@ -2238,7 +2230,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): # 模块段无法解析时退回"同名唯一 / 同名基类型一致"。 enum_fb_ty: mlirlite.LLVMType* = self._resolve_enum_base_ty(at.attr, None, trans) if enum_fb_ty is not None: return enum_fb_ty - # Module-qualified typedef (posix.DIR = t.TypedefT = t.CVoid* etc.): same + # Module-qualified typedef (posix.DIR = TypedefT = void* etc.): same # lookup_ctypedef + recursive resolution as the Name branch. if self.Gen.TypeGenCType.CTypedefResolveDepth < TypeGenMixin.MAX_CTYPEDEF_DEPTH: mod_td_str: str = self.Gen.TypeGenCType.lookup_ctypedef(at.attr) @@ -2305,10 +2297,10 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if k == ast.ASTKind.Subscript: sub: ast.Subscript* = ast.Subscript*(node) if sub.value is None: self.Gen.TypeGenErr.fatal_type_error(node, "Subscript node missing value") - # An empty subscript (t.Callable[]) has slice None: allowed only for unsigned function + # An empty subscript (Callable[]) has slice None: allowed only for unsigned function # pointers (handled as i8* below); other types still require a slice. if sub.slice is None and self._IsCallableSubscriptValue(sub.value) == 0: self.Gen.TypeGenErr.fatal_type_error(node, "Subscript node missing value or slice") - # Function pointers (bare callable / Callable) equal t.Callable point-for-point and + # Function pointers (bare callable / Callable) equal Callable point-for-point and # map to i8*; must be checked before the Name path (generic/ArrayT/builtins fallback). if sub.value.kind() == ast.ASTKind.Name and self._IsCallableSubscriptValue(sub.value) != 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) # Generic class annotation (list[int]): look up the specialized struct for a Name value. @@ -2329,20 +2321,17 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): and self.Gen.ClassGenGeneric._find_generic_template(gen_nm.id) is None: bx_inner: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) if bx_inner is None: return None - match bx_inner: - case mlirlite.LLVMType.Ptr(_bp): return bx_inner - case _: return mlirlite.Ptr(pool, bx_inner) + return mlirlite.Ptr(pool, bx_inner) # `RawT[T]`: the unboxed value type. if string.strcmp(gen_nm.id, "RawT") == 0 \ and self.Gen.ClassGenGeneric._find_generic_template("RawT") is None: return self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) # `own T`: one owned pointer layer (exclusive ownership of a heap object). + # Every `own` adds a layer, so `own own T` / `Box[Box[T]]` stay two pointers. if string.strcmp(gen_nm.id, "OwnT") == 0: ow_inner: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) if ow_inner is None: return None - match ow_inner: - case mlirlite.LLVMType.Ptr(_op): return ow_inner - case _: return mlirlite.Ptr(pool, ow_inner) + return mlirlite.Ptr(pool, ow_inner) # Builtin marker wrappers: descriptor / storage / value-reference markers carry # no LLVM type of their own, so resolve the wrapped type. if string.strcmp(gen_nm.id, "ConstT") == 0 or string.strcmp(gen_nm.id, "VolatileT") == 0 \ @@ -2370,7 +2359,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if count_node_arr.kind() == ast.ASTKind.Constant: cnt_arr0: ast.Constant* = ast.Constant*(count_node_arr) if cnt_arr0.const_kind == ast.CONST_NONE: - self.Gen.TypeGenErr.fatal_type_error(count_node_arr, "Built-in fixed-size array constructor 'array' length must be a positive integer constant (>0); None is not allowed (use t.ArrayT[E] for automatic length inference). Correct example: state: array[uint32, 4]") + self.Gen.TypeGenErr.fatal_type_error(count_node_arr, "Built-in fixed-size array constructor 'array' length must be a positive integer constant (>0); None is not allowed (use [N]T for automatic length inference). Correct example: state: array[uint32, 4]") elem_ty_arr: mlirlite.LLVMType* = self.resolve_annotation_type(pool, elem_node_arr, imported_modules, from_imports, trans, depth + 1) if elem_ty_arr is None: return None count_arr: int = self.Gen.TypeGenCType._eval_const_int_expr(count_node_arr, pool, from_imports, imported_modules) @@ -2478,13 +2467,13 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): return mlirlite.Ptr(pool, elem_ty_single) # Two-arg form: t.ArrayT[elem_ty, count] -> [count x elem_ty] tup: ast.Tuple* = ast.Tuple*(sub.slice) - if tup.elts is None: self.Gen.TypeGenErr.fatal_type_error(node, "t.ArrayT Tuple missing elts") + if tup.elts is None: self.Gen.TypeGenErr.fatal_type_error(node, "array tuple missing elements") elts_list: list[ast.AST*]* = tup.elts elts_n: sizet = len(elts_list) - if elts_n < 2: self.Gen.TypeGenErr.fatal_type_error(node, "t.ArrayT Tuple has too few elements") + if elts_n < 2: self.Gen.TypeGenErr.fatal_type_error(node, "array tuple has too few elements") elem_node: ast.AST* = elts_list.get(0) count_node: ast.AST* = elts_list.get(1) - if elem_node is None or count_node is None: self.Gen.TypeGenErr.fatal_type_error(node, "t.ArrayT Tuple element is None") + if elem_node is None or count_node is None: self.Gen.TypeGenErr.fatal_type_error(node, "array tuple element is None") # Recursively resolve the element type elem_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, elem_node, imported_modules, from_imports, trans, depth + 1) if elem_ty is None: @@ -2500,8 +2489,8 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): # Use _eval_const_int_expr for the constant integer expression (Constant/Name/BinOp); # pool/from_imports/imported_modules enable cross-module DefineT lookup. count_val = self.Gen.TypeGenCType._eval_const_int_expr(count_node, pool, from_imports, imported_modules) - if self.Gen.TypeGenCType.ConstEvalOk == 0: self.Gen.TypeGenErr.fatal_type_error(count_node, "t.ArrayT count must be an integer constant, None, a DefineT constant name, or a constant expression") - if count_val <= 0: self.Gen.TypeGenErr.fatal_type_error(count_node, "t.ArrayT count must be positive") + if self.Gen.TypeGenCType.ConstEvalOk == 0: self.Gen.TypeGenErr.fatal_type_error(count_node, "array count must be an integer constant, None, a DefineT constant name, or a constant expression") + if count_val <= 0: self.Gen.TypeGenErr.fatal_type_error(count_node, "array count must be positive") # Create the ArrayType (no Ptr wrapping), so build_alloca allocates the array # and alloca returns [N x elem_ty]*. return mlirlite.Array(pool, elem_ty, count_val)