Files
ViperC/App/lib/core/SelfBuild.vp
T
TermiNexus 4bf04c2452 feat(vpc): binary .vpb stub companion and str->char* migration in call gen
- add VpiStub: fixed-layout blob written next to every .vpi (dual write at all
  writers, Ensure() backfills a missing or stale companion on cache reuse)
- serve arity/signature queries for top-level functions from the blob, with a
  silent fallback to the text scanner (methods, varargs, duplicates, corrupt file)
- prefer the injected declare over the text arity in the cross-module check
- CDefine/type text indexes and in-place SSA renumbering and bulk appends
- WIP: char* conversion in CallGenBase and CallGenLeafKit
2026-10-31 16:25:18 +08:00

308 lines
12 KiB
Plaintext

from stdint import *
import stdio
import stdlib
import string
import viperlib
import os
import os.path
import win32.win32base
import win32.win32file
import win32.win32process
import lib.Phase.BuildPipeline as BuildPipeline
define SELF_EXE_CAP = 4096
define SELF_BUILD_DIR = "__vpcbuild_2__"
define COPY_DELAY_SECONDS = 5
def _GetSelfExe() -> str:
buf: bytes = stdlib.malloc(sizet(SELF_EXE_CAP))
if buf is None:
return None
n: uint32 = win32.win32file.GetModuleFileNameA(None, buf, uint32(SELF_EXE_CAP))
if int(n) <= 0:
stdlib.free(buf)
return None
ni: int = int(n)
if ni >= SELF_EXE_CAP:
ni = SELF_EXE_CAP - 1
buf[ni] = 0
return str(buf)
def _TryRoot(dir: str) -> str:
if dir is None or len(dir) == 0:
return None
root: str = os.path.normpath(dir)
if root is None:
return None
project: str = os.path.join(root, "project.vpj")
copyself_project: str = os.path.join(os.path.join(root, "copyself"), "project.vpj")
if project is not None and copyself_project is not None:
if os.isfile(project) and os.isfile(copyself_project):
return root
return None
def _FindViperCRoot(self_exe: str) -> str:
base: str = os.path.dirname(self_exe)
if base is None:
base = ""
parent1: str = os.path.dirname(base)
parent2: str = os.path.dirname(parent1)
# Executable-derived candidates first: cwd is normally the workspace root,
# not the compiler tree.
root: str = _TryRoot(base)
if root is not None:
return root
root = _TryRoot(parent1)
if root is not None:
return root
root = _TryRoot(parent2)
if root is not None:
return root
root = _TryRoot(os.path.join(base, "ViperC"))
if root is not None:
return root
root = _TryRoot(os.path.join(base, "../ViperC"))
if root is not None:
return root
root = _TryRoot(os.path.join(base, "../../ViperC"))
if root is not None:
return root
# Fallback only when the executable is detached from the compiler tree.
cwd: str = os.getcwd()
root = _TryRoot(cwd)
if root is not None:
return root
if cwd is not None:
root = _TryRoot(os.path.join(cwd, "ViperC"))
if root is not None:
return root
root = _TryRoot(os.path.join(os.path.dirname(cwd), "ViperC"))
if root is not None:
return root
return None
def CollectForwardedArgs(argc: int, argv: str*) -> str:
"""--build-self: argv minus --build-self / --project / --build-dir (and their values).
No whitelist: any current or future flag is forwarded verbatim, so a self-build can
enable the same modes as a normal build (e.g. `viperc --build-self --spend-file p.txt
--vpf 3`). The three excluded entries are the ones the self-build invocation owns.
"""
if argv is None or argc <= 1: return ""
total: sizet = 1
i: int = 1
while i < argc:
a: str = argv[i]
if a is not None:
if string.strcmp(a, "--build-self") == 0:
i += 1
continue
if string.strcmp(a, "--project") == 0 or string.strcmp(a, "--build-dir") == 0:
i += 2
continue
total += len(a) + 1
i += 1
buf: str = stdlib.malloc(total + 1)
if buf is None: return ""
buf[0] = '\0'
i = 1
while i < argc:
a2: str = argv[i]
if a2 is not None:
if string.strcmp(a2, "--build-self") == 0:
i += 1
continue
if string.strcmp(a2, "--project") == 0 or string.strcmp(a2, "--build-dir") == 0:
i += 2
continue
if buf[0] != '\0': string.strcat(buf, " ")
string.strcat(buf, a2)
i += 1
return buf
def _FormatSelfBuildCommand(self_exe: str, project: str, build_dir: str, extra: str) -> str:
# `extra` is the caller's forwarded argument string (empty when nothing was forwarded):
# the rebuilt compiler then carries exactly the modes the user asked for.
extra_len: sizet = 0
if extra is not None: extra_len = len(extra)
need: sizet = len(self_exe) + len(project) + len(build_dir) + extra_len + 64
buf: bytes = stdlib.malloc(need)
if buf is None:
return None
viperlib.snprintf(buf, need, "\"%s\" --project \"%s\" --clean --build-dir \"%s\"",
self_exe, project, build_dir)
if extra is not None and extra_len > 0:
pos: sizet = string.strlen(str(buf))
viperlib.snprintf(buf + pos, need - pos, " %s", extra)
return str(buf)
def _FindCopySelfExe(root: str) -> str:
base: str = os.path.join(root, "copyself")
standard: str = os.path.join(os.path.join(os.path.join(base, "__vpcbuild__"), "output"), "copyself.exe")
if os.isfile(standard):
return standard
legacy: str = os.path.join(os.path.join(base, "output"), "copyself.exe")
if os.isfile(legacy):
return legacy
return None
def _FormatCopySelfCommand(copyself_exe: str) -> str:
need: sizet = len(copyself_exe) + 128
buf: bytes = stdlib.malloc(need)
if buf is None:
return None
viperlib.snprintf(buf, need, "\"%s\" %d ../../__vpcbuild_2__ ../../__vpcbuild__",
copyself_exe, COPY_DELAY_SECONDS)
return str(buf)
def _SpawnDetached(cmd: str, cwd: str) -> int:
if cmd is None or cwd is None:
stdio.printf("FAIL: --build-self cannot launch copyself without a command and working directory\n")
return -1
clen: sizet = len(cmd)
cmd_line: bytes = stdlib.malloc(clen + 1)
if cmd_line is None:
stdio.printf("FAIL: --build-self failed to allocate the copyself command line\n")
return -1
string.strcpy(cmd_line, cmd)
si: win32.win32process.STARTUPINFOA = win32.win32process.STARTUPINFOA()
pi: win32.win32process.PROCESS_INFORMATION = win32.win32process.PROCESS_INFORMATION()
string.memset(&si, 0, win32.win32process.STARTUPINFOA.__sizeof__())
string.memset(&pi, 0, win32.win32process.PROCESS_INFORMATION.__sizeof__())
si.cb = win32.win32process.STARTUPINFOA.__sizeof__()
ok: int = win32.win32process.CreateProcessA(None, cmd_line, None, None, 1, 0,
None, cwd, &si, &pi)
if ok == 0:
err: uint32 = win32.win32base.GetLastError()
stdio.printf("FAIL: --build-self could not launch copyself (Win32 error %d)\n", int(err))
stdio.printf(" command: %s\n", cmd)
stdlib.free(cmd_line)
return -1
stdio.printf("build-self: copyself launched (pid=%d)\n", int(pi.dwProcessId))
win32.win32base.CloseHandle(pi.hProcess)
win32.win32base.CloseHandle(pi.hThread)
stdlib.free(cmd_line)
return 0
# _SelfExeDir - directory containing the running compiler ("D:/x/viperc.exe" -> "D:/x").
def _SelfExeDir(self_exe: str) -> str:
if self_exe is None: return None
n: sizet = string.strlen(self_exe)
cut: sizet = n
while cut > 0:
ch: char = self_exe[cut - 1]
if ch == '\\' or ch == '/':
cut -= 1
break
cut -= 1
if cut >= n: return None
out: str = stdlib.malloc(cut + 1)
if out is None: return None
if cut > 0: string.strncpy(out, self_exe, cut)
out[cut] = '\0'
return out
# _CrashHint - name the common NTSTATUS exit codes. The child inherits the parent console, so
# its own output is already visible; these codes are raised as fail-fast and carry no SEH
# exception, which is why a traceback handler inside the child cannot print a stack.
def _CrashHint(rc: int) -> None:
u: uint32 = uint32(rc)
if u == uint32(0xC0000005):
stdio.printf(" child crashed: access violation (0xC0000005)\n")
elif u == uint32(0xC0000374):
stdio.printf(" child crashed: heap corruption (0xC0000374) -- fail-fast, no SEH traceback\n")
elif u == uint32(0xC00000FD):
stdio.printf(" child crashed: stack overflow (0xC00000FD)\n")
elif u == uint32(0xC0000409):
stdio.printf(" child crashed: fail-fast / security check (0xC0000409)\n")
def RunSelfBuild(extra: str) -> int:
self_exe: str = _GetSelfExe()
if self_exe is None:
stdio.printf("FAIL: --build-self could not resolve the compiler executable path\n")
return 1
root: str = _FindViperCRoot(self_exe)
if root is None:
stdio.printf("FAIL: --build-self could not locate the ViperC root\n")
stdio.printf(" expected both: project.vpj and copyself/project.vpj\n")
return 1
project: str = os.path.join(root, "project.vpj")
build_dir: str = os.path.join(root, SELF_BUILD_DIR)
copyself_exe: str = _FindCopySelfExe(root)
if copyself_exe is None:
stdio.printf("FAIL: copyself.exe not found under %s\n", os.path.join(root, "copyself"))
stdio.printf(" expected: output/copyself.exe or __vpcbuild__/output/copyself.exe\n")
stdio.printf(" build it first: viperc --project %s --clean\n",
os.path.join(os.path.join(root, "copyself"), "project.vpj"))
return 1
cmd: str = _FormatSelfBuildCommand(self_exe, project, build_dir, extra)
if cmd is None:
stdio.printf("FAIL: --build-self failed to build the compiler command line\n")
return 1
stdio.printf("build-self: %s\n", cmd)
rc: int = BuildPipeline._run_cmd_wait(cmd)
if rc != 0:
stdio.printf("FAIL: --build-self compilation failed (exit=%d)\n", rc)
_CrashHint(rc)
return 1
built_exe: str = os.path.join(os.path.join(build_dir, "output"), "viperc.exe")
if not os.isfile(built_exe):
stdio.printf("FAIL: --build-self did not produce %s\n", built_exe)
return 1
# The requested arguments are relative to ViperC/copyself/output. Use that
# directory as the launch cwd even when copyself.exe lives under
# copyself/__vpcbuild__/output.
copyself_cwd: str = os.path.join(os.path.join(root, "copyself"), "output")
if not os.isdir(copyself_cwd):
if os.mkdir(copyself_cwd) != 0:
if not os.isdir(copyself_cwd):
err_mkdir: ULONG = win32.win32base.GetLastError()
stdio.printf("FAIL: --build-self could not create %s (Win32 error %d)\n",
copyself_cwd, int(err_mkdir))
return 1
# copyself replaces ViperC\__vpcbuild__ wholesale, so everything the compiler needs next to
# viperc.exe must be in the new output directory *before* the copy runs. ircore.dll is the
# important one: without it every PhaseB module silently falls back to spawning
# mlir-opt/mlir-translate/llc (3 processes per module), which is both slower and a
# different code path than the one being profiled.
self_dir: str = _SelfExeDir(self_exe)
if self_dir is not None:
src_dll: str = os.path.join(self_dir, "ircore.dll")
dst_dll: str = os.path.join(os.path.join(build_dir, "output"), "ircore.dll")
if os.isfile(src_dll) and not os.isfile(dst_dll):
cop: BOOL = win32.win32file.CopyFileA(src_dll, dst_dll, 0)
if cop == 0:
stdio.printf("build-self: warning: could not copy ircore.dll next to the new viperc.exe\n")
else:
stdio.printf("build-self: copied ircore.dll into the new output directory\n")
copy_cmd: str = _FormatCopySelfCommand(copyself_exe)
if copy_cmd is None:
stdio.printf("FAIL: --build-self failed to build the copyself command line\n")
return 1
if _SpawnDetached(copy_cmd, copyself_cwd) != 0:
return 1
stdio.printf("build-self: __vpcbuild__ will be replaced after %d seconds\n", COPY_DELAY_SECONDS)
return 0