274 lines
10 KiB
Plaintext
274 lines
10 KiB
Plaintext
from stdint import *
|
||
import string
|
||
import viperlib
|
||
import stdio
|
||
import memhub
|
||
from linkedlist import GSListNode
|
||
from .__types import LLVMType, TypePrint
|
||
|
||
|
||
# ============================================================
|
||
# Value: SSA value representation
|
||
#
|
||
# Represents an LLVM IR value: an SSA temporary (%0/%result) or a constant literal (42/0).
|
||
# Each value has a type pointer, a name, and an is-constant flag.
|
||
# Inherits GSListNode[Value] to get a strongly-typed Next: Value|CPtr (the value pool list)
|
||
#
|
||
# Note: novirtual must be added (see the Line comment in __function.py for the reason).
|
||
# ============================================================
|
||
novirtual class Value(GSListNode[Value]):
|
||
Ty: LLVMType* # value type
|
||
Name: str # SSA name ("%0"/"%result") or constant text ("42"/"1.5e+00")
|
||
IsConst: int # 1 = constant literal, 0 = SSA temporary
|
||
IntVal: int64 # integer constant value (used when IsConst=1)
|
||
FloatVal: double # float constant value (used when IsConst=1 and Ty is Float)
|
||
|
||
|
||
# ============================================================
|
||
# Factory functions
|
||
# ============================================================
|
||
# Value hardcoded size: Next(8) + Ty(8) + Name(8) + IsConst(4) + IntVal(8) + FloatVal(8) = 48 bytes
|
||
define VALUE_SIZE = 48
|
||
|
||
def new_value(pool: memhub.MemBuddy*) -> Value*:
|
||
"""Allocate a zero-initialized Value from mpool."""
|
||
ptr: Value* = pool.alloc(VALUE_SIZE)
|
||
if ptr:
|
||
string.memset(ptr, 0, VALUE_SIZE)
|
||
return ptr
|
||
|
||
|
||
def ConstInt(pool: memhub.MemBuddy*, ty: LLVMType*,
|
||
val: int64, name: str) -> Value*:
|
||
"""Create an integer constant value.
|
||
ty: integer type (Int1/Int8/Int32/Int64); val: integer value; name: caller-provided constant text (e.g. "42").
|
||
"""
|
||
v: Value* = new_value(pool)
|
||
if v is None: return None
|
||
v.Ty = ty
|
||
v.Name = name
|
||
v.IsConst = 1
|
||
v.IntVal = val
|
||
return v
|
||
|
||
|
||
def ConstNull(pool: memhub.MemBuddy*, ty: LLVMType*,
|
||
name: str) -> Value*:
|
||
"""Create a null pointer constant."""
|
||
v: Value* = new_value(pool)
|
||
if v is None: return None
|
||
v.Ty = ty
|
||
v.Name = name
|
||
v.IsConst = 1
|
||
v.IntVal = 0
|
||
return v
|
||
|
||
|
||
def ConstZero(pool: memhub.MemBuddy*, ty: LLVMType*) -> Value*:
|
||
"""Create a zeroinitializer constant (zero-initialized aggregate: struct/array)."""
|
||
v: Value* = new_value(pool)
|
||
if v is None: return None
|
||
v.Ty = ty
|
||
v.Name = "zeroinitializer"
|
||
v.IsConst = 1
|
||
v.IntVal = 0
|
||
return v
|
||
|
||
|
||
def _HexDigits(out: str, pos: int, value: uint64, nibbles: int) -> int:
|
||
"""Write exactly `nibbles` hex digits (high nibble first) of value at out[pos...]; return the new pos.
|
||
|
||
逐 nibble 自己写,不用 snprintf 的 %x/%llx:现场出现过负位型被符号扩展成 16 位文本
|
||
(值 '0xffffffffc0490fdb' 配 f32 类型),MLIR 渲染阶段直接判定位宽不匹配 → 整个模块编不过。
|
||
自己写保证"位数 == nibbles",与 MLIR 侧按类型取位宽的口径**构造上一致**。
|
||
"""
|
||
digits: str = "0123456789abcdef"
|
||
i: int = 0
|
||
while i < nibbles:
|
||
shift: int = (nibbles - 1 - i) * 4
|
||
out[pos + i] = digits[int((value >> shift) & 15)]
|
||
i += 1
|
||
return pos + nibbles
|
||
|
||
|
||
def ConstFloat(pool: memhub.MemBuddy*, ty: LLVMType*,
|
||
val: double) -> Value*:
|
||
"""Create a float constant value.
|
||
|
||
ty: float type; val: float value. Name is the raw bit pattern (f16 0x%04x, f32 0x%08x,
|
||
f64 0x%016llx, f128 0x%032x hi+lo) so MLIR FloatAttr is lossless; use CRT stdio.snprintf (%g in viperlib is buggy).
|
||
|
||
位宽取类型对象自带的 LLVMType.Bits(f16/f32/f64/f128 ↔ 4/8/16/32 位十六进制),
|
||
且十六进制逐 nibble 自写(_HexDigits)⇒ 名字位数严格等于类型位数,与 MLIR 渲染器按同一
|
||
Bits 推出的期望位数一致(不再用 snprintf:它会把高位置位的 32 位值打印成 16 位)。
|
||
"""
|
||
v: Value* = new_value(pool)
|
||
if v is None: return None
|
||
v.Ty = ty
|
||
v.IsConst = 1
|
||
v.FloatVal = val
|
||
v.IntVal = 0
|
||
# 十六进制位数 = 该浮点类型的位宽/4(f16/f32/f64/f128 ↔ 4/8/16/32)。
|
||
# 位宽取 LLVMType.Bits:类型对象自带,且渲染器打印类型文本用的也是同一个 Bits
|
||
# ⇒ 名字位数与渲染器期望位数一致。
|
||
# 注意**不要**在这里调 MlirTypeText:它的第 3 个参数是"LLVM 类型文本字符串"而不是
|
||
# LLVMType*,传类型对象会得到空文本并 fail-fast
|
||
# (现场:cannot convert to MLIR type text (v1 does not cover this form): '')。
|
||
# 旧实现的真凶是 printf(对高位置位的 32 位值按符号扩展后的 64 位打印 → 16 位文本),
|
||
# 这里改成 _HexDigits 逐 nibble 写,位数严格等于 nibbles。
|
||
f_bits: int = 64
|
||
match ty:
|
||
case LLVMType.Float(ftb):
|
||
f_bits = ftb
|
||
nibbles: int = f_bits // 4
|
||
if nibbles != 4 and nibbles != 8 and nibbles != 16 and nibbles != 32: nibbles = 16
|
||
name_buf: str = pool.alloc(64)
|
||
if name_buf is not None:
|
||
name_buf[0] = '0'
|
||
name_buf[1] = 'x'
|
||
endp: int = 2
|
||
if nibbles == 4:
|
||
# half: round to half first (double -> half is fptrunc), then take its 2-byte bit pattern
|
||
h16: float16 = float16(val)
|
||
b16: uint16 = 0
|
||
*&b16 = *&h16
|
||
endp = _HexDigits(name_buf, 2, uint64(b16), 4)
|
||
elif nibbles == 8:
|
||
# f32 bit pattern must be the float 32-bit encoding, not the low 32 bits of the double
|
||
# (3.14 double=0x40091EB851EB851F, low 0x51EB851F != f32 0x4048F5C3): round to f32 first.
|
||
f32v: float = float(val)
|
||
b32: uint32 = 0
|
||
*&b32 = *&f32v
|
||
endp = _HexDigits(name_buf, 2, uint64(b32), 8)
|
||
elif nibbles == 32:
|
||
# quad: double -> fp128 is lossless (fpext); little-endian low 64 first, print big-endian
|
||
# (hi first). c.Load width follows the source pointee, so cast the source to u64* to avoid overflow.
|
||
q128: float128 = float128(val)
|
||
q8: uint8* = (uint8*)(&q128)
|
||
q_lo: uint64 = 0
|
||
q_hi: uint64 = 0
|
||
*&q_lo = *(uint64*)(q8)
|
||
*&q_hi = *(uint64*)(q8 + 8)
|
||
endp = _HexDigits(name_buf, 2, q_hi, 16)
|
||
endp = _HexDigits(name_buf, endp, q_lo, 16)
|
||
else:
|
||
fv_bits: uint64 = 0
|
||
*&fv_bits = *&val
|
||
endp = _HexDigits(name_buf, 2, fv_bits, 16)
|
||
name_buf[endp] = '\0'
|
||
v.Name = name_buf
|
||
return v
|
||
|
||
|
||
def SSAValue(pool: memhub.MemBuddy*, ty: LLVMType*,
|
||
name: str) -> Value*:
|
||
"""Create an SSA temporary value.
|
||
ty: value type; name: caller-provided SSA name (e.g. "%0"/"%result").
|
||
"""
|
||
v: Value* = new_value(pool)
|
||
if v is None: return None
|
||
v.Ty = ty
|
||
v.Name = name
|
||
v.IsConst = 0
|
||
v.IntVal = 0
|
||
return v
|
||
|
||
|
||
# ============================================================
|
||
# ValuePrint: serialize a value to MLIR text (diagnostics only)
|
||
#
|
||
# SSA value -> "%0" (operands are bare SSA names); constant -> "42 : i32" (MLIR attribute form).
|
||
# Instruction rendering MUST NOT depend on this; op rendering goes through __function.vp (constants
|
||
# must first be materialized as llvm.mlir.constant/zero/addressof ops).
|
||
# ============================================================
|
||
def ValuePrint(buf: str, size: sizet,
|
||
val: Value*, pool: memhub.MemBuddy*):
|
||
"""Serialize val into buf as MLIR text; pool is used to allocate scratch buffers for
|
||
recursive complex types (Array/Struct/Func).
|
||
"""
|
||
if val is None or buf is None or size == 0:
|
||
return
|
||
buf[0] = '\0'
|
||
if val.Name is None:
|
||
return
|
||
if val.IsConst == 0:
|
||
# SSA value: emit only the SSA name
|
||
_append_str(buf, size, val.Name)
|
||
return
|
||
# constant: "<constant text> : <MLIR type>"
|
||
_append_str(buf, size, val.Name)
|
||
_append_str(buf, size, " : ")
|
||
tp: str = pool.alloc_tagged(4096, memhub.DETSITE_VALUES_168)
|
||
if tp is not None:
|
||
TypePrint(tp, 4096, val.Ty, pool)
|
||
_append_str(buf, size, tp)
|
||
|
||
|
||
# ============================================================
|
||
# Value field accessors (let other modules bypass stub-type limits)
|
||
#
|
||
# Cross-module callers only see a stub Value (missing/wrong fields), so TransPyC silently skips
|
||
# field access; these accessors run inside this module with the full type definition.
|
||
# ============================================================
|
||
def value_get_ty(val: Value*) -> LLVMType*:
|
||
"""Get the value's type."""
|
||
if val is None:
|
||
return None
|
||
return val.Ty
|
||
|
||
|
||
def value_get_next(val: Value*) -> Value*:
|
||
"""Get the next value in the list."""
|
||
if val is None:
|
||
return None
|
||
return val.Next
|
||
|
||
|
||
def value_set_ty(val: Value*, ty: LLVMType*):
|
||
"""Set the value's type."""
|
||
if val is None:
|
||
return
|
||
val.Ty = ty
|
||
|
||
|
||
def value_set_next(val: Value*, nxt: Value*):
|
||
"""Set the next value in the list."""
|
||
if val is None:
|
||
return
|
||
val.Next = nxt
|
||
|
||
|
||
def value_set_name(val: Value*, name: str):
|
||
"""Set the value's name."""
|
||
if val is None:
|
||
return
|
||
val.Name = name
|
||
|
||
|
||
def value_set_isconst(val: Value*, is_const: int):
|
||
"""Set whether the value is a constant."""
|
||
if val is None:
|
||
return
|
||
val.IsConst = is_const
|
||
|
||
|
||
# ============================================================
|
||
# Internal helpers
|
||
# ============================================================
|
||
def _append_str(dst: str, dst_size: sizet, src: str):
|
||
"""Append src to the end of dst."""
|
||
if dst is None or src is None: return
|
||
dlen: sizet = string.strlen(dst)
|
||
# Unsigned-underflow guard: if dlen >= buffer size (buffer corrupt/dangling) return early,
|
||
# else remain = dst_size - dlen underflows and dst[dlen+i] would write past the buffer.
|
||
if dlen >= dst_size:
|
||
return
|
||
slen: sizet = string.strlen(src)
|
||
remain: sizet = dst_size - dlen
|
||
if remain <= 0: return
|
||
i: sizet = 0
|
||
while i < slen and i + 1 < remain:
|
||
dst[dlen + i] = src[i]
|
||
i += 1
|
||
dst[dlen + i] = '\0'
|