Compilation 2014 Activation Records Aslan Askarov aslan@cs.au.dk Revised from slides by E. Ernst
(Abstract) computer organization Program memory code segment contains program text data segment contains static program data (globals) stack for locals and function arguments heap for dynamically allocated memory Processors registers Operations for moving data between registers/memory }size fixed prior to runtime }size changes at runtime
Call stack of a program Higher addresses in memory contiguous region in memory that the program can use the stack grows from higher memory addresses to low ones (could be otherwise depending on architecture) Purpose of the stack: store local variables pass arguments store return values save registers Low addresses in memory stack limit set by OS
Call stack of a program Higher addresses in memory contiguous region in memory that the program can use the stack grows from higher memory addresses to low ones (could be otherwise depending on architecture) Purpose of the stack: store local variables pass arguments store return addresses save registers Low addresses in memory Q: what happens if we push past beyond stack limit? stack limit set by OS
Stack frames Function 1 calls Function 2 that calls Function 3 active functions, because they haven t returned yet Function 1 Function 1 Function 1 Function 2 Function 2 Function 2 Function 2 Function 2 Function 3 Function 3 Function 3 Idea: the maximum amount of memory that each function needs for its locals, temps, etc can be (usually) precomputed by the compiler Let s increase the stack by that much at once instead of many small increases Call the region of the stack corresponding to each active function that function s stack frame (also called activation record) Function 3 stack manipulations by each of the functions
Stack frames Function 1 calls Function 2 that calls Function 3 active functions, because they haven t returned yet Function 1 Function 1 activation record (or stack frame) for Function 1 Function 1 Function 2 Function 2 Function 2 Function 2 stack frame for Function 2 Function 2 Function 3 Function 3 Function 3 stack frame for Function 3 Function 3 stack manipulations by each of the functions
Frame pointer and Stack pointer Stack pointer (SP): points to the top of the stack FP activation record for Function 1 Frame pointer (FP): the value of SP at the time the frame got activated activation record for Function 2 SP Not allocated: garbage
Frame layout: calling conventions Cross-language calls important: using libraries Reasonable to follow a standard: calling convention Specifies stack frame layout, register usage, routine entry, exit code Likely C bias FP SP argument 2 argument 1 returnaddr localvar1 localvar2 storedr1 temp1 Not allocated: garbage
Typical frame layout Fits RISC architectures (such as MIPS) well Note staticlink Consider offsets from FP and SP: are all known at compile time? FP could be virtual, if frame size is fixed FP SP (prev. frame) arg_k arg_1 staticlink localvar_1 localvar_m returnaddr temp_1 temp_p saved_r1 saved_rt (args for next)
Our Tiger frame layout Fits x86, with simple register usage strategy Worth noting return address pushed automatically by call instruction FP non-virtual, always saved SP adjusted at runtime: arguments pushed old FP FP SP SP SP (prev. frame) arg_k arg_1 staticlink returnaddr saved_fp localvar_1 localvar_m returnaddr temp_1 temp_p nextarg_k nextarg_2 nextarg_1
Accessing Tiger frame slots old FP (prev.frame)... arg_k... arg_1 staticlink returnaddr Memory[saved_FP], Memory[FP+4*(2+k)] Memory[FP+4*(2+1)] Memory[FP+8] Memory[FP+4] Memory[staticLink]? FP SP SP SP SP saved_fp localvar_1... localvar_m temp_1... temp_p... (args for next) Memory[FP] Memory[FP-4*1] Memory[FP-4*m] Memory[FP-4*(m+1)] Memory[FP-4*(m+p)] Memory[FP-who_cares] Top of frame: known early Bottom: known later Activations
The Frame Pointer Relative concepts: caller/callee frames, routines On routine entry SP points to arg_1 or staticlink or returnaddr.. (will return to this later) Allocate new frame (same figure): push FP FP := SP SP := SP - framesize If SP fixed, may replace FP by SP - framesize ( virtual FP) FP SP (prev.frame)...???
Saving Registers Re: A frame/routine may be a caller or a callee Roles for registers: Caller-save vs. callee-save E.g. on MIPS: r16-r23 callee-save, others callersave Don t mess with r16-r23 / Don t rely on others Scenario: Nice if value in r3 is dead before call Scenario: Put long-lived value in r21 (think: loop)
Passing Parameters Pre-1960: Use globals, no recursion 1970 ies: Pass parameters on stack Later: 4-6 parameters in registers, rest on stack Experience: Few routines >6 parameters Our approach: Pass parameters on stack (fits x86, C calling conventions)
Why is register passing useful? Scenario: Calling f(a1..an) which calls g(z) As much as possible kept in registers (prev.frame)... saved_ri... staticlink Tempting claim: No difference Concept: Leaf routines How rare? Most invocations are leaves May not even need stack frame f frame g frame localvar_1... localvar_m returnaddr... saved_a1... staticlink localvar_1...
C extremely well-established, collaboration needed Address-of operator & applicable to arguments Varargs (printf) requires address arithmetics Allocate space for all arguments at end of frame, save only if address taken Some C Language Issues Address taken also known as escaping, may use separate analysis
Managing Return Addresses Return address not statically known (.. why?) Solution: Store return address Old approach: Push at call instruction New: Store in register at call instruction Non-leaf routines will have to write to the stack
Forcing Memory Storage Using registers a good default, may fail... Address of variable taken ( &, pass-by-reference) Variable used from nested function Variable too large for register (use several?) Pointer arithmetics used (C arrays) Spilling
Static links: concept The previous frame is not necessarily the one that is lexically scoped Record information about enclosing frames Pass nearest enclosing frame as hidden argument (static link) Keep display : global array of currently nearest frame at all nesting levels Lambda lifting: lots of arguments We use static links
Static Links: Example Code type tree = {key: string, left: tree, right: tree} function prettyprint(tree: tree): string = let var output := " " function write(s: string) = output := concat(output, s) function show(n: int, t: tree) = let function indent(s: string) = ( for i:=1 to n do write(" ") ; output := concat(output,s) ; write("\n")) in if t=nil then indent(".") else ( indent(t.key) ; show(n+1, t.left) ; show(n+1, t.right)) end in show(0,tree); output end Main uses of static link: Emphasized name introductions
Static Links: Example Stack type tree = {key: string, left: tree, right: tree} function prettyprint(tree: tree): string = let var output := " " function write(s: string) = output := concat(output, s) function show(n: int, t: tree) = let function indent(s: string) = ( for i:=1 to n do write(" ") ; output := concat(output,s) ; write("\n")) in if t=nil then indent(".") else ( indent(t.key) ; show(n+1, t.left) ; show(n+1, t.right)) end in show(0,tree); output end saved FP [prettyprint] [show] wanted [show] [indent] [write]
Static Links: Example Stack type tree = {key: string, left: tree, right: tree} function prettyprint(tree: tree): string = let var output := " " function write(s: string) = output := concat(output, s) function show(n: int, t: tree) = let function indent(s: string) = ( for i:=1 to n do write(" ") ; output := concat(output,s) ; write("\n")) in if t=nil then indent(".") else ( indent(t.key) ; show(n+1, t.left) ; show(n+1, t.right)) end in show(0,tree); output end static link saved FP [prettyprint] [show] wanted [show] [indent] [write]
Static Links: Call Nested type tree = {key: string, left: tree, right: tree} function prettyprint(tree: tree): string = let var output := " " function write(s: string) = output := concat(output, s) function show(n: int, t: tree) = let function indent(s: string) = ( for i:=1 to n do write(" ") ; output := concat(output,s) ; write("\n")) in if t=nil then indent(".") else ( indent(t.key) ; show(n+1, t.left) ; show(n+1, t.right)) end in show(0,tree); output end Case call nested : Static link for show from prettyprint is the frame of prettyprint itself, etc. SL = FP SL = FP
Static Links: Call Same type tree = {key: string, left: tree, right: tree} function prettyprint(tree: tree): string = let var output := " " function write(s: string) = output := concat(output, s) function show(n: int, t: tree) = let function indent(s: string) = ( for i:=1 to n do write(" ") ; output := concat(output,s) ; write("\n")) in if t=nil then indent(".") else ( indent(t.key) ; show(n+1, t.left) ; show(n+1, t.right)) end in show(0,tree); output end Case call same : Static link for show from show is the given static link SL = SL
Static Links: Call Less Nested type tree = {key: string, left: tree, right: tree} function prettyprint(tree: tree): string = let var output := " " function write(s: string) = output := concat(output, s) function show(n: int, t: tree) = let function indent(s: string) = ( for i:=1 to n do write(" ") ; output := concat(output,s) ; write("\n")) in if t=nil then indent(".") else ( indent(t.key) ; show(n+1, t.left) ; show(n+1, t.right)) end in show(0,tree); output end Case call outer : Static link for write from indent is found by following the static link twice SL = SL.SL
Modules for activations Goals: Frame should not depend on static links Semant should not depend on machine details in Frame Machine addresses and registers should be abstract where possible (especially in IRgen) Structure of our compiler Note the difference from the book, where top-level translation is implemented in Semant IRgen Top-level translation Translate Translation details Frame Temp Machine dependent elements
Structure for frames Module type FRAME encapsulates machine dependent details Uses abstract machine code addresses: Temp signature FRAME = sig type frame type access val newframe: { name: Temp.label, formals: bool list} -> frame val name: frame -> Temp.label val formals: frame -> access list val alloclocal: frame -> bool -> access end IRGen Translate Frame Temp Implementation: structure MipsFrame: FRAME = struct... end Semi-encapsulated usage: structure Frame: FRAME = MipsFrame
Structure for frames signature FRAME = sig type frame type access val newframe: { name: Temp.label, formals: bool list} -> frame val name: frame -> Temp.label val formals: frame -> access list val alloclocal: frame -> bool -> access end frame: info about formal parameters and local vars formals: bool list: escaping or not? val formals: create access descriptions of formals datatype access = InFrame of int InReg of Temp.temp val alloclocal: update frame for an extra var -> access
Understanding frame access NB: assume arg1 escapes arg1 arg2 arg3 InFrame(8) InFrame(0) InFrame(8) InFrame(12) InReg(t InReg(t InFrame(16) InReg(t InReg(t M[SP] := FP SP := SP - K save SP,-K,SP Viewshift FP := SP M[SP+K] := r2 M[FP+68] := i0 SP := SP - K t157 t157 t158 t158 x86 MIPS SPARC Viewshift may move data and handle changes due to call
Abstract registers and addresses Structure Temp provides and manages abstractions: Type temp represents registers (an unbounded set) Type label represents addresses namedlabel allows for labels controlled externally structure Temp: sig eqtype temp val newtemp: unit -> temp type label = Symbol.symbol val newlabel: unit -> label val namedlabel: string -> label... end = struct... end IRgen Translate Frame Temp
Adding static links Structure Translate: TRANSLATE hides translation details, adds support for static links to the bottom level Note: Translate.access Frame.access signature TRANSLATE = sig type level type access val outermost: level val newlevel: { parent: level, name: Temp.label, formals: bool list} -> level val formals: level -> access list val alloclocal: level -> bool -> access... end IRgen Translate Frame Temp
Environments updated Structure Env: ENV now extended to hold access information for variables and level information for functions signature ENV = sig... datatype enventry = VarEntry of { ty: ty, access: Translate.access} FunEntry of { formals: ty list,, result: ty, level: Translate.level, label: Temp.label}... end
Summary Procedural abstraction requires LIFO discipline Use stack push/pop complete frame Frame layout: Convention plus many considerations Our Tiger frame layout: Heavily InFrame-ish Concepts: caller/callee, -save registers, longevity Benefits of using registers, forces against it Static links: Purpose, computation Tiger software Frame, Translate, Temp. Abstract registers and addresses, environment update