web-dev-qa-db-fra.com

Langage d'assemblage - Comment faire Modulo?

Existe-t-il quelque chose comme un opérateur ou une instruction modulo dans un assemblage x86?

34
enne87

Si votre module/diviseur est une constante connue et que vous vous souciez des performances, consultez this et this . Un inverse multiplicatif est même possible pour les valeurs invariantes en boucle qui ne sont pas connues avant l'exécution, par ex. voir https://libdivide.com/ (Mais sans code-gen JIT, c'est moins efficace que de coder en dur juste les étapes nécessaires pour une constante.)

N'utilisez jamais div pour des puissances connues de 2: c'est beaucoup plus lent que and pour le reste, ou vers la droite pour la division. Regardez la sortie du compilateur C pour des exemples de division non signée ou signée par puissances de 2, par exemple sur l'explorateur du compilateur Godbolt . Si vous savez qu'une entrée d'exécution est une puissance de 2, utilisez lea eax, [esi-1]; and eax, edi Ou quelque chose comme ça pour faire x & (y-1). Modulo 256 est encore plus efficace: movzx eax, cl N'a aucune latence sur les processeurs Intel récents (mov-élimination), tant que les deux registres sont séparés.


Dans le cas simple/général: valeur inconnue à l'exécution

L'instruction DIV (et son équivalent IDIV pour les numéros signés) donne à la fois le quotient et le reste. Pour non signé, reste et module sont la même chose. Pour les idiv signés, cela vous donne le reste (pas le module) qui peut être négatif:
par exemple. -5 / 2 = -2 rem -1. La sémantique de division x86 correspond exactement à l'opérateur % de C99.

DIV r32 Divise un nombre 64 bits dans EDX:EAX Par un opérande 32 bits (dans n'importe quel registre ou mémoire) et stocke le quotient dans EAX et le reste dans EDX. Il fait défaut sur débordement du quotient.

Exemple 32 bits non signé (fonctionne dans n'importe quel mode)

mov eax, 1234          ; dividend low half
mov edx, 0             ; dividend high half = 0.  prefer  xor edx,edx

mov ebx, 10            ; divisor can be any register or memory

div ebx       ; Divides 1234 by 10.
        ; EDX =   4 = 1234 % 10  quotient
        ; EAX = 123 = 1234 / 10  remainder

Dans l'assemblage 16 bits, vous pouvez faire div bx Pour diviser un opérande 32 bits en EDX:EAX Par EBX. Voir Intels Architectures Software Developer’s Manuals pour plus d’informations.

Normalement, utilisez toujours xor edx,edx Avant de ne pas signer div pour étendre zéro EAX dans EDX: EAX. Voici comment vous effectuez la division "normale" 32 bits/32 bits => 32 bits.

Pour la division signée, utilisez cdq avant idiv à signe - étendez EAX dans EDX: EAX. Voir aussi Pourquoi EDX devrait être 0 avant d'utiliser l'instruction DIV? . Pour les autres tailles d'opérande, utilisez cbw (AL-> AX), cwd (AX-> DX: AX), cdq (EAX-> EDX: EAX), ou cqo (RAX-> RDX: RAX) pour régler la moitié supérieure sur 0 ou -1 en fonction du bit de signe de la moitié inférieure.

div/idiv sont disponibles dans des tailles d'opérande de 8, 16, 32 et (en mode 64 bits) 64 bits. La taille d'opérande 64 bits est beaucoup plus lente que 32 bits ou moins sur les processeurs Intel actuels, mais les processeurs AMD ne se soucient que de l'ampleur réelle des nombres, quelle que soit la taille de l'opérande.

Notez que la taille de l'opérande 8 bits est spéciale: les entrées/sorties implicites sont en AH: AL (aka AX), pas DL: AL. Voir 8086 Assembly on DOSBox: Bug with idiv instruction? pour un exemple.

Exemple de division 64 bits signé (nécessite le mode 64 bits)

   mov    rax,  0x8000000000000000   ; INT64_MIN = -9223372036854775808
   mov    ecx,  10           ; implicit zero-extension is fine for positive numbers

   cqo                       ; sign-extend into RDX, in this case = -1 = 0xFF...FF
   idiv   rcx
       ; quotient  = RAX = -922337203685477580 = 0xf333333333333334
       ; remainder = RDX = -8                  = 0xfffffffffffffff8

Limitations/erreurs courantes

div dword 10 N'est pas encodable en code machine (donc votre assembleur signalera une erreur sur les opérandes invalides).

Contrairement à mul/imul (où vous devriez normalement utiliser plus rapidement 2 opérandes imul r32, r/m32 Ou 3 opérandes imul r32, r/m32, imm8/32 À la place qui ne perdent pas de temps à écrire un résultat demi-haut), il n'y a pas d'opcode plus récent pour la division par une division immédiate, ou 32 bits/32 bits => 32 bits ou le reste sans l'entrée de dividende demi-haut.

La division est si lente et (espérons-le) rare qu'ils n'ont pas pris la peine d'ajouter un moyen pour vous permettre d'éviter EAX et EDX, ou d'utiliser directement un immédiat.


div et idiv seront défectueux si le quotient ne rentre pas dans un registre (AL/AX/EAX/RAX, la même largeur que le dividende). Cela inclut la division par zéro, mais cela se produira également avec un EDX non nul et un diviseur plus petit. C'est pourquoi les compilateurs C étendent simplement zéro ou étendent le signe au lieu de diviser une valeur 32 bits en DX: AX.

Et aussi pourquoi INT_MIN / -1 Est un comportement C non défini: il déborde le quotient signé sur les systèmes complémentaires de 2 comme x86. Voir Pourquoi la division entière par -1 (un négatif) entraîne-t-elle FPE? pour un exemple de x86 par rapport à ARM. x86 idiv fait en effet défaut dans ce cas.

L'exception x86 est #DE - exception de division. Sur les systèmes Unix/Linux, le noyau délivre un signal d'exception arithmétique SIGFPE aux processus qui provoquent une exception #DE. ( Sur quelles plateformes l'entier divise par zéro déclenche-t-il une exception à virgule flottante? )

Pour div, l'utilisation d'un dividende avec high_half < divisor Est sûre. par exemple. 0x11:23 / 0x12 Est inférieur à 0xff, Il tient donc dans un quotient de 8 bits.

La division de précision étendue d'un grand nombre par un petit nombre peut être implémentée en utilisant le reste d'un bloc comme le demi-dividende élevé (EDX) pour le bloc suivant. C'est probablement pourquoi ils ont choisi reste = EDX quotient = EAX au lieu de l'inverse.

66
user786653

Si vous calculez modulo une puissance de deux, l'utilisation ET binaire est plus simple et généralement plus rapide que la division. Si b est une puissance de deux, a % b == a & (b - 1).

Par exemple, prenons une valeur dans le registre EAX, modulo 64.
Le moyen le plus simple serait AND EAX, 63, car 63 est 111111 en binaire.

Les chiffres masqués et supérieurs ne nous intéressent pas. Essaye le!

De manière analogue, au lieu d'utiliser MUL ou DIV avec des puissances de deux, le décalage binaire est la voie à suivre. Attention aux entiers signés!

26
Andreiasw