📄 chapter 8 procedures and functions.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0062)http://topaz.cs.byu.edu/text/html/Textbook/Chapter8/index.html -->
<HTML><HEAD><TITLE>Chapter 8: Procedures and Functions</TITLE>
<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
<META content="MSHTML 6.00.2800.1458" name=GENERATOR></HEAD>
<BODY><!----------------------------------------------------------------------------->
<H2>8.1 Procedures and Functions</H2><!----------------------------------------------------------------------------->Up
to this point, we have made a strong effort to base the material in this text on
the grammar rules. Each point has been explained by showing where it was in the
compiler in relation to some grammar rule. This chapter is a little different,
since the points of reference are fairly sparce. In this chapter we will explain
how procedures work in SAL. Generating code for a procedure is not really too
complicated. The hardest part is in getting the arguments (if there are any) off
of the EES. For the most part, the real work is shown in figure {RULE60}.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/RULE60.gif">
<P><FONT face=arial size=-1><B>Figure {RULE60}.</B> The code generation that
needs to be done in rule ProcFuncBlock. The bulk of the work that needs to be
done is in storing parameters and in allocating arrays. The latter part, i.e.,
the call to <TT>PutCodeText()</TT> writes the procedure to disk.</FONT>
</P></MENU>Basically, a procedure/function is laid out in memory as is shown in
figure {PROCMEM}.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/PROCMEM.gif">
<P><FONT face=arial size=-1><B>Figure {PROCMEM}.</B> Layout of a procedure or
function in memory.</FONT> </P></MENU>A procedure or function consists of five
parts:
<MENU><B>The <TT>ENTR</TT> instruction:</B> This should always be the first
instruction in every procedure block. The VM actually checks to see if this
instruction is present before executing a procedure or a function. The
<TT>ENTR</TT> instruction also takes a 16-bit immediate parameter which tells
the VM how many bytes of space on the procedure stack need to be allocated for
local variables.
<P><B>Storing Parameters:</B> If parameters are passed by value, they need to
be stored in local variables. If they are passed by reference, then pointers
to the memory locations where their variables are stored need to be stored in
local memory.
<P><B>Allocate Local Records/Arrays:</B> Local records and arrays are stored
just like global records and arrays: by a 32-bit pointer that references the
actual data. This needs to be allocated by the compiler.
<P><B>Procedure Body:</B> This is the body of the procedure. This is where all
the statements of the procedure goes.
<P><B>The <TT>RTN</TT> Instruction:</B> This is the lase instruction. It needs
to be emitted (for safety's sake) even if the compiler was compiling a
function, which normally ends with a <TT>return</TT> statement. </P></MENU>Let
us examine each of these areas in greater detail, one by one.
<P>
<P>
<P>
<H3>8.1.1 The <TT>ENTR</TT> Instruction</H3><!----------------------------------------------------------------------------->The
purpose of the <TT>ENTR</TT> instruction is to allocate space on the stack for
local variables. Figure {ENTR} is a description of how this works. We can see
that all it really does is move the S register over so we have space to store
some variables.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/ENTR.gif">
<P><FONT face=arial size=-1><B>Figure {ENTR}.</B> The <TT>ENTR</TT>
instruction.</FONT> </P></MENU>The amount of bytes allocated by this instruction
is the total size of a procedure/function's variables: that is, size of
parameters plus size of local variables. At this time, arrays and records are
given a size of just their pointer, which is four bytes. For instance, the
following function would start with an <TT>ENTR</TT> instruction that specified
a size of 13 bytes. <PRE> func DoIt(x: int; var z: MyArray)
var
p: MyRecord;
q: bool;
begin
. . .
end func.
</PRE>All arrays and records are considered to be pointers, and have four bytes.
All parameters that are passed by reference are considered to be pointers, and
are also four bytes. All other parameters are considered to be their normal
size, e.g., <TT>bool</TT>s are 1, <TT>char</TT>s are 1, <TT>int</TT>s are 4,
etc.
<P>
<P>
<P>
<H3>8.1.2 Storing Parameters</H3>Procedures and functions in SAL always make
local copies of each of their parameters. For each dummy argument, a variable is
created, named, and is given an offset on the local stack. This is the reverse
of the process used when calling a procedure or function, which is handled in
rule ProcFuncCall. This task is illustrated in figure {STOPAR}.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/STOPAR.gif">
<P><FONT face=arial size=-1><B>Figure {STOPAR}.</B> Storing a
procedure/function's parameters into local variables.</FONT> </P></MENU>This
process (numbered (2) in figures {RULE60} and {PROCMEM}) is done by the
following algorithm:
<P><PRE><B>01</B> //*** Get a pointer to the last parameter
<B>02</B> ident = funcblock->getLastParam();
<B>03</B>
<B>04</B> //*** Loop thru all parameters in reverse
<B>05</B> while ident <> null do
<B>06</B> if ident->getByVal() then //*** if pass by value copy it in
<B>07</B> select ident->getType() from
<B>08</B> case arraytyp: // array, record
<B>09</B> case recordtyp:
<B>10</B> Emit (LID, ident->getExtra()->getSize());
<B>11</B> Emit (PCOP, ident->getOffset());
<B>12</B> else: // bool, char, int, card, or real
<B>13</B> select ident->getSize() from
<B>14</B> case 1:
<B>15</B> Emit (SLB, ident->getOffset());
<B>16</B> case 2:
<B>17</B> Emit (SLW, ident->getOffset());
<B>18</B> case 4:
<B>19</B> Emit (SLD, ident->getOffset());
<B>20</B> case 8:
<B>21</B> Emit (SLQ, ident->getOffset());
<B>22</B> end select;
<B>23</B> end select;
<B>24</B> else
<B>25</B> Emit (SLD, ident->getOffset()); //*** Arg is passed by reference; store pointer
<B>26</B> end if;
<B>27</B>
<B>28</B> ident = funcblock->getPrevIdent();
<B>29</B> loop;
<B>30</B>
<B>31</B>
</PRE>
<MENU><FONT face=arial size=-1><B>Listing {LCLSTO}.</B> The algorithm for
storing local copies of all parameters.</FONT> </MENU>If the argument is an
array passed by value then we need to make a local copy. If the argument is an
intrinsic type, then we just store it at its offset. Otherwise, if the argument
is passed by reference then it is always a pointer, and we store that. The next
three sections take a closer look at how this is done.
<P>
<P>
<H4>8.1.2.1 Arrays and Records Passed by Value <I>(Lines 8-11)</I></H4>When we
pass an array or a record by value, we need to make our own local copy. We are
given a pointer on the EES to the actual array. What we need to do is allocate
space for the local copy, and set the local pointer to point to the local copy.
This can be accomplished with a single instruction, called <TT>PCOP</TT>.
<TT>PCOP</TT> takes a 32-bit immediate parameter that indicates the local offset
of the pointer to the data, and 32-bit parameter from the EES that indicates the
size of the data. Figure {PCOP} explains all this.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/PCOP.gif">
<P><FONT face=arial size=-1><B>Figure {PCOP}.</B> The <TT>PCOP</TT>
instruction. This instruction not only copies the array or record, it also
initializes the local pointer.</FONT> </P></MENU>There is a difference between a
<TT>PCOP</TT> instruction and a memory-copy, like <TT>memcpy()</TT>. A
<TT>memcpy()</TT> copies memory from a source pointer to a destination pointer.
A <TT>PCOP</TT> copies data from the EES to a pointer. The difference is that
the source pointer is on the EES, and the destination pointer is the S register.
When <TT>PCOP</TT> is through, it will store the value of the S register in the
pointer to the structure, and then move S to the byte immediately following the
structure's data.
<P>
<P>
<P>
<H4>8.1.2.2 Intrinsic Types Passed by Value <I>(Lines 13-22)</I></H4>These
instructions explicitly store each scalar parameter one by one, from the EES
into local memory. We use the <TT>SLx</TT> instructions to accomplish this task.
Each time we give the offset to the local variable. See the code for details.
<P>
<P>
<P>
<H4>8.1.2.3 Parameters Passed by Reference <I>(Lines 25)</I></H4>When parameters
are passed by reference, all we store is a pointer. This is represented by
figure {BYREF}. See listing {LCLSTO} for details.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/BYREF.gif">
<P><FONT face=arial size=-1><B>Figure {BYREF}.</B> Anything passed by
reference is stored locally as a pointer to the data.</FONT> </P></MENU>
<P>The last thing that the algorithm does is on line 28. The algorithm has a
loop that iterates continually in reverse until there are no more parameters.
<P>
<P>
<P>
<H3>8.1.3 Allocating Local Arrays and Records</H3>This step is numbered (3) in
figures {RULE60} and {PROCMEM}. Since the area for storage of local arrays and
records was not allocated with the <TT>ENTR</TT> instruction, we need to have
the compiler allocate the space explicitly. This is done using the
<TT>ALLOC</TT> instruction.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/ALLOC.gif">
<P><FONT face=arial size=-1><B>Figure {ALLOC}.</B> The <TT>ALLOC</TT>
instruction before and after execution. This instruction loads the value of S
onto the EES, and then increments S by the size indicated in its immediate
parameter.</FONT> </P></MENU>Once the space for the array or record has been
allocated, the address of the block will be placed on the EES. This address must
be stored in the pointer to the array or record, using a <TT>SLD</TT>. The
offset to the <TT>SLD</TT> instruction should be the offset to the pointer to
the array/record. Once the array/record block has been allocated on the stack
and the address of the block stored at the pointer's location, the array should
look like figure {LOCAR}.
<MENU><IMG src="Chapter 8 Procedures and Functions.files/LOCAR.gif">
<P><FONT face=arial size=-1><B>Figure {LOCAR}.</B> A local array/record after
it has been properly allocated and the pointer has been properly set.</FONT>
</P></MENU>The general algorithm goes as follows. We get the function's first
identifier and loop forward through the identifiers. For each identifier that is
an array or a record, we emit an <TT>ALLOC</TT> and the size of the
array/record's data. Then we emit a <TT>SLW</TT> at the offset to the pointer to
the array/record. <PRE> ident = funcblock->getFirstVar();
while ident <> null do
if (ident->getObj = varobj) and ident->getType()->IsComplexType() then
Emit (ALLOC, ident->getExtra()->size());
Emit (SLD, ident->getOffset());
end if;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -