build(phaseb): move llvm-link + opt for raw-IR modules into ircore
Raw-IR modules (`__llvmir__` / `__mlir__`) were the last common case that still needed child processes: llvm-link merged the `.raw.ll` sidecar into the translated module, then `opt -passes=always-inline,globaldce` inlined the raw bodies. Both now run inside the DLL: - new `ircore_mlir_to_obj_raw(mlir, len, raw_ir, raw_ir_len, ...)`: the sidecar is parsed into the same LLVM context, merged with LLVMLinkModules2, then run through always-inline,globaldce via LLVMRunPasses (IRCORE_FLAG_INLINE covers the `.inline` marker case, which has no sidecar) - the worker reads the sidecar itself with stdlib buffers (MemBuddy is not thread-safe), so `.ll.pre`, `.ll.link` and `.ll.inline` intermediates are gone as well - LLVMLinkModules2 is exported by libLLVM-22.dll but no longer declared in the llvm-c headers we build against, so it is declared by hand (stable C ABI) - PhaseB falls back to the legacy flow only for --phase2command now Verified: RawIRTest runs through ircore with no `.mlir.opt`/`.ll.pre`/`.ll.link`/ `.ll.inline` leftovers and prints the same results as the legacy build; the FFI suite gained a raw-merge check (the sidecar symbol is undefined without it, linked and inlined away with it); TestFBProject (also uses __llvmir__) and 12 representative projects pass.
This commit is contained in:
1 parent
f8368d5e47
commit
c9d33bc202
1 file changed
+71
-42
@@ -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 - "<ir_path without .ll><suffix>", 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
|
||||
|
||||
Reference in new issue
Block a user