x86汇编中带符号64位乘法与128位除法函数实现求助
Great question! Adapting your unsigned MASM arithmetic functions to signed versions requires understanding how x86-64 handles signed integer operations and aligning with MSVC's calling convention (since you're using Visual Studio). Let's walk through both implementations step by step:
Signed 64-bit Multiplication (128-bit Result)
For signed multiplication, we leverage the imul instruction (instead of unsigned mul), which natively handles sign extension and produces a 128-bit signed result in rdx:rax—perfect for returning a __int128 in MSVC.
C++ Function Declaration
__int128 mul_s64(int64_t a, int64_t b);
MASM Implementation
.code mul_s64 proc ; MSVC calling convention: rcx = first operand (a), rdx = second operand (b) mov rax, rcx ; Load first operand into rax imul rdx ; Signed multiply rax * rdx → result stored in rdx:rax ; MSVC uses rdx:rax to return __int128 values, so we can directly return ret mul_s64 endp end
Key Notes
imulautomatically handles sign extension for the 128-bit result, so no extra logic is needed to adjust the sign of the product.- This matches the behavior of C++'s built-in signed multiplication, including overflow handling (which wraps around per two's complement rules).
Signed 128-bit Division (128-bit Quotient + 32-bit Remainder)
Signed division is trickier because we need to ensure the quotient's sign is the XOR of the dividend and divisor's signs, and the remainder's sign matches the dividend. We'll use the idiv instruction, which handles signed division of a 128-bit dividend (rdx:rax) by a 64-bit divisor.
C++ Function Declaration
__int128 div_s128(__int128 dividend, int64_t divisor, int32_t* remainder);
MASM Implementation
.code div_s128 proc ; MSVC calling convention breakdown: ; rcx = dividend (low 64 bits), rdx = dividend (high 64 bits) ; r8 = divisor, r9 = pointer to store 32-bit remainder push rbx ; Save non-volatile register (required by MSVC convention) mov rbx, r9 ; Store remainder pointer in rbx for later use ; Set up dividend in rdx:rax (already aligned with MSVC's __int128 parameter passing) mov rax, rcx ; Load low 64 bits of dividend into rax ; rdx already holds the high 64 bits (sign-extended for __int128) idiv r8 ; Signed division: rdx:rax ÷ r8 → rax=quotient, rdx=remainder ; Remainder will have the same sign as the dividend ; Extend 64-bit quotient to 128-bit signed value mov rdx, 0 test rax, rax ; Check if quotient is positive (bit 63 is 0) jns quotient_positive mov rdx, 0FFFFFFFFFFFFFFFFh ; Sign-extend negative quotient to 128 bits quotient_positive: ; Prepare 32-bit remainder (sign-extend to match int32_t) mov eax, edx ; Grab low 32 bits of the 64-bit remainder cdq ; Sign-extend eax to edx:eax (ensures correct negative representation) mov [rbx], eax ; Store the signed 32-bit remainder in the output pointer pop rbx ; Restore non-volatile register ret ; Return 128-bit quotient in rdx:rax div_s128 endp end
Key Notes
- Sign Handling:
idivautomatically computes the correct quotient and remainder signs, so we don't need to manually flip signs (unlike manual unsigned-to-signed conversion approaches). - 128-bit Quotient: Since a 128-bit dividend divided by a 64-bit divisor can only produce a 64-bit quotient, we sign-extend it to 128 bits by setting
rdxto all 1s for negative values or 0 for positive values. - 32-bit Remainder: We truncate the 64-bit remainder to 32 bits and use
cdqto ensure proper sign extension for theint32_ttype. - Boundary Cases: Be sure to handle division by zero in your C++ caller (the assembly will throw an exception if
divisoris 0) and overflow scenarios (e.g.,INT128_MIN ÷ -1, which exceedsINT128_MAX).
内容的提问来源于stack exchange,提问作者b.sullender

