fix(asm): 内联汇编/LLVMIR 文本缓冲扩容并将所有超限改为 fail-fast
ASM_TEXT_BUF_SIZE 2048→8192(内核 _do_switch 模板已 2245 字节),c.LLVMIR 模板 512→4096。_asm_append_cstr 不再静默截断:装不下时报 TypeError 并给出已用/待写/容量字节数(此前截断会把模板切成半条 mov,最终以 llc invalid operand 的面目出现)。操作数超过 16、clobber 超过 32、符号改写缓冲溢出同样 fail-fast;约束串/参数串/extractvalue 行缓冲改为按实际操作数与类型文本长度动态分配。
This commit is contained in:
1 parent
201c881b43
commit
15952d3852
1 file changed
+80
-41
@@ -20,12 +20,18 @@ define ASM_MAX_OPERANDS = 16
|
||||
# Clobber (~{...}) capacity is decoupled from the operand limit: vipersimd x8
|
||||
# needs 21 entries, so ASM_MAX_OPERANDS=16 silently truncated it to 16.
|
||||
define ASM_MAX_CLOBBERS = 32
|
||||
define ASM_TEXT_BUF_SIZE = 2048
|
||||
# ASM_TEXT_BUF_SIZE holds the whole inline-asm template (a kernel switch statement
|
||||
# template is already 2245 bytes). Overflow is fail-fast in _asm_append_cstr, never
|
||||
# silently truncated: a cut-in-half template used to surface as a bogus llc parse
|
||||
# error (e.g. a dangling `mov` with its operands chopped off).
|
||||
define ASM_TEXT_BUF_SIZE = 8192
|
||||
# Constraint buffer base size; the real capacity scales with operand/clobber count.
|
||||
define ASM_CONST_BUF_SIZE = 512
|
||||
define ASM_ARGS_BUF_SIZE = 512
|
||||
define ASM_LINE_BUF_SIZE = 4096
|
||||
define LLVMIR_MAX_OPS = 16
|
||||
define LLVMIR_TEMPLATE_BUF = 512
|
||||
# Raw LLVM IR templates (c.LLVMIR) are multi-instruction text, not one-liners.
|
||||
define LLVMIR_TEMPLATE_BUF = 4096
|
||||
|
||||
|
||||
# CallGenCBuiltin - c.*/asm/llvmir builtins
|
||||
@@ -441,15 +447,22 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
return loaded
|
||||
|
||||
|
||||
def _asm_append_cstr(self, dst: str, dst_size: sizet, src: str):
|
||||
"""Append the C string src to the end of dst."""
|
||||
def _asm_append_cstr(self, dst: str, dst_size: sizet, src: str, what: str = "inline asm"):
|
||||
"""Append the C string src to the end of dst; fail-fast when it does not fit.
|
||||
|
||||
Silent truncation corrupts the generated text (a template cut in the middle of an
|
||||
instruction yields a bogus llc/verifier error much later), so overflow is reported
|
||||
here at the real cause.
|
||||
"""
|
||||
if dst is None or src is None: return
|
||||
dlen: sizet = string.strlen(dst)
|
||||
slen: sizet = string.strlen(src)
|
||||
remain: sizet = dst_size - dlen
|
||||
if remain <= 0: return
|
||||
# Capacity includes the terminating NUL.
|
||||
if dlen + slen + 1 > dst_size:
|
||||
self.Gen.TypeGenErr.fatal_error(None, f"{what} text does not fit the fixed buffer: {dlen} byte(s) already used + {slen} byte(s) to append + 1 NUL > capacity {dst_size} byte(s); shorten or split the template (silent truncation would generate broken code)", "TypeError")
|
||||
return
|
||||
i: sizet = 0
|
||||
while i < slen and i + 1 < remain:
|
||||
while i < slen:
|
||||
dst[dlen + i] = src[i]
|
||||
i += 1
|
||||
dst[dlen + i] = '\0'
|
||||
@@ -492,9 +505,14 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
return asm_text
|
||||
cur = cur.Next
|
||||
if matched == 0:
|
||||
if out_pos + 1 < asm_size * 2:
|
||||
out[out_pos] = asm_text[i]
|
||||
out_pos += 1
|
||||
# The output buffer is 2x the input, so this cannot trigger for a plain copy;
|
||||
# keep it fail-fast so a future longer rewrite (mangled expansion) can never
|
||||
# silently drop characters.
|
||||
if out_pos + 1 >= asm_size * 2:
|
||||
self.Gen.TypeGenErr.fatal_error(None, f"inline asm text overflows the symbol-mangling buffer (capacity {asm_size * 2} byte(s)); shorten the template", "TypeError")
|
||||
return asm_text
|
||||
out[out_pos] = asm_text[i]
|
||||
out_pos += 1
|
||||
i += 1
|
||||
out[out_pos] = '\0'
|
||||
return out
|
||||
@@ -729,7 +747,11 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
|
||||
def _asm_add_operand(self, operands: GenTypes.AsmOperand*, count: int, val: mlirlite.Value*, constraint: str, is_output: int) -> int:
|
||||
"""Append an operand, dedup by value and direction, and return the new count."""
|
||||
if count >= ASM_MAX_OPERANDS: return count
|
||||
# fail-fast: the operand array is fixed at ASM_MAX_OPERANDS slots; dropping the
|
||||
# excess operand here would silently change the generated constraint/argument lists.
|
||||
if count >= ASM_MAX_OPERANDS:
|
||||
self.Gen.TypeGenErr.fatal_error(None, f"inline asm has more than {ASM_MAX_OPERANDS} operands (compiler limit); split the asm block into several ones", "TypeError")
|
||||
return count
|
||||
# fail-fast: AsmInp/AsmOut operands must not carry SHA1-mangled symbols;
|
||||
# LLVM MC cannot parse digit-leading names, so mark them t.CExport.
|
||||
if val is not None and val.Name is not None:
|
||||
@@ -956,11 +978,15 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
if celt is None: continue
|
||||
cstr: str = self._resolve_asm_descr(pool, celt)
|
||||
if cstr is None or cstr[0] == '\0': continue
|
||||
if clobber_count < ASM_MAX_CLOBBERS:
|
||||
cl_addr: uint64 = uint64(clobbers) + clobber_count * 8
|
||||
cl_ptr: str* = (str*)(cl_addr)
|
||||
cl_ptr[0] = cstr
|
||||
clobber_count += 1
|
||||
# fail-fast: the clobber slot array is fixed at ASM_MAX_CLOBBERS;
|
||||
# dropping the excess would silently weaken the clobber list.
|
||||
if clobber_count >= ASM_MAX_CLOBBERS:
|
||||
self.Gen.TypeGenErr.fatal_error(celt, f"inline asm has more than {ASM_MAX_CLOBBERS} clobbers (compiler limit); remove duplicates or split the asm block", "TypeError")
|
||||
return None
|
||||
cl_addr: uint64 = uint64(clobbers) + clobber_count * 8
|
||||
cl_ptr: str* = (str*)(cl_addr)
|
||||
cl_ptr[0] = cstr
|
||||
clobber_count += 1
|
||||
|
||||
# 3. Parse the second positional arg as the clobber list
|
||||
if len(cargs) >= 2:
|
||||
@@ -975,11 +1001,13 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
if pelt is None: continue
|
||||
pstr: str = self._resolve_asm_descr(pool, pelt)
|
||||
if pstr is None or pstr[0] == '\0': continue
|
||||
if clobber_count < ASM_MAX_CLOBBERS:
|
||||
pcl_addr: uint64 = uint64(clobbers) + clobber_count * 8
|
||||
pcl_ptr: str* = (str*)(pcl_addr)
|
||||
pcl_ptr[0] = pstr
|
||||
clobber_count += 1
|
||||
if clobber_count >= ASM_MAX_CLOBBERS:
|
||||
self.Gen.TypeGenErr.fatal_error(pelt, f"inline asm has more than {ASM_MAX_CLOBBERS} clobbers (compiler limit); remove duplicates or split the asm block", "TypeError")
|
||||
return None
|
||||
pcl_addr: uint64 = uint64(clobbers) + clobber_count * 8
|
||||
pcl_ptr: str* = (str*)(pcl_addr)
|
||||
pcl_ptr[0] = pstr
|
||||
clobber_count += 1
|
||||
|
||||
# 4. Count outputs/inputs and determine the return type
|
||||
output_count: int = 0
|
||||
@@ -1106,8 +1134,12 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
cn4: ast.Constant* = ast.Constant*(tmpl_node)
|
||||
if cn4.const_kind == ast.CONST_STR and cn4.str_val is not None: self._asm_append_cstr(asm_text, ASM_TEXT_BUF_SIZE, cn4.str_val)
|
||||
|
||||
# 5. Build the constraint string in reordered order (outputs first)
|
||||
const_buf: str = pool.alloc(ASM_CONST_BUF_SIZE)
|
||||
# 5. Build the constraint string in reordered order (outputs first).
|
||||
# Capacity scales with the operand/clobber count: a 32-entry clobber list alone is
|
||||
# ~300 bytes, so the old fixed 512-byte cap could silently cut it. Overflow of this
|
||||
# (or any asm text buffer) is fail-fast in _asm_append_cstr.
|
||||
const_cap: sizet = ASM_CONST_BUF_SIZE + sizet(operand_count + clobber_count + 1) * 64
|
||||
const_buf: str = pool.alloc(const_cap)
|
||||
if const_buf is None: return None
|
||||
const_buf[0] = '\0'
|
||||
|
||||
@@ -1118,29 +1150,31 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
cons_val: str = cop.Constraint
|
||||
if cons_val is None: cons_val = "r"
|
||||
|
||||
if first_const == 0: self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, ",")
|
||||
if first_const == 0: self._asm_append_cstr(const_buf, const_cap, ",")
|
||||
first_const = 0
|
||||
|
||||
if cop.IsOutput != 0:
|
||||
# Output constraint: ensure it starts with =
|
||||
if cons_val[0] != '=': self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, "=")
|
||||
self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, cons_val)
|
||||
else: self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, cons_val)
|
||||
if cons_val[0] != '=': self._asm_append_cstr(const_buf, const_cap, "=")
|
||||
self._asm_append_cstr(const_buf, const_cap, cons_val)
|
||||
else: self._asm_append_cstr(const_buf, const_cap, cons_val)
|
||||
|
||||
# Append clobber constraints ~{name}
|
||||
for cidx in range(int(clobber_count)):
|
||||
cl_addr2: uint64 = uint64(clobbers) + cidx * 8
|
||||
cname: str = (str*)(cl_addr2)[0]
|
||||
if cname is None or cname[0] == '\0': continue
|
||||
if first_const == 0: self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, ",")
|
||||
if first_const == 0: self._asm_append_cstr(const_buf, const_cap, ",")
|
||||
first_const = 0
|
||||
self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, "~{")
|
||||
self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, cname)
|
||||
self._asm_append_cstr(const_buf, ASM_CONST_BUF_SIZE, "}")
|
||||
self._asm_append_cstr(const_buf, const_cap, "~{")
|
||||
self._asm_append_cstr(const_buf, const_cap, cname)
|
||||
self._asm_append_cstr(const_buf, const_cap, "}")
|
||||
|
||||
# 6. Build the argument list text (input operands only): each arg is type text (an
|
||||
# inlined named struct can reach 512 bytes) plus the value name, so scale the buffer.
|
||||
asm_arg_cap: sizet = sizet(operand_count + 1) * 544 + 64
|
||||
# 6. Build the argument list text (input operands only): each arg is type text
|
||||
# (TypePrint is given a 4096-byte buffer below) plus the value name, so size the
|
||||
# buffer from that worst case instead of underestimating it (544 bytes/operand
|
||||
# silently cut wide inlined named structs).
|
||||
asm_arg_cap: sizet = sizet(operand_count + 1) * 4224 + 64
|
||||
args_buf: str = pool.alloc(asm_arg_cap)
|
||||
if args_buf is None: return None
|
||||
args_buf[0] = '\0'
|
||||
@@ -1215,12 +1249,17 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
# Multi-output: extractvalue index out_emit_idx, then store
|
||||
if oo.Value is not None and oo.Value.Ty is not None and oo.Value.Ty.Pointee != 0:
|
||||
ev_name: str = pool.alloc(32)
|
||||
ev_line: str = pool.alloc(ASM_LINE_BUF_SIZE)
|
||||
# Size the line from its actual parts: ret_ty_buf alone can be ~4096
|
||||
# bytes, so the old fixed cap could cut the instruction in half.
|
||||
ev_cap: sizet = ASM_LINE_BUF_SIZE + 64
|
||||
if ret_ty_buf is not None: ev_cap += string.strlen(ret_ty_buf)
|
||||
if ssa_name is not None: ev_cap += string.strlen(ssa_name)
|
||||
ev_line: str = pool.alloc(ev_cap)
|
||||
if ev_name is not None and ev_line is not None:
|
||||
# ssa_name is "%N"; build a unique "%asm.N.e{i}" (letter-
|
||||
# initial) so %N.e{i} is not parsed as id plus a bad suffix.
|
||||
viperlib.snprintf(ev_name, 32, "%%asm.%s.e%d", ssa_name + 1, out_emit_idx)
|
||||
viperlib.snprintf(ev_line, ASM_LINE_BUF_SIZE, "%s = extractvalue %s %s, %d", ev_name, ret_ty_buf, ssa_name, out_emit_idx)
|
||||
viperlib.snprintf(ev_line, ev_cap, "%s = extractvalue %s %s, %d", ev_name, ret_ty_buf, ssa_name, out_emit_idx)
|
||||
mlirlite.block_append_text(pool, builder.CurBlock, ev_line)
|
||||
ev_val: mlirlite.Value* = mlirlite.SSAValue(pool, oo.Value.Ty.Pointee, ev_name)
|
||||
mlirlite.build_store(builder, ev_val, oo.Value)
|
||||
@@ -1271,7 +1310,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
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)
|
||||
if cn.str_val is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn.str_val, "c.LLVMIR")
|
||||
|
||||
elif pk == ast.ASTKind.FormattedValue:
|
||||
fv: ast.FormattedValue* = ast.FormattedValue*(part)
|
||||
@@ -1289,7 +1328,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
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)
|
||||
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")
|
||||
|
||||
elif string.strcmp(attr_nm, "LOut") == 0:
|
||||
# Output operand: allocate a unique SSA register
|
||||
@@ -1303,21 +1342,21 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
|
||||
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)
|
||||
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
|
||||
continue
|
||||
# Non-LInp/LOut f-string expression: translate and embed the value 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)
|
||||
if ival is not None and ival.Name is not None: self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, ival.Name, "c.LLVMIR")
|
||||
|
||||
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)
|
||||
self._asm_append_cstr(text, LLVMIR_TEMPLATE_BUF, cn2.str_val, "c.LLVMIR")
|
||||
|
||||
# 2. Embed the template text directly (no instruction parsing) + LOut writeback
|
||||
mlirlite.block_append_text(pool, builder.CurBlock, text)
|
||||
|
||||
Reference in new issue
Block a user