Files
ViperC/App/lib/core/Gen/Expr/ExprGenName.vp
T

954 lines
73 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 CDefine 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 a @life(willFree(...)) call", "LifetimeError")
# CDefine compile-time constant: return the constant (float -> double, int -> i32).
# Existing variables win over CDefine 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 CDefine (e.g. ZLIB_VERSION: t.CDefine = "1.3.2"): return a global
# string (i8*) for `return ZLIB_VERSION`.
cdef_sv: str = self.Gen.TypeGenCDef.lookup_cdefine_string(nm_id)
if cdef_sv is not None: return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, cdef_sv, trans)
self.Gen.TypeGenErr.fatal_error(node, f"CDefine 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 CDefine: 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 CDefine 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:
if self.Gen.TypeGenCDef.is_cdefine_str() != 0:
cdef_sv2: str = self.Gen.TypeGenCDef.lookup_cdefine_string(nm_id)
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"CDefine string value missing: {nm_id}", "InternalCompilerError")
# Cross-module float CDefine is cached as double; return a float constant
# rather than a truncated integer.
dchk_nm: double = self.Gen.TypeGenCDef.lookup_cdefine_double(nm_id)
# 同本模块路径: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 CDefine)
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)
# CDefine 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
# t.CVolatile 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 (e.g. local `c` after `import t, c`) 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
# t.CVolatile local: load carries volatile
vol_l: int = 0
bo_l: int = 0
if trans is not None:
ve_l: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_id)
vol_l = self.Gen.VarGen.var_is_volatile(ve_l)
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 CDefine (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, "CIf") == 0 or string.strcmp(nm_id, "CIfdef") == 0 or string.strcmp(nm_id, "CIfndef") == 0 or string.strcmp(nm_id, "CElif") == 0 or string.strcmp(nm_id, "CElse") == 0 or string.strcmp(nm_id, "CEndif") == 0:
# The c.CIf-family spellings were removed; report the replacement.
self.Gen.TypeGenErr.fatal_error(node, "c.CIf/c.CIfdef/c.CIfndef/c.CElif/c.CElse/c.CEndif were removed; use the cif/celif/celse statements and the defined(NAME) predicate instead (e.g. `cif defined(NAME):` ... `celse:` ...)", "NameError")
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/CPtr) -> 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[CPtr]") 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): CPtr[...] / rawptr[...]
cur_e: str = elem_buf_e
while string.strncmp(cur_e, "CPtr[", 5) == 0 or string.strncmp(cur_e, "rawptr[", 7) == 0:
if string.strncmp(cur_e, "CPtr[", 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*, lm_obj: mlirlite.Value*, idx_val: mlirlite.Value*, rhs_val: mlirlite.Value*, node: ast.AST* = None) -> int:
"""Inline list __setitem__; returns 0."""
if builder is None or pool is None or lm_obj is None or idx_val is None or rhs_val is None: return 0
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)
s_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
s_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, s_idx0)
if s_dpp is None: return 0
s_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, s_dpp)
# Load __elem_size__ (offset 32, index 4)
s_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
s_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, s_idx4)
if s_epp is None: return 0
s_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, s_epp)
if s_data is None or s_esize is None: return 0
s_idx_i64: mlirlite.Value* = self.coerce_to_type(builder, idx_val, i64_ty, node)
if s_idx_i64 is None: return 0
s_offset: mlirlite.Value* = mlirlite.build_mul(builder, s_idx_i64, s_esize)
if s_offset is None: return 0
s_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s_data, s_offset)
if s_addr is None: return 0
# Element type T from the list specialization name (list[int]->i32, list[str]->i8*); a fixed
# i8* store would overwrite adjacent elements (CallGenMixin list branch, ListTest pop FAILED).
s_sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(lm_obj)
s_name: str = None
if s_sty is not None: s_name = mlirlite.get_struct_name(s_sty)
s_elem_ty: mlirlite.LLVMType* = None
if s_name is not None: s_elem_ty = self._list_elem_type_from_name(pool, s_name)
if s_elem_ty is None: s_elem_ty = i8_ptr_ty
s_val_ptr: mlirlite.Value* = self.coerce_to_type(builder, rhs_val, s_elem_ty, node)
if s_val_ptr is None: return 0
s_ep: mlirlite.Value* = mlirlite.build_bitcast(builder, s_addr, mlirlite.Ptr(pool, s_elem_ty))
if s_ep is not None: mlirlite.build_store(builder, s_val_ptr, s_ep)
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
# Check whether pointee is Ptr(Struct)
inner_ty: mlirlite.LLVMType* = None
match pointee:
case mlirlite.LLVMType.Ptr(it):
inner_ty = it
case _:
return None
if inner_ty is None: return None
# Check whether inner_ty is a Struct whose name contains "list["
struct_name: str = None
match inner_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
builder: mlirlite.IRBuilder* = trans._cur_builder
return mlirlite.build_load(builder, pointee, alloca)
# _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[t.CVolatile | 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 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:
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[t.CVolatile|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 | t.CPtr union (element type Int64)."""
if annot is None: return 0
if string.strstr(annot, "CInt64T") is not None: return 1
if string.strstr(annot, "CUInt64T") is not None: return 1
if string.strstr(annot, "CSizeT") 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.CArray[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 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