Implementing Code Generator in C++

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

  1. File I/O: The program opens AIP.TXT for reading and ANOP.TXT for writing. Each iteration of the while loop reads one four-field IR instruction using fscanf.
  2. Operator dispatch: A series of strcmp checks on op1 determines which arithmetic or assignment template to emit. Each arithmetic template follows the same three-step pattern: load operand1 into AX, load operand2 into BX, execute the operation (result lands in AX), then store AX into the result operand.
  3. Assignment: The = case generates a single MOV dest, src instruction — no arithmetic register needed.
  4. Output: All generated instructions are written to ANOP.TXT. The console prints “Code generation successful” on completion.

Output File (ANOP.TXT)

MOV AX, o1 ; load first operand of addition MOV BX, o2 ; load second operand ADD AX, BX ; o3 = o1 + o2 MOV o3, AX ; store result MOV AX, o3 ; load first operand of subtraction MOV BX, o1 SUB AX, BX ; o3 = o3 – o1 MOV o3, AX MOV AX, o1 ; load first operand of multiplication MOV BX, o2 MUL BX ; o3 = o1 * o2 (product in AX) MOV o3, AX MOV AX, o3 ; load first operand of division MOV BX, o1 DIV BX ; o3 = o3 / o1 (quotient in AX) MOV o3, AX MOV o5, o6 ; assignment: o5 = o6

Output Explanation

  1. Addition (+ o1 o2 o3): Four instructions are emitted. o1 and o2 are loaded into registers; ADD AX, BX computes the sum; the result is stored back in o3.
  2. Subtraction (- o3 o1 o3): Same pattern but with SUB. Note that o3 is both source and destination here, a common pattern in compiler-generated temporaries.
  3. Multiplication (* o1 o2 o3): Uses MUL BX (8086 unsigned multiply; the implicit source is AX).
  4. Division (/ o3 o1 o3): Uses DIV BX; quotient is left in AX.
  5. Assignment (= o5 o6 %): A single MOV o5, o6 is generated. The % in the IR is a placeholder and is not used by this case.

See Also

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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.