// |jit-test| skip-if: !hasDisassembler() || wasmCompileMode() != "ion" || !getBuildConfiguration().arm64; include:codegen-arm64-test.js

// Basic constant folding tests

for ( [op, lhs, rhs, expect] of
      [['add',   5,    8,    'd28001a0  mov     x0, #0xd'],
       ['sub',   4,    5,    '92800000  mov     x0, #0xffffffffffffffff'],
       ['mul',   8,    3,    'd2800300  mov     x0, #0x18'],
       ['div_s', -8,   3,    '92800020  mov     x0, #0xfffffffffffffffe'],
       ['div_u', 8,    3,    'd2800040  mov     x0, #0x2'],
       ['rem_s', 8,    5,    'd2800060  mov     x0, #0x3'],
       ['rem_u', -7,   4,    'd2800020  mov     x0, #0x1'],
       ['and',   0xfe, 0x77, 'd2800ec0  mov     x0, #0x76'],
       ['or',    0xfe, 0x77, 'd2801fe0  mov     x0, #0xff'],
       ['xor',   0xfe, 0x77, 'd2801120  mov     x0, #0x89'],
       ['shl',   3,    4,    'd2800600  mov     x0, #0x30'],
       ['shr_s', -8,   1,    '92800060  mov     x0, #0xfffffffffffffffc'],
       ['shr_u', -8,   1,    'b27ef3e0  mov     x0, #0x7ffffffffffffffc']] ) {
    codegenTestARM64_adhoc(`
(module
  (func (export "f") (result i64)
    (i64.${op} (i64.const ${lhs}) (i64.const ${rhs}))))`,
                           'f',
                           expect);
}

// Basic tests that addition and multiplication identities are collapsed, use
// arg 1 here to force an explicit move to be emitted.

for ( [op, args, expect] of
      [['add',   '(local.get 1) (i64.const 0)', 'aa0103e0  mov     x0, x1'],
       ['add',   '(i64.const 0) (local.get 1)', 'aa0103e0  mov     x0, x1'],
       ['mul',   '(local.get 1) (i64.const 1)', 'aa0103e0  mov     x0, x1'],
       ['mul',   '(i64.const 1) (local.get 1)', 'aa0103e0  mov     x0, x1']] ) {
    codegenTestARM64_adhoc(`
(module
  (func (export "f") (param i64) (param i64) (result i64)
    (i64.${op} ${args})))`,
                           'f',
                           expect);
}

// Test that multiplication by -1 yields negation.

let neg32 =
    `(module
       (func (export "f") (param i32) (result i32)
         (i32.mul (local.get 0) (i32.const -1))))`;
codegenTestARM64_adhoc(
    neg32,
    'f',
    '4b0003e0  neg w0, w0');
assertEq(wasmEvalText(neg32).exports.f(-37), 37)
assertEq(wasmEvalText(neg32).exports.f(42), -42)

let neg64 = `(module
       (func (export "f") (param i64) (result i64)
         (i64.mul (local.get 0) (i64.const -1))))`
codegenTestARM64_adhoc(
    neg64,
    'f',
    'cb0003e0  neg x0, x0');
assertEq(wasmEvalText(neg64).exports.f(-37000000000n), 37000000000n)
assertEq(wasmEvalText(neg64).exports.f(42000000000n), -42000000000n)

// Test that multiplication by zero yields zero

let zero32 =
    `(module
       (func (export "f") (param i32) (result i32)
         (i32.mul (local.get 0) (i32.const 0))))`;
codegenTestARM64_adhoc(
    zero32,
    'f',
    '2a1f03e0  mov w0, wzr');
assertEq(wasmEvalText(zero32).exports.f(-37), 0)
assertEq(wasmEvalText(zero32).exports.f(42), 0)

let zero64 = `(module
       (func (export "f") (param i64) (result i64)
         (i64.mul (local.get 0) (i64.const 0))))`
codegenTestARM64_adhoc(
    zero64,
    'f',
    'aa1f03e0  mov x0, xzr');
assertEq(wasmEvalText(zero64).exports.f(-37000000000n), 0n)
assertEq(wasmEvalText(zero64).exports.f(42000000000n), 0n)

