463 lines
19 KiB
Plaintext
463 lines
19 KiB
Plaintext
from stdint import *
|
||
import memhub
|
||
import string
|
||
import viperlib
|
||
|
||
# ============================================================
|
||
# mlirlite -- an MLIR (llvm dialect) text emission library, isomorphic to llvmlite name for name
|
||
#
|
||
# Design principles:
|
||
# * The internal model (LLVMType/Value/Function/BasicBlock/IRBuilder/Module...) is
|
||
# **isomorphic to includes/llvmlite name for name and signature for signature**, so on the VPC side it suffices to rename
|
||
# `llvmlite.` to `mlirlite.` in order to compile;
|
||
# * The difference lies only in the "printing" stage: TypePrint / ValuePrint / FunctionPrint /
|
||
# LLVMModulePrint emit MLIR text, while the builders still generate the intermediate instruction text as before;
|
||
# * Names keep the LLVMxxx form (isomorphism first). See README.md for semantic differences.
|
||
# ============================================================
|
||
|
||
# Import submodules
|
||
from .__types import (
|
||
LLVMType, ParamNode, LLVMTypeCore,
|
||
Int1, Int8, Int16, Int32, Int64, Ptr, Func, Void, Array, Struct,
|
||
Half, Float, Double, FP128, Label,
|
||
new_param_node, param_list_append,
|
||
paramnode_get_ty, paramnode_get_next, paramnode_set_ty, paramnode_set_next,
|
||
TypePrint, PrintStructDefinition, get_struct_name,
|
||
get_struct_fields, get_struct_fcount, DeepCopyType,
|
||
)
|
||
from .__values import (
|
||
Value, new_value, ConstInt, ConstNull, ConstZero, ConstFloat, SSAValue, ValuePrint,
|
||
value_get_ty, value_get_next,
|
||
value_set_ty, value_set_next, value_set_name, value_set_isconst,
|
||
)
|
||
from .__function import (
|
||
Line, BasicBlock, Param, Function,
|
||
new_line, new_basic_block, new_param, new_function,
|
||
function_add_param, function_add_block,
|
||
block_append_line, block_append_text,
|
||
function_set_attrs, param_set_attrs,
|
||
function_set_attr_group,
|
||
function_set_dbg,
|
||
function_get_name, function_get_name_len, function_get_ret_ty, function_get_params,
|
||
function_get_param_head, function_get_next, function_is_declared,
|
||
param_get_name, param_get_ty, param_get_next,
|
||
FunctionPrint, G_PeepEnable,
|
||
DebugSetEnabled, DebugIsEnabled, DebugNoteFunc, DebugClearFunc,
|
||
DebugNoteLine, DebugResetFunc, DebugTakeText,
|
||
)
|
||
from .__module import (
|
||
LLVMModule, GlobalVariable, NamedTypeNode, DINode, DINamedMeta,
|
||
new_module, new_global_variable,
|
||
module_add_function, module_add_global, module_add_named_type,
|
||
module_set_named_type_packed,
|
||
module_set_target, module_set_datalayout,
|
||
module_add_di_node, module_add_named_metadata,
|
||
global_set_constant, global_set_linkage, global_set_unnamed_addr, global_set_section,
|
||
LLVMModulePrint, OUTPUT_FULL, OUTPUT_STUB, OUTPUT_TEXT,
|
||
module_ensure_opaque_for_type,
|
||
)
|
||
from .__builder import (
|
||
IRBuilder, new_builder, position_at_end,
|
||
builder_cur_block_is_terminated,
|
||
builder_set_debug_location, builder_clear_debug_location,
|
||
build_alloca, build_alloca_aligned, build_alloca_at_entry, build_load, build_store,
|
||
build_store_at_entry,
|
||
build_add, build_sub, build_mul, build_sdiv, build_udiv, build_srem, build_urem,
|
||
build_and, build_or, build_xor, build_shl, build_lshr, build_ashr,
|
||
build_fadd, build_fsub, build_fmul, build_fdiv, build_frem, build_fneg,
|
||
build_icmp, build_fcmp,
|
||
build_br, build_cond_br, build_ret, build_ret_void,
|
||
build_call, build_call_indirect, build_bitcast, build_sext, build_trunc, build_zext,
|
||
build_ptrtoint, build_inttoptr,
|
||
build_fpext, build_fptrunc, build_fp2si, build_si2fp, build_fp2ui, build_ui2fp,
|
||
build_gep, build_gep_array, build_gep_struct,
|
||
build_phi, build_switch, build_select, build_unreachable,
|
||
build_atomicrmw, build_cmpxchg, build_inline_asm, build_va_arg,
|
||
PhiIncoming, new_phi_incoming, SwitchCase, new_switch_case,
|
||
ICMP_EQ, ICMP_NE, ICMP_SGT, ICMP_SGE, ICMP_SLT, ICMP_SLE,
|
||
ICMP_UGT, ICMP_UGE, ICMP_ULT, ICMP_ULE,
|
||
FCMP_FALSE, FCMP_OEQ, FCMP_OGT, FCMP_OGE, FCMP_OLT, FCMP_OLE,
|
||
FCMP_ONE, FCMP_ORD, FCMP_UNO, FCMP_UEQ, FCMP_UGT, FCMP_UGE,
|
||
FCMP_ULT, FCMP_ULE, FCMP_UNE, FCMP_TRUE,
|
||
ATOMIC_XCHG, ATOMIC_ADD, ATOMIC_SUB, ATOMIC_AND, ATOMIC_NAND,
|
||
ATOMIC_OR, ATOMIC_XOR, ATOMIC_MAX, ATOMIC_MIN, ATOMIC_UMAX, ATOMIC_UMIN,
|
||
ATOMIC_ORDER_NOTATOMIC, ATOMIC_ORDER_UNORDERED, ATOMIC_ORDER_MONOTONIC,
|
||
ATOMIC_ORDER_ACQUIRE, ATOMIC_ORDER_RELEASE, ATOMIC_ORDER_ACQ_REL,
|
||
ATOMIC_ORDER_SEQ_CST,
|
||
)
|
||
from .__verify import (
|
||
VerifyResult, NameEntry,
|
||
verify_function, verify_module,
|
||
VERIFY_OK, VERIFY_ERR_NO_TERMINATOR, VERIFY_ERR_DUPLICATE_DEF,
|
||
VERIFY_ERR_UNDEFINED_USE, VERIFY_ERR_PHI_NOT_FIRST,
|
||
_new_verify_result,
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# Convenience factory: string copy
|
||
# ============================================================
|
||
def _str_dup(pool: memhub.MemBuddy*, src: str) -> str:
|
||
"""Allocate from pool and copy a C string."""
|
||
if src is None: return None
|
||
slen: sizet = string.strlen(src)
|
||
buf: str = pool.alloc(slen + 1)
|
||
if buf is None: return None
|
||
string.strcpy(buf, src)
|
||
return buf
|
||
|
||
|
||
# ============================================================
|
||
# Convenience factory: create a named basic block
|
||
# ============================================================
|
||
def create_block(pool: memhub.MemBuddy*, func: Function*,
|
||
name: str) -> BasicBlock*:
|
||
"""Create a basic block and add it to the function."""
|
||
# Block-name/param conflict guard: block label "entry:" is value %entry, so a param %entry
|
||
# collides; on collision append .1/.2/... (strip a leading % from Param.Name before comparing).
|
||
use_name: str = name
|
||
if func is not None and func.Params is not None and name is not None \
|
||
and name[0] != '\0':
|
||
cb_len: sizet = string.strlen(name) + 16
|
||
cb_buf: str = pool.alloc(cb_len)
|
||
if cb_buf is not None:
|
||
cb_k: int = 1
|
||
cb_collide: int = 1
|
||
while cb_collide != 0:
|
||
cb_collide = 0
|
||
cb_p: Param* = function_get_param_head(func)
|
||
while cb_p is not None:
|
||
cb_pnm: str = param_get_name(cb_p)
|
||
if cb_pnm is not None:
|
||
cb_pv: str = cb_pnm
|
||
if cb_pv[0] == '%':
|
||
cb_pv = cb_pv + 1
|
||
if string.strcmp(cb_pv, use_name) == 0:
|
||
cb_collide = 1
|
||
break
|
||
cb_p = param_get_next(cb_p)
|
||
if cb_collide != 0:
|
||
viperlib.snprintf(cb_buf, cb_len, "%s.%d", name, cb_k)
|
||
use_name = cb_buf
|
||
cb_k += 1
|
||
name_dup: str = _str_dup(pool, use_name)
|
||
blk: BasicBlock* = new_basic_block(pool, name_dup)
|
||
if blk is not None:
|
||
function_add_block(func, blk)
|
||
return blk
|
||
|
||
|
||
# ============================================================
|
||
# Convenience factory: create an integer constant value
|
||
# ============================================================
|
||
def const_int(pool: memhub.MemBuddy*, bits: int,
|
||
val: int64) -> Value*:
|
||
"""Create an integer constant value (auto-creates type + name text).
|
||
bits: bit width (1/8/16/32/64); val: integer value.
|
||
"""
|
||
# create type
|
||
ty: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if ty is None: return None
|
||
string.memset(ty, 0, LLVMType.__sizeof__())
|
||
*ty = *LLVMType.Int(bits)
|
||
|
||
# create name text
|
||
name: str = pool.alloc(32)
|
||
if name is None: return None
|
||
# LLVM i64 literals exclude INT64_MIN: represent it as the unsigned bit pattern
|
||
# 9223372036854775808 (accepted by LLVM), else opt rejects "-9223372036854775808".
|
||
if bits == 64 and val == -9223372036854775807 - 1:
|
||
viperlib.snprintf(name, 32, "9223372036854775808")
|
||
else:
|
||
viperlib.snprintf(name, 32, "%lld", val)
|
||
|
||
return ConstInt(pool, ty, val, name)
|
||
|
||
|
||
def const_int32(pool: memhub.MemBuddy*, val: int64) -> Value*:
|
||
"""Convenience: create an i32 integer constant."""
|
||
return const_int(pool, 32, val)
|
||
|
||
|
||
def const_int64(pool: memhub.MemBuddy*, val: int64) -> Value*:
|
||
"""Convenience: create an i64 integer constant."""
|
||
return const_int(pool, 64, val)
|
||
|
||
|
||
# ============================================================
|
||
# Convenience factory: create a function and add it to the module
|
||
# ============================================================
|
||
def create_function(pool: memhub.MemBuddy*, mod: LLVMModule*,
|
||
name: str, ret_ty: LLVMType*) -> Function*:
|
||
"""Create a function and add it to the module."""
|
||
name_dup: str = _str_dup(pool, name)
|
||
func: Function* = new_function(pool, name_dup, ret_ty)
|
||
if func is not None:
|
||
module_add_function(mod, func)
|
||
return func
|
||
|
||
|
||
def add_param(pool: memhub.MemBuddy*, func: Function*,
|
||
ty: LLVMType*, name: str) -> Param*:
|
||
"""Create a parameter and add it to the function."""
|
||
name_dup: str = _str_dup(pool, name)
|
||
param: Param* = new_param(pool, ty, name_dup)
|
||
if param is not None:
|
||
function_add_param(func, param)
|
||
return param
|
||
|
||
|
||
# ============================================================
|
||
# Convenience factory: create a function declaration (declare)
|
||
# ============================================================
|
||
def create_declare(pool: memhub.MemBuddy*, mod: LLVMModule*,
|
||
name: str, ret_ty: LLVMType*) -> Function*:
|
||
"""Create a function declaration (declare, no body) and add it to the module.
|
||
Unlike create_function, it emits only: declare <ret_ty> @<name>(<params>)
|
||
"""
|
||
name_dup: str = _str_dup(pool, name)
|
||
func: Function* = new_function(pool, name_dup, ret_ty)
|
||
if func is not None:
|
||
func.IsDeclared = 1
|
||
module_add_function(mod, func)
|
||
return func
|
||
|
||
|
||
# ============================================================
|
||
# Convenience factory: create a string-constant global variable
|
||
#
|
||
# The initializer is stored in gv.Initializer as llvmlite-shaped LLVM syntax text (c"contents\00");
|
||
# the MLIR render stage (__module.vp/_MlirGlobalInit) converts it to the attribute form "contents".
|
||
# ============================================================
|
||
def create_global_string(pool: memhub.MemBuddy*, mod: LLVMModule*,
|
||
name: str, text: str) -> GlobalVariable*:
|
||
"""Create a string-constant global variable and add it to the module.
|
||
name: global name (e.g. ".str"); text: C string without trailing NUL (added automatically).
|
||
Returns: GlobalVariable pointer of type [len+1 x i8].
|
||
"""
|
||
if text is None: return None
|
||
slen: sizet = string.strlen(text)
|
||
# 显式收窄:LLVMType.Array 的 Count 是 int(i32),而 slen 是 sizet(u64);
|
||
# 这里按显式转换(非隐式窄化)取 i32(字符串长度远超 2^31 不现实)。
|
||
count: int = int(slen + 1) # includes the trailing NUL
|
||
|
||
# create i8 type
|
||
i8_ty: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if i8_ty is None: return None
|
||
string.memset(i8_ty, 0, LLVMType.__sizeof__())
|
||
*i8_ty = *LLVMType.Int(8)
|
||
|
||
# create [count x i8] array type
|
||
arr_ty: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if arr_ty is None: return None
|
||
string.memset(arr_ty, 0, LLVMType.__sizeof__())
|
||
*arr_ty = *LLVMType.Array(i8_ty, count)
|
||
|
||
# create global variable
|
||
name_dup: str = _str_dup(pool, name)
|
||
gv: GlobalVariable* = new_global_variable(pool, name_dup, arr_ty)
|
||
if gv is None: return None
|
||
|
||
# set attributes: private + unnamed_addr + constant
|
||
global_set_linkage(gv, "private")
|
||
global_set_unnamed_addr(gv, 1)
|
||
global_set_constant(gv, 1)
|
||
|
||
# Build initializer text c"contents\00": LLVM IR string literal c"<chars>" with " \ and
|
||
# non-printables escaped as \xx; buffer sized for worst case (4 bytes/char) + c"..." + \00 + "
|
||
init_buf: str = pool.alloc(slen * 4 + 8)
|
||
if init_buf is None: return None
|
||
pos: sizet = 0
|
||
init_buf[pos] = 'c'
|
||
pos += 1
|
||
init_buf[pos] = '"'
|
||
pos += 1
|
||
for i in range(int(slen)):
|
||
ch: uint8 = text[i]
|
||
if ch == 0x22 or ch == 0x5C or ch < 0x20 or ch >= 0x7F:
|
||
# escape: " -> \22, \ -> \5c, non-printable -> \xx
|
||
init_buf[pos] = '\\'
|
||
pos += 1
|
||
hi: int = (ch >> 4) & 0x0F
|
||
lo: int = ch & 0x0F
|
||
if hi < 10:
|
||
init_buf[pos] = char(0x30 + hi)
|
||
else:
|
||
init_buf[pos] = char(0x61 + hi - 10)
|
||
pos += 1
|
||
if lo < 10:
|
||
init_buf[pos] = char(0x30 + lo)
|
||
else:
|
||
init_buf[pos] = char(0x61 + lo - 10)
|
||
pos += 1
|
||
else:
|
||
init_buf[pos] = ch
|
||
pos += 1
|
||
# trailing NUL byte
|
||
init_buf[pos] = '\\'
|
||
pos += 1
|
||
init_buf[pos] = '0'
|
||
pos += 1
|
||
init_buf[pos] = '0'
|
||
pos += 1
|
||
init_buf[pos] = '"'
|
||
pos += 1
|
||
init_buf[pos] = '\0'
|
||
gv.Initializer = init_buf
|
||
|
||
module_add_global(mod, gv)
|
||
return gv
|
||
|
||
|
||
# ============================================================
|
||
# Convenience factory: create an integer global with an initializer
|
||
# ============================================================
|
||
def create_global_int(pool: memhub.MemBuddy*, mod: LLVMModule*,
|
||
name: str, bits: int, val: int64) -> GlobalVariable*:
|
||
"""Create an integer global variable and add it to the module.
|
||
MLIR output: llvm.mlir.global @<name>(<val> : i<bits>) : i<bits>
|
||
"""
|
||
# create integer type
|
||
ty: LLVMType* = pool.alloc(LLVMType.__sizeof__())
|
||
if ty is None: return None
|
||
string.memset(ty, 0, LLVMType.__sizeof__())
|
||
*ty = *LLVMType.Int(bits)
|
||
|
||
name_dup: str = _str_dup(pool, name)
|
||
gv: GlobalVariable* = new_global_variable(pool, name_dup, ty)
|
||
if gv is None: return None
|
||
|
||
# build initializer text "42" (the type is printed separately by the global render stage)
|
||
init_buf: str = pool.alloc(48)
|
||
if init_buf is None: return None
|
||
viperlib.snprintf(init_buf, 48, "%lld", val)
|
||
gv.Initializer = init_buf
|
||
|
||
module_add_global(mod, gv)
|
||
return gv
|
||
|
||
|
||
# ============================================================
|
||
# LLVMModuleCore: module/function/value creation core; wraps pool to avoid passing it repeatedly
|
||
#
|
||
# Usage:
|
||
# mcore: rawptr[LLVMModuleCore] = LLVMModuleCore(pool)
|
||
# mod: rawptr[LLVMModule] = mcore.NewModule("test")
|
||
# func: rawptr[Function] = mcore.CreateFunction(mod, "add", ty_i32)
|
||
# val: rawptr[Value] = mcore.ConstInt32(42)
|
||
# ============================================================
|
||
novirtual class LLVMModuleCore:
|
||
"""Module/function/value creation core; wraps pool so callers need not pass it."""
|
||
Pool: memhub.MemBuddy*
|
||
|
||
def __init__(self, pool: memhub.MemBuddy*):
|
||
self.Pool = pool
|
||
|
||
# ---- module operations ----
|
||
def NewModule(self, name: str) -> LLVMModule*:
|
||
return new_module(self.Pool, name)
|
||
|
||
def CreateFunction(self, mod: LLVMModule*, name: str,
|
||
ret_ty: LLVMType*) -> Function*:
|
||
return create_function(self.Pool, mod, name, ret_ty)
|
||
|
||
def CreateDeclare(self, mod: LLVMModule*, name: str,
|
||
ret_ty: LLVMType*) -> Function*:
|
||
"""Create a function declaration (declare, no body)."""
|
||
return create_declare(self.Pool, mod, name, ret_ty)
|
||
|
||
def AddParam(self, func: Function*, ty: LLVMType*,
|
||
name: str) -> Param*:
|
||
return add_param(self.Pool, func, ty, name)
|
||
|
||
def FunctionSetAttrs(self, func: Function*, attrs: str):
|
||
"""Set the function-level attribute text."""
|
||
function_set_attrs(func, attrs)
|
||
|
||
def ParamSetAttrs(self, param: Param*, attrs: str):
|
||
"""Set the parameter attribute text."""
|
||
param_set_attrs(param, attrs)
|
||
|
||
def CreateBlock(self, func: Function*,
|
||
name: str) -> BasicBlock*:
|
||
return create_block(self.Pool, func, name)
|
||
|
||
def NewBuilder(self, func: Function*) -> IRBuilder*:
|
||
return new_builder(self.Pool, func)
|
||
|
||
# ---- globals ----
|
||
def NewGlobal(self, mod: LLVMModule*, name: str,
|
||
ty: LLVMType*) -> GlobalVariable*:
|
||
"""Create a global variable and add it to the module."""
|
||
name_dup: str = _str_dup(self.Pool, name)
|
||
gv: GlobalVariable* = new_global_variable(self.Pool, name_dup, ty)
|
||
if gv is not None:
|
||
module_add_global(mod, gv)
|
||
return gv
|
||
|
||
def CreateGlobalString(self, mod: LLVMModule*, name: str,
|
||
text: str) -> GlobalVariable*:
|
||
"""Create a string-constant global variable."""
|
||
return create_global_string(self.Pool, mod, name, text)
|
||
|
||
def CreateGlobalInt(self, mod: LLVMModule*, name: str,
|
||
bits: int, val: int64) -> GlobalVariable*:
|
||
"""Create an integer global with an initializer."""
|
||
return create_global_int(self.Pool, mod, name, bits, val)
|
||
|
||
def GlobalSetConstant(self, gv: GlobalVariable*, is_const: int):
|
||
global_set_constant(gv, is_const)
|
||
|
||
def GlobalSetLinkage(self, gv: GlobalVariable*, linkage: str):
|
||
global_set_linkage(gv, linkage)
|
||
|
||
def GlobalSetUnnamedAddr(self, gv: GlobalVariable*, level: int):
|
||
global_set_unnamed_addr(gv, level)
|
||
|
||
def GlobalSetSection(self, gv: GlobalVariable*, section: str):
|
||
global_set_section(gv, section)
|
||
|
||
# ---- value creation ----
|
||
def ConstInt(self, bits: int, val: int64) -> Value*:
|
||
return const_int(self.Pool, bits, val)
|
||
|
||
def ConstInt32(self, val: int64) -> Value*:
|
||
return const_int32(self.Pool, val)
|
||
|
||
def ConstInt64(self, val: int64) -> Value*:
|
||
return const_int64(self.Pool, val)
|
||
|
||
def ConstFloat(self, ty: LLVMType*, val: double) -> Value*:
|
||
return ConstFloat(self.Pool, ty, val)
|
||
|
||
def SSAValue(self, ty: LLVMType*, name: str) -> Value*:
|
||
return SSAValue(self.Pool, ty, name)
|
||
|
||
def ConstNull(self, ty: LLVMType*, name: str) -> Value*:
|
||
return ConstNull(self.Pool, ty, name)
|
||
|
||
# ---- printing ----
|
||
def PrintValue(self, buf: str, size: sizet, val: Value*):
|
||
ValuePrint(buf, size, val, self.Pool)
|
||
|
||
def PrintFunction(self, buf: str, size: sizet, func: Function*):
|
||
FunctionPrint(buf, size, func, self.Pool)
|
||
|
||
def PrintModule(self, buf: str, size: sizet, mod: LLVMModule*):
|
||
LLVMModulePrint(buf, size, mod, self.Pool, OUTPUT_FULL)
|
||
|
||
# ---- SSA verification ----
|
||
def NewVerifyResult(self, buf_size: sizet) -> VerifyResult*:
|
||
"""Create a verify-result object (buf_size = error message buffer size)."""
|
||
return _new_verify_result(self.Pool, buf_size)
|
||
|
||
def VerifyFunction(self, func: Function*,
|
||
result: VerifyResult*) -> int:
|
||
"""Verify SSA correctness of one function; returns error count (0 = pass)."""
|
||
return verify_function(self.Pool, func, result)
|
||
|
||
def VerifyModule(self, mod: LLVMModule*,
|
||
result: VerifyResult*) -> int:
|
||
"""Verify SSA correctness of all functions in the module; returns error count (0 = pass)."""
|
||
return verify_module(self.Pool, mod, result)
|