Section courante

A propos

Section administrative du site

L'assembleur intégré

L'assembleur intégré de Turbo Pascal permet d'écrire du code assembleur 8086/8087 et 80286/80287 directement dans vos programmes Pascal. Bien sûr, vous pouvez toujours convertir manuellement les instructions assembleur en code machine pour les utiliser dans des instructions INLINE, ou créer un lien dans des fichiers .OBJ contenant des procédures et des fonctions externes lorsque vous souhaitez mélanger Pascal et assembleur. L'assembleur intégré met en oeuvre un grand sous-ensemble de la syntaxe prise en charge par Turbo Assembler et le Macro Assembler de Microsoft. L'assembleur intégré prend en charge tous les opcodes 8086/8087 et 80286/80287, et tous les opérateurs d'expression de Turbo Assembler sauf quelques-uns.

À l'exception de DB, DW et DD (définissant l'octet, le mot et le double mot), aucune des directives de Turbo Assembler, telles que EQU, PROC, STRUC, SEGMENT et MACRO, n'est prise en charge par l'assembleur intégré. Les opérations est mise en oeuvre via les directives Turbo Assembler, cependant, correspondent en grande partie aux constructions Turbo Pascal correspondantes. Par exemple, la plupart des directives EQU correspondent aux déclarations CONST, VAR et TYPE dans Turbo Pascal, la directive PROC correspond aux déclarations de procédure et de fonction et la directive STRUC correspond aux types d'enregistrement Turbo Pascal. En fait, l'assembleur intégré de Turbo Pascal peut être considéré comme un compilateur de langage assembleur utilisant la syntaxe Pascal pour toutes les déclarations.

L'instruction ASM

L'assembleur intégré est accessible via des instructions ASM. Voici la syntaxe d'une instruction ASM :

ASM AsmStatement [ Separator AsmStatement ] END

Le paramètre AsmStatement est une instruction assembleur et Separator est un point-virgule, une nouvelle ligne ou un commentaire Pascal. Plusieurs instructions d'assembleur peuvent être placées sur une seule ligne si elles sont séparées par des points-virgules. Un point-virgule n'est pas requis entre deux instructions assembleur si les instructions sont sur des lignes séparées. Un point-virgule n'indique pas que le reste de la ligne est un commentaire. Les commentaires doivent être écrits dans le style Pascal en utilisant «{» et «}» ou «(*» et «*)».

Utilisation de registres

En général, les règles d'utilisation du registre dans une instruction ASM sont les mêmes que celles d'une procédure ou fonction externe. Une instruction ASM doit conserver les registres BP, SP, SS et DS, mais peut modifier librement les registres AX, BX, CX, DX, SI, DI, ES et Flags. À l'entrée d'une instruction ASM, BP pointe vers la trame de pile actuelle, SP pointe vers le haut de la pile, SS contient l'adresse de segment du segment de pile et DS contient l'adresse de segment du segment de données. À l'exception de BP, SP, SS et DS, une instruction ASM ne peut rien supposer sur le contenu du registre à l'entrée de l'instruction.

Syntaxe de l'instruction de l'assembleur

Voici la syntaxe d'une instruction assembleur :

[ Label ":" ] <Prefix> [ Opcode [ Operand < "," Operand> ] ]

Le paramètre Label est un identificateur d'étiquette, le paramètre Prefix est un opcode de préfixe assembleur (code d'opération), le paramètre Opcode est un opcode ou directive d'instruction assembleur et le paramètre Operand est une expression assembleur. Les commentaires sont autorisés entre les instructions de l'assembleur, mais pas dans celles-ci. Par exemple, ceci est autorisé :

  1. ASM
  2.  MOV AX,1   { Valeur initiale }
  3.  MOV CX,100 { Compteur }
  4. END;

mais celle-ci est une erreur :

  1. ASM
  2.  MOV { Valeur initiale } AX,1;
  3.  MOV CX, { compteur } 100
  4. END;

Étiquettes

Les étiquettes sont définies dans l'assembleur comme elles le sont en Pascal - en écrivant un identificateur d'étiquette et un signe deux-points avant une instruction. Et comme ils le sont en Pascal, les étiquettes définies dans l'assembleur doivent être déclarées dans une partie de déclaration d'étiquette dans le bloc contenant l'instruction ASM. Il existe une exception à cette règle : les étiquettes locales. Les étiquettes locales sont des étiquettes commençant par un signe arobase (@). Étant donné qu'un signe arobase ne peut pas faire partie d'un identificateur Pascal, ces étiquettes locales sont automatiquement restreintes à être utilisées dans les instructions ASM. Une étiquette locale n'est connue que dans l'instruction ASM la définissant (c'est-à-dire que la portée d'une étiquette locale s'étend du mot-clef ASM au mot-clef END de l'instruction ASM la contenant). Contrairement à une étiquette normale, une étiquette locale n'a pas besoin d'être déclarée dans une partie de déclaration d'étiquette avant d'être utilisée. La composition exacte d'un identifiant d'étiquette locale est un signe arobase (@) suivi d'une ou plusieurs lettres (A..Z), chiffres (0..9), traits de soulignement (_) ou arobases. Comme pour toutes les étiquettes, l'identificateur est suivi de deux points (:).

