About Compilers
1 Review: Interpreter runtime environment
Previously, we developed the runtime backend as a collection of classes corresponding to different programming constructs.
Consider the if-else-then construct. It is implemented as:
class Ifelse (
  val cond: Expr,
  val trueExpr: Expr,
  val falseExpr: Expr? = null
): Expr() {
  override fun eval(scope: Runtime): Data {
    ...
  }
}An instance of if-then-else code would be equivalent in constructing a Ifelse object, and invoke its eval.
Ifelse(
  condExpr,
  trueExpr,
  falseExpr,
)
.eval(scope)We can create an interpreted programming language using SDD and data attributes.
expr returns [Expr expr]
  : ifElse      { $expr = ifElse.expr; }
  | while       { $expr = while.expr; }
  | deref       { $expr = deref.expr; }
  | funcDecl    { ... }
  | funcInvoke  { ... }
  | ...
  ;
  
ifElse returns [Ifelse expr]
  : 'if' '(' cond=expr ')'
    '{' trueExpr=expr '}'
    ('else' '{' falseExpr=expr '}')?
    {
      $expr = new Ifelse(
        cond.expr,
        trueExpr.expr,
        falseExpr.expr
      );
    }
  ;2 High-performance Runtime
x86 assembly
- direct access to CPU, registers and memory
- assembly code
- best possible high performance
- not portable across different hardware architectures
LLVM
- abstraction of low-level hardware to register based architecture
- advanced optimization toolchain
- high overhead to get started
- not portable across different hardware, but easy to transcompile, with no dependencies.
JVM
- abstraction of low-level hardware to stack machine
- advanced (automatic) code optimization
- low overhead to get started
- portable across different hardware, but requires JVM installed.