Code generation is the final back-end phase of a compiler. It takes the intermediate representation (IR) of the program — typically a sequence of three-address instructions — and translates each one into the equivalent target machine instructions. For a register-based architecture like the 8086, this means emitting MOV, ADD, SUB, MUL, DIV, and assignment instructions that use the AX and BX registers.
This C++ program reads three-address IR instructions from an input file (AIP.TXT), generates the corresponding 8086-style assembly code, and writes it to an output file (ANOP.TXT). Each IR instruction has the form:
operator operand1 operand2 result
C++ Code
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>
void main() {
clrscr();
/* op1 = operator, op2 = operand1, op3 = operand2, op4 = result */
char op1[10], op2[10], op3[10], op4[10];
FILE *ip, *op;
/* Open input IR file for reading */
ip = fopen("aip.txt", "r");
if (!ip) {
cout << "error in opening file";
}
/* Open output assembly file for writing */
op = fopen("anop.txt", "w");
/* Process each IR instruction until end-of-file */
while (!feof(ip)) {
fscanf(ip, "%s%s%s%s", &op1, &op2, &op3, &op4);
/* --- Addition: result = operand1 + operand2 --- */
if (strcmp(op1, "+") == 0) {
fprintf(op, "MOV AX, %s\n", op2); /* load operand1 into AX */
fprintf(op, "MOV BX, %s\n", op3); /* load operand2 into BX */
fprintf(op, "ADD AX, BX\n"); /* AX = AX + BX */
fprintf(op, "MOV %s, AX\n", op4); /* store result */
}
/* --- Subtraction: result = operand1 - operand2 --- */
if (strcmp(op1, "-") == 0) {
fprintf(op, "MOV AX, %s\n", op2);
fprintf(op, "MOV BX, %s\n", op3);
fprintf(op, "SUB AX, BX\n"); /* AX = AX - BX */
fprintf(op, "MOV %s, AX\n", op4);
}
/* --- Multiplication: result = operand1 * operand2 --- */
if (strcmp(op1, "*") == 0) {
fprintf(op, "MOV AX, %s\n", op2);
fprintf(op, "MOV BX, %s\n", op3);
fprintf(op, "MUL BX\n"); /* AX = AX * BX (8086 MUL) */
fprintf(op, "MOV %s, AX\n", op4);
}
/* --- Division: result = operand1 / operand2 --- */
if (strcmp(op1, "/") == 0) {
fprintf(op, "MOV AX, %s\n", op2);
fprintf(op, "MOV BX, %s\n", op3);
fprintf(op, "DIV BX\n"); /* AX = AX / BX (quotient in AX) */
fprintf(op, "MOV %s, AX\n", op4);
}
/* --- Assignment: operand2 = operand1 --- */
if (strcmp(op1, "=") == 0) {
fprintf(op, "MOV %s, %s\n", op2, op3);
}
}
fclose(ip);
fclose(op);
cout << "\n Code generation successful";
getch();
}
Input File (AIP.TXT)
+ o1 o2 o3
- o3 o1 o3
* o1 o2 o3
/ o3 o1 o3
= o5 o6 %
Each line contains four fields separated by spaces: the operator (+, -, *, /, or =), two source operands, and a destination operand.
How the Code Works
- File I/O: The program opens
AIP.TXTfor reading andANOP.TXTfor writing. Each iteration of thewhileloop reads one four-field IR instruction usingfscanf. - Operator dispatch: A series of
strcmpchecks onop1determines which arithmetic or assignment template to emit. Each arithmetic template follows the same three-step pattern: load operand1 intoAX, load operand2 intoBX, execute the operation (result lands inAX), then storeAXinto the result operand. - Assignment: The
=case generates a singleMOV dest, srcinstruction — no arithmetic register needed. - Output: All generated instructions are written to
ANOP.TXT. The console prints “Code generation successful” on completion.
Output File (ANOP.TXT)
Output Explanation
- Addition (
+ o1 o2 o3): Four instructions are emitted.o1ando2are loaded into registers;ADD AX, BXcomputes the sum; the result is stored back ino3. - Subtraction (
- o3 o1 o3): Same pattern but withSUB. Note thato3is both source and destination here, a common pattern in compiler-generated temporaries. - Multiplication (
* o1 o2 o3): UsesMUL BX(8086 unsigned multiply; the implicit source isAX). - Division (
/ o3 o1 o3): UsesDIV BX; quotient is left inAX. - Assignment (
= o5 o6 %): A singleMOV o5, o6is generated. The%in the IR is a placeholder and is not used by this case.
See Also
- Implementing Multi-pass Assembler in C
- Implementing Macro Processor in C
- Implementing Absolute Loader in C++
- Implementing Lexical Analyser in C++
- Implementation of Bottom-Up (Shift-Reduce) Parsing in C++
Conclusion
This code generator captures the essence of compiler back-end work: mapping abstract three-address IR instructions to concrete register-based machine code. Real compilers extend this template with register allocation (avoiding redundant MOVs) and instruction selection (choosing the most efficient opcode for each operation). Connecting this generator to a lexical analyser and a parser gives you a minimal end-to-end compiler pipeline.