tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 304c3faa920dfec0faa3b9c92b3ab145b9197b2c
parent 78054ddcf46cd0744dd2c18152a9be98ccf4514e
Author: André Bargull <andre.bargull@gmail.com>
Date:   Sat, 15 Nov 2025 10:15:24 +0000

Bug 1998457 - Part 6: Add codegen tests for division/remainder with constants. r=spidermonkey-reviewers,iain

Add codegen tests to ensure division and modulus by constants generate the
expected code sequences.

Differential Revision: https://phabricator.services.mozilla.com/D271442

Diffstat:
Ajs/src/jit-test/tests/wasm/binop-divrem-with-constant-arm64-ion-codegen.js | 774+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/src/jit-test/tests/wasm/binop-divrem-with-constant-x64-ion-codegen.js | 719+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1493 insertions(+), 0 deletions(-)

diff --git a/js/src/jit-test/tests/wasm/binop-divrem-with-constant-arm64-ion-codegen.js b/js/src/jit-test/tests/wasm/binop-divrem-with-constant-arm64-ion-codegen.js @@ -0,0 +1,774 @@ +// |jit-test| skip-if: !hasDisassembler() || wasmCompileMode() != "ion" || !getBuildConfiguration("arm64"); include:codegen-arm64-test.js + +const WasmTrapIns = `dcps0 \\{#0x0\\} \\(Wasm Trap\\)`; + +// Signed 32-bit division with constants. +const i32_div_s = [ + // Division by zero. + { + divisor: 0, + expected: `mov w2, w0 + mov w1, w2 + ${WasmTrapIns}`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov w2, w0 + mov w1, w2 + mov w0, w1`, + }, + { + divisor: 2, + expected: `mov w2, w0 + mov w1, w2 + lsr w0, w1, #31 + add w0, w0, w1 + asr w0, w0, #1`, + }, + { + divisor: 4, + expected: `mov w2, w0 + mov w1, w2 + asr w0, w1, #31 + lsr w0, w0, #30 + add w0, w0, w1 + asr w0, w0, #2`, + }, + + // Division by -1 needs an overflow check. + { + divisor: -1, + expected: `mov w2, w0 + mov w1, w2 + negs w0, w1 + b.vc #\\+0x8 \\(addr 0x${HEX}+\\) + ${WasmTrapIns}`, + }, + + // Other divisors. + { + divisor: 3, + expected: `mov w2, w0 + mov w1, w2 + mov w16, #0x5556 + movk w16, #0x5555, lsl #16 + smull x0, w16, w1 + asr x0, x0, #32 + sub w0, w0, w1, asr #31`, + }, + { + divisor: 5, + expected: `mov w2, w0 + mov w1, w2 + mov w16, #0x6667 + movk w16, #0x6666, lsl #16 + smull x0, w16, w1 + asr x0, x0, #33 + sub w0, w0, w1, asr #31`, + }, + { + divisor: 7, + expected: `mov w2, w0 + mov w1, w2 + mov w16, #0x2493 + movk w16, #0x9249, lsl #16 + lsl x0, x1, #32 + smaddl x0, w16, w1, x0 + asr x0, x0, #34 + sub w0, w0, w1, asr #31`, + }, + { + divisor: 9, + expected: `mov w2, w0 + mov w1, w2 + mov w16, #0x8e39 + movk w16, #0x38e3, lsl #16 + smull x0, w16, w1 + asr x0, x0, #33 + sub w0, w0, w1, asr #31`, + }, +]; + +for (let {divisor, expected} of i32_div_s) { + let divs32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.div_s (local.get 0) (i32.const ${divisor}))))` + codegenTestARM64_adhoc(divs32, 'f', expected); + + // Test negative divisors, too. + if (divisor > 1) { + let divs32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.div_s (local.get 0) (i32.const -${divisor}))))` + codegenTestARM64_adhoc(divs32, 'f', expected + ` + neg w0, w0` + ); + } +} + +// Unsigned 32-bit division with constants. +const i32_div_u = [ + // Division by zero. + { + divisor: 0, + expected: `mov w2, w0 + mov w1, w2 + ${WasmTrapIns}`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov w2, w0 + mov w1, w2 + mov w0, w1`, + }, + { + divisor: 2, + expected: `mov w2, w0 + mov w1, w2 + lsr w0, w1, #1`, + }, + { + divisor: 4, + expected: `mov w2, w0 + mov w1, w2 + lsr w0, w1, #2`, + }, + + // Other divisors. + { + divisor: 3, + expected: `mov w2, w0 + mov w1, w2 + mov w16, #0xaaab + movk w16, #0xaaaa, lsl #16 + umull x0, w16, w1 + lsr x0, x0, #33`, + }, + { + divisor: 5, + expected: `mov w2, w0 + mov w1, w2 + mov w16, #0xcccd + movk w16, #0xcccc, lsl #16 + umull x0, w16, w1 + lsr x0, x0, #34`, + }, + { + divisor: 7, + expected: `mov w2, w0 + mov w1, w2 + mov w16, #0x4925 + movk w16, #0x2492, lsl #16 + umull x0, w16, w1 + add x0, x1, x0, lsr #32 + lsr x0, x0, #3`, + }, + { + divisor: 9, + expected: `mov w2, w0 + mov w1, w2 + mov w16, #0x8e39 + movk w16, #0x38e3, lsl #16 + umull x0, w16, w1 + lsr x0, x0, #33`, + }, + + // Special case: Zero (additional) shift amount. + { + divisor: 641, + expected: `mov w2, w0 + mov w1, w2 + mov w16, #0x3d81 + movk w16, #0x66, lsl #16 + umull x0, w16, w1 + lsr x0, x0, #32`, + }, +]; + +for (let {divisor, expected} of i32_div_u) { + let divu32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.div_u (local.get 0) (i32.const ${divisor}))))` + codegenTestARM64_adhoc(divu32, 'f', expected); +} + +// Signed 64-bit division with constants. +const i64_div_s = [ + // Division by zero. + { + divisor: 0, + expected: `mov x2, x0 + mov x1, x2 + ${WasmTrapIns}`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov x2, x0 + mov x1, x2 + mov x0, x1`, + }, + { + divisor: 2, + expected: `mov x2, x0 + mov x1, x2 + lsr x0, x1, #63 + add x0, x0, x1 + asr x0, x0, #1`, + }, + { + divisor: 4, + expected: `mov x2, x0 + mov x1, x2 + asr x0, x1, #63 + lsr x0, x0, #62 + add x0, x0, x1 + asr x0, x0, #2`, + }, + { + divisor: 0x1_0000_0000, + expected: `mov x2, x0 + mov x1, x2 + asr x0, x1, #63 + lsr x0, x0, #32 + add x0, x0, x1 + asr x0, x0, #32`, + }, + + // Division by -1 needs an overflow check. + { + divisor: -1, + expected: `mov x2, x0 + mov x1, x2 + negs x0, x1 + b.vc #\\+0x8 \\(addr 0x${HEX}+\\) + ${WasmTrapIns}`, + }, + + // Other divisors. + { + divisor: 3, + expected: `mov x2, x0 + mov x1, x2 + mov x16, #0x5556 + movk x16, #0x5555, lsl #16 + movk x16, #0x5555, lsl #32 + movk x16, #0x5555, lsl #48 + smulh x0, x1, x16 + sbfx x0, x0, #0, #64 + sub x0, x0, x1, asr #63`, + }, + { + divisor: 5, + expected: `mov x2, x0 + mov x1, x2 + mov x16, #0x6667 + movk x16, #0x6666, lsl #16 + movk x16, #0x6666, lsl #32 + movk x16, #0x6666, lsl #48 + smulh x0, x1, x16 + asr x0, x0, #1 + sub x0, x0, x1, asr #63`, + }, + { + divisor: 7, + expected: `mov x2, x0 + mov x1, x2 + mov x16, #0x4925 + movk x16, #0x2492, lsl #16 + movk x16, #0x9249, lsl #32 + movk x16, #0x4924, lsl #48 + smulh x0, x1, x16 + asr x0, x0, #1 + sub x0, x0, x1, asr #63`, + }, + { + divisor: 9, + expected: `mov x2, x0 + mov x1, x2 + mov x16, #0x1c72 + movk x16, #0x71c7, lsl #16 + movk x16, #0xc71c, lsl #32 + movk x16, #0x1c71, lsl #48 + smulh x0, x1, x16 + sbfx x0, x0, #0, #64 + sub x0, x0, x1, asr #63`, + }, +]; + +for (let {divisor, expected} of i64_div_s) { + let divs64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.div_s (local.get 0) (i64.const ${divisor}))))` + codegenTestARM64_adhoc(divs64, 'f', expected); + + // Test negative divisors, too. + if (divisor > 1) { + let divs64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.div_s (local.get 0) (i64.const -${divisor}))))` + codegenTestARM64_adhoc(divs64, 'f', expected + ` + neg x0, x0`); + } +} + +// Unsigned 64-bit division with constants. +const i64_div_u = [ + // Division by zero. + { + divisor: 0, + expected: `mov x2, x0 + mov x1, x2 + ${WasmTrapIns}`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov x2, x0 + mov x1, x2 + mov x0, x1`, + }, + { + divisor: 2, + expected: `mov x2, x0 + mov x1, x2 + lsr x0, x1, #1`, + }, + { + divisor: 4, + expected: `mov x2, x0 + mov x1, x2 + lsr x0, x1, #2`, + }, + { + divisor: 0x1_0000_0000, + expected: `mov x2, x0 + mov x1, x2 + lsr x0, x1, #32`, + }, + + // Other divisors. + { + divisor: 3, + expected: `mov x2, x0 + mov x1, x2 + mov x16, #0xaaab + movk x16, #0xaaaa, lsl #16 + movk x16, #0xaaaa, lsl #32 + movk x16, #0xaaaa, lsl #48 + umulh x0, x1, x16 + lsr x0, x0, #1`, + }, + { + divisor: 5, + expected: `mov x2, x0 + mov x1, x2 + mov x16, #0xcccd + movk x16, #0xcccc, lsl #16 + movk x16, #0xcccc, lsl #32 + movk x16, #0xcccc, lsl #48 + umulh x0, x1, x16 + lsr x0, x0, #2`, + }, + { + divisor: 7, + expected: `mov x2, x0 + mov x1, x2 + mov x16, #0x2493 + movk x16, #0x9249, lsl #16 + movk x16, #0x4924, lsl #32 + movk x16, #0x2492, lsl #48 + umulh x0, x1, x16 + sub x16, x1, x0 + add x0, x0, x16, lsr #1 + lsr x0, x0, #2`, + }, + { + divisor: 9, + expected: `mov x2, x0 + mov x1, x2 + mov x16, #0xe38f + movk x16, #0x8e38, lsl #16 + movk x16, #0x38e3, lsl #32 + movk x16, #0xe38e, lsl #48 + umulh x0, x1, x16 + lsr x0, x0, #3`, + }, + + // Special case: Zero shift amount. + { + divisor: 274177, + expected: `mov x2, x0 + mov x1, x2 + mov x16, #0xd101 + movk x16, #0xf19c, lsl #16 + movk x16, #0x3d30, lsl #32 + umulh x0, x1, x16 + lsr x0, x0, #0`, + }, +]; + +for (let {divisor, expected} of i64_div_u) { + let divu64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.div_u (local.get 0) (i64.const ${divisor}))))` + codegenTestARM64_adhoc(divu64, 'f', expected); +} + +////////////// + + + +// Signed 32-bit remainder with constants. +const i32_rem_s = [ + // Division by zero. + { + divisor: 0, + expected: `mov w2, w0 + mov w1, w2 + ${WasmTrapIns}`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov w2, w0 + mov w1, w2 + mov w0, wzr`, + }, + { + divisor: 2, + expected: `mov w2, w0 + mov w1, w2 + tst w1, w1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and w0, w1, #0x1 + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg w0, w1 + and w0, w0, #0x1 + neg w0, w0`, + }, + { + divisor: 4, + expected: `mov w2, w0 + mov w1, w2 + tst w1, w1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and w0, w1, #0x3 + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg w0, w1 + and w0, w0, #0x3 + neg w0, w0`, + }, + { + divisor: 0x100, + expected: `mov w2, w0 + mov w1, w2 + tst w1, w1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and w0, w1, #0xff + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg w0, w1 + and w0, w0, #0xff + neg w0, w0`, + }, + { + divisor: 0x10000, + expected: `mov w2, w0 + mov w1, w2 + tst w1, w1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and w0, w1, #0xffff + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg w0, w1 + and w0, w0, #0xffff + neg w0, w0`, + }, + { + divisor: 0x8000_0000, + expected: `mov w2, w0 + mov w1, w2 + tst w1, w1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and w0, w1, #0x7fffffff + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg w0, w1 + and w0, w0, #0x7fffffff + neg w0, w0`, + }, +]; + +for (let {divisor, expected} of i32_rem_s) { + let rems32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.rem_s (local.get 0) (i32.const ${divisor}))))` + codegenTestARM64_adhoc(rems32, 'f', expected); + + // Test negative divisors, too. + if (divisor > 0) { + let rems32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.rem_s (local.get 0) (i32.const -${divisor}))))` + codegenTestARM64_adhoc(rems32, 'f', expected); + } +} + +// Unigned 32-bit remainder with constants. +const u32_rem_s = [ + // Division by zero. + { + divisor: 0, + expected: `mov w2, w0 + mov w1, w2 + ${WasmTrapIns}`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov w2, w0 + mov w1, w2 + mov w0, wzr`, + }, + { + divisor: 2, + expected: `mov w2, w0 + mov w1, w2 + and w0, w1, #0x1`, + }, + { + divisor: 4, + expected: `mov w2, w0 + mov w1, w2 + and w0, w1, #0x3`, + }, + { + divisor: 0x100, + expected: `mov w2, w0 + mov w1, w2 + and w0, w1, #0xff`, + }, + { + divisor: 0x10000, + expected: `mov w2, w0 + mov w1, w2 + and w0, w1, #0xffff`, + }, + { + divisor: 0x8000_0000, + expected: `mov w2, w0 + mov w1, w2 + and w0, w1, #0x7fffffff`, + }, +]; + +for (let {divisor, expected} of u32_rem_s) { + let remu32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.rem_u (local.get 0) (i32.const ${divisor}))))` + codegenTestARM64_adhoc(remu32, 'f', expected); +} + +// Signed 64-bit remainder with constants. +const i64_rem_s = [ + // Division by zero. + { + divisor: 0, + expected: `mov x2, x0 + mov x1, x2 + ${WasmTrapIns}`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov x2, x0 + mov x1, x2 + mov x0, xzr`, + }, + { + divisor: 2, + expected: `mov x2, x0 + mov x1, x2 + tst x1, x1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and x0, x1, #0x1 + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg x0, x1 + and x0, x0, #0x1 + neg x0, x0`, + }, + { + divisor: 4, + expected: `mov x2, x0 + mov x1, x2 + tst x1, x1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and x0, x1, #0x3 + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg x0, x1 + and x0, x0, #0x3 + neg x0, x0`, + }, + { + divisor: 0x100, + expected: `mov x2, x0 + mov x1, x2 + tst x1, x1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and x0, x1, #0xff + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg x0, x1 + and x0, x0, #0xff + neg x0, x0`, + }, + { + divisor: 0x10000, + expected: `mov x2, x0 + mov x1, x2 + tst x1, x1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and x0, x1, #0xffff + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg x0, x1 + and x0, x0, #0xffff + neg x0, x0`, + }, + { + divisor: 0x8000_0000, + expected: `mov x2, x0 + mov x1, x2 + tst x1, x1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and x0, x1, #0x7fffffff + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg x0, x1 + and x0, x0, #0x7fffffff + neg x0, x0`, + }, + { + divisor: 0x1_0000_0000, + expected: `mov x2, x0 + mov x1, x2 + tst x1, x1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + mov w0, w1 + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg x0, x1 + mov w0, w0 + neg x0, x0`, + }, + { + divisor: 0x8000_0000_0000_0000n, + expected: `mov x2, x0 + mov x1, x2 + tst x1, x1 + b.mi #\\+0xc \\(addr 0x${HEX}+\\) + and x0, x1, #0x7fffffffffffffff + b #\\+0x10 \\(addr 0x${HEX}+\\) + neg x0, x1 + and x0, x0, #0x7fffffffffffffff + neg x0, x0`, + }, +]; + +for (let {divisor, expected} of i64_rem_s) { + let rems64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.rem_s (local.get 0) (i64.const ${divisor}))))` + codegenTestARM64_adhoc(rems64, 'f', expected); + + // Test negative divisors, too. + if (divisor > 0) { + let rems64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.rem_s (local.get 0) (i64.const -${divisor}))))` + codegenTestARM64_adhoc(rems64, 'f', expected); + } +} + +// Unsigned 64-bit remainder with constants. +const i64_rem_u = [ + // Division by zero. + { + divisor: 0, + expected: `mov x2, x0 + mov x1, x2 + ${WasmTrapIns}`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov x2, x0 + mov x1, x2 + mov x0, xzr`, + }, + { + divisor: 2, + expected: `mov x2, x0 + mov x1, x2 + and x0, x1, #0x1`, + }, + { + divisor: 4, + expected: `mov x2, x0 + mov x1, x2 + and x0, x1, #0x3`, + }, + { + divisor: 0x100, + expected: `mov x2, x0 + mov x1, x2 + and x0, x1, #0xff`, + }, + { + divisor: 0x10000, + expected: `mov x2, x0 + mov x1, x2 + and x0, x1, #0xffff`, + }, + { + divisor: 0x8000_0000, + expected: `mov x2, x0 + mov x1, x2 + and x0, x1, #0x7fffffff`, + }, + { + divisor: 0x1_0000_0000, + expected: `mov x2, x0 + mov x1, x2 + mov w0, w1`, + }, + { + divisor: 0x8000_0000_0000_0000n, + expected: `mov x2, x0 + mov x1, x2 + and x0, x1, #0x7fffffffffffffff`, + }, +]; + +for (let {divisor, expected} of i64_rem_u) { + let remu64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.rem_u (local.get 0) (i64.const ${divisor}))))` + codegenTestARM64_adhoc(remu64, 'f', expected); +} diff --git a/js/src/jit-test/tests/wasm/binop-divrem-with-constant-x64-ion-codegen.js b/js/src/jit-test/tests/wasm/binop-divrem-with-constant-x64-ion-codegen.js @@ -0,0 +1,719 @@ +// |jit-test| skip-if: !hasDisassembler() || wasmCompileMode() != "ion" || getBuildConfiguration("windows") || !getBuildConfiguration("x64") || getBuildConfiguration("simulator"); include:codegen-x64-test.js + +// Windows is disallowed because the argument registers are different from on +// Linux, and matching both is both difficult and not of much value. + +// Signed 32-bit division with constants. +const i32_div_s = [ + // Division by zero. + { + divisor: 0, + expected: `ud2`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov %edi, %eax`, + }, + { + divisor: 2, + expected: `mov %edi, %eax + shr \\$0x1F, %eax + add %edi, %eax + sar \\$0x01, %eax`, + }, + { + divisor: 4, + expected: `mov %edi, %eax + sar \\$0x1F, %eax + shr \\$0x1E, %eax + add %edi, %eax + sar \\$0x02, %eax`, + }, + + // Division by -1 needs an overflow check. + { + divisor: -1, + expected: `mov %edi, %eax + neg %eax + jno 0x${HEX}+ + ud2`, + }, + + // Other divisors. + { + divisor: 3, + expected: `movsxd %edi, %rax + imul \\$0x55555556, %rax, %rax + shr \\$0x20, %rax + mov %edi, %ecx + sar \\$0x1F, %ecx + sub %ecx, %eax`, + }, + { + divisor: 5, + expected: `movsxd %edi, %rax + imul \\$0x66666667, %rax, %rax + sar \\$0x21, %rax + mov %edi, %ecx + sar \\$0x1F, %ecx + sub %ecx, %eax`, + }, + { + divisor: 7, + expected: `movsxd %edi, %rax + imul \\$-0x6DB6DB6D, %rax, %rax + shr \\$0x20, %rax + add %edi, %eax + sar \\$0x02, %eax + mov %edi, %ecx + sar \\$0x1F, %ecx + sub %ecx, %eax`, + }, + { + divisor: 9, + expected: `movsxd %edi, %rax + imul \\$0x38E38E39, %rax, %rax + sar \\$0x21, %rax + mov %edi, %ecx + sar \\$0x1F, %ecx + sub %ecx, %eax`, + }, +]; + +for (let {divisor, expected} of i32_div_s) { + let divs32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.div_s (local.get 0) (i32.const ${divisor}))))` + codegenTestX64_adhoc(divs32, 'f', expected); + + // Test negative divisors, too. + if (divisor > 1) { + let divs32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.div_s (local.get 0) (i32.const -${divisor}))))` + codegenTestX64_adhoc(divs32, 'f', expected + ` + neg %eax` + ); + } +} + +// Unsigned 32-bit division with constants. +const i32_div_u = [ + // Division by zero. + { + divisor: 0, + expected: `ud2`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov %edi, %ecx + mov %ecx, %eax`, + }, + { + divisor: 2, + expected: `mov %edi, %ecx + mov %ecx, %eax + shr \\$0x01, %eax`, + }, + { + divisor: 4, + expected: `mov %edi, %ecx + mov %ecx, %eax + shr \\$0x02, %eax`, + }, + + // Other divisors. + { + divisor: 3, + expected: `mov %edi, %eax + mov \\$-0x55555555, %ecx + imul %rcx, %rax + shr \\$0x21, %rax`, + }, + { + divisor: 5, + expected: `mov %edi, %eax + mov \\$-0x33333333, %ecx + imul %rcx, %rax + shr \\$0x22, %rax`, + }, + { + divisor: 7, + expected: `mov %edi, %eax + imul \\$0x24924925, %rax, %rax + shr \\$0x20, %rax + mov %edi, %ecx + sub %eax, %ecx + shr \\$0x01, %ecx + add %ecx, %eax + shr \\$0x02, %eax`, + }, + { + divisor: 9, + expected: `mov %edi, %eax + imul \\$0x38E38E39, %rax, %rax + shr \\$0x21, %rax`, + }, + + // Special case: Zero (additional) shift amount. + { + divisor: 641, + expected: `mov %edi, %eax + imul \\$0x663D81, %rax, %rax + shr \\$0x20, %rax`, + }, +]; + +for (let {divisor, expected} of i32_div_u) { + let divu32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.div_u (local.get 0) (i32.const ${divisor}))))` + codegenTestX64_adhoc(divu32, 'f', expected); +} + +// Signed 64-bit division with constants. +const i64_div_s = [ + // Division by zero. + { + divisor: 0, + expected: `ud2`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov %rdi, %rax`, + }, + { + divisor: 2, + expected: `mov %rdi, %rax + shr \\$0x3F, %rax + add %rdi, %rax + sar \\$0x01, %rax`, + }, + { + divisor: 4, + expected: `mov %rdi, %rax + sar \\$0x3F, %rax + shr \\$0x3E, %rax + add %rdi, %rax + sar \\$0x02, %rax`, + }, + { + divisor: 0x1_0000_0000, + expected: `mov %rdi, %rax + sar \\$0x3F, %rax + shr \\$0x20, %rax + add %rdi, %rax + sar \\$0x20, %rax`, + }, + + // Division by -1 needs an overflow check. + { + divisor: -1, + expected: `mov %rdi, %rax + neg %rax + jno 0x${HEX}+ + ud2`, + }, + + // Other divisors. + { + divisor: 3, + expected: `mov \\$0x5555555555555556, %rax + imul %rdi + mov %rdi, %rax + sar \\$0x3F, %rax + sub %rax, %rdx`, + }, + { + divisor: 5, + expected: `mov \\$0x6666666666666667, %rax + imul %rdi + sar \\$0x01, %rdx + mov %rdi, %rax + sar \\$0x3F, %rax + sub %rax, %rdx`, + }, + { + divisor: 7, + expected: `mov \\$0x4924924924924925, %rax + imul %rdi + sar \\$0x01, %rdx + mov %rdi, %rax + sar \\$0x3F, %rax + sub %rax, %rdx`, + }, + { + divisor: 9, + expected: `mov \\$0x1C71C71C71C71C72, %rax + imul %rdi + mov %rdi, %rax + sar \\$0x3F, %rax + sub %rax, %rdx`, + }, +]; + +for (let {divisor, expected} of i64_div_s) { + let result = IsPowerOfTwo(divisor) ? "rax" : "rdx"; + let mov_rdx_to_rax = result !== "rax" ? ` + mov %rdx, %rax` : ``; + + let divs64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.div_s (local.get 0) (i64.const ${divisor}))))` + codegenTestX64_adhoc(divs64, 'f', expected + mov_rdx_to_rax); + + // Test negative divisors, too. + if (divisor > 1) { + let divs64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.div_s (local.get 0) (i64.const -${divisor}))))` + codegenTestX64_adhoc(divs64, 'f', expected + ` + neg %${result}` + mov_rdx_to_rax + ); + } +} + +function IsPowerOfTwo(x) { + x = BigInt(x); + if (x < 0) { + x = -x; + } + return x && (x & (x - 1n)) === 0n; +} + +// Unsigned 64-bit division with constants. +const i64_div_u = [ + // Division by zero. + { + divisor: 0, + expected: `ud2 + mov %rdx, %rax`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov %rdi, %rcx + mov %rcx, %rax`, + }, + { + divisor: 2, + expected: `mov %rdi, %rcx + mov %rcx, %rax + shr \\$0x01, %rax`, + }, + { + divisor: 4, + expected: `mov %rdi, %rcx + mov %rcx, %rax + shr \\$0x02, %rax`, + }, + { + divisor: 0x1_0000_0000, + expected: `mov %rdi, %rcx + mov %rcx, %rax + shr \\$0x20, %rax`, + }, + + // Other divisors. + { + divisor: 3, + expected: `mov \\$-0x5555555555555555, %rax + mul %rdi + shr \\$0x01, %rdx + mov %rdx, %rax`, + }, + { + divisor: 5, + expected: `mov \\$-0x3333333333333333, %rax + mul %rdi + shr \\$0x02, %rdx + mov %rdx, %rax`, + }, + { + divisor: 7, + expected: `mov \\$0x2492492492492493, %rax + mul %rdi + mov %rdi, %rax + sub %rdx, %rax + shr \\$0x01, %rax + add %rax, %rdx + shr \\$0x02, %rdx + mov %rdx, %rax`, + }, + { + divisor: 9, + expected: `mov \\$-0x1C71C71C71C71C71, %rax + mul %rdi + shr \\$0x03, %rdx + mov %rdx, %rax`, + }, + + // Special case: Zero shift amount. + { + divisor: 274177, + expected: `mov \\$0x3D30F19CD101, %rax + mul %rdi + mov %rdx, %rax`, + }, +]; + +for (let {divisor, expected} of i64_div_u) { + let divu64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.div_u (local.get 0) (i64.const ${divisor}))))` + codegenTestX64_adhoc(divu64, 'f', expected); +} + +////////////// + + + +// Signed 32-bit remainder with constants. +const i32_rem_s = [ + // Division by zero. + { + divisor: 0, + expected: `ud2`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov %edi, %ecx + mov %ecx, %eax + xor %eax, %eax`, + }, + { + divisor: 2, + expected: `mov %edi, %ecx + mov %ecx, %eax + test %eax, %eax + js 0x${HEX}+ + and \\$0x01, %eax + jmp 0x${HEX}+ + neg %eax + and \\$0x01, %eax + neg %eax`, + }, + { + divisor: 4, + expected: `mov %edi, %ecx + mov %ecx, %eax + test %eax, %eax + js 0x${HEX}+ + and \\$0x03, %eax + jmp 0x${HEX}+ + neg %eax + and \\$0x03, %eax + neg %eax`, + }, + { + divisor: 0x100, + expected: `mov %edi, %ecx + mov %ecx, %eax + test %eax, %eax + js 0x${HEX}+ + movzx %al, %eax + jmp 0x${HEX}+ + neg %eax + movzx %al, %eax + neg %eax`, + }, + { + divisor: 0x10000, + expected: `mov %edi, %ecx + mov %ecx, %eax + test %eax, %eax + js 0x${HEX}+ + movzx %ax, %eax + jmp 0x${HEX}+ + neg %eax + movzx %ax, %eax + neg %eax`, + }, + { + divisor: 0x8000_0000, + expected: `mov %edi, %ecx + mov %ecx, %eax + test %eax, %eax + js 0x${HEX}+ + and \\$0x7FFFFFFF, %eax + jmp 0x${HEX}+ + neg %eax + and \\$0x7FFFFFFF, %eax + neg %eax`, + }, +]; + +for (let {divisor, expected} of i32_rem_s) { + let rems32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.rem_s (local.get 0) (i32.const ${divisor}))))` + codegenTestX64_adhoc(rems32, 'f', expected); + + // Test negative divisors, too. + if (divisor > 0) { + let rems32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.rem_s (local.get 0) (i32.const -${divisor}))))` + codegenTestX64_adhoc(rems32, 'f', expected); + } +} + +// Unigned 32-bit remainder with constants. +const u32_rem_s = [ + // Division by zero. + { + divisor: 0, + expected: `ud2`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov %edi, %ecx + mov %ecx, %eax + xor %eax, %eax`, + }, + { + divisor: 2, + expected: `mov %edi, %ecx + mov %ecx, %eax + and \\$0x01, %eax`, + }, + { + divisor: 4, + expected: `mov %edi, %ecx + mov %ecx, %eax + and \\$0x03, %eax`, + }, + { + divisor: 0x100, + expected: `mov %edi, %ecx + mov %ecx, %eax + movzx %al, %eax`, + }, + { + divisor: 0x10000, + expected: `mov %edi, %ecx + mov %ecx, %eax + movzx %ax, %eax`, + }, + { + divisor: 0x8000_0000, + expected: `mov %edi, %ecx + mov %ecx, %eax + and \\$0x7FFFFFFF, %eax`, + }, +]; + +for (let {divisor, expected} of u32_rem_s) { + let remu32 = + `(module + (func (export "f") (param i32) (result i32) + (i32.rem_u (local.get 0) (i32.const ${divisor}))))` + codegenTestX64_adhoc(remu32, 'f', expected); +} + +// Signed 64-bit remainder with constants. +const i64_rem_s = [ + // Division by zero. + { + divisor: 0, + expected: `ud2`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov %rdi, %rcx + mov %rcx, %rax + xor %eax, %eax`, + }, + { + divisor: 2, + expected: `mov %rdi, %rcx + mov %rcx, %rax + test %rax, %rax + js 0x${HEX}+ + and \\$0x01, %eax + jmp 0x${HEX}+ + neg %rax + and \\$0x01, %eax + neg %rax`, + }, + { + divisor: 4, + expected: `mov %rdi, %rcx + mov %rcx, %rax + test %rax, %rax + js 0x${HEX}+ + and \\$0x03, %eax + jmp 0x${HEX}+ + neg %rax + and \\$0x03, %eax + neg %rax`, + }, + { + divisor: 0x100, + expected: `mov %rdi, %rcx + mov %rcx, %rax + test %rax, %rax + js 0x${HEX}+ + movzx %al, %eax + jmp 0x${HEX}+ + neg %rax + movzx %al, %eax + neg %rax`, + }, + { + divisor: 0x10000, + expected: `mov %rdi, %rcx + mov %rcx, %rax + test %rax, %rax + js 0x${HEX}+ + movzx %ax, %eax + jmp 0x${HEX}+ + neg %rax + movzx %ax, %eax + neg %rax`, + }, + { + divisor: 0x8000_0000, + expected: `mov %rdi, %rcx + mov %rcx, %rax + test %rax, %rax + js 0x${HEX}+ + and \\$0x7FFFFFFF, %eax + jmp 0x${HEX}+ + neg %rax + and \\$0x7FFFFFFF, %eax + neg %rax`, + }, + { + divisor: 0x1_0000_0000, + expected: `mov %rdi, %rcx + mov %rcx, %rax + test %rax, %rax + js 0x${HEX}+ + mov %eax, %eax + jmp 0x${HEX}+ + neg %rax + mov %eax, %eax + neg %rax`, + }, + { + divisor: 0x8000_0000_0000_0000n, + expected: `mov %rdi, %rcx + mov %rcx, %rax + test %rax, %rax + js 0x${HEX}+ + mov \\$0x7FFFFFFFFFFFFFFF, %r11 + and %r11, %rax + jmp 0x${HEX}+ + neg %rax + mov \\$0x7FFFFFFFFFFFFFFF, %r11 + and %r11, %rax + neg %rax`, + }, +]; + +for (let {divisor, expected} of i64_rem_s) { + let rems64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.rem_s (local.get 0) (i64.const ${divisor}))))` + codegenTestX64_adhoc(rems64, 'f', expected); + + // Test negative divisors, too. + if (divisor > 0) { + let rems64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.rem_s (local.get 0) (i64.const -${divisor}))))` + codegenTestX64_adhoc(rems64, 'f', expected); + } +} + +// Unsigned 64-bit remainder with constants. +const i64_rem_u = [ + // Division by zero. + { + divisor: 0, + expected: `ud2`, + }, + + // Power of two divisor + { + divisor: 1, + expected: `mov %rdi, %rcx + mov %rcx, %rax + xor %eax, %eax`, + }, + { + divisor: 2, + expected: `mov %rdi, %rcx + mov %rcx, %rax + and \\$0x01, %eax`, + }, + { + divisor: 4, + expected: `mov %rdi, %rcx + mov %rcx, %rax + and \\$0x03, %eax`, + }, + { + divisor: 0x100, + expected: `mov %rdi, %rcx + mov %rcx, %rax + movzx %al, %eax`, + }, + { + divisor: 0x10000, + expected: `mov %rdi, %rcx + mov %rcx, %rax + movzx %ax, %eax`, + }, + { + divisor: 0x8000_0000, + expected: `mov %rdi, %rcx + mov %rcx, %rax + and \\$0x7FFFFFFF, %eax`, + }, + { + divisor: 0x1_0000_0000, + expected: `mov %rdi, %rcx + mov %rcx, %rax + mov %eax, %eax`, + }, + { + divisor: 0x8000_0000_0000_0000n, + expected: `mov %rdi, %rcx + mov %rcx, %rax + mov \\$0x7FFFFFFFFFFFFFFF, %r11 + and %r11, %rax`, + }, +]; + +for (let {divisor, expected} of i64_rem_u) { + let remu64 = + `(module + (func (export "f") (param i64) (result i64) + (i64.rem_u (local.get 0) (i64.const ${divisor}))))` + codegenTestX64_adhoc(remu64, 'f', expected); +}