Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions emulation/rocjitsu/lib/python/amdisa/codegen/_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6148,6 +6148,10 @@ def _semantic_reg_class(opnd: Operand) -> RegClass:
mode_sensitive_f16_dst=not cls.startswith('scalar_'),
mask_result_writer=result_writer,
)
inst_fields = getattr(self, '_current_inst_fields', set())
integer_clamp_dtype = profile.integer_clamp_dtypes.get(inst.name)
if is_vop3 and 'clamp' in inst_fields and integer_clamp_dtype:
lctx.integer_saturation_dtype = integer_clamp_dtype
if cls == 'vector_cmp':
# V_CMP writes a fresh wave mask initialized to zero, so false
# lanes can remain clear without emitting redundant bit clears.
Expand Down Expand Up @@ -6266,17 +6270,13 @@ def _semantic_reg_class(opnd: Operand) -> RegClass:
),
'V_MAD_I16': (
3,
(
'int32_t a = static_cast<int16_t>(s0);',
'int32_t b = static_cast<int16_t>(s1);',
'int32_t c = static_cast<int16_t>(s2);',
),
'static_cast<uint32_t>(static_cast<uint16_t>(a * b + c))',
(),
'amdgpu::vop3_integer_mad<int16_t, 16>(s0, s1, s2, inst_.clamp)',
),
'V_MAD_U16': (
3,
(),
'(s0 * s1 + s2) & 0xffffu',
'amdgpu::vop3_integer_mad<uint16_t, 16>(s0, s1, s2, inst_.clamp)',
),
}
if is_true16_vop3 and inst.name in true16_special_vop3_ops:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,21 @@ def _register_handlers() -> None:
c.dst_ops, c.src_ops, c.op, c.profile.uses_vgpr_msb_indexing
)
DISPATCH['vector_qsad'] = lambda c: gen_vector_qsad(
c.dst_ops, c.src_ops, c.op, c.profile.uses_vgpr_msb_indexing
c.dst_ops,
c.src_ops,
c.op,
c.profile.uses_vgpr_msb_indexing,
c.is_vop3 and c.inst.name in c.profile.integer_clamp_dtypes,
)
DISPATCH['vector_trig_preop'] = lambda c: gen_vector_trig_preop(
c.dst_ops, c.src_ops, c.is_vop3, c.has_abs
)
DISPATCH['vector_mad_64_32'] = lambda c: gen_vector_mad_64_32(
c.dst_ops, c.src_ops, c.dtype, c.result_writer
c.dst_ops,
c.src_ops,
c.dtype,
c.result_writer,
c.is_vop3 and c.inst.name in c.profile.integer_clamp_dtypes,
)
DISPATCH['vector_mad_32_16'] = lambda c: gen_vector_mad_32_16(
c.dst_ops, c.src_ops, c.dtype, c.is_vop3
Expand Down Expand Up @@ -309,6 +317,7 @@ def _register_handlers() -> None:
c.dtype,
op_sel_hi_2_expr=c.op_sel_hi_2_expr,
opsel_exprs=c.opsel_exprs,
integer_clamp=c.inst.name in c.profile.integer_clamp_dtypes,
)
DISPATCH['pk_fmac_vop2'] = lambda c: (
gen_pk_fmac_vop3(c.dst_ops, c.src_ops)
Expand Down
45 changes: 41 additions & 4 deletions emulation/rocjitsu/lib/python/amdisa/codegen/execute/packed.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,16 @@ def gen_pk_binop(
),
}
lo_expr, hi_expr = i_op_map[op]
L.append(f' uint16_t rlo = static_cast<uint16_t>({lo_expr});')
L.append(f' uint16_t rhi = static_cast<uint16_t>({hi_expr});')
if op in ('add', 'sub'):
L.append(
f' uint16_t rlo = static_cast<uint16_t>(amdgpu::vop3_integer_{op}<int16_t>(a_lo, b_lo, inst_.clamp));'
)
L.append(
f' uint16_t rhi = static_cast<uint16_t>(amdgpu::vop3_integer_{op}<int16_t>(a_hi, b_hi, inst_.clamp));'
)
else:
L.append(f' uint16_t rlo = static_cast<uint16_t>({lo_expr});')
L.append(f' uint16_t rhi = static_cast<uint16_t>({hi_expr});')
L.append(
f' amdgpu::RegisterAccess(wf).write_lane({d}, lane, static_cast<uint32_t>(rlo) | (static_cast<uint32_t>(rhi) << 16));'
)
Expand Down Expand Up @@ -216,8 +224,16 @@ def gen_pk_binop(
),
}
lo_expr, hi_expr = u_op_map[op]
L.append(f' uint16_t rlo = static_cast<uint16_t>({lo_expr});')
L.append(f' uint16_t rhi = static_cast<uint16_t>({hi_expr});')
if op in ('add', 'sub'):
L.append(
f' uint16_t rlo = static_cast<uint16_t>(amdgpu::vop3_integer_{op}<uint16_t>(a_lo, b_lo, inst_.clamp));'
)
L.append(
f' uint16_t rhi = static_cast<uint16_t>(amdgpu::vop3_integer_{op}<uint16_t>(a_hi, b_hi, inst_.clamp));'
)
else:
L.append(f' uint16_t rlo = static_cast<uint16_t>({lo_expr});')
L.append(f' uint16_t rhi = static_cast<uint16_t>({hi_expr});')
L.append(
f' amdgpu::RegisterAccess(wf).write_lane({d}, lane, static_cast<uint32_t>(rlo) | (static_cast<uint32_t>(rhi) << 16));'
)
Expand All @@ -233,6 +249,7 @@ def gen_pk_ternary(
dtype: str | None,
op_sel_hi_2_expr: str = '',
opsel_exprs: tuple[str, str] = ('', ''),
integer_clamp: bool = False,
) -> str:
"""Generate packed 16-bit ternary op (V_PK_FMA_F16, V_PK_MAD_I16, etc.)."""
d, s0, s1, s2 = dst[0], src[0], src[1], src[2]
Expand Down Expand Up @@ -416,6 +433,17 @@ def gen_pk_ternary(
L.append(
' uint16_t rhi = static_cast<uint16_t>(std::max(std::max(a_hi, b_hi), c_hi));'
)
elif op == 'mad' and integer_clamp:
L.append(
' uint16_t rlo = amdgpu::vop3_integer_mad<int16_t, 16>('
'static_cast<uint16_t>(a_lo), static_cast<uint16_t>(b_lo), '
'static_cast<uint16_t>(c_lo), inst_.clamp);'
)
L.append(
' uint16_t rhi = amdgpu::vop3_integer_mad<int16_t, 16>('
'static_cast<uint16_t>(a_hi), static_cast<uint16_t>(b_hi), '
'static_cast<uint16_t>(c_hi), inst_.clamp);'
)
else:
L.append(
' uint16_t rlo = static_cast<uint16_t>(static_cast<uint32_t>(a_lo) * b_lo + c_lo);'
Expand Down Expand Up @@ -478,6 +506,15 @@ def gen_pk_ternary(
elif op in ('max3', 'maximum3'):
L.append(' uint16_t rlo = std::max(std::max(a_lo, b_lo), c_lo);')
L.append(' uint16_t rhi = std::max(std::max(a_hi, b_hi), c_hi);')
elif op == 'mad' and integer_clamp:
L.append(
' uint16_t rlo = amdgpu::vop3_integer_mad<uint16_t, 16>('
'a_lo, b_lo, c_lo, inst_.clamp);'
)
L.append(
' uint16_t rhi = amdgpu::vop3_integer_mad<uint16_t, 16>('
'a_hi, b_hi, c_hi, inst_.clamp);'
)
else:
L.append(
' uint16_t rlo = static_cast<uint16_t>(static_cast<uint32_t>(a_lo) * b_lo + c_lo);'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class LoweringContext:
vector_sgpr_once: bool = False
clear_false_lane_mask_writes: bool = True
mode_sensitive_f16_dst: bool = True
integer_saturation_dtype: str | None = None


_INFIX_OPS: dict[SemaNodeKind, str] = {
Expand Down Expand Up @@ -537,6 +538,12 @@ def _lower_expr(node: SemaNode, ctx: LoweringContext) -> str:
if kind == SemaNodeKind.ID:
return _lower_id(node, ctx)

if (
kind in (SemaNodeKind.ADD, SemaNodeKind.SUB)
and ctx.integer_saturation_dtype is not None
):
return _lower_saturating_integer_binary(node, ctx)

if kind in _INFIX_OPS:
if (expr := _lower_less_greater_once(node, ctx)) is not None:
return expr
Expand Down Expand Up @@ -727,6 +734,33 @@ def _lower_expr(node: SemaNode, ctx: LoweringContext) -> str:
raise ValueError(f'Unhandled SemaNodeKind in lowering: {kind.name}')


def _lower_saturating_integer_binary(node: SemaNode, ctx: LoweringContext) -> str:
"""Lower VOP3 integer add/sub with optional CLAMP saturation."""
dtype = ctx.integer_saturation_dtype
if dtype not in ('i16', 'u16', 'i32', 'u32', 'u64'):
raise ValueError(f'Unsupported integer saturation type: {dtype}')

child_ctx = replace(ctx, integer_saturation_dtype=None)
if node.kind == SemaNodeKind.ADD and node.children[0].kind == SemaNodeKind.MUL:
product = node.children[0]
lhs = _lower_expr(product.children[0], child_ctx)
rhs = _lower_expr(product.children[1], child_ctx)
addend = _lower_expr(node.children[1], child_ctx)
cpp_type = f'{"int" if dtype.startswith("i") else "uint"}{dtype[1:]}_t'
return (
f'amdgpu::vop3_integer_mad<{cpp_type}, {dtype[1:]}>('
f'{lhs}, {rhs}, {addend}, inst_.clamp)'
)

lhs = _lower_expr(node.children[0], child_ctx)
rhs = _lower_expr(node.children[1], child_ctx)
operation = 'add' if node.kind == SemaNodeKind.ADD else 'sub'
cpp_type = f'{"int" if dtype.startswith("i") else "uint"}{dtype[1:]}_t'
return (
f'amdgpu::vop3_integer_{operation}<{cpp_type}>(' f'{lhs}, {rhs}, inst_.clamp)'
)


def _lower_lit(node: SemaNode) -> str:
"""Lower a literal value with appropriate C++ suffix."""
val = node.lit_value or '0'
Expand Down Expand Up @@ -1729,6 +1763,66 @@ def _lower_call(node: SemaNode, ctx: LoweringContext) -> str:
args = [_lower_expr(c, ctx) for c in node.children[1:]]
args_str = ', '.join(args)

add_minmax_calls = {
'add_max_i32': ('int32_t', 'true'),
'add_max_u32': ('uint32_t', 'true'),
'add_min_i32': ('int32_t', 'false'),
'add_min_u32': ('uint32_t', 'false'),
}
if ctx.integer_saturation_dtype is not None and callee in add_minmax_calls:
cpp_type, select_max = add_minmax_calls[callee]
return (
f'amdgpu::vop3_integer_add_minmax<{cpp_type}, {select_max}>(' f'{args_str})'
)

saturating_calls = {
'mul_i24': ('int32_t', 24, False),
'mul_u24': ('uint32_t', 24, False),
'mad_i24': ('int32_t', 24, True),
'mad_u24': ('uint32_t', 24, True),
'mad_lo_u16': ('uint16_t', 16, True),
}
if ctx.integer_saturation_dtype is not None and callee in saturating_calls:
cpp_type, source_bits, has_addend = saturating_calls[callee]
helper = 'vop3_integer_mad' if has_addend else 'vop3_integer_mul'
return (
f'amdgpu::{helper}<{cpp_type}, {source_bits}>(' f'{args_str}, inst_.clamp)'
)

if ctx.integer_saturation_dtype == 'u32' and callee in (
'sad_hi_u8',
'sad_u8',
'sad_u16',
'sad_u32',
'msad_u8',
):
return f'amdgpu::vop3_integer_{callee}({args_str}, inst_.clamp)'

if ctx.integer_saturation_dtype == 'u32' and callee in (
'add_co',
'sub_co',
'addc_co',
'subbc_co',
):
if callee in ('add_co', 'addc_co'):
terms = ' + '.join(f'static_cast<uint64_t>({arg})' for arg in args)
return (
f'[&]() {{ uint64_t w = {terms};'
' if (w > 0xFFFFFFFFULL) vcc |= (1ULL << lane);'
' else vcc &= ~(1ULL << lane);'
' return inst_.clamp && w > 0xFFFFFFFFULL ? UINT32_MAX'
' : static_cast<uint32_t>(w); }()'
)
minuend, subtrahend, *borrow = args
borrow_expr = f' + static_cast<uint64_t>({borrow[0]})' if borrow else ''
return (
f'[&]() {{ uint64_t a = static_cast<uint64_t>({minuend}),'
f' b = static_cast<uint64_t>({subtrahend}){borrow_expr};'
' if (a < b) vcc |= (1ULL << lane);'
' else vcc &= ~(1ULL << lane);'
' return inst_.clamp && a < b ? 0u : static_cast<uint32_t>(a - b); }()'
)

pseudo_scalar_operations = {
'exp2': 'EXP2',
'log2': 'LOG2',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1735,10 +1735,12 @@ def _build_simd_vopc_vop3_f64() -> dict[str, str]:
#
# 16-bit forms compute a 32-bit add/sub and mask the low 16 bits, matching the
# scalar body's `uint32_t(uint16_t(int16_t(low16(a)+low16(b))))` (or the u16
# variant) — both reduce to `(a + b) & 0xFFFFu` / `(a - b) & 0xFFFFu` because
# variant) — both reduce to `(a + b) & 0xFFFFu` / `(a - b) & 0xFFFFu` when
# CLAMP is clear because
# unsigned 32-bit wrap-around at the low 16 bits is identical to signed/unsigned
# 16-bit wrap. 32-bit forms use the wrap-around add/sub on uint32 lanes;
# signed-vs-unsigned wraps the same way.
# signed-vs-unsigned wraps the same way. The shared glue falls back to scalar
# execution when CLAMP requests saturating arithmetic.
SIMD_VOP3_BINARY_INT_EXTRA: dict[str, tuple[str, str]] = {
# v_bcnt_u32_b32: VOP3-only (no VOP1 twin). D = CountOneBits(S0) + S1 -- the
# second source is an accumulator, so this is binary, not unary. Keep the
Expand Down
Loading
Loading