Verified compilation of a first-order Lisp language

Similar documents
Parsing Scheme (+ (* 2 3) 1) * 1

Background. From my PhD (2009): Verified Lisp interpreter in ARM, x86 and PowerPC machine code

A verified runtime for a verified theorem prover

Verification of an ML compiler. Lecture 3: Closures, closure conversion and call optimisations

Verified compilers. Guest lecture for Compiler Construction, Spring Magnus Myréen. Chalmers University of Technology

Scope, Functions, and Storage Management

Verified Characteristic Formulae for CakeML. Armaël Guéneau, Magnus O. Myreen, Ramana Kumar, Michael Norrish April 27, 2017

Functional Programming. Pure Functional Programming

Recap: Functions as first-class values

A New Verified Compiler Backend for CakeML. Yong Kiam Tan* Magnus O. Myreen Ramana Kumar Anthony Fox Scott Owens Michael Norrish

G Programming Languages - Fall 2012

CakeML. A verified implementation of ML. LFCS seminar, Edinburgh, May Magnus Myreen Chalmers University of Technology & University of Cambridge

Comp 411 Principles of Programming Languages Lecture 7 Meta-interpreters. Corky Cartwright January 26, 2018

Computer Science 21b (Spring Term, 2015) Structure and Interpretation of Computer Programs. Lexical addressing

G Programming Languages Spring 2010 Lecture 4. Robert Grimm, New York University

Principles of Programming Languages

Control in Sequential Languages

CS 4110 Programming Languages & Logics. Lecture 35 Typed Assembly Language

Pierce Ch. 3, 8, 11, 15. Type Systems

A Brief Introduction to Scheme (II)

Begin at the beginning

Variables and Bindings

CSE413: Programming Languages and Implementation Racket structs Implementing languages with interpreters Implementing closures

CSE341: Programming Languages Lecture 17 Implementing Languages Including Closures. Dan Grossman Autumn 2018

Lecture 09: Data Abstraction ++ Parsing is the process of translating a sequence of characters (a string) into an abstract syntax tree.

Compiler Construction

Compiler Construction

Where We Are. Lexical Analysis. Syntax Analysis. IR Generation. IR Optimization. Code Generation. Machine Code. Optimization.

Konzepte von Programmiersprachen

regsim.scm ~/umb/cs450/ch5.base/ 1 11/11/13

News. CSE 130: Programming Languages. Environments & Closures. Functions are first-class values. Recap: Functions as first-class values

Register Machines. Connecting evaluators to low level machine code

Below are example solutions for each of the questions. These are not the only possible answers, but they are the most common ones.

PROGRAM ANALYSIS & SYNTHESIS

Compiler Construction Lent Term 2013 Lectures 9 & 10 (of 16)

Syntactic Sugar: Using the Metacircular Evaluator to Implement the Language You Want

CSE 413 Languages & Implementation. Hal Perkins Winter 2019 Structs, Implementing Languages (credits: Dan Grossman, CSE 341)

Outline. Introduction Concepts and terminology The case for static typing. Implementing a static type system Basic typing relations Adding context

6.184 Lecture 4. Interpretation. Tweaked by Ben Vandiver Compiled by Mike Phillips Original material by Eric Grimson

A verified runtime for a verified theorem prover

The Racket Virtual Machine and Randomized Testing

Agenda. CSE P 501 Compilers. Java Implementation Overview. JVM Architecture. JVM Runtime Data Areas (1) JVM Data Types. CSE P 501 Su04 T-1

Dynamic Types , Spring March 21, 2017

Recursion. What is Recursion? Simple Example. Repeatedly Reduce the Problem Into Smaller Problems to Solve the Big Problem

Principles of Programming Languages Topic: Scope and Memory Professor Louis Steinberg Fall 2004

Compiler Construction

CS558 Programming Languages

Principles of Programming Languages

An Overview to Compiler Design. 2008/2/14 \course\cpeg421-08s\topic-1a.ppt 1

CS 4110 Programming Languages & Logics

Programming Languages

Fall Semester, The Metacircular Evaluator. Today we shift perspective from that of a user of computer langugaes to that of a designer of

