{ CHAPTER 15 Code File Emission This chapter implements the creation and maintenance of 'code- spaces', as defined in Section 5.4, together with the final output of the codefile which these create. The 'code-spaces' which correspond to blocks in the source program compiled, have nested lifetimes. A record of the currently open code-spaces is maintained as a code-space stack pointed to by the variable CurrentSpace. This stack is maintained by the procedures OpenCodeSpace and CloseCodeSpace defined below. The output codefile is a file of target machine word images comprising: (a) initial option words which transmit the chosen compile-time options to the run-time system; (b) a sequence of code blocks, one for each code-space opened; (c) an index to the program entry point within the final code- block, which represents a pseudo-block enclosing the program. Item (a) is written during initialisation of the code file emitter. Items (b) and (c) form the addressable code-space of the program at run time and are written via the following procedure: } procedure FileWord(Word: MCWord); begin CodeFile^ := Word; put(CodeFile); CodeFileSize := CodeFileSize + 1 end { fileword }; { Each code block consists of a set of data words and a stream of code bytes. Data words are allocated and indexed backward from the position of code byte 0, which acts as the base address of the code block. The data words of a code-block are allocated and filled throughout the life time of the corresponding code-space. The partially com- pleted data sets for all existing code-spaces are held in the array DataStack and indexed from the records of the code-space stack. The stream of code bytes for the current code-space is accumulated in the array CodeBuffer. The following procedures carry out the allocation and filling of data words and code bytes with these arrays. } procedure CodeByte(Byte: MCByte); begin if CodeCounter = CodeByteLimit then SystemError(3) else begin CodeBuffer[CodeCounter] := Byte; CodeCounter := CodeCounter + 1 end end { codebyte }; procedure FillByte(Index: CodeByteRange; Byte: MCByte); begin CodeBuffer[Index] := Byte end; procedure WordOfBytes(Word: MCWordForm); var ByteIndex: MCByteIndex; begin { recast(word,asbytes) }; for ByteIndex := 0 to MCMaxByteNum do CodeByte(Word.WBytes[ByteIndex]) end { wordofbytes }; procedure HalfWordOfBytes(Word: MCWordForm) ; var ByteIndex: MCByteIndex ; begin { recast(Word,AsBytes) } for ByteIndex := 0 to 1 do CodeByte(Word.WBytes[ByteIndex]) end { HalfWordOfBytes } ; procedure Align; begin while CodeCounter mod MCBytesPerWord <> 0 do CodeByte(NoOperation) end { align }; procedure DataWord(Word: MCWordForm); begin if (DataCounter = DataWordLimit) or (DataCounter - CurrentBlock^.DataBase > MCMaxHalfWord) then SystemError(4) else begin DataStack[DataCounter] := Word; DataCounter := DataCounter + 1 end end { dataword }; procedure FillWord(Offset: MCHalfWord; Word: MCWordForm); begin with CurrentBlock^ do DataStack[DataBase + Offset] := Word end { fillword }; { The hierarchy of access to code-blocks implied by program nesting is implemented by embedding the base address of a code block in the data words of the 'enclosing' code block. A block label (which represents an accessible code block) is therefore represented by (a) a level number which enables the code block of the enclosing program block to be located, and (b) an offset to the data word of that block which holds the base address of the code block required. The procedure FutureBlockLabel carries out the allocation of block labels on this basis. } procedure FutureBlockLabel {var L : BlockLabel }; var DataEntry: MCWordForm; begin L.BlockLevel := FrameLevel; L.EntryOffset := DataCounter - CurrentBlock^.DataBase + 1; DataEntry.Linked := false; DataEntry.Address := 0; DataWord(DataEntry) end { futureblocklevel }; { The procedures OpenCodeSpace and CloseCodeSpace carry out the necessary housekeeping on the code-space stack, the allocation and filling of data words reserved for special purposes, and the out- put of data words and code bytes to the code file itself. CloseCodeSpace also includes a call to the forward procedure EndOfBody which enables the Code Map Emitter (Chapter 27) to record the necessary information on the code block filed. } procedure EndOfBody(CodeSize, DataSize: Scalar); forward; procedure OpenCodeSpace {var L : BlockLabel}; var BlockAddress: MCWordForm; ThisBlock: BlockPtr; begin if CurrentBlock <> nil then CurrentBlock^.EntryLabel := L; new(ThisBlock); with ThisBlock^ do begin DataBase := DataCounter; Next := CurrentBlock end; DataStack[DataCounter + ParamsOffset].WValue := TopFrameEntry^.NextOffset; DataStack[DataCounter + SerialOffset].WValue := 0; DataCounter := DataCounter + HeaderSize; CurrentBlock := ThisBlock end { opencodespace }; procedure CloseCodeSpace; var CodeSize: CodeRange; Adjustment: CodeAddress; ThisBlock: BlockPtr; Index: integer; EntryPoint, Buffer: MCWordForm; ByteIndex: MCByteIndex; begin with CurrentBlock^ do begin if CodeIsToBeGenerated then Align; CodeSize := CodeCounter div MCBytesPerWord; Adjustment := (CodeFileSize + DataCounter - DataBase) * MCBytesPerWord; DataStack[DataBase + LocalsOffset].WValue := TopFrameEntry^.NextOffset - DataStack[DataBase + ParamsOffset].WValue; DataStack[DataBase + LocksOffset].WValue := TopFrameEntry^.MaxDepth * LockSize; EndOfBody(CodeSize, DataCounter - DataBase); for Index := DataCounter - 1 downto DataBase + HeaderSize do with DataStack[Index] do if Linked then FileWord(Address + Adjustment) else FileWord(Address); FileWord(DataStack[DataBase + SerialOffset].WValue); FileWord(DataStack[DataBase + LocksOffset].WValue); FileWord(DataStack[DataBase + LocalsOffset].WValue); FileWord(DataStack[DataBase + ParamsOffset].WValue); { save entry-point address } EntryPoint.Linked := false; EntryPoint.Address := CodeFileSize; ByteIndex := 0; for Index := 0 to CodeCounter - 1 do begin Buffer.WBytes[ByteIndex] := CodeBuffer[Index]; ByteIndex := (ByteIndex + 1) mod MCBytesPerWord; if ByteIndex = 0 then begin { recast(buffer,asvalue) }; FileWord(Buffer.WValue) end end; DataCounter := DataBase; CodeCounter := 0 end; ThisBlock := CurrentBlock; CurrentBlock := ThisBlock^.Next; if CurrentBlock <> nil then with CurrentBlock^ do FillWord(EntryLabel.EntryOffset - 1, EntryPoint); dispose(ThisBlock) end { closecodespace }; { Initialisation and finalisation of the overall code filing process is carried out by the procedures InitEmitter and EndEmitter. This includes output of the option words to the start of the code file, the creation of an initial code-space which corresponds to a pseudo-block surrounding the compiled program, and output of the number of data words created by this pseudo-block (which deter- mines the position of the program block entry within it). } procedure InitEmitter; var Option: OptionType; begin CodeIsToBeGenerated := true; rewrite(CodeFile); for Option := Listing to Other do begin case Option of Listing, CodeDump, Margin, Other : CodeFile^ := 0; Checks, PMDump : CodeFile^ := ord(Option in Requested); Level, Programs : CodeFile^ := OptionValue[Option] end; put(CodeFile) end; CodeCounter := 0; DataCounter := 0; CodeFileSize := 0; CurrentBlock := nil; OpenCodeSpace(DefaultLabel) end { initemitter }; procedure EndEmitter; var DataSize: MCByte; begin with CurrentBlock^ do DataSize := DataCounter - DataBase; CloseCodeSpace; FileWord(DataSize) end { endemitter };