fix(asm): 内联汇编输出约束修复(去重+多输出 struct)

- _asm_add_operand 去重: 模板内嵌 AsmOut/AsmInp 与 out=/inp= 声明同一变量不再重复收集
- ret_ty 多输出: output_count>1 返回匿名 struct, 元素数=输出数
- 输出处理: 多输出 extractvalue 逐元素 store(rdrand*_step 的 val+ok)
- extractvalue SSA 名改字母前缀 %asm.N.e{i} 避免数字 ID 后缀非法
This commit is contained in:
TermiNexus committed 2026-08-11 22:20:25 +08:00
1 parent 244c8efc96
commit e7b0fecb99
1 file changed
+72 -9
+72 -9
View File
@@ -1219,9 +1219,20 @@ class CallGenMixin:
val: llvmlite.Value | t.CPtr,
constraint: str,
is_output: int) -> int:
"""添加操作数到数组,返回新计数(失败返回原计数)"""
"""添加操作数到数组,返回新计数(失败返回原计数)
去重:f-string 模板内嵌的 AsmOut/AsmInp 与 out=/inp= 声明指向
同一变量时会重复收集(如 vrandom.vp 的 c.Asm(f"...{AsmOut(v)}",
out=[AsmOut(v)])),同一 (Value, IsOutput) 只保留第一个,
避免约束数多于 asm 返回元素数(llc: output constraints mismatch)。
"""
if count >= ASM_MAX_OPERANDS:
return count
if val is not None:
for ei in range(count):
eop: HandlesExprCall.AsmOperand | t.CPtr = self._asm_get_operand(operands, ei)
if eop is not None and eop.Value == val and eop.IsOutput == is_output:
return count
op: HandlesExprCall.AsmOperand | t.CPtr = self._asm_get_operand(operands, count)
op.Value = val
op.Constraint = constraint
@@ -1481,15 +1492,42 @@ class CallGenMixin:
input_count += 1
# 确定返回类型
# 多输出(如 vrandom rdrand*_step 的 val+ok 两个 AsmOut)返回
# 匿名 struct {ty1, ty2, ...},元素数 = 输出操作数(LLVM asm
# 要求输出约束数 = 返回 struct 元素数)。
ret_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Void(pool)
if output_count > 0 and first_output_idx >= 0:
out_op: HandlesExprCall.AsmOperand | t.CPtr = self._asm_get_operand(operands, first_output_idx)
if out_op.Value is not None and out_op.Value.Ty is not None:
# alloca 的类型是指针,Pointee 是目标类型
if out_op.Value.Ty.Pointee is not None:
ret_ty = out_op.Value.Ty.Pointee
if output_count > 1:
out_pn_head: llvmlite.ParamNode | t.CPtr = None
out_pn_last: llvmlite.ParamNode | t.CPtr = None
out_pn_n: int = 0
for oi5 in range(operand_count):
op5: HandlesExprCall.AsmOperand | t.CPtr = self._asm_get_operand(operands, oi5)
if op5 is None or op5.IsOutput == 0:
continue
if op5.Value is not None and op5.Value.Ty is not None \
and op5.Value.Ty.Pointee is not None:
pn5: llvmlite.ParamNode | t.CPtr = llvmlite.new_param_node(
pool, op5.Value.Ty.Pointee)
if pn5 is not None:
if out_pn_head is None:
out_pn_head = pn5
else:
out_pn_last.Next = pn5
out_pn_last = pn5
out_pn_n += 1
if out_pn_head is not None and out_pn_n > 0:
ret_ty = llvmlite.Struct(pool, out_pn_head, out_pn_n, None)
else:
ret_ty = llvmlite.Int32(pool)
else:
out_op: HandlesExprCall.AsmOperand | t.CPtr = self._asm_get_operand(operands, first_output_idx)
if out_op.Value is not None and out_op.Value.Ty is not None:
# alloca 的类型是指针,Pointee 是目标类型
if out_op.Value.Ty.Pointee is not None:
ret_ty = out_op.Value.Ty.Pointee
else:
ret_ty = llvmlite.Int32(pool)
# 判断 void — 直接基于 output_count 判断,避免 match 类型匹配问题
is_void: int = 1
@@ -1676,9 +1714,34 @@ class CallGenMixin:
# ============================================================
if is_void == 0 and first_output_idx >= 0:
result_val: llvmlite.Value | t.CPtr = llvmlite.SSAValue(pool, ret_ty, ssa_name)
out_op2: HandlesExprCall.AsmOperand | t.CPtr = self._asm_get_operand(operands, first_output_idx)
if out_op2.Value is not None:
llvmlite.build_store(builder, result_val, out_op2.Value)
out_emit_idx: int = 0
for oi_out in range(operand_count):
orig_o: int = new_order[oi_out]
oo: HandlesExprCall.AsmOperand | t.CPtr = self._asm_get_operand(operands, orig_o)
if oo is None or oo.IsOutput == 0:
continue
if output_count > 1:
# 多输出:extractvalue 取第 out_emit_idx 个元素后 store
if oo.Value is not None and oo.Value.Ty is not None \
and oo.Value.Ty.Pointee is not None:
ev_name: t.CChar | t.CPtr = pool.alloc(32)
ev_line: t.CChar | t.CPtr = pool.alloc(ASM_LINE_BUF_SIZE)
if ev_name is not None and ev_line is not None:
# ssa_name 形如 "%N"(纯数字 ID),构造唯一名
# "%asm.N.e{i}"(字母开头,避免 %N.e{i} 被解析为
# 数字 ID + 非法后缀导致 "expected '=' after id")
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)
llvmlite.block_append_text(pool, builder.CurBlock, ev_line)
ev_val: llvmlite.Value | t.CPtr = llvmlite.SSAValue(
pool, oo.Value.Ty.Pointee, ev_name)
llvmlite.build_store(builder, ev_val, oo.Value)
else:
if oo.Value is not None:
llvmlite.build_store(builder, result_val, oo.Value)
out_emit_idx += 1
return result_val
return None