CSE341 Spring 2017, Final Examination June 8, 2017

FP Foundations, Scheme

Turning proof assistants into programming assistants

MIPS Procedure Calls. Lecture 6 CS301

Lecture08: Scope and Lexical Address

CS 360 Programming Languages Interpreters

Functional Languages. Hwansoo Han

CSE443 Compilers. Dr. Carl Alphonce 343 Davis Hall

Architecture of LISP Machines. Kshitij Sudan March 6, 2008

The reflective Milawa theorem prover is sound

Test 1 Summer 2014 Multiple Choice. Write your answer to the LEFT of each problem. 5 points each 1. Preprocessor macros are associated with: A. C B.

Programming Languages

The Environment Model. Nate Foster Spring 2018

Code Generation and Optimisation. Local variables and procedures

Deferred operations. Continuations Structure and Interpretation of Computer Programs. Tail recursion in action.

Project 3 Due October 21, 2015, 11:59:59pm

15 212: Principles of Programming. Some Notes on Continuations

The Activation Record (AR)

Debugging in LISP. trace causes a trace to be printed for a function when it is called

The Racket virtual machine and randomized testing

Theorem Proving Principles, Techniques, Applications Recursion

Automatically Introducing Tail Recursion in CakeML. Master s thesis in Computer Science Algorithms, Languages, and Logic OSKAR ABRAHAMSSON

Tail Recursion: Factorial. Begin at the beginning. How does it execute? Tail recursion. Tail recursive factorial. Tail recursive factorial

Mechanized Operational Semantics

Compiler Construction Lent Term 2015 Lectures 10, 11 (of 16)

MPRI course 2-4 Functional programming languages Exercises

Recursive Functions of Symbolic Expressions and Their Computation by Machine Part I

Certified Memory Usage Analysis

The Rule of Constancy(Derived Frame Rule)

CIT Week13 Lecture

Compiler Theory. (Semantic Analysis and Run-Time Environments)

Programming Languages and Compiler Design

2/6/2018. Let s Act Like Compilers! ECE 220: Computer Systems & Programming. Decompose Finding Absolute Value

Lecture 10: Recursion vs Iteration

Run Time Environments

Run-time Environments

Homework 3 COSE212, Fall 2018

Run-time Environments