// Test that multiplication by one yields no code (this optimization currently
// exists both in constant folding and in lowering).

let one32 =
    `(module
       (func (export "f") (param i32) (result i32)
         (i32.mul (local.get 0) (i32.const 1))))`;
codegenTestARM64_adhoc(
    one32,
    'f',
    '');
assertEq(wasmEvalText(one32).exports.f(-37), -37)
assertEq(wasmEvalText(one32).exports.f(42), 42)

let one64 = `(module
       (func (export "f") (param i64) (result i64)
         (i64.mul (local.get 0) (i64.const 1))))`
codegenTestARM64_adhoc(
    one64,
    'f',
    '');
assertEq(wasmEvalText(one64).exports.f(-37000000000n), -37000000000n)
assertEq(wasmEvalText(one64).exports.f(42000000000n), 42000000000n)

// Test that multiplication by two yields an add

let double32 =
    `(module
       (func (export "f") (param i32) (result i32)
         (i32.mul (local.get 0) (i32.const 2))))`;
codegenTestARM64_adhoc(
    double32,
    'f',
    '2b000000  adds w0, w0, w0'); // The ADDS is some legacy thing, likely unnecessary
assertEq(wasmEvalText(double32).exports.f(-37), -74)
assertEq(wasmEvalText(double32).exports.f(42), 84)

let double64 = `(module
       (func (export "f") (param i64) (result i64)
         (i64.mul (local.get 0) (i64.const 2))))`
codegenTestARM64_adhoc(
    double64,
    'f',
    '8b000000  add x0, x0, x0');
assertEq(wasmEvalText(double64).exports.f(-37000000000n), -74000000000n)
assertEq(wasmEvalText(double64).exports.f(42000000000n), 84000000000n)

// Test that multiplication by four yields a shift

let quad32 =
    `(module
       (func (export "f") (param i32) (result i32)
         (i32.mul (local.get 0) (i32.const 4))))`;
codegenTestARM64_adhoc(
    quad32,
    'f',
    '531e7400  lsl w0, w0, #2');
assertEq(wasmEvalText(quad32).exports.f(-37), -148)
assertEq(wasmEvalText(quad32).exports.f(42), 168)

let quad64 = `(module
       (func (export "f") (param i64) (result i64)
         (i64.mul (local.get 0) (i64.const 4))))`
codegenTestARM64_adhoc(
    quad64,
    'f',
    'd37ef400  lsl x0, x0, #2');
assertEq(wasmEvalText(quad64).exports.f(-37000000000n), -148000000000n)
assertEq(wasmEvalText(quad64).exports.f(42000000000n), 168000000000n)

// Test that multiplication by five yields a multiply

let quint32 =
    `(module
       (func (export "f") (param i32) (result i32)
         (i32.mul (local.get 0) (i32.const 5))))`;
codegenTestARM64_adhoc(
    quint32,
    'f',
    `528000b0  mov     w16, #0x5
     1b107c00  mul     w0, w0, w16`);
assertEq(wasmEvalText(quint32).exports.f(-37), -37*5)
assertEq(wasmEvalText(quint32).exports.f(42), 42*5)

let quint64 = `(module
       (func (export "f") (param i64) (result i64)
         (i64.mul (local.get 0) (i64.const 5))))`
codegenTestARM64_adhoc(
    quint64,
    'f',
    `d28000b0  mov     x16, #0x5
     9b107c00  mul     x0, x0, x16`);
assertEq(wasmEvalText(quint64).exports.f(-37000000000n), -37000000000n*5n)
assertEq(wasmEvalText(quint64).exports.f(42000000000n), 42000000000n*5n)

// Test that add/sub/and/or/xor don't need to reuse their input register.  The
// proof here is that the destination register does not equal any of the input
// registers.
//
// We have adequate functionality tests for these elsewhere, so test only
// codegen here.

for ( [op, imm, expectVar, expectImm] of
      [['and', 64,
        '8a020020  and     x0, x1, x2',
        '927a0020  and     x0, x1, #0x40'],
       ['or', 64,
        'aa020020  orr     x0, x1, x2',
        'b27a0020  orr     x0, x1, #0x40'],
       ['xor', 64,
        'ca020020  eor     x0, x1, x2',
        'd27a0020  eor     x0, x1, #0x40'],
       ['add', 64,
        '8b020020  add     x0, x1, x2',
        '91010020  add     x0, x1, #0x40 \\(64\\)'],
       ['sub', 64,
        'cb020020  sub     x0, x1, x2',
        'd1010020  sub     x0, x1, #0x40 \\(64\\)']] ) {
    codegenTestARM64_adhoc(`
(module
  (func (export "f") (param i64) (param i64) (param i64) (result i64)
    (i64.${op} (local.get 1) (local.get 2))))`,
                           'f',
                           expectVar);
    codegenTestARM64_adhoc(`
(module
  (func (export "f") (param i64) (param i64) (result i64)
    (i64.${op} (local.get 1) (i64.const ${imm}))))`,
                           'f',
                           expectImm);
}

// Test that shifts and rotates with a constant don't need to reuse their input
// register.  The proof here is that the destination register does not equal any
// of the input registers.
//
// We have adequate functionality tests for these elsewhere, so test only
// codegen here.

for ( [op, expect] of
      [['shl',   'd37ef420  lsl     x0, x1, #2'],
       ['shr_s', '9342fc20  asr     x0, x1, #2'],
       ['shr_u', 'd342fc20  lsr     x0, x1, #2'],
       ['rotl',  '93c1f820  ror     x0, x1, #62'],
       ['rotr',  '93c10820  ror     x0, x1, #2']] ) {
    codegenTestARM64_adhoc(`
(module
  (func (export "f") (param i64) (param i64) (result i64)
    (i64.${op} (local.get 1) (i64.const 2))))`,
                           'f',
                           expect);
}

// Test that 0-n yields negation.

let subneg32 =
    `(module
       (func (export "f") (param i32) (result i32)
         (i32.sub (i32.const 0) (local.get 0))))`
codegenTestARM64_adhoc(
    subneg32,
    'f',
    '4b0003e0  neg w0, w0');
assertEq(wasmEvalText(subneg32).exports.f(-37), 37)
assertEq(wasmEvalText(subneg32).exports.f(42), -42)

let subneg64 = `(module
       (func (export "f") (param i64) (result i64)
         (i64.sub (i64.const 0) (local.get 0))))`
codegenTestARM64_adhoc(
    subneg64,
    'f',
    'cb0003e0  neg x0, x0');
assertEq(wasmEvalText(subneg64).exports.f(-37000000000n), 37000000000n)
assertEq(wasmEvalText(subneg64).exports.f(42000000000n), -42000000000n)

// Test that select does something reasonable and does not tie its output to one
// of its inputs.

codegenTestARM64_adhoc(
    `(module
       (func (export "f") (param i64) (param i64) (param i64) (param i32) (result i64)
         (select (local.get 1) (local.get 2) (local.get 3))))`,
    'f',
    `6a03007f  tst     w3, w3
     9a821020  csel    x0, x1, x2, ne`)

codegenTestARM64_adhoc(
    `(module
       (func (export "f") (param f64) (param f64) (param f64) (param i32) (result f64)
         (select (local.get 1) (local.get 2) (local.get 3))))`,
    'f',
    `6a00001f  tst     w0, w0
     1e621c20  fcsel   d0, d1, d2, ne`)

// Here we test that no boolean is generated and then re-tested, and that
// operands are swapped so that we can use an immediate constant, and that the
// input is not tied to the output.

codegenTestARM64_adhoc(
    `(module
       (func (export "f") (param $a i32) (param $b i32) (param $c i32) (param $d i32) (result i32)
         (select (local.get $b) (local.get $d) (i32.lt_s (i32.const 0) (local.get $c)))))`,
    'f',
    `7100005f  cmp     w2, #0x0 \\(0\\)
     1a83c020  csel    w0, w1, w3, gt`)

codegenTestARM64_adhoc(
    `(module
       (func (export "f") (param $a f64) (param $b f64) (param $c f64) (param $d f64) (result f64)
         (select (local.get $b) (local.get $d) (f64.lt (f64.const 0) (local.get $c)))))`,
    'f',
    `2f00e400  movi    d0, #0x0
     1e622000  fcmp    d0, d2
     1e633c20  fcsel   d0, d1, d3, lo`)

