kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 3c940a4af437a98fd7b190e07c0d1c2136e3ddda
parent ecab4f793fbd705a1071bc8c3626fa9adf61819d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 29 May 2026 20:02:05 -0700

test: expand round-trip corpus to the full core op set

Author-and-verify fan-out (one agent per CG op family) over the assembler
round-trip harness, now that the infrastructure (P2 branch relaxation, .L
locals, data-section symbolization, FP load/store, dot-mangled names) lands
every lane. 133 new freestanding cases, each returning 42, covering:

  iarith ibit icmp iunop   integer arith/bitwise/shift/compare/unary, w+x
  fparith fpcmp conv        float+double arith/compare, all conversions, bitcast
  mem                       loads/stores every width, signed/unsigned, arrays
  ctl sw                    if/loops/ternary/short-circuit; switch chain+table
  callx                     indirect call, recursion, >8-arg stack passing
  agg                       struct by-val/by-ref, nested, bitfield, union
  glob                      globals, static locals, addr-of, string literal

Full corpus: 834 lane-checks pass (140 files x O0/O1 x L0/L1/L2), 1 skip.

The lone skip is glob_bss_write: the standalone assembler emits .bss as
SHT_PROGBITS rather than NOBITS (no bss-position tracking), so a store to a
zero-init global faults in the JIT image. Documented in the .skip and the
doc as a follow-up; glob_rw covers the global-write path via .data.

Diffstat:
Atest/asm/roundtrip/agg_bitfield.c | 13+++++++++++++
Atest/asm/roundtrip/agg_bitfield.expected | 1+
Atest/asm/roundtrip/agg_bitfield_signed.c | 12++++++++++++
Atest/asm/roundtrip/agg_bitfield_signed.expected | 1+
Atest/asm/roundtrip/agg_byptr.c | 14++++++++++++++
Atest/asm/roundtrip/agg_byptr.expected | 1+
Atest/asm/roundtrip/agg_byval.c | 12++++++++++++
Atest/asm/roundtrip/agg_byval.expected | 1+
Atest/asm/roundtrip/agg_byval_big.c | 13+++++++++++++
Atest/asm/roundtrip/agg_byval_big.expected | 1+
Atest/asm/roundtrip/agg_nested.c | 14++++++++++++++
Atest/asm/roundtrip/agg_nested.expected | 1+
Atest/asm/roundtrip/agg_nested_byval.c | 14++++++++++++++
Atest/asm/roundtrip/agg_nested_byval.expected | 1+
Atest/asm/roundtrip/agg_ret.c | 13+++++++++++++
Atest/asm/roundtrip/agg_ret.expected | 1+
Atest/asm/roundtrip/agg_ret_sret.c | 15+++++++++++++++
Atest/asm/roundtrip/agg_ret_sret.expected | 1+
Atest/asm/roundtrip/agg_union.c | 12++++++++++++
Atest/asm/roundtrip/agg_union.expected | 1+
Atest/asm/roundtrip/callx_chain.c | 9+++++++++
Atest/asm/roundtrip/callx_chain.expected | 1+
Atest/asm/roundtrip/callx_indirect.c | 10++++++++++
Atest/asm/roundtrip/callx_indirect.expected | 1+
Atest/asm/roundtrip/callx_indirect_retval.c | 10++++++++++
Atest/asm/roundtrip/callx_indirect_retval.expected | 1+
Atest/asm/roundtrip/callx_recursion.c | 9+++++++++
Atest/asm/roundtrip/callx_recursion.expected | 1+
Atest/asm/roundtrip/callx_recursion_long.c | 8++++++++
Atest/asm/roundtrip/callx_recursion_long.expected | 1+
Atest/asm/roundtrip/callx_retval.c | 10++++++++++
Atest/asm/roundtrip/callx_retval.expected | 1+
Atest/asm/roundtrip/callx_stackargs.c | 12++++++++++++
Atest/asm/roundtrip/callx_stackargs.expected | 1+
Atest/asm/roundtrip/callx_stackargs_long.c | 13+++++++++++++
Atest/asm/roundtrip/callx_stackargs_long.expected | 1+
Atest/asm/roundtrip/conv_bitcast_f2i.c | 16++++++++++++++++
Atest/asm/roundtrip/conv_bitcast_f2i.expected | 1+
Atest/asm/roundtrip/conv_bitcast_i2f.c | 13+++++++++++++
Atest/asm/roundtrip/conv_bitcast_i2f.expected | 1+
Atest/asm/roundtrip/conv_fp_fp.c | 15+++++++++++++++
Atest/asm/roundtrip/conv_fp_fp.expected | 1+
Atest/asm/roundtrip/conv_int_double.c | 12++++++++++++
Atest/asm/roundtrip/conv_int_double.expected | 1+
Atest/asm/roundtrip/conv_int_float_s.c | 16++++++++++++++++
Atest/asm/roundtrip/conv_int_float_s.expected | 1+
Atest/asm/roundtrip/conv_int_float_u.c | 15+++++++++++++++
Atest/asm/roundtrip/conv_int_float_u.expected | 1+
Atest/asm/roundtrip/conv_intnarrow.c | 20++++++++++++++++++++
Atest/asm/roundtrip/conv_intnarrow.expected | 1+
Atest/asm/roundtrip/conv_intwiden_s.c | 22++++++++++++++++++++++
Atest/asm/roundtrip/conv_intwiden_s.expected | 1+
Atest/asm/roundtrip/conv_intwiden_u.c | 23+++++++++++++++++++++++
Atest/asm/roundtrip/conv_intwiden_u.expected | 1+
Atest/asm/roundtrip/conv_long_double_s.c | 15+++++++++++++++
Atest/asm/roundtrip/conv_long_double_s.expected | 1+
Atest/asm/roundtrip/conv_long_double_u.c | 15+++++++++++++++
Atest/asm/roundtrip/conv_long_double_u.expected | 1+
Atest/asm/roundtrip/ctl_breakcont.c | 23+++++++++++++++++++++++
Atest/asm/roundtrip/ctl_breakcont.expected | 1+
Atest/asm/roundtrip/ctl_dowhile.c | 11+++++++++++
Atest/asm/roundtrip/ctl_dowhile.expected | 1+
Atest/asm/roundtrip/ctl_for.c | 11+++++++++++
Atest/asm/roundtrip/ctl_for.expected | 1+
Atest/asm/roundtrip/ctl_for_long.c | 11+++++++++++
Atest/asm/roundtrip/ctl_for_long.expected | 1+
Atest/asm/roundtrip/ctl_ifelse.c | 17+++++++++++++++++
Atest/asm/roundtrip/ctl_ifelse.expected | 1+
Atest/asm/roundtrip/ctl_loops_mixed.c | 22++++++++++++++++++++++
Atest/asm/roundtrip/ctl_loops_mixed.expected | 1+
Atest/asm/roundtrip/ctl_nested.c | 14++++++++++++++
Atest/asm/roundtrip/ctl_nested.expected | 1+
Atest/asm/roundtrip/ctl_shortcircuit_and.c | 12++++++++++++
Atest/asm/roundtrip/ctl_shortcircuit_and.expected | 1+
Atest/asm/roundtrip/ctl_shortcircuit_or.c | 12++++++++++++
Atest/asm/roundtrip/ctl_shortcircuit_or.expected | 1+
Atest/asm/roundtrip/ctl_shortcircuit_val.c | 10++++++++++
Atest/asm/roundtrip/ctl_shortcircuit_val.expected | 1+
Atest/asm/roundtrip/ctl_ternary.c | 8++++++++
Atest/asm/roundtrip/ctl_ternary.expected | 1+
Atest/asm/roundtrip/ctl_ternary_chain.c | 11+++++++++++
Atest/asm/roundtrip/ctl_ternary_chain.expected | 1+
Atest/asm/roundtrip/ctl_ternary_long.c | 7+++++++
Atest/asm/roundtrip/ctl_ternary_long.expected | 1+
Atest/asm/roundtrip/ctl_while.c | 11+++++++++++
Atest/asm/roundtrip/ctl_while.expected | 1+
Atest/asm/roundtrip/ctl_while_unsigned.c | 11+++++++++++
Atest/asm/roundtrip/ctl_while_unsigned.expected | 1+
Atest/asm/roundtrip/fparith_double.c | 9+++++++++
Atest/asm/roundtrip/fparith_double.expected | 1+
Atest/asm/roundtrip/fparith_double_add.c | 9+++++++++
Atest/asm/roundtrip/fparith_double_add.expected | 1+
Atest/asm/roundtrip/fparith_double_div.c | 8++++++++
Atest/asm/roundtrip/fparith_double_div.expected | 1+
Atest/asm/roundtrip/fparith_double_mul.c | 8++++++++
Atest/asm/roundtrip/fparith_double_mul.expected | 1+
Atest/asm/roundtrip/fparith_double_neg.c | 10++++++++++
Atest/asm/roundtrip/fparith_double_neg.expected | 1+
Atest/asm/roundtrip/fparith_double_sub.c | 8++++++++
Atest/asm/roundtrip/fparith_double_sub.expected | 1+
Atest/asm/roundtrip/fparith_float_add.c | 9+++++++++
Atest/asm/roundtrip/fparith_float_add.expected | 1+
Atest/asm/roundtrip/fparith_float_div.c | 8++++++++
Atest/asm/roundtrip/fparith_float_div.expected | 1+
Atest/asm/roundtrip/fparith_float_mul.c | 8++++++++
Atest/asm/roundtrip/fparith_float_mul.expected | 1+
Atest/asm/roundtrip/fparith_float_neg.c | 9+++++++++
Atest/asm/roundtrip/fparith_float_neg.expected | 1+
Atest/asm/roundtrip/fparith_float_sub.c | 8++++++++
Atest/asm/roundtrip/fparith_float_sub.expected | 1+
Atest/asm/roundtrip/fpcmp_branch.c | 10++++++++++
Atest/asm/roundtrip/fpcmp_branch.expected | 1+
Atest/asm/roundtrip/fpcmp_double_eq.c | 11+++++++++++
Atest/asm/roundtrip/fpcmp_double_eq.expected | 1+
Atest/asm/roundtrip/fpcmp_double_lt.c | 15+++++++++++++++
Atest/asm/roundtrip/fpcmp_double_lt.expected | 1+
Atest/asm/roundtrip/fpcmp_float_eq.c | 11+++++++++++
Atest/asm/roundtrip/fpcmp_float_eq.expected | 1+
Atest/asm/roundtrip/fpcmp_float_lt.c | 15+++++++++++++++
Atest/asm/roundtrip/fpcmp_float_lt.expected | 1+
Atest/asm/roundtrip/fpcmp_nan.c | 16++++++++++++++++
Atest/asm/roundtrip/fpcmp_nan.expected | 1+
Atest/asm/roundtrip/fpcmp_zero.c | 11+++++++++++
Atest/asm/roundtrip/fpcmp_zero.expected | 1+
Atest/asm/roundtrip/glob_addr.c | 12++++++++++++
Atest/asm/roundtrip/glob_addr.expected | 1+
Atest/asm/roundtrip/glob_array.c | 9+++++++++
Atest/asm/roundtrip/glob_array.expected | 1+
Atest/asm/roundtrip/glob_array_long.c | 9+++++++++
Atest/asm/roundtrip/glob_array_long.expected | 1+
Atest/asm/roundtrip/glob_bss_write.c | 17+++++++++++++++++
Atest/asm/roundtrip/glob_bss_write.expected | 1+
Atest/asm/roundtrip/glob_bss_write.skip | 1+
Atest/asm/roundtrip/glob_const_array.c | 9+++++++++
Atest/asm/roundtrip/glob_const_array.expected | 1+
Atest/asm/roundtrip/glob_long_rw.c | 11+++++++++++
Atest/asm/roundtrip/glob_long_rw.expected | 1+
Atest/asm/roundtrip/glob_rw.c | 13+++++++++++++
Atest/asm/roundtrip/glob_rw.expected | 1+
Atest/asm/roundtrip/glob_static.c | 20++++++++++++++++++++
Atest/asm/roundtrip/glob_static.expected | 1+
Atest/asm/roundtrip/glob_string.c | 9+++++++++
Atest/asm/roundtrip/glob_string.expected | 1+
Atest/asm/roundtrip/glob_string_addr.c | 15+++++++++++++++
Atest/asm/roundtrip/glob_string_addr.expected | 1+
Atest/asm/roundtrip/iarith_addsub.c | 13+++++++++++++
Atest/asm/roundtrip/iarith_addsub.expected | 1+
Atest/asm/roundtrip/iarith_divrem_s.c | 15+++++++++++++++
Atest/asm/roundtrip/iarith_divrem_s.expected | 1+
Atest/asm/roundtrip/iarith_divrem_u.c | 14++++++++++++++
Atest/asm/roundtrip/iarith_divrem_u.expected | 1+
Atest/asm/roundtrip/iarith_long.c | 21+++++++++++++++++++++
Atest/asm/roundtrip/iarith_long.expected | 1+
Atest/asm/roundtrip/iarith_mul.c | 9+++++++++
Atest/asm/roundtrip/iarith_mul.expected | 1+
Atest/asm/roundtrip/ibit_andnot.c | 12++++++++++++
Atest/asm/roundtrip/ibit_andnot.expected | 1+
Atest/asm/roundtrip/ibit_logic.c | 13+++++++++++++
Atest/asm/roundtrip/ibit_logic.expected | 1+
Atest/asm/roundtrip/ibit_logic_imm.c | 12++++++++++++
Atest/asm/roundtrip/ibit_logic_imm.expected | 1+
Atest/asm/roundtrip/ibit_long.c | 14++++++++++++++
Atest/asm/roundtrip/ibit_long.expected | 1+
Atest/asm/roundtrip/ibit_long_logic_imm.c | 12++++++++++++
Atest/asm/roundtrip/ibit_long_logic_imm.expected | 1+
Atest/asm/roundtrip/ibit_long_shift_const.c | 16++++++++++++++++
Atest/asm/roundtrip/ibit_long_shift_const.expected | 1+
Atest/asm/roundtrip/ibit_long_shift_var.c | 17+++++++++++++++++
Atest/asm/roundtrip/ibit_long_shift_var.expected | 1+
Atest/asm/roundtrip/ibit_not.c | 7+++++++
Atest/asm/roundtrip/ibit_not.expected | 1+
Atest/asm/roundtrip/ibit_shift_combo.c | 14++++++++++++++
Atest/asm/roundtrip/ibit_shift_combo.expected | 1+
Atest/asm/roundtrip/ibit_shift_const.c | 15+++++++++++++++
Atest/asm/roundtrip/ibit_shift_const.expected | 1+
Atest/asm/roundtrip/ibit_shift_var.c | 18++++++++++++++++++
Atest/asm/roundtrip/ibit_shift_var.expected | 1+
Atest/asm/roundtrip/icmp_branch_eqne.c | 16++++++++++++++++
Atest/asm/roundtrip/icmp_branch_eqne.expected | 1+
Atest/asm/roundtrip/icmp_branch_signed_int.c | 18++++++++++++++++++
Atest/asm/roundtrip/icmp_branch_signed_int.expected | 1+
Atest/asm/roundtrip/icmp_branch_signed_long.c | 16++++++++++++++++
Atest/asm/roundtrip/icmp_branch_signed_long.expected | 1+
Atest/asm/roundtrip/icmp_branch_unsigned_int.c | 17+++++++++++++++++
Atest/asm/roundtrip/icmp_branch_unsigned_int.expected | 1+
Atest/asm/roundtrip/icmp_branch_unsigned_long.c | 17+++++++++++++++++
Atest/asm/roundtrip/icmp_branch_unsigned_long.expected | 1+
Atest/asm/roundtrip/icmp_cmp_imm.c | 11+++++++++++
Atest/asm/roundtrip/icmp_cmp_imm.expected | 1+
Atest/asm/roundtrip/icmp_csel_int.c | 20++++++++++++++++++++
Atest/asm/roundtrip/icmp_csel_int.expected | 1+
Atest/asm/roundtrip/icmp_cset_eq_int.c | 10++++++++++
Atest/asm/roundtrip/icmp_cset_eq_int.expected | 1+
Atest/asm/roundtrip/icmp_cset_eqne_long.c | 11+++++++++++
Atest/asm/roundtrip/icmp_cset_eqne_long.expected | 1+
Atest/asm/roundtrip/icmp_cset_ne_int.c | 9+++++++++
Atest/asm/roundtrip/icmp_cset_ne_int.expected | 1+
Atest/asm/roundtrip/icmp_cset_signed_int.c | 14++++++++++++++
Atest/asm/roundtrip/icmp_cset_signed_int.expected | 1+
Atest/asm/roundtrip/icmp_cset_signed_long.c | 15+++++++++++++++
Atest/asm/roundtrip/icmp_cset_signed_long.expected | 1+
Atest/asm/roundtrip/icmp_cset_unsigned_int.c | 16++++++++++++++++
Atest/asm/roundtrip/icmp_cset_unsigned_int.expected | 1+
Atest/asm/roundtrip/icmp_cset_unsigned_long.c | 15+++++++++++++++
Atest/asm/roundtrip/icmp_cset_unsigned_long.expected | 1+
Atest/asm/roundtrip/iunop_chain.c | 10++++++++++
Atest/asm/roundtrip/iunop_chain.expected | 1+
Atest/asm/roundtrip/iunop_lognot.c | 7+++++++
Atest/asm/roundtrip/iunop_lognot.expected | 1+
Atest/asm/roundtrip/iunop_lognot64.c | 7+++++++
Atest/asm/roundtrip/iunop_lognot64.expected | 1+
Atest/asm/roundtrip/iunop_lognot_nonzero.c | 8++++++++
Atest/asm/roundtrip/iunop_lognot_nonzero.expected | 1+
Atest/asm/roundtrip/iunop_lognot_unsigned.c | 7+++++++
Atest/asm/roundtrip/iunop_lognot_unsigned.expected | 1+
Atest/asm/roundtrip/iunop_neg.c | 8++++++++
Atest/asm/roundtrip/iunop_neg.expected | 1+
Atest/asm/roundtrip/iunop_neg64.c | 7+++++++
Atest/asm/roundtrip/iunop_neg64.expected | 1+
Atest/asm/roundtrip/iunop_neg_unsigned.c | 8++++++++
Atest/asm/roundtrip/iunop_neg_unsigned.expected | 1+
Atest/asm/roundtrip/iunop_not.c | 7+++++++
Atest/asm/roundtrip/iunop_not.expected | 1+
Atest/asm/roundtrip/iunop_not64.c | 7+++++++
Atest/asm/roundtrip/iunop_not64.expected | 1+
Atest/asm/roundtrip/iunop_not_unsigned.c | 7+++++++
Atest/asm/roundtrip/iunop_not_unsigned.expected | 1+
Atest/asm/roundtrip/mem_array.c | 13+++++++++++++
Atest/asm/roundtrip/mem_array.expected | 1+
Atest/asm/roundtrip/mem_global_array.c | 9+++++++++
Atest/asm/roundtrip/mem_global_array.expected | 1+
Atest/asm/roundtrip/mem_long_array.c | 12++++++++++++
Atest/asm/roundtrip/mem_long_array.expected | 1+
Atest/asm/roundtrip/mem_ptr_widths.c | 18++++++++++++++++++
Atest/asm/roundtrip/mem_ptr_widths.expected | 1+
Atest/asm/roundtrip/mem_signed.c | 16++++++++++++++++
Atest/asm/roundtrip/mem_signed.expected | 1+
Atest/asm/roundtrip/mem_signed_long.c | 12++++++++++++
Atest/asm/roundtrip/mem_signed_long.expected | 1+
Atest/asm/roundtrip/mem_store_narrow.c | 14++++++++++++++
Atest/asm/roundtrip/mem_store_narrow.expected | 1+
Atest/asm/roundtrip/mem_struct.c | 21+++++++++++++++++++++
Atest/asm/roundtrip/mem_struct.expected | 1+
Atest/asm/roundtrip/mem_u32_zext.c | 11+++++++++++
Atest/asm/roundtrip/mem_u32_zext.expected | 1+
Atest/asm/roundtrip/mem_volatile.c | 13+++++++++++++
Atest/asm/roundtrip/mem_volatile.expected | 1+
Atest/asm/roundtrip/mem_widths.c | 17+++++++++++++++++
Atest/asm/roundtrip/mem_widths.expected | 1+
Atest/asm/roundtrip/sw_char.c | 17+++++++++++++++++
Atest/asm/roundtrip/sw_char.expected | 1+
Atest/asm/roundtrip/sw_default.c | 17+++++++++++++++++
Atest/asm/roundtrip/sw_default.expected | 1+
Atest/asm/roundtrip/sw_dense_signed.c | 21+++++++++++++++++++++
Atest/asm/roundtrip/sw_dense_signed.expected | 1+
Atest/asm/roundtrip/sw_enum.c | 17+++++++++++++++++
Atest/asm/roundtrip/sw_enum.expected | 1+
Atest/asm/roundtrip/sw_fallthrough.c | 19+++++++++++++++++++
Atest/asm/roundtrip/sw_fallthrough.expected | 1+
Atest/asm/roundtrip/sw_long.c | 16++++++++++++++++
Atest/asm/roundtrip/sw_long.expected | 1+
Atest/asm/roundtrip/sw_nested.c | 23+++++++++++++++++++++++
Atest/asm/roundtrip/sw_nested.expected | 1+
Atest/asm/roundtrip/sw_sparse.c | 18++++++++++++++++++
Atest/asm/roundtrip/sw_sparse.expected | 1+
Atest/asm/roundtrip/sw_unsigned.c | 16++++++++++++++++
Atest/asm/roundtrip/sw_unsigned.expected | 1+
267 files changed, 1857 insertions(+), 0 deletions(-)

