Expressions assembleur

De RAD Studio
Aller à : navigation, rechercher

Remonter à Code assembleur inline - Index

L'assembleur intégré évalue toutes les expressions en valeurs entières 32 bits. Il ne supporte pas les valeurs à virgule flottante et les valeurs chaîne, à l'exception des constantes chaîne.

Les expressions sont construites à partir d'éléments et d'opérateurs d'expression ; chaque expression a une classe d'expression et un type d'expression associés.

Différences entre les expressions Delphi et assembleur

La différence la plus importante entre les expressions Delphi et celles de l'assembleur intégré est que toutes les expressions de l'assembleur doivent se résoudre en une valeur constante. En d'autres termes, elles doivent se résoudre en une valeur qui peut être calculée lors de la compilation. Par exemple, soit ces déclarations :

const
  X = 10;
  Y = 20;
var
  Z: Integer;

l'instruction suivante est valide :

asm
  MOV      Z,X+Y
end;

Comme X et Y sont des constantes, l'expression X + Y est juste un moyen commode d'écrire la constante 30 et l'instruction résultante place simplement la valeur 30 dans la variable Z. Mais si X et Y sont des variables

var
  X, Y: Integer;

l'assembleur intégré ne peut calculer la valeur de X + Y à la compilation. Dans ce cas, pour placer la somme de X et Y dans Z, vous devez utiliser

asm
  MOV          EAX,X
  ADD          EAX,Y
  MOV          Z,EAX
end;

Dans une expression Delphi, la référence à une variable indique le contenu de cette variable. Mais, dans une expression assembleur, la référence à une variable indique l'adresse de la variable. Dans Delphi, l'expression X + 4 (où X est une variable) correspond au contenu de X plus 4, alors que pour l'assembleur intégré cela désigne le contenu du mot situé à l'adresse située quatre octets après l'adresse de X. Donc, même si vous avez le droit d'écrire

asm
  MOV          EAX,X+4
end;

ce code ne charge pas la valeur de X plus 4 dans AX

asm
  MOV          EAX,X
  ADD          EAX,4
end;

Eléments d'expression

Les éléments composant une expression sont les constantes, les registres et les symboles.

Constantes numériques

Les constantes numériques doivent être des entiers et leurs valeurs doivent être comprises entre 2 147 483 648 et 4 294 967 295.

Par défaut, les constantes numériques utilisent la notation décimale, mais l'assembleur intégré supporte aussi les notations binaires, octales et hexadécimales. La notation binaire est sélectionnée en écrivant un B après le nombre, la notation octale en écrivant un O après le nombre et la notation hexadécimale en écrivant un H après le nombre ou un $ devant le nombre.

Les constantes numériques doivent commencer par un des chiffres compris entre 0 et 9 ou le caractère $. Par conséquent lorsque vous écrivez une constante hexadécimale en utilisant le suffixe H, un zéro supplémentaire devant le nombre est nécessaire si le premier chiffre significatif est un des chiffres allant de A à F. Par exemple, 0BAD4H et $BAD4 sont des constantes hexadécimales, mais BAD4H est un identificateur car il commence par une lettre.

Constantes chaîne

Les constantes chaîne doivent être délimitées par des apostrophes ou des guillemets. Deux apostrophes ou guillemets de même type que les marques de délimitation englobantes comptent pour un seul caractère. Voici des exemples de constantes chaîne :

'Z'
'Delphi'
'Windows'
"That's all folks"
'"That''s all folks," he said.'''
'100'
'"'
"'"

Les constantes chaîne de toute longueur sont autorisées dans les directives DB et provoquent l'allocation d'une séquence d'octets contenant les valeurs ASCII des caractères de la chaîne. Dans tous les autres cas, une constante chaîne ne peut pas dépasser quatre caractères, et désigne une valeur numérique qui peut participer à une expression. La valeur numérique d'une constante chaîne est calculée ainsi :

Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24

