[Next] [Art of Assembly][Randall Hyde] [WEBster Home Page]


Art of Assembly Language: Chapter Twelve


Art of Assembly/Win32 Edition is now available. Let me read that version.


PLEASE: Before emailing me asking how to get a hard copy of this text, read this.


PDF version of text. The Best Way to read "The Art of Assembly Language Programming"
Support Software for "Art of Assembly"


Important Notice: As you have probably discovered by now, I am no longer updating this document. The reason is quite simple: I'm working on a Windows version of "The Art of Assembly Language Programming". In the past I have encouraged individuals to send me corrections to this text. However, as I am no longer updating this material, don't expect those correctioins to appear in a future release. I am collecting errata that I will post to Webster someday, so feel free to continue sending corrections to AoA/DOS (16-bit) to rhyde@cs.ucr.edu. If you're more interested in leading edge material, please see the information about the Win/32 edition, above.


The Legal Stuff (Copyrights, etc.)


Chapter 12 - Procedures: Advanced Topics
12.0 - Chapter Overview
12.1 - Lexical Nesting, Static Links, and Displays
12.1.1 - Scope
12.1.2 - Unit Activation, Address Binding, and Variable Lifetime
12.1.3 - Static Links
12.1.4 - Accessing Non-Local Variables Using Static Links
12.1.5 - The Display
12.1.6 - The 80286 ENTER and LEAVE Instructions
12.2 - Passing Variables at Different Lex Levels as Parameters.
12.2.1 - Passing Parameters by Value in a Block Structured Language
12.2.2 - Passing Parameters by Reference, Result, and Value-Result in a Block Structured Language
12.2.3 - Passing Parameters by Name and Lazy-Evaluation in a Block Structured Language
12.3 - Passing Parameters as Parameters to Another Procedure
12.3.1 - Passing Reference Parameters to Other Procedures
12.3.2 - Passing Value-Result and Result Parameters as Parameters
12.3.3 - Passing Name Parameters to Other Procedures
12.3.4 - Passing Lazy Evaluation Parameters as Parameters
12.3.5 - Parameter Passing Summary
12.4 - Passing Procedures as Parameters
12.5 - Iterators
12.5.1 - Implementing Iterators Using In-Line Expansion
12.5.2 - Implementing Iterators with Resume Frames
12.6 - Sample Programs
12.6.1 - An Example of an Iterator
12.6.2 - Another Iterator Example




Chapter 12 Procedures: Advanced Topics


The last chapter described how to create procedures, pass parameters, and allocate and access local variables. This chapter picks up where that one left off and describes how to access non-local variables in other procedures, pass procedures as parameters, and implement some user-defined control structures.

Warning: there are several known errors in this chapter. Someday I will post an errata document for this chapter. I may even go ahead and update this chapter (since the defects are so bad). The basic information in this chapter is correct, however, many of the program examples don't work. If you're interested in seeing a correct version of this information, check out the corresponding chapters in the "Advanced Assembly Language Programming" volume of the Win32 edition of this text (see the link above). RLH - Dec 4, 2000


12.0 Chapter Overview


This chapter completes the discussion of procedures, parameters, and local variables begun in the previous chapter. This chapter describes how block structured languages like Pascal, Modula-2, Algol, and Ada access local and non-local variables. This chapter also describes how to implement a user-defined control structure, the iterator. Most of the material in this chapter is of interest to compiler writers and those who want to learn how compilers generate code for certain types of program constructs. Few pure assembly language programs will use the techniques this chapter describes. Therefore, none of the material in this chapter is particularly important to those who are just learning assembly language. However, if you are going to write a compiler, or you want to learn how compilers generate code so you can write efficient HLL programs, you will want to learn the material in this chapter sooner or later.

This chapter begins by discussing the notion of scope and how HLLs like Pascal access variables in nested procedures. The first section discusses the concept of lexical nesting and the use of static links and displays to access non-local variables. Next, this chapter discusses how to pass variables at different lex levels as parameters. The third section discusses how to pass parameters of one procedure as parameters to another procedure. The fourth major topic this chapter covers is passing procedures as parameters. This chapter concludes with a discussion of iterators, a user-defined control structure.

This chapter assumes a familiarity with a block structured language like Pascal or Ada. If your only HLL experience is with a non-block structured language like C, C++, BASIC, or FORTRAN, some of the concepts in this chapter may be completely new and you will have trouble understanding them. Any introductory text on Pascal or Ada will help explain any concept you don't understand that this chapter assumes is a prerequisite.


12.1 Lexical Nesting, Static Links, and Displays


In block structured languages like Pascal[1] it is possible to nest procedures and functions. Nesting one procedure within another limits the access to the nested procedure; you cannot access the nested procedure from outside the enclosing procedure. Likewise, variables you declare within a procedure are visible inside that procedure and to all procedures nested within that procedure[2]. This is the standard block structured language notion of scope that should be quite familiar to anyone who has written Pascal or Ada programs.

There is a good deal of complexity hidden behind the concept of scope, or lexical nesting, in a block structured language. While accessing a local variable in the current activation record is efficient, accessing global variables in a block structured language can be very inefficient. This section will describe how a HLL like Pascal deals with non-local identifiers and how to access global variables and call non-local procedures and functions.


12.1.1 Scope


Scope in most high level languages is a static, or compile-time concept[3]. Scope is the notion of when a name is visible, or accessible, within a program. This ability to hide names is useful in a program because it is often convenient to reuse certain (non-descriptive) names. The i variable used to control most for loops in high level languages is a perfect example. Throughout this chapter you've seen equates like xyz_i, xyz_j, etc. The reason for choosing such names is that MASM doesn't support the same notion of scoped names as high level languages. Fortunately, MASM 6.x and later does support scoped names.

By default, MASM 6.x treats statement labels (those with a colon after them) as local to a procedure. That is, you may only reference such labels within the procedure in which they are declared. This is true even if you nest one procedure inside another. Fortunately, there is no good reason why anyone would want to nest procedures in a MASM program.

Having local labels within a procedure is nice. It allows you to reuse statement labels (e.g., loop labels and such) without worrying about name conflicts with other procedures. Sometimes, however, you may want to turn off the scoping of names in a procedure; a good example is when you have a case statement whose jump table appears outside the procedure. If the case statement labels are local to the procedure, they will not be visible outside the procedure and you cannot use them in the case statement jump table. There are two ways you can turn off the scoping of labels in MASM 6.x. The first way is to include the statement in your program:


















                 option  noscoped

This will turn off variable scoping from that point forward in your program's source file. You can turn scoping back on with a statement of the form


















                option  scoped

By placing these statements around your procedure you can selectively control scoping.

Another way to control the scoping of individual names is to place a double colon ("::") after a label. This informs the assembler that this particular name should be global to the enclosing procedure.

MASM, like the C programming language, supports three levels of scope: public, global (or static), and local. Local symbols are visible only within the procedure they are defined. Global symbols are accessible throughout a source file, but are not visible in other program modules. Public symbols are visible throughout a program, across modules. MASM uses the following default scoping rules:













Note that these rules apply to MASM 6.x only. Other assemblers and earlier versions of MASM follow different rules.

Overriding the default on the first rule above is easy - either use the option noscoped statement or use a double colon to make a label global. You should be aware, though, that you cannot make a local label public using the public or externdef directives. You must make the symbol global (using either technique) before you make it public.

Having all procedure names public by default usually isn't much of a problem. However, it might turn out that you want to use the same (local) procedure name in several different modules. If MASM automatically makes such names public, the linker will give you an error because there are multiple public procedures with the same name. You can turn on and off this default action using the following statements:






                option  proc:private    ;procedures are global
                option  proc:export     ;procedures are public

Note that some debuggers only provide symbolic information if a procedure's name is public. This is why MASM 6.x defaults to public names. This problem does not exist with CodeView; so you can use whichever default is most convenient. Of course, if you elect to keep procedure names private (global only), then you will need to use the public or externdef directive to make desired procedure names public.

This discussion of local, global, and public symbols applies mainly to statement and procedure labels. It does not apply to variables you've declared in your data segment, equates, macros, typedefs, or most other symbols. Such symbols are always global regardless of where you define them. The only way to make them public is to specify their names in a public or externdef directive.

There is a way to declare parameter names and local variables, allocated on the stack, such that their names are local to a given procedure. See the proc directive in the MASM reference manual for details.

The scope of a name limits its visibility within a program. That is, a program has access to a variable name only within that name's scope. Outside the scope, the program cannot access that name. Many programming languages, like Pascal and C++, allow you to reuse identifiers if the scopes of those multiple uses do not overlap. As you've seen, MASM provides some minimal scoping features for statement labels. There is, however, another issue related to scope: address binding and variable lifetime. Address binding is the process of associating a memory address with a variable name. Variable lifetime is that portion of a program's execution during which a memory location is bound to a variable. Consider the following Pascal procedures:


















procedure One(Entry:integer);
var
        i,j:integer;

        procedure Two(Parm:integer);
        var j:integer;
        begin

                for j:= 0 to 5 do writeln(i+j);
                if Parm < 10 then One(Parm+1);

        end;

begin {One}
        for i := 1 to 5 do Two(Entry);
end;

The figure below shows the scope of identifiers One, Two, Entry, i, j, and Parm.

The local variable j in Two masks the identifier j in procedure One while inside Two.


12.1.2 Unit Activation, Address Binding, and Variable Lifetime


Unit activation is the process of calling a procedure or function. The combination of an activation record and some executing code is considered an instance of a routine. When unit activation occurs a routine binds machine addresses to its local variables. Address binding (for local variables) occurs when the routine adjusts the stack pointer to make room for the local variables. The lifetime of those variables is from that point until the routine destroys the activation record eliminating the local variable storage.

Although scope limits the visibility of a name to a certain section of code and does not allow duplicate names within the same scope, this does not mean that there is only one address bound to a name. It is quite possible to have several addresses bound to the same name at the same time. Consider a recursive procedure call. On each activation the procedure builds a new activation record. Since the previous instance still exists, there are now two activation records on the stack containing local variables for that procedure. As additional recursive activations occur, the system builds more activation records each with an address bound to the same name. To resolve the possible ambiguity (which address do you access when operating on the variable?), the system always manipulates the variable in the most recent activation record.

Note that procedures One and Two in the previous section are indirectly recursive. That is, they both call routines which, in turn, call themselves. Assuming the parameter to One is less than 10 on the initial call, this code will generate multiple activation records (and, therefore, multiple copies of the local variables) on the stack. For example, were you to issue the call One(9), the stack would look like the figure below upon first encountering the end associated with the procedure Two:

As you can see, there are several copies of I and J on the stack at this point. Procedure Two (the currently executing routine) would access J in the most recent activation record that is at the bottom of the figure. The previous instance of Two will only access the variable J in its activation record when the current instance returns to One and then back to Two.

The lifetime of a variable's instance is from the point of activation record creation to the point of activation record destruction. Note that the first instance of J above (the one at the top of the diagram above) has the longest lifetime and that the lifetimes of all instances of J overlap.


[1] Note that C and C++ are not block structured languages. Other block structured languages include Algol, Ada, and Modula-2.
[2] Subject, of course, to the limitation that you not reuse the identifier within the nested procedure.
[3] There are languages that support dynamic, or run-time, scope; this text will not consider such languages.

12.0 - Chapter Overview
12.1 - Lexical Nesting, Static Links, and Displays
12.1.1 - Scope
12.1.2 - Unit Activation, Address Binding, and Variable Lifetime
12.1.3 - Static Links
12.1.4 - Accessing Non-Local Variables Using Static Links
12.1.5 - The Display
12.1.6 - The 80286 ENTER and LEAVE Instructions
12.2 - Passing Variables at Different Lex Levels as Parameters.
12.2.1 - Passing Parameters by Value in a Block Structured Language
12.2.2 - Passing Parameters by Reference, Result, and Value-Result in a Block Structured Language
12.2.3 - Passing Parameters by Name and Lazy-Evaluation in a Block Structured Language
12.3 - Passing Parameters as Parameters to Another Procedure
12.3.1 - Passing Reference Parameters to Other Procedures
12.3.2 - Passing Value-Result and Result Parameters as Parameters
12.3.3 - Passing Name Parameters to Other Procedures
12.3.4 - Passing Lazy Evaluation Parameters as Parameters
12.3.5 - Parameter Passing Summary
12.4 - Passing Procedures as Parameters
12.5 - Iterators
12.5.1 - Implementing Iterators Using In-Line Expansion
12.5.2 - Implementing Iterators with Resume Frames
12.6 - Sample Programs
12.6.1 - An Example of an Iterator
12.6.2 - Another Iterator Example


Art of Assembly Language: Chapter Twelve - 27 SEP 1996

[Next] [Art of Assembly][Randall Hyde]


Number of Web Site Hits since Jan 1, 2000: