⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter 8 procedures and functions.htm

📁 英文版编译器设计:里面详细介绍啦C编译器的设计
💻 HTM
📖 第 1 页 / 共 3 页
字号:
        ident = funcblock->getNextIdent();
      loop;
</PRE>
<P>&nbsp;
<P>
<H3>8.1.4 Procedure Body</H3>This is step (4) in figures {RULE60} and {PROCMEM}. 
This part is very straightforward. We call <TT>RuleStatementSequence()</TT> and 
let it do its thing.
<P>
<P>&nbsp;
<P>
<H3>8.1.3 The <TT>RTN</TT> Instruction and <TT>PutCodeText()</TT></H3>This is 
step (5) in figures {RULE60} and {PROCMEM}. The last instruction to emit is a 
<TT>RTN</TT>. This step needs to be done in all cases as a safety measure. That 
way if the user forgets to put a <TT>return</TT> statement at the end of a 
function, there will be a <TT>RTN</TT> instruction, anyway.
<P>The last thing to do in this step is to call <TT>PutCodeText()</TT>. This 
function takes a single parameter that is the procedure-number for the 
procedure/function that has just been compiled. The number can be anything. 
Typically however, procedure numbers are assigned in the order that procedures 
are declared, starting with the number one. For example, the following program 
declares four procedures (two are nested) and has a main routine. <PRE>      program NumberProcs;

        proc Proc1();        // Index is 1
  
          proc Nested1();    // Index is 2
          begin
          end proc;

          proc Nested2();    // Index is 3
          begin
          end proc;

        begin
        end proc;


        proc Proc2();        // Index is 4
        begin
        end proc.

      begin                  // Main procedure's index is 0
      end program.
</PRE>The main routine'index is zero. All others are indexed according to the 
order that they are declared. Because of this convention, it is possible that 
procedure bodies will be compiled out of order. In this example, the bodies of 
the nested procedures <TT>Nested1()</TT> and <TT>Nested2()</TT> will be compiled 
before the body of <TT>Proc1()</TT>. This is permissible.
<P>
<P>&nbsp;
<P><!----------------------------------------------------------------------------->
<H2>8.2 Calling a Procedure</H2><!----------------------------------------------------------------------------->Here 
is the syntax diagram for rule ProcFuncCall, with all the necessary steps to 
generate code. 
<MENU><IMG src="Chapter 8 Procedures and Functions.files/RULE21.gif">
  <P><FONT face=arial size=-1><B>Figure {RULE21}.</B> 
  <TT>RuleProcFuncCall()</TT>. The purpose of this rule is to evaluate a series 
  of expressions, leaving their values on the EES, and then calling a procedure 
  or function.</FONT> </P></MENU>Remember that this rule is called from 
RuleIdentExpr, which handles the name of the procedure or function.
<P>The first thing we do is loop through the parameter list and evaluate 
expressions. For parameters that are passed by value, we call rule Expression. 
For parameters that are passed by reference, we call rule IdentExpr, and tell it 
to generate the address of a variable. The pattern of code that we are trying to 
generate looks like this:
<P>
<MENU><IMG src="Chapter 8 Procedures and Functions.files/CALLMEM.gif">
  <P><FONT face=arial size=-1><B>Figure {CALLMEM}.</B></FONT> </P></MENU>Note that 
parameters are pushed onto the EES in left-to-right order. In other words, for 
the procedure, <PRE>      proc foo(x, y: int);
</PRE>we first push <TT>x</TT> and then <TT>y</TT>. This has the effect of the 
parameters ending up on the stack in reverse order. <PRE>         EES  
       -------
      |   x   |
      |-------|
      |   y   |
      |-------|
      |       |
</PRE>All languages have this problem whether they run on the SAL VM or not. 
Some languages actually evaluate the parameters in reverse. In C, for instance, 
the comma is considered an operator, and it evaluates its operands in left to 
right order (this is a good reason why we chose to study SAL, instead of C for 
this course). This makes it difficult to write a recursive descent parser for C.
<P>There are a host of different calling instructions. The most often-used one 
is <TT>CL</TT>, which calls another procedure at the same level in the same 
module. There are two others that we will use in this assignment. They are 
<TT>CX</TT>, for calling external procedures, and <TT>CI</TT> for calling other 
nested procedures.
<P>
<H3>8.2.1 Calling Local Procedures</H3><!----------------------------------------------------------------------------->This 
is the most general case. The <TT>CL</TT> instruction is used in most cases, 
except for two exceptions. Figure {CLDIAG} shows how this instruction works. 
<MENU><IMG src="Chapter 8 Procedures and Functions.files/CLDIAG.gif">
  <P><FONT face=arial size=-1><B>Figure {CLDIAG}.</B></FONT> </P></MENU>We can see 