Ch1 est le caractère le plus à droite (le dernier) et Ch4 le plus à gauche (le premier). Si la chaîne est inférieure à quatre caractères, les caractères les plus à gauche sont pris pour zéro. Le tableau suivant présente des constantes chaîne et leurs valeurs numériques.

Exemples de chaînes et leurs valeurs :

Chaîne Valeur

'a'

00000061H

'ba'

00006261H

'cba'

00636261H

'dcba'

64636261H

'a '

00006120H

' a'

20202061H

'a' * 2

000000E2H

'a'-'A'

00000020H

not 'a'

FFFFFF9EH

Registres

Les symboles réservés suivants indiquent des registres CPU de l'assembleur inline :


Registres CPU

Catégorie

Identificateurs

Registres CPU 8 bits

AH, AL, BH, BL, CH, CL, DH, DL (registres à usage général) ;

Registres CPU 16 bits

AX, BX, CX, DX (registres à usage général) ; DI, SI, SP, BP (registres d'index) ; CS, DS, SS, ES (registres de segment) ; IP (pointeur d'instruction)

Registres CPU 32 bits

EAX, EBX, ECX, EDX (registres à usage général) ; EDI, ESI, ESP, EBP (registres d'index) ; FS, GS (registres de segment) ; EIP

FPU

ST(0), ..., ST(7)

Registres MMX FPU

mm0, ..., mm7

Registres XMM

xmm0, ..., xmm7 (..., xmm15 sur x64)

Registres Intel 64

RAX, RBX, ...


Registres à usage général x64 CPU, registres de données x86 FPU, et registres de données x64 SSE

X64 GPR.pngX86 FPU.pngX64 SSE.png

Quand un opérande consiste uniquement en un nom de registre, il est appelé opérande de registre. Tous les registres peuvent être utilisés comme opérandes de registre, certains registres peuvent être utilisés dans d'autres contextes.

Les registres de base (BX et BP) et les registres d'index (SI et DI) peuvent être écrits entre crochets pour indiquer l'indexation. Les combinaisons de registre de base/d'index valides sont [BX], [BP], [SI], [DI], [BX+SI], [BX+DI], [BP+SI] et [BP+DI]. Vous pouvez aussi indexer avec tous les registres 32 bits, par exemple [EAX+ECX], [ESP] et [ESP+EAX+5].

Les registres de segment (ES, CS, SS, DS, FS et GS) sont supportés, mais les segments ne sont normalement pas utiles dans les applications 32 bits.

Le symbole ST indique le registre le plus au-dessus sur la pile des registres à virgule flottante 8087. Chacun des huit registres à virgule flottante peut être désigné par ST(X), où X est une constante comprise entre 0 et 7 indiquant la distance depuis le haut de la pile des registres.

Symboles

L'assembleur intégré vous permet d'accéder à presque tous les identificateurs Delphi dans des expressions en langage assembleur, notamment les constantes, les types, les variables, les procédures et les fonctions. De plus, l'assembleur intégré implémente le symbole spécial @Result qui correspond à la variable Result dans le corps d'une fonction. Par exemple, la fonction :

function Sum(X, Y: Integer): Integer;
begin
  Result := X + Y;
end;

peut s'écrire en langage assembleur de la manière suivante :

function Sum(X, Y: Integer): Integer; stdcall;
begin
  asm
    MOV        EAX,X
    ADD        EAX,Y
    MOV        @Result,EAX
  end;
end;

Les symboles suivants ne peuvent pas être utilisés dans des instructions asm :

  • Procédures et fonctions standard (par exemple, Writeln et Chr).
  • Constantes chaîne, virgule flottante et ensemble (excepté lors du chargement des registres).
  • Libellés non déclarés dans le bloc en cours.
  • Le symbole @Result en dehors des fonctions.

Le tableau suivant résume les types de symbole utilisables dans les instructions asm.

Symboles reconnus par l'assembleur intégré :

Symbole Valeur Classe Type

Libellé

Adresse du libellé

Référence mémoire  

Taille de type

Constante

Valeur de constante

Valeur immédiate

0

Type

0

Référence mémoire

Taille de type

Champ

Déplacement de champ

Mémoire

Taille de type

Variable

Adresse de variable ou adresse d'un pointeur sur la variable

Référence mémoire

Taille de type

Procédure

Adresse de procédure

Référence mémoire

Taille de type

Fonction

Adresse de fonction

Référence mémoire

Taille de type

Unité

0

Valeur immédiate

0

@Result

Déplacement de la variable de résultat

Référence mémoire

Taille de type

Avec les optimisations désactivées, les variables locales (variables déclarées dans les procédures et fonctions) sont toujours allouées sur la pile et accessibles relativement à EBP, et la valeur d'un symbole variable locale est son offset (déplacement) signé depuis EBP. L'assembleur ajoute automatiquement [EBP] dans les références aux variables locales. Par exemple, soit la déclaration :

var Count: Integer;

dans une fonction ou une procédure, l'instruction :

MOV    EAX,Count

s'assemble dans MOV EAX,[EBP4].

L'assembleur intégré traite les paramètres var comme des pointeurs 32 bits et la taille d'un paramètre var est toujours 4. La syntaxe d'accès à un paramètre var est différente de la syntaxe d'accès à un paramètre valeur. Pour accéder au contenu d'un paramètre var, vous devez d'abord charger le pointeur 32 bits puis accéder à l'emplacement qu'il pointe. Par exemple :

function Sum(var X, Y: Integer): Integer; stdcall;
begin
  asm
    MOV                EAX,X
    MOV                EAX,[EAX]
    MOV                EDX,Y
    ADD                EAX,[EDX]
    MOV                @Result,EAX
  end;
end;

Les identificateurs peuvent être qualifiés dans les instructions asm. Par exemple, soit les déclarations :

type
  TPoint = record
    X, Y: Integer;
  end;
  TRect = record
    A, B: TPoint;
  end;
var
  P: TPoint;
  R: TRect;

les constructions suivantes peuvent être utilisées dans une instruction asm pour accéder aux champs :

MOV    EAX,P.X
MOV    EDX,P.Y
MOV    ECX,R.A.X
MOV    EBX,R.B.Y

Un identificateur de type peut être utilisé pour construire des variables à la volée. Chacune des instructions suivantes génère le même code machine, qui charge le contenu de [EDX] dans EAX.

MOV    EAX,(TRect PTR [EDX]).B.X
MOV    EAX,TRect([EDX]).B.X
MOV    EAX,TRect[EDX].B.X
MOV    EAX,[EDX].TRect.B.X

Classes d'expression

L'assembleur intégré divise les expressions en trois classes : registres, références mémoire et valeurs immédiates.

Une expression seulement constituée d'un nom de registre est une expression registre. Des exemples d'expressions registre sont AX, CL, DI et ES. Utilisées comme opérandes, les expressions registre dirigent l'assembleur pour générer des instructions qui opèrent sur les registres CPU.

Les expressions qui indiquent les emplacements mémoire sont appelées références mémoire. Les libellés, variables, constantes typées, procédures et fonctions de Delphi appartiennent à cette catégorie.

Les expressions qui ne sont pas des registres et ne sont pas associées aux emplacements mémoire sont des valeurs immédiates. Ce groupe inclut les constantes non typées et les identificateurs de type de Delphi.

Les valeurs immédiates et les références mémoire provoquent la génération d'un code différent quand elles sont utilisées comme opérandes. Par exemple :

const
  Start = 10;
var
  Count: Integer;
// …
asm
  MOV  EAX,Start       { MOV EAX,xxxx }
  MOV  EBX,Count       { MOV EBX,[xxxx] }
  MOV  ECX,[Start]     { MOV ECX,[xxxx] }
  MOV  EDX,OFFSET Count    { MOV EDX,xxxx }
end;

Comme Start est une valeur immédiate, le premier MOV est assemblé dans une instruction de déplacement immédiat. Le deuxième MOV, cependant, est traduit en une instruction mémoire de déplacement, comme Count est une référence mémoire. Dans le troisième MOV, les crochets convertissent Start en une référence mémoire (dans ce cas, le mot à l'offset 10 dans le segment de données). Dans le quatrième MOV, l'opérateur OFFSET convertit Count en une valeur immédiate (l'offset de Count dans le segment de données).

Les crochets et l'opérateur OFFSET se complémentent l'un l'autre. L'instruction asm suivante produit un code machine identique aux deux premières lignes de l'instruction asm précédente :

asm
  MOV      EAX,OFFSET [Start]
  MOV      EBX,[OFFSET Count]
end;

Les références mémoire et les valeurs immédiates sont classées comme relogeables ou absolues. Le relogement est le processus par lequel le lieur assigne des adresses absolues aux symboles. Une expression relogeable désigne une valeur qui nécessite un relogement lors de la liaison, tandis qu'une expression absolue désigne une valeur qui ne nécessite pas un tel relogement. Typiquement, les expressions faisant référence aux libellés, variables, procédures ou fonctions sont relogeables, car l'adresse finale de ces symboles est inconnue lors de la compilation. Les expressions qui opèrent seulement sur des constantes sont absolues.

L'assembleur intégré vous permet d'exécuter toute opération sur une valeur absolue, mais restreint les opérations sur les valeurs relogeables pour l'addition et la soustraction des constantes.

Types d'expression

Chaque expression de l'assembleur intégré a un type, ou plus précisément une taille, car l'assembleur considère le type d'une expression simplement comme la taille de son emplacement mémoire. Par exemple, le type d'une variable Integer est quatre, car elle occupe 4 octets. L'assembleur intégré effectue des vérifications de type à chaque fois que cela est possible, comme dans les instructions suivantes :

var
  QuitFlag: Boolean;
  OutBufPtr: Word;
// …
asm
  MOV      AL,QuitFlag
  MOV      BX,OutBufPtr
end;

l'assembleur vérifie que la taille de QuitFlag est un (un octet) et que la taille de OutBufPtr est deux (un mot). L'instruction

MOV        DL,OutBufPtr

produit une erreur car DL est un registre de taille octet et OutBufPtr est un mot. Le type d'une référence mémoire peut être changé via un transtypage

MOV        DL,BYTE PTR OutBufPtr
MOV        DL,Byte(OutBufPtr)
MOV        DL,OutBufPtr.Byte

Ces instructions MOV font toutes référence au premier octet (le moins significatif) de la variable OutBufPtr.

Dans certains cas, une référence mémoire est non typée. Un exemple est une valeur immédiate (Buffer) entourée de crochets :

procedure Example(var Buffer);
asm
   MOV AL,     [Buffer]
   MOV CX,     [Buffer]
   MOV EDX,   [Buffer]
end;

L'assembleur intégré autorise ces instructions, car l'expression [Buffer] n'a pas de type. [Buffer] signifie "le contenu de l'emplacement indiqué par Buffer" et le type peut être déterminé à partir du premier opérande (octet pour AL, mot pour CX et double-mot pour EDX).

Dans les cas où le type ne peut pas être déterminé à partir d'un autre opérande, l'assembleur intégré requiert un transtypage explicite. Par exemple :

INC     BYTE PTR [ECX]
IMUL    WORD PTR [EDX]

Le tableau suivant résume les symboles de types prédéfinis que l'assembleur intégré fournit en plus des types Delphi actuellement déclarés.

Symboles de types prédéfinis :

Symbole    Type   

BYTE

1

WORD

2

DWORD

4

QWORD

8

TBYTE

10

Opérateurs d'expression

L'assembleur intégré propose divers opérateurs. Les règles de priorité sont différentes de celles du langage Delphi. Par exemple, dans une instruction asm, AND a une priorité plus faible que les opérateurs d'addition et de soustraction. Le tableau suivant liste les opérateurs d'expression de l'assembleur intégré par ordre décroissant de priorité.

Priorité des opérateurs d'expression de l'assembleur intégré

Opérateurs Remarques Priorité

&

Maximale

(... ), [... ],., HIGH, LOW

+, -

+ et - unaires

:

OFFSET, TYPE, PTR, *, /, MOD, SHL, SHR, +, -   

+ et - binaires

NOT, AND, OR, XOR

Minimale


Le tableau suivant définit les opérateurs d'expression de l'assembleur intégré :

Définitions des opérateurs d'expression de l'assembleur intégré :

Opérateur Description

&

Redéfinition d'identificateur. L'identificateur suivant immédiatement cet opérateur est traité comme un symbole défini par l'utilisateur, même si l'orthographe est la même qu'un symbole réservé de l'assembleur intégré.

(... )

Sous-expression. Les expressions entre parenthèses sont évaluées complètement avant d'être traitées comme un élément d'expression simple. Une autre expression peut précéder l'expression entre parenthèses ; le résultat dans ce cas est la somme des valeurs des deux expressions, avec le type de la première expression.

[... ]

Référence mémoire. L'expression entre crochets est évaluée complètement avant d'être traitée comme un élément d'expression simple. Une autre expression peut précéder l'expression entre crochets ; le résultat dans ce cas est la somme des valeurs des deux expressions, avec le type de la première expression. Le résultat est toujours une référence mémoire.

.

Sélecteur de membre de structure. Le résultat est la somme de l'expression avant le point et de l'expression après le point, avec le type de l'expression après le point. L'accès aux symboles appartenant à la portée identifiée par l'expression avant le point peut s'effectuer dans l'expression après le point.

HIGH

Renvoie les huit bits de poids fort de l'expression mot suivant l'opérateur. L'expression doit être une valeur immédiate absolue.

LOW

Renvoie les huit bits de poids faible de l'expression mot suivant l'opérateur. L'expression doit être une valeur immédiate absolue.

+

Plus unaire. Renvoie l'expression suivant le plus sans changement. L'expression doit être une valeur immédiate absolue.

-

Moins unaire. Renvoie la valeur négative de l'expression suivant le moins. L'expression doit être une valeur immédiate absolue.

+

Addition. Les expressions peuvent être des valeurs immédiates ou des références mémoire, mais une seule des expressions peut être une valeur relogeable. Si l'une des expressions est une valeur relogeable, le résultat est aussi une valeur relogeable. Si l'une des expressions est une référence mémoire, le résultat est aussi une référence mémoire.

-

Soustraction. La première expression peut avoir n'importe quelle classe, mais la deuxième expression doit être une valeur immédiate absolue. Le résultat a la même classe que la première expression.

:

Redéfinition de segment. Instruit l'assembleur que l'expression après les deux-points appartient au segment donné par le nom de registre segment (CS, DS, SS, FS, GS ou ES) avant les deux-points. Le résultat est une référence mémoire avec la valeur de l'expression après les deux-points. Quand une redéfinition de segment est utilisée dans un opérande d'instruction, l'instruction est préfixée par une instruction préfixe de redéfinition de segment appropriée afin de garantir la sélection du segment indiqué.

OFFSET

Renvoie la partie offset (double mot) de l'expression suivant l'opérateur. Le résultat est une valeur immédiate.

TYPE

Renvoie le type (taille en octets) de l'expression suivant l'opérateur. Le type d'une valeur immédiate est 0.

PTR

Opérateur de transtypage. Le résultat est une référence mémoire avec la valeur de l'expression suivant l'opérateur et le type de l'expression devant l'opérateur.

*

Multiplication. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.

/

Division entière. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.

MOD

Reste après la division entière. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.

SHL

Décalage gauche logique. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.

SHR

Décalage droite logique. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.

NOT

Négation bit-à-bit. L'expression doit être une valeur immédiate absolue et le résultat est une valeur immédiate absolue.

AND

AND bit-à-bit. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.

OR

OR bit-à-bit. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.

XOR

OR exclusif bit-à-bit. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.

Voir aussi