9.7 Subroutines

     

Subroutines and methods are the basic building blocks of larger programs. At the heart of every subroutine call are two fundamental actions: it has to store the current location so it can come back to it, and it has to transfer control to the subroutine. The bsr opcode does both. It pushes the address of the next instruction onto the control stack, and then branches to a label that marks the subroutine:

 print "in main\n"   bsr _sub   print "and back\n"   end _sub:   print "in sub\n"   ret 

At the end of the subroutine, the ret instruction pops a location back off the control stack and goes there, returning control to the caller. The jsr opcode pushes the current location onto the call stack and jumps to a subroutine. Just like the jump opcode, it takes an absolute address in an integer register, so the address has to be calculated first with the set_addr opcode:

 print "in main\n"   set_addr I0, _sub   jsr I0   print "and back\n"   end _sub:   print "in sub\n"   ret 

9.7.1 Calling Conventions

A bsr or jsr is fine for a simple subroutine call, but few subroutines are quite that simple. The biggest issues revolve around register usage. Parrot has 32 registers of each type, and the caller and the subroutine share the same set of registers. How does the subroutine keep from destroying the caller's values? More importantly, who is responsible for saving and restoring registers? Where are arguments for the subroutine stored? Where are the subroutine's return values stored? A number of different answers are possible. You've seen how many ways Parrot has of storing values. The critical point is that the caller and the called subroutine have to agree on all the answers.

9.7.1.1 Reserved registers

A very simple system would be to declare that the caller uses registers through 15, and the subroutine uses 16-31. This works in a small program with light register usage. But what about a subroutine call from within another subroutine or a recursive call? The solution doesn't extend to a large scale.

9.7.1.2 Callee saves

Another possibility is to make the subroutine responsible for saving the caller's registers:

 set I0, 42   save I0              # pass args on stack   bsr _inc             # j = inc(i)   restore I1           # restore args from stack   print I1   print "\n"   end _inc:   saveall              # preserve all registers   restore I0           # get argument   inc I0               # do all the work   save I0              # push return value   restoreall           # restore caller's registers   ret 

This example stores arguments to the subroutine and return values from the subroutine on the user stack. The first statement in the _inc subroutine is a saveall to save all the caller's registers onto the backing stacks, and the last statement before the return restores them.

One advantage of this approach is that the subroutine can choose to save and restore only the register frames it actually uses, for a small speed gain. The example above could use pushi and popi instead of saveall and restoreall because it uses only integer registers. One disadvantage is that it doesn't allow optimization of tail calls, where the last statement of a recursive subroutine is a call to itself.

9.7.1.3 Parrot-calling conventions

Internal subroutines can use whatever calling convention serves them best. Externally visible subroutines and methods need stricter rules. Since these routines may be called as part of an included library or module and even from a different high-level language, it's important to have a consistent interface.

Under the Parrot-calling conventions the caller is responsible for preserving its own registers. The first 11 arguments of each register type are passed in Parrot registers, as are several other pieces of information. Register usage for subroutine calls is listed in Table 9-4.

Table 9-4. Calling and return conventions

Register

Usage

P0

Subroutine/method object

P1

Return continuation if applicable

P2

Object for a method call (invocant) or NULL for a subroutine call

P3

Array with overflow parameters/return values

S0

Fully qualified method name , if it's a method call

I0

True for prototyped parameters

I1

Number of integer arguments/return results

I2

Number of string arguments/return results

I3

Number of PMC arguments/return results

I4

Number of float arguments/return results

I5 . . . I15

First 11 integer arguments/return results

N5 . . . N15

First 11 float arguments/return results

S5 . . . S15

First 11 string arguments/return results

P5 . . . P15

First 11 PMC arguments/return results


If there are more than 11 arguments or return values of one type for the subroutine, overflow parameters are passed in an array in P3 . Subroutines without a prototype pass all their arguments or return values in P registers and if needed in the overflow array. [8]

[8] Prototyped subroutines have a defined signature.

The _inc subroutine from above can be rewritten as a prototyped subroutine:

 set I16, 42                 # use local regs from 16..31   newsub P0, .Sub, _inc       # create a new Sub object   set I5, I16                 # first integer argument   set I0, 1                   # prototype used   set I1, 1                   # one integer argument   null I2                     # no string arguments   null I3                     # no PMC arguments   null I4                     # no numeric arguments   null P2                     # no object (invocant)   pushtopi                    # preserve top I register frame   invokecc                    # call function object in P0   poptopi                     # restore registers   print I5   print "\n"   # I16 is still valid here, whatever the subroutine did   end .pcc_sub _inc:   inc I5                      # do all the work   set I0, 1                   # prototyped return   set I1, 1                   # one retval in I5   null I2                     # nothing else   null I3   null I4   invoke P1                   # return from the sub 

Instead of using a simple bsr , this set of conventions uses a subroutine object. There are several kinds of subroutine-like objects, but Sub is a class for PASM subroutines.

The .pcc_sub directive defines globally accessible subroutine objects. The _inc function above can be found as:

 find_global P20, "_inc" 

Subroutine objects of all kinds can be called with the invoke opcode. With no arguments, it calls the subroutine in P0 , which is the standard for the Parrot-calling conventions. There is also an invoke P x instruction for calling objects held in a different register.

The invokecc opcode is like invoke , but it also creates and stores a new return continuation in P1 . When the called subroutine invokes this return continuation, it returns control to the instruction after the function call. This kind of call is known as Continuation Passing Style (CPS).

In a simple example like this, it isn't really necessary to set up all the registers to obey to the Parrot-calling conventions. But when you call into library code, the subroutine is likely to check the number and type of arguments passed to it. So it's always a good idea to follow the full conventions. This is equally true for return values. The caller might check how many arguments the subroutine really returned.

Setting all these registers for every subroutine call might look wasteful at first glance, and it does increase the size of the bytecode, but you don't need to worry about execution time: the JIT system executes each register setup opcode in one CPU cycle.

9.7.2 Native Call Interface

A special version of the Parrot-calling conventions are used by the Native Call Interface (NCI) for calling subroutines with a known prototype in shared libraries. This is not really portable across all libraries, but it's worth a short example. This is a simplified version of the first test in t/pmc/nci.t :

 loadlib P1, "libnci"          # get library object for a shared lib   print "loaded\n"   dlfunc P0, P1, "nci_dd", "dd" # obtain the function object   print "dlfunced\n"   set I0, 1                     # prototype used - unchecked   set N5, 4.0                   # first argument   invoke                        # call nci_dd   ne N5, 8.0, nok_1             # the test functions returns 2*arg   print "ok 1\n"   end nok_1:    . . . 

This example shows two new instructions: loadlib and dlfunc . The loadlib opcode obtains a handle for a shared library. It searches for the shared library in the current directory, in runtime/parrot/dynext , and in a few other configured directories. It also tries to load the provided filename unaltered and with appended extensions like .so or .dll . Which extensions it tries depends on the operating system on which Parrot is running.

The dlfunc opcode gets a function object from a previously loaded library (second argument) of a specified name (third argument) with a known function signature (fourth argument). The function signature is a string where the first character is the return value and the rest of the parameters are the function parameters. The characters used in NCI function signatures are listed in Table 9-5.

Table 9-5. Function signature letters

Character

Register set

C type

v

-

void (no return value)

c

I

char

s

I

short

i

I

int

l

I

long

f

N

float

d

N

double

t

S

char *

p

P

void * (or other pointer)

I

-

Parrot_Interp * interpreter

C

-

A callback function pointer

D

-

A callback function pointer

Y

P

The subroutine into which C or D calls

Z

P

The argument for Y


For more information on callback functions, read the documentation in docs/pdds/pdd16_native_call.pod and docs/pmc/struct.pod .

9.7.3 Closures

A closure is a subroutine that retains values from the lexical scope where it was defined, even when it's called from an entirely different scope. The closure shown here is equivalent to this Perl 5 code snippet:

 #   sub foo {   #       my ($n) = @_;   #       sub {$n += shift}   #   }   #   my $closure = foo(10);   #   print &$closure(3), "\n";   #   print &$closure(20), "\n";   # call _foo   newsub P16, .Sub, _foo  # new subroutine object at address _foo   new P17, .PerlInt       # value for $n   set P17, 10             # we use local vars from P16  . . .    set P0, P16             # the subroutine   set P5, P17             # first argument   pushtopp                # save registers   invokecc                # call foo   poptopp                 # restore registers   set P18, P5             # the returned closure   # call _closure   new P19, .PerlInt       # argument to closure   set P19, 3   set P0, P18             # the closure   set P5, P19             # one argument   pushtopp                # save registers   invokecc                # call closure(3)   poptopp   print P5                # prints 13   print "\n"   # call _closure   set P19, 20             # and again   set P5, P19   set P0, P18   pushtopp   invokecc                # call closure(20)   poptopp   print P5                # prints 33   print "\n"   end _foo:   new_pad 0               # push a new pad   store_lex -1, "$n", P5  # store $n   newsub P5, .Closure, _closure                            # P5 has the lexical "$n" in the pad   invoke P1               # return _closure:   find_lex P16, "$n"      # invoking the closure pushes the lexical pad                           # of the closure on the pad stack   add P16, P5             # $n += shift   set P5, P16             # set return value   invoke P1               # return 

