- 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
308 lines
12 KiB
Plaintext
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
|