public inbox for gcc@gcc.gnu.org
 help / color / mirror / Atom feed
From: "Stefan Kanthak" <stefan.kanthak@nexgo.de>
To: <gcc@gnu.org>
Subject: Widening multiplication, but no narrowing division [i386/AMD64]
Date: Mon, 9 Jan 2023 13:20:28 +0100	[thread overview]
Message-ID: <554A1354252F43BB8915A74129C41BE3@H270> (raw)

Hi,

GCC (and other C compilers too) support the widening multiplication
of i386/AMD64 processors, but DON'T support their narrowing division:

--- demo.c ---
unsigned long long product(unsigned long multiplicand,
                           unsigned long multiplier)
{
    return (unsigned long long) multiplicand * multiplier;
}

unsigned long long quotient(unsigned long long dividend,
                            unsigned long divisor,
                            unsigned long *remainder)
{
    *remainder = dividend % divisor;
    return dividend / divisor;
}
--- EOF ---

GCC 12.2: gcc -m32 -O2 demo.c

# https://godbolt.org/z/1M9dohMcE
product(unsigned long, unsigned long):
        mov     eax, DWORD PTR [esp+8]
        mul     DWORD PTR [esp+4]
        ret
quotient(unsigned long long, unsigned long, unsigned long*):
        push    ebx
        xor     edx, edx
        sub     esp, 24
        mov     eax, DWORD PTR [esp+40]
        lea     ecx, [esp+8]
        sub     esp, 12
        push    ecx
        push    edx
        push    eax
        push    DWORD PTR [esp+60]
        push    DWORD PTR [esp+60]
        call    __udivmoddi4
        mov     ebx, DWORD PTR [esp+40]
        mov     ecx, DWORD PTR [esp+76]
        mov     DWORD PTR [ecx], ebx
        add     esp, 56
        pop     ebx
        ret

### Diversion ###

Even worse and completely BRAINDEAD, another compiler calls __udivdi3()
and wastes a multiplication to compute the remainder, ignoring the fact
that __udivdi3() calls __udivmoddi4() which already returns quotient
and remainder:

clang 15.0.0: clang -m32 -O2 demo.c

# https://godbolt.org/z/rv1sTe7xv
product(unsigned long, unsigned long):
        mov     eax, dword ptr [esp + 8]
        mul     dword ptr [esp + 4]
        ret
quotient(unsigned long long, unsigned long, unsigned long*):
        push    ebp
        push    ebx
        push    edi
        push    esi
        sub     esp, 12
        call    .L1$pb
.L1$pb:
        pop     ebx
.Ltmp2:
        add     ebx, offset _GLOBAL_OFFSET_TABLE_+(.Ltmp2-.L1$pb)
        mov     esi, dword ptr [esp + 44]
        mov     edi, dword ptr [esp + 32]
        mov     ebp, dword ptr [esp + 40]
        push    0
        push    ebp
        push    dword ptr [esp + 44]
        push    edi
        call    __udivdi3@PLT
        add     esp, 16
        imul    ebp, eax
        sub     edi, ebp
        mov     dword ptr [esi], edi
        add     esp, 12
        pop     esi
        pop     edi
        pop     ebx
        pop     ebp
        ret

### end of diversion ###

Both compilers miss the fact that the i386 processor has a narrowing
integer division and can therefore divide 64-bit / 32-bit numbers,
for example with the well-known "long" alias "schoolbook" division,
returning 64-bit quotient and 32-bit remainder:

.arch   generic 32
.code32
.intel_syntax noprefix
.text

quotient:
        mov     ecx, [esp+12]   # ecx = divisor
.if 0
        xor     edx, edx
        mov     eax, [esp+8]    # edx:eax = high dword of dividend
        cmp     eax, edx
        je      0f              # high dword of dividend = 0?
.else
        xor     eax, eax
        mov     edx, [esp+8]    # eax:edx = high dword of dividend
        cmp     edx, ecx
        jb      0f              # high dword of dividend < divisor?
                                # quotient < 2**32?
        xchg    eax, edx
.endif
        div     ecx             # eax = high dword of quotient,
0:                              # edx = high dword of dividend'
        push    eax
        mov     eax, [esp+8]    # edx:eax = dividend'
        div     ecx             # eax = low dword of quotient,
                                # edx = remainder
        mov     ecx, remainder  # ecx = address of remainder
        mov     [ecx], edx
        pop     edx             # edx:eax = quotient
        ret
.end

JFTR: dependent on the magnitude of the numbers and the processor
      it MIGHT be better to omit comparison and branch: there's a
      trade-öff between the latency of the (un-pipelined) division
      instruction and the latency of the conditional branch due to
      misprediction.

Stefan Kanthak


             reply	other threads:[~2023-01-09 12:31 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-01-09 12:20 Stefan Kanthak [this message]
2023-01-09 13:10 ` LIU Hao
2023-01-09 13:19   ` Stefan Kanthak
2023-01-09 14:23 ` Paul Koning
2023-01-09 15:20   ` Stefan Kanthak
2023-01-09 15:30     ` Paul Koning
2023-01-09 16:27       ` Stefan Kanthak
2023-01-10 16:49         ` Paul Koning

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=554A1354252F43BB8915A74129C41BE3@H270 \
    --to=stefan.kanthak@nexgo.de \
    --cc=gcc@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).