That's quite a lot of PASM code for such a little bit of Perl 5 code, but anonymous subroutines and closures hide a lot of magic under that simple interface. The core of this example is that when the new subroutine is created in _foo with:

 newsub P5, .Closure, _closure 

it inherits and stores the current lexical scratchpad ”the topmost scratchpad on the pad stack at the time. Later, when _closure is invoked from the main body of code, the stored pad is automatically pushed onto the pad stack. So, all the lexical variables that were available when _closure was defined are available when it's called.

9.7.4 Coroutines

As we mentioned in Chapter 8, coroutines are subroutines that can suspend themselves and return control to the caller ”and then pick up where they left off the next time they're called, as if they never left.

In PASM, coroutines are subroutine-like objects:

 newsub P0, .Coroutine, _co_entry 

The Coroutine object has its own user stack, register frame stacks, control stack, and pad stack. The pad stack is inherited from the caller. The coroutine's control stack has the caller's control stack prepended, but is still distinct. When the coroutine invokes itself, it returns to the caller and restores the caller's context (basically swapping all stacks). The next time the coroutine is invoked, it continues to execute from the point at which it previously returned:

 new_pad 0                # push a new lexical pad on stack   new P0, .PerlInt         # save one variable in it   set P0, 10   store_lex -1, "var", P0   newsub P0, .Coroutine, _cor                            # make a new coroutine object   saveall                  # preserve environment   invoke                   # invoke the coroutine   restoreall   print "back\n"   saveall   invoke                   # invoke coroutine again   restoreall   print "done\n"   pop_pad   end _cor:   find_lex P1, "var"       # inherited pad from caller   print "in cor "   print P1   print "\n"   inc P1                   # var++   saveall   invoke                   # yield( )   restoreall   print "again "   branch _cor              # next invocation of the coroutine 

This prints out the result:

 in cor 10 back again in cor 11 done 

The invoke inside the coroutine is commonly referred to as yield . The coroutine never ends. When it reaches the bottom, it branches back up to _cor and executes until it hits invoke again.

The interesting part about this example is that the coroutine yields in the same way that a subroutine is called. This means that the coroutine has to preserve its own register values. This example uses saveall but it could have only stored the registers the coroutine actually used. Saving off the registers like this works because coroutines have their own register frame stacks.

9.7.5 Continuations

A continuation is a subroutine that gets a complete copy of the caller's context, including its own copy of the call stack. Invoking a continuation starts or restarts it at the entry point:

 new P1, .PerlInt  set P1, 5  newsub P0, .Continuation, _con _con:   print "in cont "   print P1   print "\n"   dec P1   unless P1, done   invoke                        # P0 done:   print "done\n"   end 

This prints:

 in cont 5 in cont 4 in cont 3 in cont 2 in cont 1 done 

9.7.6 Evaluating a Code String

This isn't really a subroutine operation, but it does produce a code object that can be invoked. In this case, it's a bytecode segment object.

The first step is to get an assembler or compiler for the target language:

 compreg P1, "PASM" 

Within the Parrot interpreter there are currently three registered languages: PASM , PIR , and PASM1 . The first two are for Parrot assembly language and Parrot intermediate represention code. The third is for evaluating single statements in PASM. Parrot automatically adds an end opcode at the end of PASM1 strings before they're compiled.

This example places a bytecode segment object into the destination register P0 and then invokes it with invoke :

 compreg P1, "PASM1"                # get compiler set S1, "in eval\n" compile P0, P1, "print S1" invoke                             # eval code P0 print "back again\n" end 

You can register a compiler or assembler for any language inside the Parrot core and use it to compile and invoke code from that language. These compilers may be written in PASM or reside in shared libraries.

 compreg "MyLanguage", P10 

In this example the compreg opcode registers the subroutine-like object P10 as a compiler for the language "MyLanguage". See examples/compilers and examples/japh/japh16.pasm for an external compiler in a shared library.



Perl 6 and Parrot Essentials
Perl 6 and Parrot Essentials, Second Edition
ISBN: 059600737X
EAN: 2147483647
Year: 2003
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net