Expressions assembleur
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.
Sommaire
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
où 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
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. |