6.001, Spring Semester, 1998, Final Exam Solutions Your Name: 2 Part c: The procedure eval-until: (define (eval-until exp env) (let ((return (eval-seq

CSc 453 Interpreters & Interpretation

Reasoning About LLVM Code Using Codewalker

Documentation for LISP in BASIC

Verification of an ML compiler. Lecture 1: An introduction to compiler verification

Compiler Construction

TAIL RECURSION, SCOPE, AND PROJECT 4 11

CSE341 Spring 2017, Final Examination June 8, 2017

Run-time Environments. Lecture 13. Prof. Alex Aiken Original Slides (Modified by Prof. Vijay Ganesh) Lecture 13

First-class continuations. call/cc, stack-passing CEK machines

Transcription:

Verified compilation of a first-order Lisp language Lecture 7 MPhil ACS & Part III course, Functional Programming: Implementation, Specification and Verification Magnus Myreen Michaelmas term, 2013

Interpreter vs compiler FP interpreter: program that implements the small-step semantics operates over syntax of the source FP program naive implementation wastes time (slow) time spent figuring out what operation to perform

Interpreter vs compiler FP interpreter: program that implements the small-step semantics operates over syntax of the source FP program naive implementation wastes time (slow) time spent figuring out what operation to perform Compilation: compiler translates source to native machine code uation is compile-then-execute-generated-code performance can be good compilation is dynamic or just-in-time (JIT) if it happens at runtime

Compilation in this lecture Source: simple first-order FP language bytecode : stack-based little language machine code: as described in previous lectures

Compilation in this lecture Ramana s guest lectures will compile higher-order FP Source: simple first-order FP language bytecode : stack-based little language machine code: as described in previous lectures

Little source language val = SExp exp ::= Var string Const SExp Add exp exp Car exp Cdr exp Cons exp exp Let string exp exp If exp exp exp...

Little source language val = SExp exp ::= Var string Const SExp Add exp exp Car exp Cdr exp Cons exp exp Let string exp exp If exp exp exp... first-order Lisp, i.e. no closure values

Little source language val = SExp exp ::= Var string Const SExp Add exp exp Car exp Cdr exp Cons exp exp Let string exp exp If exp exp exp... first-order Lisp, i.e. no closure values Semantics: big-step operational semantics similar to Lecture 2.

Bytecode bc_inst ::= LOAD num PUSH SExp ADD CAR CDR CONS POP POP1 JUMP num JUMP_IF num... bytecode_program = bc_inst list

Bytecode bc_inst ::= LOAD num PUSH SExp ADD CAR CDR CONS POP POP1 JUMP num JUMP_IF num... bytecode_program = bc_inst list Semantics: small-step op. semantics sketched on next slide.

Semantics of bytecode Operational semantics: small-step similar to machine code semantics, but more abstract stack based state tuple: (stack, pc, code)

Semantics of bytecode Operational semantics: assumes memory abstraction small-step similar to machine code semantics, but more abstract stack based state tuple: (stack, pc, code)

Semantics of bytecode Operational semantics: small-step similar to machine code semantics, but more abstract stack based state tuple: (stack, pc, code) assumes memory abstraction instead of regs, memory, flags etc.

Semantics of bytecode Operational semantics: small-step similar to machine code semantics, but more abstract stack based state tuple: (stack, pc, code) Examples: assumes memory abstraction instead of regs, memory, flags etc. fetch pc code = POP (x::stack, pc, code) (stack, pc + ilength [POP], code)

Semantics of bytecode Operational semantics: small-step similar to machine code semantics, but more abstract stack based state tuple: (stack, pc, code) assumes memory abstraction instead of regs, memory, flags etc. Examples: POP instruction to be executed fetch pc code = POP (x::stack, pc, code) (stack, pc + ilength [POP], code)

Semantics of bytecode Operational semantics: small-step similar to machine code semantics, but more abstract stack based state tuple: (stack, pc, code) assumes memory abstraction instead of regs, memory, flags etc. Examples: POP instruction to be executed fetch pc code = POP (x::stack, pc, code) (stack, pc + ilength [POP], code) element at top of stack

Semantics of bytecode Operational semantics: small-step similar to machine code semantics, but more abstract stack based state tuple: (stack, pc, code) assumes memory abstraction instead of regs, memory, flags etc. Examples: POP instruction to be executed fetch pc code = POP (x::stack, pc, code) (stack, pc + ilength [POP], code) element at top of stack is removed by POP

Semantics of bytecode Operational semantics: small-step similar to machine code semantics, but more abstract stack based state tuple: (stack, pc, code) assumes memory abstraction instead of regs, memory, flags etc. Examples: POP instruction to be executed fetch pc code = POP (x::stack, pc, code) (stack, pc + ilength [POP], code) fetch pc code = PUSH x (stack, pc, code) (x::stack, pc + ilength [PUSH x], code)

More examples fetch pc code = POP1 (y::x::stack, pc, code) (y::stack, pc + ilength [POP1], code)

More examples fetch pc code = POP1 (y::x::stack, pc, code) removed element below top (y::stack, pc + ilength [POP1], code)

More examples fetch pc code = POP1 (y::x::stack, pc, code) removed element below top (y::stack, pc + ilength [POP1], code) fetch pc code = CONS (y::x::stack, pc, code) introduced Dot pair (Dot x y::stack, pc + ilength [CONS], code)

More examples fetch pc code = POP1 (y::x::stack, pc, code) removed element below top (y::stack, pc + ilength [POP1], code) fetch pc code = CONS (y::x::stack, pc, code) introduced Dot pair (Dot x y::stack, pc + ilength [CONS], code) fetch pc code = JUMP_IF n istrue x jumped by offset n (x::stack, pc, code) (stack, pc + n, code)

More examples fetch pc code = POP1 (y::x::stack, pc, code) removed element below top (y::stack, pc + ilength [POP1], code) fetch pc code = CONS (y::x::stack, pc, code) introduced Dot pair (Dot x y::stack, pc + ilength [CONS], code) fetch pc code = JUMP_IF n istrue x jumped by offset n (x::stack, pc, code) (stack, pc + n, code) fetch pc code = JUMP_IF n (istrue x) didn t jump (x::stack, pc, code) (stack, pc + ilength [JUMP_IF n], code)

More examples fetch pc code = POP1 (y::x::stack, pc, code) removed element below top (y::stack, pc + ilength [POP1], code) fetch pc code = CONS (y::x::stack, pc, code) introduced Dot pair (Dot x y::stack, pc + ilength [CONS], code) fetch pc code = JUMP_IF n istrue x jumped by offset n (x::stack, pc, code) (stack, pc + n, code) fetch pc code = JUMP_IF n (istrue x) didn t jump (x::stack, pc, code) (stack, pc + ilength [JUMP_IF n], code) fetch pc code = LOAD (length xs) copied x and pushed to top (xs ++ x::stack, pc, code) (x::xs ++ x::stack, pc + ilength [LOAD (length xs) n], code)

More examples fetch pc code = POP1 (y::x::stack, pc, code) removed element below top (y::stack, pc + ilength [POP1], code) fetch pc code = CONS (y::x::stack, pc, code) introduced Dot pair (Dot x y::stack, pc + ilength [CONS], code) fetch pc code = JUMP_IF n istrue x jumped by offset n (x::stack, pc, code) (stack, pc + n, code) fetch pc code = JUMP_IF n (istrue x) didn t jump (x::stack, pc, code) (stack, pc + ilength [JUMP_IF n], code) fetch pc code = LOAD (length xs) copied x and pushed to top (xs ++ x::stack, pc, code) (x::xs ++ x::stack, pc + ilength [LOAD (length xs) n], code)

Bytecode machine code Define a function (to_mc) from bc_inst to machine code: to_mc (POP::rest) = load r0,[r0+4] ++ to_mc rest

Bytecode machine code Define a function (to_mc) from bc_inst to machine code: to_mc (POP::rest) = load r0,[r0+4] ++ to_mc rest We prove the correctness of this machine code implementation w.r.t. state assertion BYTECODE: (stack, pc, code) (stack, pc, code ) =) { PC (base + pc) * BYTECODE (stack,err) } base: to_mc code { PC (base + pc ) * BYTECODE (stack,err) PC err * true }

Bytecode machine code Define a function (to_mc) from bc_inst to machine code: to_mc (POP::rest) = load r0,[r0+4] ++ to_mc rest We prove the correctness of this machine code implementation w.r.t. state assertion BYTECODE: if bytecode executes step (stack, pc, code) (stack, pc, code ) =) { PC (base + pc) * BYTECODE (stack,err) } base: to_mc code { PC (base + pc ) * BYTECODE (stack,err) PC err * true }

Bytecode machine code Define a function (to_mc) from bc_inst to machine code: to_mc (POP::rest) = load r0,[r0+4] ++ to_mc rest We prove the correctness of this machine code implementation w.r.t. state assertion BYTECODE: if bytecode executes step (stack, pc, code) (stack, pc, code ) =) { PC (base + pc) * BYTECODE (stack,err) } base: to_mc code { PC (base + pc ) * BYTECODE (stack,err) PC err * true } then m.c. performs the same w.r.t. the BYTECODE assertion

Bytecode machine code Define a function (to_mc) from bc_inst to machine code: to_mc (POP::rest) = load r0,[r0+4] ++ to_mc rest We prove the correctness of this machine code implementation w.r.t. state assertion BYTECODE: if bytecode executes step (stack, pc, code) (stack, pc, code ) =) { PC (base + pc) * BYTECODE (stack,err) } base: to_mc code { PC (base + pc ) * BYTECODE (stack,err) PC err * true } then m.c. performs the same w.r.t. the BYTECODE assertion or jumps to err (due to lack of heap space)

BYTECODE assertion Proper definition: uses a real stack in memory (array). That s fast.

BYTECODE assertion Proper definition: uses a real stack in memory (array). That s fast. But to keep it simple, let s just use HEAP from previous lecture:

BYTECODE assertion Proper definition: uses a real stack in memory (array). That s fast. But to keep it simple, let s just use HEAP from previous lecture: stack can be packaged up in an s-expression: to_sexp [] = Sym NIL to_sexp (x::xs) = Dot x (to_sexp xs)

BYTECODE assertion Proper definition: uses a real stack in memory (array). That s fast. But to keep it simple, let s just use HEAP from previous lecture: stack can be packaged up in an s-expression: to_sexp [] = Sym NIL to_sexp (x::xs) = Dot x (to_sexp xs) we can now define: BYTECODE (stack,err) = HEAP (to_sexp stack,_,_,_,err)

BYTECODE assertion Proper definition: uses a real stack in memory (array). That s fast. But to keep it simple, let s just use HEAP from previous lecture: stack can be packaged up in an s-expression: to_sexp [] = Sym NIL to_sexp (x::xs) = Dot x (to_sexp xs) we can now define: BYTECODE (stack,err) = HEAP (to_sexp stack,_,_,_,err) Exercise: prove POP case correct for to_mc function (prev. slide).

Source Bytecode Initial example: (cons 1 2) i.e. Cons (Const (Val 1)) (Const (Val 2))

Source Bytecode Initial example: (cons 1 2) i.e. Cons (Const (Val 1)) (Const (Val 2)) is to compile to [PUSH (Val 1), PUSH (Val 2), CONS]

Source Bytecode Initial example: (cons 1 2) i.e. Cons (Const (Val 1)) (Const (Val 2)) is to compile to [PUSH (Val 1), PUSH (Val 2), CONS] Execution of this pushes Dot (Val 1) (Val 2) onto the stack, leaves rest of stack untouched.

Source Bytecode Initial example: (cons 1 2) i.e. Cons (Const (Val 1)) (Const (Val 2)) is to compile to [PUSH (Val 1), PUSH (Val 2), CONS] Execution of this pushes Dot (Val 1) (Val 2) onto the stack, leaves rest of stack untouched. Draft implementation: to_bc (Const x) = [PUSH x] to_bc (Cons e1 e2) = to_bc e1 ++ to_bc e2 ++ [CONS]

Correctness of compilation exp env result code stack pc. (exp, env) =) + (stack, 0, code) result to_bc exp = code (result :: stack, ilength code, code)

