From 37c4b0da3bebf9f1a5abcc0b54f9d477f111a260 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 13 Oct 2026 12:30:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(gen):=20=E5=AD=97=E8=8A=82=E5=BA=8F=20bswa?= =?UTF-8?q?p=20=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=AD=97=E6=AE=B5/=E5=B1=80=E9=83=A8=E5=8F=98=E9=87=8F/?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E5=85=83=E7=B4=A0=E8=AF=BB=E5=86=99=E5=A4=84?= =?UTF-8?q?=E6=88=90=E5=AF=B9=20llvm.bswap=EF=BC=88=E4=BB=85=20BigEndian?= =?UTF-8?q?=20=E4=B8=94=2016/32/64=20=E4=BD=8D=E6=95=B4=E6=95=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/lib/core/Gen/AssignGenMixin.vp | 80 +++++++++++++++++++++++++--- App/lib/core/Gen/Expr/ExprGenAttr.vp | 32 +++++++++-- App/lib/core/Gen/Expr/ExprGenBase.vp | 21 ++++++++ App/lib/core/Gen/Expr/ExprGenName.vp | 14 ++++- App/lib/core/Gen/GenTypes.vp | 4 ++ App/lib/core/Gen/VarGenMixin.vp | 19 +++++++ 6 files changed, 158 insertions(+), 12 deletions(-) diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index e5af5ae..ee651fa 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -1331,8 +1331,12 @@ novirtual class AssignGenMixin: return 0 # _init_global_array_from_list - initialize a global array from a list literal - def _init_global_array_from_list(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, array_alloca: mlirlite.Value*, list_node: ast.AST*, trans: HT.Translator*, owner_ctx: str = None) -> int: - """Initialize a global array from a list literal via per-element GEP+store.""" + def _init_global_array_from_list(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, array_alloca: mlirlite.Value*, list_node: ast.AST*, trans: HT.Translator*, owner_ctx: str = None, byte_order: int = 0) -> int: + """Initialize a global array from a list literal via per-element GEP+store. + + byte_order (0=native / 1=BigEndian / 2=LittleEndian): when the array variable is big-endian + every element is stored byte-swapped, so a later element read (which un-swaps) sees the same + value; left at 0 for module globals / struct-field defaults (not registered).""" if builder is None or array_alloca is None or list_node is None: return 1 # Get the array type (pointee of [N x elem_ty]*) @@ -1415,7 +1419,7 @@ novirtual class AssignGenMixin: # Null check for nested element: use is not None (not integer 0) if elem_node is not None and elem_node.kind() == ast.ASTKind.List: inner_arr = 1 if inner_arr != 0: - self._init_global_array_from_list(builder, pool, mod, elem_ptr, elem_node, trans, owner_ctx) + self._init_global_array_from_list(builder, pool, mod, elem_ptr, elem_node, trans, owner_ctx, byte_order) continue elem_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, elem_node, None, 0, trans) if elem_val is None: @@ -1424,6 +1428,8 @@ novirtual class AssignGenMixin: # Type coercion (e.g. i32 -> i8); Pointee is statically i64 so compare with != 0 store_val: mlirlite.Value* = elem_val if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, elem_val, elem_ptr.Ty.Pointee, elem_node) + # BigEndian array: store each element byte-swapped (pairs the un-swap on element read). + store_val = self.Gen.ExprGenBase.apply_bswap_if_big(pool, builder, store_val, byte_order) mlirlite.build_store(builder, store_val, elem_ptr) return 0 @@ -1571,6 +1577,9 @@ novirtual class AssignGenMixin: self.Gen.TypeGenErr.fatal_error(target, "field type information missing", "TypeError") # t.CVolatile 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.CVolatile|T] base: field load/store also carry volatile at_aug: ast.Attribute* = ast.Attribute*(target) if at_aug is not None: @@ -1579,6 +1588,7 @@ novirtual class AssignGenMixin: if cur_val is None: # fail-fast: attribute current value load failed self.Gen.TypeGenErr.fatal_error(target, "cannot load the current value of the attribute", "TypeError") + cur_val = self.Gen.ExprGenBase.apply_bswap_if_big(pool, builder, cur_val, bo_fa) # 3. Translate the RHS value rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans) @@ -1597,6 +1607,9 @@ novirtual class AssignGenMixin: if result is None: # fail-fast: type coercion failed self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError") + # BigEndian field: re-swap the result so the slot keeps the byte-swapped pattern + # (pairs the un-swap of cur_val above, keeping memory big-endian). + result = self.Gen.ExprGenBase.apply_bswap_if_big(pool, builder, result, bo_fa) mlirlite.build_store(builder, result, field_ptr, vol_fa) return 0 @@ -1637,10 +1650,18 @@ novirtual class AssignGenMixin: 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) + # BigEndian array variable (base is a Name with a registered byte order): element load/store + # bswap (TPC HandlesAssign._StoreArrElemWithCoerceBswap). Non-Name bases yield 0 -> no swap. + bo_sa: int = 0 + if sub_aug is not None and sub_aug.value is not None and sub_aug.value.kind() == ast.ASTKind.Name: + sub_nm_aug: ast.Name* = ast.Name*(sub_aug.value) + if sub_nm_aug.id is not None: + bo_sa = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, sub_nm_aug.id)) cur_val: mlirlite.Value* = mlirlite.build_load(builder, target_ty, elem_ptr, vol_sa) if cur_val is None: # fail-fast: element current value load failed self.Gen.TypeGenErr.fatal_error(target, "cannot load the current value of the subscript element", "TypeError") + cur_val = self.Gen.ExprGenBase.apply_bswap_if_big(pool, builder, cur_val, bo_sa) # 3. Translate the RHS value rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans) @@ -1659,6 +1680,8 @@ novirtual class AssignGenMixin: if result is None: # fail-fast: type coercion failed self.Gen.TypeGenErr.fatal_error(target, "type coercion failed", "TypeError") + # BigEndian array element: re-swap the result so the slot stays byte-swapped (pairs cur_val above). + result = self.Gen.ExprGenBase.apply_bswap_if_big(pool, builder, result, bo_sa) mlirlite.build_store(builder, result, elem_ptr, vol_sa) return 0 @@ -1715,6 +1738,9 @@ novirtual class AssignGenMixin: # t.CVolatile 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). + bo_aa: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) # 2. Load the current value cur_val: mlirlite.Value* = None @@ -1743,6 +1769,8 @@ novirtual class AssignGenMixin: self.Gen.TypeGenErr.fatal_error(target, "variable type information missing", "TypeError") target_ty = target_alloca.Ty.Pointee cur_val = mlirlite.build_load(builder, target_ty, target_alloca, vol_aa) + # BigEndian local: un-swap the loaded slot value before the arithmetic (paired with the store below). + cur_val = self.Gen.ExprGenBase.apply_bswap_if_big(pool, builder, cur_val, bo_aa) if cur_val is None: # fail-fast: target variable current value load failed @@ -1808,7 +1836,10 @@ novirtual class AssignGenMixin: if nl_ptr is not None: mlirlite.build_store(builder, result, nl_ptr) else: # Ordinary local variable - if target_alloca is not None: mlirlite.build_store(builder, result, target_alloca, vol_aa) + if target_alloca is not None: + # BigEndian local: re-swap the result so the slot keeps the byte-swapped pattern (pairs the load above). + result = self.Gen.ExprGenBase.apply_bswap_if_big(pool, builder, result, bo_aa) + mlirlite.build_store(builder, result, target_alloca, vol_aa) return 0 @@ -2292,6 +2323,11 @@ 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 + # 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. + 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) new_vars: int = 0 existing: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nm.id) @@ -2325,7 +2361,10 @@ novirtual class AssignGenMixin: if local_arr_ty is not None: zero_arr: mlirlite.Value* = mlirlite.ConstZero(pool, local_arr_ty) if zero_arr is not None: mlirlite.build_store(builder, zero_arr, alloca) - self._init_global_array_from_list(builder, pool, mod, alloca, aa.value, trans) + # BigEndian array variable: pass its byte order so the literal elements are stored + # byte-swapped (pairs the un-swap on element read). + arr_list_bo: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) + self._init_global_array_from_list(builder, pool, mod, alloca, aa.value, trans, None, arr_list_bo) else: # Set CurAssignAnnot/CurAssignNode for bare generic construction (sync across all four branches) trans.CurAssignAnnot = aa.annotation @@ -2414,6 +2453,10 @@ novirtual class AssignGenMixin: # M3 所有权记账:必须**先**记账再写值——覆盖释放要读到槽里的旧值 # (`p: T* = new heap slife …` → 释放旧对象 + 影子置 1;`q: T* = p` → 交还所有权) self._slife_note_assign(pool, builder, mod, trans, alloca, aa.value, nm.id) + # BigEndian local: store the byte-swapped value so the slot is big-endian in memory + # (pairs the un-swap done by ExprGenName.translate_name_value on the read path). + decl_bo: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) + rhs_val = self.Gen.ExprGenBase.apply_bswap_if_big(pool, builder, rhs_val, decl_bo) mlirlite.build_store(builder, rhs_val, alloca, vol_laa) # P2 willFree:带注解声明/赋值后清除失效态(新变量本就为 0;复用槽兜底) self.Gen.VarGen.clear_var_freed(trans.SymTab, nm.id) @@ -2507,6 +2550,16 @@ novirtual class AssignGenMixin: if store_val is None: self.Gen.TypeGenErr.fatal_error(aa, "type coercion failed for the annotated assignment to an attribute/subscript", "TypeError") return 0 + # BigEndian field / array element: byte-swap before the store (pairs the read paths). + bo_lv: int = 0 + if is_attr != 0: + bo_lv = self.Gen.ExprGenAttr.attr_field_byte_order(pool, trans, target) + else: + sub_lv_b: ast.Subscript* = ast.Subscript*(target) + if sub_lv_b.value is not None and sub_lv_b.value.kind() == ast.ASTKind.Name: + 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_bswap_if_big(pool, builder, store_val, bo_lv) # t.CVolatile 字段 / rawptr[t.CVolatile|T] 基址:写出同样带 volatile。 vol_lv: int = 0 if is_attr != 0: @@ -2724,9 +2777,16 @@ novirtual class AssignGenMixin: self._reject_whole_object_copy_into_ptr_slot(pool, trans, target, elem_ptr, rhs_val) # Guard: coerce only when the element type is complete if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, elem_ptr.Ty.Pointee, node, trans) + # BigEndian array variable (base is a Name with a registered byte order): store the + # byte-swapped element so the slot is big-endian in memory (pairs the element read path). + bo_sw: int = 0 + sub_wr: ast.Subscript* = ast.Subscript*(target) + if sub_wr is not None and sub_wr.value is not None and sub_wr.value.kind() == ast.ASTKind.Name: + 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_bswap_if_big(pool, builder, store_val, bo_sw) # rawptr[t.CVolatile|T] base: p[i] = v stores with volatile when the base is resolvable vol_sw: int = 0 - sub_wr: ast.Subscript* = ast.Subscript*(target) 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) continue @@ -2859,6 +2919,10 @@ novirtual class AssignGenMixin: # Get the field type and coerce rhs_val (e.g. i32 -> i64) store_val: mlirlite.Value* = rhs_val if field_ptr.Ty is not None and field_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, field_ptr.Ty.Pointee, node, trans) + # BigEndian field: store the byte-swapped value so the slot is big-endian in memory + # (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_bswap_if_big(pool, builder, store_val, bo_fw) # t.CVolatile field: store with the volatile qualifier vol_fw: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target) # rawptr[t.CVolatile|T] base: p.field = v also stores with volatile @@ -2968,6 +3032,10 @@ novirtual class AssignGenMixin: # Coerce the value to the alloca type (e.g. double -> float) store_val: mlirlite.Value* = rhs_val if alloca.Ty is not None and alloca.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, alloca.Ty.Pointee, node, trans) + # BigEndian local variable: store the byte-swapped value so the slot is big-endian in + # 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_bswap_if_big(pool, builder, store_val, bo_las) # t.CVolatile 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 所有权记账:必须先记账再写值(覆盖释放要读到旧值); diff --git a/App/lib/core/Gen/Expr/ExprGenAttr.vp b/App/lib/core/Gen/Expr/ExprGenAttr.vp index 8be3271..093b7fd 100644 --- a/App/lib/core/Gen/Expr/ExprGenAttr.vp +++ b/App/lib/core/Gen/Expr/ExprGenAttr.vp @@ -648,8 +648,13 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase): decl_ptr_rn: mlirlite.LLVMType* = mlirlite.Ptr(pool, decl_ty_rn) if decl_ptr_rn is not None: cast_ptr_rn: mlirlite.Value* = mlirlite.build_bitcast(builder, alias_ptr, decl_ptr_rn) - if cast_ptr_rn is not None: return mlirlite.build_load(builder, decl_ty_rn, cast_ptr_rn) - return mlirlite.build_load(builder, alias_fe.Ty, alias_ptr, vol_ba_rd) + if cast_ptr_rn is not None: + rn_alias_val: mlirlite.Value* = mlirlite.build_load(builder, decl_ty_rn, cast_ptr_rn) + # BigEndian REnum payload alias: un-swap the load (pairing the write side). + return self.apply_bswap_if_big(pool, builder, rn_alias_val, alias_fe.ByteOrder) + rn_slot_val: mlirlite.Value* = mlirlite.build_load(builder, alias_fe.Ty, alias_ptr, vol_ba_rd) + # BigEndian REnum slot: un-swap the load (pairing the write side). + return self.apply_bswap_if_big(pool, builder, rn_slot_val, alias_fe.ByteOrder) # property getter fallback: when obj.attr is a @property/@x.getter method, emit a call # (try before reporting a field-not-found error, to avoid a misleading message) if field_info is None and at.value is not None and trans is not None: @@ -709,7 +714,10 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase): if self.Gen.StructGen.is_union_by_type(struct_ty) == 1: field_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, field_ty) casted: mlirlite.Value* = mlirlite.build_bitcast(builder, obj_ptr, field_ptr_ty) - if casted is not None: return mlirlite.build_load(builder, field_ty, casted, field_vol_rd) + if casted is not None: + union_val_rd: mlirlite.Value* = mlirlite.build_load(builder, field_ty, casted, field_vol_rd) + # BigEndian field: the slot holds the byte-swapped pattern, so un-swap the load. + return self.apply_bswap_if_big(pool, builder, union_val_rd, field_info.ByteOrder) return None # normal struct: GEP + load field_idx: int = field_info.Index @@ -724,11 +732,17 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase): decl_ptr_rd: mlirlite.LLVMType* = mlirlite.Ptr(pool, decl_ty_rd) if decl_ptr_rd is not None: cast_ptr_rd: mlirlite.Value* = mlirlite.build_bitcast(builder, field_ptr, decl_ptr_rd) - if cast_ptr_rd is not None: return mlirlite.build_load(builder, decl_ty_rd, cast_ptr_rd, field_vol_rd) + if cast_ptr_rd is not None: + rn_val_rd: mlirlite.Value* = mlirlite.build_load(builder, decl_ty_rd, cast_ptr_rd, field_vol_rd) + # BigEndian REnum payload slot: un-swap the load, pairing with the write side. + return self.apply_bswap_if_big(pool, builder, rn_val_rd, field_info.ByteOrder) # an array-typed field cannot be loaded as an SSA value; return the field pointer for # later indexing: self.state[0] → GEP the state field → GEP the array element if self.is_array_type(field_ty) != 0: return field_ptr loaded_val: mlirlite.Value* = mlirlite.build_load(builder, field_ty, field_ptr, field_vol_rd) + # BigEndian field: the memory slot holds the byte-swapped pattern, so un-swap the + # loaded value; a no-op for non-16/32/64-bit ints (pointers/aggregates unaffected). + loaded_val = self.apply_bswap_if_big(pool, builder, loaded_val, field_info.ByteOrder) # a union-typed field (e.g. Token*) is compiled as i8*; when AnnotClassName points to a # concrete struct type, bitcast the loaded pointer (value→pointer bitcast would be invalid). fc_annot: str = self.Gen.StructGen.get_field_annot_class_name(field_info) @@ -934,6 +948,16 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase): fe: GenTypes.FieldEntry* = self._resolve_attr_field_entry(pool, trans, attr_node) return self.Gen.StructGen.field_is_const(fe) + # attr_field_byte_order - the field's byte order (0=native / 1=BigEndian / 2=LittleEndian) for the + # write path, where get_attribute_ptr resolves the pointer but not the FieldEntry. Mirrors + # attr_field_is_volatile/attr_field_is_const (same _resolve_attr_field_entry resolution: only a + # Name base resolves; a nested/global receiver yields 0 -> no swap, never a wrong swap). + def attr_field_byte_order(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*) -> int: + """Return the obj.field field's byte order (0 = native/unknown).""" + fe: GenTypes.FieldEntry* = self._resolve_attr_field_entry(pool, trans, attr_node) + if fe is None: return 0 + return fe.ByteOrder + def attr_field_decl_src(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*) -> str: """Return the field annotation source name for const diagnostics, or None.""" fe: GenTypes.FieldEntry* = self._resolve_attr_field_entry(pool, trans, attr_node) diff --git a/App/lib/core/Gen/Expr/ExprGenBase.vp b/App/lib/core/Gen/Expr/ExprGenBase.vp index b61db12..f45a2c4 100644 --- a/App/lib/core/Gen/Expr/ExprGenBase.vp +++ b/App/lib/core/Gen/Expr/ExprGenBase.vp @@ -149,6 +149,27 @@ novirtual class ExprGenBase: string.memset(ty, 0, mlirlite.LLVMType.__sizeof__()) *ty = *mlirlite.LLVMType.Int(bits) return ty + # apply_bswap_if_big - byte-order bswap primitive (TPC parity: BaseGen._apply_bswap_if_big). + # Only BigEndian (byte_order==1) on a 16/32/64-bit integer emits `llvm.bswap`; every other case + # returns val unchanged. byte_order==0 (native) must be zero-cost: no IR is emitted at all, and + # 2 (LittleEndian) also returns unchanged because the host is little-endian (same as TPC, which + # only swaps for 'big'), so the in-memory slot stays little-endian and no swap is needed. + # Call name: bare "llvm.bswap" (no width suffix). build_call emits `call i32 @llvm.bswap(i32 %x)`; + # __function.vp:2607 detects a bare `@llvm.*` (non-`llvm.mlir.`) callee and renders it as + # `llvm.call_intrinsic "llvm.bswap"(...)`, and mlir-translate mangles the name from the operand + # type to `llvm.bswap.i32` (README.md:367-378, the same path used by CallGenVa.vp:123 `llvm.va_start` + # -> `llvm.va_start.p0`). Passing `llvm.bswap.i32` explicitly is unnecessary and would break the + # uniform mangle path, so the un-suffixed name is used. + def apply_bswap_if_big(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, val: mlirlite.Value*, byte_order: int) -> mlirlite.Value*: + """Return llvm.bswap(val) when byte_order==1 and val is a 16/32/64-bit int; else val unchanged.""" + if byte_order != 1: return val + if val is None or val.Ty is None: return val + bits: int = self.get_llvm_type_bits(val.Ty) + if bits != 16 and bits != 32 and bits != 64: return val + # Same-type in-and-out (iN -> iN); ret_ty=val.Ty keeps the call well-typed before mangling. + swapped: mlirlite.Value* = mlirlite.build_call(builder, "llvm.bswap", val, 1, val.Ty, 0) + if swapped is None: return val + return swapped # _is_zero_int_literal - is the AST node the integer literal 0? # Used for ptr != 0 / ptr == 0, where 0 means NULL and is allowed. def _is_zero_int_literal(self, node: ast.AST*) -> int: diff --git a/App/lib/core/Gen/Expr/ExprGenName.vp b/App/lib/core/Gen/Expr/ExprGenName.vp index bb21e41..c880b5e 100644 --- a/App/lib/core/Gen/Expr/ExprGenName.vp +++ b/App/lib/core/Gen/Expr/ExprGenName.vp @@ -173,10 +173,14 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase): return alloca # t.CVolatile local: load carries volatile vol_l: int = 0 + bo_l: int = 0 if trans is not None: ve_l: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_id) vol_l = self.Gen.VarGen.var_is_volatile(ve_l) - return mlirlite.build_load(builder, load_ty, alloca, vol_l) + bo_l = self.Gen.VarGen.var_byte_order(ve_l) + local_val_l: mlirlite.Value* = mlirlite.build_load(builder, load_ty, alloca, vol_l) + # BigEndian local: the slot is byte-swapped, so un-swap the load (pairs the store in AssignGenMixin). + return self.apply_bswap_if_big(pool, builder, local_val_l, bo_l) # REPL session variables: a name missing from the local table is read back from its ctx slot, # checked after locals and before module aliases; non-REPL behavior is unchanged. @@ -561,7 +565,13 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase): case mlirlite.LLVMType.Array(elem_ty, count): # Array traversal: getelementptr [N x elem_ty], ... , i32 0, i32 %idx elem_ptr: mlirlite.Value* = mlirlite.build_gep_array(builder, pointee, elem_ty, alloca, idx_val) - if elem_ptr is not None: return mlirlite.build_load(builder, elem_ty, elem_ptr) + if elem_ptr is not None: + arr_val_rd: mlirlite.Value* = mlirlite.build_load(builder, elem_ty, elem_ptr) + # BigEndian array variable: the element slot is byte-swapped; un-swap + # on load (pairs the element store in AssignGenMixin). Non-Name bases are + # not covered (matches TPC, which also keys off the base Name). + bo_arr_rd: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) + return self.apply_bswap_if_big(pool, builder, arr_val_rd, bo_arr_rd) return None case mlirlite.LLVMType.Struct(_fs_v, _fc_v, lsn_v): # list[T] value variable (an unannotated assignment rhs returns a list diff --git a/App/lib/core/Gen/GenTypes.vp b/App/lib/core/Gen/GenTypes.vp index 4d2935a..ca6b23f 100644 --- a/App/lib/core/Gen/GenTypes.vp +++ b/App/lib/core/Gen/GenTypes.vp @@ -79,6 +79,10 @@ class VarEntry: # 生命期标签(LifeTag,见文件头 define LIFE_*):该变量**当前值**的生命期。 # 追加在末尾以保持既有字段偏移不变。默认 LIFE_UNKNOWN(未登记 = 保守放行)。 Life: int + # Byte order of this variable, from the `T | t.BigEndian` / `T | t.LittleEndian` 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 # 注:P2 的"已失效"状态**不放在这里** —— 给这个热结构加字段会改变布局、牵连整条变量注册 # 路径(现场:任何工程编译时都在"函数形参注册刚结束"处 access-violation)。改由 # VarGenMixin 的独立失效槽表 _g_freed_slots 保存(键 = 变量槽 alloca,槽本身唯一)。 diff --git a/App/lib/core/Gen/VarGenMixin.vp b/App/lib/core/Gen/VarGenMixin.vp index 7858291..867c224 100644 --- a/App/lib/core/Gen/VarGenMixin.vp +++ b/App/lib/core/Gen/VarGenMixin.vp @@ -216,6 +216,23 @@ novirtual class VarGenMixin: if entry is None: return 0 return entry.IsConst + # set_var_byte_order - register a local variable's byte order (VarEntry.ByteOrder) from the + # `T | t.BigEndian` / `T | t.LittleEndian` annotation union: 1=BigEndian / 2=LittleEndian. + # The value is only 1/2 when a marker is present, so the local load/store paths bswap only then. + def set_var_byte_order(self, symtab: GenTypes.SymbolTable*, name: str, byte_order: int) -> int: + """Set a local variable's byte order (VarEntry.ByteOrder), return 0=success / 1=failure""" + if symtab is None or name is None: return 1 + entry: GenTypes.VarEntry* = self.lookup_var_entry(symtab, name) + if entry is None: return 1 + entry.ByteOrder = byte_order + return 0 + + # var_byte_order - whether a variable is big/little-endian (unified read entry for load/store). + def var_byte_order(self, entry: GenTypes.VarEntry*) -> int: + """The variable entry's byte order (0=native / 1=BigEndian / 2=LittleEndian; 0 when None)""" + if entry is None: return 0 + return entry.ByteOrder + # ============================================================ # 生命期标签读写(LifeTag,见 GenTypes define LIFE_*;架构见 ownership-and-new.md §11) # ============================================================ @@ -320,6 +337,8 @@ novirtual class VarGenMixin: entry.IsConst = 0 # 生命期标签显式初始化:未登记 = LIFE_UNKNOWN(保守放行)。 entry.Life = GenTypes.LIFE_UNKNOWN + # 字节序显式初始化:未标注 = 0 (native),只有 set_var_byte_order 才置 1/2(避免槽复用残留)。 + entry.ByteOrder = 0 # [S3 dual write] Value-type Spec (alloca.Ty is Ptr(value type), so take its pointee) entry.Spec = self._spec_of_var_alloca(alloca) return 0