diff --git a/test/asm/roundtrip/agg_bitfield.c b/test/asm/roundtrip/agg_bitfield.c @@ -0,0 +1,13 @@ +/* A bitfield struct: fields packed into sub-byte/sub-word ranges. Reading and + * writing bitfields exercises the mask/shift sequences (and/orr/ubfx/bfi-style + * encodings) the codegen emits for bitfield access. All values fit their widths + * (no overflow). a(5) + b(9) + c(28) = 42. */ +struct Bits { unsigned a : 4; unsigned b : 5; unsigned c : 6; }; +int test_main(void) { + volatile unsigned va = 5, vb = 9, vc = 28; + struct Bits s; + s.a = va; + s.b = vb; + s.c = vc; + return (int)(s.a + s.b + s.c); +} diff --git a/test/asm/roundtrip/agg_bitfield.expected b/test/asm/roundtrip/agg_bitfield.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/agg_bitfield_signed.c b/test/asm/roundtrip/agg_bitfield_signed.c @@ -0,0 +1,12 @@ +/* A SIGNED bitfield: writing a value into a narrow signed field and reading it + * back sign-extends, which exercises the signed bitfield extract (sbfx-style) + * encoding distinct from the unsigned (ubfx) path. Here b is a 4-bit signed + * field holding -2 (read back as -2). 44 + (-2) = 42. */ +struct Bits { int a : 8; int b : 4; }; +int test_main(void) { + volatile int va = 44, vb = -2; + struct Bits s; + s.a = va; + s.b = vb; + return s.a + s.b; +} diff --git a/test/asm/roundtrip/agg_bitfield_signed.expected b/test/asm/roundtrip/agg_bitfield_signed.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/agg_byptr.c b/test/asm/roundtrip/agg_byptr.c @@ -0,0 +1,14 @@ +/* A struct passed BY POINTER (explicit address), the callee reading and writing + * fields through the pointer. Exercises plain field loads/stores at struct + * offsets via a register base (ldr/str [base, #off]). `noinline` forces the + * real call. After accumulate, p.a = 18 + 24 = 42. */ +struct Pair { int a; int b; }; +__attribute__((noinline)) static void accumulate(struct Pair *p) { + p->a = p->a + p->b; +} +int test_main(void) { + volatile int x = 18, y = 24; + struct Pair p = { x, y }; + accumulate(&p); + return p.a; +} diff --git a/test/asm/roundtrip/agg_byptr.expected b/test/asm/roundtrip/agg_byptr.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/agg_byval.c b/test/asm/roundtrip/agg_byval.c @@ -0,0 +1,12 @@ +/* A small struct (two ints, 8 bytes) passed BY VALUE. On the AArch64 PCS a + * struct <= 16 bytes is passed in integer registers (here packed into one x + * register), so this exercises the caller packing the fields and the callee + * unpacking them. `noinline` forces a real call so the byval lowering is + * actually emitted. sum.a + sum.b = 20 + 22 = 42. */ +struct Pair { int a; int b; }; +__attribute__((noinline)) static int sum(struct Pair p) { return p.a + p.b; } +int test_main(void) { + volatile int x = 20, y = 22; + struct Pair p = { x, y }; + return sum(p); +} diff --git a/test/asm/roundtrip/agg_byval.expected b/test/asm/roundtrip/agg_byval.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/agg_byval_big.c b/test/asm/roundtrip/agg_byval_big.c @@ -0,0 +1,13 @@ +/* A struct LARGER than 16 bytes (five ints, 20 bytes) passed by value. On the + * AArch64 PCS an aggregate > 16 bytes is passed indirectly: the caller copies + * it to a hidden stack slot and passes a pointer. Exercises the byval-by-memory + * lowering (caller-side memcpy/stores + pointer arg). sum of 2+4+6+8+22 = 42. */ +struct Five { int a, b, c, d, e; }; +__attribute__((noinline)) static int sum5(struct Five s) { + return s.a + s.b + s.c + s.d + s.e; +} +int test_main(void) { + volatile int x = 2; + struct Five s = { x, x + 2, x + 4, x + 6, x + 20 }; + return sum5(s); +} diff --git a/test/asm/roundtrip/agg_byval_big.expected b/test/asm/roundtrip/agg_byval_big.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/agg_nested.c b/test/asm/roundtrip/agg_nested.c @@ -0,0 +1,14 @@ +/* A NESTED struct: an inner struct embedded in an outer one, accessed via + * chained member offsets (outer.inner.field). Exercises nested field offset + * computation in loads/stores. Passed by pointer to keep the access pattern + * about offset math. Result: 10 + 12 + 20 = 42. */ +struct Inner { int x; int y; }; +struct Outer { int tag; struct Inner in; }; +__attribute__((noinline)) static int eval(struct Outer *o) { + return o->tag + o->in.x + o->in.y; +} +int test_main(void) { + volatile int a = 10, b = 12, c = 20; + struct Outer o = { a, { b, c } }; + return eval(&o); +} diff --git a/test/asm/roundtrip/agg_nested.expected b/test/asm/roundtrip/agg_nested.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/agg_nested_byval.c b/test/asm/roundtrip/agg_nested_byval.c @@ -0,0 +1,14 @@ +/* A nested struct (inner struct embedded in outer, 12 bytes total) passed BY + * VALUE. <= 16 bytes so it travels in integer registers per the PCS; the callee + * must extract the nested fields from the packed register operands. Exercises + * nested-field access combined with byval register lowering. 6+15+21 = 42. */ +struct Inner { int x; int y; }; +struct Outer { int tag; struct Inner in; }; +__attribute__((noinline)) static int eval(struct Outer o) { + return o.tag + o.in.x + o.in.y; +} +int test_main(void) { + volatile int a = 6, b = 15, c = 21; + struct Outer o = { a, { b, c } }; + return eval(o); +} diff --git a/test/asm/roundtrip/agg_nested_byval.expected b/test/asm/roundtrip/agg_nested_byval.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/agg_ret.c b/test/asm/roundtrip/agg_ret.c @@ -0,0 +1,13 @@ +/* A small struct (two ints, 8 bytes) RETURNED by value, returned in an integer + * register per the AArch64 PCS. Exercises the callee packing the result and the + * caller unpacking it. `noinline` forces a real call. r.a + r.b = 19 + 23 = 42. */ +struct Pair { int a; int b; }; +__attribute__((noinline)) static struct Pair mk(int x, int y) { + struct Pair p = { x, y }; + return p; +} +int test_main(void) { + volatile int x = 19, y = 23; + struct Pair r = mk(x, y); + return r.a + r.b; +} diff --git a/test/asm/roundtrip/agg_ret.expected b/test/asm/roundtrip/agg_ret.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/agg_ret_sret.c b/test/asm/roundtrip/agg_ret_sret.c @@ -0,0 +1,15 @@ +/* A struct LARGER than 16 bytes (five ints, 20 bytes) returned by value, which + * forces the sret (struct-return via hidden pointer in x8) calling convention: + * the caller allocates the result slot and passes its address in x8, the callee + * stores fields through x8. Exercises the sret lowering on both sides. Sum of + * 4+8+10+12+8 = 42. */ +struct Five { int a, b, c, d, e; }; +__attribute__((noinline)) static struct Five mk5(int base) { + struct Five s = { base, base * 2, base * 2 + 2, base * 3, base * 2 }; + return s; +} +int test_main(void) { + volatile int x = 4; + struct Five s = mk5(x); + return s.a + s.b + s.c + s.d + s.e; +} diff --git a/test/asm/roundtrip/agg_ret_sret.expected b/test/asm/roundtrip/agg_ret_sret.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/agg_union.c b/test/asm/roundtrip/agg_union.c @@ -0,0 +1,12 @@ +/* A union: storing through one member and reading the overlapping bytes through + * a wider member. Exercises overlapping-storage loads/stores at the same offset + * with different widths. Little-endian AArch64: bytes {42,0,0,0} written as an + * int read as the low byte = 42. The high bytes are zero-initialized. */ +union U { unsigned char bytes[4]; unsigned word; }; +int test_main(void) { + volatile unsigned char lo = 42; + union U u; + u.word = 0; + u.bytes[0] = lo; + return (int)(u.word & 0xff); +} diff --git a/test/asm/roundtrip/agg_union.expected b/test/asm/roundtrip/agg_union.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/callx_chain.c b/test/asm/roundtrip/callx_chain.c @@ -0,0 +1,9 @@ +/* A chain of direct calls feeding each other, so multiple CALL26 relocations + * appear and the result of one call becomes an argument to the next. `noinline` + * keeps each callee a distinct function. inc(20)=21, dbl(21)=42. */ +__attribute__((noinline)) static int inc(int x) { return x + 1; } +__attribute__((noinline)) static int dbl(int x) { return x + x; } +int test_main(void) { + volatile int a = 20; + return dbl(inc(a)); +} diff --git a/test/asm/roundtrip/callx_chain.expected b/test/asm/roundtrip/callx_chain.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/callx_indirect.c b/test/asm/roundtrip/callx_indirect.c @@ -0,0 +1,10 @@ +/* Indirect call through a function pointer. The callee address is taken via a + * volatile function-pointer local so codegen cannot devirtualize it to a direct + * `bl`; it must load the address (GOT/ADRP+ADD reloc) and emit `blr <reg>`. + * Exercises the indirect-call register form on the asm round-trip surface. + * Exit code dbl(21) = 42. */ +__attribute__((noinline)) static int dbl(int x) { return x + x; } +int test_main(void) { + int (*volatile fp)(int) = dbl; + return fp(21); +} diff --git a/test/asm/roundtrip/callx_indirect.expected b/test/asm/roundtrip/callx_indirect.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/callx_indirect_retval.c b/test/asm/roundtrip/callx_indirect_retval.c @@ -0,0 +1,10 @@ +/* Indirect call whose return value is consumed by the caller. The volatile + * function-pointer local defeats devirtualization, forcing `blr`, and the + * result is reused in arithmetic so it cannot collapse into a tail call. + * triple(13) = 39, then +3 = 42. */ +__attribute__((noinline)) static int triple(int x) { return x * 3; } +int test_main(void) { + int (*volatile fp)(int) = triple; + int r = fp(13); + return r + 3; +} diff --git a/test/asm/roundtrip/callx_indirect_retval.expected b/test/asm/roundtrip/callx_indirect_retval.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/callx_recursion.c b/test/asm/roundtrip/callx_recursion.c @@ -0,0 +1,9 @@ +/* A small recursive sum: sum(n) = n + sum(n-1), sum(0) = 0. `noinline` keeps + * the recursive self-call a real CALL26 (`bl sum`) plus the base-case branch. + * Exercises a self-referential same-section call relocation. sum(8) = 36, then + * +6 = 42. */ +__attribute__((noinline)) static int sum(int n) { + if (n <= 0) return 0; + return n + sum(n - 1); +} +int test_main(void) { return sum(8) + 6; } diff --git a/test/asm/roundtrip/callx_recursion.expected b/test/asm/roundtrip/callx_recursion.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/callx_recursion_long.c b/test/asm/roundtrip/callx_recursion_long.c @@ -0,0 +1,8 @@ +/* 64-bit recursive factorial-ish accumulation: ssum(n) = n + ssum(n-1) over + * long, returning a long. Exercises a self-call CALL26 with 64-bit operands. + * ssum(8) = 36, then +6 = 42. */ +__attribute__((noinline)) static long ssum(long n) { + if (n <= 0) return 0; + return n + ssum(n - 1); +} +int test_main(void) { return (int)(ssum(8) + 6); } diff --git a/test/asm/roundtrip/callx_recursion_long.expected b/test/asm/roundtrip/callx_recursion_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/callx_retval.c b/test/asm/roundtrip/callx_retval.c @@ -0,0 +1,10 @@ +/* A function returning a value that the caller then consumes in further + * arithmetic (not a tail-return of the call result). Forces the call result in + * x0/w0 to be moved/spilled and reused. `noinline` makes it a real call. + * mul(6,7) = 42, used directly. */ +__attribute__((noinline)) static int mul(int a, int b) { return a * b; } +int test_main(void) { + volatile int x = 6, y = 7; + int r = mul(x, y); + return r - 0; +} diff --git a/test/asm/roundtrip/callx_retval.expected b/test/asm/roundtrip/callx_retval.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/callx_stackargs.c b/test/asm/roundtrip/callx_stackargs.c @@ -0,0 +1,12 @@ +/* A call with MANY (12) int arguments: the AArch64 PCS passes the first 8 in + * x0..x7 and the remaining 4 on the stack. Exercises the caller-side stack-arg + * stores (str to [sp, #off]) and the callee-side stack-arg loads. `noinline` + * forces the real call. Sum 1+2+...+12 = 78, then -36 = 42. */ +__attribute__((noinline)) static int add12(int a, int b, int c, int d, + int e, int f, int g, int h, + int i, int j, int k, int l) { + return a + b + c + d + e + f + g + h + i + j + k + l; +} +int test_main(void) { + return add12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) - 36; +} diff --git a/test/asm/roundtrip/callx_stackargs.expected b/test/asm/roundtrip/callx_stackargs.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/callx_stackargs_long.c b/test/asm/roundtrip/callx_stackargs_long.c @@ -0,0 +1,13 @@ +/* The 64-bit (long) analogue of callx_stackargs: 10 long arguments, so two + * spill to the stack. Exercises 64-bit caller-side stack-arg stores (str x..) + * and callee-side loads. `noinline` forces the real call. 1+2+...+10 = 55, + * then -13 = 42. */ +__attribute__((noinline)) static long add10l(long a, long b, long c, long d, + long e, long f, long g, long h, + long i, long j) { + return a + b + c + d + e + f + g + h + i + j; +} +int test_main(void) { + long r = add10l(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + return (int)(r - 13); +} diff --git a/test/asm/roundtrip/callx_stackargs_long.expected b/test/asm/roundtrip/callx_stackargs_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_bitcast_f2i.c b/test/asm/roundtrip/conv_bitcast_f2i.c @@ -0,0 +1,16 @@ +/* Bitcast: reinterpret a float's bits as an int via a union (same size, no + * value conversion). The backend lowers this to an FMOV w,s (FP->GPR move) or + * a store/reload, NOT an FCVT. volatile defeats folding. + * + * 2.0f has IEEE-754 bits 0x40000000. We mask off pieces so the final result + * is exactly 42 regardless of which bit pattern materializes. */ +union fb { float f; unsigned int u; }; +int test_main(void) { + volatile float src = 2.0f; /* bits 0x40000000 */ + union fb x; + x.f = src; /* reinterpret as bits, fmov w,s */ + unsigned int bits = x.u; /* 0x40000000 */ + /* top byte is 0x40 = 64; bottom 24 bits are 0. 64 - 22 = 42. */ + unsigned int top = bits >> 24; /* 0x40 = 64 */ + return (int)(top - 22); /* 42 */ +} diff --git a/test/asm/roundtrip/conv_bitcast_f2i.expected b/test/asm/roundtrip/conv_bitcast_f2i.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_bitcast_i2f.c b/test/asm/roundtrip/conv_bitcast_i2f.c @@ -0,0 +1,13 @@ +/* Bitcast the other direction: reinterpret an int's bits as a float via a + * union (FMOV s,w — GPR->FP move, no conversion). volatile defeats folding. + * + * bits 0x42280000 is the IEEE-754 encoding of 42.0f. Reinterpreting and + * truncating yields exactly 42. */ +union fb { unsigned int u; float f; }; +int test_main(void) { + volatile unsigned int src = 0x42280000u; /* bits of 42.0f */ + union fb x; + x.u = src; /* reinterpret as float, fmov s,w */ + float val = x.f; /* 42.0f */ + return (int)val; /* fcvtzs -> 42 */ +} diff --git a/test/asm/roundtrip/conv_bitcast_i2f.expected b/test/asm/roundtrip/conv_bitcast_i2f.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_fp_fp.c b/test/asm/roundtrip/conv_fp_fp.c @@ -0,0 +1,15 @@ +/* float <-> double precision conversion: FCVT s->d (widen) and FCVT d->s + * (narrow). Values chosen so both directions are exact (no rounding), keeping + * the result deterministic. volatile operands defeat folding. + * + * f = 21.0f : d = (double)f = 21.0 (fcvt d,s widen) + * d2 = 84.0 : f2 = (float)d2 = 84.0f (fcvt s,d narrow) */ +int test_main(void) { + volatile float f = 21.0f; + volatile double d2 = 84.0; + double d = (double)f; /* fcvt d,s (widen) */ + float f2 = (float)d2; /* fcvt s,d (narrow) */ + int a = (int)d; /* 21 */ + int b = (int)(f2 / 4.0f); /* 21 */ + return a + b; /* 21 + 21 = 42 */ +} diff --git a/test/asm/roundtrip/conv_fp_fp.expected b/test/asm/roundtrip/conv_fp_fp.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_int_double.c b/test/asm/roundtrip/conv_int_double.c @@ -0,0 +1,12 @@ +/* int <-> double round-trip: SCVTF (int->double, 32-bit src) and FCVTZS + * (double->int). Doubles represent every 32-bit int exactly so the values are + * deterministic across opt levels. volatile operands defeat folding. + * + * i = 84 : d = (double)i = 84.0 (scvtf d,w) + * back : (int)(d/2) = 42 (fcvtzs w,d) */ +int test_main(void) { + volatile int i = 84; + double d = (double)i; /* scvtf d,w */ + int back = (int)(d / 2.0); /* 42, fcvtzs w,d */ + return back; +} diff --git a/test/asm/roundtrip/conv_int_double.expected b/test/asm/roundtrip/conv_int_double.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_int_float_s.c b/test/asm/roundtrip/conv_int_float_s.c @@ -0,0 +1,16 @@ +/* Signed int <-> float round-trip: SCVTF (int->float) and FCVTZS + * (float->int, truncating toward zero). volatile operands defeat folding so + * the conversions are really emitted. + * + * i = 42 : f = (float)i = 42.0f (scvtf s, w) + * back : (int)f = 42 (fcvtzs w, s) + * neg : (float)(-5) = -5.0f ; (int)(-5.0f*1.0f) = -5 exercises sign. */ +int test_main(void) { + volatile int i = 84; + volatile int j = -5; + float f = (float)i; /* 84.0f, scvtf s,w */ + float g = (float)j; /* -5.0f, scvtf s,w */ + int bi = (int)(f / 2.0f); /* 42, fcvtzs w,s */ + int bj = (int)g; /* -5, fcvtzs w,s */ + return bi + bj + 5; /* 42 - 5 + 5 = 42 */ +} diff --git a/test/asm/roundtrip/conv_int_float_s.expected b/test/asm/roundtrip/conv_int_float_s.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_int_float_u.c b/test/asm/roundtrip/conv_int_float_u.c @@ -0,0 +1,15 @@ +/* Unsigned int <-> float round-trip: UCVTF (uint->float) and FCVTZU + * (float->uint). A source above INT_MAX exercises the unsigned-specific + * encoding rather than the signed path. volatile operands defeat folding. + * + * u = 0x80000000 : f = (float)u = 2147483648.0f (ucvtf s,w) + * back : (unsigned)(f / k) reduces deterministically (fcvtzu). */ +int test_main(void) { + volatile unsigned int u = 0x80000000u; /* 2147483648 */ + volatile unsigned int v = 126u; + float f = (float)u; /* ucvtf s,w (unsigned) */ + float g = (float)v; /* ucvtf s,w */ + unsigned int bu = (unsigned int)(f / f); /* 1, fcvtzu w,s */ + unsigned int bv = (unsigned int)(g / 3.0f);/* 42, fcvtzu w,s */ + return (int)(bv + bu - 1); /* 42 + 1 - 1 = 42 */ +} diff --git a/test/asm/roundtrip/conv_int_float_u.expected b/test/asm/roundtrip/conv_int_float_u.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_intnarrow.c b/test/asm/roundtrip/conv_intnarrow.c @@ -0,0 +1,20 @@ +/* Integer narrowing: long -> int -> short -> char (truncation). + * Narrowing drops high bits; the backend models this with register-width + * selection and masking moves. Source values have set bits above each target + * width so the truncation is observable. volatile sources defeat folding. + * + * L = 0x1_0000_002A : (int)L = 0x2A = 42 (use w view) + * I = 0x1234_0040 : (short)I = 0x0040 = 64 + * I2 = 0x0000_12AB : (char)I2 = 0xAB sign-extends; use unsigned char + * Combine so the answer is exactly 42. */ +int test_main(void) { + volatile long L = 0x10000002AL; /* low 32 bits = 0x2A = 42 */ + volatile int I = 0x12340040; /* low 16 bits = 0x40 = 64 */ + volatile unsigned int I2 = 0x000012ABu; /* low 8 bits = 0xAB = 171 */ + int n_int = (int)L; /* 42 (truncate 64->32) */ + short n_short = (short)I; /* 64 (truncate 32->16) */ + unsigned char n_char = (unsigned char)I2;/* 171 (truncate 32->8) */ + /* 42 == n_int already; verify narrowing and fold the others away. */ + int extra = (n_short - 64) + ((int)n_char - 171); /* 0 */ + return n_int + extra; /* 42 + 0 = 42 */ +} diff --git a/test/asm/roundtrip/conv_intnarrow.expected b/test/asm/roundtrip/conv_intnarrow.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_intwiden_s.c b/test/asm/roundtrip/conv_intwiden_s.c @@ -0,0 +1,22 @@ +/* Signed integer widening: char/short -> int -> long via sign-extension. + * Negative source values exercise the sign bits so SXTB/SXTH/SXTW (sbfm + * aliases) are genuinely required, not a zero/move. volatile sources defeat + * folding so the extends are really emitted. + * + * sc = -2 : (int)sc = -2 (sxtb w) + * ss = -3 : (int)ss = -3 (sxth w) + * si = -4 : (long)si = -4 (sxtw x, sxtw alias of sbfm x) + * also char->long directly (sxtb into x). + * Sum the absolute contributions back up to 42. */ +int test_main(void) { + volatile signed char sc = -2; + volatile short ss = -3; + volatile int si = -4; + int a = (int)sc; /* -2, sxtb */ + int b = (int)ss; /* -3, sxth */ + long c = (long)si; /* -4, sxtw */ + long d = (long)sc; /* -2, sxtb -> x */ + long acc = -(long)a + -(long)b + -(long)c + -(long)d; /* 2+3+4+2 = 11 */ + long base = 31; + return (int)(acc + base); /* 11 + 31 = 42 */ +} diff --git a/test/asm/roundtrip/conv_intwiden_s.expected b/test/asm/roundtrip/conv_intwiden_s.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_intwiden_u.c b/test/asm/roundtrip/conv_intwiden_u.c @@ -0,0 +1,23 @@ +/* Unsigned integer widening: unsigned char/short -> int -> long via + * zero-extension. High source values exercise the zero bits so UXTB/UXTH + * (ubfm aliases) and the implicit 32->64 zero-extend (uxtw / mov w which + * zero-extends to x) are genuinely required. volatile sources defeat folding. + * + * uc = 200 : (int)uc = 200 (uxtb w / and w,#0xff) + * us = 300 : (int)us = 300 (uxth w / and w,#0xffff) + * ui = 0x80000005 : (unsigned long)ui (uxtw / 32->64 zero ext) + * Reduce to 42 with masks/mods so the result is deterministic. */ +int test_main(void) { + volatile unsigned char uc = 200; + volatile unsigned short us = 300; + volatile unsigned int ui = 0x80000005u; + unsigned int a = (unsigned int)uc; /* 200, uxtb */ + unsigned int b = (unsigned int)us; /* 300, uxth */ + unsigned long c = (unsigned long)ui; /* 0x0000000080000005, uxtw -> x */ + /* Zero-extension correctness: high half is 0 (a sign-extend would make it + * 0xFFFFFFFF since bit 31 is set). c>>32 == 0 confirms the uxtw path. */ + unsigned long hi = c >> 32; /* 0 when correctly zero-extended */ + unsigned long lo_nib = c & 0xFu; /* 5 */ + unsigned long acc = (a % 100) + (b % 100) + lo_nib + hi; /* 0+0+5+0 = 5 */ + return (int)(acc + 37); /* 5 + 37 = 42 */ +} diff --git a/test/asm/roundtrip/conv_intwiden_u.expected b/test/asm/roundtrip/conv_intwiden_u.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_long_double_s.c b/test/asm/roundtrip/conv_long_double_s.c @@ -0,0 +1,15 @@ +/* Signed long <-> double round-trip: SCVTF (long->double) and FCVTZS + * (double->long), using the 64-bit GPR forms. A value above 2^32 forces the + * 64-bit register variant. volatile operands defeat folding. + * + * L = 0x2_0000_0000 = 8589934592 : d = (double)L (scvtf d,x) + * back: (long)(d / 0x2_0000_0000) = 1 (fcvtzs x,d) */ +int test_main(void) { + volatile long L = 0x200000000L; /* 8589934592 */ + volatile long M = -84L; + double d = (double)L; /* scvtf d,x */ + double e = (double)M; /* scvtf d,x (negative) */ + long b1 = (long)(d / 8589934592.0); /* 1, fcvtzs x,d */ + long b2 = (long)(e / 2.0); /* -42, fcvtzs x,d */ + return (int)(-b2 + b1 - 1); /* 42 + 1 - 1 = 42 */ +} diff --git a/test/asm/roundtrip/conv_long_double_s.expected b/test/asm/roundtrip/conv_long_double_s.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/conv_long_double_u.c b/test/asm/roundtrip/conv_long_double_u.c @@ -0,0 +1,15 @@ +/* Unsigned long <-> double round-trip: UCVTF (ulong->double) and FCVTZU + * (double->ulong), 64-bit GPR forms. A value with the top bit set forces the + * unsigned 64-bit variant. volatile operands defeat folding. + * + * U = 0x8000_0000_0000_0000 : d = (double)U (ucvtf d,x, unsigned) + * back path reduces deterministically with fcvtzu x,d. */ +int test_main(void) { + volatile unsigned long U = 0x8000000000000000UL; + volatile unsigned long V = 84UL; + double d = (double)U; /* ucvtf d,x (unsigned, top bit) */ + double e = (double)V; /* ucvtf d,x */ + unsigned long b1 = (unsigned long)(d / d);/* 1, fcvtzu x,d */ + unsigned long b2 = (unsigned long)(e / 2.0);/* 42, fcvtzu x,d */ + return (int)(b2 + b1 - 1); /* 42 + 1 - 1 = 42 */ +} diff --git a/test/asm/roundtrip/conv_long_double_u.expected b/test/asm/roundtrip/conv_long_double_u.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_breakcont.c b/test/asm/roundtrip/ctl_breakcont.c @@ -0,0 +1,23 @@ +/* Nested loops exercising both `break` and `continue`. The inner loop skips + * even j (continue) and breaks once it has accumulated enough; the outer loop + * breaks when the running total reaches the target. volatile bounds keep every + * branch real so the loop-exit / skip-edge branches round-trip. Exit code 42. */ +int test_main(void) { + volatile int outer = 100, inner = 100; + int total = 0; + for (int i = 0; i < outer; i++) { + for (int j = 0; j < inner; j++) { + if ((j & 1) == 0) { + continue; /* skip even j */ + } + total += 1; + if (total >= 42) { + break; /* stop the inner loop */ + } + } + if (total >= 42) { + break; /* stop the outer loop */ + } + } + return total; +} diff --git a/test/asm/roundtrip/ctl_breakcont.expected b/test/asm/roundtrip/ctl_breakcont.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_dowhile.c b/test/asm/roundtrip/ctl_dowhile.c @@ -0,0 +1,11 @@ +/* A do/while loop: the condition is tested at the bottom, so the body always + * runs once and the back-edge is a single conditional branch. volatile bound + * keeps it real. Adds 7 each pass until reaching 42 (7*6). Exit code 42. */ +int test_main(void) { + volatile int limit = 42; + int v = 0; + do { + v += 7; + } while (v < limit); + return v; +} diff --git a/test/asm/roundtrip/ctl_dowhile.expected b/test/asm/roundtrip/ctl_dowhile.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_for.c b/test/asm/roundtrip/ctl_for.c @@ -0,0 +1,11 @@ +/* A simple counted for loop. The volatile bound keeps the loop real (no + * unrolling to a constant) so the loop back-edge branch and the loop-exit + * compare/branch round-trip through `as`. Sum 0+1+..+8 = 36, +6 = 42. */ +int test_main(void) { + volatile int n = 9; + int sum = 0; + for (int i = 0; i < n; i++) { + sum += i; + } + return sum + 6; +} diff --git a/test/asm/roundtrip/ctl_for.expected b/test/asm/roundtrip/ctl_for.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_for_long.c b/test/asm/roundtrip/ctl_for_long.c @@ -0,0 +1,11 @@ +/* A 64-bit counted loop: the induction variable and bound are `long`, so the + * loop compare/branch operate on x-registers (64-bit cmp). volatile bound keeps + * the loop real. Sums 0..8 = 36, +6 = 42. Exit code 42. */ +int test_main(void) { + volatile long n = 9; + long sum = 0; + for (long i = 0; i < n; i++) { + sum += i; + } + return (int)(sum + 6); +} diff --git a/test/asm/roundtrip/ctl_for_long.expected b/test/asm/roundtrip/ctl_for_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_ifelse.c b/test/asm/roundtrip/ctl_ifelse.c @@ -0,0 +1,17 @@ +/* if/else chain compiled to intra-function conditional branches. volatile + * input defeats constant folding so the comparisons + branches are really + * emitted; the branch labels round-trip through `as`. Exit code 42. */ +int test_main(void) { + volatile int x = 2; + int r; + if (x == 0) { + r = 10; + } else if (x == 1) { + r = 20; + } else if (x == 2) { + r = 42; + } else { + r = 99; + } + return r; +} diff --git a/test/asm/roundtrip/ctl_ifelse.expected b/test/asm/roundtrip/ctl_ifelse.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_loops_mixed.c b/test/asm/roundtrip/ctl_loops_mixed.c @@ -0,0 +1,22 @@ +/* A mix of loop forms in one function: a while accumulator feeding a do/while + * that drains it, with an if guard between them. Exercises multiple distinct + * branch labels and back-edges in a single body so label allocation and the + * round-trip of several branches is checked together. volatile bounds keep all + * branches real. Net result computed to 42. Exit code 42. */ +int test_main(void) { + volatile int up = 10, down = 3; + int acc = 0; + int i = 0; + while (i < up) { /* 0+1+..+9 = 45 */ + acc += i; + i++; + } + if (acc > 40) { + int j = 0; + do { /* subtract 3 once: 45 - 3 = 42 */ + acc -= down; + j++; + } while (j < 1); + } + return acc; +} diff --git a/test/asm/roundtrip/ctl_loops_mixed.expected b/test/asm/roundtrip/ctl_loops_mixed.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_nested.c b/test/asm/roundtrip/ctl_nested.c @@ -0,0 +1,14 @@ +/* Doubly-nested counted loops with no break/continue: pure back-edges. The + * inner loop contributes a multiply-accumulate; volatile bounds keep both loop + * structures real so the inner back-edge, outer back-edge, and both exit + * branches round-trip. 6 outer * 7 inner = 42 increments. Exit code 42. */ +int test_main(void) { + volatile int rows = 6, cols = 7; + int count = 0; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + count += 1; + } + } + return count; +} diff --git a/test/asm/roundtrip/ctl_nested.expected b/test/asm/roundtrip/ctl_nested.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_shortcircuit_and.c b/test/asm/roundtrip/ctl_shortcircuit_and.c @@ -0,0 +1,12 @@ +/* Short-circuit && : the second operand must not be evaluated when the first + * is false, which the compiler implements with a conditional branch past the + * RHS. volatile operands keep both sub-conditions real so the skip branch + * round-trips. Both true => add 42. Exit code 42. */ +int test_main(void) { + volatile int a = 5, b = 9; + int r = 0; + if (a > 0 && b > 0) { + r = 42; + } + return r; +} diff --git a/test/asm/roundtrip/ctl_shortcircuit_and.expected b/test/asm/roundtrip/ctl_shortcircuit_and.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_shortcircuit_or.c b/test/asm/roundtrip/ctl_shortcircuit_or.c @@ -0,0 +1,12 @@ +/* Short-circuit || : the second operand is skipped when the first is true, + * implemented with a conditional branch to the taken block. volatile operands + * keep both sub-conditions real so the skip branch round-trips. First true => + * r = 42. Exit code 42. */ +int test_main(void) { + volatile int a = 1, b = 0; + int r = 0; + if (a != 0 || b != 0) { + r = 42; + } + return r; +} diff --git a/test/asm/roundtrip/ctl_shortcircuit_or.expected b/test/asm/roundtrip/ctl_shortcircuit_or.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_shortcircuit_val.c b/test/asm/roundtrip/ctl_shortcircuit_val.c @@ -0,0 +1,10 @@ +/* Short-circuit && / || used for their *value* (0/1) rather than as an if + * condition. The compiler materializes a boolean via compares and conditional + * branches/selects. volatile operands keep them real. (a&&b) is 1, (c||d) is 1; + * 1*20 + 1*22 = 42. Exit code 42. */ +int test_main(void) { + volatile int a = 3, b = 4, c = 0, d = 5; + int p = (a && b); /* 1 */ + int q = (c || d); /* 1 */ + return p * 20 + q * 22; +} diff --git a/test/asm/roundtrip/ctl_shortcircuit_val.expected b/test/asm/roundtrip/ctl_shortcircuit_val.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_ternary.c b/test/asm/roundtrip/ctl_ternary.c @@ -0,0 +1,8 @@ +/* The ?: ternary operator. volatile operands defeat folding so the conditional + * select (compare + conditional branch, or a csel) is really emitted and its + * branch/select round-trips. a > b picks a (=42); else would pick b. */ +int test_main(void) { + volatile int a = 42, b = 17; + int r = (a > b) ? a : b; + return r; +} diff --git a/test/asm/roundtrip/ctl_ternary.expected b/test/asm/roundtrip/ctl_ternary.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_ternary_chain.c b/test/asm/roundtrip/ctl_ternary_chain.c @@ -0,0 +1,11 @@ +/* A chain of nested ?: ternaries (a small jump-ladder of conditional selects). + * volatile selector defeats folding so each compare/select round-trips. + * sel==2 yields 42. */ +int test_main(void) { + volatile int sel = 2; + int r = (sel == 0) ? 10 + : (sel == 1) ? 20 + : (sel == 2) ? 42 + : 99; + return r; +} diff --git a/test/asm/roundtrip/ctl_ternary_chain.expected b/test/asm/roundtrip/ctl_ternary_chain.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_ternary_long.c b/test/asm/roundtrip/ctl_ternary_long.c @@ -0,0 +1,7 @@ +/* A ?: ternary over 64-bit `long` operands, so the compare/select runs on + * x-registers. volatile operands defeat folding. a < b selects b (=42). */ +int test_main(void) { + volatile long a = 10, b = 42; + long r = (a < b) ? b : a; + return (int)r; +} diff --git a/test/asm/roundtrip/ctl_ternary_long.expected b/test/asm/roundtrip/ctl_ternary_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_while.c b/test/asm/roundtrip/ctl_while.c @@ -0,0 +1,11 @@ +/* A while loop with a top-of-loop condition test. volatile bound forces the + * compare + conditional branch and the unconditional back-edge to be emitted + * so both round-trip through `as`. Counts 0..42 stepping by 6 => 42. */ +int test_main(void) { + volatile int limit = 42; + int v = 0; + while (v < limit) { + v += 6; + } + return v; +} diff --git a/test/asm/roundtrip/ctl_while.expected b/test/asm/roundtrip/ctl_while.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ctl_while_unsigned.c b/test/asm/roundtrip/ctl_while_unsigned.c @@ -0,0 +1,11 @@ +/* A while loop driven by an *unsigned* comparison, which selects an + * unsigned-condition branch (HS/LO) distinct from the signed forms exercised + * elsewhere. volatile bound keeps the loop real. Steps 0,6,..,42 => 42. */ +int test_main(void) { + volatile unsigned int limit = 42u; + unsigned int v = 0u; + while (v < limit) { + v += 6u; + } + return (int)v; +} diff --git a/test/asm/roundtrip/ctl_while_unsigned.expected b/test/asm/roundtrip/ctl_while_unsigned.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_double.c b/test/asm/roundtrip/fparith_double.c @@ -0,0 +1,9 @@ +/* Double-precision fadd/fsub/fmul/fdiv round-trip. + * volatile locals defeat constant folding so real FADD/FSUB/FMUL/FDIV (double) + * are emitted, plus an FCVTZS for the final (int) conversion. + * ((6.0 + 1.0) * 3.0) / 0.5 - 0.0 = 42.0; (int)42.0 == 42. */ +int test_main(void) { + volatile double a = 6.0, b = 1.0, c = 3.0, d = 0.5; + double r = ((a + b) * c) / d; /* (7*3)/0.5 = 42 */ + return (int)r == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_double.expected b/test/asm/roundtrip/fparith_double.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_double_add.c b/test/asm/roundtrip/fparith_double_add.c @@ -0,0 +1,9 @@ +/* Double FADD round-trip. volatile int inputs are converted to double so a real + * SCVTF + FADD (double) is emitted (no constant folding). 40.0 + 2.0 = 42.0; + * (int)42.0 == 42. */ +int test_main(void) { + volatile int ia = 40, ib = 2; + double a = (double)ia, b = (double)ib; + double r = a + b; + return (int)r == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_double_add.expected b/test/asm/roundtrip/fparith_double_add.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_double_div.c b/test/asm/roundtrip/fparith_double_div.c @@ -0,0 +1,8 @@ +/* Double FDIV round-trip. volatile int inputs -> double so a real FDIV (double) + * is emitted. 84.0 / 2.0 = 42.0; (int)42.0 == 42. */ +int test_main(void) { + volatile int ia = 84, ib = 2; + double a = (double)ia, b = (double)ib; + double r = a / b; + return (int)r == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_double_div.expected b/test/asm/roundtrip/fparith_double_div.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_double_mul.c b/test/asm/roundtrip/fparith_double_mul.c @@ -0,0 +1,8 @@ +/* Double FMUL round-trip. volatile int inputs -> double so a real FMUL (double) + * is emitted. 6.0 * 7.0 = 42.0; (int)42.0 == 42. */ +int test_main(void) { + volatile int ia = 6, ib = 7; + double a = (double)ia, b = (double)ib; + double r = a * b; + return (int)r == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_double_mul.expected b/test/asm/roundtrip/fparith_double_mul.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_double_neg.c b/test/asm/roundtrip/fparith_double_neg.c @@ -0,0 +1,10 @@ +/* Double FNEG round-trip. volatile int input -> double, negate, negate back via + * subtraction so a real FNEG (double) is emitted. -(-42.0) = 42.0; + * (int)42.0 == 42. */ +int test_main(void) { + volatile int ia = 42; + double a = (double)ia; + double r = -a; /* FNEG: -42.0 */ + double s = -r; /* FNEG: 42.0 */ + return (int)s == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_double_neg.expected b/test/asm/roundtrip/fparith_double_neg.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_double_sub.c b/test/asm/roundtrip/fparith_double_sub.c @@ -0,0 +1,8 @@ +/* Double FSUB round-trip. volatile int inputs -> double so a real FSUB (double) + * is emitted. 50.0 - 8.0 = 42.0; (int)42.0 == 42. */ +int test_main(void) { + volatile int ia = 50, ib = 8; + double a = (double)ia, b = (double)ib; + double r = a - b; + return (int)r == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_double_sub.expected b/test/asm/roundtrip/fparith_double_sub.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_float_add.c b/test/asm/roundtrip/fparith_float_add.c @@ -0,0 +1,9 @@ +/* Single-precision FADD round-trip. volatile int inputs -> float so a real + * SCVTF + FADD (single, Sd operands) is emitted. 40.0f + 2.0f = 42.0f; + * (int)42.0f == 42. */ +int test_main(void) { + volatile int ia = 40, ib = 2; + float a = (float)ia, b = (float)ib; + float r = a + b; + return (int)r == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_float_add.expected b/test/asm/roundtrip/fparith_float_add.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_float_div.c b/test/asm/roundtrip/fparith_float_div.c @@ -0,0 +1,8 @@ +/* Single-precision FDIV round-trip. volatile int inputs -> float so a real + * FDIV (single) is emitted. 84.0f / 2.0f = 42.0f; (int)42.0f == 42. */ +int test_main(void) { + volatile int ia = 84, ib = 2; + float a = (float)ia, b = (float)ib; + float r = a / b; + return (int)r == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_float_div.expected b/test/asm/roundtrip/fparith_float_div.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_float_mul.c b/test/asm/roundtrip/fparith_float_mul.c @@ -0,0 +1,8 @@ +/* Single-precision FMUL round-trip. volatile int inputs -> float so a real + * FMUL (single) is emitted. 6.0f * 7.0f = 42.0f; (int)42.0f == 42. */ +int test_main(void) { + volatile int ia = 6, ib = 7; + float a = (float)ia, b = (float)ib; + float r = a * b; + return (int)r == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_float_mul.expected b/test/asm/roundtrip/fparith_float_mul.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_float_neg.c b/test/asm/roundtrip/fparith_float_neg.c @@ -0,0 +1,9 @@ +/* Single-precision FNEG round-trip. volatile int input -> float, negate twice so + * a real FNEG (single) is emitted. -(-42.0f) = 42.0f; (int)42.0f == 42. */ +int test_main(void) { + volatile int ia = 42; + float a = (float)ia; + float r = -a; /* FNEG: -42.0f */ + float s = -r; /* FNEG: 42.0f */ + return (int)s == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_float_neg.expected b/test/asm/roundtrip/fparith_float_neg.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fparith_float_sub.c b/test/asm/roundtrip/fparith_float_sub.c @@ -0,0 +1,8 @@ +/* Single-precision FSUB round-trip. volatile int inputs -> float so a real + * FSUB (single) is emitted. 50.0f - 8.0f = 42.0f; (int)42.0f == 42. */ +int test_main(void) { + volatile int ia = 50, ib = 8; + float a = (float)ia, b = (float)ib; + float r = a - b; + return (int)r == 42 ? 42 : 0; +} diff --git a/test/asm/roundtrip/fparith_float_sub.expected b/test/asm/roundtrip/fparith_float_sub.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fpcmp_branch.c b/test/asm/roundtrip/fpcmp_branch.c @@ -0,0 +1,10 @@ +/* fp comparison used directly as a branch condition (fcmp + b.cond) rather + * than materialized to a bool with cset. volatile operands defeat folding so + * the compare and conditional branch survive. Exit: 42. */ +int test_main(void) { + volatile double x = 6.0, y = 7.0; + int r = 0; + if (x < y) r += 42; else r += 1; /* taken: x < y */ + if (x > y) r += 100; /* not taken */ + return r; +} diff --git a/test/asm/roundtrip/fpcmp_branch.expected b/test/asm/roundtrip/fpcmp_branch.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fpcmp_double_eq.c b/test/asm/roundtrip/fpcmp_double_eq.c @@ -0,0 +1,11 @@ +/* double == and != as bool results (fcmp d, d + cset). volatile operands + * defeat constant folding so fcmp is actually emitted. Exit: 21*2 = 42. */ +int test_main(void) { + volatile double a = 3.5, b = 3.5, c = 9.0; + int eq = (a == b); /* 1 */ + int ne = (a != c); /* 1 */ + int r = 0; + if (eq) r += 20; + if (ne) r += 22; + return r; +} diff --git a/test/asm/roundtrip/fpcmp_double_eq.expected b/test/asm/roundtrip/fpcmp_double_eq.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fpcmp_double_lt.c b/test/asm/roundtrip/fpcmp_double_lt.c @@ -0,0 +1,15 @@ +/* double <, <=, >, >= as bool results (fcmp d, d + cset with various conds). + * volatile operands defeat folding. Exit: sum of four 1s scaled = 42. */ +int test_main(void) { + volatile double a = 1.25, b = 2.5; + int lt = (a < b); /* 1 */ + int le = (a <= b); /* 1 */ + int gt = (b > a); /* 1 */ + int ge = (b >= a); /* 1 */ + int r = 0; + if (lt) r += 10; + if (le) r += 10; + if (gt) r += 11; + if (ge) r += 11; + return r; +} diff --git a/test/asm/roundtrip/fpcmp_double_lt.expected b/test/asm/roundtrip/fpcmp_double_lt.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fpcmp_float_eq.c b/test/asm/roundtrip/fpcmp_float_eq.c @@ -0,0 +1,11 @@ +/* float == and != as bool results (fcmp s, s + cset). volatile operands + * defeat constant folding. Exit: 20 + 22 = 42. */ +int test_main(void) { + volatile float a = 1.5f, b = 1.5f, c = -4.0f; + int eq = (a == b); /* 1 */ + int ne = (a != c); /* 1 */ + int r = 0; + if (eq) r += 20; + if (ne) r += 22; + return r; +} diff --git a/test/asm/roundtrip/fpcmp_float_eq.expected b/test/asm/roundtrip/fpcmp_float_eq.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fpcmp_float_lt.c b/test/asm/roundtrip/fpcmp_float_lt.c @@ -0,0 +1,15 @@ +/* float <, <=, >, >= as bool results (fcmp s, s + cset with various conds). + * volatile operands defeat folding. Exit: 10+10+11+11 = 42. */ +int test_main(void) { + volatile float a = 0.5f, b = 7.5f; + int lt = (a < b); /* 1 */ + int le = (a <= b); /* 1 */ + int gt = (b > a); /* 1 */ + int ge = (b >= a); /* 1 */ + int r = 0; + if (lt) r += 10; + if (le) r += 10; + if (gt) r += 11; + if (ge) r += 11; + return r; +} diff --git a/test/asm/roundtrip/fpcmp_float_lt.expected b/test/asm/roundtrip/fpcmp_float_lt.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fpcmp_nan.c b/test/asm/roundtrip/fpcmp_nan.c @@ -0,0 +1,16 @@ +/* NaN-tolerant ordered/unordered double comparisons. A runtime 0.0/0.0 + * produces a NaN deterministically; comparisons against NaN exercise the + * unordered condition codes (== false, != true, < and > false). volatile + * defeats folding and keeps the divide at runtime. Exit: 20 + 22 = 42. */ +int test_main(void) { + volatile double z = 0.0; + volatile double nan = z / z; /* NaN at runtime */ + volatile double a = 1.0; + int r = 0; + if (!(nan == a)) r += 20; /* unordered: == is false, so this taken */ + if (nan != a) r += 22; /* unordered: != is true, so this taken */ + if (nan < a) r += 100; /* unordered: < is false, not taken */ + if (nan > a) r += 100; /* unordered: > is false, not taken */ + if (nan >= a) r += 100; /* unordered: >= is false, not taken */ + return r; +} diff --git a/test/asm/roundtrip/fpcmp_nan.expected b/test/asm/roundtrip/fpcmp_nan.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/fpcmp_zero.c b/test/asm/roundtrip/fpcmp_zero.c @@ -0,0 +1,11 @@ +/* Comparison against the literal 0.0 can lower to the fcmp-with-zero form + * (fcmp d, #0.0 / fcmp s, #0.0) which is encoded differently from the + * register-register fcmp. volatile defeats folding. Exit: 21 + 21 = 42. */ +int test_main(void) { + volatile double d = 5.0; + volatile float f = -3.0f; + int r = 0; + if (d > 0.0) r += 21; /* taken: fcmp d, #0.0 */ + if (f < 0.0f) r += 21; /* taken: fcmp s, #0.0 */ + return r; +} diff --git a/test/asm/roundtrip/fpcmp_zero.expected b/test/asm/roundtrip/fpcmp_zero.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_addr.c b/test/asm/roundtrip/glob_addr.c @@ -0,0 +1,12 @@ +/* glob: take the address of a file-scope global and pass it to a noinline + * function that mutates through the pointer. Codegen materializes &g with + * adrp/add (ADR_PREL_PG_HI21 + ADD_ABS_LO12_NC against `g`) into the arg + * register, and the callee does a plain indirect store. `g` is initialized + * nonzero to keep it in .data (a .bss global hits the SHT_NOBITS assembler gap + * — see glob_bss_write.skip). Exit: g set to 42. */ +int g = 1; +__attribute__((noinline)) static void set42(int *p) { *p = 42; } +int test_main(void) { + set42(&g); /* adrp/add &g -> x0, bl set42 */ + return g; +} diff --git a/test/asm/roundtrip/glob_addr.expected b/test/asm/roundtrip/glob_addr.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_array.c b/test/asm/roundtrip/glob_array.c @@ -0,0 +1,9 @@ +/* glob: read a file-scope (non-const) global int array. Codegen materializes + * the array base with adrp/add (ADR_PREL_PG_HI21 + ADD_ABS_LO12_NC against + * `tab`) and indexes it with scaled loads. A volatile index defeats folding. + * Exit: tab[2] + tab[4] = 13 + 29 = 42. */ +int tab[6] = { 5, 9, 13, 17, 29, 31 }; +int test_main(void) { + volatile int i = 2, j = 4; + return tab[i] + tab[j]; +} diff --git a/test/asm/roundtrip/glob_array.expected b/test/asm/roundtrip/glob_array.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_array_long.c b/test/asm/roundtrip/glob_array_long.c @@ -0,0 +1,9 @@ +/* glob: read a file-scope global `long` array — the index scaling is 8 so the + * load is a 64-bit scaled-register form, distinct from the 32-bit array case. + * adrp/add (ADR_PREL_PG_HI21 + ADD_ABS_LO12_NC against `tabl`) plus ldr x. + * A volatile index defeats folding. Exit: tabl[1] + tabl[2] = 15 + 27 = 42. */ +long tabl[4] = { 3, 15, 27, 39 }; +int test_main(void) { + volatile int i = 1, j = 2; + return (int)(tabl[i] + tabl[j]); +} diff --git a/test/asm/roundtrip/glob_array_long.expected b/test/asm/roundtrip/glob_array_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_bss_write.c b/test/asm/roundtrip/glob_bss_write.c @@ -0,0 +1,17 @@ +/* glob: write to a ZERO-initialized file-scope global (lives in .bss). This is + * the canonical case for the .bss assembler gap: codegen emits `.bss` as + * SHT_NOBITS (no file contents, JIT allocates a fresh writable zero page), but + * `cfree as` re-emits the section as SHT_PROGBITS. The round-tripped object's + * .bss therefore lands in the read-only image region, and the `str` against + * `g` faults (BUS on WRITE) under the JIT. L0/L1 pass (the .text bytes and + * relocs match; L1 does not compare .bss section type), L2 aborts. + * + * Currently SKIPPED (see glob_bss_write.skip). Counterpart glob_rw.c keeps the + * write path green by initializing its global nonzero so it lands in .data. + * Exit when the gap is fixed: g becomes 42. */ +int g = 0; +int test_main(void) { + volatile int v = 42; + g = v; /* adrp/str against g in .bss */ + return g; +} diff --git a/test/asm/roundtrip/glob_bss_write.expected b/test/asm/roundtrip/glob_bss_write.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_bss_write.skip b/test/asm/roundtrip/glob_bss_write.skip @@ -0,0 +1 @@ +assembler .bss is PROGBITS, not NOBITS: cc -S emits `.section .bss` + `.zero N`, but `as` writes real zero bytes and tracks position by byte count, so the symbol lands at offset 0 and the section emits SHT_PROGBITS. The round-tripped .bss loads read-only in the JIT image and a store faults. L0/L1 pass; L2 aborts. Needs NOBITS position-tracking in the assembler (symbol offsets + .zero/.skip/.align advance bss_size). Tracked in doc/ASM_ROUNDTRIP_TESTING.md. glob_rw covers the write path via a .data global. diff --git a/test/asm/roundtrip/glob_const_array.c b/test/asm/roundtrip/glob_const_array.c @@ -0,0 +1,9 @@ +/* glob: read a file-scope `const` global array (lives in .rodata rather than + * .data). Addressing is the same adrp/add :lo12: symbolic pair, but against a + * read-only section symbol, exercising the .rodata data reloc. A volatile index + * defeats folding. Exit: ct[1] + ct[3] = 20 + 22 = 42. */ +static const int ct[5] = { 18, 20, 8, 22, 16 }; +int test_main(void) { + volatile int i = 1, j = 3; + return ct[i] + ct[j]; +} diff --git a/test/asm/roundtrip/glob_const_array.expected b/test/asm/roundtrip/glob_const_array.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_long_rw.c b/test/asm/roundtrip/glob_long_rw.c @@ -0,0 +1,11 @@ +/* glob: read+write a 64-bit file-scope global. The store/load are 64-bit + * (LDST64_ABS_LO12_NC against `gl`) so the :lo12: scaling differs from the + * 32-bit case. A volatile local defeats folding. `gl` is initialized nonzero + * to keep it in .data (a .bss global hits the SHT_NOBITS assembler gap — see + * glob_bss_write.skip). Exit: gl truncated to 42. */ +long gl = 1; +int test_main(void) { + volatile long v = 42; + gl = v; /* adrp/str x against gl */ + return (int)gl; /* adrp/ldr x against gl */ +} diff --git a/test/asm/roundtrip/glob_long_rw.expected b/test/asm/roundtrip/glob_long_rw.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_rw.c b/test/asm/roundtrip/glob_rw.c @@ -0,0 +1,13 @@ +/* glob: read AND write a file-scope global int. The write forces an adrp/str + * pair (ADR_PREL_PG_HI21 + LDST32_ABS_LO12_NC against `g`) in addition to the + * load, so both the symbolic store and load round-trip. A volatile local + * defeats folding the final value to a constant. `g` is initialized nonzero so + * it lands in .data (a zero-init global would land in .bss, which the + * assembler currently mis-emits as SHT_PROGBITS — see glob_bss_write.skip). + * Exit: g becomes 42. */ +int g = 1; +int test_main(void) { + volatile int v = 42; + g = v; /* adrp/str against g */ + return g; /* adrp/ldr against g */ +} diff --git a/test/asm/roundtrip/glob_rw.expected b/test/asm/roundtrip/glob_rw.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_static.c b/test/asm/roundtrip/glob_static.c @@ -0,0 +1,20 @@ +/* glob: a function-local `static` that persists across calls. The static lives + * in .data (initialized nonzero to dodge the .bss SHT_NOBITS assembler gap) and + * is addressed with the same adrp/add+ldr/str symbolic pair as a file-scope + * global, but through a local symbol. noinline keeps `bump` a real call so the + * accumulation actually happens across 6 invocations. + * + * NOTE: currently SKIPPED. The compiler mangles the static-local symbol as + * `acc.1`, and the assembler's :lo12: operand parser stops at the `.`, so + * `ldr w9, [x16, :lo12:acc.1]` fails to assemble (`expected ']'`). See + * glob_static.skip. Exit when the gap is fixed: bump returns 6 + 6*6 = 42. */ +__attribute__((noinline)) static int bump(void) { + static int acc = 6; /* nonzero -> .data, not .bss */ + acc += 6; + return acc; +} +int test_main(void) { + int r = 0; + for (int i = 0; i < 6; ++i) r = bump(); + return r; /* 6 + 6*6 = 42 */ +} diff --git a/test/asm/roundtrip/glob_static.expected b/test/asm/roundtrip/glob_static.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_string.c b/test/asm/roundtrip/glob_string.c @@ -0,0 +1,9 @@ +/* glob: index a string literal. The literal lives in a .rodata string-merge + * section addressed via a local .L label (adrp/add against the .Lstr symbol); + * a volatile index defeats constant folding so the scaled byte load survives. + * "*"=='*' has ASCII 42. Exit: 42. */ +int test_main(void) { + const char *s = "0123456789*"; + volatile int i = 10; /* index of '*' */ + return s[i]; /* ldrb of '*' == 42 */ +} diff --git a/test/asm/roundtrip/glob_string.expected b/test/asm/roundtrip/glob_string.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/glob_string_addr.c b/test/asm/roundtrip/glob_string_addr.c @@ -0,0 +1,15 @@ +/* glob: pass the address of a string literal to a noinline function. The .L + * string label is materialized with adrp/add into the arg register (the reloc + * is against the local .Lstr symbol, not a global), and the callee reads bytes + * through the pointer. sumlen walks until the NUL. The 6-char body sums to 42: + * '\1'+'\2'+'\3'+'\4'+'\5'+'\6' = 21, doubled by adding twice... actually we + * pick bytes summing to 42 directly. Exit: 42. */ +__attribute__((noinline)) static int sumbytes(const char *p) { + int s = 0; + while (*p) { s += (unsigned char)*p; ++p; } + return s; +} +int test_main(void) { + /* bytes 0x0c + 0x0c + 0x0c + 0x06 = 42, none are NUL */ + return sumbytes("\x0c\x0c\x0c\x06"); +} diff --git a/test/asm/roundtrip/glob_string_addr.expected b/test/asm/roundtrip/glob_string_addr.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iarith_addsub.c b/test/asm/roundtrip/iarith_addsub.c @@ -0,0 +1,13 @@ +/* iarith: 32-bit integer add and subtract. + * + * volatile operands force real `add w`/`sub w` instructions to be emitted + * (the optimizer cannot fold them to a single mov). No relocs, no branches, + * so all of L0/L1/L2 should round-trip through `cc -S | as`. + * + * (50 + 17) - 25 = 42. */ +int test_main(void) { + volatile int a = 50, b = 17, c = 25; + int sum = a + b; + int diff = sum - c; + return diff; +} diff --git a/test/asm/roundtrip/iarith_addsub.expected b/test/asm/roundtrip/iarith_addsub.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iarith_divrem_s.c b/test/asm/roundtrip/iarith_divrem_s.c @@ -0,0 +1,15 @@ +/* iarith: 32-bit SIGNED divide and remainder. + * + * volatile operands force a real `sdiv w` and the `msub w` that aarch64 uses + * to compute the remainder (rem = a - (a/b)*b). Operands chosen so neither + * -O0 nor -O1 can fold and no signed overflow / div-by-zero occurs. + * + * 1000 / 23 = 43 (43*23 = 989) + * 1000 % 23 = 11 + * 43 - 11 + 10 = 42. */ +int test_main(void) { + volatile int a = 1000, b = 23; + int q = a / b; + int r = a % b; + return q - r + 10; +} diff --git a/test/asm/roundtrip/iarith_divrem_s.expected b/test/asm/roundtrip/iarith_divrem_s.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iarith_divrem_u.c b/test/asm/roundtrip/iarith_divrem_u.c @@ -0,0 +1,14 @@ +/* iarith: 32-bit UNSIGNED divide and remainder. + * + * Unsigned operands select `udiv w` (vs the signed `sdiv w`) plus the + * `msub w` for the remainder. volatile defeats folding. + * + * 4000000000u / 100000000u = 40 + * 4000000000u % 100000000u = 0 + * 40 + 2 = 42. */ +int test_main(void) { + volatile unsigned int a = 4000000000u, b = 100000000u; + unsigned int q = a / b; + unsigned int r = a % b; + return (int)(q + r + 2); +} diff --git a/test/asm/roundtrip/iarith_divrem_u.expected b/test/asm/roundtrip/iarith_divrem_u.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iarith_long.c b/test/asm/roundtrip/iarith_long.c @@ -0,0 +1,21 @@ +/* iarith: 64-bit (long) add/sub/mul/sdiv/udiv/rem. + * + * volatile longs force the 64-bit `x`-register forms: `add x`/`sub x`/`mul x`, + * `sdiv x`/`udiv x` and the `msub x` remainder. Values exceed 32 bits so the + * computation genuinely needs 64-bit registers. + * + * p = 0x1_0000_0007 * 3 = 0x3_0000_0015 + * p += 0x2_0000_0000 = 0x5_0000_0015 + * q = p / 0x1_0000_0000 (=5) + * r = p % 0x1_0000_0000 (=0x15=21) + * sub = q*0x100000000 ... avoided; keep it simple below. */ +int test_main(void) { + volatile long a = 0x100000007L, b = 3, c = 0x200000000L, d = 0x100000000L; + long p = a * b; /* 0x300000015 */ + p = p + c; /* 0x500000015 */ + long q = p / d; /* 5 */ + long r = p % d; /* 0x15 = 21 */ + unsigned long uq = (unsigned long)p / (unsigned long)d; /* 5, udiv x */ + long res = q + r + (long)uq + 11; /* 5 + 21 + 5 + 11 = 42 */ + return (int)res; +} diff --git a/test/asm/roundtrip/iarith_long.expected b/test/asm/roundtrip/iarith_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iarith_mul.c b/test/asm/roundtrip/iarith_mul.c @@ -0,0 +1,9 @@ +/* iarith: 32-bit integer multiply (and the mul+sub `msub`-style pattern). + * + * volatile operands force a real `mul w` rather than constant folding. + * 100 * 7 - 658 = 42. */ +int test_main(void) { + volatile int a = 100, b = 7, c = 658; + int prod = a * b; + return prod - c; +} diff --git a/test/asm/roundtrip/iarith_mul.expected b/test/asm/roundtrip/iarith_mul.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_andnot.c b/test/asm/roundtrip/ibit_andnot.c @@ -0,0 +1,12 @@ +/* 32-bit "and-not" / "or-not": x & ~y and x | ~y. On aarch64 these may lower to + * the BIC (bit clear) and ORN (or-not) shifted-register forms. volatile keeps + * both operands live. + * x = 0xFF, y = 0x0F + * x & ~y = 0xF0 (240); (x | ~y) & 0xFF = 0xFF (255) + * (240 + 255) - 453 = 42. */ +int test_main(void) { + volatile int x = 0xFF, y = 0x0F; + int bic = x & ~y; + int orn = (x | ~y) & 0xFF; + return (bic + orn) - 453; +} diff --git a/test/asm/roundtrip/ibit_andnot.expected b/test/asm/roundtrip/ibit_andnot.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_logic.c b/test/asm/roundtrip/ibit_logic.c @@ -0,0 +1,13 @@ +/* 32-bit bitwise AND/ORR/EOR with register operands. volatile locals defeat + * constant folding so real `and`/`orr`/`eor` (shifted-register form) are + * emitted rather than folded to a mov. Branch-free, no relocs. + * a = 0xF0, b = 0x3C + * a & b = 0x30 (48), a | b = 0xFC (252), a ^ b = 0xCC (204) + * (48 + 252 + 204) - 462 = 42. */ +int test_main(void) { + volatile int a = 0xF0, b = 0x3C; + int and_ = a & b; + int or_ = a | b; + int xor_ = a ^ b; + return (and_ + or_ + xor_) - 462; +} diff --git a/test/asm/roundtrip/ibit_logic.expected b/test/asm/roundtrip/ibit_logic.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_logic_imm.c b/test/asm/roundtrip/ibit_logic_imm.c @@ -0,0 +1,12 @@ +/* 32-bit bitwise ops with an immediate operand. On aarch64 these use the + * logical-immediate (bitmask) encoding of and/orr/eor when the constant is a + * representable bitmask. volatile on the variable side keeps the op live. + * a = 0x3C; a & 0xF0 = 0x30 (48); a | 0x03 = 0x3F (63) + * a ^ 0xFF = 0xC3 (195); (48 + 63 + 195) - 264 = 42. */ +int test_main(void) { + volatile int a = 0x3C; + int and_ = a & 0xF0; + int or_ = a | 0x03; + int xor_ = a ^ 0xFF; + return (and_ + or_ + xor_) - 264; +} diff --git a/test/asm/roundtrip/ibit_logic_imm.expected b/test/asm/roundtrip/ibit_logic_imm.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_long.c b/test/asm/roundtrip/ibit_long.c @@ -0,0 +1,14 @@ +/* 64-bit bitwise AND/ORR/EOR/NOT on `long`. Exercises the x-register (64-bit) + * forms of and/orr/eor/mvn. volatile defeats folding. Branch-free, no relocs. + * a = 0xF0, b = 0x3C + * a & b = 0x30 (48), a | b = 0xFC (252), a ^ b = 0xCC (204) + * ~a = 0xFFFFFFFFFFFFFF0F; (~a & 0xFF) = 0x0F (15) + * (48 + 252 + 204 + 15) - 477 = 42. */ +int test_main(void) { + volatile long a = 0xF0, b = 0x3C; + long and_ = a & b; + long or_ = a | b; + long xor_ = a ^ b; + long not_ = ~a & 0xFF; + return (int)((and_ + or_ + xor_ + not_) - 477); +} diff --git a/test/asm/roundtrip/ibit_long.expected b/test/asm/roundtrip/ibit_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_long_logic_imm.c b/test/asm/roundtrip/ibit_long_logic_imm.c @@ -0,0 +1,12 @@ +/* 64-bit bitwise ops with an immediate operand on `long`. Exercises the 64-bit + * logical-immediate (bitmask) encoding of and/orr/eor on x-registers. volatile + * on the variable side keeps the op live. + * a = 0x3C; a & 0xF0 = 0x30 (48); a | 0x03 = 0x3F (63) + * a ^ 0xFF = 0xC3 (195); (48 + 63 + 195) - 264 = 42. */ +int test_main(void) { + volatile long a = 0x3C; + long and_ = a & 0xF0; + long or_ = a | 0x03; + long xor_ = a ^ 0xFF; + return (int)((and_ + or_ + xor_) - 264); +} diff --git a/test/asm/roundtrip/ibit_long_logic_imm.expected b/test/asm/roundtrip/ibit_long_logic_imm.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_long_shift_const.c b/test/asm/roundtrip/ibit_long_shift_const.c @@ -0,0 +1,16 @@ +/* 64-bit shifts by a constant amount on `long`/`unsigned long`. Exercises the + * 64-bit immediate shift forms (LSL/LSR/ASR via the x-register UBFM/SBFM). + * volatile defeats folding. + * u = 0x15 (21): u << 1 = 42 (logical left) + * v = 0x150 (336): v >> 3 = 42 (logical right, unsigned) + * s = -336: s >> 3 = -42 (arithmetic right, signed) + * 42 + 42 + (-42) = 42. */ +int test_main(void) { + volatile unsigned long u = 0x15; + volatile unsigned long v = 0x150; + volatile long s = -336; + long lsl = (long)(u << 1); + long lsr = (long)(v >> 3); + long asr = s >> 3; + return (int)(lsl + lsr + asr); +} diff --git a/test/asm/roundtrip/ibit_long_shift_const.expected b/test/asm/roundtrip/ibit_long_shift_const.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_long_shift_var.c b/test/asm/roundtrip/ibit_long_shift_var.c @@ -0,0 +1,17 @@ +/* 64-bit shifts by a variable amount on `long`/`unsigned long`. Exercises the + * 64-bit variable-shift register forms (LSLV/LSRV/ASRV on x-registers). + * volatile on value and count keeps the register form live. + * u = 0x15 (21), sh1 = 1: u << 1 = 42 (logical left) + * v = 0x150 (336), sh3 = 3: v >> 3 = 42 (logical right, unsigned) + * s = -336, sh3 = 3: s >> 3 = -42 (arithmetic right, signed) + * 42 + 42 + (-42) = 42. */ +int test_main(void) { + volatile unsigned long u = 0x15; + volatile unsigned long v = 0x150; + volatile long s = -336; + volatile int sh1 = 1, sh3 = 3; + long lsl = (long)(u << sh1); + long lsr = (long)(v >> sh3); + long asr = s >> sh3; + return (int)(lsl + lsr + asr); +} diff --git a/test/asm/roundtrip/ibit_long_shift_var.expected b/test/asm/roundtrip/ibit_long_shift_var.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_not.c b/test/asm/roundtrip/ibit_not.c @@ -0,0 +1,7 @@ +/* 32-bit bitwise NOT (~). On aarch64 `~x` lowers to `mvn w, w` (ORN with the + * zero register). volatile defeats folding. Branch-free, no relocs. + * x = 0xFFFFFFD5 (-43); ~x = 0x2A = 42. */ +int test_main(void) { + volatile int x = -43; + return ~x; +} diff --git a/test/asm/roundtrip/ibit_not.expected b/test/asm/roundtrip/ibit_not.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_shift_combo.c b/test/asm/roundtrip/ibit_shift_combo.c @@ -0,0 +1,14 @@ +/* Mixed shift+mask idiom: extract a byte field via a logical right shift + * followed by an AND mask, and rebuild via a left shift + OR. Exercises the + * shift forms feeding directly into and/orr (the common bitfield pattern) so + * the operand chaining round-trips. volatile defeats folding. + * x = 0x2A3B; (x >> 8) & 0xFF = 0x2A (42) + * y = 0x10; (y << 2) | 0x02 = 0x42 -> & 0x3F = 0x02 (2) + * 42 + 2 - 2 = 42. */ +int test_main(void) { + volatile unsigned int x = 0x2A3B; + volatile unsigned int y = 0x10; + int hi = (int)((x >> 8) & 0xFF); + int lo = (int)(((y << 2) | 0x02) & 0x3F); + return hi + lo - 2; +} diff --git a/test/asm/roundtrip/ibit_shift_combo.expected b/test/asm/roundtrip/ibit_shift_combo.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_shift_const.c b/test/asm/roundtrip/ibit_shift_const.c @@ -0,0 +1,15 @@ +/* 32-bit shifts by a constant amount. On aarch64 these lower to the immediate + * forms: LSL via UBFM, LSR via UBFM, ASR via SBFM. volatile defeats folding. + * u = 0x15 (21): u << 1 = 42 (logical left) + * v = 0x150 (336): v >> 3 = 42 (logical right, unsigned) + * s = -336: s >> 3 = -42 (arithmetic right, signed) + * 42 + 42 + (-42) = 42. */ +int test_main(void) { + volatile unsigned int u = 0x15; + volatile unsigned int v = 0x150; + volatile int s = -336; + int lsl = (int)(u << 1); + int lsr = (int)(v >> 3); + int asr = s >> 3; + return lsl + lsr + asr; +} diff --git a/test/asm/roundtrip/ibit_shift_const.expected b/test/asm/roundtrip/ibit_shift_const.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/ibit_shift_var.c b/test/asm/roundtrip/ibit_shift_var.c @@ -0,0 +1,18 @@ +/* 32-bit shifts by a variable amount. On aarch64 these lower to the + * variable-shift register forms: LSLV, LSRV, ASRV. volatile on both the value + * and the shift count keeps the register form live (no folding to an immediate + * shift). + * u = 0x15 (21), sh1 = 1: u << 1 = 42 (logical left) + * v = 0x150 (336), sh3 = 3: v >> 3 = 42 (logical right, unsigned) + * s = -336, sh3 = 3: s >> 3 = -42 (arithmetic right, signed) + * 42 + 42 + (-42) = 42. */ +int test_main(void) { + volatile unsigned int u = 0x15; + volatile unsigned int v = 0x150; + volatile int s = -336; + volatile int sh1 = 1, sh3 = 3; + int lsl = (int)(u << sh1); + int lsr = (int)(v >> sh3); + int asr = s >> sh3; + return lsl + lsr + asr; +} diff --git a/test/asm/roundtrip/ibit_shift_var.expected b/test/asm/roundtrip/ibit_shift_var.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_branch_eqne.c b/test/asm/roundtrip/icmp_branch_eqne.c @@ -0,0 +1,16 @@ +/* == and != used as branch conditions. On aarch64 an equality test against a + * value often lowers to cbz/cbnz or cmp + b.eq/b.ne. volatile defeats folding; + * noinline forces real branches. Exit code 42. */ +__attribute__((noinline)) static int pick_eq(int a, int b) { return a == b ? 10 : 1; } +__attribute__((noinline)) static int pick_ne(int a, int b) { return a != b ? 11 : 1; } +__attribute__((noinline)) static int pick_eqz(int a) { return a == 0 ? 10 : 1; } +__attribute__((noinline)) static int pick_nez(int a) { return a != 0 ? 11 : 1; } +int test_main(void) { + volatile int a = 5, b = 5, z = 0, nz = 9; + int r = 0; + r += pick_eq(a, b); /* 5==5 -> 10 */ + r += pick_ne(a, nz); /* 5!=9 -> 11 */ + r += pick_eqz(z); /* 0==0 -> 10 */ + r += pick_nez(nz); /* 9!=0 -> 11 */ + return r; /* 42 */ +} diff --git a/test/asm/roundtrip/icmp_branch_eqne.expected b/test/asm/roundtrip/icmp_branch_eqne.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_branch_signed_int.c b/test/asm/roundtrip/icmp_branch_signed_int.c @@ -0,0 +1,18 @@ +/* Signed 32-bit comparisons used as if/ternary branch conditions. Each compare + * feeds a conditional branch (cmp + b.cond / cbz), exercising branch-label + * synthesis in the symbolizer (and same-section branch relaxation in `as`). + * volatile operands defeat folding. noinline keeps each compare in its own call + * so the branch is real. Exit code totals 42. */ +__attribute__((noinline)) static int pick_lt(int a, int b) { return a < b ? 10 : 1; } +__attribute__((noinline)) static int pick_le(int a, int b) { return a <= b ? 10 : 1; } +__attribute__((noinline)) static int pick_gt(int a, int b) { return a > b ? 1 : 11; } +__attribute__((noinline)) static int pick_ge(int a, int b) { return a >= b ? 1 : 11; } +int test_main(void) { + volatile int a = -3, b = 7; + int r = 0; + r += pick_lt(a, b); /* -3 < 7 -> 10 */ + r += pick_le(a, b); /* -3 <= 7 -> 10 */ + r += pick_gt(a, b); /* -3 > 7 false -> 11 */ + r += pick_ge(a, b); /* -3 >= 7 false -> 11 */ + return r; /* 42 */ +} diff --git a/test/asm/roundtrip/icmp_branch_signed_int.expected b/test/asm/roundtrip/icmp_branch_signed_int.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_branch_signed_long.c b/test/asm/roundtrip/icmp_branch_signed_long.c @@ -0,0 +1,16 @@ +/* Signed 64-bit comparisons used as branch conditions (cmp x,x + b.lt/le/gt/ge). + * Operands straddle the 32-bit range so the x-register compare is required. + * volatile defeats folding; noinline forces real branches. Exit code 42. */ +__attribute__((noinline)) static int pick_lt(long a, long b) { return a < b ? 10 : 1; } +__attribute__((noinline)) static int pick_le(long a, long b) { return a <= b ? 10 : 1; } +__attribute__((noinline)) static int pick_gt(long a, long b) { return a > b ? 1 : 11; } +__attribute__((noinline)) static int pick_ge(long a, long b) { return a >= b ? 1 : 11; } +int test_main(void) { + volatile long a = -5000000000L, b = 5000000000L; + int r = 0; + r += pick_lt(a, b); /* 10 */ + r += pick_le(a, b); /* 10 */ + r += pick_gt(a, b); /* 11 */ + r += pick_ge(a, b); /* 11 */ + return r; /* 42 */ +} diff --git a/test/asm/roundtrip/icmp_branch_signed_long.expected b/test/asm/roundtrip/icmp_branch_signed_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_branch_unsigned_int.c b/test/asm/roundtrip/icmp_branch_unsigned_int.c @@ -0,0 +1,17 @@ +/* Unsigned 32-bit comparisons used as branch conditions (cmp + b.lo/b.ls/ + * b.hi/b.hs). The large operand has the top bit set so signed vs unsigned + * branches disagree, pinning the unsigned condition codes. volatile defeats + * folding; noinline forces real branches. Exit code totals 42. */ +__attribute__((noinline)) static int pick_lt(unsigned a, unsigned b) { return a < b ? 10 : 1; } +__attribute__((noinline)) static int pick_le(unsigned a, unsigned b) { return a <= b ? 10 : 1; } +__attribute__((noinline)) static int pick_gt(unsigned a, unsigned b) { return a > b ? 1 : 11; } +__attribute__((noinline)) static int pick_ge(unsigned a, unsigned b) { return a >= b ? 1 : 11; } +int test_main(void) { + volatile unsigned int a = 3u, b = 0xfffffff0u; + int r = 0; + r += pick_lt(a, b); /* 3 < big -> 10 */ + r += pick_le(a, b); /* 3 <= big -> 10 */ + r += pick_gt(a, b); /* 3 > big false -> 11 */ + r += pick_ge(a, b); /* 3 >= big false -> 11 */ + return r; /* 42 */ +} diff --git a/test/asm/roundtrip/icmp_branch_unsigned_int.expected b/test/asm/roundtrip/icmp_branch_unsigned_int.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_branch_unsigned_long.c b/test/asm/roundtrip/icmp_branch_unsigned_long.c @@ -0,0 +1,17 @@ +/* Unsigned 64-bit comparisons used as branch conditions (cmp x,x + b.lo/ls/ + * hi/hs). The large operand has the top bit set so signed vs unsigned branches + * disagree, pinning the unsigned condition codes. volatile defeats folding; + * noinline forces real branches. Exit code 42. */ +__attribute__((noinline)) static int pick_lt(unsigned long a, unsigned long b) { return a < b ? 10 : 1; } +__attribute__((noinline)) static int pick_le(unsigned long a, unsigned long b) { return a <= b ? 10 : 1; } +__attribute__((noinline)) static int pick_gt(unsigned long a, unsigned long b) { return a > b ? 1 : 11; } +__attribute__((noinline)) static int pick_ge(unsigned long a, unsigned long b) { return a >= b ? 1 : 11; } +int test_main(void) { + volatile unsigned long a = 7ul, b = 0xfffffffffffffff0ul; + int r = 0; + r += pick_lt(a, b); /* 10 */ + r += pick_le(a, b); /* 10 */ + r += pick_gt(a, b); /* 11 */ + r += pick_ge(a, b); /* 11 */ + return r; /* 42 */ +} diff --git a/test/asm/roundtrip/icmp_branch_unsigned_long.expected b/test/asm/roundtrip/icmp_branch_unsigned_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_cmp_imm.c b/test/asm/roundtrip/icmp_cmp_imm.c @@ -0,0 +1,11 @@ +/* Comparison against a small immediate, which lowers to `cmp w, #imm` (the + * immediate form of subs) rather than a register compare. Covers the immediate + * encoding of the compare in the disasm/encode round-trip. volatile defeats + * folding. Exit code: a=10: (a<100)=1, (a>=5)=1, (a==10)=1 -> 1*20+1*20+1*2 = 42. */ +int test_main(void) { + volatile int a = 10; + int lt = (a < 100); /* 1, cmp w,#100 + cset lt */ + int ge = (a >= 5); /* 1, cmp w,#5 + cset ge */ + int eq = (a == 10); /* 1, cmp w,#10 + cset eq */ + return lt * 20 + ge * 20 + eq * 2; /* 42 */ +} diff --git a/test/asm/roundtrip/icmp_cmp_imm.expected b/test/asm/roundtrip/icmp_cmp_imm.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_csel_int.c b/test/asm/roundtrip/icmp_csel_int.c @@ -0,0 +1,20 @@ +/* Several boolean comparison results summed directly into an int, producing a + * chain of back-to-back `cmp + cset` (csinc) sequences with no intervening + * branch. Exercises the disasm/encode round-trip of consecutive csinc forms. + * volatile operands defeat folding. The aarch64 backend lowers a C `?:` to a + * branch (covered by ctl_ternary / icmp_branch_*); this case instead pins the + * branchless boolean-materialization path. + * Exit code: a=4,b=9: + * (a<b)=1 (a<=b)=1 (a!=b)=1 (a>b)=0 (a>=b)=0 (a==b)=0 + * -> sum=3; 3*13 + 3 = 42. */ +int test_main(void) { + volatile int a = 4, b = 9; + int s = 0; + s += (a < b); /* 1 */ + s += (a <= b); /* 1 */ + s += (a != b); /* 1 */ + s += (a > b); /* 0 */ + s += (a >= b); /* 0 */ + s += (a == b); /* 0 */ + return s * 13 + 3; /* 3*13 + 3 = 42 */ +} diff --git a/test/asm/roundtrip/icmp_csel_int.expected b/test/asm/roundtrip/icmp_csel_int.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_cset_eq_int.c b/test/asm/roundtrip/icmp_cset_eq_int.c @@ -0,0 +1,10 @@ +/* icmp == on 32-bit ints materialized to a boolean (cmp + cset eq). volatile + * operands defeat constant folding so the compare and cset are really emitted. + * Exercises the disasm/encode round-trip of `cmp w,w` + `cset w, eq`. + * Exit code: (5==5)*40 + (5==6)*100 + 2 = 40 + 0 + 2 = 42. */ +int test_main(void) { + volatile int a = 5, b = 5, c = 6; + int eq_true = (a == b); /* 1 */ + int eq_false = (a == c); /* 0 */ + return eq_true * 40 + eq_false * 100 + 2; +} diff --git a/test/asm/roundtrip/icmp_cset_eq_int.expected b/test/asm/roundtrip/icmp_cset_eq_int.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_cset_eqne_long.c b/test/asm/roundtrip/icmp_cset_eqne_long.c @@ -0,0 +1,11 @@ +/* icmp == and != on 64-bit longs materialized to booleans (cmp x,x + cset + * eq/ne). Values exceed the 32-bit range so the x-register compare is used. + * volatile defeats folding. + * Exit code: a=b=0x100000001, c differs: eq=1 ne=1 + * 1*20 + 1*20 + 2 = 42. */ +int test_main(void) { + volatile long a = 0x100000001L, b = 0x100000001L, c = 0x200000002L; + int eq = (a == b); /* 1 */ + int ne = (a != c); /* 1 */ + return eq * 20 + ne * 20 + 2; +} diff --git a/test/asm/roundtrip/icmp_cset_eqne_long.expected b/test/asm/roundtrip/icmp_cset_eqne_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_cset_ne_int.c b/test/asm/roundtrip/icmp_cset_ne_int.c @@ -0,0 +1,9 @@ +/* icmp != on 32-bit ints materialized to a boolean (cmp + cset ne). volatile + * operands defeat constant folding. Exercises `cset w, ne`. + * Exit code: (5!=6)*40 + (5!=5)*100 + 2 = 40 + 0 + 2 = 42. */ +int test_main(void) { + volatile int a = 5, b = 5, c = 6; + int ne_true = (a != c); /* 1 */ + int ne_false = (a != b); /* 0 */ + return ne_true * 40 + ne_false * 100 + 2; +} diff --git a/test/asm/roundtrip/icmp_cset_ne_int.expected b/test/asm/roundtrip/icmp_cset_ne_int.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_cset_signed_int.c b/test/asm/roundtrip/icmp_cset_signed_int.c @@ -0,0 +1,14 @@ +/* Signed 32-bit ordering comparisons materialized to booleans: + * < -> cset w, lt <= -> cset w, le + * > -> cset w, gt >= -> cset w, ge + * volatile operands (one negative) defeat folding and force signed compares. + * Exit code: with a=-3, b=7: lt=1 le=1 gt=0 ge=0 + * 1*10 + 1*10 + 0*100 + 0*100 + 22 = 42. */ +int test_main(void) { + volatile int a = -3, b = 7; + int lt = (a < b); /* 1 */ + int le = (a <= b); /* 1 */ + int gt = (a > b); /* 0 */ + int ge = (a >= b); /* 0 */ + return lt * 10 + le * 10 + gt * 100 + ge * 100 + 22; +} diff --git a/test/asm/roundtrip/icmp_cset_signed_int.expected b/test/asm/roundtrip/icmp_cset_signed_int.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_cset_signed_long.c b/test/asm/roundtrip/icmp_cset_signed_long.c @@ -0,0 +1,15 @@ +/* Signed 64-bit ordering comparisons materialized to booleans: + * < -> cmp x,x + cset w, lt <= -> le + * > -> gt >= -> ge + * Values straddle the 32-bit range so the 64-bit (x-register) compare is + * required. volatile defeats folding. + * Exit code: a=-5000000000, b=5000000000: lt=1 le=1 gt=0 ge=0 + * 1*10 + 1*10 + 0*100 + 0*100 + 22 = 42. */ +int test_main(void) { + volatile long a = -5000000000L, b = 5000000000L; + int lt = (a < b); /* 1 */ + int le = (a <= b); /* 1 */ + int gt = (a > b); /* 0 */ + int ge = (a >= b); /* 0 */ + return lt * 10 + le * 10 + gt * 100 + ge * 100 + 22; +} diff --git a/test/asm/roundtrip/icmp_cset_signed_long.expected b/test/asm/roundtrip/icmp_cset_signed_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_cset_unsigned_int.c b/test/asm/roundtrip/icmp_cset_unsigned_int.c @@ -0,0 +1,16 @@ +/* Unsigned 32-bit ordering comparisons materialized to booleans: + * < -> cset w, lo (cc) <= -> cset w, ls + * > -> cset w, hi >= -> cset w, hs (cs) + * volatile operands defeat folding and force unsigned compares. A large value + * (0xfffffff0) would be "less" if treated as signed, ensuring the unsigned + * condition codes are what's emitted. + * Exit code: with a=3, b=0xfffffff0u: lt=1 le=1 gt=0 ge=0 + * 1*10 + 1*10 + 0*100 + 0*100 + 22 = 42. */ +int test_main(void) { + volatile unsigned int a = 3u, b = 0xfffffff0u; + int lt = (a < b); /* 1 (unsigned) */ + int le = (a <= b); /* 1 */ + int gt = (a > b); /* 0 */ + int ge = (a >= b); /* 0 */ + return lt * 10 + le * 10 + gt * 100 + ge * 100 + 22; +} diff --git a/test/asm/roundtrip/icmp_cset_unsigned_int.expected b/test/asm/roundtrip/icmp_cset_unsigned_int.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/icmp_cset_unsigned_long.c b/test/asm/roundtrip/icmp_cset_unsigned_long.c @@ -0,0 +1,15 @@ +/* Unsigned 64-bit ordering comparisons materialized to booleans: + * < -> cmp x,x + cset w, lo (cc) <= -> ls + * > -> hi >= -> hs (cs) + * The large operand has the top bit set so signed vs unsigned compares disagree; + * this pins the unsigned condition codes. volatile defeats folding. + * Exit code: a=7, b=0xfffffffffffffff0: lt=1 le=1 gt=0 ge=0 + * 1*10 + 1*10 + 0*100 + 0*100 + 22 = 42. */ +int test_main(void) { + volatile unsigned long a = 7ul, b = 0xfffffffffffffff0ul; + int lt = (a < b); /* 1 (unsigned) */ + int le = (a <= b); /* 1 */ + int gt = (a > b); /* 0 */ + int ge = (a >= b); /* 0 */ + return lt * 10 + le * 10 + gt * 100 + ge * 100 + 22; +} diff --git a/test/asm/roundtrip/icmp_cset_unsigned_long.expected b/test/asm/roundtrip/icmp_cset_unsigned_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_chain.c b/test/asm/roundtrip/iunop_chain.c @@ -0,0 +1,10 @@ +/* Composed unary ops: negate then complement then negate, all 32-bit, to + * exercise back-to-back neg/mvn with the value flowing through volatile loads. + * a = 41; -a = -41; ~(-41) = 40; -(40) = -40; then 82 + (-40) = 42. */ +int test_main(void) { + volatile int a = 41; + int x = -a; /* -41 */ + int y = ~x; /* 40 */ + int z = -y; /* -40 */ + return 82 + z; /* 42 */ +} diff --git a/test/asm/roundtrip/iunop_chain.expected b/test/asm/roundtrip/iunop_chain.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_lognot.c b/test/asm/roundtrip/iunop_lognot.c @@ -0,0 +1,7 @@ +/* Logical not at 32-bit (int): !x is `x == 0`, emits cmp + cset (no branch on + * aarch64). volatile defeats folding. !0 == 1, and we scale to 42. */ +int test_main(void) { + volatile int a = 0; + int n = !a; /* 1 */ + return n * 42; /* 42 */ +} diff --git a/test/asm/roundtrip/iunop_lognot.expected b/test/asm/roundtrip/iunop_lognot.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_lognot64.c b/test/asm/roundtrip/iunop_lognot64.c @@ -0,0 +1,7 @@ +/* Logical not at 64-bit (long): !x is `x == 0` on a 64-bit operand, emits a + * 64-bit cmp + cset. volatile defeats folding. !0L == 1, scaled to 42. */ +int test_main(void) { + volatile long a = 0; + long n = !a; /* 1 */ + return (int)(n * 42); /* 42 */ +} diff --git a/test/asm/roundtrip/iunop_lognot64.expected b/test/asm/roundtrip/iunop_lognot64.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_lognot_nonzero.c b/test/asm/roundtrip/iunop_lognot_nonzero.c @@ -0,0 +1,8 @@ +/* Logical not at 32-bit, nonzero operand: !x with x != 0 yields 0. + * Exercises the cmp/cset path with the false outcome. volatile defeats folding. + * !7 == 0; 42 + 0 = 42. */ +int test_main(void) { + volatile int a = 7; + int n = !a; /* 0 */ + return 42 + n; /* 42 */ +} diff --git a/test/asm/roundtrip/iunop_lognot_nonzero.expected b/test/asm/roundtrip/iunop_lognot_nonzero.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_lognot_unsigned.c b/test/asm/roundtrip/iunop_lognot_unsigned.c @@ -0,0 +1,7 @@ +/* Logical not at 32-bit unsigned: !x on an unsigned operand. cmp + cset. + * volatile defeats folding. !0u == 1, scaled to 42. */ +int test_main(void) { + volatile unsigned a = 0u; + int n = !a; /* 1 */ + return n * 42; /* 42 */ +} diff --git a/test/asm/roundtrip/iunop_lognot_unsigned.expected b/test/asm/roundtrip/iunop_lognot_unsigned.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_neg.c b/test/asm/roundtrip/iunop_neg.c @@ -0,0 +1,8 @@ +/* Arithmetic negate at 32-bit (int): -x emits `neg w` (sub from wzr). + * volatile defeats folding so the negate is really emitted. + * Exit code: -(-42) = 42. */ +int test_main(void) { + volatile int a = -42; + int n = -a; + return n; +} diff --git a/test/asm/roundtrip/iunop_neg.expected b/test/asm/roundtrip/iunop_neg.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_neg64.c b/test/asm/roundtrip/iunop_neg64.c @@ -0,0 +1,7 @@ +/* Arithmetic negate at 64-bit (long): -x emits `neg x` (sub from xzr). + * volatile defeats folding. Exit code: -(-42) = 42. */ +int test_main(void) { + volatile long a = -42; + long n = -a; + return (int)n; +} diff --git a/test/asm/roundtrip/iunop_neg64.expected b/test/asm/roundtrip/iunop_neg64.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_neg_unsigned.c b/test/asm/roundtrip/iunop_neg_unsigned.c @@ -0,0 +1,8 @@ +/* Arithmetic negate at 32-bit unsigned: -(unsigned)x is well-defined modular + * wraparound (no UB). -x emits `neg w`. volatile defeats folding. + * 0u - 0xFFFFFFD6 = 0x2A = 42 (two's complement of -42). */ +int test_main(void) { + volatile unsigned a = 0xFFFFFFD6u; /* == (unsigned)-42 */ + unsigned n = -a; + return (int)n; +} diff --git a/test/asm/roundtrip/iunop_neg_unsigned.expected b/test/asm/roundtrip/iunop_neg_unsigned.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_not.c b/test/asm/roundtrip/iunop_not.c @@ -0,0 +1,7 @@ +/* Bitwise complement at 32-bit (int): ~x emits `mvn w` (orn from wzr). + * volatile defeats folding. ~(-43) = 42. */ +int test_main(void) { + volatile int a = -43; + int n = ~a; + return n; +} diff --git a/test/asm/roundtrip/iunop_not.expected b/test/asm/roundtrip/iunop_not.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_not64.c b/test/asm/roundtrip/iunop_not64.c @@ -0,0 +1,7 @@ +/* Bitwise complement at 64-bit (long): ~x emits `mvn x` (orn from xzr). + * volatile defeats folding. ~(-43) = 42. */ +int test_main(void) { + volatile long a = -43; + long n = ~a; + return (int)n; +} diff --git a/test/asm/roundtrip/iunop_not64.expected b/test/asm/roundtrip/iunop_not64.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/iunop_not_unsigned.c b/test/asm/roundtrip/iunop_not_unsigned.c @@ -0,0 +1,7 @@ +/* Bitwise complement at 32-bit unsigned: ~x emits `mvn w`. Well-defined. + * volatile defeats folding. ~0xFFFFFFD5u = 0x2A = 42. */ +int test_main(void) { + volatile unsigned a = 0xFFFFFFD5u; + unsigned n = ~a; + return (int)n; +} diff --git a/test/asm/roundtrip/iunop_not_unsigned.expected b/test/asm/roundtrip/iunop_not_unsigned.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_array.c b/test/asm/roundtrip/mem_array.c @@ -0,0 +1,13 @@ +/* mem: a small local int array summed in a counted loop. The element accesses + * lower to scaled-register loads (`ldr w, [base, x, lsl #2]`) or post/pre + * computed addresses; the loop also exercises the back-edge branch. A volatile + * bound defeats unrolling/folding so the loads survive. Exit: 1+2+...+8 = 36, + * +6 = 42. */ +int test_main(void) { + int a[8]; + for (int i = 0; i < 8; ++i) a[i] = i + 1; + volatile int n = 8; + int sum = 0; + for (int i = 0; i < n; ++i) sum += a[i]; + return sum + 6; /* 36 + 6 */ +} diff --git a/test/asm/roundtrip/mem_array.expected b/test/asm/roundtrip/mem_array.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_global_array.c b/test/asm/roundtrip/mem_global_array.c @@ -0,0 +1,9 @@ +/* mem: read from a file-scope (global) array. Codegen materializes the array + * base with adrp/add (ADR_PREL_PG_HI21 + ADD_ABS_LO12_NC against `tab`) then + * indexes it; the loads against an initialized .data/.rodata array exercise + * symbolic addressing plus a scaled load. Exit: tab[3] + tab[5] = 20 + 22 = 42. */ +static int tab[8] = { 10, 12, 14, 20, 18, 22, 24, 26 }; +int test_main(void) { + volatile int i = 3, j = 5; + return tab[i] + tab[j]; +} diff --git a/test/asm/roundtrip/mem_global_array.expected b/test/asm/roundtrip/mem_global_array.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_long_array.c b/test/asm/roundtrip/mem_long_array.c @@ -0,0 +1,12 @@ +/* mem: a local 64-bit (long) array summed in a loop. Element accesses use the + * 8-byte scaled forms (`ldr x, [base, x, lsl #3]` / `str x`). Values exceed 32 + * bits so the 64-bit width is required. Exit: sum -> 42. */ +int test_main(void) { + long a[6]; + for (int i = 0; i < 6; ++i) a[i] = (long)(i + 1) << 32; /* big values */ + volatile int n = 6; + long sum = 0; + for (int i = 0; i < n; ++i) sum += a[i]; /* (1+..+6)<<32 = 21<<32 */ + long top = sum >> 32; /* 21 */ + return (int)(top + 21); /* 42 */ +} diff --git a/test/asm/roundtrip/mem_long_array.expected b/test/asm/roundtrip/mem_long_array.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_ptr_widths.c b/test/asm/roundtrip/mem_ptr_widths.c @@ -0,0 +1,18 @@ +/* mem: stores and loads through explicit pointers at each width, using a + * noinline helper so the pointer is opaque and the compiler cannot fold the + * round-trip. Exercises strb/ldrb, strh/ldrh, str w/ldr w, str x/ldr x via + * base-register addressing. + * 8:5 16:6 32:9 64:22 -> 42 */ +__attribute__((noinline)) static void put8 (unsigned char *p, unsigned char v) { *p = v; } +__attribute__((noinline)) static void put16(unsigned short *p, unsigned short v) { *p = v; } +__attribute__((noinline)) static void put32(unsigned int *p, unsigned int v) { *p = v; } +__attribute__((noinline)) static void put64(unsigned long *p, unsigned long v) { *p = v; } +int test_main(void) { + unsigned char c; unsigned short s; unsigned int i; unsigned long l; + put8 (&c, 5); + put16(&s, 6); + put32(&i, 9); + put64(&l, 22); + unsigned long sum = (unsigned long)c + s + i + l; + return (int)sum; +} diff --git a/test/asm/roundtrip/mem_ptr_widths.expected b/test/asm/roundtrip/mem_ptr_widths.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_signed.c b/test/asm/roundtrip/mem_signed.c @@ -0,0 +1,16 @@ +/* mem: signed narrow values stored to and reloaded from volatile stack slots, + * then sign-extended into a 64-bit chain. The narrow stores use the unscaled + * forms `sturb`/`sturh`/`stur w`; the reloads use `ldurb`/`ldurh`/`ldur` and + * the cast to long sign-extends the loaded value in-register. The negative + * magnitudes must survive sign extension; a lost sign would change the exit. + * c = -3 (sturb/ldurb) + * s = -7 (sturh/ldurh) + * i = -8 (stur w/ldur w, widened to long) + * sum = -3 + -7 + -8 = -18 ; 60 + (-18) = 42 */ +int test_main(void) { + volatile signed char c = -3; + volatile short s = -7; + volatile int i = -8; + long sum = (long)c + (long)s + (long)i; /* -18 */ + return (int)(sum + 60); +} diff --git a/test/asm/roundtrip/mem_signed.expected b/test/asm/roundtrip/mem_signed.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_signed_long.c b/test/asm/roundtrip/mem_signed_long.c @@ -0,0 +1,12 @@ +/* mem: a signed 32-bit value stored to / reloaded from a volatile stack slot + * (stur w/ldur w) then sign-extended into a 64-bit chain, with a negative + * magnitude large enough that a wrong (zero-extending) widening would change + * the result. Distinct from mem_signed in that the value is consumed as a long + * throughout (sxtw / 64-bit arithmetic). Exit: 42. */ +int test_main(void) { + volatile int neg = -1000000; /* 0xFFF0BDC0 as bits */ + long widened = neg; /* ldrsw: must stay negative */ + long doubled = widened + widened; /* -2000000 */ + long back = doubled / -1000000; /* 2 ; no signed overflow */ + return (int)(back * 21); /* 42 */ +} diff --git a/test/asm/roundtrip/mem_signed_long.expected b/test/asm/roundtrip/mem_signed_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_store_narrow.c b/test/asm/roundtrip/mem_store_narrow.c @@ -0,0 +1,14 @@ +/* mem: store narrow values into an array of bytes and shorts, then read them + * back. Forces strb/strh (the store side of the narrow forms) distinct from the + * load-side cases, plus ldrb/ldrh on read-back. Exit: 42. */ +int test_main(void) { + unsigned char bytes[4]; + unsigned short halves[3]; + bytes[0] = 1; bytes[1] = 2; bytes[2] = 3; bytes[3] = 4; /* strb: sum 10 */ + halves[0] = 100; halves[1] = 200; halves[2] = 300; /* strh */ + volatile int k = 1; + unsigned int bsum = (unsigned int)bytes[0] + bytes[1] + bytes[2] + bytes[3]; + unsigned int hsel = halves[k]; /* ldrh -> 200 */ + /* bsum=10, hsel=200; 10 + (200 - 168) = 42 */ + return (int)(bsum + (hsel - 168)); +} diff --git a/test/asm/roundtrip/mem_store_narrow.expected b/test/asm/roundtrip/mem_store_narrow.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_struct.c b/test/asm/roundtrip/mem_struct.c @@ -0,0 +1,21 @@ +/* mem: loads/stores to struct fields at mixed widths and offsets. A volatile + * struct defeats SROA so the fields are really spilled to and reloaded from + * memory at their byte offsets, exercising the narrow + wide load/store forms + * with base+offset addressing (sturb/ldurb, sturh/ldurh, stur/ldur w and x; + * plus scaled ldrh `[base, #2]` when a field is addressed through a pointer). + * b = 1, h = 2, w = 9, q = 30 -> 1+2+9+30 = 42 */ +struct S { + unsigned char b; + unsigned short h; + unsigned int w; + unsigned long q; +}; +int test_main(void) { + volatile struct S s; + s.b = 1; + s.h = 2; + s.w = 9; + s.q = 30; + unsigned long sum = (unsigned long)s.b + s.h + s.w + s.q; + return (int)sum; +} diff --git a/test/asm/roundtrip/mem_struct.expected b/test/asm/roundtrip/mem_struct.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_u32_zext.c b/test/asm/roundtrip/mem_u32_zext.c @@ -0,0 +1,11 @@ +/* mem: an unsigned 32-bit load that feeds a 64-bit computation. `ldr w` + * zero-extends the upper 32 bits implicitly, so no separate uxtw is needed; the + * round-trip must keep the 32-bit (w) form rather than promoting to `ldr x`. + * The stored value has its top bit set to make the zero-extension observable. + * v = 0x8000000B (2147483659u); v & 0xFF = 0x0B = 11 ; 11 + 31 = 42 */ +int test_main(void) { + volatile unsigned int v = 0x8000000Bu; + unsigned long w = v; /* ldr w zero-extends */ + unsigned long low = w & 0xFFu; /* 11 */ + return (int)(low + 31); +} diff --git a/test/asm/roundtrip/mem_u32_zext.expected b/test/asm/roundtrip/mem_u32_zext.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_volatile.c b/test/asm/roundtrip/mem_volatile.c @@ -0,0 +1,13 @@ +/* mem: repeated volatile accesses. Each read/write of a volatile object must + * produce a real load/store (no elision, no forwarding), so the same 32-bit + * str/ldr pair is emitted several times. Round-trip must preserve every one. + * Exit: 42. */ +int test_main(void) { + volatile int v = 0; + v = 10; /* str w */ + int a = v; /* ldr w */ + v = a + 11; /* ldr-add-str: 21 */ + int b = v; /* ldr w -> 21 */ + v = b + v; /* two loads of v + store: 42 */ + return v; /* ldr w -> 42 */ +} diff --git a/test/asm/roundtrip/mem_volatile.expected b/test/asm/roundtrip/mem_volatile.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/mem_widths.c b/test/asm/roundtrip/mem_widths.c @@ -0,0 +1,17 @@ +/* mem: stores then loads at all four integer widths through a pointer, forcing + * the 8/16/32/64-bit store + load forms. + * + * Volatile-qualified storage defeats store-to-load forwarding so each width + * genuinely emits its own store and load. Stack slots use the unscaled + * forms: sturb/ldurb (8-bit), sturh/ldurh (16-bit), stur w/ldur w (32-bit), + * stur x/ldur x (64-bit). Each width's distinct opcode must round-trip. + * Values are chosen so the sum is 42. + * c = 5, s = 6, i = 9, l = 22 -> 5+6+9+22 = 42 */ +int test_main(void) { + volatile unsigned char c = 5; + volatile unsigned short s = 6; + volatile unsigned int i = 9; + volatile unsigned long l = 22; + unsigned long sum = (unsigned long)c + s + i + l; + return (int)sum; +} diff --git a/test/asm/roundtrip/mem_widths.expected b/test/asm/roundtrip/mem_widths.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/sw_char.c b/test/asm/roundtrip/sw_char.c @@ -0,0 +1,17 @@ +/* A switch on a char-typed selector. The selector is narrowed to 8 bits, so + * the dispatch compares byte values; this exercises the sub-register compare + * path feeding the switch. volatile char defeats folding. The character 'B' + * (0x42) selects the case returning 42. Exit code 42. */ +int sel(char c) { + switch (c) { + case 'A': return 10; + case 'B': return 42; + case 'C': return 12; + case 'D': return 13; + default: return 99; + } +} +int test_main(void) { + volatile char c = 'B'; + return sel(c); +} diff --git a/test/asm/roundtrip/sw_char.expected b/test/asm/roundtrip/sw_char.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/sw_default.c b/test/asm/roundtrip/sw_default.c @@ -0,0 +1,17 @@ +/* A switch whose DEFAULT arm is taken: the selector matches none of the listed + * cases, so the dispatch must fall through every comparison to the default + * label. volatile selector defeats folding. Selector 77 matches nothing, so + * the default returns 42. Exit code 42. */ +int sel(int x) { + switch (x) { + case 0: return 10; + case 1: return 11; + case 2: return 12; + case 3: return 13; + default: return 42; + } +} +int test_main(void) { + volatile int x = 77; + return sel(x); +} diff --git a/test/asm/roundtrip/sw_default.expected b/test/asm/roundtrip/sw_default.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/sw_dense_signed.c b/test/asm/roundtrip/sw_dense_signed.c @@ -0,0 +1,21 @@ +/* A dense switch whose case values are contiguous but include negatives, so the + * backend must bias the selector before indexing (or build a compare chain). + * This complements the existing 0-based dense jumptable case by starting below + * zero. volatile selector defeats folding. Selector -1 selects the case + * returning 42. Exit code 42. */ +int sel(int x) { + switch (x) { + case -3: return 10; + case -2: return 11; + case -1: return 42; + case 0: return 13; + case 1: return 14; + case 2: return 15; + case 3: return 16; + default: return 99; + } +} +int test_main(void) { + volatile int x = -1; + return sel(x); +} diff --git a/test/asm/roundtrip/sw_dense_signed.expected b/test/asm/roundtrip/sw_dense_signed.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/sw_enum.c b/test/asm/roundtrip/sw_enum.c @@ -0,0 +1,17 @@ +/* A switch on an enum-typed selector. enum constants lower to int comparisons; + * this checks the enum-as-selector path round-trips like a plain int switch. + * volatile defeats folding. The selector COLOR_GREEN selects the case + * returning 42. Exit code 42. */ +enum color { COLOR_RED, COLOR_GREEN, COLOR_BLUE }; +int sel(enum color c) { + switch (c) { + case COLOR_RED: return 10; + case COLOR_GREEN: return 42; + case COLOR_BLUE: return 12; + default: return 99; + } +} +int test_main(void) { + volatile int raw = COLOR_GREEN; + return sel((enum color)raw); +} diff --git a/test/asm/roundtrip/sw_enum.expected b/test/asm/roundtrip/sw_enum.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/sw_fallthrough.c b/test/asm/roundtrip/sw_fallthrough.c @@ -0,0 +1,19 @@ +/* A switch with deliberate fall-through: cases without a break flow into the + * next case body, so the lowering must NOT insert a branch at the boundary. + * volatile selector and accumulator defeat folding so the additions + the + * dispatch are really emitted. Selector 2 falls through 2 -> 1 -> 0, adding + * 20 + 12 + 10 = 42. Exit code 42. */ +int sel(int x) { + volatile int acc = 0; + switch (x) { + case 2: acc += 20; /* fall through */ + case 1: acc += 12; /* fall through */ + case 0: acc += 10; break; + default: acc = 99; break; + } + return acc; +} +int test_main(void) { + volatile int x = 2; + return sel(x); +} diff --git a/test/asm/roundtrip/sw_fallthrough.expected b/test/asm/roundtrip/sw_fallthrough.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/sw_long.c b/test/asm/roundtrip/sw_long.c @@ -0,0 +1,16 @@ +/* A switch on a 64-bit (long) selector with far-apart case values, forcing a + * compare-and-branch chain over 64-bit registers. This exercises the x-register + * compare path feeding the dispatch. volatile selector defeats folding. The + * value 0x100000000 (2^32) selects the case returning 42. Exit code 42. */ +int sel(long x) { + switch (x) { + case 0L: return 10; + case 0x100000000L: return 42; + case 0x200000000L: return 12; + default: return 99; + } +} +int test_main(void) { + volatile long x = 0x100000000L; + return sel(x); +} diff --git a/test/asm/roundtrip/sw_long.expected b/test/asm/roundtrip/sw_long.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/sw_nested.c b/test/asm/roundtrip/sw_nested.c @@ -0,0 +1,23 @@ +/* A switch nested inside another switch. Each level dispatches independently; + * this checks that multiple dispatch sequences and their branch labels coexist + * and round-trip. volatile selectors defeat folding. Outer 1 -> inner 1 selects + * the arm returning 42. Exit code 42. */ +int sel(int a, int b) { + switch (a) { + case 0: + return 10; + case 1: + switch (b) { + case 0: return 20; + case 1: return 42; + default: return 21; + } + default: + return 99; + } +} +int test_main(void) { + volatile int a = 1; + volatile int b = 1; + return sel(a, b); +} diff --git a/test/asm/roundtrip/sw_nested.expected b/test/asm/roundtrip/sw_nested.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/sw_sparse.c b/test/asm/roundtrip/sw_sparse.c @@ -0,0 +1,18 @@ +/* A SPARSE switch: case values are few and far apart, so the backend lowers it + * to a compare-and-branch chain (subs/cmp + b.eq) rather than a jump table. + * volatile selector defeats constant folding; each comparison + conditional + * branch is really emitted and the branch labels round-trip through `as`. + * Selector 1000 hits the case returning 42. Exit code 42. */ +int sel(int x) { + switch (x) { + case 0: return 1; + case 100: return 2; + case 1000: return 42; + case 9999: return 3; + default: return 99; + } +} +int test_main(void) { + volatile int x = 1000; + return sel(x); +} diff --git a/test/asm/roundtrip/sw_sparse.expected b/test/asm/roundtrip/sw_sparse.expected @@ -0,0 +1 @@ +42 diff --git a/test/asm/roundtrip/sw_unsigned.c b/test/asm/roundtrip/sw_unsigned.c @@ -0,0 +1,16 @@ +/* A switch on an unsigned selector with a large case value above INT_MAX, so + * the dispatch must compare without sign extension. This exercises the unsigned + * compare path feeding the switch. volatile selector defeats folding. The + * value 0x80000001u selects the case returning 42. Exit code 42. */ +int sel(unsigned x) { + switch (x) { + case 0u: return 10; + case 0x80000001u: return 42; + case 0xFFFFFFFFu: return 12; + default: return 99; + } +} +int test_main(void) { + volatile unsigned x = 0x80000001u; + return sel(x); +} diff --git a/test/asm/roundtrip/sw_unsigned.expected b/test/asm/roundtrip/sw_unsigned.expected @@ -0,0 +1 @@ +42