{ MODULE 6 Object Program Generation Object program generation is embedded within the syntactic- semantic analyser as a set of procedure calls. These calls and the types underlying their parameter lists, provide a generation interface which is independent of the precise object code to be generated. Between calls, the analyser stores and transmits data of these types but without any detailed knowledge of their inter- nal nature. Likewise, the generative procedures called may operate without any knowledge of the analyser's functioning or of the structures within which it stores the common data. In practice however it is unnecessarily cumbersome in a one pass system to segregate the analyser's data on types from those which deter- mine and describe their representation in the object program. In these situations the interface allows the generative pro- cedures to access the data-structures built by the analyser. } program Interface; #include "globals.x" #include "objvalues.pf" { 6.1 Representation of Data Types The representation and storage of data within the object program is described by the compiler as follows: 1. Each TypEntry carries a field Representation of type TypeRepresentation which describes how such data are to be represented in the object program. 2. Each IdEntry for a directly referenceable stored data item (variable, formal parameter or function result), car- ries a corresponding field of type RunTimeAddress which holds the neccessary address co-ordinates for the run-time access of that data. 3. Each IdEntry for a record field name carries a field Offset of type FieldOffset which specifies the field's run-time co-ordinates relative to those of the record as a whole. Representations and field offsets are generated by the procedure SetRepresentationFor which determines the representation for a given type. For record types it also determines the field offset for each field identifier. } {procedure SetRepresentationFor(TheType: TypEntry); visible; begin end; } { Because any component array of a multi-dimensional array may be referenced individually, the representation of each dimension must be fully and separately specified. For structured types in gen- eral the representation of any component type must be set before that of the structured type. In the case of multidimensional array types, therefore, the representation of each dimension is set in turn, from the innermost to the outermost dimension. Thus the type-definition multi = array [1..10,-100..100] of T ; gives rise to the calls: SetRepresentationFor(TypEntry for array[-100..100] of T) SetRepresentationFor(TypEntry for array[1..10] of array...) For record types the linear lists of fields and variant structure entries located within the record type entry allow all field offsets and alternative variant representations to be determined by a single call to SetRepresentationFor. For the proper implementation of files, and for the secure imple- mentation of variables of other types, hidden procedures must be generated for initialising, finalising and intermediate inspection of these variables. In general this may be achieved by generating a module of internal or 'hidden' procedures that perform type- specific presetting, postsetting and inspection, and recording their entry-points in the 'representation' of the type entry. The generation of these internal procedures is assumed to be triggered by SetRepresentationFor to complete the representation of the type. Variables subsequently declared with the type may be ini- tialized, finalized and checked by passing the variable address as a parameter to the appropriate internal procedure. } procedure AdjustRepresentationFor(TheType: TypEntry); visible; begin end; { ICL Pascal permits the representation of any new type T1 to be adjusted to fit the representation of some other type T2. T1 is designated as occupying T2, and is said to be a sub-type of T2. In this case, the call to SetRepresentationFor is followed by a cal to procedure AdjustRepresentationFor and the TypEntry for T1 will contain a field Occupying which links to the TypEntry for T2. For T1 to be a valid sub-type of T2 it is necessary to check that the storage requirement for T2 can indeed accommodate a variable of type T1, and then to update the representation of T1 accord- ingly. Note that any other information embedded in the Representa- tion field of T1 (such as that to enable the generation of runtime checks, must remain intact.) 6.2 Variable Storage Allocation and Initialisation 6.2.1 Stack Frames and Variable Storage Allocation of storage for variable data is handled by the follow- ing procedures: } {procedure OpenStackFrame; visible; begin end; procedure CloseStackFrame; visible; begin end; procedure SaveStackFrame; visible; begin end; procedure ReclaimStackFrame; visible; begin end; procedure SetAddressFor(VarId: IdEntry); visible; begin end; } { Runtime storage for all variable data, other than dynamically allocated variables, is assumed to lie within a conventional run- time stack. This requires supporting compile-time analysis to compute the layout and total storage requirement for each stack frame. Procedure OpenStackFrame is used to initiate an image of the stack frame corresponding to the current block scope. Storage is then allocated within the stack frame as the parameters and variables of the block are declared. During analysis of the statement part of the block, additional storage may be required by the generated object code to hold saved operands, addresses and so on. When the block scope is closed, the analyser calls procedure CloseStackFrame to discard the record of the current stack frame. To handle forward procedures and functions in a consistent manner, the analyser 'saves' and 'restores' the state of the current stack frame of every procedure and function between analysis of its heading and analysis of the corresponding block by calling pro- cedures SaveStackFrame and ReclaimStackFrame. For normal pro- cedures and functions, these calls are adjacent, but for forward procedures and functions, analysis of other procedures and func- tions may intervene. During analysis of declarations, variable, formal parameter and function result address co-ordinates are assigned within the current stack frame by calls to the procedure SetAddressFor. The example below indicates the manner in which a stack-frame is created for a function block-scope. Note that the function result is assigned an address which is saved in the result field of the function identifier entry. Thus: function F (A,B, : integer) : real ; type T = 1..10 ; var C : T ; begin ...... results in the following sequence of interface calls: OpenStackFrame SetAddressFor(IdEntry for A) SetAddressFor(IdEntry for B) SaveStackFrame ReclaimStackFrame SetAddressFor(IdEntry for F) SetRepresentationFor(TypEntry for 1..10) SetAddressFor(IdEntry for C) The stack-frame is closed on completing analysis of the statement part of the block. For convenience of analysis, storage allocation is also signalled for the parameter lists of formal procedures and functions, but the resulting stack frame is discarded as soon as the formal pro- cedure or function heading has been processed. 6.2.2 Conformant Array Parameter Representation The representation associated with (each dimension of) a confor- mant array schema is generated in a similar manner to that of a normal array type. However, for a conformant array schema each index-type-specification is represented by the allocation of a 'bound-pair block' of storage locations within the stack frame of the procedure or function concerned. During activation of the procedure or function, the values denoted by the pair of bound- identifiers contained by the index-type-specification, are held within implementation-defined locations of the bound-pair block. This block and the addresses of the bounds held within it are allocated by the procedure: } {procedure SetBoundPairBlockFor(LowBound, HighBound: IdEntry); visible; begin end; } { Conceptually, a bound-pair block associated with an outer dimen- sion of a multi-dimensional conformant array schema is regarded as embracing the blocks associated with index-type-specifications of its component conformant-array-schema. In practice this is achieved by sequential allocation of bound-pair blocks from the outermost to the innermost dimension, so that the address of the outer bound-pair block gives implicit access to the immediately following inner blocks. For example, the formal parameter declaration: A,B : array [M1..N1: integer; M2..N2: integer] of real results in the following sequence of interface calls: SetBoundPairBlockFor(IdEntry for M1, IdEntry for N1) SetBoundPairBlockFor(IdEntry for M2, IdEntry for N2) SetRepresentationFor(TypEntry for array [M2..N2: integer] of real) SetRepresentationFor(TypEntry for array [M1..N1: integer] of ... ) SetAddressFor(IdEntry for A) SetAddressFor(IdEntry for B) The call to SetAddressFor for each conformant array parameter allocates storage for a descriptor or reference which, in conjunc- tion with the corresponding bound pair blocks, enables access to the actual array. Allocation of the storage occupied by value conformant array parameters is discussed in Sections 6.2.3 and 6.11. In ICL Pascal, a conformant array schema may be specified with a constant bound. This may result in compacting either the calling sequences to such procedures (by reducing the number of actual bounds that have to be passed), or improving element access code. The precise benefits however cannot be quantified without prior knowledge of the actual representation chosen for a bound-pair block. 6.2.3.1 Variable Initialisation and Finalisation Initialisation and finalisation of variables at block entry and exit are generated by the procedures: } {procedure InitializeVariables; visible; begin end; procedure FinalizeVariables; visible; begin end; } { In principle, each variable that is designated "undefined" at the start of a block must be initialised at block entry. In practice the analyser makes a single call to procedure InitialiseVariables to trigger the collective presetting of the local variables before code-generation for the statement part begins. In general, the parameters of a procedure or function may be omit- ted from the initialisation process. However, the auxiliary vari- able associated with a value conformant array parameter may be created at this time using available heap storage, and initialised with the contents of the actual array. The auxiliary variable must then be mapped onto the formal parameter by overwriting the actual array reference. A complementary discussion on the auxili- ary variable is contained in Section 6.11. The procedure FinaliseVariables is called analagously immediately prior to leaving the block. In practice only file variables and value conformant array parameters need be processed: in the first case to close and terminate the file, and in the second case to discard the auxiliary variable. 6.2.3.2 Variable Presets In ICL Pascal, global variables may be initialised with valid values supplied in the preset or readonly declaration parts. Such variables will be defined on entry to the program block, and will hold values given by the initialisation part of the variable declaration. In practice, such initialisation is usually achieved by presetting an image of the global data segment at compile-time, although the implementor is under no obligation to adopt this approach. Accordingly, the code-generation interface provides the following general procedures for variable presetting, and leaves open the final decision as to whether or not they generate execut- able code. } procedure OpenPresetSpace(Representation: TypeRepresentation); visible; begin end; procedure InxPreset(PackedArray: Boolean; LowIndex, HighIndex, LowBound: ObjectValue; Representation: TypeRepresentation); visible; begin end; procedure FieldPreset(Field: FieldOffset); visible; begin end; procedure PresetSelector(VarPartRep, VariantRep: TypeRepresentation); visible; begin end; procedure AssignPreset; visible; begin end; procedure CopyPreset; visible; begin end; procedure StackPreset; visible; begin end; procedure ClsPresetSpace; visible; begin end; { Conceptually, each variable is preset by assigning the contents of a preset-space to the variable when storage for the variable is allocated. A preset-space may be thought of as a work variable which the program does not otherwise contain. Both the dimensions and manner in which the preset space is initialised are determined from the type of the variable concerned. Accordingly, a call to OpenPresetSpace is furnished with a TypeRepresentation parameter, whose contents indicate the size of preset space required. The actual parameter value is simply the Representation field of the TypEntry for the type concerned. For scalar types, the preset space is homogeneous and is initial- ised by a single call to the procedure AssignPreset. Thus the code fragment preset i,j,k: integer := 0; gives rise to the call sequence OpenPresetSpace(IntegerRepresentation); StackConstant(ZeroValue); AssignPreset; StackPreset; SetAddressFor(Identry for i); SetAddressFor(Identry for j); SetAddressFor(Identry for k); ClosePresetSpace; The initialised preset space is stacked by the call to StackPreset and its contents copied by SetAddressFor into the storage loca- tions created for i, j, and k. The preset space is finally unstacked and discarded by the call to ClosePresetSpace. Pointer and set variables also adhere to this scheme. Thus preset Head, Tail: ListPointer := nil; Letters: set of char := ['A'..'Z', 'a'..'z']; gives rise to the sequence OpenPresetSpace(PointerRepresentation); StackConstant(NilValue); AssignPreset; StackPreset; SetAddressFor(Identry for Head); SetAddressFor(Identry for Tail); ClosePresetSpace; OpenPresetSpace(Representation for set of char); . . sequence of calls to construct the set constant ['A'..'Z', 'a'..'z'] . . AssignPreset; StackPreset; SetAddressFor(Identry for Letters); ClosePresetSpace; For a record variable, preset values must be specified for all the fields of the fixed and variant parts. ICL Pascal provides two schemas referred to as named and positional presetting. In the first, fields are designated by name and initialised in the order in which they are nominated. In the second, fields are initialised according to the order in which they are declared. Either schema must be used consistently throughout any single field-list, although the two schemas may be mixed over the record-type as a whole. Thus the fixed-part may be preset using the positional schema, whilst the variant-part may be preset using the named schema. In either case, the preset-space is divided into sub- spaces, whose offsets and dimensions are respectively determined by the offset and type of each field-variable. Each sub-space is then preset before the entire preset-space is assigned to the record variable as a whole. For example, the fragment preset r1, r2: rec := (f1&f2 => 0.0, i=>100, p=>nil); gives rise to the following series of calls: OpenPresetSpace(Representation of rec); FieldPreset(Offset for f1); FieldPreset(Offset for f2); OpenPresetSpace(RealRepresentation); StackConstant(RealZeroValue); AssignPreset; StackPreset; CopyPreset; FieldPreset(Offset for i); OpenPresetSpace(IntegerRepresetation); StackConstant(Value of 100); AssignPreset; StackPreset; CopyPreset; FieldPreset(Offset for p); OpenPresetSpace(PointerRepresentation); StackConstant(NilValue); AssignPreset; StackPreset; CopyPreset; StackPreset; SetAddressFor(Identry for r1); SetAddressFor(Identry for r2); ClosePresetSpace Each of the calls to CopyPreset assigns the contents of the sub- space to the fields whose offsets have been given in preceding calls to FieldPreset. In this simple case, the sub-spaces are scalar, but in general they may themselves possess arbitrarily complex structure. For record-types possessing a variant-part, the selector will in general need to be preset independently of the tag-field. This invisible preset operation is executed by procedure PresetSelec- tor, which receives as its parameters, the representation of the TypEntry describing the variant-part, and the representation of the variant to be activated. The first of these is assumed to con- tain the offset of an invisible selector field that exists independently of the tag-field, and the second contains the inter- nal identity of the activated variant. If a tag-field does exist, then it is preset as a normal record field. For example, given the fragments type vrec = record case t: Boolean of false: (i: integer); real: (r: real) end; preset r: rec := (true, 0); gives rise to the calls OpenPresetSpace(Representation for vrec); PresetSelector(variant-part representation, 'false' variant representation); FieldPreset(Offset of t); OpenPresetSpace(BooleanRepresentation); StackConstant(FalseValue); AssignPreset; StackPreset; CopyPreset; FieldPreset(Offset of i); OpenPresetSpace(IntegerRepresentation); StackConstant(ZeroValue); AssignPreset; StackPreset; CopyPreset; StackPreset; SetAddressFor(Identry for r); ClosePresetSpace; Array-types may be preset by indexed and positional schemas. In the first case, an element or range of elements is selected by an index value or range, and in the second case, elements are selected in row-major order. Each schema must be applied con- sistently to a given array dimension, but may be mixed over the array as a whole. For a given dimension, the preset-space is divided into sub-spaces whose dimensions and offset are given from the representation of the element-type, and the index-value or range for the selected elements. Thus the fragment preset a: array[1..10] of integer := (1..5=>1, otherwise =>0); gives rise to the calls OpenPresetSpace(Representation for array) IndexedPreset( .. Value(1),Value(5), ..); OpenPresetSpace(IntegerRepresentation); StackConstant(OneValue); AssignPreset; StackPreset; CopyPreset; IndexedPreset( .. ,Value(6),Value(10), .. ); OpenPresetSpace(IntegerRepresentation); StackConstant(ZeroValue); AssignPreset; StackPreset; CopyPreset; StackPreset; SetAddressFor(Identry for a); ClosePresetSpace; The procedure IndexedPreset supplies all information necessary to allow the offsets of the element ranges to be calculated. The sub-spaces for individual elements are preset by the nested preset call sequences, and the resulting preset pattern is then propogated into the elements field nominated by the index-ranges. In general, an array element may itself have arbitrarily complex sub-structure. 6.3 Representation of Literal Values The representation of literal values appearing in the analysed program must be determined by the code-generator. To enable this, the record type ValueDetails is provided. The analyser inserts the source representation of each literal into such a record, and then requests the code-generator to derive the corresponding object-value by calling the procedure Evaluate. This object-value is used thereafter to represent the literal across the interface. } {procedure Evaluate(var SourceValue: ValueDetails); visible; var Index: 1..LineMax; Digit: 0..15; Base: integer; c: char; Wd: ICLWord; begin with SourceValue do case Kind of OrdValue : Velue.IVal := IVal; IntValue : begin Velue.IVal := 0; for Index := 1 to Length do begin Digit := ord(String[Index]) - ord('0'); if (Velue.IVal > maxint div 10) or ((Velue.IVal = maxint div 10) and (Digit > maxint mod 10)) then Velue.IVal := maxint else Velue.IVal := 10 * Velue.IVal + Digit end end; BaseValue : begin Base := Velue.IVal; Velue.IVal := 0; for Index := 1 to Length do begin c := String[Index]; if (c in ['0'..'9']) then Digit := ord(c) - ord('0') else Digit := ord(c) - ord('A') + 10; if (Velue.WVal > MaxWord div Base) or ((Velue.WVal = MaxWord div Base) and (Digit > MaxWord mod Base)) then Velue.WVal := MaxWord else Velue.WVal := Base * Velue.WVal + Digit end; end; CharValue : Velue.IVal := ord(String[1]); RealValue, StringValue : end end ;} { evaluate } procedure MakeValue(Magnitude: integer; var Velue: ObjectValue); visible; var Buffer: ValueDetails; begin with Buffer do begin Kind := OrdValue; IVal := Magnitude end; Evaluate(Buffer); Velue := Buffer.Velue end { MakeValue }; { A number of semantic checks performed by the analyser require that the generator provides for comparison and negation of object- values. These are performed by the procedures: } {procedure NegateValue(var Velue: ObjectValue); visible; begin Velue.IVal := -Velue.IVal end; function SameValue(Value1, Value2: ObjectValue): Boolean; visible; begin SameValue := (Value1.IVal = Value2.IVal) end; function OrderedValues(Value1, Value2: ObjectValue): Boolean; visible; begin OrderedValues := (Value1.IVal < Value2.IVal) end; } function Disjoint(Value1, Value2, Value3, Value4: ObjectValue): Boolean; visible; begin Disjoint := OrderedValues(Value2, Value3) or OrderedValues(Value4, Value1) end { Disjoint }; function InRange(Value1, Value, Value2: ObjectValue): Boolean; visible; begin InRange := not Disjoint(Value1, Value2, Value, Value) end { InRange }; procedure CheckValue(Min, Max, Value: ObjectValue; Code: Scalar); visible; begin end; { The function: } {function Range(Min, Max: ObjectValue): integer; visible; } { Computes the range defined by two object values within } { the limits of the host integer-space. The value maxint } { is returned for those values whose range cannot be } { represented by a host integer value. } { var Finite: Boolean; begin if Max.IVal < 0 then Finite := true else Finite := (Max.IVal - maxint) < Min.IVal; if Finite then Range := Max.IVal - Min.IVal + 1 else Range := maxint end ; } { range } { is provided to enable the analyser to check the completeness of variant record parts, using the cardinality of the tag-type. In addition, the procedures } procedure AddWords(LVal, RVal: ObjectValue; var Result: ObjectValue); visible; begin if LVal.WVal <= MaxWord - RVal.WVal then Result.WVal := LVal.WVal + RVal.WVal else Result.WVal := MaxWord end; procedure SubWords (LVal, RVal: ObjectValue; var Result: ObjectValue); visible; begin if LVal.WVal >= RVal.WVal then Result.WVal := LVal.WVal - RVal.WVal else Result.WVal := 0 end; procedure AddValues (LVal, RVal: ObjectValue; var Result: ObjectValue); visible; var Overflows, Underflows: Boolean; begin Overflows := false; Underflows := false; if (LVal.IVal > 0) and (RVal.IVal > 0) then Overflows := LVal.IVal >= maxint - RVal.IVal; if (LVal.IVal < 0) and (RVal.IVal < 0) then Underflows := LVal.IVal <= -maxint - RVal.IVal; if Overflows then Result.IVal := maxint else if Underflows then Result.IVal := -maxint else Result.IVal := LVal.IVal + RVal.IVal end; procedure SubValues(LVal, RVal: ObjectValue; var Result: ObjectValue); visible; var Overflows, Underflows: Boolean; begin Overflows := false; Underflows := false; if (LVal.IVal > 0) and (RVal.IVal < 0) then Overflows := LVal.IVal >= maxint + RVal.IVal; if (LVal.IVal < 0) and (RVal.IVal > 0) then Underflows := LVal.IVal <= -maxint + RVal.IVal; if Overflows then Result.IVal := maxint else if Underflows then Result.IVal := -maxint else Result.IVal := LVal.IVal - RVal.IVal end; procedure NotValues(LVal, RVal: ObjectValue; var Result: ObjectValue); visible; begin if LVal.IVal = 1 then Result.IVal := 0 else Result.IVal := 1 end; procedure OrValues(LVal, RVal: ObjectValue; var Result: ObjectValue); visible; begin if (LVal.IVal = 1) or (RVal.IVal = 1) then Result.IVal := 1 else Result.IVal := 0 end; procedure AndValues (LVal, RVal: ObjectValue; var Result: ObjectValue); visible; begin if (LVal.IVal = 1) and (RVal.IVal = 1) then Result.IVal := 1 else Result.IVal := 0 end; procedure ModValues (LVal, RVal: ObjectValue; var Result: ObjectValue); visible; begin if RVal.IVal <= 0 then Result.IVal := 0 else Result.IVal := LVal.IVal mod RVal.IVal end; procedure DivValues (LVal, RVal: ObjectValue; var Result: ObjectValue); visible; begin if RVal.IVal = 0 then Result.IVal := maxint else Result.IVal := LVal.IVal div RVal.IVal end; procedure MultiplyValues (LVal, RVal: ObjectValue; var Result: ObjectValue); visible; begin if abs(LVal.IVal) <= maxint div abs(RVal.IVal) then Result.IVal := LVal.IVal * RVal.IVal else if (LVal.IVal < 0) and (RVal.IVal >= 0) or (LVal.IVal >= 0) and (RVal.IVal < 0) then Result.IVal := -maxint else Result.IVal := maxint end; procedure CompareValues(var LVal: ObjectValue; RVal: ObjectValue; RelOp: OpType); visible; begin case RelOp of LtOp : LVal.IVal := ord(LVal.IVal < RVal.IVal); LeOp : LVal.IVal := ord(LVal.IVal <= RVal.IVal); GeOp : LVal.IVal := ord(LVal.IVal >= RVal.IVal); GtOp : LVal.IVal := ord(LVal.IVal > RVal.IVal); NeOp : LVal.IVal := ord(LVal.IVal <> RVal.IVal); EqOp : LVal.IVal := ord(LVal.IVal = RVal.IVal) end end; procedure ConstIntFn(Which: StdProcFuncs; ArgValue: ObjectValue; var Result: ObjectValue); visible; begin end; procedure ConstRealFn(Which: StdProcFuncs; ArgValue: ObjectValue; var Result: ObjectValue); visible; begin end; procedure ConstWrdFn(Which: StdProcFuncs; Arg1, Arg2: ObjectValue; var Result: ObjectValue); visible; begin end; { provide for the compile-time evaluation of constant expressions in ICL Pascal. Since the host and target machines are identical, the accuracy of reals is limited by the accuracy with which they can be read from the source-text. The procedure InitCodeGeneration is called before any other gen- eration interface procedure to initialise all variables and facil- ities made available by the generation interface. Similarly the procedure EndCodeGeneration is called after all use of the inter- face is complete. } {procedure InitCodeGeneration; visible; } { a temporary version which assumes the definition:- } { } { objectvalue = integer } { } { and adopts arbitrary limits for integers and character } { sets. } { begin NilValue.IVal := 0; EmptyValue.IVal := 0; FalseValue.IVal := 0; TrueValue.IVal := 1; ZeroValue.IVal := 0; OneValue.IVal := 1; EightValue.IVal := 8; MaxintValue.IVal := maxint; MinLabValue.IVal := 0; MaxLabValue.IVal := 9999; MinCharValue.IVal := 0; MaxCharValue.IVal := 255; MaxWordValue.WVal := MaxWord; MaxRealValue.IVal := maxint; LineFeed.IVal := 0; PageThrow.IVal := 1; DftValue.IVal := 0; DftAddress := 0; DftOffset := 0; EmptyRepresentation := 0; RealRepresentation := 0; BooleanRepresentation := 0; CharRepresentation := 0; IntegerRepresentation := 0; PtrRepresentation := 0; DefaultRepresentation := 0; WordRepresentation := 0 end ; } { initcodegeneration } {procedure EndCodeGeneration; visible; begin end; } { 6.4 Block and Program Housekeeping The object code generated for a Pascal program is assumed to be a set of code blocks, one for each source program block. Each code block is accumulated in a 'code-space' which exists throughout the analysis of the corresponding source block, though in practice most implementations generate code for the current block only dur- ing analysis of the corresponding statement part. To enable inter-block references (procedure and function calls), the analyser holds a variable 'CodeBody' of generator-defined type BlockLabel within each procedure, function or program identifier entry. This variable is initialised by a call to the procedure FutureBlockLabel, and bound to the corresponding code block when the code space is opened. The procedures OpenCodeSpace and CloseCodeSpace are responsible for maintaining the code space for a given code block and for fil- ing its contents on the object program file. ICL Pascal permits references to externally compiled blocks. The entry points of such blocks are designated by external block labels which are assumed to be represented within the generator by suitably tagged BlockLabels. The application of such a tag is triggered by a call to the procedure ExternalBlockLabel which occurs whenever a block is designated extern. } procedure FutureBlockLabel(var l: BlockLabel); visible; begin end; procedure ExternalBlockLabel(var l: BlockLabel); visible; begin end; procedure OpenCodeSpace(var l: BlockLabel); visible; begin end; procedure CloseCodeSpace; visible; begin end; { Thus the program fragment: procedure Outer ; procedure Inner ; begin . . end ; begin . . end ; gives rise to the following series of interface calls: FutureBlockLabel (CodeBody for Outer) OpenCodeSpace(CodeBody for Outer) FutureBlockLabel(CodeBody for Inner) OpenCodeSpace(CodeBody for Inner) . . Code generation calls for Inner . . CloseCodeSpace . . Code generation calls for Outer . . Closecodespace 6.5 Operand Addressing The code generation interface for variable access, expression evaluation, and assignment assumes a postfix code form (though the generating procedures called may transform this code thereafter). The generating calls represent operations on a hypothetical run-time stack of operand references and values. Thus, variable access is realised by the following hypothetical operations:- } {procedure StackReference(Indirect: Boolean; Location: RunTimeAddress); visible; begin end; procedure InxReference (PackedArray: Boolean; LowBound, HighBound: ObjectValue; Element: TypeRepresentation); visible; begin end; procedure InxCAPReference (PackedSchema: Boolean; LowBound, HighBound: CAPBound; BoundPairRepresentation, Component: TypeRepresentation); visible; begin end; procedure DoVariantChecks(VarPart: TypEntry; FieldId: IdEntry); visible; begin end; procedure FieldReference(Field: FieldOffset; TagField: Boolean); visible; begin end; procedure PnterReference; visible; begin end; procedure BufferReference(PackedFile, TextFile: Boolean; Element: TypeRepresentation); visible; begin end; } { StackReference is the basic means of generating a reference to a variable, parameter or function result on the stack. Indirct is true for variable parameter references, false otherwise. IndexedReference combines a reference to an array variable with the value of an index expression, to produce a reference to the element indexed. IndexedCAPReference does the same for conformant arrays, but the parameter-type CAPBound in this case indicates whether a bound is a constant or variable. In the former case, the actual bound value is supplied, and in the latter case, the relevant location of the bound-pair block is supplied. The optim- isation resulting from the provision of a constant bound will in general depend upon which bound is supplied, and the overall code generation strategy adopted for indexing conformant arrays. FieldReference converts a record variable reference to a reference to the field whose offset is specified. In the case of a variant field, however, the FieldReference operation is preceded by a call to VariantChecks which may generate a cascade of tag or selector checks to ensure that the variant containing the field is active. PnterReference converts a pointer variable reference to a refer- ence to the dynamic variable it points to, while BufferReference converts a file variable reference to a reference to the corresponding file buffer variable. For example, given a variable declaration: var a : array [1..10, 1..20] of record b:... c:... end ; a variable access of the form: a[i,j].c would generate the following sequence of interface calls: StackReference(false,address for a) ...stack value of i.... IndexedReference(false,objectvalue(1), objectvalue(10), representation of array [1..20] of record...) ...stack value of j.... IndexedReference(false,objectvalue(1), objectvalue(20), representation of record...end) FieldReference (offset for c, false) 6.6 Operand Evaluation and Arithmetic Primary operand values are placed on the stack either by convert- ing a stacked reference, or by stacking an explicit literal value. Thus variable values are placed on the stack by the following operations: } {procedure DeReference(Representation: TypeRepresentation); visible; begin end; procedure CAPDeReference (PackedSchema: Boolean; LowBound, HighBound: CAPBound; BoundPairRepresentation, ComponentRepresentation: TypeRepresentation); visible; begin end; } { CAPDereference is used in the special case when the value of a conformant array parameter is to be assigned to another conformant array with the same schema. Some variable values stacked in this way are subject to checking operations signalled by the following procedures: } {procedure TailoredFactorCheck(Representation: TypeRepresentation); visible; begin end; procedure UndefinedVariableCheck; visible; begin end; } { TailoredFactorCheck is used to check that a dynamic record vari- able has not been 'tailored', i.e. created using the extended form of new. UndefinedVariableCheck is used to check that the variable value is not (wholly or partially) undefined. Explicit literal values are stacked by the procedure: } {procedure StackConstant(ConstValue: ObjectValue); visible; begin end; } { Operand values on the stack may be modified or combined by a variety of operations to produce new operands. Integer arithmetic is effected by the following set of operations: } {procedure IntFunction(WhichFunc: StdProcFuncs); visible; begin end; } procedure WordFunction(WhichFunc: StdProcFuncs); visible; begin end; procedure MaxMinFunction(WhichFunc: StdProcFuncs; Min, Max: ObjectValue); visible; begin end; {procedure NegInteger; visible; begin end; procedure BinaryIntegerOperation(Operator: OpType); visible; begin end; procedure OrdinelComparison(Operator: OpType); visible; begin end; procedure RangeCheck(Min, Max: ObjectValue); visible; begin end; } { IntFunction applies the standard functions, abs, sqr, add, succ, pred, ord, chr, and the ICL function int to the topmost operand on the stack (TOS). WordFunction performs the ICL functions wrd, andw, orw, neqw, notw, shw, and rotw to (TOS). MaxMinFunction applies the ICL functions minval and maxval to (TOS). All ordinal types are assumed to use the same representation as integer. Nega- teInteger negates TOS, and BinaryIntegerOperation applies the operators +,-,*, div and mod to the two topmost operands (TOS-1) and (TOS). Thus the integer expression -a+2*abs(b) would involve the follow- ing sequence of calls: StackReference(...,runtime address of a) Dereference(integer representation) NegateInteger StackConstant(objectvalue(2)) StackReference(.., runtime address of b) Dereference(integer representation) OrdinalFunction(absf) BinaryIntegerOperation(mul) BinaryIntegerOperation(plus) OrdinalComparison applies the operators =, <>, <,<=, > and >= to (TOS-1) and (TOS) for all ordinal types. In certain contexts, such as subrange variable assignment and parameter passing, an ordinal value on the stack is subject to an explicit range check signalled by the procedure RangeCheck. Note, however, that the range checks associated with array indexing, the function chr, and the implementation limits of integer arithmetic itself are implicit in the operations concerned and are not sig- nalled explicitly. Real arithmetic is implemented by a similar set of procedures, as follows: } {procedure FloatInteger(StackPosition: StackTop); visible; begin end; procedure RealFunction(WhichFunc: StdProcFuncs); visible; begin end; procedure NegReal; visible; begin end; procedure BinaryRealOperation(RealOperator: OpType); visible; begin end; procedure RealComparison(Operator: OpType); visible; begin end; } { The procedure FloatInteger signals the conversion of an integer value to the corresponding real value. Note, however, that it may apply either to the topmost or next-to-top operand, according to the parameter StackPosition. Thus the real expression -1/x involves the following sequence of interface calls: StackConstant(objectvalue(1)) StackReference(.., runtime address of x) Dereference(real representation) Floatinteger(NextToTop) BinaryRealOperation(rdiv) NegateReal Boolean arithmetic is implemented by the following procedures: } {procedure NegateBoolean; visible; begin end; procedure BinaryBooleanOperation(Operator: OpType; FirstOperation: Boolean); visible; begin end; procedure EliminateConditions; visible; begin end; } { The calls to BinaryBooleanOperation allow generation of either jump-out or full evaluation code. For each sequence of and's or or's compiled, an initial call to BinaryBooleanOperation, with First = true, is made between the first and second operands. Thus the expression: (a = 0) or b or not c involves the following interface call pattern: ...stack value of a... StackConstant(objectvalue(0)) ScalarComparison(eqop) BinaryBooleanOperation(true,orop) ...stack value of b... BinaryBooleanOperation(false,orop) ...stack value of c... NegateBoolean BinaryBooleanOperation(false,orop) In some contexts, however, such as Boolean assignments or Boolean comparisons, implementations which generate jumpout code for con- ditions must reduce a jumpout sequence to a simple Boolean value. The points to do so are signalled by an additional call to the procedure ExcludeConditions. Implementations which compute Boolean values throughout may ignore such calls. Set arithmetic is provided by the following procedures: } {procedure SingletonSet(SetRepresentation: TypeRepresentation); visible; begin end; procedure RangeSet(SetRepresentation: TypeRepresentation); visible; begin end; procedure BinarySetOperation(SetOperator: OpType); visible; begin end; procedure SetComparison(SetOperator: OpType); visible; begin end; procedure SetCheck(Min, Max: ObjectValue); visible; begin end; } { Set constructors are implemented using the elementary SingletonSet and RangeSet operations, with calls to BinarySetOperation to com- bine the resultant components if necessary. Thus the constructor ['0','2','4'..'7'] involves the following sequence of interface calls: StackConstant(objectvalue('0')) SingletonSet(representation of set of char) ....['0'] StackConstant(objectvalue('2')) SingletonSet(representation of set of char) ....['2'] BinarySetOperation(plus) ....['0','2'] StackConstant(objectvalue('4')) StackConstant(objectvalue('7')) RangeSet(representation of set of char) ....['4'..'7'] BinarySetOperation(plus) ....['0','2','4'..'7'] A set value which is to be assigned to a set variable with a subrange base type is checked for assignment compatibility by the operation SetCheck. The special comparisons required for pointer and string operands and for the ICL functions ptr, wptr, and cptr are implemented by the following additional procedures: } {procedure PnterComparison(Operator: OpType); visible; begin end; } procedure PnterFunction(Which: StdProcFuncs); visible; begin end; {procedure StringComparison(Operator: OpType; Length: ObjectValue); visible; begin end; } { 6.7 Variable Assignment An assignment is expressed as a single postfix operation which assumes the variable reference and expression value have already been stacked. Depending on whether the variable to be assigned is a tag field of a variant record this operation is expressed as a call to one of the following procedures: } {procedure Assign(VarRep: TypeRepresentation); visible; begin end; procedure AssignTag(SelectorRep: TypeRepresentation); visible; begin end; } { In the case of Assign, the type representation parameter enables the generator to implement assignments to structured as well as simple variables. In the case of AssignTag, the parameter is assumed to contain information necessary to implement the checked selection and activation of the variant associated with the tag value. 6.8 With Statements With statements are implemented by extending the reference inter- face described in 6.5 with the following procedures: } {procedure OpenWith(WithBase: DispRange); visible; begin end; procedure WithReference(WithBase: DispRange); visible; begin end; procedure CloseWith; visible; begin end; } { The operation OpenWith preserves the record variable reference currently on top of the stack, and associates it with the WithBase index supplied by the analyser. Subsequent WithReference opera- tions which cite the same WithBase index recreate this record variable reference on the stack for use by a subsequent Fiel- dReference operation. The operation CloseWith merely signals the point at which the record variable reference may be discarded. Thus, given a declaration: var r : record a : integer ; .... end the statement: with r do a := a + 1 involves the following sequence of interface calls: StackReference(false,runtimeaddress of r) OpenWith(n) WithReference(n) FieldReference(offset for a,false) WithReference(n) FieldReference(offset for a,false) Dereference(integer representation) StackConstant(objectvalue(1)) BinaryIntegerOperation(plus) Assign(integer representation) CloseWith 6.9 Standard Procedures 6.9.1 Standard input/output All standard input/output operations are performed with respect to a file operand that is referenced explicity or implicitly by the coresponding standard procedure parameter list. The procedures: } {procedure SelectFile(FType: TypEntry); visible; begin end; procedure DiscardFile; visible; begin end; } { are used to preserve this reference and to discard it when analysis of the remaining i/o parameters is complete. SelectFile is provided with the TypEntry for the file operand, and assumes that (TOS) contains the file reference. To ensure this is so, the analyser inspects the standard procedure or function parameter list and automatically stacks a reference to the variables Input or Output if appropriate. Note, however, that in this case the StackReference-SelectFile sequence comes after the first variable or expression of a read or write statement has been stacked. The procedures: } {procedure FileOperation(Which: StdProcFuncs; Form: IOFormat); visible; begin end; procedure FileFunction(WhichFunc: StdProcFuncs); visible; begin end; } { respectively implement the primitive operations get, put, reset, and rewrite, and the predicates eoln and eof. In the latter cases, FileFunction leaves the Boolean function result on the stack. The procedures: } {procedure ReadBuffer; visible; begin end; procedure ReadNumeric(ReadMode: InputKind); visible; begin end; procedure ReadLayout; visible; begin end; } { are used to implement the standard procedures read and readln. The procedure ReadNumeric is used to read real or integer data from a text file. The procedure ReadBuffer is used for all other data and file types. In the following examples the declarations: var A : integer ; B : real ; C : char ; F : text ; are assumed. Thus: read(F,A) ; generates the interface calls: StackReference(false,runtime address of F) SelectFile(TypEntry for text) StackReference(false,runtime address of A) ReadNumeric(IntKind) Assign(integer representation) DiscardFile The procedure ReadNumeric receives a single parameter of type InputKind indicating whether the digit sequence on the text file is to be interpreted as a real or integer number. In either case, its effect is to stack the value read in addition to updating the buffer-variable accordingly. The procedure ReadBuffer is used where no interpretive action is required. Thus: readln(C) ; generates: StackReference(false,runtime address for C) StackReference(false,runtime address for Input) SelectFile(TypEntry for text) ReadBuffer Assign(char representation) ReadLayout DiscardFile The standard procedures write, writeln, and page, are implemented by calls to: } {procedure WriteBuffer; visible; begin end; procedure WriteScalars(WriteMode: OutputKind; Format: FormatKind); visible; begin end; procedure WriteString(ActualLength: ObjectValue; Format: FormatKind); visible; begin end; } procedure WriteCAPString(HighBound: CAPBound; Format: FormatKind); visible; begin end; {procedure WriteLayout(Format: IOFormat); visible; begin end; } { where WriteScalars is chosen for integer, real, Boolean and for- matted char values, WriteString for literal strings, and Wri- teBuffer for all other data types and file types. WriteLayout is called for both writeln and page, and uses the layout-character value on top of the stack to implement the appropriate formatting. The procedure WriteCAPString provides the ICL extension for out- puting conformant string types. In this case the length of the string must be deduced at runtime from the contents of the associ- ated bound-pair block In the following example, the above declarations are assumed: writeln('Answer = ',B:12:3) ; generates: StackConstant(objectvalue('Answer = ')) StackReference(for Output) SelectFile(TypEntry for text) WriteString(objectvalue(9),Default) StackReference(false,runtime address of B) DeReference(real representation) StackConstant(objectvalue(12)) StackConstant(objectvalue(3)) WriteScalars(RealKind,Fixed) StackConstant(objectvalue(LineFeed)) WriteLayout DiscardFile WriteString takes two parameters, the first being the character length of the string, and the second of type FormatKind, indicat- ing formatting (if any) of the expression value. 6.9.2 Pack/Unpack. The transfer procedures pack and unpack are implemented for both array and conformant array arguments by calls to: } {procedure ArrayArrayOp(Which: StdProcFuncs; UPLowBound, UPHighBound: ObjectValue; UnpackedRep, PackedRep: TypeRepresentation; PKLowBound, PKHighBound: ObjectValue); visible; begin end; procedure ArrayCAPOp(Which: StdProcFuncs; UPLowBound, UPHighBound: ObjectValue; UnpackedRep, PackedRep: TypeRepresentation; PKLowBound, PKHighBound: CAPBound); visible; begin end; procedure CAPArrayOp(Which: StdProcFuncs; UPLowBound, UPHighBound: CAPBound; BpRep, UnpackedRep, PackedRep: TypeRepresentation; PKLowBound, PKHighBound: ObjectValue); visible; begin end; procedure CAPCAPOp(Which: StdProcFuncs; UPLowBound, UPHighBound: CAPBound; BpRep, UnpackedRep, PackedRep: TypeRepresentation; PKLowBound, PKHighBound: CAPBound); visible; begin end; } { The array bounds, or bound-pair-block addresses are supplied as parameters to indicate the number of array elements to be pro- cessed and to construct the appropriate range checks on the index- ing expression. The first parameter indicates whether packing or unpacking is required. In the following examples, the declarations: Type T = 1..10 ; procedure P ( A : packed array [L1..U1:T] of char ; B : array [L2..U2:T] of char ) ; var C : packed array [T] of char ; D : array [T] of char ; are assumed. Then: pack(D,1,C) ; gives rise to the interface calls: StackReference(false,runtime address for D) StackConstant(objectvalue(1)) StackReference(false,runtime address of C) ArrayArrayOp(packp,objectvalue(1),objectvalue(10), char representation,char representation, objectvalue(1),objectvalue(10)) and: Unpack(A,B,1) ; gives rise to the calls: StackReference(false,runtime address for A) StackReference(false,runtime address for B) StackConstant(objectvalue(1)) CAPCAPOp(unpackp, low bound address of B, high bound address of B, CharRepresentation,CharRepresentation, lowboundaddress of A, high bound address of A) With the same context of stacked operands and index values, pack(B,1,C) and unpack(C,1,B) result in calls to CAPArrayOp since the unpacked-type in each case is a conformant array schema. Similarly, pack(D,1,A) and unpack(A,D,1) result in calls to Array- CAPOp, since the unpacked-type in each case is an array-type. 6.9.3 New/Dispose/SizeOf The dynamic storage procedures new and dispose are implemented by a sequence of calls to the procedures: } procedure SizeRequest(Requested: TypeRepresentation); visible; begin end; {procedure HeapRequest(Requested: TypeRepresentation); visible; begin end; procedure TailorRequest(SelectorRep, SelectedRep: TypeRepresentation); visible; begin end; procedure HeapOperation(WhichPf: StdProcFuncs); visible; begin end; } { made in a sequence of the form: HeapRequest ... TailorRequest ... HeapOperation where the TailorRequest calls are included only if case-constants are specified in the new or dispose parameter lists. The magni- tude of the initial request is assumed to be embedded in the domain type representation which is passed as parameter to HeapRe- quest. The procedure TailorRequest takes two parameters: the first is the representation associated with the variant part con- taining the variant, and the second is the representation of the variant specified by the case-constant. It is assumed that in addition to storage requested, information can be extracted from these representations which allows the consistency checks between new and dispose to be applied. The appropriate allocation pro- cedure is specified by the parameter to HeapOperation. Assuming the definitions: type rec = record f : integer ; case which : boolean of false : ( ) true : (sample : real) end ; var p : ^rec then: new(p,true) ; gives rise to the sequence of calls: StackReference(false,runtime address of p) HeapRequest(representation for type rec) TailorRequest(representation for variant part, representation for variant 'true') HeapOperation(Newp) The sequence of request calls for dispose is identical but the first parameter is an expression and hence we have: StackReference(false,runtime address of p) DeReference(representation for ^p) . . Calls as before . . HeapOperation (Disposep) In ICL Pascal, the function sizeof is implemented in an analagous fashion with an initial call to procedure SizeRequest replacing the call to HeapRequest. If the variable is a variant record, the size of a particular configuration of variants may be requested by means of a case constant-list, resulting in one or more calls to TailorRequest. Finally the size of the variable is pushed onto TOS by a final call to procedure IntFunction, quoting sizef as argu- ment. In practice, much generator logic can be shared between pro- cedures SizeRequest and HeapRequest. Using the above example, the fragment sizeof(p, true); gives rise to the call sequence StackReference(false, address of p); SizeRequest(representation for type rec); TailorRequest(representation for variant-part, representation for variant 'true'); IntFunction(Sizef); 6.9.4 Date and Time Procedures ICL Pascal contains additional standard procedures for writing the date and time. (TOS) contains a reference to an 8-character packed array and the argument to StampProcedure identifies which function is required. For example, the fragment date(Today); gives rise to the calls StackReference(false, address of Today); StampProcedure(Datep); 6.10 Control Statements The code generated, whatever its form, is assumed to be for sequential execution. Each point that can be reached other than sequentially is represented at compile time by a record of type CodeLabel. These records are bound to points in the code by the procedures: } procedure StampProcedure(WhichPf: StdProcFuncs); visible; begin end; { NewCodeLabel is used for labels that are bound before they are referenced by any jump (i.e. backward jumps only). FutureCodeLa- bel and NextIsCodeLabel are used for labels that may be referenced before they are bound (i.e. forward jumps). The jumps required by if, while and repeat statements are provided by the following operations: } {procedure NewCodeLabel(var l: CodeLabel); visible; begin end; procedure FutureCodeLabel(var l: CodeLabel); visible; begin end; procedure NxIsCodeLabel(var l: CodeLabel); visible; begin end; } { Thus the sequence of calls arising from a while statement: while c do s would use two analyser-declared code labels (L1,L2, say) as follows: NewCodeLabel(L1) ... stack value of c ... FutureCodeLabel(L2) JumponFalse(L2) ... code for s ... Jump(L1) NextIsCodeLabel(L2) The simple code label mechanism is not used for explicit statement labels declared and used in the Pascal program. Instead an analo- gous StatementLabel type is introduced to represent such labels, with the following operations available. } {procedure JumpOnFalse(var Destination: CodeLabel); visible; begin end; procedure Jump(var Destination: CodeLabel); visible; begin end; } { Implementations may or may not equivalance statement labels with code labels, depending on the nature of the object code to be gen- erated. Case statements require the following additional operations: } {procedure FutureStatementLabel(var l: StatementLabel); visible; begin end; procedure NextIsStatementLabel(var l: StatementLabel); visible; begin end; procedure LabelJump(var Destination: StatementLabel; LabelLevel: DispRange); visible; begin end; } { Opencase is invoked immediately following evaluation of the case selector. Each label on a case limb is signalled by NextIsCase, and the final case limb is followed by a CloseCase operation. In addition a future code label is introduced to label the code after the entire case statement, and each case limb is followed by a jump to this label. Thus a case statement of the form: case n of 1 : s1 ; 2,4 : s2 ; . . end involves an interface call sequence as follows: ...stack value of n... FutureCodeLabel(aftercase) OpenCase NextIsCase(objectvalue(1)) ...code for s1... Jump(aftercase) NextIsCase(objectvalue(2)) NextIsCase(objectvalue(4)) ...code for s2... Jump(aftercase) . . . Closecase NextIsCodeLabel(aftercase) ICL Pascal allows constant ranges to be supplied instead of label lists. In such cases, the calls to NextIsCase are replaced by calls to NextIsRange in which the first and last values of the range are supplied. ICL Pascal also permits the use of an other- wise clause to handle exceptional cases. Thus a fragment of the form case c of 'A'..'Z' : s1; 'a'..'z' : s2; '0'..'9' : s3; otherwise s4 end; gives rise to the call sequence ... stack value of c ... FutureCodeLabel(aftercase); OpenCase NextIsRange(Value('A'), Value('Z'); ... s1 ... Jump(aftercase); NextIsRange(Value('a'), value('z')); ... s2 ... Jump(aftercase); NextIsRange(Value('0'), Value('9')); ... s3 ... Jump(aftercase); DefaultCase; ... s4 ... Jump(aftercase); CloseCase; NextIsCodeLabel(aftercase); For statements are handled by the following special operations: } {procedure OpenCase; visible; begin end; procedure NxIsCase(CaseConst: ObjectValue); visible; begin end; } procedure NextIsRange(Const1, Const2: ObjectValue); visible; begin end; procedure OtherCases; visible; begin end; {procedure CloseCase; visible; begin end; } { Assuming a declaration var c: 1..10; a statement of the form: for c := i to f do s involves the following sequence of interface calls: StackReference(false,runtime address of c) ...stack value of i... ...stack value of f... Openfor(true, objectvalue(1), objectvalue(10)) ...code for s... Closefor 6.11 Procedure and Function Calls Procedure and function calls are implemented by calls to the interface procedures: } {procedure OpenFor(Increasing: Boolean; ControlMin, ControlMax: ObjectValue); visible; begin end; procedure CloseFor; visible; begin end; } { made in a sequence: (StackActualBlock|StackFormalBlock) ... parameters ... CallBlock [TakeResult] where the first call establishes the nature of the block called. For an actual block call, the parameter specifies the actual pro- cedure or function entry point, but for a formal block call, the entry point is assumed to be embedded within the formal procedure or function parameter whose address is supplied by the parameter Faddress. A function call transforms the stacked block reference into a result value whose representation is supplied to the generator by a call to procedure TakeResult. Thus assuming: function G ( I : integer ) : integer ; the call: G(X) gives rise to the following interface calls: StackActualBlock(BlockLabel for G) . . parameters . . CallBlock TakeResult(integer representation) Variable, value and procedural parameters are passed by calls to: } {procedure StackActualBlock(var Body: BlockLabel); visible; begin end; procedure StackFormalBlock(FAddress: RunTimeAddress); visible; begin end; procedure CallBlock; visible; begin end; procedure TakeResult(Representation: TypeRepresentation); visible; begin end; } { made in a sequence: OpenParameterList ... PassReference|PassValue|PassBlock ... CloseParameterList OpenParameterList and CloseParameterList are called even for parameterless procedures or functions. PassReference takes a sin- gle parameter which indicates whether the actual variable parame- ter is potentially insecure. This enables the generation of run- time checks associated with the preservation of such 'extended' references. Assuming the program fragments: function F ( I : integer ) : integer ; procedure G ( function H ( X : integer ) : integer ) ; var Y,Z : integer ; begin . . Z := H(Y) ; . . end ; A call to G passing the function F as actual parameter would result in: StackActualBlock(BlockLabel for G) OpenParameterList(proc) StackActualBlock(BlockLabel for F) PassBlock CloseParameterList CallBlock and the call H(Y) inside the body of G results in the calls: StackFormalBlock(runtime address of parameter H) OpenParameterList(Func) StackReference(false,runtime address for Y) DeReference(integer representation) Passvalue CloseParameterList CallBlock TakeResult(IntegerRepresentation) The conformant array parameter mechanism is implemented by calls to: } {procedure OpenParameterList(ClassOfCall: IdClass); visible; begin end; procedure PassBlock; visible; begin end; procedure PassReference(RefStatus: RefSecurity); visible; begin end; procedure PassValue(RepRequired: TypeRepresentation); visible; begin end; procedure CloseParameterList; visible; begin end; } { made in a sequence: StartBoundPairs ... PassArrayBoundPair|PassCAPBoundPair ... StartBoundPairs is simply a trigger that informs the generator that series of array bounds is about to be passed. The bounds themselves are passed within the parameter lists of the procedures PassArrayBoundPairs and PassCAPBoundPairs. A reference to each actual array parameter is then passed, but for those passed by value, a call to procedure: } {procedure StartBoundPairs; visible; begin end; procedure PassArrayBounds(ActualLowBound, ActualHighBound: ObjectValue; SchemaLowBound, SchemaHighBound: CAPBound; IndexLow, IndexHigh: ObjectValue; ArrayRep: TypeRepresentation; ArrayPacked: Boolean); visible; begin end; procedure PassCAPBounds(LowBound, HighBound, SchemaLowBound, SchemaHighBound: CAPBound; IndexLow, IndexHigh: ObjectValue; BoundPairRep: TypeRepresentation); visible; begin end; } { is inserted to allow a copy of the actual array to be made at the point of call. (For an alternative scheme of creating the copy, see Section 6.2.3) Assuming the program fragment: var a,b : array[1..10,-10..10] of integer ; procedure P ( x,y : array [l1..u1:integer;l2..u2:integer] of integer ) ; . . a call P(a,b) generates the sequence: StackActualBlock(entry BlockLabel for P) OpenParameterList(proc) StartBoundPairs PassArrayBoundPair( .. array bounds 1 and 10 .. ) PassArrayBoundPair( .. array bounds -10 and 10 .. ) StackReference(false,runtime address of a) DeReference(representation of array-type of a) MakeAuxiliary(representation of array-type of a) PassReference(secure) StackReference(false,runtime address of b) DeReference(representation of array-type of b) MakeAuxiliary(representation of array-type of b) PassReference(secure) CloseParameterList CallBlock Prelude and postlude code supporting block entry and exit is gen- erated by calls to: } {procedure MakeAuxiliary(RepRequired: TypeRepresentation); visible; begin end; } { In the case of the block-entry procedures, unrestricted access is granted to the table entry for the block-identifier, to permit the generation of optimal entry-code sequences. 6.12 Program Parameters The interface contains provision for the exchange of information between the object program and its environment by means of the calls: } {procedure EnterProgram(ProgId: IdEntry; SourceLine: Scalar); visible; begin end; procedure EnterPfBody(PfId: IdEntry; SourceLine: Scalar); visible; begin end; procedure LeaveProcedure; visible; begin end; procedure LeaveFunction(Result: RunTimeAddress; Representation: TypeRepresentation); visible; begin end; procedure ExitProgram; visible; begin end; } { In general, calls to AcceptProgparameter and ReturnProgParameter can be made for any type declared in the program, but in practice, they will most likely be used to construct a mapping between file parameters and the corresponding external physical files. This correspondence is sometimes established by using the file variable identifier spelling, and consequently the procedures are granted direct access to the identifier table entry for each program parameter. Special processing is required for the variables Input and Output, whose appearance in the program parameter list also constitutes their point of declaration. For these variables, the analyser automatically makes calls to the i/o procedures to effect a reset for Input and a rewrite for Output. Assuming the program fragments: program P ( input,output,data ) ; . var data : file of integer ; . . begin the sequence of calls at entry to the program block is: InitialiseVariables OpenProgParameterList AcceptProgParameter(IdEntry for input) StackReference(false,runtime address for input) SelectFile(Texttype) FileOperation(Resetp) DiscardFile AcceptProgParameter(IdEntry for output) StackReference(false,runtime address for output) SelectFile(Texttype) FileOperation(Rewritep) DiscardFile AcceptProgParameter(IdEntry for data) CloseProgParameterList with a corresponding sequence of calls prior to exit from the block. } procedure OpenProgParameterList; visible; begin end; procedure AcceptProgParameter(ParamId: IdEntry); visible; begin end; procedure ReturnProgParameter(ParamId: IdEntry); visible; begin end; procedure ClsProgParameterList; visible; begin end; begin { end of module } end.