// |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', '0b000000 add w0, w0, w0'); 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}` ); }