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