- 浮点字面量在 f32 目标(float(1.5) / x: float = 1.5 / f32 形参)下直接物化 f32 常量, 负号折叠(-c)同口径;去掉旧的「f64 常量 + fptrunc」 - CDefine 浮点条目记录宽度种类(1=f64 / 2=f32):define PI: float = ... 的使用处 直接出 f32 常量,与 f32 字面量混算不再被提升成 double(现场 33 处 cvtss2sd) - 本模块注册 / pyi·vpi 跨模块重解析 / xmod 缓存三处都按注解裁决宽度,保持跨模块一致
1150 lines
86 KiB
Plaintext
1150 lines
86 KiB
Plaintext
import lib.core.Gen.GenTypes as GenTypes
|
||
import t, c
|
||
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import string
|
||
import stdio
|
||
import stdlib
|
||
import viperlib
|
||
import lib.core.Gen.Translator as HT
|
||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
|
||
import lib.core.VLogger as VLogger
|
||
|
||
|
||
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
|
||
import lib.core.Gen.GenApi as GenApi
|
||
|
||
# ============================================================
|
||
# ExprGenAttr - Attribute access / global references
|
||
novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||
"""Attribute access and global references."""
|
||
# find_global_in_module - find a global variable by name in the module's global list:
|
||
# first an exact match, then a suffix match ".name" (e.g. "_mbuddy" → @"sha1._mbuddy").
|
||
def find_global_in_module(self, mod: mlirlite.LLVMModule*, name: str) -> mlirlite.GlobalVariable*:
|
||
"""Find a global variable by name in the module's global list."""
|
||
if mod is None or name is None: return None
|
||
name_len: sizet = string.strlen(name)
|
||
cur: mlirlite.GlobalVariable* = mod.GlobalHead
|
||
# first pass: exact match
|
||
while cur is not None:
|
||
if cur.Name is not None:
|
||
if string.strcmp(cur.Name, name) == 0: return cur
|
||
cur = cur.Next
|
||
# second pass: suffix match (.name)
|
||
cur = mod.GlobalHead
|
||
while cur is not None:
|
||
if cur.Name is not None:
|
||
cur_len: sizet = string.strlen(cur.Name)
|
||
if cur_len > name_len + 1:
|
||
suffix_start: sizet = cur_len - name_len
|
||
if cur.Name[suffix_start - 1] == '.':
|
||
Matched: int = 1
|
||
for i in range(int(name_len)):
|
||
if cur.Name[suffix_start + i] != name[i]:
|
||
Matched = 0
|
||
break
|
||
if Matched == 1: return cur
|
||
cur = cur.Next
|
||
return None
|
||
# make_global_ref - create a global variable reference (@name, type ty*); used for
|
||
# cross-module global access when the stub is not injected (linked via external global).
|
||
def make_global_ref(self, pool: memhub.MemBuddy*, name: str, ty: mlirlite.LLVMType*) -> mlirlite.Value*:
|
||
"""Create a global variable reference named @name with type ty*."""
|
||
if name is None or ty is None: return None
|
||
ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, ty)
|
||
ref_name: str = pool.alloc(96)
|
||
if ref_name is None: return None
|
||
# LLVM unquoted identifiers cannot start with a digit: a SHA1-prefixed global name
|
||
# ("sha1.name") must be quoted @"sha1.name", else llc: "expected instruction opcode".
|
||
if self.Gen.ClassGenVTable._vtable_name_needs_quote(name) != 0: viperlib.snprintf(ref_name, 96, "@\"%s\"", name)
|
||
else: viperlib.snprintf(ref_name, 96, "@%s", name)
|
||
return mlirlite.SSAValue(pool, ptr_ty, ref_name)
|
||
# _resolve_module_attribute_global - resolve a module-attribute access to a global variable
|
||
# reference: look up the current module first, else create a forward reference @attr_name.
|
||
def _is_t_type_object_name(self, name: str) -> int:
|
||
"""Check whether name is a t.py type-object name (CPtr/CInt/CDouble, ...)."""
|
||
if name is None: return 0
|
||
# type object names in t.py (PascalCase, initial uppercase)
|
||
if string.strcmp(name, "CPtr") == 0: return 1
|
||
if string.strcmp(name, "CInt") == 0: return 1
|
||
if string.strcmp(name, "CChar") == 0: return 1
|
||
if string.strcmp(name, "CShort") == 0: return 1
|
||
if string.strcmp(name, "CLong") == 0: return 1
|
||
if string.strcmp(name, "CLongLong") == 0: return 1
|
||
if string.strcmp(name, "CDouble") == 0: return 1
|
||
if string.strcmp(name, "CFloat") == 0: return 1
|
||
if string.strcmp(name, "CBool") == 0: return 1
|
||
if string.strcmp(name, "CVoid") == 0: return 1
|
||
if string.strcmp(name, "CInt8T") == 0: return 1
|
||
if string.strcmp(name, "CInt16T") == 0: return 1
|
||
if string.strcmp(name, "CInt32T") == 0: return 1
|
||
if string.strcmp(name, "CInt64T") == 0: return 1
|
||
if string.strcmp(name, "CUInt8T") == 0: return 1
|
||
if string.strcmp(name, "CUInt16T") == 0: return 1
|
||
if string.strcmp(name, "CUInt32T") == 0: return 1
|
||
if string.strcmp(name, "CUInt64T") == 0: return 1
|
||
if string.strcmp(name, "CUnsigned") == 0: return 1
|
||
if string.strcmp(name, "CUnsignedInt") == 0: return 1
|
||
if string.strcmp(name, "CUnsignedChar") == 0: return 1
|
||
if string.strcmp(name, "CUnsignedShort") == 0: return 1
|
||
if string.strcmp(name, "CUnsignedLong") == 0: return 1
|
||
if string.strcmp(name, "CUnsignedLongLong") == 0: return 1
|
||
if string.strcmp(name, "CSizeT") == 0: return 1
|
||
if string.strcmp(name, "CIntPtrT") == 0: return 1
|
||
if string.strcmp(name, "CUIntPtrT") == 0: return 1
|
||
if string.strcmp(name, "CPtrDiffT") == 0: return 1
|
||
if string.strcmp(name, "CType") == 0: return 1
|
||
if string.strcmp(name, "CDefine") == 0: return 1
|
||
if string.strcmp(name, "CStatic") == 0: return 1
|
||
if string.strcmp(name, "CExport") == 0: return 1
|
||
if string.strcmp(name, "CArray") == 0: return 1
|
||
if string.strcmp(name, "CTypedef") == 0: return 1
|
||
if string.strcmp(name, "CNoVTable") == 0: return 1
|
||
if string.strcmp(name, "CRef") == 0: return 1
|
||
if string.strcmp(name, "CTempRef") == 0: return 1
|
||
if string.strcmp(name, "CAutoPtr") == 0: return 1
|
||
# Byte-order markers (t.BigEndian / t.LittleEndian): decorator markers used as
|
||
# `X | t.BigEndian`; recognized here so they are not taken for an unregistered class.
|
||
if string.strcmp(name, "BigEndian") == 0: return 1
|
||
if string.strcmp(name, "LittleEndian") == 0: return 1
|
||
return 0
|
||
def _attr_module_path_str(self, pool: memhub.MemBuddy*, node: ast.AST*) -> str:
|
||
"""Convert a module-reference AST into a dotted module path; None for non-module forms."""
|
||
if node is None: return None
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Name:
|
||
nm_m: ast.Name* = ast.Name*(node)
|
||
if nm_m.id is not None: return nm_m.id
|
||
return None
|
||
if k == ast.ASTKind.Attribute:
|
||
at_m: ast.Attribute* = ast.Attribute*(node)
|
||
if at_m.attr is None: return None
|
||
base_m: str = self._attr_module_path_str(pool, at_m.value)
|
||
if base_m is None: return None
|
||
blen: sizet = string.strlen(base_m)
|
||
alen: sizet = string.strlen(at_m.attr)
|
||
buf_m: str = pool.alloc(blen + alen + 2)
|
||
if buf_m is None: return None
|
||
viperlib.snprintf(buf_m, blen + alen + 2, "%s.%s", base_m, at_m.attr)
|
||
return buf_m
|
||
return None
|
||
# _CheckStaticCrossModule - reject cross-module `mod.name` access to static symbols
|
||
# (static = internal linkage); kept separate from translate_attribute to protect its inference.
|
||
def _CheckStaticCrossModule(self, pool: memhub.MemBuddy*, trans: HT.Translator*, at: ast.Attribute*) -> int:
|
||
if at is None or at.value is None or trans is None: return 0
|
||
if trans.ModuleSha1 is None or trans._imported_modules is None: return 0
|
||
# fast shortcut: zero cost when the project has no static symbols (no sha1/table scan)
|
||
if self.Gen.CallGenBase.StaticCount <= 0: return 0
|
||
cs_path: str = self._attr_module_path_str(pool, at.value)
|
||
if cs_path is None: return 0
|
||
# resolve import alias (import X as Y → look up the original module name, else sha1 misses)
|
||
if trans._from_imports is not None:
|
||
cs_orig: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, cs_path, 0)
|
||
if cs_orig is not None:
|
||
cs_len: sizet = 0
|
||
while cs_orig[cs_len] != ' ' and cs_orig[cs_len] != '\0' and cs_orig[cs_len] != ':': cs_len += 1
|
||
if cs_len > 0:
|
||
cs_buf: str = pool.alloc(cs_len + 1)
|
||
if cs_buf is not None:
|
||
string.strncpy(cs_buf, cs_orig, cs_len)
|
||
cs_buf[cs_len] = '\0'
|
||
cs_path = cs_buf
|
||
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, cs_path) == 0: return 0
|
||
cs_sha1: str = self._lookup_module_sha1(cs_path)
|
||
if cs_sha1 is None: return 0
|
||
if string.strncmp(cs_sha1, trans.ModuleSha1, 16) == 0: return 0
|
||
if self.Gen.CallGenBase.IsStaticSymbol(cs_sha1, at.attr) == 0: return 0
|
||
self.Gen.TypeGenErr.fatal_error(at, f"'{cs_path}.{at.attr}' is a static symbol, cross-module use is forbidden (static is only visible inside its defining module)", "NameError")
|
||
return 0
|
||
def _resolve_module_attribute_global(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, at: ast.Attribute*, ty_hint: mlirlite.LLVMType*) -> mlirlite.Value*:
|
||
"""Resolve a module attribute access to a global variable reference for cross-module use."""
|
||
if at is None or at.value is None or at.attr is None: return None
|
||
if trans is None or trans._imported_modules is None: return None
|
||
# extract the module path: Name → "sys"; nested Attribute → "os._win32" (the latter
|
||
# matters for writes like `os._win32._mbuddy = v`, not just Name modules).
|
||
mod_path_w: str = self._attr_module_path_str(pool, at.value)
|
||
if mod_path_w is None: return None
|
||
# resolve aliases (import lib.core.Gen.Generator as GenGenerator) via from_imports, else
|
||
# sha1 lookup fails and the global gets an undefined SHA1-prefixed symbol at link time.
|
||
if trans._from_imports is not None:
|
||
orig_mw: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, mod_path_w, 0)
|
||
if orig_mw is not None:
|
||
oml_a: sizet = 0
|
||
while orig_mw[oml_a] != ' ' and orig_mw[oml_a] != '\0' and orig_mw[oml_a] != ':': oml_a += 1
|
||
if oml_a > 0:
|
||
om_buf_a: str = pool.alloc(oml_a + 1)
|
||
if om_buf_a is not None:
|
||
string.strncpy(om_buf_a, orig_mw, oml_a)
|
||
om_buf_a[oml_a] = '\0'
|
||
mod_path_w = om_buf_a
|
||
# check whether it is an imported module
|
||
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, mod_path_w) == 0: return None
|
||
# note: static cross-module interception is done in translate_attribute's _CheckStaticCrossModule.
|
||
# Look up the global preferring an exact "target-module SHA1 prefix + attr" match, else the bare name.
|
||
gv_q: str = self.Gen.FuncGenBase._mangle_name_with_sha1(self._lookup_module_sha1(mod_path_w), at.attr)
|
||
gv: mlirlite.GlobalVariable* = self.find_global_in_module(mod, gv_q)
|
||
# CExport globals (defined with a bare name like @_sched_ptr): when the mangled name is not
|
||
# found, fall back to the bare exact name, else an undefined SHA1-prefixed symbol results.
|
||
if gv is None: gv = self.find_global_in_module(mod, at.attr)
|
||
if gv is not None and gv.Ty is not None:
|
||
# build the reference from gv.Name (full name, matching the definition when SHA1-prefixed),
|
||
# avoiding an undefined symbol for a bare @_mbuddy under per-module prefixed definitions
|
||
return self.make_global_ref(pool, gv.Name, gv.Ty)
|
||
# A module function used as a value (`return serial.puts`, `cb = serial.puts`): the symbol
|
||
# is already declared in this module as `llvm.func @<sha1>.puts`, so manufacturing an
|
||
# external global under the same name makes mlir-opt reject the module with "redefinition
|
||
# of symbol named '<sha1>.puts'". Hand back the function symbol itself instead: a Value
|
||
# whose text is the reference `@<mangled>` typed Ptr(Func), the same shape the same-module
|
||
# `&f` branch (ExprGenOps.translate_unaryop) produces -- the caller must not load it.
|
||
if gv_q is not None:
|
||
fn_ma: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, at.attr)
|
||
if fn_ma is not None:
|
||
fname_ma: str = mlirlite.function_get_name(fn_ma)
|
||
# exact match only: a bare suffix hit could be another module's same-name function
|
||
if fname_ma is not None and string.strcmp(fname_ma, gv_q) == 0:
|
||
fn_ty_ma: mlirlite.LLVMType* = self.Gen.CallGenBase._FuncTyFromFunction(pool, fn_ma)
|
||
if fn_ty_ma is not None:
|
||
fnlen_ma: sizet = string.strlen(fname_ma)
|
||
fbuf_ma: str = pool.alloc(fnlen_ma + 4)
|
||
if fbuf_ma is not None:
|
||
if self.Gen.ClassGenVTable._vtable_name_needs_quote(fname_ma) != 0: viperlib.snprintf(fbuf_ma, fnlen_ma + 4, "@\"%s\"", fname_ma)
|
||
else: viperlib.snprintf(fbuf_ma, fnlen_ma + 4, "@%s", fname_ma)
|
||
return mlirlite.SSAValue(pool, mlirlite.Ptr(pool, fn_ty_ma), fbuf_ma)
|
||
# not found: create an external global declaration and add it to the module; otherwise only
|
||
# an @_argc reference is emitted with no declaration (LLVM: "use of undefined value").
|
||
use_ty: mlirlite.LLVMType* = ty_hint
|
||
if use_ty is None: use_ty = mlirlite.Int8(pool)
|
||
# ensure cross-module struct types in use_ty have an opaque declaration
|
||
mlirlite.module_ensure_opaque_for_type(mod, pool, use_ty)
|
||
# special case: type objects in t.py (CPtr/CInt/CDouble etc.). t.py is not compiled but
|
||
# these objects are referenced at runtime, so emit a linkonce_odr definition with null init.
|
||
is_t_type_obj: int = 0
|
||
if string.strcmp(mod_path_w, "t") == 0: is_t_type_obj = self._is_t_type_object_name(at.attr)
|
||
# a class name is not a module-level global: registered classes/structs/REnums are always
|
||
# handled by the class/REnum paths, never manufactured into an external global here.
|
||
if is_t_type_obj == 0 and self.Gen.StructGen.find_struct(at.attr) is not None: return None
|
||
# fallback declaration name: t type objects (t.py uncompiled, no sha1) keep the bare
|
||
# linkonce_odr name; other module attributes carry the target module's SHA1 prefix.
|
||
gv_name_ba: str = at.attr
|
||
if is_t_type_obj == 0: gv_name_ba = self.Gen.FuncGenBase._mangle_name_with_sha1(self._lookup_module_sha1(mod_path_w), at.attr)
|
||
ext_gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, gv_name_ba, use_ty)
|
||
if ext_gv is not None:
|
||
if is_t_type_obj != 0:
|
||
# t.CPtr etc.: create a definition with a null initializer (linkonce_odr)
|
||
ext_gv.Linkage = "linkonce_odr"
|
||
# initializer text for i8* null
|
||
null_init: bytes = pool.alloc(8)
|
||
if null_init is not None:
|
||
null_init[0] = 'n'
|
||
null_init[1] = 'u'
|
||
null_init[2] = 'l'
|
||
null_init[3] = 'l'
|
||
null_init[4] = '\0'
|
||
ext_gv.Initializer = null_init
|
||
else: ext_gv.Initializer = None
|
||
else:
|
||
# regular module attribute: external declaration (definition provided elsewhere)
|
||
ext_gv.Linkage = "external"
|
||
ext_gv.Initializer = None
|
||
mlirlite.module_add_global(mod, ext_gv)
|
||
return self.make_global_ref(pool, gv_name_ba, use_ty)
|
||
def _lookup_module_sha1(self, mod_name: str) -> str:
|
||
"""Look up a module name's SHA1 via StubMergerStore, returning None when not found."""
|
||
return StubMergerStore.LookupSha1ByModName(mod_name)
|
||
# translate_attribute - translate Attribute(value, attr, ctx) i.e. obj.field by priority: enum member,
|
||
# module attribute (sys._mbuddy → global), then struct field (GEP + load). Name bases skip the struct load.
|
||
|
||
|
||
# _get_cderef_attr_base - base pointer of a c.Deref(ptr).attr access (like *p.a → p->a): return the ptr
|
||
# itself, not the loaded struct value; None for non-c.Deref forms (_deref_if_ptr_ptr resolves the level).
|
||
def _get_cderef_attr_base(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, at_value: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Return the ptr itself for a dereferenced attribute base (`c.Deref(ptr).attr` / `(*ptr).attr`), else None (no load)."""
|
||
if at_value is None: return None
|
||
# New syntax form: `(*ptr).attr` is AST UnaryOp(OpKind.Deref) — identical semantics to the legacy
|
||
# `c.Deref(ptr).attr`. Without this branch the attribute write path sees a *loaded struct value*
|
||
# as the base and fails with "base=非指针类型,无法做属性写入"; the read path silently does an
|
||
# extra struct load. Both must GEP the pointer itself (R4 migration of `c.Deref` -> `*`).
|
||
if at_value.kind() == ast.ASTKind.UnaryOp:
|
||
uo_cd: ast.UnaryOp* = ast.UnaryOp*(at_value)
|
||
if uo_cd.op != ast.OpKind.Deref or uo_cd.operand is None: return None
|
||
inner_uo: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo_cd.operand, None, 0, trans)
|
||
if inner_uo is not None and self.is_ptr_type(inner_uo.Ty) != 0: return inner_uo
|
||
return None
|
||
if at_value.kind() != ast.ASTKind.Call: return None
|
||
cl_cd: ast.Call* = ast.Call*(at_value)
|
||
if cl_cd.func is None or cl_cd.func.kind() != ast.ASTKind.Attribute: return None
|
||
f_cd: ast.Attribute* = ast.Attribute*(cl_cd.func)
|
||
if f_cd.attr is None or string.strcmp(f_cd.attr, "Deref") != 0: return None
|
||
if f_cd.value is None or f_cd.value.kind() != ast.ASTKind.Name: return None
|
||
nm_cd: ast.Name* = ast.Name*(f_cd.value)
|
||
if nm_cd.id is None or string.strcmp(nm_cd.id, "c") != 0: return None
|
||
if cl_cd.args is None or len(cl_cd.args) < 1: return None
|
||
inner_cd: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cl_cd.args.get(0), None, 0, trans)
|
||
if inner_cd is not None and self.is_ptr_type(inner_cd.Ty) != 0: return inner_cd
|
||
return None
|
||
def translate_attribute(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Translate an attribute access and return the loaded field value."""
|
||
if node is None or builder is None: return None
|
||
at: ast.Attribute* = ast.Attribute*(node)
|
||
if at is None or at.value is None or at.attr is None:
|
||
# fast-fail: a missing AST field is a parser bug; report the specific missing field
|
||
diag_buf_an: bytes = stdlib.malloc(256)
|
||
if diag_buf_an is not None:
|
||
missing_an: str = "?"
|
||
if at is not None:
|
||
if at.value is None: missing_an = "value"
|
||
elif at.attr is None: missing_an = "attr"
|
||
viperlib.snprintf(diag_buf_an, 256, "attribute access failed: AST node field missing (%s sha1=%s:%d)", missing_an, trans.ModuleSha1 if trans is not None and trans.ModuleSha1 is not None else "?", at.lineno if at is not None and at.lineno is not None else 0)
|
||
VLogger.error(diag_buf_an, "ATTR")
|
||
return None
|
||
|
||
# static symbol (cross-module use forbidden): `mod.name` hitting another module's static
|
||
# symbol is fail-fast; the logic stays in _CheckStaticCrossModule, never inlined here.
|
||
self._CheckStaticCrossModule(pool, trans, at)
|
||
|
||
# magic attribute __doc__: consult the docstring registry; a hit inlines a string constant, a miss
|
||
# returns null. Supports local and cross-module symbols (func / Class / Class.method / mod.func...).
|
||
if string.strcmp(at.attr, "__doc__") == 0:
|
||
doc_mod_buf: bytes = pool.alloc(64)
|
||
doc_sym_buf: bytes = pool.alloc(256)
|
||
if doc_mod_buf is None or doc_sym_buf is None: return None
|
||
doc_mod_buf[0] = '\0'
|
||
doc_sym_buf[0] = '\0'
|
||
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
|
||
nm_doc: ast.Name* = ast.Name*(at.value)
|
||
if nm_doc.id is not None: self.Gen.ExprGenCls._resolve_doc_symbol_name(trans, nm_doc.id, doc_mod_buf, doc_sym_buf)
|
||
elif at.value is not None and at.value.kind() == ast.ASTKind.Attribute: self.Gen.ExprGenCls._resolve_doc_symbol_attr(trans, ast.Attribute*(at.value), doc_mod_buf, doc_sym_buf)
|
||
doc_val: str = None
|
||
if doc_mod_buf[0] != '\0' and doc_sym_buf[0] != '\0': doc_val = self.Gen.ExprGenCls.lookup_docstring(doc_mod_buf, doc_sym_buf)
|
||
if doc_val is None:
|
||
i8_ty_doc: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
if i8_ty_doc is None: return None
|
||
i8_ptr_ty_doc: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_doc)
|
||
if i8_ptr_ty_doc is None: return None
|
||
return mlirlite.ConstNull(pool, i8_ptr_ty_doc, "null")
|
||
return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, doc_val, trans)
|
||
|
||
# enum member access: EnumName.MemberName → constant value, when value is a Name whose id
|
||
# is a registered enum, look up the member and return its constant.
|
||
if at.value.kind() == ast.ASTKind.Name:
|
||
enum_nm: ast.Name* = ast.Name*(at.value)
|
||
if enum_nm.id is not None:
|
||
if self.Gen.StmtGenDecl.is_enum_class(enum_nm.id) == 1:
|
||
member: GenTypes.EnumMember* = self.Gen.StmtGenDecl.lookup_enum_member(enum_nm.id, at.attr)
|
||
if member is not None:
|
||
base_ty: mlirlite.LLVMType* = self.Gen.StmtGenDecl.get_enum_base_type(enum_nm.id)
|
||
if base_ty is not None:
|
||
# emit a numeric string as the constant name (LLVM IR prints numeric values)
|
||
name_buf: str = pool.alloc(32)
|
||
if name_buf is not None: viperlib.snprintf(name_buf, 32, "%lld", member.Value)
|
||
return mlirlite.ConstInt(pool, base_ty, member.Value, name_buf)
|
||
return mlirlite.const_int32(pool, member.Value)
|
||
# fast-fail: enum member not found
|
||
VLogger.error(f"enum member not found: {enum_nm.id}.{at.attr}", "ATTR")
|
||
return None
|
||
|
||
# nested enum member access: Module.EnumName.MemberName → constant, when value is an Attribute at any
|
||
# module depth whose attr is a registered enum. Only the innermost attr is the enum class name.
|
||
if at.value.kind() == ast.ASTKind.Attribute:
|
||
inner_at_e: ast.Attribute* = ast.Attribute*(at.value)
|
||
if inner_at_e.attr is not None:
|
||
if self.Gen.StmtGenDecl.is_enum_class(inner_at_e.attr) == 1:
|
||
member_e: GenTypes.EnumMember* = self.Gen.StmtGenDecl.lookup_enum_member( inner_at_e.attr, at.attr)
|
||
if member_e is not None:
|
||
base_ty_e: mlirlite.LLVMType* = self.Gen.StmtGenDecl.get_enum_base_type(inner_at_e.attr)
|
||
if base_ty_e is not None:
|
||
name_buf_e: str = pool.alloc(32)
|
||
if name_buf_e is not None: viperlib.snprintf(name_buf_e, 32, "%lld", member_e.Value)
|
||
return mlirlite.ConstInt(pool, base_ty_e, member_e.Value, name_buf_e)
|
||
return mlirlite.const_int32(pool, member_e.Value)
|
||
# fast-fail: nested enum member not found
|
||
dbg_ee: GenTypes.EnumEntry* = self.Gen.StmtGenDecl.find_enum(inner_at_e.attr)
|
||
dbg_mc: int = -1
|
||
if dbg_ee is not None: dbg_mc = dbg_ee.MemberCount
|
||
VLogger.error(f"nested enum member not found: {inner_at_e.attr}.{at.attr} (enum_members={dbg_mc})", "ATTR")
|
||
return None
|
||
|
||
# REnum variant reference as a value: ClassName.Variant / Module.ClassName.Variant → the variant tag
|
||
# constant (matching _lookup_renum_variant_in_class); factory names return -1 and pass through.
|
||
renum_cls_rv: str = None
|
||
if at.value.kind() == ast.ASTKind.Name:
|
||
rn_nm_rv: ast.Name* = ast.Name*(at.value)
|
||
if rn_nm_rv.id is not None: renum_cls_rv = rn_nm_rv.id
|
||
elif at.value.kind() == ast.ASTKind.Attribute:
|
||
rn_at_rv: ast.Attribute* = ast.Attribute*(at.value)
|
||
if rn_at_rv.attr is not None: renum_cls_rv = rn_at_rv.attr
|
||
if renum_cls_rv is not None and self.Gen.ClassGenEnum._is_renum_class_name(renum_cls_rv) != 0:
|
||
rv_tag: int = self.Gen.ClassGenEnum._lookup_renum_variant_in_class(renum_cls_rv, at.attr)
|
||
if rv_tag >= 0:
|
||
name_buf_rv: str = pool.alloc(32)
|
||
if name_buf_rv is not None:
|
||
viperlib.snprintf(name_buf_rv, 32, "%d", rv_tag)
|
||
return mlirlite.ConstInt(pool, mlirlite.Int32(pool), rv_tag, name_buf_rv)
|
||
return mlirlite.ConstInt(pool, mlirlite.Int32(pool), rv_tag, "0")
|
||
|
||
# FakeDuck: shadow-struct attribute read (per-instance fields of a str variable): for a
|
||
# Name variable with a {var}__meta__ shadow (created by fd2), GEP the field and load it.
|
||
if at.value.kind() == ast.ASTKind.Name and trans is not None:
|
||
fd_nm: ast.Name* = ast.Name*(at.value)
|
||
if fd_nm.id is not None:
|
||
# pooled persistent buffer (an f-string stack buffer dies on return)
|
||
fd_meta_buf: str = pool.alloc(256)
|
||
if fd_meta_buf is not None: viperlib.snprintf(fd_meta_buf, 256, "%s__meta__", fd_nm.id)
|
||
fd_meta_v: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, fd_meta_buf)
|
||
if fd_meta_v is not None:
|
||
fd_sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type("_str")
|
||
if fd_sty is not None:
|
||
fd_fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_sty, at.attr)
|
||
if fd_fe is not None and fd_fe.Ty is not None:
|
||
fd_fp: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_sty, fd_fe.Ty, fd_meta_v, fd_fe.Index)
|
||
if fd_fp is not None: return mlirlite.build_load(builder, fd_fe.Ty, fd_fp)
|
||
|
||
# module attribute access (read path): sys._mbuddy → load global @_mbuddy; when obj is an
|
||
# imported module name (and not an ordinary variable), find/create the global and load it.
|
||
if at.value.kind() == ast.ASTKind.Name and trans is not None:
|
||
nm_ma: ast.Name* = ast.Name*(at.value)
|
||
if nm_ma.id is not None:
|
||
# first check whether it is an ordinary variable (higher priority than module attr)
|
||
ma_is_var: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_ma.id)
|
||
if ma_is_var is None:
|
||
# first check whether it is a CDefine compile-time constant (e.g. ast.CONST_INT /
|
||
# win32base.INVALID_HANDLE_VALUE); such constants require a cross-module .vp/.vpi search
|
||
cdef_ma_val: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(at.attr)
|
||
cd_local: int = self.Gen.TypeGenCDef.is_cdefine_found()
|
||
if cd_local == 0:
|
||
# determine the real module name: prefer resolving the alias from _from_imports,
|
||
# whose entries look like "local_name:real_module" (e.g. win32base:win32.win32base)
|
||
real_mod_name: str = nm_ma.id
|
||
if trans._from_imports is not None:
|
||
resolved_mod: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, nm_ma.id, 0)
|
||
if resolved_mod is not None:
|
||
# copy the module name into a new buffer (truncate at space, null, or ':')
|
||
rm_len: sizet = 0
|
||
while resolved_mod[rm_len] != ' ' and resolved_mod[rm_len] != '\0' and resolved_mod[rm_len] != ':': rm_len += 1
|
||
rm_buf: str = pool.alloc(rm_len + 1)
|
||
if rm_buf is not None:
|
||
for rm_i in range(int(rm_len)): rm_buf[rm_i] = resolved_mod[rm_i]
|
||
rm_buf[rm_len] = '\0'
|
||
real_mod_name = rm_buf
|
||
# even when _from_imports is None (e.g. a plain `import ast`), still attempt the
|
||
# cross-module CDefine lookup, else ast.CONST_INT is misread as @CONST_INT (undefined).
|
||
if real_mod_name is not None:
|
||
ma_fi_len: sizet = string.strlen(at.attr) + string.strlen(real_mod_name) + 2
|
||
ma_fi: str = pool.alloc(ma_fi_len)
|
||
if ma_fi is not None:
|
||
viperlib.snprintf(ma_fi, ma_fi_len, "%s:%s", at.attr, real_mod_name)
|
||
cdef_ma_val = self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, at.attr, ma_fi)
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
# cross-module string CDefine (e.g. pyzlib.ZLIB_VERSION = "1.3.2")
|
||
# → string global constant (test_pyzlib.vp: ver = pyzlib.ZLIB_VERSION)
|
||
if self.Gen.TypeGenCDef.is_cdefine_str() != 0:
|
||
cdef_str_v: str = self.Gen.TypeGenCDef.lookup_cdefine_string(at.attr)
|
||
if cdef_str_v is not None: return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, cdef_str_v, trans)
|
||
# cross-module float CDefine (e.g. vipermath.U_M_PI) → float constant
|
||
dchk_ma: double = self.Gen.TypeGenCDef.lookup_cdefine_double(at.attr)
|
||
# f32 声明的常量直接出 f32 常量(否则 f32 字面量会被提升成 double)
|
||
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
|
||
if self.Gen.TypeGenCDef.is_cdefine_float32() != 0: return mlirlite.ConstFloat(pool, mlirlite.Float(pool), dchk_ma)
|
||
return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dchk_ma)
|
||
return self.Gen.ExprGenCdef._const_int_for_cdefine(pool, at.attr, cdef_ma_val)
|
||
# check whether nm_ma.id.at.attr is an imported submodule (e.g. win32.win32file);
|
||
# for a submodule do not create a global, let the nested attribute path handle it
|
||
is_submod: int = 0
|
||
if trans._imported_modules is not None:
|
||
submod_len: sizet = string.strlen(nm_ma.id) + string.strlen(at.attr) + 2
|
||
submod_buf: str = pool.alloc(submod_len)
|
||
if submod_buf is not None:
|
||
viperlib.snprintf(submod_buf, submod_len, "%s.%s", nm_ma.id, at.attr)
|
||
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, submod_buf) == 1: is_submod = 1
|
||
# also check from_imports: the at.attr alias maps to the real module win32.win32file
|
||
if is_submod == 0 and trans._from_imports is not None:
|
||
fi_mod_raw: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, at.attr, 0)
|
||
if fi_mod_raw is not None:
|
||
# copy the module name into a new buffer (truncate at space, null, or ':')
|
||
fm_len: sizet = 0
|
||
while fi_mod_raw[fm_len] != ' ' and fi_mod_raw[fm_len] != '\0' and fi_mod_raw[fm_len] != ':': fm_len += 1
|
||
fi_mod: str = pool.alloc(fm_len + 1)
|
||
if fi_mod is not None:
|
||
for fm_i in range(int(fm_len)): fi_mod[fm_i] = fi_mod_raw[fm_i]
|
||
fi_mod[fm_len] = '\0'
|
||
# check whether fi_mod starts with "nm_ma.id." (e.g. win32.win32file with win32.)
|
||
prefix_len: sizet = string.strlen(nm_ma.id)
|
||
fi_mod_len: sizet = string.strlen(fi_mod)
|
||
if fi_mod_len > prefix_len + 1:
|
||
match_prefix: int = 1
|
||
for pi in range(int(prefix_len)):
|
||
if fi_mod[pi] != nm_ma.id[pi]:
|
||
match_prefix = 0
|
||
break
|
||
if match_prefix == 1 and fi_mod[prefix_len] == '.': is_submod = 1
|
||
if is_submod != 0:
|
||
return None # it is a submodule, hand it to the nested attribute path
|
||
# special case: t.ASM_DESCR is the inline-asm constraint namespace, not a CDefine; its
|
||
# .XXX is handled by _asm_resolve_constraint, so return None for the nested path to handle.
|
||
if string.strcmp(nm_ma.id, "t") == 0 and string.strcmp(at.attr, "ASM_DESCR") == 0: return None
|
||
# ALL_CAPS module attribute that is not a CDefine: load it as a cross-module global when the
|
||
# module defines a same-named global (e.g. PA_DEPS_MAX_STR); else fail-fast (undefined value).
|
||
if self.Gen.ExprGenCdef._is_cdefine_name_pattern(at.attr) != 0:
|
||
if self.find_global_in_module(mod, at.attr) is None:
|
||
self.Gen.TypeGenErr.fatal_error(at, f"cross-module CDefine not found: '{nm_ma.id}.{at.attr}' (CDefine cross-module lookup failed)", "TypeError")
|
||
# hit a cross-module global → fall through to the cross-module global load path
|
||
# not a CDefine: use i8* as the type hint (module-level variables usually hold pointers)
|
||
i8_ptr_hint: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
mod_gv_ref: mlirlite.Value* = self._resolve_module_attribute_global(pool, mod, trans, at, i8_ptr_hint)
|
||
if mod_gv_ref is not None and mod_gv_ref.Ty is not None:
|
||
load_ty_mod: mlirlite.LLVMType* = mod_gv_ref.Ty.Pointee
|
||
if load_ty_mod is not None:
|
||
# A module function used as a value is already the address (Ptr(Func),
|
||
# the symbol reference itself): loading it would read the function body,
|
||
# so return the reference unchanged.
|
||
match load_ty_mod:
|
||
case mlirlite.LLVMType.Func(_, _, _):
|
||
return mod_gv_ref
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
# module-level global array (e.g. zdef.zdeflate_len_base: CArray[T, N]):
|
||
# decay to the base address per C semantics; loading [N x T] cannot be GEP base.
|
||
return mod_gv_ref
|
||
case _:
|
||
pass
|
||
return mlirlite.build_load(builder, load_ty_mod, mod_gv_ref)
|
||
|
||
# nested module attribute access (win32.win32file.GENERIC_READ → global @GENERIC_READ): when
|
||
# at.value is an Attribute resolving to an imported submodule, look up at.attr as a global constant
|
||
if at.value.kind() == ast.ASTKind.Attribute and trans is not None:
|
||
inner_at: ast.Attribute* = ast.Attribute*(at.value)
|
||
if inner_at.value is not None and inner_at.value.kind() == ast.ASTKind.Name:
|
||
inner_nm: ast.Name* = ast.Name*(inner_at.value)
|
||
if inner_nm.id is not None and inner_at.attr is not None:
|
||
if self.Gen.VarGen.lookup_var(trans.SymTab, inner_nm.id) is None:
|
||
# build the qualified module name: "win32.win32file"
|
||
qual_len: sizet = string.strlen(inner_nm.id) + string.strlen(inner_at.attr) + 2
|
||
qual_buf: str = pool.alloc(qual_len)
|
||
if qual_buf is not None:
|
||
viperlib.snprintf(qual_buf, qual_len, "%s.%s", inner_nm.id, inner_at.attr)
|
||
# module knownness: an explicit import (win32.win32file) or the global module
|
||
# registry (pkg.X registered via `from . import X` in __init__.vp, e.g. intr.idt)
|
||
mod_known_n: int = self.Gen.ImportsGen.is_module_imported(trans._imported_modules, qual_buf)
|
||
if mod_known_n == 0 and self._lookup_module_sha1(qual_buf) is not None:
|
||
# known submodule not explicitly imported: inject its .vpi declarations first, else
|
||
# find_global_in_module misses its CExport global (@idt) (main.vp intr.idt.idt[32]).
|
||
StubMergerBuild.LoadDepVpiIntoModule(pool, mod, qual_buf, trans._from_imports, self.Gen.TypeGenBase.get_temp_dir(), trans)
|
||
mod_known_n = 1
|
||
if mod_known_n != 0:
|
||
# look up the global at.attr with an exact qualified-module SHA1 prefix
|
||
# match (avoiding suffix collision); fall back to the bare name if sha1 unknown
|
||
gv_nested_q: str = self.Gen.FuncGenBase._mangle_name_with_sha1(self._lookup_module_sha1(qual_buf), at.attr)
|
||
gv_nested: mlirlite.GlobalVariable* = self.find_global_in_module(mod, gv_nested_q)
|
||
# CExport global (defined with a bare name like @idt): fall back to the
|
||
# bare exact lookup when the mangled name misses (matching the read path)
|
||
if gv_nested is None: gv_nested = self.find_global_in_module(mod, at.attr)
|
||
if gv_nested is not None and gv_nested.Ty is not None:
|
||
gv_ref_nested: mlirlite.Value* = self.make_global_ref(pool, gv_nested.Name, gv_nested.Ty)
|
||
if gv_ref_nested is not None and gv_ref_nested.Ty is not None:
|
||
load_ty_nested: mlirlite.LLVMType* = gv_ref_nested.Ty.Pointee
|
||
if load_ty_nested is not None:
|
||
# module-level global array (e.g. idt.idt: CArray[T, N]) returns
|
||
# the base-address pointer per C semantics; indexing/address keeps GEP
|
||
match load_ty_nested:
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
return gv_ref_nested
|
||
case _:
|
||
pass
|
||
return mlirlite.build_load(builder, load_ty_nested, gv_ref_nested)
|
||
# global not found: try a CDefine constant lookup; for nested module attributes
|
||
# like win32.win32base.INVALID_HANDLE_VALUE, resolve the CDefine from the .vpi
|
||
cdef_val_nested: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(at.attr)
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() == 0:
|
||
# build a temporary from_imports "attr:module" for the cross-module lookup
|
||
tmp_fi_len: sizet = string.strlen(at.attr) + string.strlen(qual_buf) + 2
|
||
tmp_fi: str = pool.alloc(tmp_fi_len)
|
||
if tmp_fi is not None:
|
||
viperlib.snprintf(tmp_fi, tmp_fi_len, "%s:%s", at.attr, qual_buf)
|
||
cdef_val_nested = self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, at.attr, tmp_fi)
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
# cross-module float CDefine → float constant (f32 声明直接出 f32)
|
||
dchk_nst: double = self.Gen.TypeGenCDef.lookup_cdefine_double(at.attr)
|
||
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
|
||
if self.Gen.TypeGenCDef.is_cdefine_float32() != 0: return mlirlite.ConstFloat(pool, mlirlite.Float(pool), dchk_nst)
|
||
return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dchk_nst)
|
||
return self.Gen.ExprGenCdef._const_int_for_cdefine(pool, at.attr, cdef_val_nested)
|
||
# CDefine not found: fail-fast and stop compiling (no more undefined global
|
||
# references, which would make llc report "use of undefined value").
|
||
if self.Gen.ExprGenCdef._is_cdefine_name_pattern(at.attr) != 0:
|
||
self.Gen.TypeGenErr.fatal_error(at, f"nested attribute cross-module CDefine not found: '{qual_buf}.{at.attr}' (CDefine cross-module lookup failed)", "TypeError")
|
||
return None
|
||
|
||
# for a Name obj, look up the alloca directly (no struct load); nonlocal variables (hoisted
|
||
# into the function) read their shared heap value from env, avoiding a cross-function alloca ref.
|
||
obj_ptr: mlirlite.Value* = None
|
||
if at.value.kind() == ast.ASTKind.Name and trans is not None:
|
||
nm: ast.Name* = ast.Name*(at.value)
|
||
if nm.id is not None:
|
||
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
|
||
raw_nl_a: mlirlite.Value* = self.Gen.StmtGenDecl._load_raw_var_ptr(trans, nm.id)
|
||
if raw_nl_a is not None:
|
||
nl_ty_a: mlirlite.LLVMType* = GenApi.get_nonlocal_type(trans, nm.id)
|
||
if nl_ty_a is not None:
|
||
# object is the pointer: Struct/Array use the heap address directly (bitcast,
|
||
# no load); pointer types (e.g. Ptr(Point)) load the object pointer from the heap
|
||
match nl_ty_a:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
obj_ptr = mlirlite.build_bitcast(builder, raw_nl_a, mlirlite.Ptr(pool, nl_ty_a))
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
obj_ptr = mlirlite.build_bitcast(builder, raw_nl_a, mlirlite.Ptr(pool, nl_ty_a))
|
||
case _:
|
||
nl_val_ptr_a: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl_a, mlirlite.Ptr(pool, nl_ty_a))
|
||
if nl_val_ptr_a is not None: obj_ptr = mlirlite.build_load(builder, nl_ty_a, nl_val_ptr_a)
|
||
else: obj_ptr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||
|
||
# non-Name path: translate the object value (which loads)
|
||
if obj_ptr is None:
|
||
# c.Deref(ptr).attr: the base is the pointer itself, not the loaded struct value
|
||
# (c.Deref(p).a means (*p).a, so GEP p directly)
|
||
cd_base_rd: mlirlite.Value* = self._get_cderef_attr_base(builder, pool, mod, at.value, trans)
|
||
if cd_base_rd is not None: obj_ptr = cd_base_rd
|
||
elif at.value.kind() == ast.ASTKind.Subscript:
|
||
# subscript-base attribute access (e.g. pts[0].x): list[T]/OOP __getitem__ classes use
|
||
# translate_value; plain struct-pointer arrays use get_subscript_ptr (GEP the element address).
|
||
if self._sub_base_uses_getitem(pool, mod, trans, at.value) != 0: obj_ptr = self.Gen.ExprGen.translate_value(builder, pool, mod, at.value, None, 0, trans)
|
||
else: obj_ptr = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, at.value, trans)
|
||
elif at.value.kind() == ast.ASTKind.Attribute:
|
||
# nested attribute base (e.g. r.origin.x): take the inner field address (recursive
|
||
# get_attribute_ptr, address semantics); pointer fields (X*) are loaded by _deref_if_ptr_ptr
|
||
obj_ptr = self.get_attribute_ptr(builder, pool, mod, at.value, trans)
|
||
if obj_ptr is None: obj_ptr = self.Gen.ExprGen.translate_value(builder, pool, mod, at.value, None, 0, trans)
|
||
else: obj_ptr = self.Gen.ExprGen.translate_value(builder, pool, mod, at.value, None, 0, trans)
|
||
|
||
if obj_ptr is None or obj_ptr.Ty is None: return None
|
||
|
||
# if obj_ptr is Ptr(Ptr(Struct)) (the alloca of an X* variable), load to deref and get Ptr(Struct)
|
||
obj_ptr = self._deref_if_ptr_ptr(builder, obj_ptr)
|
||
|
||
# rawptr[t.CVolatile | T] base (volatile pointee): loads of fields (p.field) through this pointer carry
|
||
# volatile. It is 1 only when the base resolves to a VarEntry (Name) / FieldEntry (Attribute), else 0.
|
||
vol_ba_rd: int = self.base_pointee_is_volatile(pool, trans, at.value)
|
||
|
||
# look up the struct type info; obj_ptr's type should be Ptr(Struct(...))
|
||
match obj_ptr.Ty:
|
||
case mlirlite.LLVMType.Ptr(struct_ty):
|
||
# ensure the cross-module struct definition is available in this module (for field GEP)
|
||
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, struct_ty)
|
||
# look up the field index and type
|
||
field_info: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(struct_ty, at.attr)
|
||
# REnum variant field lookup: when lookup_field fails, try the REnum alias. REnum struct
|
||
# fields are named _p1/_p2/..., but source uses variant field names (e.g. ty.Pointee).
|
||
if field_info is None:
|
||
renum_se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(struct_ty)
|
||
if renum_se is not None and renum_se.Name is not None:
|
||
is_rn: int = self.Gen.ClassGenEnum._is_renum_class_name(renum_se.Name)
|
||
if is_rn == 1:
|
||
alias_idx: int = self.Gen.ClassGenEnum._lookup_renum_field_alias(renum_se.Name, at.attr)
|
||
if alias_idx >= 1:
|
||
alias_fe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(renum_se, alias_idx)
|
||
if alias_fe is not None and alias_fe.Ty is not None:
|
||
alias_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, struct_ty, alias_fe.Ty, obj_ptr, alias_idx)
|
||
if alias_ptr is not None:
|
||
# REnum variant field read by its declared type (matching match binding): the slot is i32/i64,
|
||
# so bitcast the slot address then load the declared type (no info loss) to avoid an int→ptr error.
|
||
decl_ty_rn: mlirlite.LLVMType* = self.Gen.ClassGenEnum._lookup_renum_field_alias_ty(renum_se.Name, at.attr)
|
||
if decl_ty_rn is not None:
|
||
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:
|
||
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_byte_order_bswap(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_byte_order_bswap(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:
|
||
prop_cls_fb: str = self.Gen.StructGen.get_class_name_by_type(pool, struct_ty)
|
||
prop_val: mlirlite.Value* = self.Gen.CallGenProp._try_property_getter_attr(pool, builder, mod, at, obj_ptr, trans, prop_cls_fb)
|
||
if prop_val is not None: return prop_val
|
||
# on-demand filter: when struct_ty is i8 (simplified union), look up by class name via
|
||
# AnnotClassName (base class name from a Name var annotation or an Attribute field annotation).
|
||
if field_info is None:
|
||
base_class: str = None
|
||
if at.value.kind() == ast.ASTKind.Name and trans is not None:
|
||
nm_fb: ast.Name* = ast.Name*(at.value)
|
||
if nm_fb.id is not None:
|
||
var_entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_fb.id)
|
||
if var_entry is not None: base_class = self.Gen.VarGen.var_annot_class_name(var_entry)
|
||
elif at.value.kind() == ast.ASTKind.Attribute:
|
||
# allow_module_global=1: the base may be "module name.global variable" (Mod.g, e.g.
|
||
# GenApi._g_gen); without it the receiver class cannot be resolved and lookup_field fails.
|
||
base_class = self._resolve_attr_field_class(pool, trans, at.value, 1)
|
||
elif at.value.kind() == ast.ASTKind.Call:
|
||
# pointer-cast base (`(ast.Call*(x)).IsVolatile`): `T*(x)` yields the union-simplified i8*,
|
||
# so extract the class name from the cast annotation (see _base_class_from_cast_base).
|
||
base_class = self._base_class_from_cast_base(pool, trans, at.value)
|
||
if base_class is not None:
|
||
cur_sha1: str = trans.ModuleSha1
|
||
field_info = self.Gen.StructGen.lookup_field_by_class(base_class, at.attr, cur_sha1, at, trans._from_imports)
|
||
# REnum variant field alias fallback: a variant field name (e.g. LLVMType.Pointee → the
|
||
# _p2 slot) is not a normal field; lookup_field_by_class misses it when struct_ty is i8.
|
||
if field_info is None and self.Gen.ClassGenEnum._is_renum_class_name(base_class) != 0:
|
||
rn_alias: int = self.Gen.ClassGenEnum._lookup_renum_field_alias(base_class, at.attr)
|
||
if rn_alias >= 1:
|
||
rn_se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(base_class)
|
||
if rn_se is not None:
|
||
rn_fe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(rn_se, rn_alias)
|
||
if rn_fe is not None and rn_fe.Ty is not None: field_info = rn_fe
|
||
# on-demand filter succeeded: bitcast obj_ptr to the AnnotClassName struct type;
|
||
# the original struct_ty may be i8 (X* simplified to Ptr(i8)), so GEP needs the real type.
|
||
if field_info is not None:
|
||
annot_se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(base_class)
|
||
if annot_se is not None and annot_se.Ty is not None:
|
||
annot_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, annot_se.Ty)
|
||
casted_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, obj_ptr, annot_ptr_ty)
|
||
if casted_ptr is not None:
|
||
obj_ptr = casted_ptr
|
||
struct_ty = annot_se.Ty
|
||
# after the on-demand filter updates struct_ty, re-ensure the annotated
|
||
# struct definition is available (the original struct_ty may be i8)
|
||
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, annot_se.Ty)
|
||
if field_info is not None:
|
||
field_ty: mlirlite.LLVMType* = field_info.Ty
|
||
# t.CVolatile field: loads through the object carry the volatile qualifier
|
||
field_vol_rd: int = self.Gen.StructGen.field_is_volatile(field_info)
|
||
# merge "the field's pointee is volatile": when the base is rawptr[t.CVolatile|T],
|
||
# field loads through it must also be volatile (OR of both flags)
|
||
if vol_ba_rd != 0: field_vol_rd = 1
|
||
# union: bitcast obj_ptr to field_ty* then load
|
||
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:
|
||
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_byte_order_bswap(pool, builder, union_val_rd, field_info.ByteOrder)
|
||
return None
|
||
# normal struct: GEP + load
|
||
field_idx: int = field_info.Index
|
||
field_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, struct_ty, field_ty, obj_ptr, field_idx)
|
||
if field_ptr is not None:
|
||
# [FIX 2026-08-31] read an REnum variant field by its declared type (matching the main
|
||
# alias fallback): bitcast the _pN slot address, then load by declared type (no info loss).
|
||
rn_rd_se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(struct_ty)
|
||
if rn_rd_se is not None and rn_rd_se.Name is not None and self.Gen.ClassGenEnum._is_renum_class_name(rn_rd_se.Name) != 0:
|
||
decl_ty_rd: mlirlite.LLVMType* = self.Gen.ClassGenEnum._lookup_renum_field_alias_ty(rn_rd_se.Name, at.attr)
|
||
if decl_ty_rd is not None and self.is_array_type(decl_ty_rd) == 0:
|
||
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:
|
||
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_byte_order_bswap(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_byte_order_bswap(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)
|
||
if loaded_val is not None and fc_annot is not None and self.is_ptr_type(field_ty) != 0:
|
||
annot_struct: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(fc_annot)
|
||
if annot_struct is not None and annot_struct.Ty is not None:
|
||
# ensure the target struct has a declaration here (its AnnotClassName may name an
|
||
# unimported class); Ptr(Ty) lets the opaque scan declare the struct name itself.
|
||
mlirlite.module_ensure_opaque_for_type(mod, pool, mlirlite.Ptr(pool, annot_struct.Ty))
|
||
annot_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, annot_struct.Ty)
|
||
return mlirlite.build_bitcast(builder, loaded_val, annot_ptr_ty)
|
||
return loaded_val
|
||
# field not found: may be Phase A deps not ready (definition-side module uncompiled, an
|
||
# empty vpi stub class with FieldCount=0, e.g. LARGE_INTEGER) → fall through to the real error
|
||
if trans is not None:
|
||
diag_annot: str = None
|
||
diag_var_nm: str = None
|
||
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
|
||
dnm: ast.Name* = ast.Name*(at.value)
|
||
diag_var_nm = dnm.id
|
||
if dnm.id is not None:
|
||
dve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, dnm.id)
|
||
if dve is not None: diag_annot = self.Gen.VarGen.var_annot_class_name(dve)
|
||
diag_var_txt: str = "?"
|
||
if diag_var_nm is not None: diag_var_txt = diag_var_nm
|
||
diag_annot_txt: str = "None"
|
||
if diag_annot is not None: diag_annot_txt = diag_annot
|
||
# receiver chain path: `var=?` means the receiver is not a local variable, so spell out the
|
||
# full chain (e.g. "GenApi._g_gen.TypeGenCDef") to tell a global chain from a field chain.
|
||
diag_base_txt: str = "?"
|
||
if at.value is not None:
|
||
dbg_bp: str = self._attr_module_path_str(pool, at.value)
|
||
if dbg_bp is not None: diag_base_txt = dbg_bp
|
||
sha1_attr: str = "?"
|
||
if trans.ModuleSha1 is not None: sha1_attr = trans.ModuleSha1
|
||
self.Gen.TypeGenErr.fatal_error(at, f"attribute access failed: .{at.attr} (var={diag_var_txt} annot={diag_annot_txt} base={diag_base_txt} sha1={sha1_attr})", "TypeError")
|
||
return None
|
||
case _:
|
||
# fast-fail: attribute access on a non-pointer type (e.g. i32.x) aborts immediately via a
|
||
# Python traceback, rather than silently returning None (which loses the error site)
|
||
attr_nm_ap: str = "?"
|
||
if at is not None and at.attr is not None: attr_nm_ap = at.attr
|
||
sha1_ap: str = "?"
|
||
if trans is not None and trans.ModuleSha1 is not None: sha1_ap = trans.ModuleSha1
|
||
ln_ap: int = 0
|
||
if at is not None and at.lineno is not None: ln_ap = at.lineno
|
||
self.Gen.TypeGenErr.fatal_error(at, f"attribute access failed: .{attr_nm_ap} (base=non-pointer type, cannot do attribute access sha1={sha1_ap}:{ln_ap})", "TypeError")
|
||
# explicit return after fatal_error: the final match arm must be terminated, else falling off
|
||
# the end is misjudged by the function tail as fail-fast
|
||
return None
|
||
# get_attribute_ptr - get the field pointer for an attribute access (no value load, for assignment);
|
||
# for a Name obj, use the alloca pointer directly (no struct load).
|
||
def _resolve_attr_field_class(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*, allow_module_global: int = 0) -> str:
|
||
"""Resolve the annotation class name of the final field in an attribute chain (a.b.c)."""
|
||
if attr_node is None or trans is None: return None
|
||
k: int = attr_node.kind()
|
||
if k != ast.ASTKind.Attribute: return None
|
||
at: ast.Attribute* = ast.Attribute*(attr_node)
|
||
if at is None or at.attr is None or at.value is None: return None
|
||
# recursively resolve the base's annotation class name (Name → var annotation; Attribute → field annotation)
|
||
parent_class: str = None
|
||
if at.value.kind() == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(at.value)
|
||
if nm.id is None: return None
|
||
var_entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
if var_entry is None:
|
||
if allow_module_global != 0:
|
||
# the base itself is "module name.global variable" (Mod.g, e.g. GenApi._g_gen): its class name
|
||
# comes from the target module global's element type (else None, keeping the caller's semantics).
|
||
return self._resolve_module_global_class_name(pool, trans, nm.id, at.attr)
|
||
return None
|
||
parent_class = self.Gen.VarGen.var_annot_class_name(var_entry)
|
||
elif at.value.kind() == ast.ASTKind.Attribute: parent_class = self._resolve_attr_field_class(pool, trans, at.value, allow_module_global)
|
||
elif at.value.kind() == ast.ASTKind.Call:
|
||
# pointer-cast base (the recursive parent of `(T*(x)).f.g`): extract the class name from the
|
||
# cast annotation (see the _base_class_from_cast_base header)
|
||
parent_class = self._base_class_from_cast_base(pool, trans, at.value)
|
||
else: return None
|
||
if parent_class is None: return None
|
||
# look up the at.attr field in the parent class and return its annotation class name
|
||
cur_sha1: str = None
|
||
if trans is not None: cur_sha1 = trans.ModuleSha1
|
||
lfi_pc: str = None
|
||
if trans is not None: lfi_pc = trans._from_imports
|
||
fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field_by_class(parent_class, at.attr, cur_sha1, at, lfi_pc)
|
||
if fe is None: return None
|
||
return self.Gen.StructGen.get_field_annot_class_name(fe)
|
||
|
||
# _base_class_from_cast_base - bare class name of a pointer-cast base `T*(x)` (parsed as `(T*)(x)`, whose
|
||
# union-with-t.CPtr type is i8*), extracted from the BinOp annotation; bare name matches TPC's convention.
|
||
def _base_class_from_cast_base(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> str:
|
||
"""Return the bare class name of a T*(x) cast base, or None for other forms."""
|
||
if node is None or trans is None: return None
|
||
if node.kind() != ast.ASTKind.Call: return None
|
||
cl_cb: ast.Call* = ast.Call*(node)
|
||
if cl_cb.func is None or cl_cb.func.kind() != ast.ASTKind.BinOp: return None
|
||
bo_cb: ast.BinOp* = ast.BinOp*(cl_cb.func)
|
||
if bo_cb.op != ast.OpKind.BitOr: return None
|
||
# when one side of the union is the t.CPtr marker (the nested union of `T*` / `T**`), take the
|
||
# other side; otherwise (a hand-written `X | Y`) take the left side, matching parser _make_ptr_type
|
||
tgt_cb: ast.AST* = bo_cb.left
|
||
if self.Gen.TypeGenResolve._is_cptr_marker(bo_cb.left, trans._imported_modules) != 0: tgt_cb = bo_cb.right
|
||
if tgt_cb is None: return None
|
||
k_cb: int = tgt_cb.kind()
|
||
if k_cb == ast.ASTKind.Attribute:
|
||
at_cb: ast.Attribute* = ast.Attribute*(tgt_cb)
|
||
return at_cb.attr
|
||
if k_cb == ast.ASTKind.Name:
|
||
nm_cb: ast.Name* = ast.Name*(tgt_cb)
|
||
return nm_cb.id
|
||
return None
|
||
|
||
# _resolve_module_global_class_name - struct class name of a "module name.global variable" Mod.g: taken
|
||
# from the target module global's element type (lookup_var_entry is None for such a base); else None.
|
||
def _resolve_module_global_class_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, mod_name: str, global_name: str) -> str:
|
||
"""Resolve the element type class name of a module-qualified global Mod.g, else None."""
|
||
if pool is None or trans is None or mod_name is None or global_name is None: return None
|
||
mod: mlirlite.LLVMModule* = trans.Module
|
||
if mod is None or trans._imported_modules is None: return None
|
||
# module path: resolve the import alias (same truncation as _resolve_module_attribute_global)
|
||
mod_path: str = mod_name
|
||
if trans._from_imports is not None:
|
||
orig_mw: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, mod_name, 0)
|
||
if orig_mw is not None:
|
||
oml_mg: sizet = 0
|
||
while orig_mw[oml_mg] != ' ' and orig_mw[oml_mg] != '\0' and orig_mw[oml_mg] != ':': oml_mg += 1
|
||
if oml_mg > 0:
|
||
om_buf_mg: str = pool.alloc(oml_mg + 1)
|
||
if om_buf_mg is not None:
|
||
string.strncpy(om_buf_mg, orig_mw, oml_mg)
|
||
om_buf_mg[oml_mg] = '\0'
|
||
mod_path = om_buf_mg
|
||
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, mod_path) == 0: return None
|
||
# global location: SHA1-prefixed name (matching the definition-side mangling) → bare-name fallback (CExport)
|
||
gv: mlirlite.GlobalVariable* = self.find_global_in_module(mod, self.Gen.FuncGenBase._mangle_name_with_sha1(self._lookup_module_sha1(mod_path), global_name))
|
||
if gv is None: gv = self.find_global_in_module(mod, global_name)
|
||
if gv is None or gv.Ty is None: return None
|
||
if self.is_ptr_type(gv.Ty) == 0: return None
|
||
# the global's type is "pointer to element type" (@"<sha1>._g_gen" : %Generator*) ⇒ take the class
|
||
# name from its element type; it may still be a pointer (e.g. %Generator**) ⇒ unwrap level by level.
|
||
elem_ty: mlirlite.LLVMType* = gv.Ty.Pointee
|
||
level_mg: int = 0
|
||
while elem_ty is not None and level_mg < 3:
|
||
cls_mg: str = self.Gen.StructGen.get_class_name_by_type(pool, elem_ty)
|
||
if cls_mg is not None: return cls_mg
|
||
if self.is_ptr_type(elem_ty) == 0: return None
|
||
elem_ty = elem_ty.Pointee
|
||
level_mg += 1
|
||
return None
|
||
|
||
def attr_field_is_volatile(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*) -> int:
|
||
"""Return 1 if the obj.field field is volatile, 0 otherwise."""
|
||
if attr_node is None or trans is None: return 0
|
||
if attr_node.kind() != ast.ASTKind.Attribute: return 0
|
||
at: ast.Attribute* = ast.Attribute*(attr_node)
|
||
if at is None or at.attr is None or at.value is None: return 0
|
||
parent_class: str = None
|
||
if at.value.kind() == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(at.value)
|
||
if nm.id is None: return 0
|
||
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
if ve is None: return 0
|
||
parent_class = self.Gen.VarGen.var_annot_class_name(ve)
|
||
elif at.value.kind() == ast.ASTKind.Attribute: parent_class = self._resolve_attr_field_class(pool, trans, at.value)
|
||
if parent_class is None: return 0
|
||
fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field_by_class(parent_class, at.attr, trans.ModuleSha1, at, trans._from_imports)
|
||
return self.Gen.StructGen.field_is_volatile(fe)
|
||
|
||
# pointee volatile (rawptr[t.CVolatile | T], i.e. C's volatile T*): resolves a base to a
|
||
# VarEntry/FieldEntry so that loads/stores through p[i] / p.field / *p carry the volatile qualifier.
|
||
def _resolve_attr_field_entry(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*) -> GenTypes.FieldEntry*:
|
||
"""Return the FieldEntry for an obj.field access via a non-fatal read-only resolution."""
|
||
if attr_node is None or trans is None: return None
|
||
if attr_node.kind() != ast.ASTKind.Attribute: return None
|
||
at: ast.Attribute* = ast.Attribute*(attr_node)
|
||
if at is None or at.attr is None or at.value is None: return None
|
||
if at.value.kind() != ast.ASTKind.Name: return None
|
||
nm: ast.Name* = ast.Name*(at.value)
|
||
if nm.id is None: return None
|
||
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
if ve is None: return None
|
||
cls_nm: str = self.Gen.VarGen.var_annot_class_name(ve)
|
||
if cls_nm is None: return None
|
||
# take the bare class name (before the dot): only a bare-name struct lookup, avoiding fatal module-qualified parsing
|
||
bare: str = cls_nm
|
||
dot: str = self.Gen.StructGen._last_dot_outside_brackets(cls_nm)
|
||
if dot is not None: bare = dot + 1
|
||
if bare is None: return None
|
||
st: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(bare)
|
||
if st is None: return None
|
||
for fi in range(int(st.FieldCount)):
|
||
fe_u: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(st, fi)
|
||
if fe_u is not None and fe_u.Name is not None and string.strcmp(fe_u.Name, at.attr) == 0: return fe_u
|
||
return None
|
||
|
||
def attr_field_pointee_is_volatile(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*) -> int:
|
||
"""Return 1 if the pointee of the obj.field field is volatile, 0 otherwise."""
|
||
fe: GenTypes.FieldEntry* = self._resolve_attr_field_entry(pool, trans, attr_node)
|
||
return self.Gen.StructGen.field_pointee_is_volatile(fe)
|
||
|
||
def attr_field_is_const(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*) -> int:
|
||
"""Return 1 if the obj.field field itself is a const object, 0 otherwise."""
|
||
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)
|
||
return self.Gen.StructGen.get_field_annot_class_name(fe)
|
||
|
||
def base_pointee_is_volatile(self, pool: memhub.MemBuddy*, trans: HT.Translator*, base_node: ast.AST*) -> int:
|
||
"""Return 1 if the pointer base's pointee is volatile, 0 otherwise."""
|
||
if base_node is None or trans is None: return 0
|
||
if base_node.kind() == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(base_node)
|
||
if nm.id is None: return 0
|
||
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
return self.Gen.VarGen.var_pointee_is_volatile(ve)
|
||
if base_node.kind() == ast.ASTKind.Attribute: return self.attr_field_pointee_is_volatile(pool, trans, base_node)
|
||
return 0
|
||
|
||
def get_attribute_ptr(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Return the field pointer for an attribute access (used for assignment lhs)."""
|
||
if node is None or builder is None: return None
|
||
at: ast.Attribute* = ast.Attribute*(node)
|
||
if at is None or at.value is None or at.attr is None: return None
|
||
|
||
# for a Name obj, look up the alloca directly (no struct load); nonlocal variables (hoisted
|
||
# into the function) read their shared heap value from env, avoiding a cross-function alloca ref.
|
||
obj_ptr: mlirlite.Value* = None
|
||
if at.value.kind() == ast.ASTKind.Name and trans is not None:
|
||
nm: ast.Name* = ast.Name*(at.value)
|
||
if nm.id is not None:
|
||
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
|
||
raw_nl_a: mlirlite.Value* = self.Gen.StmtGenDecl._load_raw_var_ptr(trans, nm.id)
|
||
if raw_nl_a is not None:
|
||
nl_ty_a: mlirlite.LLVMType* = GenApi.get_nonlocal_type(trans, nm.id)
|
||
if nl_ty_a is not None:
|
||
# object is the pointer: Struct/Array use the heap address directly (bitcast,
|
||
# no load); pointer types (e.g. Ptr(Point)) load the object pointer from the heap
|
||
match nl_ty_a:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
obj_ptr = mlirlite.build_bitcast(builder, raw_nl_a, mlirlite.Ptr(pool, nl_ty_a))
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
obj_ptr = mlirlite.build_bitcast(builder, raw_nl_a, mlirlite.Ptr(pool, nl_ty_a))
|
||
case _:
|
||
nl_val_ptr_a: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl_a, mlirlite.Ptr(pool, nl_ty_a))
|
||
if nl_val_ptr_a is not None: obj_ptr = mlirlite.build_load(builder, nl_ty_a, nl_val_ptr_a)
|
||
else: obj_ptr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||
|
||
# module attribute access (write path): sys._mbuddy = mb / os._win32._mbuddy = v → store to the module
|
||
# global. When obj is an imported module name (Name or nested Attribute path), find/create the global.
|
||
if obj_ptr is None and trans is not None:
|
||
mod_path_c: str = self._attr_module_path_str(pool, at.value)
|
||
if mod_path_c is not None:
|
||
# a CDefine constant is not writable: report FATAL instead of creating an undefined global
|
||
# (only a single-layer Name module can be precisely identified as a CDefine)
|
||
if at.value.kind() == ast.ASTKind.Name:
|
||
nm_wma: ast.Name* = ast.Name*(at.value)
|
||
if nm_wma.id is not None:
|
||
if self.Gen.ExprGenCdef._is_cdefine_name_pattern(at.attr) != 0:
|
||
err_buf_wma: str = pool.alloc(256)
|
||
if err_buf_wma is not None:
|
||
viperlib.snprintf(err_buf_wma, 256, "[CD] FATAL: Cannot assign to CDefine constant '%s.%s'\n", nm_wma.id, at.attr)
|
||
VLogger.error(err_buf_wma, "CD")
|
||
return None
|
||
# use i8* as the type hint (module-level variables usually hold pointers)
|
||
i8_ptr_w: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
wma_ref: mlirlite.Value* = self._resolve_module_attribute_global(pool, mod, trans, at, i8_ptr_w)
|
||
if wma_ref is not None: return wma_ref
|
||
|
||
# non-Name path: translate the object value (which loads)
|
||
if obj_ptr is None:
|
||
# c.Deref(ptr).attr write: the base is the pointer itself, not the loaded struct value
|
||
# (c.Deref(p).a = val means (*p).a = val, so GEP p directly)
|
||
cd_base_wr: mlirlite.Value* = self._get_cderef_attr_base(builder, pool, mod, at.value, trans)
|
||
if cd_base_wr is not None: obj_ptr = cd_base_wr
|
||
elif at.value.kind() == ast.ASTKind.Subscript:
|
||
# subscript-base attribute write (e.g. pts[0].x = val): list[T]/OOP __getitem__ classes use
|
||
# translate_value; plain struct-pointer arrays use get_subscript_ptr (GEP the element address).
|
||
if self._sub_base_uses_getitem(pool, mod, trans, at.value) != 0: obj_ptr = self.Gen.ExprGen.translate_value(builder, pool, mod, at.value, None, 0, trans)
|
||
else: obj_ptr = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, at.value, trans)
|
||
elif at.value.kind() == ast.ASTKind.Attribute:
|
||
# nested attribute base write (e.g. r.origin.x = val): take the inner field address
|
||
# (recursive get_attribute_ptr, address semantics); pointer fields are loaded by _deref_if_ptr_ptr
|
||
obj_ptr = self.get_attribute_ptr(builder, pool, mod, at.value, trans)
|
||
if obj_ptr is None: obj_ptr = self.Gen.ExprGen.translate_value(builder, pool, mod, at.value, None, 0, trans)
|
||
else: obj_ptr = self.Gen.ExprGen.translate_value(builder, pool, mod, at.value, None, 0, trans)
|
||
|
||
if obj_ptr is None or obj_ptr.Ty is None: return None
|
||
|
||
# if obj_ptr is Ptr(Ptr(Struct)) (the alloca of an X* variable), load to deref and get Ptr(Struct)
|
||
obj_ptr = self._deref_if_ptr_ptr(builder, obj_ptr)
|
||
|
||
match obj_ptr.Ty:
|
||
case mlirlite.LLVMType.Ptr(struct_ty):
|
||
# ensure the cross-module struct definition is available in this module (for field GEP);
|
||
# as in translate_attribute, this avoids a GEP error on opaque types
|
||
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, struct_ty)
|
||
field_info: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(struct_ty, at.attr)
|
||
# on-demand filter: when struct_ty is i8 (simplified union), resolve the base class name (Name →
|
||
# var annotation; Attribute → recursive field annotation, e.g. lx.tokens_tail.next: Lexer → Token).
|
||
if field_info is None:
|
||
base_class: str = None
|
||
if at.value.kind() == ast.ASTKind.Name and trans is not None:
|
||
nm_fb: ast.Name* = ast.Name*(at.value)
|
||
if nm_fb.id is not None:
|
||
var_entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_fb.id)
|
||
if var_entry is not None: base_class = self.Gen.VarGen.var_annot_class_name(var_entry)
|
||
elif at.value.kind() == ast.ASTKind.Attribute:
|
||
# allow_module_global=1, as in translate_attribute (the base may be "module name.global
|
||
# variable", e.g. GenApi._g_gen.TypeGenCDef)
|
||
base_class = self._resolve_attr_field_class(pool, trans, at.value, 1)
|
||
elif at.value.kind() == ast.ASTKind.Call:
|
||
# pointer-cast base (`(ast.Call*(vinner)).IsVolatile = 1`): `T*(x)` yields the union-simplified
|
||
# i8*, so extract the class name from the cast annotation (see _base_class_from_cast_base).
|
||
base_class = self._base_class_from_cast_base(pool, trans, at.value)
|
||
if base_class is not None:
|
||
cur_sha1: str = None
|
||
lfi_gp: str = None
|
||
if trans is not None:
|
||
cur_sha1 = trans.ModuleSha1
|
||
lfi_gp = trans._from_imports
|
||
field_info = self.Gen.StructGen.lookup_field_by_class(base_class, at.attr, cur_sha1, at, lfi_gp)
|
||
# REnum variant field alias fallback (as in translate_attribute): the assignment path's
|
||
# on-demand filter also needs it, since the main alias search is skipped when struct_ty is i8.
|
||
if field_info is None and self.Gen.ClassGenEnum._is_renum_class_name(base_class) != 0:
|
||
rn_alias_g: int = self.Gen.ClassGenEnum._lookup_renum_field_alias(base_class, at.attr)
|
||
if rn_alias_g >= 1:
|
||
rn_se_g: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(base_class)
|
||
if rn_se_g is not None:
|
||
rn_fe_g: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(rn_se_g, rn_alias_g)
|
||
if rn_fe_g is not None and rn_fe_g.Ty is not None: field_info = rn_fe_g
|
||
# on-demand filter succeeded: bitcast obj_ptr to the AnnotClassName struct type;
|
||
# the original struct_ty may be i8 (X* simplified to Ptr(i8)), so GEP needs the real type.
|
||
if field_info is not None:
|
||
annot_se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(base_class)
|
||
if annot_se is not None and annot_se.Ty is not None:
|
||
annot_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, annot_se.Ty)
|
||
casted_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, obj_ptr, annot_ptr_ty)
|
||
if casted_ptr is not None:
|
||
obj_ptr = casted_ptr
|
||
struct_ty = annot_se.Ty
|
||
# after the on-demand filter updates struct_ty, re-ensure the annotation struct
|
||
# definition is available in this module
|
||
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, annot_se.Ty)
|
||
if field_info is not None:
|
||
# union: bitcast obj_ptr to field_ty* (field pointer for the store)
|
||
if self.Gen.StructGen.is_union_by_type(struct_ty) == 1:
|
||
field_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, field_info.Ty)
|
||
return mlirlite.build_bitcast(builder, obj_ptr, field_ptr_ty)
|
||
# normal struct: GEP to the field pointer
|
||
return mlirlite.build_gep_struct(builder, struct_ty, field_info.Ty, obj_ptr, field_info.Index)
|
||
# field not found: may be Phase A deps not ready (definition-side module uncompiled, an
|
||
# empty vpi stub class with FieldCount=0, e.g. SECURITY_ATTRIBUTES) → fall through to the real error
|
||
if trans is not None:
|
||
diag_annot_g: str = None
|
||
diag_var_nm_g: str = None
|
||
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
|
||
dnm_g: ast.Name* = ast.Name*(at.value)
|
||
diag_var_nm_g = dnm_g.id
|
||
if dnm_g.id is not None:
|
||
dve_g: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, dnm_g.id)
|
||
if dve_g is not None: diag_annot_g = self.Gen.VarGen.var_annot_class_name(dve_g)
|
||
diag_var_txt_g: str = "?"
|
||
if diag_var_nm_g is not None: diag_var_txt_g = diag_var_nm_g
|
||
diag_annot_txt_g: str = "None"
|
||
if diag_annot_g is not None: diag_annot_txt_g = diag_annot_g
|
||
# receiver chain path (the read-side completion in translate_attribute): `var=?` means the
|
||
# receiver is not a local variable, so spell out the full form (Mod.g.F[.F]).
|
||
diag_base_txt_g: str = "?"
|
||
if at.value is not None:
|
||
dbg_bp_g: str = self._attr_module_path_str(pool, at.value)
|
||
if dbg_bp_g is not None: diag_base_txt_g = dbg_bp_g
|
||
sha1_attr_g: str = "?"
|
||
if trans.ModuleSha1 is not None: sha1_attr_g = trans.ModuleSha1
|
||
self.Gen.TypeGenErr.fatal_error(at, f"attribute write failed: .{at.attr} (var={diag_var_txt_g} annot={diag_annot_txt_g} base={diag_base_txt_g} sha1={sha1_attr_g})", "TypeError")
|
||
return None
|
||
case _:
|
||
# fast-fail: attribute write on a non-pointer type (e.g. i32.x = ...) aborts immediately via a
|
||
# Python traceback, rather than silently returning None (which loses the error site)
|
||
attr_nm_gp: str = "?"
|
||
if at is not None and at.attr is not None: attr_nm_gp = at.attr
|
||
sha1_gp: str = "?"
|
||
if trans is not None and trans.ModuleSha1 is not None: sha1_gp = trans.ModuleSha1
|
||
ln_gp: int = 0
|
||
if at is not None and at.lineno is not None: ln_gp = at.lineno
|
||
self.Gen.TypeGenErr.fatal_error(at, f"attribute write failed: .{attr_nm_gp} (base=non-pointer type, cannot do attribute write sha1={sha1_gp}:{ln_gp})", "TypeError")
|
||
# explicit return after fatal_error: the final match arm must be terminated
|
||
return None |