954 lines
73 KiB
Plaintext
954 lines
73 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 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 |