Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I unroll (compile) an interpreter loop?

I heard that some languages go from interpreted to compiled by "unrolling the interpreter loop".

Let's say I have the following pseudo-c-code interpreter for an ast tree.

int interpret(node)
{
    switch(node) {
        case PLUS:
             return interpret(child(0))+interpret(child(1));
        case MINUS:
             return interpret(child(0))-interpret(child(1));       
    }
}

How do I unroll this loop to create a compiled program?

I see you all downvoting this like I don't know what I am talking about, but here is a quote from Wikipedia that states exactly what I am describing.

"Factor was originally only interpreted, but is now fully compiled (the non-optimizing compiler basically unrolls the interpreter loop"

like image 217
Unknown Avatar asked Feb 08 '09 20:02

Unknown


1 Answers

"Unrolling a loop" normally means replacing a repetition with a sequence of actions. The loop:

for (int i = 0; i < 4; ++i) {
    a[i] = b[i] + c[i];
}

would unroll into the equivalent:

a[0] = b[0] + c[0];
a[1] = b[1] + c[1];
a[2] = b[2] + c[2];
a[3] = b[3] + c[3];

It appears to me that whoever was being quoted by Wikipedia was using the phrase in a somewhat metaphorical sense. So, in that sense...

Your sample would normally be invoked inside a interpreter that is walking a tree of AST nodes, which might look something like this:

 ASSIGN
    |
 +--+---+
 |      |
REF   MINUS
 |      |
 x   +--+---+
     |      |
    VAR    PLUS
     |      |
     a   +--+--+
         |     |
        VAR  CONST
         |     |
         b     3

and the interpret function would have additional options:

int interpret(node) {
    switch(node) {
        case PLUS:
             return interpret(child(0))+interpret(child(1));
        case MINUS:
             return interpret(child(0))-interpret(child(1));       
        case ASSIGN:
             return set(child(0), interpret(child(1));
        case VAR:
             return fetch(child(0));
        case CONST:
             return value(child(0));
        ...
    }
}

If you walk the AST with that interpet function (actually performing the operations), you're interpreting. But if the function records the actions to be performed, rather than executing them, you're compiling. In pseudocode (actually, pseudocode twice, as I'm assuming a hypothetical stack machine as the compilation target):

string compile(node) {
    switch(node) {
        case PLUS:
             return(compile(child(0))) + compile(child(1)) + ADD);
        case MINUS:
             return(compile(child(0))) + compile(child(1)) + SUB);
        case ASSIGN:
             return(PUSHA(child(0))) + compile(child(1)) + STORE);
        case REF:
             return(PUSHA(child(0)));
        case VAR:
             return(PUSHA(child(0)) + FETCH);
        case CONST:
             return(PUSHLIT + value(child(0)));
        ...
    }
}

Invoking compile on that AST (ignoring any pseudocode typos ;-) would spit out something like:

PUSHA x
PUSHA a
FETCH
PUSHA b
FETCH
PUSHLIT 3
ADD 
SUB
STORE

FWIW, I'd tend to think of that as unrolling the AST, rather than unrolling the interpreter, but won't criticize somebody else's metaphor without reading it in context.

like image 82
joel.neely Avatar answered Oct 05 '22 23:10

joel.neely