Correctness of compilation (exp, env) =) If big-step sem. terminates with result exp env result code stack pc. + (stack, 0, code) result to_bc exp = code (result :: stack, ilength code, code)

Correctness of compilation (exp, env) =) If big-step sem. terminates with result exp env result code stack pc. + (stack, 0, code) result to_bc exp = code (result :: stack, ilength code, code) then execution of generated bytecode pushes result onto stack.

Correctness of compilation (exp, env) =) If big-step sem. terminates with result exp env result code stack pc. + (stack, 0, code) result to_bc exp = code (result :: stack, ilength code, code) then execution of generated bytecode pushes result onto stack. Proof: by induction on +

Correctness of compilation (exp, env) =) If big-step sem. terminates with result exp env result code stack pc. + (stack, 0, code) result to_bc exp = code (result :: stack, ilength code, code) needs to be generalised for proof by induction then execution of generated bytecode pushes result onto stack. Proof: by induction on +

Correctness of compilation (exp, env) =) If big-step sem. terminates with result exp env result code stack pc. + (stack, 0, code) result to_bc exp = code (result :: stack, ilength code, code) needs to be generalised for proof by induction then execution of generated bytecode pushes result onto stack. Proof: by induction on + What about compilation of Var?

Correctness of compilation (exp, env) =) If big-step sem. terminates with result exp env result code stack pc. + (stack, 0, code) result to_bc exp = code (result :: stack, ilength code, code) needs to be generalised for proof by induction then execution of generated bytecode pushes result onto stack. Proof: by induction on + What about compilation of Var? need to modify compiler and theorem.

Compilation of Var and Let Implementation: to_bc st (Const x) = [PUSH x] to_bc st (Cons e1 e2) = to_bc st e1 ++ to_bc (NONE::st) e2 ++ [CONS] to_bc st (Var v) = [LOAD (index_of v st)] to_bc st (Let v e1 e2) = to_bc st e1 ++ to_bc (SOME v::st) e2 ++ [POP1] index_of v (x::st) = if x = SOME v then 0 else index_of v st + 1

Compilation of Var and Let Implementation: keeps track of where variables are to_bc st (Const x) = [PUSH x] to_bc st (Cons e1 e2) = to_bc st e1 ++ to_bc (NONE::st) e2 ++ [CONS] to_bc st (Var v) = [LOAD (index_of v st)] to_bc st (Let v e1 e2) = to_bc st e1 ++ to_bc (SOME v::st) e2 ++ [POP1] index_of v (x::st) = if x = SOME v then 0 else index_of v st + 1

Compilation of Var and Let Implementation: to_bc st (Const x) = [PUSH x] to_bc st (Cons e1 e2) = to_bc st e1 ++ to_bc (NONE::st) e2 ++ [CONS] to_bc st (Var v) keeps track of where variables are = [LOAD (index_of v st)] shape of stack to_bc st (Let v e1 e2) = to_bc st e1 ++ to_bc (SOME v::st) e2 ++ [POP1] index_of v (x::st) = if x = SOME v then 0 else index_of v st + 1

Compilation of Var and Let Implementation: to_bc st (Const x) = [PUSH x] to_bc st (Cons e1 e2) = to_bc st e1 ++ to_bc (NONE::st) e2 ++ [CONS] to_bc st (Var v) keeps track of where variables are = [LOAD (index_of v st)] to_bc st (Let v e1 e2) = to_bc st e1 ++ to_bc (SOME v::st) e2 ++ [POP1] Var compiles to LOAD shape of stack index_of v (x::st) = if x = SOME v then 0 else index_of v st + 1

Compilation of Var and Let Implementation: to_bc st (Const x) = [PUSH x] to_bc st (Cons e1 e2) = to_bc st e1 ++ to_bc (NONE::st) e2 ++ [CONS] to_bc st (Var v) keeps track of where variables are = [LOAD (index_of v st)] shape of stack to_bc st (Let v e1 e2) = to_bc st e1 ++ to_bc (SOME v::st) e2 ++ [POP1] Var compiles to LOAD Let expression can refer to bound var index_of v (x::st) = if x = SOME v then 0 else index_of v st + 1

Correctness (revisited) exp env result code stack pc st. (exp, env) =) + (stack, 0, code) result to_bc st exp = code stack_inv stack st env (result :: stack, ilength code, code)

Correctness (revisited) exp env result code stack pc st. (exp, env) =) + (stack, 0, code) result to_bc st exp = code stack_inv stack st env (result :: stack, ilength code, code) where stack_inv ensures that stack holds values of env according to st. stack_inv xs [] env = true stack_inv (x::xs) (NONE::st) env = stack_inv xs st env stack_inv (x::xs) (SOME v::st) env = stack_inv xs (del v st) env env(v) = x del v [] = [] del v (NONE::st) = NONE :: del v st del v (SOME w::st) = if v = w then NONE :: del v st else SOME w :: del v st

Function calls exp env result code stack pc st fns ctxt. (exp, fns, env) result stack_inv stack st env to_bc (st, ctxt, ilength c1) exp = c2 to_bc-compiled code for all function in ctxt exists in c1++c2 =) (stack, ilength c1, c1++c2) + (result :: stack, ilength (c1++c2), c1++c2)

Function calls function definitions (first-order Lisp) exp env result code stack pc st fns ctxt. (exp, fns, env) result stack_inv stack st env to_bc (st, ctxt, ilength c1) exp = c2 to_bc-compiled code for all function in ctxt exists in c1++c2 =) (stack, ilength c1, c1++c2) + (result :: stack, ilength (c1++c2), c1++c2)

Function calls function definitions (first-order Lisp) mapping from func. names to compiler info exp env result code stack pc st fns ctxt. (exp, fns, env) result stack_inv stack st env to_bc (st, ctxt, ilength c1) exp = c2 to_bc-compiled code for all function in ctxt exists in c1++c2 =) (stack, ilength c1, c1++c2) + (result :: stack, ilength (c1++c2), c1++c2)

Function calls function definitions (first-order Lisp) mapping from func. names to compiler info exp env result code stack pc st fns ctxt. (exp, fns, env) result stack_inv stack st env to_bc (st, ctxt, ilength c1) exp = c2 to_bc-compiled code for all function in ctxt exists in c1++c2 =) (stack, ilength c1, c1++c2) + (result :: stack, ilength (c1++c2), c1++c2) we assume all functions in ctxt have been compiled using to_bc

Function calls function definitions (first-order Lisp) mapping from func. names to compiler info exp env result code stack pc st fns ctxt. (exp, fns, env) result stack_inv stack st env to_bc (st, ctxt, ilength c1) exp = c2 to_bc-compiled code for all function in ctxt exists in c1++c2 =) (stack, ilength c1, c1++c2) + (result :: stack, ilength (c1++c2), c1++c2) c2 included to allow recurs we assume all functions in ctxt have been compiled using to_bc

Tail calls Efficient loops in FP are written as tail-recursion (using tail calls).

Tail calls Efficient loops in FP are written as tail-recursion (using tail calls). Two versions of fac: fac n = if n = 0 then 1 else n fac (n-1) fac n = f (n,1) where f (n,k) = if n = 0 then k else f (n-1,k n)

Tail calls Efficient loops in FP are written as tail-recursion (using tail calls). Two versions of fac: not a tail call, leaves n to be done after call fac n = if n = 0 then 1 else n fac (n-1) fac n = f (n,1) where f (n,k) = if n = 0 then k else f (n-1,k n)

Tail calls Efficient loops in FP are written as tail-recursion (using tail calls). Two versions of fac: not a tail call, leaves n to be done after call fac n = if n = 0 then 1 else n fac (n-1) fac n = f (n,1) where f (n,k) = if n = 0 then k else f (n-1,k n) tail call leaves no work in this function left to be done

Tail calls Efficient loops in FP are written as tail-recursion (using tail calls). Two versions of fac: not a tail call, leaves n to be done after call fac n = if n = 0 then 1 else n fac (n-1) fac n = f (n,1) where f (n,k) = if n = 0 then k else f (n-1,k n) tail call leaves no work in this function left to be done FP implementations must ensure that tail calls do not waste space.

Tail calls Efficient loops in FP are written as tail-recursion (using tail calls). Two versions of fac: not a tail call, leaves n to be done after call fac n = if n = 0 then 1 else n fac (n-1) fac n = f (n,1) where f (n,k) = if n = 0 then k else f (n-1,k n) tail call leaves no work in this function left to be done FP implementations must ensure that tail calls do not waste space. in our example: to_bc must rewind stack before tail-call otherwise, the subroutine cannot immediately return to caller

Closures What if we source sem. has closure values?

Closures What if we source sem. has closure values? Short answer: brings new complexity

Closures What if we source sem. has closure values? Short answer: brings new complexity Example: correctness theorem requires relation R exp env result. (exp, env) =) + x. (stack, ) result (x :: stack, ) R x result

Closures What if we source sem. has closure values? Short answer: brings new complexity Example: correctness theorem requires relation R exp env result. (exp, env) =) + x. (stack, ) result (x :: stack, ) R x result non-trivial relation between semantics values (e.g. closures) and bytecode values (e.g. code pointers)

Summary Compilation faster than interpreter-based implementation best staged, e.g. via bytecode language that operates on top of memory abstraction (from previous lecture) easy to compile to stack-based language tail-calls must be efficient

Summary Compilation faster than interpreter-based implementation best staged, e.g. via bytecode language that operates on top of memory abstraction (from previous lecture) easy to compile to stack-based language tail-calls must be efficient Correctness correctness statement non-trivial for non-trivial language proof by induction on big-step op. sem. closure values introduce complexity (topic of next lecture)

Summary Compilation faster than interpreter-based implementation best staged, e.g. via bytecode language that operates on top of memory abstraction (from previous lecture) easy to compile to stack-based language tail-calls must be efficient Correctness correctness statement non-trivial for non-trivial language proof by induction on big-step op. sem. closure values introduce complexity (topic of next lecture) guest lectures by Ramana Kumar