How to declare nested function for Jasmin byte code?

108 views Asked by At

I am writing a compiler and I need to convert Mini Pascal (a simple version of Pascal) to Jasmin byte code.

  1. But how do I declare the nested function in the Jasmin language?

  2. Because function tt(I): I can only pass in one Integer (which is rr) how do I transfer the variable d from function ss to function tt?

C++ (translated from Mini Pascal by myself):

#include <iostream>

using namespace std;

int a, b;

int ss(int rr)
{
  int d;

  int tt(int rr)
  {
    int e;
    e = rr * 3;
    return rr + d - e + b;
  }

  d = rr - 4;
  return tt(rr);
}

int main()
{
  b = -5;
  a = ss(3);
  cout << a;

  return 0;
}

Mini Pascal:

PROGRAM test_nested_function(input, output, error);
VAR a, b : INTEGER;

  FUNCTION ss(rr :INTEGER) : INTEGER;
  VAR d : INTEGER;

    FUNCTION tt(rr : INTEGER) : INTEGER;
    VAR e : INTEGER;
    BEGIN
      e := rr * 3;
      tt := rr + d - e + b;
    END;

  BEGIN
    d := rr - 4;
    ss := tt(rr)
  END;

BEGIN
  b := -5;
  a := ss(3);
  writelnI(a)
END.
1

There are 1 answers

0
Silvio Mayolo On

When you're translating from a language that has closures to one that doesn't, you have to retain a reference to the outer function somehow. There are, broadly speaking, two ways to do that: nested closures and flat closures. The important thing to note is that, in both of these cases, your closure is not an ordinary top level function. It's some structure that happens to be callable.

Nested Closures

In a nested closure, your nested function simply maintains a reference to the enclosing scope as a pointer to some abstract data structure. So, tt maintains a reference to ss (in the abstract) and then can access the d variable via ss.d. Java pseudo-code for the same:

public class SsStructure {
  private int rr;
  private int d;

  public SsStructure(int rr) {
    this.rr = rr;
    this.d = 0;
  }

}

public class TtStructure {
  private SsStructure closure;

  public TtStructure(SsStructure closure) {
    this.closure = closure;
  }

  public int call(int rr) {
    ...
  }
 
}

public class Main {
  public static int ss(int rr) {
    SsStructure ss = new SsStructure(rr);
    TtStructure tt = new TtStructure(ss);
    ss.d = ss.rr - 4;
    return tt.call(ss.rr - 4);
  }
}

In this model, your ss function gets a closure frame in your program. All of its local variables (or at least, those needed by the closures) get put into that structure rather than being declared as actual local variables. Then that closure structure gets passed to any nested function that needs it.

If you have functions nested multiple layers deep, then a closure maintains a reference to the immediately enclosing scope. If it needs to access variables that exist multiple layers up the closure stack, it will do so through indirect references, since each closure frame retains a reference to its own enclosing scope.

Flat Closures

With flat closures, your nested function receives copies of the actual concrete variables from the enclosing scope. Your tt is still a structure, but rather than storing a reference to some other ss structure, it gets the actual int it needs.

public class TtStructure {
  private int d;

  public TtStructure(int d) {
    this.d = d;
  }

  public int call(int rr) {
    ...
  }
 
}

public class Main {
  public static int ss(int rr) {
    int d = rr - 4;
    TtStructure tt = new TtStructure(d);
    return tt.call(rr - 4);
  }
}

This is a more space-efficient approach. Rather than storing a bunch of pointers everywhere, you store only the actual data, and the data you need. Further, if you have nested closures, you automatically flatten them when you create these specialized structures, so there's less indirection.

However, care must be taken if you have mutable closures. If you have a final variable, then there's no problem. In fact, this is exactly why the Java language forbids non-final variables from being closed over.

If you want to allow mutable variables in closures, you'll need to explicitly add in a layer of indirection. This can be a simple class that stores one instance variable, and then you can use that variable in both the enclosing scope and the closure.

public class Cell<T> {
  public T impl;
  public Cell(T impl) {
    this.impl = impl;
  }
}

You need to wrap any local variable in a Cell<T> if it is (a) used in a closure, and (b) reassigned to at any point. Variables that are final (hence never reassigned) need not be wrapped, and those that are only used locally (never in a closure) need not be wrapped.