// FP ABS should not tie its input to its output.

codegenTestARM64_adhoc(
    `(module
       (func (export "f") (param f32) (param f32) (result f32)
         (f32.abs (local.get 1))))`,
    'f',
    '1e20c020  fabs    s0, s1');

codegenTestARM64_adhoc(
    `(module
       (func (export "f") (param f64) (param f64) (result f64)
         (f64.abs (local.get 1))))`,
    'f',
    '1e60c020  fabs    d0, d1');

// AND{32,64} followed by `== 0`: check the two operations are merged into a
// single 'tst' insn, and no 'and' insn.  The merging isn't done for
// {OR,XOR}{32,64}.  This is for both arguments being non-constant.

for ( [ty, expect_tst] of
      [['i32',   '6a01001f  tst w0, w1'],
       ['i64',   'ea01001f  tst x0, x1']] ) {
   codegenTestARM64_adhoc(
    `(module
       (func (export "f") (param $p1 ${ty}) (param $p2 ${ty}) (result i32)
         (local $x i32)
         (set_local $x (i32.const 0x4D2))
         (if (${ty}.eq (${ty}.and (local.get $p1) (local.get $p2))
                       (${ty}.const 0))
           (set_local $x (i32.const 0x11D7))
         )
         (get_local $x)
       )
    )`,
    'f',
    `${expect_tst}
     54000061  b\\.ne  #\\+0xc \\(addr .*\\)
     52823ae0  mov     w0, #0x11d7
     14000002  b       #\\+0x8 \\(addr .*\\)
     52809a40  mov     w0, #0x4d2`
   );
}

// AND64 followed by `== 0`, with one of the args being a constant.

for ( [imm, expect1, expect2] of
      [ // as a valid logical-immediate => imm in insn
        ['0x0F0F0F0F0F0F0F0F',
         'f200cc1f  tst  x0, #0xf0f0f0f0f0f0f0f',
         ''],
        // anything else => imm synth'd into a reg
        ['-0x4771',
         '9288ee10  mov  x16, #0xffffffffffffb88f',
         'ea10001f  tst  x0, x16']]
      ) {
   codegenTestARM64_adhoc(
    `(module
       (func (export "f") (param $p1 i64) (result i32)
         (local $x i32)
         (set_local $x (i32.const 0x4D2))
         (if (i64.eq (i64.and (i64.const ${imm}) (local.get $p1))
                     (i64.const 0))
           (set_local $x (i32.const 0x11D7))
         )
         (get_local $x)
       )
    )`,
    'f',
    `${expect1}
     ${expect2}
     54000061  b\\.ne  #\\+0xc \\(addr .*\\)
     52823ae0  mov     w0, #0x11d7
     14000002  b       #\\+0x8 \\(addr .*\\)
     52809a40  mov     w0, #0x4d2`
   );
}

// For integer comparison followed by select, check that the comparison result
// isn't materialised into a register, for specific types.

for ( [cmpTy, cmpOp, selTy, cmpRegPfx, cselRegPfx, armCC] of
      [ ['i32', 'le_s', 'i32',  'w', 'w', 'le'],
        ['i32', 'lt_u', 'i64',  'w', 'x', 'lo'],
        ['i64', 'le_s', 'i32',  'x', 'w', 'le'],
        ['i64', 'lt_u', 'i64',  'x', 'x', 'lo'],
      ] ) {
   codegenTestARM64_adhoc(
    `(module
       (func (export "f")
             (param $p1 ${cmpTy}) (param $p2 ${cmpTy})
             (param $p3 ${selTy}) (param $p4 ${selTy})
             (result ${selTy})
         (select (local.get $p3)
                 (local.get $p4)
                 (${cmpTy}.${cmpOp} (local.get $p1) (local.get $p2)))
       )
    )`,
    'f',
    `.b01001f  cmp  ${cmpRegPfx}0, ${cmpRegPfx}1
     .a83.040  csel ${cselRegPfx}0, ${cselRegPfx}2, ${cselRegPfx}3, ${armCC}`
   );
}
