8
votes

Modèle de conception pour faire un assembleur

Je fais un assembleur 8051.

Avant que tout soit un tokéniseur qui lit les jetons suivants, définit les drapeaux d'erreur, reconnaît EOF, etc. Ensuite, il y a la boucle principale du compilateur, qui lit les jetons suivants et vérifie les mnémoniques valides: xxx

et il continue à plusieurs cas. Pire que c'est le code à l'intérieur de chaque cas, qui vérifie les paramètres valides puis la convertit au code compilé. À l'heure actuelle, cela ressemble à ceci: xxx

pour chacun des mnémoniques, je dois vérifier les paramètres valides, puis créez le code compilé correct. Codes très similaires pour vérifier les paramètres valides pour chaque répétition mnémonique dans chaque cas.

existe donc un modèle de conception pour améliorer ce code?
Ou simplement un moyen plus simple de mettre en œuvre cela?

EDIT: J'ai accepté la réponse de PLINTH, grâce à lui. Pourtant, si vous avez des idées à ce sujet, je serai heureux de les apprendre. Merci tout.


0 commentaires

5 Réponses :


4
votes

oui. La plupart des assembleurs utilisent une table de données décrivant les instructions: Mnemonic, code OP, les opérandes Formulaires, etc.

Je suggère de regarder le code source pour comme . J'ai du mal à le trouver. look Ici . (Merci à Hossein.)


5 commentaires

@Wallyk: J'ai déjà pensé à une table de recherche qui contient des mnémoniques, nombre de paramètres pour chaque mnémonique, leurs types, etc. Mais la vitesse et la mémoire sont importantes pour moi ici, car il va être exécuté sur une machine avec une petite bélier et processeur lent. Je pense donc que ce sera mon dernier choix.


@HOSSEIN: une table de recherche ne prendrait probablement plus de mémoire que d'avoir plusieurs copies du code de cas que vous avez actuellement. En ce qui concerne la performance, vous ne remarquerez probablement aucune différence. Votre code utilise déjà des comparaisons de chaîne et des appels de fonction, ce n'est donc pas comme si vous optimisez une boucle interne serrée. Rappelez-vous la règle 80/20.


L'approche axée sur la table est fortement essayée et vraie pour la petite mémoire, des implémentations de processeur lente. Considérez la quantité de code qu'il faut pour configurer chaque comparaison dans votre approche. Dans l'approche axée sur la table, il n'y a qu'un seul peu de code qui fait la comparaison pour, disons, le mnémonique. C'est à l'intérieur d'une boucle qui n'est pas plus lente qu'une séquence de comparaisons littérales. Essayez les deux: Je suis convaincu que vous trouverez que la table est supérieure de la plupart des façons.


Si la vitesse et la mémoire sont vos préoccupations, vous devez absolument utiliser une table. L'un des assembleurs les plus rapides pour l'Apple II est entré autour de 8 000% et a couru à plusieurs milliers de lignes par minute sur une machine de 1 MHz. Quelles sont vos limitations de mémoire? Je serais surpris si vous ne pouviez pas décrire l'ensemble des instructions et ses modes d'adressage en moins de 1k.


"Les structures de données intelligentes et le code muet fonctionnent beaucoup mieux que l'inverse." - la cathédrale et le bazar ( en.wikipedia.org/wiki/the_cathedral_and_the_bazaar )



0
votes

Je pense que vous devriez examiner le motif de visiteur. Cela pourrait ne pas rendre votre code beaucoup plus simple, mais réduira le couplage et augmentera la réutilisation. sableCC est un cadre Java pour créer des compilateurs qui l'utilisent de manière approfondie.


2 commentaires

Es-tu sûr? Je vois des visiteurs principalement utilisés dans les arbres (comme le T dans ast). Vous n'avez pas exactement besoin / avoir un arbre dans un assembleur, par opposition à un compilateur pour une langue plus grande.


Non, mais vous pourriez avoir une classe mnémonique qui pourrait définir son code de sortie à travers un motif de visiteur. Vous en boucle les mnémoniques, transmettez-leur le visiteur, ils renvoient leur propre sortie. Je n'ai jamais conçu d'assembleur, alors c'est juste une suppose heh.



0
votes

Avez-vous regardé le modèle "Dispatcher de commande"?

http://en.wikipedia.org/wiki/command_pattern

L'idée générale serait de créer un objet qui gère chaque instruction (commande) et crée une table de recherche qui correspond à chaque instruction à la classe de manutention. Chaque classe de commandes aurait une interface commune (commande.execute (* args) par exemple) qui vous donnerait certainement une conception plus propre / plus flexible que votre énoncé de commutation énorme actuel.


1 commentaires

Je préfère rester à l'écart de «trop» d'objet à cause de problèmes de performance.



0
votes

Lorsque je jouais avec un outil d'émulateur de microcode, j'ai tout converti en descendants d'une classe . De instruction étaient des classes de catégories, telles que arithmétic_instrucance et branch_instruction . J'ai utilisé un modèle usine pour créer les instances.

Votre meilleur pari peut être de disposer de la spécification de la syntaxe de la langue d'assemblage. Écrivez un lexer pour convertir des jetons (** s'il vous plaît, n'utilisez pas si-evelif-adeders). Ensuite, basé sur la sémantique, émettez le code.

Il y a longtemps, les assembleurs ont eu un minimum de deux passes: le premier à résoudre les constantes et forment le code squelettique (y compris les tables de symboles). Le deuxième passage était de générer des valeurs plus bétones ou absolues.

Avez-vous lu le livre de dragon récemment?


1 commentaires

Le livre de dragon discute-t-il aussi des assembleurs? J'essaie toujours de trouver une version téléchargeable de celui-ci ou d'obtenir une version traduite mais que vous avez réussi.



8
votes

J'ai écrit un certain nombre d'assembleurs au fil des ans en faisant une analyse de la main et franchement, vous ferez probablement mieux d'utiliser une langue grammaire et un générateur d'analyseur.

Voici pourquoi - une chaîne de montage typique aura probablement ressembler à quelque chose comme ceci: p> xxx pré>

et une instruction sera: p> xxx pré>

et une directive sera: p>

char *line = ReadLine();
int nextStart = 0;
int labelLen;
char *label = GetLabel(line, &labelLen, nextStart, &nextStart); // may be empty
int mnemonicLen;
char *mnemonic = GetMnemonic(line, &mnemonicLen, nextStart, &nextStart); // may be empty
if (IsOpcode(mnemonic, mnemonicLen)) {
    AddressingModeInfo info = GetAddressingModeInfo(line, nextStart, &nextStart);
    if (IsValidInstruction(mnemonic, info)) {
        GenerateCode(mnemonic, info);
    }
    else throw new BadInstructionException(mnemonic, info);
}
else if (IsDirective()) { /* etc. */ }


2 commentaires

Ma mémoire est d'environ 200 kib et je suis tellement heureuse de remplacer le code orienté objet précédent avec celui-ci! Maintenant, un suivi (je sais ...) ensemble; } Liste publique Info {Obtenir; ensemble; }} le modèle de commande? Et y a-t-il des raisons / situations que je préfère le modèle de commande à des tables de recherche?


Modèle de commande? Non - les deux auraient été axés sur des tables de recherche. Le code C # précédent n'est qu'un seul qui profite de la prise en charge de la langue pour les collections génériques et les propriétés automatiques.