that all it does is create a new call frame. It does not allocate any space for 
local variables. The static link is null, and the dynamic link always has the 
previous L. There is only one case where this instruction should be used: when 
calling a global procedure. Here is an example: <PRE>      program UsesCL;

        //********************
        proc foo();
        begin
          . . .
        end proc;


        //********************
        proc bar()

        begin
          foo();    // Uses CL to call nested child procedure
        end proc;

      begin
        bar();      // Uses CL to call a global procedure
      end program.
</PRE>In the case of calling a global procedure, the preservation of the dynamic 
link in not crucial. In fact, it does nothing. The same thing applies to 
functions.
<P>
<H3>8.2.2 Calling External Procedures</H3><!----------------------------------------------------------------------------->In 
this case, we are calling a procedure that is in a module that we have imported. 
Like <TT>CL</TT>, this instruction just makes a new call frame. 
<MENU><IMG src="Chapter 8 Procedures and Functions.files/CXDIAG.gif">
  <P><FONT face=arial size=-1><B>Figure {CXDIAG}.</B></FONT> </P></MENU>Here is an 
example of where a <TT>CX</TT> instruction is used: <PRE>      library DemonstrateCX;

        export proc foo();
        begin
          . . .
        end proc;

      end library.


      program UsesCX;
        import DemonstrateCX;
      
      begin
        foo();      // We use the CX instruction to call external procs/funcs 
      end program.
</PRE>The <TT>CX</TT> instruction changes many things in the VM. It takes two 
immediate word parameters. The first is the number of the new module, and the 
second is the number of the new procedure within that module. The first thing it 
does is set the M register to the new module. Next, it gets the pointer to the 
global data area for the new module and stores it in G. When this instruction 
finishes, the VM is switched into the context of a new module, entirely. As far 
as memory on the local stack goes, however, this instruction behaves just like a 
<TT>CL</TT>
<P>
<H3>8.2.3 Calling a Nested Procedure</H3><!----------------------------------------------------------------------------->When 
calling a nested procedure, it is important that the nested procedure see the 
variables in its parent procedure's scope. The stack has to be set up properly 
in order for this to happen. In order to maintain this link, the static link 
must be set to the call frame of the parent procedure. This is accomplished in 
two steps. First is through the use of the <TT>GB</TT> instruction. This 
instruction takes an immediate byte, and walks that many iterations through the 
procedure stack, following the static link field. This is explained in figure 
{GBDIAG}. 
<MENU><IMG src="Chapter 8 Procedures and Functions.files/GBDIAG.gif">
  <P><FONT face=arial size=-1><B>Figure {GBDIAG}.</B></FONT> </P></MENU>The result 
of this instruction (which is the L for a previous procedure) is pushed onto the 
EES. If <TT>GB</TT> is told to go back zero steps, it merely returns the current 
value of L.
<P>Once we have pulled the proper previous scope from somewhere arbitrarily deep 
in the stack, we use the <TT>CI</TT> instruction to call the procedure. 
<TT>CI</TT> takes the base pointer from the EES, and puts it into the static 
link of its own call frame. This effectively creates another link in the chain, 
back to its immediate parent, which could be linked to its parent, and that 
procedure's parent, and so on, as we see in figure {GBDIAG}.
<P>Figure {CIDIAG} shows how the call frame is set up for a <TT>CI</TT>. 
<MENU><IMG src="Chapter 8 Procedures and Functions.files/CIDIAG.gif">
  <P><FONT face=arial size=-1><B>Figure {CIDIAG}.</B></FONT> </P></MENU>Here we 
see an example of using the <TT>GB</TT> instruction in tandem with <TT>CI</TT>: <PRE>      program UsesGBCI;

        proc foo();
          var
            x: int;
          
          proc Nested1();
          begin
            x:= 5;
          end proc;

          proc Nested2();
          begin
            Nested1();     // GB 01
          end proc;        // CI Nested1 

        begin
          Nested2();       // GB 00
        end proc;          // CI Nested2

      begin
        foo();

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -