{ MODULE 14 Static Storage Management The static storage required by a procedure, function or program block is calculated at compile-time by recording: (1) the storage required by local variables, formal parameters, and function results; (2) the storage required by temporaries used internally by the generated code; (3) the storage required to preserve 'extended references'; as the block is analysed. Storage for local variables and parameters is allocated using the WordSize field of the variable type's representation. The run-time addresses allocated reflect the order of declaration. Thereafter, temporaries may be allocated during analysis of the statement-part of the block to avoid repeated address or expression evaluation. To preserve vulnerable references to file buffer variables, dynamic variables, and record variant fields made in the context of a with-statement or in passing an actual-variable parameter, a 'lock stack' is embedded within each stack frame. The static storage requirement for the lock stack is calculated from the max- imum depth of the lock stack recorded during analysis of the statement part. Storage for the lock stack is reserved after the last temporary. The general layout of the run time stack-frames is: |-------------|<--- frame base | | | local | | variables | | | |-------------| | temporaries | |-------------| | lock-stack | !_____________! 13.1 Stack Frame Management The requirements for local-variable, temporary, and lock-stack storage of each block are logged in fields of a corresponding FrameRecord as the block is analysed. In particular: (1) NextOffset is the word offset of the next available storage word in the stack frame. (2) LockDepth and MaxDepth are respectively the current depth, and maximum depth to date of the lock stack. (3) Local variables requiring manual initialisation and finalisa- tion are recorded as anonymous items in the lists PresetList and PostsetList. (4) Value conformant arrays are accumulated in the list CAPlist. Corresponding entries in the PresetList and PostsetList are made after the code for handling the auxiliary variable has been generated. Since temporary and lock stack storage cannot be accumulated until the statement part of a block, its frame record must be preserved while nested local blocks are processed. Frame records for all open blocks are therefore held within a frame stack pointed to by the variable TopFrameEntry. To enable non-local variable access, each nested stack frame is identified by the value of the global variable FrameLevel. FrameLevel is incremented by each call to OpenStackFrame and decremented accordingly by CloseStackFrame. The stack of nested frame records is maintained by the following procedures: } program StaticStore; #include "globals.x" #include "semantics.pf" #include "varref.pf" #include "withstmts.pf" procedure StartManuals(var List: ManualList); visible; forward; procedure OpenStackFrame; visible; var TopFrame: FrameEntry; begin FrameLevel := FrameLevel + 1; new(TopFrame); with TopFrame^ do begin NextOffset := FirstOffset; LockDepth := 0; MaxDepth := 0; StartManuals(PresetList); StartManuals(PostsetList); StartList(CAPList); Next := TopFrameEntry end; TopFrameEntry := TopFrame end { openstackframe }; procedure CloseStackFrame; visible; var TopFrame: FrameEntry; begin TopFrame := TopFrameEntry; with TopFrame^ do begin DsposeList(CAPList); TopFrameEntry := Next end; dispose(TopFrame); FrameLevel := FrameLevel - 1 end { closestackframe }; procedure SaveStackFrame; visible; begin with Display[ScopeLevel] do begin SavedFrame := TopFrameEntry; TopFrameEntry := TopFrameEntry^.Next end; FrameLevel := FrameLevel - 1 end { savestackframe }; procedure ReclaimStackFrame; visible; begin with Display[ScopeLevel] do begin SavedFrame^.Next := TopFrameEntry; TopFrameEntry := SavedFrame end; FrameLevel := FrameLevel + 1 end { restorestackframe }; procedure InitFrames; visible; begin FrameLevel := 0; new(TopFrameEntry); with TopFrameEntry^ do begin NextOffset := FirstOffset; LockDepth := 0; MaxDepth := 0; StartManuals(PresetList); StartManuals(PostsetList); Next := nil end end { initframes }; { The following procedures enable the allocation of variable and lock-stack locations within the current stack frame: } procedure Acquire(Amount: WordRange; var Address: RuntimeAddress); visible; begin with TopFrameEntry^ do begin Address.BlockLevel := FrameLevel; Address.WordOffset := NextOffset; NextOffset := NextOffset + Amount end end { acquire }; procedure PushLock; visible; begin with TopFrameEntry^ do begin LockDepth := LockDepth + 1; if LockDepth > MaxDepth then MaxDepth := LockDepth end end { pushlock }; procedure PopLocks(OldDepth: Scalar); visible; begin with TopFrameEntry^ do LockDepth := OldDepth end { poplocks }; { 13.2 Storage and Address Allocation Storage and address allocation are performed for each declared variable by a call to the procedure SetAddressFor. Each variable and function IdRecord carries with it a field of type RuntimeAd- dress whose internal fields BlockLevel and WordOffset respectively locate the variable within a nested stack frame at a particular word offset from the start of the frame. SetAddressFor calls the procedure SelectManuals to decide if the allocated variable should be added to the Pre- or PostsetList. The procedure SetBoundPairBlockFor is called to allocate storage for the bound pair block associated with each conformant array schema, and to map the associated bound-identifiers onto the first and second words of the block. } procedure SelectManual(Action: TypeActions; var List: ManualList; LocalId: IdEntry); forward; procedure SetAddressFor (VarId : IdEntry); visible; var Address: RuntimeAddress; WordsNeeded: WordRange; begin with VarId^ do case Klass of Vars : begin if (VarKind = VarParam) or (IdType^.Form = CAPSchema) then WordsNeeded := 1 else WordsNeeded := IdType^.Representation.WordSize; Acquire(WordsNeeded, Address); VarAddress := Address; if VarKind <> VarParam then with TopFrameEntry^ do if IsConformant(IdType) then AppendId(CAPList, VarId) else if VarKind = LocalVar then begin SelectManual(Presets, PresetList, VarId); SelectManual(Postsets, PostsetList, VarId) end end; Proc, Func : if PfKind = Formal then begin Acquire(2, Address); FAddress := Address end else begin WordsNeeded := IdType^.Representation.WordSize; Acquire(WordsNeeded, Address); Result := Address end end end { setaddressfor }; procedure SetBoundPairBlockFor ( LowBound, HighBound : IdEntry ); visible; var Address: RuntimeAddress; begin Acquire(CAPBpSize, Address); LowBound^.BdAddress := Address; with Address do WordOffset := WordOffset + 1; HighBound^.BdAddress := Address end { setboundpairblock }; { 13.3 Variable Initialisation and Finalisation If runtime checks have been requested, each local variable must be preset on entry to any block such that its value is initially 'undefined'. This is achieved in one of three ways: (1) The P-machine automatically presets each new stack frame so that every storage location therein holds a default undefined pattern. This suffices for all scalar local variables and for all unpacked structured types except files. (2) For structured variables occupying a single word but with embedded packed structure such as: var a : packed array [1..2] of char in-line code is generated to deposit the structured check value into the bitfields occupied by a[1] and a[2]. (3) For file-types and all other types which must be preset manu- ally by type-specific procedures, code is generated to call the preset procedure, passing the appropriate variable address as parameter. Given the declaration part: var i,j : integer ; b : Boolean ; a : array [1..100, 1..100] of real ; r : record c : char ; s : 0..10 ; t : (on, off) end ; s : set of char ; the automatic presetting undertaken by the P-machine is sufficient to initialize every variable. Given the declarations: var r : packed record b1,b2 : boolean end ; s : record l : 0..255 ; t : packed array [0..255] of char end ; f : text ; manual code would be required to preset r in-line, and to preset s and f by calling the pre-compiled type-presetting procedure. Presetting code is generated prior to code for the statement part of a block. File variables, or variables containing embedded file components, also need post-setting code which must be executed prior to leaving a block. An internally declared statement label FinalPart is sited at the start of the post-setting code-sequence by a call to NextIsStatementLabel. As explained in Chapter 22, this ensures that code will be generated to void the lock stack, therebye discarding all references extended within the current block. The declaration and siting of FinalPart is achieved by calls to FutureStatementLabel and NextIsStatementLabel made from procedures Block and PostsetBlock in Chapter 10. On exiting a block by means of a non-local goto, care must be taken to finalize all active blocks nested within the destination global block. The procedure SelectManual is used by SetAddressFor and by PresetRecord to determine if a variable or field should be added to a corresponding Pre- or PostsetList. As these lists are built, items of similar shape are merged to achieve optimization when variables of the same type are declared together. For example, given var r1,r2,r3 : packed record b1,b2 : boolean end ; the two preset values for b1 and b2 can be combined into a compo- site value 10, and this value can be preset repeatedly into the consecutive word locations for r1, r2 and r3. The manual preset and postset lists are built by procedures: } procedure StartManuals { var List : ManualList }; begin List.FirstEntry := nil; List.LastEntry := nil end { StartManuals }; procedure AppendManual(var List: ManualList; Entry: ManualEntry); visible; begin with List do begin if FirstEntry = nil then FirstEntry := Entry else LastEntry^.NextItem := Entry; LastEntry := Entry end end { appendmanual }; procedure CreateItem(Representation: TypeRepresentation; var Item: ManualEntry); visible; begin { new(Item); with Item^ do begin ItsRepresentation := Representation; ItsOffset := DefaultOffset; Repeated := 1; NextItem := nil end } end { createitem }; procedure SetItShape(Item: ManualEntry; ItsClass: IdClass); visible; begin { with Item^ do if ItsClass = Field then ItsShape := ItemShapes [ItsOffset.PartWord, StructuredWord(ItsRepresentation)] else if StructuredWord(ItsRepresentation) then ItsShape := WordStructure else ItsShape := Word } end { setitsshape }; procedure SetItCode(Item: ManualEntry; ItsClass: IdClass; Action: TypeActions); visible; begin { with Item^ do begin case Action of Presets, ValueChecks : if EnvelopeUsed(ItsRepresentation, Action) then ItsCode := Indirect else if not (Checks in Requested) then ItsCode := None else if ItsClass = Field then ItsCode := Inline else if ItsShape = WordStructure then ItsCode := Inline else ItsCode := None; Postsets : if EnvelopeUsed(ItsRepresentation, Postsets) then ItsCode := Indirect else ItsCode := None end end } end { setitemcode }; function SameWordSize(r1, r2: TypeRepresentation): Boolean; begin SameWordSize := r1.WordSize = r2.WordSize end; function SameCheckValues(r1, r2: TypeRepresentation): Boolean; begin SameCheckValues := r1.CheckValue.Magnitude = r2.CheckValue.Magnitude end { samecheckvalues }; function SameLabels(l1, l2: BlockLabel): Boolean; begin SameLabels := (l1.BlockLevel = l2.BlockLevel) and (l1.EntryOffset = l2.EntryOffset) end { samelabels }; procedure SelectManual { Action: TypeActions; var List: ManualList; LocalId : IdEntry }; var NewItem: ManualEntry; procedure AbsorbManual; var Merging: Boolean; begin Merging := false; if List.LastEntry <> nil then with List.LastEntry^ do if ItsCode = NewItem^.ItsCode then case ItsCode of Indirect : if ItsRepresentation.Kind <> ForCAP then case Action of Presets : Merging := SameLabels (ItsRepresentation.PresetCode, NewItem^.ItsRepresentation.PresetCode); Postsets : Merging := SameLabels (ItsRepresentation.PostsetCode, NewItem^.ItsRepresentation.PostsetCode); ValueChecks : Merging := SameLabels (ItsRepresentation.CheckCode, NewItem^.ItsRepresentation.CheckCode) end; Inline : if ItsShape = NewItem^.ItsShape then case ItsShape of Word : Merging := SameWordSize (ItsRepresentation, NewItem^.ItsRepresentation); WordStructure : Merging := SameCheckValues (ItsRepresentation, NewItem^.ItsRepresentation); PartWrd, PartWrdStructure : Merging := false end; None : Merging := true end; if Merging then begin with List.LastEntry^ do Repeated := Repeated + 1; dispose(NewItem) end else AppendManual(List, NewItem) end { absorbmanual }; begin with LocalId^ do begin CreateItem(IdType^.Representation, NewItem); with NewItem^ do if Klass = Field then ItsOffset := Offset else ItsOffset.WordOffset := VarAddress.WordOffset; SetItShape(NewItem, Klass); SetItCode(NewItem, Klass, Action); AbsorbManual end end { selectmanual }; procedure DisposeManuals(var List: ManualList); visible; var OldEntry: ManualEntry; begin with List do while FirstEntry <> nil do begin OldEntry := FirstEntry; FirstEntry := OldEntry^.NextItem; dispose(OldEntry) end; List.LastEntry := nil end { disposemanuals }; { The generation of manual preset and postset code is triggered by calls to the procedures InitialiseVariables and FinaliseVariables. Variables requiring manual pre- or postset code are treated as fields of a conceptual record and transformed into anonymous items identified by a field offset deduced from the runtime address. Preset code is emitted by the procedure PresetManuals according to the contents of the PresetList. The PostsetList is processed similarly by procedure PostsetManuals. Details of PresetManuals and PostsetManuals are deferred to Chapter 25. } procedure PresetManuals(ManualScope: DispRange; List: ManualList); visible; begin end; procedure PostsetManuals(ManualScope: DispRange; List: ManualList); visible; begin end; procedure SelectCAPS(Action: TypeActions); var CAPEntry: ListEntry; begin with TopFrameEntry^ do begin CAPEntry := CAPList.FirstEntry; while CAPEntry <> nil do begin case Action of Presets : SelectManual(Presets, PresetList, CAPEntry^.Id); Postsets : SelectManual(Postsets, PostsetList, CAPEntry^.Id) end; CAPEntry := CAPEntry^.Next end end end { selectcaps }; procedure InitializeVariables; visible; var FrameBase: RuntimeAddress; begin with TopFrameEntry^ do begin SelectCAPS(Presets); FrameBase.BlockLevel := FrameLevel; FrameBase.WordOffset := FirstOffset; StackReference(false, FrameBase); OpenWith(FrameLevel); PresetManuals(FrameLevel, PresetList); CloseWith; DisposeManuals(PresetList) end end { initialisevariables }; procedure FinalizeVariables; visible; var FrameBase: RuntimeAddress; begin with TopFrameEntry^ do begin SelectCAPS(Postsets); FrameBase.BlockLevel := FrameLevel; FrameBase.WordOffset := FirstOffset; StackReference(false, FrameBase); OpenWith(FrameLevel); PostsetManuals(FrameLevel, PostsetList); CloseWith; DisposeManuals(PostsetList) end end { finalizevariables }; begin { end of module } end.