feat(codegen): inline arbitrary LLVM IR and raw MLIR blocks
This commit is contained in:
1 parent
645e193729
commit
6a15adbad0
3 files changed
+407
-72
No files matched your search
+165
-13
@@ -51,6 +51,22 @@ _phase2_bat_init: int = 0
|
||||
MLIR_OPT_CMD: str = "mlir-opt"
|
||||
MLIR_TRANSLATE_CMD: str = "mlir-translate"
|
||||
|
||||
# Raw-IR support (__llvmir__/__mlir__): raw LLVM function bodies are split into <sha1>.raw.ll
|
||||
# and merged with the lowered module; always-inline then removes the call boundary so the raw
|
||||
# body behaves like inline IR again.
|
||||
LLVM_LINK_CMD: str = "llvm-link"
|
||||
INLINE_PASSES: str = "always-inline,globaldce"
|
||||
define RAW_LLVM_MARKER = "//__VPC_RAW_LLVM__"
|
||||
|
||||
|
||||
def _file_exists(path: str) -> int:
|
||||
"""1 when path exists (opens it for reading and closes), else 0."""
|
||||
if path is None: return 0
|
||||
f: io.File* = io.File(path, io.MODE.R)
|
||||
if f.closed: return 0
|
||||
f.close()
|
||||
return 1
|
||||
|
||||
|
||||
def _ir_sibling_path(ir_path: str, suffix: str) -> str:
|
||||
"""Derive a sibling path from <...>.ll: strip the .ll suffix and append suffix; mbuddy-allocated."""
|
||||
@@ -339,9 +355,20 @@ def build_sliced_path(base_dir: str, sha1: str, ext: str) -> str:
|
||||
|
||||
|
||||
def write_ir_to_file(ir_buf: bytes, ir_len: sizet, output_dir: str, module_name: str) -> int:
|
||||
"""Write the IR buffer to a .ll file; 0 on success, non-zero on failure."""
|
||||
"""Write the IR buffer to <sha1>.ll; raw LLVM text is split into <sha1>.raw.ll."""
|
||||
if ir_buf is None or output_dir is None or module_name is None: return 1
|
||||
|
||||
# Raw LLVM functions (__llvmir__) follow a marker after the MLIR module: split them out so
|
||||
# mlir-opt only ever receives the MLIR module.
|
||||
raw_pos: str = string.strstr(ir_buf, RAW_LLVM_MARKER)
|
||||
mlir_len: sizet = ir_len
|
||||
raw_start: sizet = 0
|
||||
if raw_pos is not None:
|
||||
mlir_len = sizet(uint64(raw_pos) - uint64(ir_buf))
|
||||
raw_start = mlir_len + string.strlen(RAW_LLVM_MARKER)
|
||||
while raw_start < ir_len and (ir_buf[raw_start] == '\r' or ir_buf[raw_start] == '\n'):
|
||||
raw_start += 1
|
||||
|
||||
# build sliced path: output_dir/{sha1 prefix}/{module_name}.ll
|
||||
path: str = build_sliced_path(output_dir, module_name, "ll")
|
||||
if path is None: return 1
|
||||
@@ -350,9 +377,30 @@ def write_ir_to_file(ir_buf: bytes, ir_len: sizet, output_dir: str, module_name:
|
||||
f: io.File* = io.File(path, io.MODE.W)
|
||||
if f.closed: return 1
|
||||
|
||||
written: int64 = f.write(ir_buf, ir_len)
|
||||
written: int64 = f.write(ir_buf, mlir_len)
|
||||
f.close()
|
||||
if written < 0: return 1
|
||||
|
||||
if raw_pos is not None and ir_len > raw_start:
|
||||
raw_path: str = build_sliced_path(output_dir, module_name, "raw.ll")
|
||||
if raw_path is None: return 1
|
||||
rf: io.File* = io.File(raw_path, io.MODE.W)
|
||||
if rf.closed: return 1
|
||||
rw: int64 = rf.write(ir_buf + raw_start, ir_len - raw_start)
|
||||
rf.close()
|
||||
if rw < 0: return 1
|
||||
|
||||
# Inline-pass marker: raw LLVM functions and/or a raw-MLIR body (__mlir__) are present, so
|
||||
# the object must go through opt -passes=always-inline,globaldce before llc.
|
||||
need_inline: int = 0
|
||||
if raw_pos is not None: need_inline = 1
|
||||
elif string.strstr(ir_buf, ".__vpc_mlir_") is not None: need_inline = 1
|
||||
if need_inline != 0:
|
||||
flag_path: str = build_sliced_path(output_dir, module_name, "inline")
|
||||
if flag_path is None: return 1
|
||||
ff: io.File* = io.File(flag_path, io.MODE.W)
|
||||
if ff.closed: return 1
|
||||
ff.close()
|
||||
return 0
|
||||
|
||||
|
||||
@@ -369,22 +417,56 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s
|
||||
compile_input: str = ir_path
|
||||
|
||||
# ---- MLIR lowering (fixed, no switch): mlir-opt (validate) -> mlir-translate ----
|
||||
# Must run before ASan opt and llc, replacing compile_input with <...>.ll.local.
|
||||
# Must run before ASan opt and llc, replacing compile_input with the final LLVM IR file.
|
||||
ll_local_path: str = _ir_sibling_path(ir_path, ".ll.local")
|
||||
mlir_opt_out: str = _ir_sibling_path(ir_path, ".mlir.opt")
|
||||
if ll_local_path is None or mlir_opt_out is None: return 1
|
||||
raw_path: str = _ir_sibling_path(ir_path, ".raw.ll")
|
||||
inline_flag: str = _ir_sibling_path(ir_path, ".inline")
|
||||
ll_pre_path: str = _ir_sibling_path(ir_path, ".ll.pre")
|
||||
ll_link_path: str = _ir_sibling_path(ir_path, ".ll.link")
|
||||
ll_inline_path: str = _ir_sibling_path(ir_path, ".ll.inline")
|
||||
if ll_local_path is None or mlir_opt_out is None or raw_path is None or inline_flag is None \
|
||||
or ll_pre_path is None or ll_link_path is None or ll_inline_path is None: return 1
|
||||
has_raw: int = _file_exists(raw_path)
|
||||
translate_out: str = ll_local_path
|
||||
if has_raw != 0: translate_out = ll_pre_path
|
||||
mo_len: sizet = len(MLIR_OPT_CMD) + len(ir_path) + len(mlir_opt_out) + 16
|
||||
cmd_mo: str = _mbuddy.alloc(mo_len)
|
||||
if cmd_mo is None: return 1
|
||||
viperlib.snprintf(cmd_mo, mo_len, "%s %s -o %s", MLIR_OPT_CMD, ir_path, mlir_opt_out)
|
||||
mt_len: sizet = len(MLIR_TRANSLATE_CMD) + len(mlir_opt_out) + len(ll_local_path) + 40
|
||||
mt_len: sizet = len(MLIR_TRANSLATE_CMD) + len(mlir_opt_out) + len(translate_out) + 40
|
||||
cmd_mt: str = _mbuddy.alloc(mt_len)
|
||||
if cmd_mt is None: return 1
|
||||
viperlib.snprintf(cmd_mt, mt_len, "%s --mlir-to-llvmir %s -o %s", MLIR_TRANSLATE_CMD, mlir_opt_out, ll_local_path)
|
||||
viperlib.snprintf(cmd_mt, mt_len, "%s --mlir-to-llvmir %s -o %s", MLIR_TRANSLATE_CMD, mlir_opt_out, translate_out)
|
||||
|
||||
# Raw LLVM functions (__llvmir__): merge the sidecar into the lowered module.
|
||||
cmd_link: str = None
|
||||
if has_raw != 0:
|
||||
lk_len: sizet = len(LLVM_LINK_CMD) + len(ll_pre_path) + len(raw_path) + len(ll_link_path) + 32
|
||||
cmd_link = _mbuddy.alloc(lk_len)
|
||||
if cmd_link is None: return 1
|
||||
viperlib.snprintf(cmd_link, lk_len, "%s %s %s -S -o %s", LLVM_LINK_CMD, ll_pre_path, raw_path, ll_link_path)
|
||||
|
||||
# Always-inline pass: restores inline semantics for raw functions/blocks (raw LLVM bodies and
|
||||
# raw MLIR bodies both become ordinary calls first).
|
||||
need_inline: int = 0
|
||||
if has_raw != 0: need_inline = 1
|
||||
elif _file_exists(inline_flag) != 0: need_inline = 1
|
||||
cmd_inline: str = None
|
||||
inline_src: str = ll_local_path
|
||||
if has_raw != 0: inline_src = ll_link_path
|
||||
if need_inline != 0:
|
||||
in_len: sizet = len("opt -passes=") + len(INLINE_PASSES) + len(inline_src) + len(ll_inline_path) + 32
|
||||
cmd_inline = _mbuddy.alloc(in_len)
|
||||
if cmd_inline is None: return 1
|
||||
viperlib.snprintf(cmd_inline, in_len, "opt -passes=%s -S %s -o %s", INLINE_PASSES, inline_src, ll_inline_path)
|
||||
|
||||
if _phase2_cmd_path is not None:
|
||||
# write the command only (must precede the ASan/llc commands below)
|
||||
# write the commands only (must precede the ASan/llc commands below)
|
||||
_phase2_write_cmd(cmd_mo)
|
||||
_phase2_write_cmd(cmd_mt)
|
||||
if cmd_link is not None: _phase2_write_cmd(cmd_link)
|
||||
if cmd_inline is not None: _phase2_write_cmd(cmd_inline)
|
||||
else:
|
||||
mo_rc: int = _run_cmd_wait(cmd_mo)
|
||||
if mo_rc != 0:
|
||||
@@ -394,7 +476,18 @@ def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: s
|
||||
if mt_rc != 0:
|
||||
VLogger.error(f"mlir-translate failed (module={module_name}, exit={mt_rc}, cmd={cmd_mt})", "MLIR")
|
||||
return 1
|
||||
compile_input = ll_local_path
|
||||
if cmd_link is not None:
|
||||
lk_rc: int = _run_cmd_wait(cmd_link)
|
||||
if lk_rc != 0:
|
||||
VLogger.error(f"llvm-link failed for raw LLVM IR (module={module_name}, exit={lk_rc}, cmd={cmd_link})", "RAWIR")
|
||||
return 1
|
||||
if cmd_inline is not None:
|
||||
inl_rc: int = _run_cmd_wait(cmd_inline)
|
||||
if inl_rc != 0:
|
||||
VLogger.error(f"opt always-inline failed for raw IR (module={module_name}, exit={inl_rc}, cmd={cmd_inline})", "RAWIR")
|
||||
return 1
|
||||
if need_inline != 0: compile_input = ll_inline_path
|
||||
else: compile_input = ll_local_path
|
||||
|
||||
# --phase2command mode: do not run opt/llc; write the opt (ASan) + llc commands to the bat
|
||||
if _phase2_cmd_path is not None:
|
||||
@@ -456,6 +549,8 @@ define LLC_STAGE_MLIR_OPT = 1
|
||||
define LLC_STAGE_MLIR_TRANSLATE = 2
|
||||
define LLC_STAGE_ASAN_OPT = 3
|
||||
define LLC_STAGE_LLC = 4
|
||||
define LLC_STAGE_RAW_LINK = 5
|
||||
define LLC_STAGE_RAW_INLINE = 6
|
||||
|
||||
|
||||
class LlcTask:
|
||||
@@ -472,6 +567,9 @@ class LlcTask:
|
||||
MlirTranslateCmd: bytes # mlir-translate lowering command
|
||||
# failure stage (LLC_STAGE_*, see the constants above); appended at the end.
|
||||
FailStage: int # worker-written: which step failed
|
||||
# Raw-IR support (__llvmir__/__mlir__); appended at the end, None when the module has none.
|
||||
RawLinkCmd: bytes # llvm-link merge of <sha1>.raw.ll into the lowered module
|
||||
RawInlineCmd: bytes # opt -passes=always-inline,globaldce (raw body -> inline)
|
||||
|
||||
|
||||
def init_llc_task(task: LlcTask*, ir_path: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str) -> int:
|
||||
@@ -490,19 +588,45 @@ def init_llc_task(task: LlcTask*, ir_path: str, output_dir: str, module_name: st
|
||||
# compile_ll_to_obj path (else llc ignores the instrumentation and ASan is half-blind).
|
||||
compile_input: str = ir_path
|
||||
# MLIR lowering (fixed): the worker runs mlir-opt (validate) -> mlir-translate first, so
|
||||
# the later ASan OptCmd / LlcCmd input must be <...>.ll.local.
|
||||
# the later ASan OptCmd / LlcCmd input must be the final LLVM IR file.
|
||||
ll_local_path: str = _ir_sibling_path(ir_path, ".ll.local")
|
||||
mlir_opt_out: str = _ir_sibling_path(ir_path, ".mlir.opt")
|
||||
if ll_local_path is None or mlir_opt_out is None: return 1
|
||||
raw_path: str = _ir_sibling_path(ir_path, ".raw.ll")
|
||||
inline_flag: str = _ir_sibling_path(ir_path, ".inline")
|
||||
ll_pre_path: str = _ir_sibling_path(ir_path, ".ll.pre")
|
||||
ll_link_path: str = _ir_sibling_path(ir_path, ".ll.link")
|
||||
ll_inline_path: str = _ir_sibling_path(ir_path, ".ll.inline")
|
||||
if ll_local_path is None or mlir_opt_out is None or raw_path is None or inline_flag is None \
|
||||
or ll_pre_path is None or ll_link_path is None or ll_inline_path is None: return 1
|
||||
has_raw: int = _file_exists(raw_path)
|
||||
translate_out: str = ll_local_path
|
||||
if has_raw != 0: translate_out = ll_pre_path
|
||||
mo_len: sizet = len(MLIR_OPT_CMD) + len(ir_path) + len(mlir_opt_out) + 16
|
||||
task.MlirOptCmd = stdlib.malloc(mo_len)
|
||||
if task.MlirOptCmd is None: return 1
|
||||
viperlib.snprintf(task.MlirOptCmd, mo_len, "%s %s -o %s", MLIR_OPT_CMD, ir_path, mlir_opt_out)
|
||||
mt_len: sizet = len(MLIR_TRANSLATE_CMD) + len(mlir_opt_out) + len(ll_local_path) + 40
|
||||
mt_len: sizet = len(MLIR_TRANSLATE_CMD) + len(mlir_opt_out) + len(translate_out) + 40
|
||||
task.MlirTranslateCmd = stdlib.malloc(mt_len)
|
||||
if task.MlirTranslateCmd is None: return 1
|
||||
viperlib.snprintf(task.MlirTranslateCmd, mt_len, "%s --mlir-to-llvmir %s -o %s", MLIR_TRANSLATE_CMD, mlir_opt_out, ll_local_path)
|
||||
compile_input = ll_local_path
|
||||
viperlib.snprintf(task.MlirTranslateCmd, mt_len, "%s --mlir-to-llvmir %s -o %s", MLIR_TRANSLATE_CMD, mlir_opt_out, translate_out)
|
||||
compile_input = translate_out
|
||||
# Raw LLVM functions (__llvmir__): merge the sidecar into the lowered module.
|
||||
if has_raw != 0:
|
||||
lk_len: sizet = len(LLVM_LINK_CMD) + len(ll_pre_path) + len(raw_path) + len(ll_link_path) + 32
|
||||
task.RawLinkCmd = stdlib.malloc(lk_len)
|
||||
if task.RawLinkCmd is None: return 1
|
||||
viperlib.snprintf(task.RawLinkCmd, lk_len, "%s %s %s -S -o %s", LLVM_LINK_CMD, ll_pre_path, raw_path, ll_link_path)
|
||||
compile_input = ll_link_path
|
||||
# Always-inline pass: restores inline semantics for raw functions/blocks.
|
||||
need_inline: int = 0
|
||||
if has_raw != 0: need_inline = 1
|
||||
elif _file_exists(inline_flag) != 0: need_inline = 1
|
||||
if need_inline != 0:
|
||||
in_len: sizet = len("opt -passes=") + len(INLINE_PASSES) + len(compile_input) + len(ll_inline_path) + 32
|
||||
task.RawInlineCmd = stdlib.malloc(in_len)
|
||||
if task.RawInlineCmd is None: return 1
|
||||
viperlib.snprintf(task.RawInlineCmd, in_len, "opt -passes=%s -S %s -o %s", INLINE_PASSES, compile_input, ll_inline_path)
|
||||
compile_input = ll_inline_path
|
||||
# ASan mode: opt instrumentation command (the worker runs MLIR lowering/opt before llc,
|
||||
# so LlcCmd's input must point at asan_path; failure sets Result).
|
||||
if _asan_mode != 0:
|
||||
@@ -537,6 +661,12 @@ def free_llc_task(task: LlcTask*):
|
||||
if task.MlirTranslateCmd is not None:
|
||||
stdlib.free(task.MlirTranslateCmd)
|
||||
task.MlirTranslateCmd = None
|
||||
if task.RawLinkCmd is not None:
|
||||
stdlib.free(task.RawLinkCmd)
|
||||
task.RawLinkCmd = None
|
||||
if task.RawInlineCmd is not None:
|
||||
stdlib.free(task.RawInlineCmd)
|
||||
task.RawInlineCmd = None
|
||||
if task.ModuleSha1 is not None:
|
||||
stdlib.free(task.ModuleSha1)
|
||||
task.ModuleSha1 = None
|
||||
@@ -617,6 +747,20 @@ def _llc_task_worker(arg: rawptr) -> rawptr:
|
||||
task.FailStage = LLC_STAGE_MLIR_TRANSLATE
|
||||
task.Result = 1
|
||||
return None
|
||||
if task.RawLinkCmd is not None:
|
||||
rl_rc: int = _run_cmd_wait(task.RawLinkCmd)
|
||||
if rl_rc != 0:
|
||||
task.ExitCode = rl_rc
|
||||
task.FailStage = LLC_STAGE_RAW_LINK
|
||||
task.Result = 1
|
||||
return None
|
||||
if task.RawInlineCmd is not None:
|
||||
ri_rc: int = _run_cmd_wait(task.RawInlineCmd)
|
||||
if ri_rc != 0:
|
||||
task.ExitCode = ri_rc
|
||||
task.FailStage = LLC_STAGE_RAW_INLINE
|
||||
task.Result = 1
|
||||
return None
|
||||
if task.OptCmd is not None:
|
||||
opt_rc: int = _run_cmd_wait(task.OptCmd)
|
||||
if opt_rc != 0:
|
||||
@@ -645,6 +789,8 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int:
|
||||
if tsk_p is None: continue
|
||||
if tsk_p.MlirOptCmd is not None: _phase2_write_cmd(tsk_p.MlirOptCmd)
|
||||
if tsk_p.MlirTranslateCmd is not None: _phase2_write_cmd(tsk_p.MlirTranslateCmd)
|
||||
if tsk_p.RawLinkCmd is not None: _phase2_write_cmd(tsk_p.RawLinkCmd)
|
||||
if tsk_p.RawInlineCmd is not None: _phase2_write_cmd(tsk_p.RawInlineCmd)
|
||||
if tsk_p.OptCmd is not None: _phase2_write_cmd(tsk_p.OptCmd)
|
||||
if tsk_p.LlcCmd is not None: _phase2_write_cmd(tsk_p.LlcCmd)
|
||||
VLogger.info(f"phase2command mode: {task_count} llc commands written to {_phase2_cmd_path}", "phase2cmd")
|
||||
@@ -690,6 +836,12 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int:
|
||||
elif st == LLC_STAGE_MLIR_TRANSLATE:
|
||||
stage_name = "mlir-translate lowering"
|
||||
stage_cmd = tsk2.MlirTranslateCmd
|
||||
elif st == LLC_STAGE_RAW_LINK:
|
||||
stage_name = "llvm-link raw IR merge"
|
||||
stage_cmd = tsk2.RawLinkCmd
|
||||
elif st == LLC_STAGE_RAW_INLINE:
|
||||
stage_name = "opt always-inline (raw IR)"
|
||||
stage_cmd = tsk2.RawInlineCmd
|
||||
elif st == LLC_STAGE_ASAN_OPT:
|
||||
stage_name = "opt ASan instrumentation"
|
||||
stage_cmd = tsk2.OptCmd
|
||||
|
||||
@@ -846,31 +846,33 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
return None
|
||||
|
||||
|
||||
def translate_c_llvmir(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||||
"""Inline raw LLVM IR text into the current basic block; returns None."""
|
||||
if cl is None or builder is None or pool is None or trans is None: return None
|
||||
|
||||
def _raw_ir_template(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator*, in_vals: sizet*, out_vals: sizet*, counts: int*) -> str:
|
||||
"""Expand a raw-IR template; irinp -> %in<N>, irout -> %vpc_out<N>."""
|
||||
cargs: list[ast.AST*]* = cl.args
|
||||
if cargs is None or len(cargs) < 1: self.Gen.TypeGenErr.fatal_error(cl, "c.LLVMIR missing template argument", "TypeError")
|
||||
|
||||
# LOut writeback records (alloca pointer + register name), up to LLVMIR_MAX_OPS
|
||||
out_alloca: sizet* = pool.alloc(8 * LLVMIR_MAX_OPS)
|
||||
out_regname: sizet* = pool.alloc(8 * LLVMIR_MAX_OPS)
|
||||
if out_alloca is None or out_regname is None: return None
|
||||
string.memset(out_alloca, 0, 8 * LLVMIR_MAX_OPS)
|
||||
string.memset(out_regname, 0, 8 * LLVMIR_MAX_OPS)
|
||||
out_count: int = 0
|
||||
|
||||
# Template text buffer
|
||||
text: str = pool.alloc(LLVMIR_TEMPLATE_BUF)
|
||||
if text is None: return None
|
||||
text[0] = '\0'
|
||||
|
||||
# 1. Parse the IR template (JoinedStr or Constant), replacing LInp/LOut/exprs with value text
|
||||
if cargs is None or len(cargs) < 1:
|
||||
self.Gen.TypeGenErr.fatal_error(cl, "raw IR missing the template argument", "TypeError")
|
||||
return None
|
||||
first_arg: ast.AST* = cargs.get(0)
|
||||
if first_arg is None: return None
|
||||
|
||||
# Capacity: literal fragments plus one parameter name per formatted value.
|
||||
cap: sizet = 64
|
||||
tk: int = first_arg.kind()
|
||||
if tk == ast.ASTKind.JoinedStr:
|
||||
js_cap: ast.JoinedStr* = ast.JoinedStr*(first_arg)
|
||||
if js_cap.values is not None:
|
||||
cap += len(js_cap.values) * 64
|
||||
for vi_cap in range(int(len(js_cap.values))):
|
||||
part_cap: ast.AST* = js_cap.values.get(vi_cap)
|
||||
if part_cap is not None and part_cap.kind() == ast.ASTKind.Constant:
|
||||
cn_cap: ast.Constant* = ast.Constant*(part_cap)
|
||||
if cn_cap.str_val is not None: cap += string.strlen(cn_cap.str_val)
|
||||
elif tk == ast.ASTKind.Constant:
|
||||
cn_only: ast.Constant* = ast.Constant*(first_arg)
|
||||
if cn_only.str_val is not None: cap += string.strlen(cn_only.str_val)
|
||||
text: str = pool.alloc(cap)
|
||||
if text is None: return None
|
||||
text[0] = '\0'
|
||||
|
||||
if tk == ast.ASTKind.JoinedStr:
|
||||
js: ast.JoinedStr* = ast.JoinedStr*(first_arg)
|
||||
@@ -883,15 +885,13 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
pk: int = part.kind()
|
||||
|
||||
if pk == ast.ASTKind.Constant:
|
||||
# String literal fragment
|
||||
cn: ast.Constant* = ast.Constant*(part)
|
||||
if cn.str_val is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn.str_val, "c.LLVMIR")
|
||||
if cn.str_val is not None: self._asm_append_cstr(text, cap, cn.str_val, "raw IR")
|
||||
|
||||
elif pk == ast.ASTKind.FormattedValue:
|
||||
fv: ast.FormattedValue* = ast.FormattedValue*(part)
|
||||
fexpr: ast.AST* = fv.value
|
||||
if fexpr is None: continue
|
||||
# Detect c.LInp / c.LOut
|
||||
if fexpr.kind() == ast.ASTKind.Call:
|
||||
fcall: ast.Call* = ast.Call*(fexpr)
|
||||
attr_nm: str = self._asm_is_c_call(fcall)
|
||||
@@ -901,55 +901,234 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
val_node: ast.AST* = fargs.get(0)
|
||||
|
||||
if string.strcmp(attr_nm, "LInp") == 0:
|
||||
# Input operand: translate to an LLVM value reference text (%x or constant)
|
||||
inp_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, val_node, None, 0, trans)
|
||||
if inp_val is not None and inp_val.Name is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, inp_val.Name, "c.LLVMIR")
|
||||
if inp_val is None or inp_val.Ty is None:
|
||||
self.Gen.TypeGenErr.fatal_error(fcall, "raw IR input operand cannot be translated", "TypeError")
|
||||
return None
|
||||
if counts[0] >= LLVMIR_MAX_OPS:
|
||||
self.Gen.TypeGenErr.fatal_error(fcall, f"raw IR has more than {LLVMIR_MAX_OPS} input operands (compiler limit); split the block", "TypeError")
|
||||
return None
|
||||
in_vals[counts[0]] = sizet(inp_val)
|
||||
pname: str = pool.alloc(32)
|
||||
if pname is not None:
|
||||
viperlib.snprintf(pname, 32, "%%in%d", counts[0])
|
||||
self._asm_append_cstr(text, cap, pname, "raw IR")
|
||||
counts[0] += 1
|
||||
continue
|
||||
|
||||
elif string.strcmp(attr_nm, "LOut") == 0:
|
||||
# Output operand: allocate a unique SSA register
|
||||
# %llvmirN and record the writeback to the alloca.
|
||||
# Output operand: the variable's alloca is passed as a pointer
|
||||
# parameter; the raw body defines %vpc_out<N> and the generated
|
||||
# epilogue stores it back into that alloca.
|
||||
if val_node is not None and val_node.kind() == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(val_node)
|
||||
if nm.id is not None:
|
||||
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||||
if alloca is None or out_count >= LLVMIR_MAX_OPS: self.Gen.TypeGenErr.fatal_error(fcall, f"c.LOut variable '{nm.id}' is undefined or the output count exceeds {LLVMIR_MAX_OPS}", "TypeError")
|
||||
self.Gen.CallGenBase.LLVMIRRegSeq += 1
|
||||
reg_buf: str = pool.alloc(32)
|
||||
if reg_buf is not None:
|
||||
viperlib.snprintf(reg_buf, 32, "%%llvmir_%d", self.Gen.CallGenBase.LLVMIRRegSeq)
|
||||
self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, reg_buf, "c.LLVMIR")
|
||||
out_alloca[out_count] = sizet(alloca)
|
||||
out_regname[out_count] = sizet(reg_buf)
|
||||
out_count += 1
|
||||
if alloca is None or alloca.Ty is None or alloca.Ty.Pointee == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(fcall, f"raw IR output variable '{nm.id}' is not a local variable", "TypeError")
|
||||
return None
|
||||
if counts[1] >= LLVMIR_MAX_OPS:
|
||||
self.Gen.TypeGenErr.fatal_error(fcall, f"raw IR has more than {LLVMIR_MAX_OPS} output operands (compiler limit); split the block", "TypeError")
|
||||
return None
|
||||
out_vals[counts[1]] = sizet(alloca)
|
||||
rname: str = pool.alloc(32)
|
||||
if rname is not None:
|
||||
viperlib.snprintf(rname, 32, "%%vpc_out%d", counts[1])
|
||||
self._asm_append_cstr(text, cap, rname, "raw IR")
|
||||
counts[1] += 1
|
||||
continue
|
||||
continue
|
||||
# Non-LInp/LOut f-string expression: translate and embed the value text
|
||||
# Ordinary expression: embed its value reference text.
|
||||
ival: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fexpr, None, 0, trans)
|
||||
if ival is not None and ival.Name is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, ival.Name, "c.LLVMIR")
|
||||
if ival is not None and ival.Name is not None: self._asm_append_cstr(text, cap, ival.Name, "raw IR")
|
||||
|
||||
elif tk == ast.ASTKind.Constant:
|
||||
cn2: ast.Constant* = ast.Constant*(first_arg)
|
||||
if cn2.str_val is not None:
|
||||
# A bare strcpy is unbounded and could overflow the pool block, so
|
||||
# use the same bounded append as the JoinedStr branch.
|
||||
self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn2.str_val, "c.LLVMIR")
|
||||
if cn2.str_val is not None: self._asm_append_cstr(text, cap, cn2.str_val, "raw IR")
|
||||
|
||||
# 2. Embed the template text directly (no instruction parsing) + LOut writeback
|
||||
mlirlite.block_append_text(pool, builder.CurBlock, text)
|
||||
return text
|
||||
|
||||
# LOut writeback: store <ty> %llvmirN, <ty>* %var
|
||||
oi: int
|
||||
for oi in range(int(out_count)):
|
||||
oa: mlirlite.Value* = mlirlite.Value*(out_alloca[oi])
|
||||
rn: str = str(out_regname[oi])
|
||||
if oa is None or oa.Ty is None or oa.Ty.Pointee == 0 or rn is None: continue
|
||||
reg_val: mlirlite.Value* = mlirlite.new_value(pool)
|
||||
if reg_val is None: continue
|
||||
reg_val.Ty = oa.Ty.Pointee
|
||||
reg_val.Name = rn
|
||||
reg_val.IsConst = 0
|
||||
mlirlite.build_store(builder, reg_val, oa)
|
||||
def _raw_ir_emit_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, name: str, in_vals: sizet*, in_count: int, out_vals: sizet*, out_count: int) -> mlirlite.Value*:
|
||||
"""Emit the call to the generated raw function (inputs, then output pointers)."""
|
||||
total: int = in_count + out_count
|
||||
head: mlirlite.Value* = None
|
||||
prev: mlirlite.Value* = None
|
||||
for k in range(total):
|
||||
v: mlirlite.Value* = None
|
||||
if k < in_count: v = mlirlite.Value*(in_vals[k])
|
||||
else: v = mlirlite.Value*(out_vals[k - in_count])
|
||||
if v is None: return None
|
||||
v.Next = None
|
||||
if head is None: head = v
|
||||
else: prev.Next = v
|
||||
prev = v
|
||||
void_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
|
||||
return mlirlite.build_call(builder, name, head, total, void_ty, 0)
|
||||
|
||||
return None
|
||||
def _raw_ir_func_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, tag: str) -> str:
|
||||
"""Unique symbol name for one generated raw function."""
|
||||
seq: int = self.Gen.CallGenBase.LLVMIRRegSeq + 1
|
||||
self.Gen.CallGenBase.LLVMIRRegSeq = seq
|
||||
sha1: str = trans.ModuleSha1
|
||||
if sha1 is None: sha1 = "vpc"
|
||||
cap: sizet = string.strlen(sha1) + string.strlen(tag) + 48
|
||||
name: str = pool.alloc(cap)
|
||||
if name is None: return None
|
||||
viperlib.snprintf(name, cap, "%s.%s_%d", sha1, tag, seq)
|
||||
return name
|
||||
|
||||
def translate_c_llvmir(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||||
"""Inline raw LLVM IR: emit a sidecar function whose body is the user text, call it.
|
||||
|
||||
The body is arbitrary LLVM IR parsed by LLVM itself (not MLIR), so every legal
|
||||
instruction/metadata form works. Inputs become parameters (%in<N>), output variables
|
||||
become pointer parameters; the generated epilogue stores %vpc_out<N> back.
|
||||
"""
|
||||
if cl is None or builder is None or pool is None or trans is None: return None
|
||||
in_vals: sizet* = pool.alloc(8 * LLVMIR_MAX_OPS)
|
||||
out_vals: sizet* = pool.alloc(8 * LLVMIR_MAX_OPS)
|
||||
counts: int* = pool.alloc(8 * 2)
|
||||
if in_vals is None or out_vals is None or counts is None: return None
|
||||
string.memset(in_vals, 0, 8 * LLVMIR_MAX_OPS)
|
||||
string.memset(out_vals, 0, 8 * LLVMIR_MAX_OPS)
|
||||
counts[0] = 0
|
||||
counts[1] = 0
|
||||
|
||||
body: str = self._raw_ir_template(pool, builder, mod, cl, trans, in_vals, out_vals, counts)
|
||||
if body is None: return None
|
||||
in_count: int = counts[0]
|
||||
out_count: int = counts[1]
|
||||
|
||||
name: str = self._raw_ir_func_name(pool, trans, "__vpc_llvmir")
|
||||
if name is None: return None
|
||||
|
||||
# Sidecar definition: linkonce_odr + alwaysinline so llvm-link merges it with the
|
||||
# declaration and opt -passes=always-inline restores inline semantics.
|
||||
cap: sizet = string.strlen(body) + 256 + sizet(in_count + out_count) * 4352
|
||||
fn_buf: str = pool.alloc(cap)
|
||||
ty_buf: str = pool.alloc(4096)
|
||||
if fn_buf is None or ty_buf is None: return None
|
||||
fn_buf[0] = '\0'
|
||||
viperlib.snprintf(fn_buf, cap, "define linkonce_odr void @\"%s\"(", name)
|
||||
pos: sizet = string.strlen(fn_buf)
|
||||
first: int = 1
|
||||
for k in range(in_count):
|
||||
v: mlirlite.Value* = mlirlite.Value*(in_vals[k])
|
||||
if v is None or v.Ty is None: return None
|
||||
ty_buf[0] = '\0'
|
||||
mlirlite.LlvmTypePrint(ty_buf, 4096, v.Ty, pool)
|
||||
if first == 0:
|
||||
viperlib.snprintf(fn_buf + pos, cap - pos, ", ")
|
||||
pos = string.strlen(fn_buf)
|
||||
viperlib.snprintf(fn_buf + pos, cap - pos, "%s %%in%d", ty_buf, k)
|
||||
pos = string.strlen(fn_buf)
|
||||
first = 0
|
||||
for k in range(out_count):
|
||||
a: mlirlite.Value* = mlirlite.Value*(out_vals[k])
|
||||
if a is None or a.Ty is None or a.Ty.Pointee == 0: return None
|
||||
ty_buf[0] = '\0'
|
||||
mlirlite.LlvmTypePrint(ty_buf, 4096, a.Ty, pool)
|
||||
if first == 0:
|
||||
viperlib.snprintf(fn_buf + pos, cap - pos, ", ")
|
||||
pos = string.strlen(fn_buf)
|
||||
viperlib.snprintf(fn_buf + pos, cap - pos, "%s %%out%d", ty_buf, k)
|
||||
pos = string.strlen(fn_buf)
|
||||
first = 0
|
||||
viperlib.snprintf(fn_buf + pos, cap - pos, ") alwaysinline {\n")
|
||||
pos = string.strlen(fn_buf)
|
||||
viperlib.snprintf(fn_buf + pos, cap - pos, "%s", body)
|
||||
pos = string.strlen(fn_buf)
|
||||
# Stores land in the body's last block: LLVM has no implicit fallthrough, so a separate
|
||||
# epilogue block would need an explicit branch (impossible when the user text already
|
||||
# ends with a terminator). Appending in-block matches the old inline semantics.
|
||||
viperlib.snprintf(fn_buf + pos, cap - pos, "\n")
|
||||
pos = string.strlen(fn_buf)
|
||||
for k in range(out_count):
|
||||
a2: mlirlite.Value* = mlirlite.Value*(out_vals[k])
|
||||
ty_buf[0] = '\0'
|
||||
mlirlite.LlvmTypePrint(ty_buf, 4096, a2.Ty.Pointee, pool)
|
||||
viperlib.snprintf(fn_buf + pos, cap - pos, " store %s %%vpc_out%d, ptr %%out%d\n", ty_buf, k, k)
|
||||
pos = string.strlen(fn_buf)
|
||||
viperlib.snprintf(fn_buf + pos, cap - pos, " ret void\n}\n")
|
||||
if mlirlite.module_append_raw_text(pool, mod, fn_buf) != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(cl, "raw LLVM IR: failed to record the generated function text", "MemoryError")
|
||||
return None
|
||||
|
||||
fn: mlirlite.Function* = mlirlite.create_declare(pool, mod, name, mlirlite.Void(pool))
|
||||
if fn is None: return None
|
||||
for k in range(in_count):
|
||||
v2: mlirlite.Value* = mlirlite.Value*(in_vals[k])
|
||||
mlirlite.add_param(pool, fn, v2.Ty, None)
|
||||
for k in range(out_count):
|
||||
a3: mlirlite.Value* = mlirlite.Value*(out_vals[k])
|
||||
mlirlite.add_param(pool, fn, a3.Ty, None)
|
||||
return self._raw_ir_emit_call(pool, builder, name, in_vals, in_count, out_vals, out_count)
|
||||
|
||||
def translate_c_mlir(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||||
"""Inline raw MLIR: emit a raw-body llvm.func and call it.
|
||||
|
||||
The body is spliced verbatim into the LLVM dialect module, so it may use any registered
|
||||
dialect/op that mlir-opt and mlir-translate understand. irinp operands become block
|
||||
arguments (%in<N>), irout variables become pointer arguments (%out<N>) whose stores are
|
||||
appended after the body.
|
||||
"""
|
||||
if cl is None or builder is None or pool is None or trans is None: return None
|
||||
in_vals: sizet* = pool.alloc(8 * LLVMIR_MAX_OPS)
|
||||
out_vals: sizet* = pool.alloc(8 * LLVMIR_MAX_OPS)
|
||||
counts: int* = pool.alloc(8 * 2)
|
||||
if in_vals is None or out_vals is None or counts is None: return None
|
||||
string.memset(in_vals, 0, 8 * LLVMIR_MAX_OPS)
|
||||
string.memset(out_vals, 0, 8 * LLVMIR_MAX_OPS)
|
||||
counts[0] = 0
|
||||
counts[1] = 0
|
||||
|
||||
body: str = self._raw_ir_template(pool, builder, mod, cl, trans, in_vals, out_vals, counts)
|
||||
if body is None: return None
|
||||
in_count: int = counts[0]
|
||||
out_count: int = counts[1]
|
||||
|
||||
name: str = self._raw_ir_func_name(pool, trans, "__vpc_mlir")
|
||||
if name is None: return None
|
||||
|
||||
fn: mlirlite.Function* = mlirlite.create_function(pool, mod, name, mlirlite.Void(pool))
|
||||
if fn is None: return None
|
||||
pn: str = pool.alloc(32)
|
||||
for k in range(in_count):
|
||||
v: mlirlite.Value* = mlirlite.Value*(in_vals[k])
|
||||
if v is None or v.Ty is None: return None
|
||||
if pn is not None:
|
||||
viperlib.snprintf(pn, 32, "in%d", k)
|
||||
mlirlite.add_param(pool, fn, v.Ty, pn)
|
||||
for k in range(out_count):
|
||||
a: mlirlite.Value* = mlirlite.Value*(out_vals[k])
|
||||
if a is None or a.Ty is None: return None
|
||||
if pn is not None:
|
||||
viperlib.snprintf(pn, 32, "out%d", k)
|
||||
mlirlite.add_param(pool, fn, a.Ty, pn)
|
||||
fn.Attrs = "alwaysinline"
|
||||
|
||||
cap: sizet = string.strlen(body) + 128 + sizet(out_count) * 8448
|
||||
raw: str = pool.alloc(cap)
|
||||
ty_buf: str = pool.alloc(4096)
|
||||
ptr_buf: str = pool.alloc(4096)
|
||||
if raw is None or ty_buf is None or ptr_buf is None: return None
|
||||
string.strcpy(raw, body)
|
||||
pos: sizet = string.strlen(raw)
|
||||
if pos > 0 and raw[pos - 1] != '\n':
|
||||
viperlib.snprintf(raw + pos, cap - pos, "\n")
|
||||
pos = string.strlen(raw)
|
||||
for k in range(out_count):
|
||||
a2: mlirlite.Value* = mlirlite.Value*(out_vals[k])
|
||||
if a2 is None or a2.Ty is None or a2.Ty.Pointee == 0: return None
|
||||
ty_buf[0] = '\0'
|
||||
ptr_buf[0] = '\0'
|
||||
mlirlite.TypePrint(ty_buf, 4096, a2.Ty.Pointee, pool)
|
||||
mlirlite.TypePrint(ptr_buf, 4096, a2.Ty, pool)
|
||||
viperlib.snprintf(raw + pos, cap - pos, " llvm.store %%vpc_out%d, %%out%d : %s, %s\n", k, k, ty_buf, ptr_buf)
|
||||
pos = string.strlen(raw)
|
||||
viperlib.snprintf(raw + pos, cap - pos, " llvm.return\n")
|
||||
fn.RawMlirBody = raw
|
||||
return self._raw_ir_emit_call(pool, builder, name, in_vals, in_count, out_vals, out_count)
|
||||
|
||||
# _apply_struct_defaults - apply default values to struct fields, after
|
||||
# zeroinitializer and before explicit args (which later overwrite defaults).
|
||||
@@ -255,11 +255,15 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
if hit_box is not None: hit_box[0] = 1
|
||||
return self.Gen.CallGenCBuiltin.translate_c_asm(pool, builder, mod, cl, trans)
|
||||
if bare_nm.id is not None and string.strcmp(bare_nm.id, "__llvmir__") == 0:
|
||||
# c.LLVMIR always returns void (None): set the hit flag
|
||||
# __llvmir__ always returns void (None): set the hit flag
|
||||
if hit_box is not None: hit_box[0] = 1
|
||||
return self.Gen.CallGenCBuiltin.translate_c_llvmir(pool, builder, mod, cl, trans)
|
||||
if bare_nm.id is not None and string.strcmp(bare_nm.id, "__mlir__") == 0:
|
||||
# Raw MLIR block: spliced into the LLVM-dialect module as a raw-body function.
|
||||
if hit_box is not None: hit_box[0] = 1
|
||||
return self.Gen.CallGenCBuiltin.translate_c_mlir(pool, builder, mod, cl, trans)
|
||||
if bare_nm.id is not None and (string.strcmp(bare_nm.id, "irinp") == 0 or string.strcmp(bare_nm.id, "irout") == 0):
|
||||
# irinp/irout standalone calls return 0 (meaningful only inside __llvmir__ f-strings)
|
||||
# irinp/irout standalone calls return 0 (meaningful only inside __llvmir__/__mlir__ templates)
|
||||
return mlirlite.ConstInt(pool, mlirlite.Int32(pool), 0, "0")
|
||||
|
||||
# Detect coro.* coroutine interception (inline translation, VPC-M1).
|
||||
|
||||
Reference in new issue
Block a user