Borrow exclusivity now covers direct uses of the owner, not just creating another borrow. A write or move of an owner (`c.x = 1`, `del`/move, a method call whose `self` is `&mut` or implicit) conflicts with any live borrow; a read (`c.x`, passing it on) conflicts with a live `&mut` borrow. `_BorrowCheckAccess` resolves the root binding of the access place, applies the NLL dead-borrow rule, and is wired into name loads, attribute loads, assignment targets, own moves (field/argument/assignment), and method receivers (the receiver's `self` kind is read from the parameter registry; implicit `self` is treated conservatively as a write). Reads inside a borrow expression are suppressed so `&mut x` is not reported against its own new borrow. The LSP mirrors the same three checks (write target, owner read, method receiver) with the same messages.
959 lines
75 KiB
Plaintext
959 lines
75 KiB
Plaintext
import lib.core.Gen.GenTypes as GenTypes
|
||
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import string
|
||
import stdio
|
||
import viperlib
|
||
import lib.core.Gen.Translator as HT
|
||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||
|
||
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
|
||
import lib.core.Gen.GenApi as GenApi
|
||
|
||
|
||
# ExprGenName - split out of ExprGenMixin (stage D): Name/Subscript and subscript access
|
||
novirtual class ExprGenName(ExprGenBase.ExprGenBase):
|
||
"""Name/Subscript subscript access helpers."""
|
||
|
||
def _cut_from_import_mod(self, pool: memhub.MemBuddy*, raw: str) -> str:
|
||
"""Copy the module name in raw into its own buffer, cut at space/colon/NUL; None when invalid."""
|
||
if raw is None: return None
|
||
mbl_c: sizet = 0
|
||
while raw[mbl_c] != ' ' and raw[mbl_c] != '\0' and raw[mbl_c] != ':': mbl_c += 1
|
||
if mbl_c == 0: return None
|
||
buf_c: str = pool.alloc(mbl_c + 1)
|
||
if buf_c is None: return None
|
||
string.strncpy(buf_c, raw, mbl_c)
|
||
buf_c[mbl_c] = '\0'
|
||
return buf_c
|
||
|
||
def translate_name_value(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator* = None, target_ty: mlirlite.LLVMType* = None, use_ctx: str = None) -> mlirlite.Value*:
|
||
"""Translate a variable reference (Name node) to a load; target_ty/use_ctx guide width."""
|
||
# mod must come from trans.Module: the signature has no mod param, and a bare `mod`
|
||
# would resolve to None, silently dropping string DefineT globals (llc undefined value).
|
||
mod: mlirlite.LLVMModule* = trans.Module
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm is None: return None
|
||
nm_id: str = nm.id
|
||
if nm_id is None: return None
|
||
# P2 willFree:读取已被"消费"的变量(其所有权被 @life(willFree(...)) 标注的调用夺取)→ fail-fast。
|
||
# 只在**读**路径检查;对同名变量重新赋值会清除失效态(见 AssignGenMixin)。
|
||
if trans is not None:
|
||
if self.Gen.VarGen._FreedCheck(self.Gen.VarGen.lookup_var(trans.SymTab, nm_id)) != 0:
|
||
self.Gen.TypeGenErr.fatal_error(node, f"cannot use '{nm_id}': already released (by `del`, a move, or a @life(willFree(...)) call)", "LifetimeError")
|
||
# OBS: reading an owner while a `&mut` borrow of it is live is a conflict.
|
||
if trans.StrictOBS != 0 and self.Gen.VarGen.lookup_var(trans.SymTab, nm_id) is not None \
|
||
and self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_id) == 0:
|
||
self.Gen.StmtGen._BorrowCheckAccess(trans, node, 1, node)
|
||
# DefineT compile-time constant: return the constant (float -> double, int -> i32).
|
||
# Existing variables win over DefineT table entries injected from vpi stubs (lookup_var guard).
|
||
cdef_val_d: double = self.Gen.TypeGenCDef.lookup_cdefine_double(nm_id)
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
if trans is None or self.Gen.VarGen.lookup_var(trans.SymTab, nm_id) is None:
|
||
if self.Gen.TypeGenCDef.is_cdefine_str() != 0:
|
||
# String DefineT (e.g. ZLIB_VERSION: DefineT = "1.3.2"): return a global
|
||
# string (i8*) for `return ZLIB_VERSION`.
|
||
cdef_sv: str = self.Gen.TypeGenCDef.lookup_cdefine_string(nm_id)
|
||
if cdef_sv is not None:
|
||
# `define X = None`: a named None, materialized like the None literal so a
|
||
# procedure's `return X` becomes `ret void` (never a value).
|
||
if string.strcmp(cdef_sv, "None") == 0: return self._NoneValueAsTarget(builder, target_ty)
|
||
return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, cdef_sv, trans)
|
||
self.Gen.TypeGenErr.fatal_error(node, f"DefineT string value missing: {nm_id}", "InternalCompilerError")
|
||
# 浮点常量:声明为 32 位 float(`define PI: float = ...`)时直接出 f32 常量,
|
||
# 否则出 f64 —— 出 f64 会把后续 f32 字面量整体提升成 double(fpext/cvtss2sd)。
|
||
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
|
||
if self.Gen.TypeGenCDef.is_cdefine_float32() != 0: return mlirlite.ConstFloat(pool, mlirlite.Float(pool), cdef_val_d)
|
||
return mlirlite.ConstFloat(pool, mlirlite.Double(pool), cdef_val_d)
|
||
# Integer DefineT: re-look-up the full 64-bit value (a double round-trip loses
|
||
# precision and would generate a wrong constant).
|
||
cdef_val_c: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(nm_id)
|
||
# Use-site width (passed via target_ty; use_ctx carries the site kind for messages):
|
||
# wider -> widen in place; narrower -> fail-fast; unknown -> the existing 32/64 default.
|
||
uw_val: mlirlite.Value* = self.Gen.ExprGenCdef.cdefine_use_width_value(pool, node, nm_id, cdef_val_c, target_ty, use_ctx)
|
||
if uw_val is not None: return uw_val
|
||
return self.Gen.ExprGenCdef._const_int_for_cdefine(pool, nm_id, cdef_val_c)
|
||
|
||
# Not found locally: try cross-module .vpi lookup, only for ALL_CAPS names so ordinary
|
||
# variable names never trigger the costly cross-module source read.
|
||
if self.Gen.ExprGenCdef._is_cdefine_name_pattern(nm_id) != 0:
|
||
if trans is not None:
|
||
# ALL_CAPS may also be a plain variable (e.g. CAM/FWD/RIGHT/UP: V3* module globals);
|
||
# an existing variable in scope takes priority, skipping the cross-module DefineT lookup.
|
||
if self.Gen.VarGen.lookup_var(trans.SymTab, nm_id) is None:
|
||
cdef_val: int64 = self.Gen.ExprGenCdef._lookup_cross_module_cdefine(pool, nm_id, trans._from_imports, trans._imported_modules, 0, node)
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
xmod: str = self.Gen.TypeGenCDef.get_last_cdefine_module()
|
||
if self.Gen.TypeGenCDef.is_cdefine_str() != 0:
|
||
cdef_sv2: str = self.Gen.TypeGenCDef.lookup_cdefine_string(nm_id, xmod)
|
||
if cdef_sv2 is not None: return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, cdef_sv2, trans)
|
||
self.Gen.TypeGenErr.fatal_error(node, f"DefineT string value missing: {nm_id}", "InternalCompilerError")
|
||
# Cross-module float DefineT is cached as double; return a float constant
|
||
# rather than a truncated integer.
|
||
dchk_nm: double = self.Gen.TypeGenCDef.lookup_cdefine_double(nm_id, xmod)
|
||
# 同本模块路径:f32 声明的常量直接出 f32 常量(跨模块缓存已带宽度种类)。
|
||
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
|
||
if self.Gen.TypeGenCDef.is_cdefine_float32() != 0: return mlirlite.ConstFloat(pool, mlirlite.Float(pool), dchk_nm)
|
||
return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dchk_nm)
|
||
# Same use-site width rule (cross-module DefineT)
|
||
uw_x: mlirlite.Value* = self.Gen.ExprGenCdef.cdefine_use_width_value(pool, node, nm_id, cdef_val, target_ty, use_ctx)
|
||
if uw_x is not None: return uw_x
|
||
return self.Gen.ExprGenCdef._const_int_for_cdefine(pool, nm_id, cdef_val, xmod)
|
||
|
||
# DefineT lookup failed: from-import module-level globals (e.g. `from Phase2 import
|
||
# PA_DEPS_MAX_STR`, an i64 global) are located by suffix after confirming the source module.
|
||
if trans is not None and trans._from_imports is not None:
|
||
fi_mod_raw: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, nm_id, 0)
|
||
if fi_mod_raw is not None:
|
||
# Look up by the source module's SHA1 prefix to avoid same-name suffix collisions;
|
||
# fi_mod_raw is not NUL-terminated, so truncate before the SHA1 lookup.
|
||
fi_mod_cut: str = self._cut_from_import_mod(pool, fi_mod_raw)
|
||
if fi_mod_cut is None: fi_mod_cut = fi_mod_raw
|
||
fi_gv_q: str = self.Gen.FuncGenBase._mangle_name_with_sha1(StubMergerStore.LookupSha1ByModName(fi_mod_cut), nm_id)
|
||
fi_gv: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, fi_gv_q)
|
||
if fi_gv is not None and fi_gv.Ty is not None:
|
||
fi_ref: mlirlite.Value* = self.Gen.ExprGenAttr.make_global_ref(pool, fi_gv.Name, fi_gv.Ty)
|
||
if fi_ref is not None and fi_ref.Ty is not None:
|
||
fi_pty: mlirlite.LLVMType* = fi_ref.Ty.Pointee
|
||
if fi_pty is not None:
|
||
match fi_pty:
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
# Array globals decay to the base address per C semantics
|
||
return fi_ref
|
||
case _:
|
||
pass
|
||
return mlirlite.build_load(builder, fi_pty, fi_ref)
|
||
|
||
# global variable: look up in the module scope (global declaration has highest priority)
|
||
if trans is not None:
|
||
if GenApi.is_global_name(trans, nm_id) != 0:
|
||
mod_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm_id)
|
||
if mod_alloca is not None:
|
||
load_ty: mlirlite.LLVMType* = None
|
||
if mod_alloca.Ty is not None: load_ty = mod_alloca.Ty.Pointee
|
||
if load_ty is None:
|
||
# fail-fast: a global with a missing storage type aborts compilation
|
||
self.Gen.TypeGenErr.fatal_error(node, f"variable '{nm_id}' storage type missing", "TypeError")
|
||
# Global arrays decay to the base address per C semantics; otherwise a loaded
|
||
# array value passed as i8* loses writes (test_pyzlib "error message is empty").
|
||
match load_ty:
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
return mod_alloca
|
||
case _:
|
||
pass
|
||
# volatile global: load carries volatile (prevents optimizing away repeated reads)
|
||
vol_g: int = 0
|
||
if trans is not None:
|
||
ve_g: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_id)
|
||
vol_g = self.Gen.VarGen.var_is_volatile(ve_g)
|
||
return mlirlite.build_load(builder, load_ty, mod_alloca, vol_g)
|
||
|
||
# nonlocal variable: accessed through the closure env
|
||
if GenApi.is_nonlocal_name(trans, nm_id) != 0: return self.Gen.StmtGenDecl.load_nonlocal_var(trans, nm_id)
|
||
|
||
# *args va_list: the name resolves directly to an i8* pointing at the 24-byte va_list
|
||
# buffer (no load of the buffer contents; pass the address by value when forwarding).
|
||
if trans is not None and trans._vararg_name is not None:
|
||
if nm_id is not None and string.strcmp(nm_id, trans._vararg_name) == 0:
|
||
if trans._va_list_ptr is not None: return trans._va_list_ptr
|
||
|
||
# Local variable lookup must precede the module-alias check, or a local with the same
|
||
# name as a module would be misread and not found.
|
||
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_id)
|
||
if alloca is not None:
|
||
load_ty: mlirlite.LLVMType* = None
|
||
if alloca.Ty is not None: load_ty = alloca.Ty.Pointee
|
||
if load_ty is None:
|
||
# fail-fast: a local with a missing storage type aborts compilation
|
||
self.Gen.TypeGenErr.fatal_error(node, f"variable '{nm_id}' storage type missing", "TypeError")
|
||
# Struct-value variable (alloca of Struct): rvalues return its address by default;
|
||
# a value is loaded only when the use site demands a same-type struct value.
|
||
match load_ty:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
# Struct-value locals return the alloca address by default; when the use site
|
||
# explicitly needs a same-type non-pointer struct value, load it (_same_agg_ty).
|
||
if target_ty is not None:
|
||
if self.Gen.ExprGenBase._same_agg_ty(pool, load_ty, target_ty) != 0:
|
||
vol_s: int = 0
|
||
if trans is not None:
|
||
ve_s: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_id)
|
||
vol_s = self.Gen.VarGen.var_is_volatile(ve_s)
|
||
return mlirlite.build_load(builder, load_ty, alloca, vol_s)
|
||
return alloca
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
# Array variables decay to a pointer (base address) per C semantics; loading the
|
||
# whole array value into "%s" reads wrong data (ClassArrayTest crash root cause).
|
||
return alloca
|
||
# volatile 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)
|
||
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_byte_order_bswap(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.
|
||
if trans is not None and trans.ReplCtx is not None:
|
||
sess_val: mlirlite.Value* = self.Gen.VarGen.repl_try_read(trans, nm_id, node)
|
||
if sess_val is not None: return sess_val
|
||
|
||
# Module alias check (only after SymTab misses): if nm_id is an imported module name that is
|
||
# not a declared local, return None so the caller can handle attribute access.
|
||
if trans is not None and trans._imported_modules is not None:
|
||
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, nm_id) != 0: return None
|
||
|
||
# Module-level function reference: build a real function type from the Function's RetTy/Params
|
||
# and wrap it in a new Value (returning/bitcasting the Function itself yields illegal IR).
|
||
if trans is not None and trans.Module is not None:
|
||
fn_ref: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(trans.Module, nm_id)
|
||
if fn_ref is not None:
|
||
fn_name: str = mlirlite.function_get_name(fn_ref)
|
||
if fn_name is None: return None
|
||
# Build the function type: ret_ty + parameter ParamNode list
|
||
fn_ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(fn_ref)
|
||
if fn_ret_ty is None:
|
||
# fail-fast: a missing function return type prevents building the function pointer
|
||
self.Gen.TypeGenErr.fatal_error(node, f"function '{nm_id}' return type missing, cannot build the function pointer", "TypeError")
|
||
fn_pp: mlirlite.Param* = mlirlite.function_get_param_head(fn_ref)
|
||
fn_first_node: mlirlite.ParamNode* = None
|
||
fn_prev_node: mlirlite.ParamNode* = None
|
||
fn_pcount: int = 0
|
||
while fn_pp is not None:
|
||
fn_pty: mlirlite.LLVMType* = mlirlite.param_get_ty(fn_pp)
|
||
fn_pn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, fn_pty)
|
||
if fn_pn is not None:
|
||
if fn_first_node is None: fn_first_node = fn_pn
|
||
if fn_prev_node is not None: mlirlite.paramnode_set_next(fn_prev_node, fn_pn)
|
||
fn_prev_node = fn_pn
|
||
fn_pcount += 1
|
||
fn_pp = mlirlite.param_get_next(fn_pp)
|
||
fn_ty: mlirlite.LLVMType* = mlirlite.Func(pool, fn_ret_ty, fn_first_node, fn_pcount)
|
||
if fn_ty is None: return None
|
||
# Build the function pointer Value (wrapped in new_value to avoid touching Function.Next)
|
||
fn_ref_v: mlirlite.Value* = mlirlite.new_value(pool)
|
||
if fn_ref_v is None: return None
|
||
mlirlite.value_set_ty(fn_ref_v, mlirlite.Ptr(pool, fn_ty))
|
||
mlirlite.value_set_isconst(fn_ref_v, 1)
|
||
fn_ref_name: str = pool.alloc(256)
|
||
if fn_ref_name is not None:
|
||
fn_ref_name[0] = '\0'
|
||
if self.Gen.ClassGenVTable._vtable_name_needs_quote(fn_name) != 0: viperlib.snprintf(fn_ref_name, 256, "@\"%s\"", fn_name)
|
||
else: viperlib.snprintf(fn_ref_name, 256, "@%s", fn_name)
|
||
mlirlite.value_set_name(fn_ref_v, fn_ref_name)
|
||
fn_i8ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
return mlirlite.build_bitcast(builder, fn_ref_v, fn_i8ptr_ty)
|
||
|
||
# Cross-module from-import global read (e.g. `from .tokens import _op_head`): no local SymTab
|
||
# entry exists, so emit an external reference + load; the source module provides the definition.
|
||
if trans is not None and trans._from_imports is not None:
|
||
if trans.Module is not None:
|
||
# Is a from-import name (star fallback disabled to avoid misattributing to star modules)
|
||
fi_mod: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, nm_id, 0)
|
||
if fi_mod is not None:
|
||
# Exclude registered class/enum names: these are types, not global values
|
||
fi_is_class: int = 0
|
||
if self.Gen.StructGen.get_struct_type(nm_id) is not None: fi_is_class = 1
|
||
# Exclude ALL_CAPS DefineT (already handled by local + cross-module lookup)
|
||
if self.Gen.ExprGenCdef._is_cdefine_name_pattern(nm_id) != 0: fi_is_class = 1
|
||
if fi_is_class == 0:
|
||
fi_gname: str = nm_id
|
||
fi_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, nm_id)
|
||
if fi_orig is not None:
|
||
og_len_fi: sizet = 0
|
||
while fi_orig[og_len_fi] != ' ' and fi_orig[og_len_fi] != '\0': og_len_fi += 1
|
||
if og_len_fi > 0:
|
||
og_buf_fi: str = pool.alloc(og_len_fi + 1)
|
||
if og_buf_fi is not None:
|
||
string.strncpy(og_buf_fi, fi_orig, og_len_fi)
|
||
og_buf_fi[og_len_fi] = '\0'
|
||
fi_gname = og_buf_fi
|
||
# Reference the stub-injected global directly, or fail-fast (declaring external
|
||
# i8* masks the cause); look up by source module SHA1 after truncating fi_mod.
|
||
fi_mod_cut2: str = self._cut_from_import_mod(pool, fi_mod)
|
||
if fi_mod_cut2 is None: fi_mod_cut2 = fi_mod
|
||
gv_fi_q: str = self.Gen.FuncGenBase._mangle_name_with_sha1(StubMergerStore.LookupSha1ByModName(fi_mod_cut2), fi_gname)
|
||
gv_fi: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, gv_fi_q)
|
||
ref_fi: mlirlite.Value* = None
|
||
if gv_fi is not None and gv_fi.Ty is not None: ref_fi = self.Gen.ExprGenAttr.make_global_ref(pool, gv_fi.Name, gv_fi.Ty)
|
||
else:
|
||
# Dependency vpi/stub not injected yet (file-order Phase A may translate this
|
||
# before its dependency), so return None without fail-fast to avoid false errors.
|
||
return None
|
||
if ref_fi is not None and ref_fi.Ty is not None:
|
||
lty_fi: mlirlite.LLVMType* = ref_fi.Ty.Pointee
|
||
if lty_fi is not None: return mlirlite.build_load(builder, lty_fi, ref_fi)
|
||
return ref_fi
|
||
|
||
# All lookup paths failed: fail-fast (NameError) instead of returning None and crashing
|
||
# downstream. Exempt keywords/builtins (self/pool/None/_) whose absence is normal.
|
||
if nm_id is None: return None
|
||
if string.strcmp(nm_id, "self") == 0 or string.strcmp(nm_id, "pool") == 0 or string.strcmp(nm_id, "true") == 0 or string.strcmp(nm_id, "false") == 0 or string.strcmp(nm_id, "None") == 0 or string.strcmp(nm_id, "match") == 0 or string.strcmp(nm_id, "_") == 0:
|
||
return None
|
||
# Diagnostics: report the generic context, since "undefined name 'T'" is often far from the
|
||
# real cause (0 params = no context; params>0/args 0 = lost args; both>0 = name not a param).
|
||
ctx_txt: str = ""
|
||
if trans is not None and trans.GenericTypeParamNames is not None:
|
||
ctp_n: sizet = len(trans.GenericTypeParamNames)
|
||
cta_n: sizet = 0
|
||
if trans.GenericTypeArgs is not None: cta_n = len(trans.GenericTypeArgs)
|
||
ctx_txt = f"(current generic context: {ctp_n} type params / {cta_n} type args)"
|
||
self.Gen.TypeGenErr.fatal_error(node, f"undefined name '{nm_id}'{ctx_txt}", "NameError")
|
||
return None
|
||
# list_getitem_inline - inline list[T] __getitem__; returns the element address (i8*).
|
||
# Layout (6 fields x 8 bytes): __data__(0) __count__(8) __capacity__(16) __pool__(24) __elem_size__(32) __iter_index__(40).
|
||
def list_getitem_inline(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, lm_obj: mlirlite.Value*, idx_val: mlirlite.Value*, node: ast.AST* = None) -> mlirlite.Value*:
|
||
"""Inline list __getitem__; return the element address (i8*)."""
|
||
if builder is None or pool is None or lm_obj is None or idx_val is None: return None
|
||
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
# Load __data__ (offset 0, index 0)
|
||
g_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
|
||
g_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, g_idx0)
|
||
if g_dpp is None: return None
|
||
g_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, g_dpp)
|
||
# Load __elem_size__ (offset 32, index 4)
|
||
g_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
|
||
g_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, g_idx4)
|
||
if g_epp is None: return None
|
||
g_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, g_epp)
|
||
if g_data is None or g_esize is None: return None
|
||
g_idx_i64: mlirlite.Value* = self.coerce_to_type(builder, idx_val, i64_ty, node)
|
||
if g_idx_i64 is None: return None
|
||
g_offset: mlirlite.Value* = mlirlite.build_mul(builder, g_idx_i64, g_esize)
|
||
if g_offset is None: return None
|
||
# Return the element address (i8*) without loading
|
||
return mlirlite.build_gep(builder, i8_ty, g_data, g_offset)
|
||
# _list_elem_type_from_name - extract the element LLVM type from a list type name.
|
||
# Name format: <sha1>.list[<elem_type>]; strstr checks the full pattern (no substring extraction).
|
||
def _list_elem_type_from_name(self, pool: memhub.MemBuddy*, struct_name: str) -> mlirlite.LLVMType*:
|
||
"""Extract the element LLVM type from a list type name."""
|
||
if pool is None or struct_name is None: return None
|
||
# Check the full pattern with strstr to avoid substring extraction
|
||
if string.strstr(struct_name, "list[int]") is not None: return mlirlite.Int32(pool)
|
||
if string.strstr(struct_name, "list[CInt]") is not None: return mlirlite.Int32(pool)
|
||
if string.strstr(struct_name, "list[CInt8T]") is not None: return mlirlite.Int8(pool)
|
||
if string.strstr(struct_name, "list[CInt16T]") is not None: return mlirlite.Int16(pool)
|
||
if string.strstr(struct_name, "list[CInt32T]") is not None: return mlirlite.Int32(pool)
|
||
if string.strstr(struct_name, "list[CInt64T]") is not None: return mlirlite.Int64(pool)
|
||
if string.strstr(struct_name, "list[CSizeT]") is not None: return mlirlite.Int64(pool)
|
||
if string.strstr(struct_name, "list[CDouble]") is not None: return mlirlite.Double(pool)
|
||
if string.strstr(struct_name, "list[CFloat]") is not None: return mlirlite.Float(pool)
|
||
if string.strstr(struct_name, "list[CChar]") is not None: return mlirlite.Int8(pool)
|
||
# Known pointer element types (str/bytes/PtrT) -> i8* (elements hold 8-byte addresses)
|
||
i8_ty_d: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
if string.strstr(struct_name, "list[str]") is not None: return mlirlite.Ptr(pool, i8_ty_d)
|
||
if string.strstr(struct_name, "list[bytes]") is not None: return mlirlite.Ptr(pool, i8_ty_d)
|
||
if string.strstr(struct_name, "list[PtrT]") is not None: return mlirlite.Ptr(pool, i8_ty_d)
|
||
# Unknown list[T] element type (custom struct element): the slot holds an 8-byte object
|
||
# pointer, so restore Ptr(%Struct) to carry the element class for chained method dispatch.
|
||
elem_obj_ty_l: mlirlite.LLVMType* = self._list_elem_obj_ty_from_struct_name(pool, struct_name)
|
||
if elem_obj_ty_l is not None: return elem_obj_ty_l
|
||
# Struct not registered (dependency not ready): fall back to Ptr(i8) (same 8-byte slot width),
|
||
# letting AnnotClassName/method dispatch fallbacks preserve semantics; never fail-fast here.
|
||
return mlirlite.Ptr(pool, i8_ty_d)
|
||
# _list_elem_obj_ty_from_struct_name - restore the element object pointer type from a list
|
||
# specialization name (strip rawptr[...], split unions by '|', resolve against registered structs).
|
||
def _list_elem_obj_ty_from_struct_name(self, pool: memhub.MemBuddy*, struct_name: str) -> mlirlite.LLVMType*:
|
||
"""Restore the element object pointer type from a list specialization name; None on failure."""
|
||
if pool is None or struct_name is None: return None
|
||
lb_e: str = string.strchr(struct_name, '[')
|
||
if lb_e is None: return None
|
||
rb_e: str = string.strrchr(struct_name, ']')
|
||
if rb_e is None: return None
|
||
elen_e: sizet = sizet(uint64(rb_e) - uint64(lb_e)) - 1
|
||
if elen_e == 0 or elen_e > 512: return None
|
||
elem_buf_e: str = pool.alloc(elen_e + 1)
|
||
if elem_buf_e is None: return None
|
||
string.strncpy(elem_buf_e, lb_e + 1, elen_e)
|
||
elem_buf_e[elen_e] = '\0'
|
||
# Strip the pointer wrapper (elements declared as object pointers): PtrT[...] / rawptr[...]
|
||
cur_e: str = elem_buf_e
|
||
while string.strncmp(cur_e, "PtrT[", 5) == 0 or string.strncmp(cur_e, "rawptr[", 7) == 0:
|
||
if string.strncmp(cur_e, "PtrT[", 5) == 0: cur_e = cur_e + 5
|
||
else: cur_e = cur_e + 7
|
||
# Split unions by '|', trimming leading/trailing whitespace, trailing ']' and quotes; each
|
||
# segment goes to find_struct (builtin names return None; "sha1.Class" prefix matches by module).
|
||
total_e: sizet = string.strlen(cur_e)
|
||
seg_s_e: sizet = 0
|
||
i_e: sizet = 0
|
||
while i_e <= total_e:
|
||
hit_e: int = 0
|
||
if i_e == total_e: hit_e = 1
|
||
elif cur_e[i_e] == '|': hit_e = 1
|
||
if hit_e == 0:
|
||
i_e += 1
|
||
continue
|
||
b_e: sizet = i_e
|
||
a_e: sizet = seg_s_e
|
||
# Also strip a trailing '*' (normalized element text is `T*`), since find_struct only
|
||
# accepts the class name; otherwise object elements fall back to Ptr(i8), losing the class.
|
||
while b_e > a_e and (cur_e[b_e - 1] == ' ' or cur_e[b_e - 1] == ']' or cur_e[b_e - 1] == '\'' or cur_e[b_e - 1] == '"' or cur_e[b_e - 1] == '*'): b_e -= 1
|
||
while a_e < b_e and (cur_e[a_e] == ' ' or cur_e[a_e] == '\'' or cur_e[a_e] == '"'): a_e += 1
|
||
if b_e > a_e:
|
||
seg_len_e: sizet = b_e - a_e
|
||
if seg_len_e < 256:
|
||
seg_buf_e: str = pool.alloc(seg_len_e + 1)
|
||
if seg_buf_e is not None:
|
||
string.strncpy(seg_buf_e, cur_e + a_e, seg_len_e)
|
||
seg_buf_e[seg_len_e] = '\0'
|
||
s_entry_e: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg_buf_e)
|
||
if s_entry_e is not None and s_entry_e.Ty is not None: return mlirlite.Ptr(pool, s_entry_e.Ty)
|
||
if i_e == total_e: break
|
||
seg_s_e = i_e + 1
|
||
i_e += 1
|
||
return None
|
||
# list_setitem_inline - inline list[T] __setitem__: coerce rhs_val to i8* and store to the element address.
|
||
def list_setitem_inline(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, lm_obj: mlirlite.Value*, idx_val: mlirlite.Value*, rhs_val: mlirlite.Value*, node: ast.AST* = None, trans: HT.Translator* = None) -> int:
|
||
"""Store into a list element through the list's real `set` (growth + count)."""
|
||
if builder is None or pool is None or mod is None or lm_obj is None or idx_val is None or rhs_val is None: return 0
|
||
self.Gen.CallGenCall._emit_list_set_call(pool, builder, mod, lm_obj, idx_val, rhs_val, trans, node)
|
||
return 0
|
||
# is_list_subscript - check whether a Subscript node is list[T]; returns the list object pointer (Ptr(list[T])) or None.
|
||
def is_list_subscript(self, node: ast.AST*, trans: HT.Translator*) -> mlirlite.Value*:
|
||
"""Return the list object pointer when a Subscript is list[T]; None otherwise."""
|
||
if node is None or trans is None: return None
|
||
sub: ast.Subscript* = ast.Subscript*(node)
|
||
if sub is None or sub.value is None: return None
|
||
if sub.value.kind() != ast.ASTKind.Name: return None
|
||
nm: ast.Name* = ast.Name*(sub.value)
|
||
if nm is None or nm.id is None: return None
|
||
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||
if alloca is None or alloca.Ty is None: return None
|
||
if self.is_ptr_type(alloca.Ty) == 0: return None
|
||
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
|
||
if pointee is None: return None
|
||
if self.is_ptr_type(pointee) == 0: return None # value-type list: handled by get_subscript_ptr
|
||
# Unwrap every pointer level: a reference to a list pointer (`&list[T]*`) is still
|
||
# the list object for __setitem__, not an array of list pointers.
|
||
st_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(alloca)
|
||
if st_ty is None: return None
|
||
struct_name: str = None
|
||
match st_ty:
|
||
case mlirlite.LLVMType.Struct(_, _, sname):
|
||
struct_name = sname
|
||
case _:
|
||
return None
|
||
if struct_name is None: return None
|
||
if string.strstr(struct_name, "list[") is None: return None
|
||
# Is a list type: load out the list object pointer (peel reference levels).
|
||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||
obj: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if obj is None: return None
|
||
return self.Gen.StructGen._oop_self_ptr(builder, trans.Pool, obj)
|
||
# _nonlocal_subscript_ptr - element pointer for a nonlocal array/pointer subscript: read the
|
||
# shared heap address from env (_load_raw_var_ptr), bitcast, then GEP (the outer alloca is unusable).
|
||
def _nonlocal_subscript_ptr(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, trans: HT.Translator*, nm_id: str, idx_val: mlirlite.Value*) -> mlirlite.Value*:
|
||
"""Element pointer for a nonlocal array/pointer subscript (env heap addr -> bitcast -> GEP)."""
|
||
if trans is None or nm_id is None or idx_val is None: return None
|
||
nl_ty: mlirlite.LLVMType* = GenApi.get_nonlocal_type(trans, nm_id)
|
||
if nl_ty is None: return None
|
||
raw_nl: mlirlite.Value* = self.Gen.StmtGenDecl._load_raw_var_ptr(trans, nm_id)
|
||
if raw_nl is None: return None
|
||
match nl_ty:
|
||
case mlirlite.LLVMType.Array(elem_ty_nl, _cnt_nl):
|
||
arr_ptr_nl: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl, mlirlite.Ptr(pool, nl_ty))
|
||
if arr_ptr_nl is None: return None
|
||
return mlirlite.build_gep_array(builder, nl_ty, elem_ty_nl, arr_ptr_nl, idx_val)
|
||
case mlirlite.LLVMType.Ptr(inner_nl):
|
||
arr_ptr_nl2: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl, mlirlite.Ptr(pool, nl_ty))
|
||
if arr_ptr_nl2 is None: return None
|
||
return mlirlite.build_gep(builder, inner_nl, arr_ptr_nl2, idx_val)
|
||
case _:
|
||
arr_ptr_nl3: mlirlite.Value* = mlirlite.build_bitcast(builder, raw_nl, mlirlite.Ptr(pool, nl_ty))
|
||
if arr_ptr_nl3 is None: return None
|
||
return mlirlite.build_gep(builder, nl_ty, arr_ptr_nl3, idx_val)
|
||
return None
|
||
# translate_subscript - translate a subscript expression ptr[i] / arr[i]: pointer traversal
|
||
# loads the pointer then GEP + load; arrays use a double-index GEP; list[T] goes through __getitem__.
|
||
def translate_subscript(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Translate a subscript expression; return the loaded element value."""
|
||
if node is None or builder is None: return None
|
||
sub: ast.Subscript* = ast.Subscript*(node)
|
||
if sub is None: return None
|
||
|
||
# Slice form a[start:end] (e.g. path[n:]): return an i8* base pointer plus the start offset.
|
||
# Slices must not be treated as single-index subscripts, or the argument is dropped entirely.
|
||
if sub.slice is not None and sub.slice.kind() == ast.ASTKind.Slice:
|
||
slc: ast.Slice* = ast.Slice*(sub.slice)
|
||
base_v: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.value, None, 0, trans)
|
||
if base_v is None: return None
|
||
if slc.lower is None: return base_v
|
||
start_v: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, slc.lower, None, 0, trans)
|
||
if start_v is None: return None
|
||
i64_sc: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
start_w: mlirlite.Value* = start_v
|
||
if start_v.Ty is not None:
|
||
bt_sc: int = self.Gen.ExprGenBase.get_llvm_type_bits(start_v.Ty)
|
||
if bt_sc != 0 and bt_sc < 64: start_w = mlirlite.build_sext(builder, start_v, i64_sc)
|
||
if start_w is None: return None
|
||
i8_sc: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
return mlirlite.build_gep(builder, i8_sc, base_v, start_w)
|
||
|
||
# Translate the index
|
||
idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.slice, None, 0, trans)
|
||
if idx_val is None: return None
|
||
# Narrow integer subscripts (i8/i16) are zero-extended inside mlirlite.build_gep /
|
||
# build_gep_array (the single source of truth); doing it here would miss bare pointers.
|
||
|
||
# Nested subscript read a[i][j]: recursively take the inner pointer and load, matching
|
||
# get_subscript_ptr's nested branch.
|
||
if sub.value is not None and sub.value.kind() == ast.ASTKind.Subscript:
|
||
inner_ptr_r: mlirlite.Value* = self.get_subscript_ptr(builder, pool, mod, sub.value, trans)
|
||
if inner_ptr_r is None or inner_ptr_r.Ty is None: return None
|
||
inner_elem_r: mlirlite.LLVMType* = inner_ptr_r.Ty.Pointee
|
||
if inner_elem_r is None: return None
|
||
match inner_elem_r:
|
||
case mlirlite.LLVMType.Array(arr_ie_r, _):
|
||
elem_ptr_r: mlirlite.Value* = mlirlite.build_gep_array(builder, inner_elem_r, arr_ie_r, inner_ptr_r, idx_val)
|
||
if elem_ptr_r is not None: return mlirlite.build_load(builder, arr_ie_r, elem_ptr_r)
|
||
return None
|
||
case _:
|
||
elem_ptr_r2: mlirlite.Value* = mlirlite.build_gep(builder, inner_elem_r, inner_ptr_r, idx_val)
|
||
if elem_ptr_r2 is not None: return mlirlite.build_load(builder, inner_elem_r, elem_ptr_r2)
|
||
return None
|
||
|
||
# If value is a Name, try to infer from the alloca type
|
||
if sub.value is not None and sub.value.kind() == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(sub.value)
|
||
if nm.id is not None and trans is not None:
|
||
# rawptr[volatile | T] variable (volatile pointee): element loads (p[i] dereference)
|
||
# carry volatile; loading the pointer value itself does not.
|
||
vol_sub_rd: int = self.Gen.VarGen.var_pointee_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
|
||
# nonlocal array/pointer: read the shared heap address from env (the outer alloca
|
||
# cannot be used); the read path loads the element value from the element pointer.
|
||
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
|
||
nl_ptr_r: mlirlite.Value* = self._nonlocal_subscript_ptr(builder, pool, trans, nm.id, idx_val)
|
||
if nl_ptr_r is not None and nl_ptr_r.Ty is not None:
|
||
nl_elem_ty: mlirlite.LLVMType* = nl_ptr_r.Ty.Pointee
|
||
if nl_elem_ty is not None: return mlirlite.build_load(builder, nl_elem_ty, nl_ptr_r)
|
||
return None
|
||
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||
if self.Gen.VarGen._FreedCheck(alloca) != 0:
|
||
self.Gen.TypeGenErr.fatal_error(sub.value, f"cannot use '{nm.id}': already released (by `del`, a move, or a @life(willFree(...)) call)", "LifetimeError")
|
||
return None
|
||
if alloca is not None and alloca.Ty is not None:
|
||
if self.is_ptr_type(alloca.Ty) != 0:
|
||
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
|
||
if pointee is not None:
|
||
# Do not call pointee.kind() (avoids a cross-module LLVMType.kind reference);
|
||
# use match to inspect the pointee type.
|
||
match pointee:
|
||
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:
|
||
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_byte_order_bswap(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
|
||
# value): the alloca holds the list object, so go straight to list __getitem__.
|
||
if lsn_v is not None:
|
||
if string.strstr(lsn_v, "list[") is not None:
|
||
elem_addr_v: mlirlite.Value* = self.list_getitem_inline(builder, pool, alloca, idx_val, node)
|
||
if elem_addr_v is not None:
|
||
elem_ty_v: mlirlite.LLVMType* = self._list_elem_type_from_name(pool, lsn_v)
|
||
if elem_ty_v is not None:
|
||
elem_ptr_ty_v: mlirlite.LLVMType* = mlirlite.Ptr(pool, elem_ty_v)
|
||
casted_v: mlirlite.Value* = mlirlite.build_bitcast(builder, elem_addr_v, elem_ptr_ty_v)
|
||
if casted_v is not None: return mlirlite.build_load(builder, elem_ty_v, casted_v)
|
||
# 值类型类实例(`d: dict = dict(bd)`):槽里就是对象本身,`d[k]` 必须按
|
||
# 类的 `__getitem__` 调用编译,self 传槽地址(= &对象)。此前这里直接
|
||
# return None,调用方(AnnAssign)又对 None 静默不写,整条语句凭空消失
|
||
# ——现场:p1/p2/p3 全未赋值,随后解引用栈垃圾断言崩溃。
|
||
cls_nm_val: str = self.Gen.StructGen.get_class_name_by_type(pool, pointee)
|
||
if not (cls_nm_val is None):
|
||
gi_lookup_val: str = pool.alloc(128)
|
||
if gi_lookup_val is not None:
|
||
viperlib.snprintf(gi_lookup_val, 128, "%s.__getitem__", cls_nm_val)
|
||
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_val) is not None:
|
||
arg_vals_val: sizet* = pool.alloc(8)
|
||
if not (arg_vals_val is None):
|
||
arg_vals_val[0] = sizet(idx_val)
|
||
ret_val_sub: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_val, "__getitem__", alloca, arg_vals_val, 1, trans, sub)
|
||
if not (ret_val_sub is None): return ret_val_sub
|
||
# 非 list、无 __getitem__ 的结构体值不可下标:按对象大小 GEP 是非法 IR,
|
||
# 明确 fail-fast,绝不静默返回 None(调用方会把整条语句丢掉)。
|
||
self.Gen.TypeGenErr.fatal_error(node, f"cannot subscript the value-type object '{nm.id}' of type '{lsn_v}' (the class defines no __getitem__); use a pointer variable ('T*') or define __getitem__", "TypeError")
|
||
return None
|
||
case mlirlite.LLVMType.Ptr(inner_ty):
|
||
# Check whether inner_ty is list[T]; list subscripts go through __getitem__ (inline), not pointer traversal
|
||
list_struct_name: str = None
|
||
match inner_ty:
|
||
case mlirlite.LLVMType.Struct(_, _, lsn):
|
||
list_struct_name = lsn
|
||
case _:
|
||
pass
|
||
if list_struct_name is not None:
|
||
if string.strstr(list_struct_name, "list[") is not None:
|
||
# list[T] type: inline-generate the __getitem__ logic
|
||
ptr_val_list: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val_list is not None:
|
||
elem_addr_list: mlirlite.Value* = self.list_getitem_inline(builder, pool, ptr_val_list, idx_val, node)
|
||
if elem_addr_list is not None:
|
||
# Extract the element type from the name, bitcast then load
|
||
elem_ty_list: mlirlite.LLVMType* = self._list_elem_type_from_name(pool, list_struct_name)
|
||
if elem_ty_list is not None:
|
||
elem_ptr_ty_list: mlirlite.LLVMType* = mlirlite.Ptr(pool, elem_ty_list)
|
||
casted_addr_list: mlirlite.Value* = mlirlite.build_bitcast(builder, elem_addr_list, elem_ptr_ty_list)
|
||
if casted_addr_list is not None: return mlirlite.build_load(builder, elem_ty_list, casted_addr_list)
|
||
return None
|
||
# inner_ty being Ptr means alloca.Ty is a triple pointer (e.g. T=AST*, T*=AST**),
|
||
# so [i] is a pointer dereference (stride 8), not a struct __getitem__ call.
|
||
if self.is_ptr_type(inner_ty) != 0:
|
||
# OOP priority over pointer arithmetic: a pointer chain that
|
||
# reaches a class dispatches the class __getitem__ (peeling the
|
||
# reference levels), no matter how deep the chain is.
|
||
oop_st: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(alloca)
|
||
if oop_st is not None:
|
||
oop_cls: str = self.Gen.StructGen.get_class_name_by_type(pool, oop_st)
|
||
if oop_cls is not None:
|
||
oop_obj: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if oop_obj is not None: oop_obj = self.Gen.StructGen._oop_self_ptr(builder, pool, oop_obj)
|
||
if oop_obj is not None:
|
||
if string.strstr(oop_cls, "list[") is not None:
|
||
oop_addr: mlirlite.Value* = self.list_getitem_inline(builder, pool, oop_obj, idx_val, node)
|
||
if oop_addr is not None:
|
||
oop_elem_ty: mlirlite.LLVMType* = self._list_elem_type_from_name(pool, oop_cls)
|
||
if oop_elem_ty is not None:
|
||
oop_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, oop_elem_ty)
|
||
oop_cast: mlirlite.Value* = mlirlite.build_bitcast(builder, oop_addr, oop_ptr_ty)
|
||
if oop_cast is not None: return mlirlite.build_load(builder, oop_elem_ty, oop_cast)
|
||
return None
|
||
gi_lookup_oop: str = pool.alloc(128)
|
||
if gi_lookup_oop is not None:
|
||
viperlib.snprintf(gi_lookup_oop, 128, "%s.__getitem__", oop_cls)
|
||
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_oop) is not None:
|
||
oop_args: sizet* = pool.alloc(8)
|
||
if oop_args is not None:
|
||
oop_args[0] = sizet(idx_val)
|
||
oop_ret: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, oop_cls, "__getitem__", oop_obj, oop_args, 1, trans, sub)
|
||
if oop_ret is not None: return oop_ret
|
||
ptr_val_pp: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val_pp is not None:
|
||
elem_ptr_pp: mlirlite.Value* = mlirlite.build_gep(builder, inner_ty, ptr_val_pp, idx_val)
|
||
if elem_ptr_pp is not None:
|
||
# Volatile pointee: dereference load carries volatile
|
||
return mlirlite.build_load(builder, inner_ty, elem_ptr_pp, vol_sub_rd)
|
||
return None
|
||
# Custom struct (e.g. HashTable*, JsonValue*): forward the method call only if
|
||
# the class defines __getitem__; otherwise do a plain GEP array access.
|
||
cls_nm_rd_ptr: str = self._get_custom_struct_cls_nm(pool, inner_ty)
|
||
if not (cls_nm_rd_ptr is None):
|
||
gi_lookup_rd: str = pool.alloc(128)
|
||
if gi_lookup_rd is not None:
|
||
viperlib.snprintf(gi_lookup_rd, 128, "%s.__getitem__", cls_nm_rd_ptr)
|
||
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_rd) is not None:
|
||
obj_ptr_rd: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if not (obj_ptr_rd is None):
|
||
arg_vals_rd_p: sizet* = pool.alloc(8)
|
||
if not (arg_vals_rd_p is None):
|
||
arg_vals_rd_p[0] = sizet(idx_val)
|
||
ret_rd_p: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_rd_ptr, "__getitem__", obj_ptr_rd, arg_vals_rd_p, 1, trans, sub)
|
||
if not (ret_rd_p is None): return ret_rd_p
|
||
# No __getitem__: struct-pointer array access (GEP + load)
|
||
ptr_val_rd_g: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val_rd_g is not None:
|
||
elem_rd_g: mlirlite.Value* = mlirlite.build_gep(builder, inner_ty, ptr_val_rd_g, idx_val)
|
||
if elem_rd_g is not None:
|
||
# Volatile pointee: dereference load carries volatile
|
||
return mlirlite.build_load(builder, inner_ty, elem_rd_g, vol_sub_rd)
|
||
return None
|
||
# bytes*/str*: the alloca is i8**, so GEP with an i8* stride (8 bytes) and load i8*;
|
||
# pointer traversal applies only when inner_ty is not a custom struct.
|
||
if trans is not None:
|
||
ve_pe_ts: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
if self.Gen.VarGen.var_is_ptr_element(ve_pe_ts) != 0:
|
||
# bytes*/str*/T*: the variable holds a pointer value, so elem[i] means
|
||
# load [ptr + i*8]; load the alloca first then GEP with an i8* stride.
|
||
ptr_val_pe_ts: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val_pe_ts is not None:
|
||
elem_ptr_ts: mlirlite.Value* = mlirlite.build_gep(builder, pointee, ptr_val_pe_ts, idx_val)
|
||
if elem_ptr_ts is not None:
|
||
# Volatile pointee: dereference load carries volatile
|
||
return mlirlite.build_load(builder, pointee, elem_ptr_ts, vol_sub_rd)
|
||
return None
|
||
# Plain pointer traversal: load the pointer value, then GEP
|
||
ptr_val: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val is not None:
|
||
elem_ptr2: mlirlite.Value* = mlirlite.build_gep(builder, inner_ty, ptr_val, idx_val)
|
||
if elem_ptr2 is not None:
|
||
# Volatile pointee: dereference load carries volatile
|
||
return mlirlite.build_load(builder, inner_ty, elem_ptr2, vol_sub_rd)
|
||
return None
|
||
case _:
|
||
# Union-typed variable (IsPtrElement=1, e.g. elem_ptr: T* degraded to i8*): pointee
|
||
# is i8 but elements are 8-byte pointers, so GEP/load must use an i8* stride.
|
||
if trans is not None:
|
||
ve_pe_rd: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
if self.Gen.VarGen.var_is_ptr_element(ve_pe_rd) != 0:
|
||
match pointee:
|
||
case mlirlite.LLVMType.Int(bits_rd):
|
||
if bits_rd == 8:
|
||
i8_ptr_rd: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
elem_ptr_rd: mlirlite.Value* = mlirlite.build_gep(builder, i8_ptr_rd, alloca, idx_val)
|
||
if elem_ptr_rd is not None: return mlirlite.build_load(builder, i8_ptr_rd, elem_ptr_rd)
|
||
case _:
|
||
pass
|
||
# Custom class (struct type): forward the method call only if the class defines
|
||
# __getitem__; otherwise do a plain GEP array access on the struct pointer.
|
||
cls_nm_sub: str = self.Gen.StructGen.get_class_name_by_type(pool, pointee)
|
||
if not (cls_nm_sub is None):
|
||
gi_lookup_sub: str = pool.alloc(128)
|
||
if gi_lookup_sub is not None:
|
||
viperlib.snprintf(gi_lookup_sub, 128, "%s.__getitem__", cls_nm_sub)
|
||
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_sub) is not None:
|
||
obj_val_sub: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if not (obj_val_sub is None):
|
||
arg_vals_sub: sizet* = pool.alloc(8)
|
||
if not (arg_vals_sub is None):
|
||
arg_vals_sub[0] = sizet(idx_val)
|
||
ret_sub: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_sub, "__getitem__", obj_val_sub, arg_vals_sub, 1, trans, sub)
|
||
if not (ret_sub is None): return ret_sub
|
||
# No __getitem__: struct-pointer array access (GEP + load)
|
||
ptr_val_sub_g: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val_sub_g is not None:
|
||
elem_sub_g: mlirlite.Value* = mlirlite.build_gep(builder, pointee, ptr_val_sub_g, idx_val)
|
||
if elem_sub_g is not None:
|
||
# Volatile pointee: dereference load carries volatile
|
||
return mlirlite.build_load(builder, pointee, elem_sub_g, vol_sub_rd)
|
||
return None
|
||
pass
|
||
|
||
# Generic path: translate value to obtain the pointer
|
||
ptr_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.value, None, 0, trans)
|
||
if ptr_val is None or ptr_val.Ty is None: return None
|
||
|
||
# Check whether it is a pointer type
|
||
if self.is_ptr_type(ptr_val.Ty) != 0:
|
||
# Generic-path base (e.g. self.ptr / a complex expression): element loads carry volatile only
|
||
# when the base resolves to a VarEntry/FieldEntry marked rawptr[volatile|T]; otherwise 0.
|
||
vol_sub_gen: int = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub.value)
|
||
elem_ty2: mlirlite.LLVMType* = ptr_val.Ty.Pointee
|
||
if elem_ty2 is not None:
|
||
# Array type: double-index GEP + load element
|
||
match elem_ty2:
|
||
case mlirlite.LLVMType.Array(arr_elem_ty2, arr_count2):
|
||
elem_ptr3: mlirlite.Value* = mlirlite.build_gep_array(builder, elem_ty2, arr_elem_ty2, ptr_val, idx_val)
|
||
if elem_ptr3 is not None: return mlirlite.build_load(builder, arr_elem_ty2, elem_ptr3, vol_sub_gen)
|
||
return None
|
||
case _:
|
||
pass
|
||
# 64-bit scalar pointer fields keep the declared i64 element type (a pointer needs a cast);
|
||
# custom structs forward __getitem__ only if defined, else do a plain GEP array access.
|
||
cls_nm_rd_gen: str = self._get_custom_struct_cls_nm(pool, elem_ty2)
|
||
if not (cls_nm_rd_gen is None):
|
||
gi_lookup_gen: str = pool.alloc(128)
|
||
if gi_lookup_gen is not None:
|
||
viperlib.snprintf(gi_lookup_gen, 128, "%s.__getitem__", cls_nm_rd_gen)
|
||
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_gen) is not None:
|
||
arg_vals_rd_g: sizet* = pool.alloc(8)
|
||
if not (arg_vals_rd_g is None):
|
||
arg_vals_rd_g[0] = sizet(idx_val)
|
||
ret_rd_g: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_rd_gen, "__getitem__", ptr_val, arg_vals_rd_g, 1, trans, sub)
|
||
if not (ret_rd_g is None): return ret_rd_g
|
||
# No __getitem__: struct-pointer array access (GEP + load)
|
||
elem_ptr_gen: mlirlite.Value* = mlirlite.build_gep(builder, elem_ty2, ptr_val, idx_val)
|
||
if elem_ptr_gen is not None: return mlirlite.build_load(builder, elem_ty2, elem_ptr_gen, vol_sub_gen)
|
||
return None
|
||
# Pointer type: single-index GEP + load
|
||
elem_ptr4: mlirlite.Value* = mlirlite.build_gep(builder, elem_ty2, ptr_val, idx_val)
|
||
if elem_ptr4 is not None: return mlirlite.build_load(builder, elem_ty2, elem_ptr4, vol_sub_gen)
|
||
return None
|
||
# get_subscript_ptr - get the subscript element pointer (without loading the value) for assignment lhs
|
||
def _annot_is_64bit_scalar(self, annot: str) -> int:
|
||
"""Return 1 when the annotation is a 64-bit scalar | rawptr union (element type Int64)."""
|
||
if annot is None: return 0
|
||
if string.strstr(annot, "int64") is not None: return 1
|
||
if string.strstr(annot, "sizet") is not None: return 1
|
||
return 0
|
||
|
||
# subscript_base_store_kind - read-only base storage classification: 1 = array slot (whole-object
|
||
# assignment allowed), 0 = pointer pointee (caller fail-fast), -1 = undeterminable.
|
||
def subscript_base_store_kind(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> int:
|
||
"""Base storage kind of a subscript: 1=array, 0=pointer, -1=undeterminable."""
|
||
if node is None or trans is None: return -1
|
||
sub_bk: ast.Subscript* = ast.Subscript*(node)
|
||
if sub_bk is None or sub_bk.value is None: return -1
|
||
base_ty_bk: mlirlite.LLVMType* = None
|
||
if sub_bk.value.kind() == ast.ASTKind.Name:
|
||
nm_bk: ast.Name* = ast.Name*(sub_bk.value)
|
||
if nm_bk.id is None: return -1
|
||
alloca_bk: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_bk.id)
|
||
if alloca_bk is None: alloca_bk = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm_bk.id)
|
||
if alloca_bk is None or alloca_bk.Ty is None: return -1
|
||
if self.is_ptr_type(alloca_bk.Ty) == 0: return -1
|
||
base_ty_bk = alloca_bk.Ty.Pointee
|
||
elif sub_bk.value.kind() == ast.ASTKind.Attribute:
|
||
# Single-level obj.field (e.g. self.arr): read-only, non-fatal field type resolution
|
||
fe_bk: GenTypes.FieldEntry* = self.Gen.ExprGenAttr._resolve_attr_field_entry(pool, trans, sub_bk.value)
|
||
if fe_bk is None or fe_bk.Ty is None: return -1
|
||
base_ty_bk = fe_bk.Ty
|
||
else: return -1
|
||
if base_ty_bk is None: return -1
|
||
# The base itself is an array value -> array storage
|
||
if self.is_array_type(base_ty_bk) != 0: return 1
|
||
if self.is_ptr_type(base_ty_bk) != 0:
|
||
inner_bk: mlirlite.LLVMType* = base_ty_bk.Pointee
|
||
if inner_bk is None: return -1
|
||
# Ptr(Array) -> t.ArrayT[T,N] local/field (array storage)
|
||
if self.is_array_type(inner_bk) != 0: return 1
|
||
# Ptr(Struct) -> rawptr[T] (pointer storage)
|
||
if inner_bk.kind() == mlirlite.LLVMType.Struct: return 0
|
||
return -1
|
||
|
||
def get_subscript_ptr(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Return the subscript element pointer for an assignment lhs."""
|
||
if node is None or builder is None: return None
|
||
sub: ast.Subscript* = ast.Subscript*(node)
|
||
if sub is None: return None
|
||
|
||
# Translate the index
|
||
idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.slice, None, 0, trans)
|
||
if idx_val is None: return None
|
||
# Narrow integer subscripts (i8/i16) are zero-extended inside mlirlite.build_gep /
|
||
# build_gep_array (the single source of truth); doing it here would miss bare pointers.
|
||
|
||
# Nested subscript a[i][j] = v: recursively resolve the inner subscript pointer then GEP
|
||
# (the generic translate_value path would load the inner element as a value and fail).
|
||
if sub.value is not None and sub.value.kind() == ast.ASTKind.Subscript:
|
||
inner_ptr: mlirlite.Value* = self.get_subscript_ptr(builder, pool, mod, sub.value, trans)
|
||
if inner_ptr is None or inner_ptr.Ty is None: return None
|
||
inner_elem: mlirlite.LLVMType* = inner_ptr.Ty.Pointee
|
||
if inner_elem is None: return None
|
||
match inner_elem:
|
||
case mlirlite.LLVMType.Array(arr_ie, _):
|
||
return mlirlite.build_gep_array(builder, inner_elem, arr_ie, inner_ptr, idx_val)
|
||
case _:
|
||
return mlirlite.build_gep(builder, inner_elem, inner_ptr, idx_val)
|
||
|
||
# If value is a Name, try to infer from the alloca type
|
||
if sub.value is not None and sub.value.kind() == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(sub.value)
|
||
if nm.id is not None and trans is not None:
|
||
# nonlocal array/pointer: read the shared heap address from env (the outer alloca
|
||
# cannot be used because its SSA number belongs to another function).
|
||
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return self._nonlocal_subscript_ptr(builder, pool, trans, nm.id, idx_val)
|
||
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||
if self.Gen.VarGen._FreedCheck(alloca) != 0:
|
||
self.Gen.TypeGenErr.fatal_error(sub.value, f"cannot use '{nm.id}': already released (by `del`, a move, or a @life(willFree(...)) call)", "LifetimeError")
|
||
return None
|
||
if alloca is not None and alloca.Ty is not None:
|
||
if self.is_ptr_type(alloca.Ty) != 0:
|
||
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
|
||
if pointee is not None:
|
||
# Check the pointee type with match
|
||
match pointee:
|
||
case mlirlite.LLVMType.Array(elem_ty, count):
|
||
# Array traversal
|
||
return mlirlite.build_gep_array(builder, pointee, elem_ty, alloca, idx_val)
|
||
case mlirlite.LLVMType.Ptr(inner_ty):
|
||
# inner_ty being Ptr means alloca.Ty is a triple pointer, so [i]=val is a pointer
|
||
# dereference assignment, not a __setitem__ call.
|
||
if self.is_ptr_type(inner_ty) != 0:
|
||
ptr_val_gsp: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val_gsp is not None: return mlirlite.build_gep(builder, inner_ty, ptr_val_gsp, idx_val)
|
||
return None
|
||
# Custom struct (e.g. HashTable*, JsonValue*): return None (triggering the
|
||
# __setitem__ overload) only if the class defines __setitem__; else plain GEP.
|
||
cls_nm_ptr_chk: str = self._get_custom_struct_cls_nm(pool, inner_ty)
|
||
if not (cls_nm_ptr_chk is None):
|
||
si_lookup_gsp: str = pool.alloc(128)
|
||
if si_lookup_gsp is not None:
|
||
viperlib.snprintf(si_lookup_gsp, 128, "%s.__setitem__", cls_nm_ptr_chk)
|
||
if self.Gen.CallGenBase.find_func_in_module(mod, si_lookup_gsp) is not None: return None
|
||
# bytes*/str*: the alloca is i8**, so GEP with an i8* stride (8 bytes) without loading;
|
||
# pointer traversal applies only when inner_ty is not a custom struct.
|
||
if trans is not None:
|
||
ve_pe: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
if self.Gen.VarGen.var_is_ptr_element(ve_pe) != 0:
|
||
# bytes*/str*/T*: the variable holds a pointer value, so the subscript
|
||
# means [ptr + i*8]; load the alloca then GEP, or the write misses the data.
|
||
ptr_val_pe: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val_pe is not None:
|
||
# 64-bit scalar union (int64*/uint64*/sizet*): the 8-byte cell holds an i64 scalar,
|
||
# so GEP must use Int64, not a pointer, or the store fails-fast as int->ptr.
|
||
pe_annot: str = self.Gen.VarGen.var_annot_class_name(ve_pe)
|
||
if pe_annot is not None and self._annot_is_64bit_scalar(pe_annot) != 0: return mlirlite.build_gep(builder, mlirlite.Int64(pool), ptr_val_pe, idx_val)
|
||
match pointee:
|
||
case mlirlite.LLVMType.Int(bits_pe):
|
||
if bits_pe == 8:
|
||
i8_ptr_pe: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
return mlirlite.build_gep(builder, i8_ptr_pe, ptr_val_pe, idx_val)
|
||
case _:
|
||
pass
|
||
return mlirlite.build_gep(builder, pointee, ptr_val_pe, idx_val)
|
||
return None
|
||
# Plain pointer traversal: load the pointer value, then GEP
|
||
ptr_val: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val is None: return None
|
||
gep_r: mlirlite.Value* = mlirlite.build_gep(builder, inner_ty, ptr_val, idx_val)
|
||
return gep_r
|
||
case mlirlite.LLVMType.Struct(_fs_gsp, _fc_gsp, _lsn_gsp):
|
||
# 值类型类实例(`d: dict = dict(bd)`):槽里是对象本身,`d[k] = v`
|
||
# 只能走类的 `__setitem__`。定义了 __setitem__ 时返回 None,交由
|
||
# 赋值路径的 __setitem__ 重载分支(self 用槽地址);否则按对象数组
|
||
# GEP 会生成非法 IR,明确 fail-fast 而不是静默。
|
||
cls_nm_val_gsp: str = self.Gen.StructGen.get_class_name_by_type(pool, pointee)
|
||
if not (cls_nm_val_gsp is None):
|
||
si_lookup_val: str = pool.alloc(128)
|
||
if si_lookup_val is not None:
|
||
viperlib.snprintf(si_lookup_val, 128, "%s.__setitem__", cls_nm_val_gsp)
|
||
if self.Gen.CallGenBase.find_func_in_module(mod, si_lookup_val) is not None: return None
|
||
self.Gen.TypeGenErr.fatal_error(node, f"cannot index the value-type object '{nm.id}' of type '{_lsn_gsp}' (the class defines no __setitem__); use a pointer variable ('T*') or define __setitem__", "TypeError")
|
||
return None
|
||
case _:
|
||
# Plain scalar pointee (e.g. i64/i32/i8): GEP directly to the idx-th element; union type
|
||
# (IsPtrElement=1, pointee i8 but 8-byte pointer elements) uses an i8* stride instead.
|
||
if trans is not None:
|
||
ve_pe_cs: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
if self.Gen.VarGen.var_is_ptr_element(ve_pe_cs) != 0:
|
||
# T* (degraded to i8*, e.g. _list.vp elem_ptr) holds a data pointer whose elements
|
||
# are 8-byte pointers: load the alloca then GEP with an i8* stride, or the write misses.
|
||
ptr_val_cs: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||
if ptr_val_cs is not None:
|
||
match pointee:
|
||
case mlirlite.LLVMType.Int(bits_cs):
|
||
if bits_cs == 8:
|
||
i8_ptr_cs: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
return mlirlite.build_gep(builder, i8_ptr_cs, ptr_val_cs, idx_val)
|
||
case _:
|
||
pass
|
||
return mlirlite.build_gep(builder, pointee, ptr_val_cs, idx_val)
|
||
return None
|
||
return mlirlite.build_gep(builder, pointee, alloca, idx_val)
|
||
|
||
# Generic path
|
||
ptr_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub.value, None, 0, trans)
|
||
if ptr_val is None or ptr_val.Ty is None: return None
|
||
if self.is_ptr_type(ptr_val.Ty) != 0:
|
||
elem_ty2: mlirlite.LLVMType* = ptr_val.Ty.Pointee
|
||
if elem_ty2 is not None:
|
||
# Array type: double-index GEP (getelementptr [N x ty], ptr, i32 0, i32 idx)
|
||
match elem_ty2:
|
||
case mlirlite.LLVMType.Array(arr_elem_ty, arr_count):
|
||
return mlirlite.build_gep_array(builder, elem_ty2, arr_elem_ty, ptr_val, idx_val)
|
||
case _:
|
||
pass
|
||
# Custom struct (e.g. self._ht[key]): return None (triggering the HandlesAssign __setitem__
|
||
# overload) only if the class defines __setitem__; otherwise do a plain GEP array access.
|
||
cls_nm_gen: str = self._get_custom_struct_cls_nm(pool, elem_ty2)
|
||
if not (cls_nm_gen is None):
|
||
si_lookup_gen: str = pool.alloc(128)
|
||
if si_lookup_gen is not None:
|
||
viperlib.snprintf(si_lookup_gen, 128, "%s.__setitem__", cls_nm_gen)
|
||
if self.Gen.CallGenBase.find_func_in_module(mod, si_lookup_gen) is not None: return None
|
||
# Pointer type: single-index GEP (getelementptr ty, ptr, idx)
|
||
return mlirlite.build_gep(builder, elem_ty2, ptr_val, idx_val)
|
||
return None |