Files
ViperC/App/lib/Stub/StubMerger/StubMergerIR.vp
T

236 lines
11 KiB
Plaintext

import lib.core.Gen.GenApi as GenApi
from stdint import *
import memhub
import string
import stdlib
import mlirlite
import win32.win32file as win32file
import win32.win32base as win32base
import win32.fileio as fileio
import viperlib
import sys
import ast
import lib.core.VLogger as VLogger
import lib.core.IncludesScanner as IncludesScanner
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger as StubMerger
# StubMergerIR - module structured sidecar ({sha1}.meta) write/read, submodule checks and module-name/SHA1 mapping helpers
# WriteModuleMeta - write {sha1}.meta (structured sidecar: funcs + struct layout + vtables).
# Lines: FUNC <mangled> DEF|DECL, STRUCT <sha1>.<name> FIELDS <n> PACKED <0|1>|OPAQUE, VTABLE <sha1>.<name> SLOTS <n>.
def WriteModuleMeta(mod: mlirlite.LLVMModule*, temp_dir: str, td_len: sizet, sha1: str) -> int:
"""Write the module's func defines, struct layouts and vtables to {sha1}.meta."""
if mod is None or temp_dir is None or sha1 is None:
VLogger.error("meta write: null argument", "Meta")
return 1
mpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "meta")
if mpath is None:
VLogger.error("meta write: path construction failed", "Meta")
return 1
mf: fileio.File = fileio.File(mpath, fileio.MODE.W)
if mf.closed:
VLogger.error(f"meta write: cannot open {mpath}", "Meta")
return 1
cur: mlirlite.Function* = mod.FuncHead
steps: sizet = 0
while cur is not None:
steps += 1
if steps > 100000:
VLogger.error("meta write: function list has a cycle or exceeded the limit", "Meta")
break
nm: str = mlirlite.function_get_name(cur)
if nm is not None:
nlen: sizet = string.strlen(nm)
line: bytes = stdlib.malloc(nlen + 16)
if line is None:
VLogger.error("meta write: line buffer allocation failed", "Meta")
break
mark: str = "DECL"
if mlirlite.function_is_declared(cur) == 0: mark = "DEF"
viperlib.snprintf(line, nlen + 16, "FUNC %s %s\n", nm, mark)
mf.write(line, string.strlen(line))
stdlib.free(line)
cur = mlirlite.function_get_next(cur)
# 2) Struct layout: STRUCT <name> FIELDS <n> PACKED <0|1> (opaque -> STRUCT <name> OPAQUE)
nt: mlirlite.NamedTypeNode* = mod.NamedTypeHead
nt_steps: sizet = 0
while nt is not None:
nt_steps += 1
if nt_steps > 100000:
VLogger.error("meta write: named-type list has a cycle or exceeded the limit", "Meta")
break
tn: str = mlirlite.get_struct_name(nt.Ty)
if tn is not None and tn[0] != '\0':
tl: sizet = string.strlen(tn)
lb: bytes = stdlib.malloc(tl + 48)
if lb is None:
VLogger.error("meta write: line buffer allocation failed", "Meta")
break
fc: int = mlirlite.get_struct_fcount(nt.Ty)
if fc < 0: viperlib.snprintf(lb, tl + 48, "STRUCT %s OPAQUE\n", tn)
else: viperlib.snprintf(lb, tl + 48, "STRUCT %s FIELDS %d PACKED %d\n", tn, fc, nt.IsPacked)
mf.write(lb, string.strlen(lb))
stdlib.free(lb)
nt = nt.Next
# 3) Vtable slots: VTABLE <class> SLOTS <n> (global <class>.vtable; Ty anonymous struct fcount = slots)
gv: mlirlite.GlobalVariable* = mod.GlobalHead
gv_steps: sizet = 0
while gv is not None:
gv_steps += 1
if gv_steps > 100000:
VLogger.error("meta write: global-variable list has a cycle or exceeded the limit", "Meta")
break
gn: str = gv.Name
if gn is not None:
gnl: sizet = string.strlen(gn)
if gnl > 7 and string.strcmp(gn + gnl - 7, ".vtable") == 0:
base_len: sizet = gnl - 7
base: bytes = stdlib.malloc(base_len + 1)
if base is not None:
string.strncpy(base, gn, base_len)
base[base_len] = '\0'
ar: int = mlirlite.get_struct_fcount(gv.Ty)
if ar < 0: ar = 0
lb2: bytes = stdlib.malloc(base_len + 40)
if lb2 is None: VLogger.error("meta write: line buffer allocation failed", "Meta")
else:
viperlib.snprintf(lb2, base_len + 40, "VTABLE %s SLOTS %d\n", base, ar)
mf.write(lb2, string.strlen(lb2))
stdlib.free(lb2)
stdlib.free(base)
gv = gv.Next
mf.close()
stdlib.free(mpath)
return 0
# _MetaHasDef - read {sha1}.meta to check for a function definition ("FUNC ... DEF" line).
# Returns: 1=has DEF (impl file); 0=readable but no DEF (decl-only); -1=missing/unreadable.
def _MetaHasDef(temp_dir: str, td_len: sizet, sha1: str) -> int:
"""Read {sha1}.meta: 1 if it has a FUNC ... DEF line, 0 if none, -1 if unreadable."""
if temp_dir is None or sha1 is None: return -1
mpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "meta")
if mpath is None: return -1
mf: fileio.File = fileio.File(mpath, fileio.MODE.R)
stdlib.free(mpath)
if mf.closed: return -1
mbuf: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE)
mbr: int64 = 0
if mbuf is not None: mbr = mf.read_all(mbuf, StubMerger.STUB_READ_BUF_SIZE)
mf.close()
if mbuf is None: return -1
if mbr <= 0:
# Empty meta: no DEF, decl-only
stdlib.free(mbuf)
return 0
if mbr < StubMerger.STUB_READ_BUF_SIZE: mbuf[mbr] = '\0'
# Scan line by line: a trailing " DEF" marks an impl file
mpos: sizet = 0
while mpos < mbr:
mls: sizet = mpos
while mpos < mbr:
if mbuf[mpos] == '\n': break
mpos += 1
mll: sizet = mpos - mls
if mpos < mbr: mpos += 1
if mll > 0 and mbuf[mls + mll - 1] == '\r': mll -= 1
if mll >= 10 and string.strncmp(mbuf + mls, "FUNC ", 5) == 0:
if mbuf[mls + mll - 4] == ' ' and string.strncmp(mbuf + mls + mll - 3, "DEF", 3) == 0:
stdlib.free(mbuf)
return 1
stdlib.free(mbuf)
return 0
# IsDeclOnlyModule - whether a module is decl-only (reads the structured definition list in {sha1}.meta).
# 1=decl-only (no FUNC ... DEF, e.g. all-pass modules, no .obj needed); 0=impl; -1=unknown/missing.
def IsDeclOnlyModule(temp_dir: str, td_len: sizet, sha1: str) -> int:
"""Whether the module is decl-only (its meta has no FUNC ... DEF line)."""
h: int = _MetaHasDef(temp_dir, td_len, sha1)
if h < 0: return -1
if h == 1: return 0
return 1
# _is_submodule_of_imported - check whether a module name is a submodule of an imported package.
# deps_buf holds only direct imports; a name with '.' is matched by its top-level package (e.g. "ast.lexer" -> 1).
def _is_submodule_of_imported(deps_buf: str, mod_name: str) -> int:
"""Whether mod_name is a submodule of a package imported in deps_buf."""
if deps_buf is None or mod_name is None: return 0
# Find the first '.' and extract the top-level package name
dot_pos: str = string.strchr(mod_name, 46) # '.' = 46
if dot_pos is None: return 0
pkg_len: sizet = sizet(uint64(dot_pos) - uint64(mod_name))
if pkg_len == 0 or pkg_len >= 256: return 0
# Build the top-level package name (truncated before '.') by temporary in-place
# truncation to avoid an allocation
saved_ch: char = mod_name[pkg_len]
mod_name[pkg_len] = '\0'
result: int = GenApi.is_module_imported(deps_buf, mod_name)
mod_name[pkg_len] = saved_ch
return result
# _BuildIncludesSha1Map - build the sha1 -> module_name map from the global in-memory store.
# Source is the store filled by PopulateSha1MapStore (no _sha1_map.txt read); returns the entry count.
def _BuildIncludesSha1Map(temp_dir: str, td_len: sizet, sha1_list: str, mod_list: str) -> int:
"""Copy the sha1 to module_name map from the global store into caller arrays."""
if sha1_list is None or mod_list is None: return 0
if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None: return 0
count: int = 0
for i in range(int(StubMerger._sha1_store_count)):
if count >= StubMerger.MAX_INCLUDES: break
idx: sizet = sizet(i) * 17
idx2: sizet = sizet(i) * 64
string.strcpy(sha1_list + sizet(count) * 17, StubMerger._sha1_store_arr + idx)
string.strcpy(mod_list + sizet(count) * 64, StubMerger._sha1_store_mod + idx2)
count += 1
return count
# _FindSha1ByModName - look up a SHA1 by module name (exact match) for dependency graph analysis.
# sha1_arr holds 17-byte entries, mod_arr 64-byte names; returns a pointer into sha1_arr, or None.
def _FindSha1ByModName(sha1_arr: str, mod_arr: str, count: int, mod_name: str) -> str:
"""Look up a SHA1 by module name, falling back to the .__init__ package form."""
if sha1_arr is None or mod_arr is None or mod_name is None: return None
if count <= 0: return None
# Pass 1: exact match
for i in range(int(count)):
idx: sizet = sizet(i) * 64
# Fast path: strcmp equality implies equal first char, so skip strcmp when it differs
# (hot spot: this function strcmp-scans the whole table per lookup).
if mod_arr[idx] == mod_name[0] and string.strcmp(mod_arr + idx, mod_name) == 0:
sidx: sizet = sizet(i) * 17
return sha1_arr + sidx
# Pass 2: package fallback ("ast" -> "ast.__init__")
nm_len: sizet = string.strlen(mod_name)
init_buf: bytes = stdlib.malloc(nm_len + 12)
if init_buf is not None:
viperlib.snprintf(init_buf, nm_len + 12, "%s.__init__", mod_name)
for i in range(int(count)):
idx2: sizet = sizet(i) * 64
if mod_arr[idx2] == init_buf[0] and string.strcmp(mod_arr + idx2, init_buf) == 0:
sidx2: sizet = sizet(i) * 17
stdlib.free(init_buf)
return sha1_arr + sidx2
stdlib.free(init_buf)
return None
# _AddSha1ToSet - add a SHA1 to a set (deduped; count unchanged if already present).
# set_buf holds 17-byte entries; returns the new count.
def _AddSha1ToSet(set_buf: str, count: int, sha1: str) -> int:
"""Add a SHA1 to the set (deduped) and return the new count."""
if set_buf is None or sha1 is None: return count
for i in range(int(count)):
idx: sizet = sizet(i) * 17
if string.strcmp(set_buf + idx, sha1) == 0: return count
if count < StubMerger.MAX_INCLUDES_SHA1:
idx: sizet = sizet(count) * 17
string.strcpy(set_buf + idx, sha1)
return count + 1
return count