diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index 88f12b0..c6b70c4 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -80,12 +80,13 @@ _ircore_lib: win32.win32base.HANDLE = None _ircore_ready: int = 0 # ircore_mlir_to_obj_opts(mlir, len, triple, cpu, features, opt, reloc, flags, ...): # flags carry ASan instrumentation / frame pointers, so one entry point covers every mode. -_ircore_opts_fn: callable[[char*, sizet, char*, char*, char*, int, int, int, char**, sizet*, char**, sizet*], int] = None +_ircore_raw_fn: callable[[char*, sizet, char*, sizet, char*, char*, char*, int, int, int, char**, sizet*, char**, sizet*], int] = None _ircore_free_fn: callable[[char*], int] = None # IRCORE_FLAG_* (see IRCore/irffi.h) define IRCORE_FLAG_ASAN = 1 define IRCORE_FLAG_FRAME_POINTER = 2 +define IRCORE_FLAG_INLINE = 4 define IRCORE_OPT_DEFAULT_VALUE = 2 define IRCORE_RELOC_PIC_VALUE = 2 @@ -99,7 +100,7 @@ extern def SetDllDirectoryA(lpPathName: LPCSTR) -> BOOL def _ircore_load() -> int: """Load ircore.dll and resolve its entry points; 1 = available, 0 = not available.""" - global _ircore_lib, _ircore_opts_fn, _ircore_free_fn + global _ircore_lib, _ircore_raw_fn, _ircore_free_fn if _ircore_lib is not None: return 1 h: win32.win32base.HANDLE = win32.win32base.LoadLibraryA("ircore.dll") if h is None: @@ -108,12 +109,12 @@ def _ircore_load() -> int: h = win32.win32base.LoadLibraryA("ircore.dll") if h is None: return 0 - p_opts: win32.win32base.VOIDPTR = win32.win32base.GetProcAddress(h, "ircore_mlir_to_obj_opts") + p_raw: win32.win32base.VOIDPTR = win32.win32base.GetProcAddress(h, "ircore_mlir_to_obj_raw") p_free: win32.win32base.VOIDPTR = win32.win32base.GetProcAddress(h, "ircore_free") - if p_opts is None or p_free is None: + if p_raw is None or p_free is None: win32.win32base.FreeLibrary(h) return 0 - _ircore_opts_fn = p_opts + _ircore_raw_fn = p_raw _ircore_free_fn = p_free _ircore_lib = h return 1 @@ -519,49 +520,80 @@ def write_ir_to_file(ir_buf: bytes, ir_len: sizet, output_dir: str, module_name: # _ircore_compile_module - PhaseB through ircore.dll: one in-process MLIR -> object call # replaces mlir-opt + mlir-translate + llc. Returns 0 on success, non-zero on failure. # The worker context: only stdlib.malloc buffers, no MemBuddy allocation. +# _ircore_sibling - "", stdlib-allocated: worker threads must +# not touch MemBuddy (_ir_sibling_path allocates from it). +def _ircore_sibling(ir_path: str, suffix: str) -> str: + if ir_path is None or suffix is None: return None + n: sizet = len(ir_path) + if n < 3: return None + sl: sizet = len(suffix) + out: str = stdlib.malloc(n + sl + 1) + if out is None: return None + string.strncpy(out, ir_path, n - 3) + out[n - 3] = '\0' + string.strcat(out, suffix) + return out + + +# _ircore_read_whole - read a whole file into a stdlib buffer; 1 = ok (caller frees buf[0]). +def _ircore_read_whole(path: str, buf: bytes*, n: sizet*) -> int: + if buf is not None: buf[0] = None + if n is not None: n[0] = 0 + f: io.File* = io.File(path, io.MODE.R) + if f.closed: return 0 + sz: LONGLONG = f.size() + if sz <= 0: + f.close() + return 0 + # read_all() keeps the last byte for the NUL, so ask for sz+1 and expect sz bytes. + b: bytes = stdlib.malloc(sizet(sz) + 1) + if b is None: + f.close() + return 0 + got: LONG = f.read_all(b, sizet(sz) + 1) + f.close() + if got != LONG(sz): + stdlib.free(b) + return 0 + buf[0] = b + n[0] = sizet(sz) + return 1 + + def _ircore_compile_module(ir_path: str, obj_path: str, module_name: str) -> int: if ir_path is None or obj_path is None: return -1 - rf: io.File* = io.File(ir_path, io.MODE.R) - if rf.closed: - VLogger.warning(f"ircore: cannot open module {ir_path}", "PhaseB") - return -1 - fsize: LONGLONG = rf.size() - rf.close() - if fsize <= 0: - VLogger.warning(f"ircore: empty module {ir_path}", "PhaseB") + src: bytes = None + src_len: sizet = 0 + if _ircore_read_whole(ir_path, &src, &src_len) == 0: + VLogger.warning(f"ircore: cannot read module {ir_path}", "PhaseB") return -1 - # read_all() keeps the last byte for the NUL, so ask for fsize+1 and expect fsize bytes. - src: bytes = stdlib.malloc(sizet(fsize) + 1) - if src is None: - VLogger.warning(f"ircore: module buffer allocation failed: {ir_path}", "PhaseB") - return -1 - rf = io.File(ir_path, io.MODE.R) - if rf.closed: - stdlib.free(src) - VLogger.warning(f"ircore: cannot reopen module {ir_path}", "PhaseB") - return -1 - got: LONG = rf.read_all(src, sizet(fsize) + 1) - rf.close() - if got != LONG(fsize): - stdlib.free(src) - VLogger.warning(f"ircore: short read on {ir_path} ({got}/{fsize})", "PhaseB") - return -1 + # Raw IR sidecars (__llvmir__): the DLL links them and runs always-inline,globaldce, so the + # legacy llvm-link + opt steps disappear. The .inline marker asks for that pass run alone. + raw_buf: bytes = None + raw_len: sizet = 0 + flags: int = 0 + if _asan_mode != 0: flags = flags | IRCORE_FLAG_ASAN + if _traceback_mode != 0: flags = flags | IRCORE_FLAG_FRAME_POINTER + raw_path: str = _ircore_sibling(ir_path, ".raw.ll") + if raw_path is not None: + if _file_exists(raw_path) != 0: _ircore_read_whole(raw_path, &raw_buf, &raw_len) + stdlib.free(raw_path) + inline_path: str = _ircore_sibling(ir_path, ".inline") + if inline_path is not None: + if _file_exists(inline_path) != 0: flags = flags | IRCORE_FLAG_INLINE + stdlib.free(inline_path) out_obj: char* = None out_len: sizet = 0 err: char* = None err_len: sizet = 0 - # Codegen flags: --asan runs forceattrs,asan inside the DLL; --traceback keeps frame - # pointers (llc -frame-pointer=all). The legacy flow stays reachable via --phaseb-legacy. - flags: int = 0 - if _asan_mode != 0: flags = flags | IRCORE_FLAG_ASAN - if _traceback_mode != 0: flags = flags | IRCORE_FLAG_FRAME_POINTER - rc: int = _ircore_opts_fn(src, sizet(fsize), None, None, None, - IRCORE_OPT_DEFAULT_VALUE, IRCORE_RELOC_PIC_VALUE, flags, - &out_obj, &out_len, &err, &err_len) + rc: int = _ircore_raw_fn(src, src_len, raw_buf, raw_len, None, None, None, + IRCORE_OPT_DEFAULT_VALUE, IRCORE_RELOC_PIC_VALUE, flags, + &out_obj, &out_len, &err, &err_len) stdlib.free(src) + if raw_buf is not None: stdlib.free(raw_buf) if rc != 0: # The DLL's diagnostic is the authoritative message (MLIR verifier or LLVM codegen). @@ -599,10 +631,7 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s # merging (llvm-link/opt), --traceback frame pointers and --phase2command still need the # external tools, so they fall through to the legacy command flow below (kept intact). if _phaseb_ircore_active() != 0 and _phase2_cmd_path is None: - raw_sidecar: str = _ir_sibling_path(ir_path, ".raw.ll") - inline_marker: str = _ir_sibling_path(ir_path, ".inline") - if _file_exists(raw_sidecar) == 0 and _file_exists(inline_marker) == 0: - return _ircore_compile_module(ir_path, obj_path, module_name) + return _ircore_compile_module(ir_path, obj_path, module_name) # ASan mode: run opt -passes=asan before llc. -asan-globals=false disables global # instrumentation: VPC addresses globals directly and ASan redzones would shift them. @@ -846,7 +875,7 @@ def init_llc_task(task: LlcTask*, ir_path: str, output_dir: str, module_name: st # IRCore backend: plain modules compile in-process (the commands above stay allocated for # the legacy path / reporting but are not run). ASan instrumentation, raw-IR merging and # --phase2command keep the external tools. - if _phaseb_ircore_active() != 0 and _phase2_cmd_path is None and has_raw == 0 and need_inline == 0: + if _phaseb_ircore_active() != 0 and _phase2_cmd_path is None: task.UseIrcore = 1 task.IrPath = stdlib.malloc(len(ir_path) + 1) if task.IrPath is None: return 1