Opcodes d'instruction

L'assembleur intégré prend en charge tous les opcodes d'instructions 8086/8087 et 80286/80287. Les opcodes 8087 sont disponibles uniquement dans l'état {$N+} (coprocesseur numérique activé), les opcodes 80286 ne sont disponibles que dans l'état {$G+} (génération de code 80286 activée) et les opcodes 80287 ne sont disponibles que dans l'état {$G+,N+}.

L'opcode d'instruction RET génère une instruction de code machine de retour proche ou de retour éloigné en fonction du modèle d'appel de la procédure ou de la fonction en cours.

  1. Procedure NearProc;Near;Begin
  2.  ASM
  3.   RET { Génération d'un retour court }
  4.  END;
  5. End;
  6.  
  7. Procedure FarProc;Far;Begin
  8.  ASM
  9.   RET { Génération d'un retour long }
  10.  END;
  11. End;

En revanche, les instructions RETN et RETF génèrent toujours un retour court (NEAR) et un retour long (FAR), quel que soit le modèle d'appel de la procédure ou fonction courante. Sauf indication contraire, l'assembleur intégré optimise les instructions de saut en sélectionnant automatiquement la forme la plus courte et donc la plus efficace d'une instruction de saut. Ce dimensionnement automatique du saut s'applique à l'instruction de saut inconditionnel JMP), et à toutes les instructions de saut conditionnel, lorsque la cible est une étiquette (et non une procédure ou une fonction). Pour une instruction de saut inconditionnel JMP), l'assembleur intégré génère un saut court (opcode d'un octet suivi d'un déplacement d'un octet) si la distance à l'étiquette cible est comprise entre -128 et 127 octets; sinon, un saut à proximité (opcode d'un octet suivi d'un déplacement de deux octets) est généré. Pour une instruction de saut conditionnel, un saut court (opcode de 1 octet suivi d'un déplacement de 1 octet) est généré si la distance à l'étiquette cible est comprise entre -128 et 127 octets; sinon, l'assembleur intégré génère un saut court avec la condition inverse, sautant par-dessus un saut à proximité de l'étiquette cible (5 octets au total). Par exemple, l'instruction assembleur :

JC @Stop

@Stop n'est pas à portée d'un saut court est converti en une séquence de code machine correspondant à ceci :

JNC @Skip
JMP @Stop
@Skip:

Les sauts vers les points d'entrée des procédures et des fonctions sont toujours court ou long, mais jamais courts, et les sauts conditionnels vers les procédures et les fonctions ne sont pas autorisés. Vous pouvez forcer l'assembleur intégré à générer un saut proche inconditionnel ou un saut lointain vers une étiquette à l'aide d'une construction NEAR PTR ou FAR PTR. Par exemple, les instructions assembleur :

JMP NEAR PTR @Stop
JMP FAR PTR @Stop

génère toujours un saut proche et un saut long, respectivement, même si @Stop est une étiquette à portée d'un saut court.

Directives de l'assembleur

L'assembleur intégré de Turbo Pascal prend en charge trois directives d'assembleur : DB (définir l'octet), DW (définir le mot) et DD (définir le mot double). Ils génèrent chacun des données correspondant aux opérandes séparés par des virgules suivant la directive. La directive DB génère une séquence d'octets. Chaque opérande peut être une expression constante avec une valeur comprise entre -128 et 255, ou une chaîne de caractères de n'importe quelle longueur. Les expressions constantes génèrent un octet de code et les chaînes de caractères génèrent une séquence d'octets avec des valeurs correspondant au code ASCII de chaque caractère. La directive DW génère une séquence de mots. Chaque opérande peut être une expression constante avec une valeur comprise entre -32 768 et 65 535, ou une expression d'adresse. Pour une expression d'adresse, l'assembleur intégré génère un pointeur proche, c'est-à-dire un mot qui contient la partie de déplacement de l'adresse. La directive DD génère une séquence de mots doubles. Chaque opérande peut être une expression constante avec une valeur comprise entre -2 147 483 648 et 4 294 967 295, ou une expression d'adresse. Pour une expression d'adresse, l'assembleur intégré génère un pointeur FAR, c'est-à-dire un mot contenant la partie de déplacement (OFFSET) de l'adresse, suivi d'un mot contenant la partie segment de l'adresse. Les données générées par les directives DB, DW et DD sont toujours entreposées dans le segment de code, tout comme le code généré par d'autres instructions assembleur intégrées. Pour générer des données non initialisées ou initialisées dans le segment de données, vous devez utiliser des déclarations Pascal VAR ou CONST. Voici quelques exemples de directives DB, DW et DD :

  1. Var
  2.  MaVar:Byte;
  3.  
  4. Procedure MaProc;Begin
  5. End;
  6.  
  7. BEGIN
  8.  ASM
  9.   DB 0FFh                            { Un octet }
  10.   DB 0,77                            { Deux octets }
  11.   DB 'G'                             { Ord( 'G') }
  12.   DB 'Bonjour Gladir.com !' ,0DH,0AH { Chaine de caractères suivi de CR/LF }
  13.   DB 15, "Sylvain Maltais"           { Chaine de caractères de style Pascal }
  14.   DW 0FFFFh                          { Un mot }
  15.   DW 0,777                           { Deux mots }
  16.   DW 'G'                             { Même chose que DB 'G',0 }
  17.   DW 'GL'                            { Même chose que DB 'L','G' }
  18.   DW MaVar                           { Déplacemnt de MyVar }
  19.   DW MaProc                          { Déplacement de MyProc }
  20.   DD 0FFFFFFFFh                      { Un double mot }
  21.   DD 0,999999999                     { Deux double mots }
  22.   DD 'G'                             { Même chose que DB 'G' ,0,0,0 }
  23.   DD 'GLAD'                          { Même chose que DB 'D', 'A', 'L', 'G' }
  24.   DD MaVar                           { Pointeur vers MyVar }
  25.   DD MaProc                          { Pointeur vers MyProc }
  26.  END;
  27. END.

Dans Turbo Assembler, lorsqu'un identificateur précède une directive DB, DW ou DD, il provoque la déclaration d'une variable de taille d'octet, de mot ou de mot double à l'emplacement de la directive. Par exemple, le Turbo Assembler permet ce qui suit :

  1. ByteVar DB  ?
  2. WordVar DW  ?
  3.          ; :
  4.         MOV AL,ByteVar
  5.         MOV BX,WordVar 

L'assembleur intégré ne prend pas en charge de telles déclarations de variables. Dans Turbo Pascal, le seul type de symbole pouvant être défini dans une instruction d'assembleur intégrée est une étiquette. Toutes les variables doivent être déclarées en utilisant la syntaxe Pascal, et la construction précédente correspond à ceci :

  1. Program Samples;
  2.  
  3. var
  4.  ByteVar:Byte;
  5.  WordVar:Word;
  6.  
  7. Procedure A;Begin
  8.  ASM
  9.   MOV AL,ByteVar
  10.   MOV BX,WordVar
  11.  END;
  12. End;
  13.  
  14. BEGIN
  15. END.

Opérandes

Les opérandes assembleurs intégrés sont des expressions constituées d'une combinaison de constantes, de registres, de symboles et d'opérateurs. Bien que les expressions assembleur intégrées soient construites en utilisant les mêmes principes de base que les expressions Pascal, il existe un certain nombre de différences importantes. Dans les opérandes, les mots réservés suivants ont une signification prédéfinie pour l'assembleur intégré :

Les mots réservés ont toujours la priorité sur les identificateurs définis par l'utilisateur. Par exemple, le fragment de code :

  1. Var
  2.  ch:Char;
  3. BEGIN
  4.  ASM
  5.   MOV CH,1
  6.  END;
  7. END.

charge 1 dans le registre CH, pas dans la variable CH. Pour accéder à un symbole défini par l'utilisateur avec le même nom qu'un mot réservé, vous devez utiliser l'opérateur de remplacement de l'identificateur esperluette (&) :

  1. Var
  2.  ch:Char;
  3. BEGIN
  4.  ASM
  5.   MOV &ch,1
  6.  END;
  7. END.

Il est fortement recommandé d'éviter les identificateurs définis par l'utilisateur avec les mêmes noms que les mots réservés à l'assembleur intégré, car une telle confusion de nom peut facilement conduire à des bogues obscurs et difficiles à trouver.

Expressions

L'assembleur intégré évalue toutes les expressions comme des valeurs entières 32 bits; il ne prend pas en charge les valeurs à virgule flottante et chaîne de caractères, à l'exception des constantes de chaîne de caractères. Les expressions assembleur intégrées sont construites à partir d'éléments d'expression et d'opérateurs, et chaque expression a une classe d'expression et un type d'expression associés.

Différences entre les expressions Pascal et Assembler

La différence la plus importante entre les expressions Pascal et les expressions assembleur intégrées est que toutes les expressions assembleur intégrées doivent être résolues en une valeur constante, une valeur pouvant être calculée au moment de la compilation. Par exemple, compte tenu de ces déclarations :

  1. Const
  2.  X = 10;
  3.  Y = 20;
  4. Var
  5.  Z:Integer;

ce qui suit est une instruction d'assembleur intégrée valide :

  1. ASM
  2.  MOV Z,X+Y
  3. END;

Comme les constantes X et Y sont toutes deux, l'expression X+Y est simplement un moyen plus pratique d'écrire la constante 30, et l'instruction résultante devient un déplacement immédiat de la valeur 30 dans la variable Z de la taille d'un mot. Mais si vous changez X et Y en variables :

  1. Var
  2.  X,Y:Integer;

l'assembleur intégré ne peut plus calculer la valeur de X + Y au moment de la compilation. La construction d'assembleur intégrée correcte pour déplacer la somme de X et Y dans Z est la suivante :

  1. ASM
  2.  MOV AX,X
  3.  ADD AX,Y
  4.  MOV Z,AX
  5. END;

Une autre différence importante entre Pascal et les expressions assembleur intégrées est la façon dont les variables sont interprétées. Dans une expression Pascal, une référence à une variable est interprétée comme le contenu de la variable, mais dans une expression assembleur intégrée, une référence de variable désigne l'adresse de la variable. Par exemple, en Pascal, l'expression X + 4, où X est une variable, signifie le contenu de X plus 4, alors que dans l'assembleur intégré, cela signifie le contenu du mot à une adresse quatre octets supérieure à l'adresse de X. Donc, même si tu as le droit d'écrire :

  1. ASM
  2.  MOV AX,X+4
  3. END;

le code ne charge pas la valeur de X plus 4 dans AX, mais il charge plutôt la valeur d'un mot entreposé quatre octets au-delà de X. La bonne façon d'ajouter 4 au contenu de X est :

  1. ASM
  2.  MOV AX,X
  3.  ADD AX,4
  4. END;

Éléments d'expression

Les éléments de base d'une expression sont les constantes, les registres et les symboles. L'assembleur intégré prend en charge deux types de constantes : les constantes numériques et les constantes de chaîne de caractères.

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 (base 10), mais l'assembleur intégré prend également en charge les notations binaire (base 2), octale (base 8) et hexadécimale (base 16). La notation binaire est sélectionnée en écrivant un B après le nombre, la notation octale est sélectionnée en écrivant une lettre 0 après le nombre et la notation hexadécimale est sélectionnée en écrivant un H après le nombre ou un $ avant le nombre. Les suffixes B, O et H ne sont pas pris en charge dans les expressions Pascal. Les expressions Pascal n'autorisent que la notation décimale (par défaut) et la notation hexadécimale (en utilisant un préfixe $). Les constantes numériques doivent commencer par l'un des chiffres de 0 à 9 ou un 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 requis si le premier chiffre significatif est l'un des chiffres hexadécimaux A à F. Par exemple, 0BAD4H et $BAD4 sont des constantes hexadécimales, mais BAP4H est un identifiant car il commence par une lettre et non par un chiffre.

Constantes de chaîne de caractères

Les constantes de chaîne de caractères doivent être placées entre guillemets simples ou doubles. Deux guillemets consécutifs du même type que les guillemets englobants comptent pour un seul caractère. Voici quelques exemples de constantes de chaîne de caractères :

'G'
'Gladir.com'
"C'est un exemple"
'"C"est un exemple," comme vous pouvez voir.'
'777'

Remarquez dans la quatrième chaîne de caractères l'utilisation de deux guillemets simples consécutifs pour désigner un caractère guillemet simple. Les constantes de chaîne de caractères de n'importe quelle 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 de caractères. Dans tous les autres cas, une constante de chaîne de caractères ne peut pas dépasser quatre caractères et désigne une valeur numérique pouvant participer à une expression. La valeur numérique d'une constante de chaîne de caractères est calculée comme suit :

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

Ch1 est le (dernier) caractère le plus à droite et Ch4 est le (premier) caractère le plus à gauche. Si la chaîne de caractères est plus courte que quatre caractères, le(s) premier(s) caractère(s) à gauche sont supposés être zéro. Voici quelques exemples de constantes de chaîne de caractères et de leurs valeurs numériques correspondantes :

String Valeur
'a' 00000061H
'ba' 00006261H
'eba' 00636261H
'deba' 64636261H
'a' 00006120H
'   a' 20202061H
'a'*2 000000E2H
'a'-'A' 00000020H
not 'a' FFFFFF9EH

Les symboles réservés suivants désignent les registres de microprocesseur :

Description Registres cibles
Usage général 16 bits AX BX CX DX
Bas registres 8 bits AL BL CL DL
Haut registres 8 bits AH BH CH DH
Pointeur ou index 16 bits SP BP SI DI
Registres de segment 16 bits CS DS SS ES
Pile de registre 8087 ST

Lorsqu'un opérande se compose uniquement d'un nom de registre, on l'appelle un opérande de registre. Tous les registres peuvent être utilisés comme opérandes de registre. De plus, 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 registres base / index valides sont [BX], [BP], [SI], [DI], [BX + SI], [BX + DI], [BP + SI] et [BP + DI]. Les registres de segment (ES, CS, SS et DS) peuvent être utilisés conjointement avec l'opérateur de remplacement de segment deux-points (:) pour indiquer un segment différent de celui que le processeur sélectionne par défaut. Le symbole ST désigne le registre le plus haut sur la pile de registres à virgule flottante 8087. Chacun des 8 registres à virgule flottante peut être désigné en utilisant ST(x), où x est une constante entre 0 et 7 indiquant la distance depuis le haut de la pile de registres.

Les symboles

L'assembleur intégré vous permet d'accéder à presque tous les symboles Pascal dans les expressions d'assembleur, y compris les étiquettes, les constantes, les types, les variables, les procédures et les fonctions. De plus, l'assembleur intégré met en oeuvre les symboles spéciaux suivants :

Symbole Description
@Code Segment de code
@Data Segment de données
@Result Résultat d'une fonction

Les symboles @Code et @Data représentent le code actuel et les segments de données. Ils ne doivent être utilisés qu'en conjonction avec l'opérateur SEG :

  1. ASM
  2.  MOV AX,SEG @Data
  3.  MOV DS,AX
  4. END;

Le symbole @Result représente la variable de résultat de la fonction dans la partie instruction d'une fonction. Par exemple, dans cette fonction :

  1. Function Sum(X,Y:Integer):Integer;Begin
  2.  Sum := X + Y;
  3. End;

l'instruction attribuant une valeur de résultat de fonction à Sum utiliserait la variable @Result si elle était écrite dans l'assembleur intégré :

  1. Function Sum(X,Y:Integer):Integer;Begin
  2.  ASM
  3.   MOV AX,X
  4.   ADD AX,Y
  5.   MOV @Result,AX
  6.  END;
  7. End;

Les symboles suivants ne peuvent pas être utilisés dans les expressions assembleur intégrées :

Le tableau suivant résume la valeur, la classe et le type des différents types de symboles pouvant être utilisés dans les expressions assembleur intégrées :

Symbole Valeur Classe Type
Étiquette Adresse de l'étiquette Mémoire SHORT
Constante Valeur de la constante Immédiate 0
Type 0 Mémoire Taille du type
Champ Déplacement du champ Mémoire Taille du type
Variable Adresse de variable Mémoire Taille du type
Procédure Adresse de la procédure Mémoire NEAR ou FAR
Fonction Adresse de la fonction Mémoire NEAR ou FAR
Unité 0 Immédiate 0
@Code Adresse de segment de code Mémoire 0FFF0H
@Data Adresse du segment de données Mémoire 0FFF0H
@Result Résultat de déplacement var Mémoire Taille du type

Les variables locales (variables déclarées dans les procédures et les fonctions) sont toujours allouées sur la pile et accédées par rapport à SS:BP, et la valeur d'un symbole de variable locale est son offset signé par rapport à SS:BP. L'assembleur ajoute automatiquement [BP] dans les références aux variables locales. Par exemple, compte tenu de ces déclarations :

  1. Procedure Test;
  2. Var
  3.  Count:Integer;

l'instruction :

  1. ASM
  2.  MOV AX,Count
  3. END;

s'assemble dans «MOV AX, [BP-2]». L'assembleur intégré traite toujours un paramètre var comme un pointeur 32 bits et la taille d'un paramètre var est toujours 4 (la taille d'un pointeur 32 bits). En Turbo Pascal, la syntaxe pour accéder à un paramètre var et à un paramètre value est la même - ce n'est pas le cas dans le code que vous écrivez pour l'assembleur intégré. Comme les paramètres var sont vraiment des pointeurs, vous devez les traiter comme tels. Ainsi, pour accéder au contenu d'un paramètre var, vous devez d'abord charger le pointeur 32 bits, puis accéder à l'emplacement vers lequel il pointe. Par exemple, si les paramètres X et Y de la fonction ci-dessus Sum étaient des paramètres var, le code ressemblerait à ceci :

  1. Function Sum(Var X,Y:Integer):Integer;Begin
  2.  ASM
  3.   LES BX,X
  4.   MOV AX,ES:[BX]
  5.   LES BX,Y
  6.   ADD AX,ES:[BX]
  7.   MOV @Result,AX
  8.  END;
  9. End;

Certains symboles, tels que les types d'enregistrement et les variables, ont une portée accessible à l'aide de l'opérateur de sélection de membre de structure par point (.). Par exemple, compte tenu de ces déclarations :

  1. Type
  2.  TPoint = Record
  3.   X, Y: Integer; 
  4.  End; 
  5.  TRect = Record
  6.   A, B: TPoint;
  7.  End;
  8. Var
  9.  P: TPoint;
  10.  R: TRect;

les constructions suivantes peuvent être utilisées pour accéder aux champs des variables P et R :

  1. ASM
  2.  MOV AX,P.X
  3.  MOV DX,P.Y
  4.  MOV CX,R.A.X
  5.  MOV BX,R.B.Y
  6. END;

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, chargeant le contenu de ES:[DI+4] dans AX :

  1. ASM
  2.  MOV AX,(TRect PTR ES:[DI]).B.X
  3.  MOV AX,TRect(ES:[DI]).B.X
  4.  MOV AX,ES:TRect[DI].B.X
  5.  MOV AX,TRect[ES:DI].B.X
  6.  MOV AX,ES:[DI].TRect.B.X
  7. END;

Une portée est fournie par type, champ et symboles de variable d'un type d'enregistrement ou d'objet. De plus, un identifiant d'unité ouvre la portée d'une unité particulière, tout comme un identifiant pleinement qualifié en Pascal.

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 consistant uniquement en un nom de registre est une expression de registre. Des exemples d'expressions de registre sont AX, CL, DI et ES. Utilisées comme opérandes, les expressions de registre dirigent l'assembleur pour générer des instructions opérant sur les registres du microprocesseur. Les expressions indiquant des emplacements de mémoire sont des références de mémoire; Les étiquettes, variables, constantes typées, procédures et fonctions de Pascal appartiennent à cette catégorie. Les expressions n'étant pas des registres et n'étant pas associées à des emplacements de mémoire sont des valeurs immédiates; ce groupe comprend les constantes non typées de Pascal et les identificateurs de type. Les valeurs immédiates et les références de mémoire provoquent la génération de code différent lorsqu'ils sont utilisés comme opérandes. Par exemple :

  1. Const
  2.  Start = 10;
  3. Var
  4.  Count: Integer;
  5. ASM
  6. MOV AX,Start        { MOV AX,xxxx }
  7. MOV BX,Count        { MOV BX,[xxxx] }
  8. MOV CX,[Start]      { MOV CX,[xxxx] }
  9. MOV DX,OFFSET Count { MOV DX,xxxx }
  10. END;

Étant donné que Start est une valeur immédiate, le premier MOV est assemblé en une instruction de déplacement immédiat. Le deuxième MOV, cependant, est traduit en une instruction de mémoire de mouvement, car Count est une référence de mémoire. Dans le troisième MOV, l'opérateur de crochets est utilisé pour convertir Start en une référence mémoire (dans ce cas, le mot du déplacement 10 dans le segment de données), et dans le quatrième MOV, l'opérateur OFFSET est utilisé pour convertir Count en un valeur immédiate (le déplacement de Count dans le segment de données). Comme vous pouvez le voir, les crochets et les opérateurs OFFSET se complètent. En termes de code machine résultant, l'instruction ASM suivante est identique aux deux premières lignes de l'instruction ASM précédente :

  1. ASM
  2.  MOV AX,OFFSET [Start]
  3.  MOV BX,[OFFSET Count]
  4. END;

Les références de mémoire et les valeurs immédiates sont en outre classées en tant qu'expressions déplaçables ou expressions absolues. Une expression réadressable désigne une valeur nécessitant une relocalisation au moment de la liaison, et une expression absolue dénote une valeur ne nécessitant pas une telle relocalisation. En règle générale, une expression faisant référence à une étiquette, une variable, une procédure ou une fonction peut être déplacée, et une expression fonctionnant uniquement sur des constantes est absolue. La relocalisation est le processus par lequel l'éditeur de liens attribue des adresses absolues aux symboles. Au moment de la compilation, le compilateur ne connaît pas l'adresse finale d'une étiquette, d'une variable, d'une procédure ou d'une fonction; il n'est connu qu'au moment de la liaison, lorsque l'éditeur de liens attribue une adresse absolue spécifique au symbole. L'assembleur intégré vous permet d'effectuer n'importe quelle opération sur une valeur absolue, mais il limite les opérations sur les valeurs déplaçables à l'addition et à la soustraction de constantes.

Types d'expressions

Chaque expression d'assembleur intégrée a un type associé - ou plus correctement, une taille associée, car l'assembleur intégré considère le type d'une expression simplement comme la taille de son emplacement mémoire. Par exemple, le type (taille) d'une variable Integer est égal à deux, car elle occupe 2 octets. L'assembleur intégré effectue la vérification de type chaque fois que possible, donc dans les instructions :

  1. Var
  2.  QuitFlag:Boolean;
  3.  OutBufPtr:Word;
  4. ASM
  5.  MOV AL,QuitFlag
  6.  MOV BX,OutBufPtr
  7. END;

l'assembleur intégré vérifie que la taille de QuitFlag est de un (un octet) et que la taille d'OutBufPtr est de deux (un mot). Une erreur se produit si la vérification de type échoue. Par exemple, ce n'est pas autorisé :

  1. ASM
  2.  MOV DL,OutBufPtr
  3. END;

Le problème est que DL est un registre de la taille d'un octet et OutBufPtr est un mot. Le type d'une référence mémoire peut être modifié via un transtypage; ce sont des manières correctes d'écrire l'instruction précédente :

  1. ASM
  2.  MOV DL,BYTE PTR OutBufPtr
  3.  MOV DL, Byte (OutBufPtr)
  4.  MOV DL,OutBufPtr.Byte
  5. END;

faisant tous référence au premier octet (le moins significatif) de la variable OutBufPtr. Dans certains cas, une référence mémoire n'est pas typée, c'est-à-dire qu'elle n'a pas de type associé. Un exemple est une valeur immédiate entre crochets :

  1. ASM
  2.  MOV AL,[100H]
  3.  MOV BX,[100H]
  4. END;

L'assembleur intégré autorise ces deux instructions, car l'expression [100H] n'a pas de type associé - cela signifie simplement que le contenu de l'adresse 100H dans le segment de données et le type peut être déterminé à partir du premier opérande (octet pour AL, mot pour BX). Dans les cas où le type ne peut pas être déterminé à partir d'un autre opérande, l'assembleur intégré nécessite un typage explicite :

  1. ASM
  2.  INC BYTE PTR [100H]
  3.  IMUL WORD PTR [100H]
  4. END;

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

Symbole Type
BYTE 1
WORD 2
DWORD 4
QWORD 8
TBYTE 10
NEAR 0FFFEH
FAR 0FFFFH

Remarquez en particulier les pseudotypes NEAR et FAR, étant utilisés par les symboles de procédure et de fonction pour indiquer leur modèle d'appel. Vous pouvez utiliser NEAR et FAR dans les type de castres, tout comme les autres symboles. Par exemple, si FarProc est une procédure FAR :

  1. Procedure FarProc; Far;

et si vous écrivez du code assembleur intégré dans le même module que FarProc, vous pouvez utiliser l'instruction d'appel NEAR plus efficace pour l'appeler :

  1. ASM
  2.  PUSH CS
  3.  CALL NEAR PTR FarProc
  4. END;

Opérateurs d'expression

L'assembleur intégré fournit une variété d'opérateurs, divisés en 12 classes de priorité. Le tableau suivant répertorie les opérateurs d'expression de l'assembleur intégré par ordre décroissant de priorité :

Opérateurs Commentaire
& Opérateur de remplacement d'identificateur
(), [], . Sélecteur de membres de structure
HIGH, LOW Maximum, minimum
+, - Opérateurs unaires
: Opérateur de remplacement de segment
OFFSET, SEG, TYPE, PTR, *, /, MOD, SHL, SHR Déplacement
+, - Opérateurs binaires d'addition / soustraction
NOT, AND, OR, XOR Opérateurs au niveau du bit

Voici la définitions des opérateurs d'expression assembleur intégrés :

Opérateur Description
& Remplacement d'identificateur. L'identificateur suivant immédiatement l'esperluette est traité comme un symbole défini par l'utilisateur, même si l'orthographe est identique à celle d'un symbole réservé d'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 unique. Une autre expression peut éventuellement précéder l'expression entre parenthèses; le résultat dans ce cas devient 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 unique. L'expression entre crochets peut être combinée avec les registres BX, BP, SI ou DI à l'aide de l'opérateur plus (+), pour indiquer l'indexation des registres du microprocesseur. Une autre expression peut éventuellement précéder l'expression entre crochets; le résultat dans ce cas devient 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 membres 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. Les symboles appartenant à la portée identifiée par l'expression avant le point sont accessibles dans l'expression après le point.
HIGH Renvoie les 8 bits de poids fort de l'expression de la taille d'un mot après l'opérateur. L'expression doit être une valeur immédiate absolue.
LOW Renvoie les 8 bits de poids faible de l'expression de la taille d'un mot après l'opérateur. L'expression doit être une valeur immédiate absolue.
+ Unaire plus. 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 après le signe moins. L'expression doit être une valeur immédiate absolue.
: Remplacement de segment. Indique à l'assembleur que l'expression après les deux-points appartient au segment donné par le nom du registre de segment (CS, DS, SS 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. Lorsqu'un remplacement de segment est utilisé dans un opérande d'instruction, l'instruction sera précédée d'une instruction de préfixe de remplacement de segment appropriée pour garantir que le segment indiqué est sélectionné.
OFFSET Renvoie la partie de déplacement (mot de poids faible) de l'expression après l'opérateur. Le résultat est une valeur immédiate.
SEG Renvoie la partie de segment (mot de poids fort) de l'expression après 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 type castre. Le résultat est une référence mémoire avec la valeur de l'expression après 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 Restant 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 logique à gauche. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.
SHR Décalage logique à droite. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.
+ Une 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 repositionnable. Si l'une des expressions est une valeur repositionnable, le résultat est également une valeur repositionnable. Si l'une des expressions est une référence mémoire, le résultat est également 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.
NOT Négation au niveau du bit. L'expression doit être une valeur immédiate absolue et le résultat est une valeur immédiate absolue.
AND ET au niveau du bit. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.
OR OU au niveau du bit. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.
XOR OU EXCLUSIF au niveau du bit. Les deux expressions doivent être des valeurs immédiates absolues et le résultat est une valeur immédiate absolue.

Procédures et fonctions de l'assembleur

Jusqu'à présent, chaque construction ASM...END que vous avez vue était une instruction dans une partie d'instruction BEGIN...END normale. La directive assembleur de Turbo Pascal vous permet d'écrire des procédures et des fonctions complètes dans l'assembleur intégré, sans avoir besoin d'une partie d'instruction BEGIN...END. Voici un exemple de fonction assembleur :

  1. Function LongMul(X,Y:Integer):Longint;Assembler;
  2. ASM
  3.  MOV AX,X
  4.  IMUL Y
  5. END;

La directive assembleur oblige Turbo Pascal à effectuer un certain nombre d'optimisations de génération de code :

Les fonctions utilisant la directive assembleur doivent renvoyer leurs résultats comme suit :

La directive assembleur est à bien des égards comparable à la directive externe, et les procédures et fonctions assembleur doivent obéir aux mêmes règles que les procédures et fonctions externes. Les exemples suivants illustrent certaines des différences entre les instructions ASM dans les fonctions ordinaires et les fonctions assembleur. Le premier exemple utilise une instruction ASM dans une fonction ordinaire pour convertir une chaîne de caractères en majuscules. Notez que le paramètre de valeur Str dans ce cas fait référence à une variable locale, car le compilateur génère automatiquement un code d'entrée copiant le paramètre réel dans l'entreposage local.

  1. Function UpperCase(Str:String):String;Begin
  2.  ASM
  3.   CLD
  4.   LEA SI,Str
  5.   LES DI,@Result
  6.   SEGSS LODSB
  7.   STOSB
  8.   XOR AH,AH
  9.   XCHG AX,CX
  10.   JCXZ @3
  11. @1:
  12.   SEGSS LODSB
  13.   CMP AL,'a'
  14.   JB @2
  15.   CMP AL, 'z'
  16.   JA @2
  17.   SUB AL,20h
  18. @2:
  19.   STOSB
  20.   LOOP @1
  21. @3:
  22.  END;
  23. End; 

Le deuxième exemple est une version assembleur de la fonction UpperCase. Dans ce cas, Str n'est pas copié dans l'entreposage local et la fonction doit traiter Str comme un paramètre var :

  1. Function UpperCase(Str:String):String;Assembler;
  2. ASM
  3.  PUSH DS
  4.   CLD
  5.   LDS SI,Str
  6.   LES DI,@Result
  7.   LODSB
  8.   STOSB
  9.   XOR AH,AH
  10.   XCHG AX,CX
  11.   JCXZ @3 
  12. @1:
  13.   LODSB
  14.   CMP AL,'a'
  15.   JB @2
  16.   CMP AL,' z'
  17.   JA @2
  18.   SUB AL,20H
  19. @2:
  20.   STOSB
  21.   LOOP @1
  22. @3:
  23.   POP DS
  24. END; 


Dernière mise à jour : Dimanche, le 15 novembre 2020