Sponsoring The Perl Toolchain Summit 2025: Help make this important event another success Learn more

=pod
=head0 Parrot Assembly Language
Z<CHP-5>
X<Parrot Assembly Language;;(see PASM)>
X<PASM (Parrot assembly language)>
Parrot assembly (PASM) is an assembly language written for Parrot's
virtual CPU. Basic register operations or branches in PASM generally
translate into a single CPU instruction. N<This means the JIT run time
has a performance of up to one PASM instruction per processor cycle.>
On the other hand, because it's designed to implement dynamic
high-level languages, it has support for many advanced features such as
lexical and global variables, objects, garbage collection,
continuations, coroutines, and much more. PASM is very similar in
many respects to PIR which we've already discussed, and in almost all
cases PIR should be used instead of using PASM directly. However, all
PASM syntax is also valid PIR syntax, so it's helpful to have an
understanding of the underlying operations in PASM.
X<.pasm files> A file with a F<.pasm> extension is treated as pure PASM
code by Parrot, as is any file run with the C<-a> command-line option.
This mode is mainly used for running pure PASM tests from the test suite,
and is not likely to be useful for most developers.
Some people may ask why we have PASM at all, especially with PIR which
has much nicer syntax. The answer is that PASM, like all assembly languages
has a one-to-one correspondence with the underlying Parrot Bytecode (PBC).
This makes it easy to translate from PBC to human-readable PASM code in
a disassembler program. PIR code is basically just a thin wrapper over
PASM, and you can write PASM code seamlessly in PIR files. It's always
around, and it's good to be familiar with it.
=head1 Basics
Z<CHP-5-SECT-2>
X<PASM (Parrot assembly language);overview>
PASM has a simple syntax that will be familiar to people who have experience
programming other assembly languages. Each statement stands on its own line
and there is no end-of-line delimiter like is used in many other languages.
Statements begin with a Parrot instruction, commonly referred to
as an "opcode"N<More accurately, it should probably be referred to as a
"mnemonic">. The arguments follow, separated by commas:
[label] opcode dest, source, source ...
If the opcode returns a result, it is stored in the first argument.
Sometimes the first register is both a source value and the
destination of the result, this is the case when we want to modify a
value in place, without consuming a new Parrot register to hold the
value. The arguments can be either registers or constants, although the
destination argument cannot be constant.
LABEL:
print "The answer is: "
print 42
print "\n"
end # halt the interpreter
X<PASM (Parrot assembly language);labels>
A label names a line of code so other instructions can refer to it.
Label names consist of letters, numbers, and underscores, exactly the
same syntax as is used for labels in PIR. Simple labels are often all
capital letters to make them stand out from the rest of the source code
more clearly. A label definition is simply the name of the label
followed by a colon. It can be on its own line N<In fact, we recommend
that it be on its own line, for readability.>:
LABEL:
print "Norwegian Blue\n"
or before a statement on the same line:
LABEL: print "Norwegian Blue\n"
X<PASM (Parrot assembly language);comments>
POD (plain old documentation) is also allowed in PASM like it is in PIR.
An equals sign in the first column marks the start of a POD block, and
a C<=cut> marker signals the end of a POD block.
=head1
This is POD documentation, and is treated like a
comment. The PASM interpreter ignores this.
=cut
Besides POD, there are also ordinary 1-line comments using the # sign,
which is the same in PIR:
LABEL: # This is a comment for a label
print 'Norwegian Blue\n" # Print a color
=head2 Constants
Z<CHP-5-SECT-2.1>
X<PASM (Parrot assembly language);constants>
Integer constants are signed integers.N<The size of integers is
defined when Parrot is configured. It's typically 32 bits on 32-bit
machines (a range of -2G<31> to +2G<31>-1) and twice that size on
64-bit processors.> Integer constants can have a positive (C<+>) or
negative (C<->) sign in front. Binary integers are preceded by C<0b>
or C<0B>, and hexadecimal integers are preceded by C<0x> or C<0X>:
print 42 # integer constant
print 0x2A # hexadecimal integer
print 0b1101 # binary integer
print -0b101 # binary integer with sign
Floating-point constants can also be positive or negative. Scientific
notation provides an exponent, marked with C<e> or C<E> (the sign of
the exponent is optional):
print 3.14159 # floating point constant
print 1.e6 # scientific notation
print -1.23e+45
String constants are wrapped in single or double quotation marks.
Quotation marks inside the string must be escaped by a backslash.
Other special characters also have escape sequences. These are the
same as for Perl 5's C<qq()> operator: C<\t> (tab), C<\n> (newline),
C<\r> (return), C<\f> (form feed), C<\\> (literal slash), C<\">
(literal double quote), etc.
print "string\n" # string constant with escaped newline
print "\\" # a literal backslash
print 'that\'s it' # escaped single quote
print 'a\n' # three chars: 'a', a backslash, and a 'n'
=head2 Working with Registers
Z<CHP-5-SECT-2.2>
X<PASM (Parrot assembly language);registers>
X<registers;Parrot;;(see PASM, registers)>
Parrot is a register-based virtual machine. It has 4 typed register
sets: integers, floating-point numbers, strings, and Parrot objects
(called PMCs). Register names consist of a capital letter indicating
the register set type and the number of the register. Register numbers
are non-negative (zero and positive numbers), and do not have a
pre-defined upper limit N<At least not a restrictive limit. Parrot
registers are stored internally as an array, and the register number is
an index to that array. If you call C<N2000> you are implicitly creating
a register array with 2000 entries. This can carry a performance
penalty>. For example:
I0 integer register #0
N11 number or floating point register #11
S2 string register #2
P33 PMC register #33
Integer and number registers hold values, while string and PMC
registers contain pointers to allocated memory for a string header or
a Parrot object.
In Chapter 3 we mentioned that a register name was a dollar-sign followed
by a type identifier and then a number. Now we're naming registers with
only a letter and number, not a dollar sign. Why the difference? The
dollar sign indicates to Parrot that the register names are not literal,
and that the register allocator should assign the identifier to a
physical memory location. Without the dollar sign, the register number
is an actual offset into the register array. C<N2000> is going to point
to the two thousandth register, while C<$N2000> can point to any
memory location that the register allocator determines to be free. Since
PIR attempts to protect the programmer from some of the darkest details,
Parrot requires that registers in PIR use the C<$> form. In PASM you can
use either form, but we still recommend using the C<$> form so you don't
have to worry about register allocations (and associated performance
penalties) yourself.
=head3 Register assignment
Z<CHP-5-SECT-2.2.1>
X<PASM (Parrot assembly language);registers;assignment>
The most basic operation on registers is assignment using the C<set>
opcode:
set I0, 42 # set integer register #0 to the integer value 42
set N3, 3.14159 # set number register #3 to an approximation of E<#x3C0>
set I1, I0 # set register I1 to what I0 contains
set I2, N3 # truncate the floating point number to an integer
PASM uses registers where a high-level language would use variables.
The C<exchange> opcode swaps the contents of two registers of the same
type:
exchange I1, I0 # set register I1 to what I0 contains
# and set register I0 to what I1 contains
As we mentioned before, string and PMC registers are slightly
different because they hold a pointer instead of directly holding a
value. Assigning one string register to another:
set S0, "Ford"
set S1, S0
set S0, "Zaphod"
print S1 # prints "Ford"
end
doesn't make a copy of the string; it makes a copy of the pointer.
N<Strings in Parrot use Copy-On-Write (COW) optimizations. When we
call C<set S1, S0> we copy the pointer only, so both registers point
to the same string memory. We don't actually make a copy of the string
until one of two registers is modified.> Just after C<set> C<S1>, C<S0>,
both C<S0> and C<S1> point to the same string. But assigning a constant
string to a string register allocates a new string. When "Zaphod" is
assigned to C<S0>, the pointer changes to point to the location of the
new string, leaving the old string untouched. So strings act like simple
values on the user level, even though they're implemented as pointers.
Unlike strings, assignment to a PMC doesn't automatically create a new
object; it only calls the PMC's VTABLE method for assignment N<and depending
on implementation the VTABLE assignment operation might not actually
assign anything. For now though, we can assume most VTABLE interfaces
do what they say they do.>. So, rewriting the same example using a PMC
has a completely different result:
new P0, "String"
set P0, "Ford"
set P1, P0
set P0, "Zaphod"
print P1 # prints "Zaphod"
end
The C<new> opcode creates an instance of the C<.String> class. The
class's vtable methods define how the PMC in C<P0> operates. The
first C<set> statement calls C<P0>'s vtable method
C<set_string_native>, which assigns the string "Ford" to the PMC. When
C<P0> is assigned to C<P1>:
set P1, P0
it copies the pointer, so C<P1> and C<P0> are both aliases to the same
PMC. Then, assigning the string "Zaphod" to C<P0> changes the
underlying PMC, so printing C<P1> or C<P0> prints "Zaphod".N<Contrast
this with C<assign> in A<-CHP-5-SECT-3.2>"PMC Assignment" later in
this chapter.>
=head3 PMC object types
Z<CHP-5-SECT-2.2.2>
X<PMCs (Polymorphic Containers);object types>
Internally, PMC types are represented by positive integers, and
built-in types by negative integers. PASM provides two opcodes to deal
with types. Use C<typeof> to look up the name of a type from its
integer value or to look up the named type of a PMC. Use C<find_type>
to look up the integer value of a named type.
When the source argument is a PMC and the destination is a string
register, C<typeof> returns the name of the type:
new P0, "String"
typeof S0, P0 # S0 is "String"
print S0
print "\n"
end
In this example, C<typeof> returns the type name "String".
X<PMCs (Polymorphic Containers);inheritance>
X<Parrot;classes;inheritance>
X<inheritance;with PMCs>
All Parrot classes inherit from the class C<default>. The
C<default>X<default PMC> class provides some
default functionality, but mainly throws exceptions when the default
variant of a method is called (meaning the subclass didn't define the
method).
=head3 Autoboxing
Z<CHP-5-SECT-2.2.3>
X<Auboboxing>
As we've seen in the previous chapters about PIR, we can convert between
primitive string, integer, and number types and PMCs. PIR used the C<=>
operator to make these conversions. PASM doesn't have any symbolic operators
so we have to use the underlying opcodes directly. In this case, the C<set>
opcode is used to perform data copying and data conversions automatically.
Assigning a primitive data type to a PMC of a String, Integer, or Float type
converts that PMC to the new type. So, assigning a string to a Number PMC
converts it into a String PMC. Assigning an integer value converts it to a
C<Integer>, and assigning C<undef> morphs it to C<Undef>:
new P0, "String"
set P0, "Ford\n"
print P0 # prints "Ford\n"
set P0, 42
print P0 # prints 42
print "\n"
typeof S0, P0
print S0 # prints "Integer"
print "\n"
end
C<P0> starts as a C<String>, but when C<set> assigns it an integer
value 42 (replacing the old string value C<"Ford">), it changes type
to C<Integer>. This behavior only works for the wrapper PMC types for the
primitive values string, int, and num. Other PMC classes will have different
behaviors when you try to assign a primitive value to them.
=head2 Math Operations
Z<CHP-5-SECT-2.3>
X<PASM (Parrot assembly language);math operations>
PASM has a full set of math instructions. These work with integers,
floating-point numbers, and PMCs that implement the vtable methods of
a numeric object. Most of the major math opcodes have two- and
three-argument forms:
add I0, I1 # I0 += I1
add I10, I11, I2 # I10 = I11 + I2
The three-argument form of C<add>X<add opcode (PASM)> stores the sum
of the last two registers in the first register. The two-argument form
adds the first register to the second and stores the result back in
the first register.
The source arguments can be Parrot registers or constants, but they
must be compatible with the type of the destination register.
Generally, "compatible" means that the source and destination have to
be the same type, but there are a few exceptions:
sub P0, P1, 2 # P0 = P1 - 2
sub P0, P1, 1.5 # P0 = P1 - 1.5
If the destination register is an integer register, like C<I0>, the
other arguments must be integer registers or integer constants. A
floating-point destination, like C<N0>, usually requires
floating-point arguments, but many math opcodes also allow the final
argument to be an integer. Opcodes with a PMC destination register may
take an integer, floating-point, or PMC final argument:
mul P0, P1 # P0 *= P1
mul P0, I1
mul P0, N1
mul P0, P1, P2 # P0 = P1 * P2
mul P0, P1, I2
mul P0, P1, N2
X<PMCs (Polymorphic Containers);operations on>
Operations on a PMC are implemented by the vtable method of the
destination (in the two-argument form) or the left source argument (in
the three argument form). The result of an operation is entirely
determined by the PMC. A class implementing imaginary number
operations might return an imaginary number, for example.
We won't list every math opcode here, but we'll list some of the most
common ones. You can get a complete list in A<CHP-11-SECT-1>"PASM
Opcodes" in Chapter 11.
=head3 Unary math opcodes
Z<CHP-5-SECT-2.3.1>
The unary opcodes have either a destination argument and a source
argument, or a single argument as destination and source. Some of the
most common unary math opcodes are C<inc> (increment), C<dec>
(decrement), C<abs> (absolute value), C<neg> (negate), and C<fact>
(factorial):
abs N0, -5.0 # the absolute value of -5.0 is 5.0
fact I1, 5 # the factorial of 5 is 120
inc I1 # 120 incremented by 1 is 121
=head3 Binary math opcodes
Z<CHP-5-SECT-2.3.2>
X<PASM (Parrot assembly language);math operations;binary>
Binary opcodes have two source arguments and a destination argument.
As we mentioned before, most binary math opcodes have a two-argument
form in which the first argument is both a source and the destination.
Parrot provides C<add>X<add opcode (PASM)> (addition),
C<sub>X<sub opcode (PASM)> (subtraction), C<mul>X<mul opcode (PASM)>
(multiplication), C<div>X<div opcode (PASM)> (division), and C<pow>X<pow
opcode (PASM)> (exponent) opcodes, as well as two different modulus
operations. C<mod>X<mod opcode (PASM)> is Parrot's implementation of
modulus, and C<cmod>X<cmod opcode (PASM)> is the C<%> operator from
the C library. It also provides C<gcd>X<gcd opcode (PASM)> (greatest
common divisor) and C<lcm>X<lcm opcode (PASM)> (least common
multiple).
div I0, 12, 5 # I0 = 12 / 5
mod I0, 12, 5 # I0 = 12 % 5
=head3 Floating-point operations
Z<CHP-5-SECT-2.3.3>
X<PASM (Parrot assembly language);math operations;floating-point>
Although most of the math operations work with both floating-point
numbers and integers, a few require floating-point destination
registers. Among these are C<ln> (natural log), C<log2> (log base 2),
C<log10> (log base 10), and C<exp> (I<e>G<x>), as well as a full set
of trigonometric opcodes such as C<sin> (sine), C<cos> (cosine),
C<tan> (tangent), C<sec> (secant), C<cosh> (hyperbolic cosine),
C<tanh> (hyperbolic tangent), C<sech> (hyperbolic secant), C<asin>
(arc sine), C<acos> (arc cosine), C<atan> (arc tangent), C<asec> (arc
secant), C<exsec> (exsecant), C<hav> (haversine), and C<vers>
(versine). All angle arguments for the
X<trigonometric functions (PASM)> trigonometric functions are in
radians:
sin N1, N0
exp N1, 2
The majority of the floating-point operations have a single source
argument and a single destination argument. Even though the
destination must be a floating-point register, the source can be
either an integer or floating-point number.
The C<atan>X<atan opcode (PASM)> opcode also has a three-argument
variant that implements C's C<atan2()>:
atan N0, 1, 1
=head2 Working with Strings
Z<CHP-5-SECT-2.4>
X<PASM (Parrot assembly language);string operations>
String operations work with string registers and with PMCs that implement a
string class. String operations on PMC registers require all their string
arguments to be String PMCs.
=head3 Concatenating strings
Z<CHP-5-SECT-2.4.1>
X<PASM (Parrot assembly language);string operations;concatenation>
Use the C<concat>X<concat opcode (PASM)> opcode to concatenate
strings. With string register or string constant arguments, C<concat>
has both a two-argument and a three-argument form. The first argument
is a source and a destination in the two-argument form:
set S0, "ab"
concat S0, "cd" # S0 has "cd" appended
print S0 # prints "abcd"
print "\n"
concat S1, S0, "xy" # S1 is the string S0 with "xy" appended
print S1 # prints "abcdxy"
print "\n"
end
The first C<concat> concatenates the string "cd" onto the string "ab"
in C<S0>. It generates a new string "abcd" and changes C<S0> to point
to the new string. The second C<concat> concatenates "xy" onto the
string "abcd" in C<S0> and stores the new string in C<S1>.
X<PMCs (Polymorphic Containers);concatenation>
For PMC registers, C<concat> has only a three-argument form with
separate registers for source and destination:
new P0, "String"
new P1, "String"
new P2, "String"
set P0, "ab"
set P1, "cd"
concat P2, P0, P1
print P2 # prints abcd
print "\n"
end
Here, C<concat> concatenates the strings in C<P0> and C<P1> and stores
the result in C<P2>.
=head3 Repeating strings
Z<CHP-5-SECT-2.4.2>
X<PASM (Parrot assembly language);string operations;repeating strings>
The C<repeat>X<repeat opcode (PASM)> opcode repeats a string a certain
number of times:
set S0, "x"
repeat S1, S0, 5 # S1 = S0 x 5
print S1 # prints "xxxxx"
print "\n"
end
In this example, C<repeat> generates a new string with "x" repeated
five times and stores a pointer to it in C<S1>.
=head3 Length of a string
Z<CHP-5-SECT-2.4.3>
X<PASM (Parrot assembly language);string operations;length>
The C<length>X<length opcode (PASM)> opcode returns the length of a
string in characters. This won't be the same as the length in bytes
for multibyte encoded strings:
set S0, "abcd"
length I0, S0 # the length is 4
print I0
print "\n"
end
C<length> doesn't have an equivalent for PMC strings.
=head3 Substrings
Z<CHP-5-SECT-2.4.4>
X<PASM (Parrot assembly language);string operations;substrings>
The simplest version of the C<substr>X<substr opcode (PASM)> opcode
takes four arguments: a destination register, a string, an offset
position, and a length. It returns a substring of the original string,
starting from the offset position (0 is the first character) and
spanning the length:
substr S0, "abcde", 1, 2 # S0 is "bc"
This example extracts a two-character string from "abcde" at a
one-character offset from the beginning of the string (starting with
the second character). It generates a new string, "bc", in the
destination register C<S0>.
When the offset position is negative, it counts backward from the end
of the string. So an offset of -1 starts at the last character of the
string.
C<substr> also has a five-argument form, where the fifth argument is a
string to replace the substring. This modifies the second argument and
returns the removed substring in the destination register.
set S1, "abcde"
substr S0, S1, 1, 2, "XYZ"
print S0 # prints "bc"
print "\n"
print S1 # prints "aXYZde"
print "\n"
end
This replaces the substring "bc" in C<S1> with the string "XYZ", and
returns "bc" in C<S0>.
When the offset position in a replacing C<substr> is one character
beyond the original string length, C<substr> appends the replacement
string just like the C<concat> opcode. If the replacement string is an
empty string, the characters are just removed from the original
string.
When you don't need to capture the replaced string, there's an
optimized version of C<substr> that just does a replace without
returning the removed substring.
set S1, "abcde"
substr S1, 1, 2, "XYZ"
print S1 # prints "aXYZde"
print "\n"
end
The PMC versions of C<substr> are not yet implemented.
=head3 Chopping strings
Z<CHP-5-SECT-2.4.5>
X<PASM (Parrot assembly language);string operations;chopping strings>
The C<chopn>X<chopn opcode (PASM)> opcode removes characters from the
end of a string. It takes two arguments: the string to modify and the
count of characters to remove.
set S0, "abcde"
chopn S0, 2
print S0 # prints "abc"
print "\n"
end
This example removes two characters from the end of C<S0>. If the
count is negative, that many characters are kept in the string:
set S0, "abcde"
chopn S0, -2
print S0 # prints "ab"
print "\n"
end
This keeps the first two characters in C<S0> and removes the rest.
C<chopn> also has a three-argument version that stores the chopped
string in a separate destination register, leaving the original string
untouched:
set S0, "abcde"
chopn S1, S0, 1
print S1 # prints "abcd"
print "\n"
end
=head3 Copying strings
Z<CHP-5-SECT-2.4.6>
X<PASM (Parrot assembly language);string operations;copying>
The C<clone>X<clone opcode (PASM)> opcode makes a deep copy of a
string or PMC. Instead of just copying the pointer, as normal
assignment would, it recursively copies the string or object
underneath.
new P0, "String"
set P0, "Ford"
clone P1, P0
set P0, "Zaphod"
print P1 # prints "Ford"
end
This example creates an identical, independent clone of the PMC in
C<P0> and puts a pointer to it in C<P1>. Later changes to C<P0> have
no effect on C<P1>.
With simple strings, the copy created by C<clone>, as well as the
results from C<substr>, are copy-on-write (COW). These are rather
cheap in terms of memory usage because the new memory location is only
created when the copy is assigned a new value. Cloning is rarely
needed with ordinary string registers since they always create a new
memory location on assignment.
=head3 Converting characters
Z<CHP-5-SECT-2.4.7>
X<PASM (Parrot assembly language);string operations;converting strings>
The C<chr>X<chr opcode (PASM)> opcode takes an integer value and
returns the corresponding character as a one-character string, while
the C<ord>X<ord opcode (PASM)> opcode takes a single character string
and returns the integer that represents that character in the string's
encoding:
chr S0, 65 # S0 is "A"
ord I0, S0 # I0 is 65
C<ord> has a three-argument variant that takes a character offset to
select a single character from a multicharacter string. The offset
must be within the length of the string:
ord I0, "ABC", 2 # I0 is 67
A negative offset counts backward from the end of the string, so -1 is
the last character.
ord I0, "ABC", -1 # I0 is 67
=head3 Formatting strings
Z<CHP-5-SECT-2.4.8>
X<PASM (Parrot assembly language);string operations;formatting
strings> The C<sprintf>X<sprintf opcode (PASM)> opcode generates a
formatted string from a series of values. It takes three arguments:
the destination register, a string specifying the format, and an
ordered aggregate PMC (like a C<Array>) containing the values to
be formatted. The format string and the destination register can be
either strings or PMCs:
sprintf S0, S1, P2
sprintf P0, P1, P2
The format string is similar to the one for C's C<sprintf> function,
but with some extensions for Parrot data types. Each format field in
the string starts with a C<%>
X<% (percent sign);% format strings for sprintf opcode (PASM)> and
ends with a character specifying the output format. The output format
characters are listed in A<CHP-5-TABLE-1>Table 5-1.
=begin table picture Format characters
Z<CHP-5-TABLE-1>
=headrow
=row
=cell Format
=cell Meaning
=bodyrows
=row
=cell C<%c>
=cell A character.
=row
=cell C<%d>
=cell A decimal integer.
=row
=cell C<%i>
=cell A decimal integer.
=row
=cell C<%u>
=cell An unsigned integer.
=row
=cell C<%o>
=cell An octal integer.
=row
=cell C<%x>
=cell A hex integer, preceded by 0x when # is specified.
=row
=cell C<%X>
=cell A hex integer with a capital X (when # is specified).
=row
=cell C<%b>
=cell A binary integer, preceded by 0b when # is specified.
=row
=cell C<%B>
=cell A binary integer with a capital B (when # is specified).
=row
=cell C<%p>
=cell A pointer address in hex.
=row
=cell C<%f>
=cell A floating-point number.
=row
=cell C<%e>
=cell A floating-point number in scientific notation (displayed with a
lowercase "e").
=row
=cell C<%E>
=cell The same as C<%e>, but displayed with an uppercase E.
=row
=cell C<%g>
=cell The same as either C<%e> or C<%f>,
whichever fits best.
=row
=cell C<%G>
=cell The same as C<%g>, but displayed with an uppercase E.
=row
=cell C<%s>
=cell A string.
=end table
Each format field can be specified with several options: R<flags>,
R<width>, R<precision>, and R<size>. The format flags are listed in
A<CHP-5-TABLE-2>Table 5-2.
=begin table picture Format flags
Z<CHP-5-TABLE-2>
=headrow
=row
=cell Flag
=cell Meaning
=bodyrows
=row
=cell 0
=cell Pad with zeros.
=row
=cell E<lt>spaceE<gt>
=cell Pad with spaces.
=row
=cell C<+>
=cell Prefix numbers with a sign.
=row
=cell C<->
=cell Align left.
=row
=cell C<#>
=cell Prefix a leading 0 for octal, 0x for hex, or force a decimal point.
=end table
The R<width> is a number defining the minimum width of the output from
a field. The R<precision> is the maximum width for strings or
integers, and the number of decimal places for floating-point fields.
If either R<width> or R<precision> is an asterisk (C<*>), it takes its
value from the next argument in the PMC.
The R<size> modifier defines the type of the argument the field takes.
The flags are listed in A<CHP-5-TABLE-3>Table 5-3.
=begin table picture Size flags
Z<CHP-5-TABLE-3>
=headrow
=row
=cell Character
=cell Meaning
=bodyrows
=row
=cell C<h>
=cell short or float
=row
=cell C<l>
=cell long
=row
=cell C<H>
=cell huge value (long long or long double)
=row
=cell C<v>
=cell INTVAL or FLOATVAL
=row
=cell C<O>
=cell opcode_t
=row
=cell C<P>
=cell C<PMC>
=row
=cell C<S>
=cell string
=end table
The values in the aggregate PMC must have a type compatible with the
specified R<size>.
Here's a short illustration of string formats:
new P2, "Array"
new P0, "Int"
set P0, 42
push P2, P0
new P1, "Num"
set P1, 10
push P2, P1
sprintf S0, "int %#Px num %+2.3Pf\n", P2
print S0 # prints "int 0x2a num +10.000"
print "\n"
end
The first eight lines create a C<Array> with two elements: a
C<Int> and a C<Num>. The format string of the C<sprintf> has
two format fields. The first, C<%#Px>, takes a PMC argument from the
aggregate (C<P>) and formats it as a hexadecimal integer (C<x>), with
a leading 0x (C<#>). The second format field, C<%+2.3Pf>, takes a PMC
argument (C<P>) and formats it as a floating-point number (C<f>), with
a minimum of two whole digits and a maximum of three decimal places
(C<2.3>) and a leading sign (C<+>).
The test files F<t/op/string.t> and F<t/src/sprintf.t> have many more
examples of format strings.
=head3 Testing for substrings
Z<CHP-5-SECT-2.4.9>
X<PASM (Parrot assembly language);string operations;testing for substrings>
The C<index>X<index opcode (PASM)> opcode searches for a substring
within a string. If it finds the substring, it returns the position
where the substring was found as a character offset from the beginning
of the string. If it fails to find the substring, it returns -1:
index I0, "Beeblebrox", "eb"
print I0 # prints 2
print "\n"
index I0, "Beeblebrox", "Ford"
print I0 # prints -1
print "\n"
end
C<index> also has a four-argument version, where the fourth argument
defines an offset position for starting the search:
index I0, "Beeblebrox", "eb", 3
print I0 # prints 5
print "\n"
end
This finds the second "eb" in "Beeblebrox" instead of the first,
because the search skips the first three characters in the
string.
=head3 Joining strings
The C<join> opcode joins the elements of an array PMC into a single
string. The second argument separates the individual elements of the
PMC in the final string result.
new P0, "Array"
push P0, "hi"
push P0, 0
push P0, 1
push P0, 0
push P0, "parrot"
join S0, "__", P0
print S0 # prints "hi__0__1__0__parrot"
end
This example builds a C<Array> in C<P0> with the values C<"hi">,
C<0>, C<1>, C<0>, and C<"parrot">. It then joins those values (separated
by the string C<"__">) into a single string, and stores it in C<S0>.
=head3 Splitting strings
Splitting a string yields a new array containing the resulting
substrings of the original string.
split P0, "", "abc"
set P1, P0[0]
print P1 # 'a'
set P1, P0[2]
print P1 # 'c'
end
This example splits the string "abc" into individual characters and
stores them in an array in C<P0>. It then prints out the first and
third elements of the array. For now, the split pattern (the second
argument to the opcode) is ignored except for a test to make sure that
its length is zero.
=head2 Logical and Bitwise Operations
Z<CHP-5-SECT-2.6>
X<PASM (Parrot assembly language);bitwise operations>
X<PASM (Parrot assembly language);logical operations>
The X<logical opcodes> logical opcodes evaluate the truth of their
arguments. They're often used to make decisions on control flow.
Logical operations are implemented for integers and PMCs. Numeric
values are false if they're 0, and true otherwise. Strings are false
if they're the empty string or a single character "0", and true
otherwise. PMCs are true when their
C<get_bool>X<get_bool vtable method (PASM)> vtable method returns a
nonzero value.
The C<and>X<and opcode (PASM)> opcode returns the second argument if
it's false and the third argument otherwise:
and I0, 0, 1 # returns 0
and I0, 1, 2 # returns 2
The C<or>X<or opcode (PASM)> opcode returns the second argument if
it's true and the third argument otherwise:
or I0, 1, 0 # returns 1
or I0, 0, 2 # returns 2
or P0, P1, P2
Both C<and> and C<or> are short-circuiting. If they can determine what
value to return from the second argument, they'll never evaluate the
third. This is significant only for PMCs, as they might have side
effects on evaluation.
The C<xor>X<xor opcode (PASM)> opcode returns the second argument if
it is the only true value, returns the third argument if it is the
only true value, and returns false if both values are true or both are
false:
xor I0, 1, 0 # returns 1
xor I0, 0, 1 # returns 1
xor I0, 1, 1 # returns 0
xor I0, 0, 0 # returns 0
The C<not>X<not opcode (PASM)> opcode returns a true value when the
second argument is false, and a false value if the second argument is
true:
not I0, I1
not P0, P1
The X<bitwise;opcodes (PASM)> bitwise opcodes operate on their values
a single bit at a time. C<band>X<band opcode (PASM)>,
C<bor>X<bor opcode (PASM)>, and C<bxor>X<bxor opcode (PASM)> return a
value that is the logical AND, OR, or XOR of each bit in the source
arguments. They each take a destination register and two source
registers. They also have two-argument forms where the destination is
also a source. C<bnot>X<bnot opcode (PASM)> is the logical NOT of
each bit in a single source argument.
bnot I0, I1
band P0, P1
bor I0, I1, I2
bxor P0, P1, I2
X<bitwise;string opcodes>
The bitwise opcodes also have string variants for AND, OR, and XOR:
C<bors>X<bors opcode (PASM)>, C<bands>X<bands opcode (PASM)>, and
C<bxors>X<bxors opcode (PASM)>. These take string register or PMC
string source arguments and perform the logical operation on each byte
of the strings to produce the final string.
bors S0, S1
bands P0, P1
bors S0, S1, S2
bxors P0, P1, I2
The bitwise string opcodes only have meaningful results when they're
used with simple ASCII strings because the bitwise operation is done
per byte.
The logical and arithmetic shift operations shift their values by a
specified number of bits:
shl I0, I1, I2 # shift I1 left by count I2 giving I0
shr I0, I1, I2 # arithmetic shift right
lsr P0, P1, P2 # logical shift right
=head1 Working with PMCs
Z<CHP-5-SECT-3>
In most of the examples we've shown so far, X<PMCs (Polymorphic
Containers);working with> PMCs just duplicate the functionality of
integers, numbers, and strings. They wouldn't be terribly useful if
that's all they did, though. PMCs offer several advanced features,
each with its own set of operations.
=head2 Aggregates
Z<CHP-5-SECT-3.1>
PMCs can define complex types that hold multiple values. These are
commonly called "X<PMCs (Polymorphic Containers);aggregate>
X<aggregate PMCs> aggregates." The most important feature added for
aggregates is keyed access. Elements within an aggregate PMC can be
stored and retrieved by a numeric or string key. PASM also offers a
full set of operations for manipulating aggregate data types.
Since PASM is intended to implement Perl, the two most fully featured
aggregates already in operation are arrays and hashes. Any aggregate
defined for any language could take advantage of the features
described here.
=head3 Arrays
Z<CHP-5-SECT-3.1.1>
X<PMCs (Polymorphic Containers);arrays>
The C<Array>X<Array PMC> PMC is an ordered aggregate with
zero-based integer keys. The syntax for X<keyed access to PMCs> keyed access to a
PMC puts the key in square brackets after the register name:
new P0, "Array" # obtain a new array object
set P0, 2 # set its length
set P0[0], 10 # set first element to 10
set P0[1], I31 # set second element to I31
set I0, P0[0] # get the first element
set I1, P0 # get array length
A key on the destination register of a C<set> operation sets a value
for that key in the aggregate. A key on the source register of a
C<set> returns the value for that key. If you set C<P0> without a key,
you set the length of the array, not one of its values.N<C<Array>
is an autoextending array, so you never need to set its length. Other
array types may require the length to be set explicitly.> And if you
assign the C<Array> to an integer, you get the length of the
array.
By the time you read this, the syntax for getting and setting the
length of an array may have changed. The change would separate array
allocation (how much storage the array provides) from the actual
element count. The currently proposed syntax uses C<set> to set or
retrieve the allocated size of an array, and an C<elements>
X<elements opcode (PASM)> opcode to set or retrieve the count of
elements stored in the array.
set P0, 100 # allocate store for 100 elements
elements P0, 5 # set element count to 5
set I0, P0 # obtain current allocation size
elements I0, P0 # get element count
Some other useful instructions for working with arrays are C<push>,
C<pop>, C<shift>, and C<unshift> (you'll find them in
A<CHP-11-SECT-1>"PASM Opcodes" in Chapter 11).
=head3 Hashes
Z<CHP-5-SECT-3.1.2>
X<PMCs (Polymorphic Containers);hashes>
The C<Hash>X<Hash PMC> PMC is an unordered aggregate with
string keys:
new P1, "Hash" # generate a new hash object
set P1["key"], 10 # set key and value
set I0, P1["key"] # obtain value for key
set I1, P1 # number of entries in hash
The C<exists>X<exists opcode (PASM)> opcode tests whether a keyed
value exists in an aggregate. It returns 1 if it finds the key in the
aggregate, and returns 0 if it doesn't. It doesn't care if the value
itself is true or false, only that the key has been set:
new P0, "Hash"
set P0["key"], 0
exists I0, P0["key"] # does a value exist at "key"
print I0 # prints 1
print "\n"
end
The C<delete>X<delete opcode (PASM)> opcode is also useful for working
with hashes: it removes a key/value pair.
=head3 Iterators
Z<CHP-5-SECT-3.1.3>
Iterators extract values from an aggregate PMC. You create an iterator
by creating a new C<Iterator> PMC, and passing the array to C<new> as
an additional parameter:
new P1, "Iterator", P2
The include file F<iterator.pasm> defines some constants for working
with iterators. The C<.ITERATE_FROM_START> and C<.ITERATE_FROM_END>
constants are used to select whether an array iterator starts from the
beginning or end of the array. The C<shift> opcode extracts values
from the array. An iterator PMC is true as long as it still has values
to be retrieved (tested by C<unless> below).
.include "iterator.pasm"
new P2, "Array"
push P2, "a"
push P2, "b"
push P2, "c"
new P1, "Iterator", P2
set P1, .ITERATE_FROM_START
iter_loop:
unless P1, iter_end
shift P5, P1
print P5 # prints "a", "b", "c"
branch iter_loop
iter_end:
end
Hash iterators work similarly to array iterators, but they extract
keys. With hashes it's only meaningful to iterate in one direction,
since they don't define any order for their keys.
.include "iterator.pasm"
new P2, "Hash"
set P2["a"], 10
set P2["b"], 20
set P2["c"], 30
new P1, "Iterator", P2
set P1, .ITERATE_FROM_START_KEYS
iter_loop:
unless P1, iter_end
shift S5, P1 # one of the keys "a", "b", "c"
set I9, P2[S5]
print I9 # prints e.g. 20, 10, 30
branch iter_loop
iter_end:
end
=head3 Data structures
Z<CHP-5-SECT-3.1.4>
X<PMCs (Polymorphic Containers);data structures>
Arrays and hashes can hold any data type, including other aggregates.
Accessing elements deep within nested data structures is a common
operation, so PASM provides a way to do it in a single instruction.
Complex keys specify a series of nested data structures, with each
individual key separated by a semicolon:
new P0, "Hash"
new P1, "Array"
set P1[2], 42
set P0["answer"], P1
set I1, 2
set I0, P0["answer";I1] # $i = %hash{"answer"}[2]
print I0
print "\n"
end
This example builds up a data structure of a hash containing an array.
The complex key C<P0["answer";I1]> retrieves an element of the array
within the hash. You can also set a value using a complex key:
set P0["answer";0], 5 # %hash{"answer"}[0] = 5
The individual keys are integers or strings, or registers with integer
or string values.
=head2 PMC Assignment
Z<CHP-5-SECT-3.2>
We mentioned before that C<set> on two X<PMCs (Polymorphic
Containers);assignment> PMCs simply aliases them both to the same object,
and that C<clone> creates a complete duplicate object. But if you just
want to assign the value of one PMC to another PMC, you need the
C<assign>X<assign opcode (PASM)> opcode:
new P0, "Int"
new P1, "Int"
set P0, 42
set P2, P0
assign P1, P0 # note: P1 has to exist already
inc P0
print P0 # prints 43
print "\n"
print P1 # prints 42
print "\n"
print P2 # prints 43
print "\n"
end
This example creates two C<Int> PMCs: C<P0> and C<P1>. It gives
C<P0> a value of 42. It then uses C<set> to give the same value to
C<P2>, but uses C<assign> to give the value to C<P1>. When C<P0> is
incremented, C<P2> also changes, but C<P1> doesn't. The destination
register for C<assign> must have an existing object of the right type
in it, since C<assign> doesn't create a new object (as with C<clone>)
or reuse the source object (as with C<set>).
=head2 Properties
Z<CHP-5-SECT-3.3>
X<PMCs (Polymorphic Containers);properties>
PMCs can have additional values attached to them as "properties" of
the PMC. What these properties do is entirely up to the language being
implemented. Perl 6 uses them to store extra information about a
variable: whether it's a constant, if it should always be interpreted
as a true value, etc.
The C<setprop>X<setprop opcode (PASM)> opcode sets the value of a
named property on a PMC. It takes three arguments: the PMC to be set
with a property, the name of the property, and a PMC containing the
value of the property. The C<getprop>X<getprop opcode (PASM)> opcode
returns the value of a property. It also takes three arguments: the
PMC to store the property's value, the name of the property, and the
PMC from which the property value is to be retrieved:
new P0, "String"
set P0, "Zaphod"
new P1, "Int"
set P1, 1
setprop P0, "constant", P1 # set a property on P0
getprop P3, "constant", P0 # retrieve a property on P0
print P3 # prints 1
print "\n"
end
This example creates a C<String> object in C<P0>, and a C<Int>
object with the value 1 in C<P1>. C<setprop> sets a property named
"constant" on the object in C<P0> and gives the property the value in
C<P1>.N<The "constant" property is ignored by PASM, but is significant
to the Perl 6 code running on top of it.> C<getprop> retrieves the
value of the property "constant" on C<P0> and stores it in C<P3>.
Properties are kept in a separate hash for each PMC. Property values
are always PMCs, but only references to the actual PMCs. Trying to
fetch the value of a property that doesn't exist returns a
C<Undef>.
C<delprop>X<delprop opcode (PASM)> deletes a property from a PMC.
delprop P1, "constant" # delete property
You can also return a complete hash of all properties on a PMC with
C<prophash>X<prophash opcode (PASM)>.
prophash P0, P1 # set P0 to the property hash of P1
=head1 Flow Control
Z<CHP-5-SECT-4>
X<PASM (Parrot assembly language);flow control>
Although it has many advanced features, at heart PASM is an assembly
language. All flow control in PASM--as in most assembly languages--is
done with branches and jumps.
Branch instructions transfer control to a relative offset from the
current instruction. The rightmost argument to every branch opcode is
a label, which the assembler converts to the integer value of the
offset. You can also branch on a literal integer value, but there's
rarely any need to do so. The simplest branch instruction is
C<branch>:
branch L1 # branch 4
print "skipped\n"
L1:
print "after branch\n"
end
This example unconditionally branches to the location of the label
C<L1>, skipping over the first C<print> statement.
Jump instructions transfer control to an absolute address. The C<jump>
opcode doesn't calculate an address from a label, so it's used
together with C<set_addr>:
set_addr I0, L1
jump I0
print "skipped\n"
end
L1:
print "after jump\n"
end
The C<set_addr>X<set_addr opcode (PASM)> opcode takes a label or an
integer offset and returns an absolute address.
You've probably noticed the C<end>X<end opcode (PASM)> opcode as the
last statement in many examples above. This terminates the execution
of the current run loop. Terminating the main bytecode segment (the
first run loop) stops the interpreter. Without the C<end> statement,
execution just falls off the end of the bytecode segment, with a good
chance of crashing the interpreter.
=head2 Conditional Branches
Z<CHP-5-SECT-4.1>
X<PASM (Parrot assembly language);conditional branches>
X<conditional branches in PASM>
Unconditional jumps and branches aren't really enough for flow
control. What you need to implement the control structures of
high-level languages is the ability to select different actions based
on a set of conditions. PASM has opcodes that conditionally branch
based on the truth of a single value or the comparison of two values.
The following example has C<if>X<if (conditional);opcode (PASM)> and
C<unless>X<unless (conditional);opcode (PASM)> conditional branches:
set I0, 0
if I0, TRUE
unless I0, FALSE
print "skipped\n"
end
TRUE:
print "shouldn't happen\n"
end
FALSE:
print "the value was false\n"
end
C<if> branches if its first argument is a true value, and C<unless>
branches if its first argument is a false value. In this case, the
C<if> doesn't branch because C<I0> is false, but the C<unless> does
branch.
The comparison branching opcodes compare two values and branch if the
stated relation holds true. These are
C<eq>X<eq (equal);opcode (PASM)> (branch when equal),
C<ne>X<ne (not equal);opcode (PASM)> (when not equal),
C<lt>X<lt (less than);opcode (PASM)> (when less than),
C<gt>X<gt (greater than);opcode (PASM)> (when greater than),
C<le>X<le (less than or equal);opcode (PASM)> (when less than or
equal), and C<ge>X<ge (greater than or equal);opcode (PASM)> (when
greater than or equal). The two compared arguments must be the same
register type:
set I0, 4
set I1, 4
eq I0, I1, EQUAL
print "skipped\n"
end
EQUAL:
print "the two values are equal\n"
end
This compares two integers, C<I0> and C<I1>, and branches if they're
equal. Strings of different character sets or encodings are converted
to Unicode before they're compared. PMCs have a C<cmp> vtable method.
This gets called on the left argument to perform the comparison of the
two objects.
The comparison opcodes don't specify if a numeric or string comparison
is intended. The type of the register selects for integers, floats,
and strings. With PMCs, the vtable method C<cmp> or C<is_equal> of the
first argument is responsible for comparing the PMC meaningfully with
the other operand. If you need to force a numeric or string comparison
on two PMCs, use the alternate comparison opcodes that end in the
C<_num> and C<_str> suffixes.
eq_str P0, P1, label # always a string compare
gt_num P0, P1, label # always numerically
Finally, the C<eq_addr> opcode branches if two PMCs or strings are
actually the same object (have the same address):
eq_addr P0, P1, same_pmcs_found
=head2 Iteration
Z<CHP-5-SECT-4.2>
X<iteration;in PASM>
X<PASM (Parrot assembly language);iteration>
PASM doesn't define high-level loop constructs. These are built up
from a combination of conditional and unconditional branches. A
I<do-while>X<do-while style loop;(PASM)> style loop can be constructed
with a single conditional branch:
set I0, 0
set I1, 10
REDO:
inc I0
print I0
print "\n"
lt I0, I1, REDO
end
This example prints out the numbers 1 to 10. The first time through,
it executes all statements up to the C<lt> statement. If the
condition evaluates as true (C<I0> is less than C<I1>) it branches to
the C<REDO> label and runs the three statements in the loop body
again. The loop ends when the condition evaluates as false.
Conditional and unconditional branches can build up quite complex
looping constructs, as follows:
# loop ($i=1; $i<=10; $i++) {
# print "$i\n";
# }
loop_init:
set I0, 1
branch loop_test
loop_body:
print I0
print "\n"
branch loop_continue
loop_test:
le I0, 10, loop_body
branch out
loop_continue:
inc I0
branch loop_test
out:
end
X<loops;PASM>
X<PASM (Parrot assembly language);loops>
This example emulates a X<counter-controlled loop> counter-controlled
loop like Perl 6's C<loop> keyword or C's C<for>. The first time
through the loop it sets the initial value of the counter in
C<loop_init>, tests that the loop condition is met in C<loop_test>,
and then executes the body of the loop in C<loop_body>. If the test
fails on the first iteration, the loop body will never execute. The
end of C<loop_body> branches to C<loop_continue>, which increments the
counter and then goes to C<loop_test> again. The loop ends when the
condition fails, and it branches to C<out>. The example is more
complex than it needs to be just to count to 10, but it nicely shows
the major components of a
loop.
=head1 Lexicals and Globals
Z<CHP-5-SECT-6>
So far, we've been treating Parrot registers like the variables of a
high-level language. This is fine, as far as it goes, but it isn't the
full picture. The dynamic nature and introspective features of
languages like Perl make it desirable to manipulate variables by name,
instead of just by register or stack location. These languages also
have global variables, which are visible throughout the entire
program. Storing a global variable in a register would either tie up
that register for the lifetime of the program or require some unwieldy way
to shuffle the data into and out of registers.
Parrot provides structures for storing both global and lexically
scoped named variables. Lexical and global variables must be PMC
values. PASM provides instructions for storing and retrieving
variables from these structures so the PASM opcodes can operate on
their values.
=head2 Globals
Z<CHP-5-SECT-6.1>
X<PASM (Parrot assembly language);global variables>
Global variables are stored in a C<Hash>, so every variable name
must be unique. PASM has two opcodes for globals, C<store_global> and
C<find_global>:
new P10, "Int"
set P10, 42
store_global "$foo", P10
# ...
find_global P0, "$foo"
print P0 # prints 42
end
The first two statements create a C<Int> in the PMC register
C<P10> and give it the value 42. In the third statement,
C<store_global> stores that PMC as the named global variable C<$foo>.
At some later point in the program, C<find_global> retrieves the PMC
from the global variable by name, and stores it in C<P0> so it can be
printed.
The C<store_global> opcode only stores a reference to the object. If
we add an increment statement:
inc P10
after the C<store_global> it increments the stored global, printing 43.
If that's not what you want, you can C<clone> the PMC before you store
it. Leaving the global variable as an alias does have advantages,
though. If you retrieve a stored global into a register and modify it
as follows:
find_global P0, "varname"
inc P0
the value of the stored global is directly modified, so you don't need
to call C<store_global> again.
The two-argument forms of C<store_global> and C<find_global> store or
retrieve globals from the outermost namespace (what Perl users will
know as the "main" namespace). A simple flat global namespace isn't
enough for most languages, so Parrot also needs to support
hierarchical namespaces for separating packages (classes and modules
in Perl 6). The three-argument versions of C<store_global> and
C<find_global> add an argument to select a nested namespace:
store_global "Foo", "var", P0 # store P0 as var in the Foo namespace
find_global P1, "Foo", "var" # get Foo::var
Eventually the global opcodes will have variants that take a PMC to
specify the namespace, but the design and implementation of these
aren't finished yet.
=head2 Lexicals
Z<CHP-5-SECT-6.2>
X<PASM (Parrot assembly language);lexical variables>
Lexical variables are stored in a lexical scratchpad. There's one pad
for each lexical scope. Every pad has both a hash and an array, so
elements can be stored either by name or by numeric index.
=head3 Basic instructions
Z<CHP-5-SECT-6.2.1>
To store a lexical variable in the current scope pad, use C<store_lex>.
Likewise, use C<find_lex> to retrieve a variable from the current pad.
new P0, "Int" # create a variable
set P0, 10 # assign value to it
store_lex "foo", P0 # store the var with the variable name "foo"
# ...
find_lex P1, "foo" # get the var "foo" into P1
print P1
print "\n" # prints 10
end
=head1 Subroutines
Z<CHP-5-SECT-7>
X<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
C<bsr>X<bsr opcode (PASM)> 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 C<ret> instruction pops a location
back off the control stack and goes there, returning control to the
caller. The C<jsr>X<jsr opcode (PASM)> opcode pushes the current location
onto the call stack and jumps to a subroutine. Just like the C<jump>
opcode, it takes an absolute address in an integer register, so the
address has to be calculated first with the C<set_addr>X<set_addr
opcode (PASM)> opcode:
print "in main\n"
set_addr I0, _sub
jsr I0
print "and back\n"
end
_sub:
print "in sub\n"
ret
=head2 Calling Conventions
Z<CHP-5-SECT-7.1>
X<registers;usage for subroutine calls>
X<subroutines;register usage>
X<subroutines;calling conventions>
A C<bsr> or C<jsr> is fine for a simple subroutine call, but few
subroutines are quite that simple. Who is responsible for saving and
restoring the registers, however, so that they don't get overwritten when
we perform a c<bsr> or C<jsr>? How are arguments passed? 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.
=head3 Parrot calling conventions
Z<CHP-5-SECT-7.1.2>
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.
The C<.sub> directive defines globally accessible subroutine
objects.
Subroutine objects of all kinds can be called with the
C<invoke>X<invoke opcode (PASM)> opcode. There is also an C<invoke>
C<PR<x>> instruction for calling objects held in a different register.
The C<invokecc>X<invokecc opcode (PASM)> opcode is like C<invoke>, but it
also creates and stores a new return continuation. 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).
X<CPS (Continuation Passing Style)>
X<Continuation Passing Style (CPS)>
=head2 Native Call Interface
Z<CHP-5-SECT-7.2>
X<subroutines;calling conventions;NCI>
A special version of the Parrot calling conventions are used by the
X<NCI (Native Call Interface)> 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 F<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: C<loadlib> and C<dlfunc>. The
C<loadlib>X<loadlib opcode (PASM)> opcode obtains a handle for a shared
library. It searches for the shared library in the current directory,
in F<runtime/parrot/dynext>, and in a few other configured
directories. It also tries to load the provided filename unaltered and
with appended extensions like C<.so> or C<.dll>. Which extensions it
tries depends on the OS Parrot is running on.
The C<dlfunc>X<dlfunc opcode (PASM)> 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 X<NCI (Native Call Interface);function signatures>
NCI function signatures are listed in A<CHP-5-TABLE-5>Table 5-5.
=begin table picture Function signature letters
Z<CHP-5-TABLE-5>
=headrow
=row
=cell Character
=cell Register set
=cell C type
=bodyrows
=row
=cell C<v>
=cell -
=cell void (no return value)
=row
=cell C<c>
=cell C<I>
=cell char
=row
=cell C<s>
=cell C<I>
=cell short
=row
=cell C<i>
=cell C<I>
=cell int
=row
=cell C<l>
=cell C<I>
=cell long
=row
=cell C<f>
=cell C<N>
=cell float
=row
=cell C<d>
=cell C<N>
=cell double
=row
=cell C<t>
=cell C<S>
=cell char *
=row
=cell C<p>
=cell C<P>
=cell void * (or other pointer)
=row
=cell C<I>
=cell -
=cell Parrot_Interp *interpreter
=row
=cell C<C>
=cell -
=cell a callback function pointer
=row
=cell C<D>
=cell -
=cell a callback function pointer
=row
=cell C<Y>
=cell C<P>
=cell the subroutine C<C> or C<D> calls into
=row
=cell C<Z>
=cell C<P>
=cell the argument for C<Y>
=end table
For more information on callback functions, read the documentation in
F<docs/pdds/pdd16_native_call.pod> and F<docs/pmc/struct.pod>.
=head2 Coroutines
Z<CHP-5-SECT-7.4>
As we mentioned in the previous chapter, coroutines are
X<subroutines;coroutines> 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.
X<coroutines>
In PASM, coroutines are subroutine-like objects:
newsub P0, .Coroutine, _co_entry
The C<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, "Int" # 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
X<invoke opcode (PASM);coroutines and>
The C<invoke> inside the coroutine is commonly referred to as
I<yield>. The coroutine never ends. When it reaches the bottom, it
branches back up to C<_cor> and executes until it hits C<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
C<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.
=head2 Continuations
Z<CHP-5-SECT-7.5>
X<continuations>
X<subroutines;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, "Int"
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
=head2 Evaluating a Code String
Z<CHP-5-SECT-7.6>
X<code strings, evaluating>
This isn't really a subroutine operation, but it does produce a code
object that can be invoked. In this case, it's a X<bytecode segment
object> 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: C<PASM>, C<PIR>, and C<PASM1>. The first two are for parrot
assembly language and parrot intermediate representation code. The third
is for evaluating single statements in PASM. Parrot automatically adds
an C<end> opcode at the end of C<PASM1> strings before they're
compiled.
This example places a bytecode segment object into the destination
register C<P0> and then invokes it with C<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 C<compreg> opcode registers the subroutine-like
object C<P10> as a compiler for the language "MyLanguage". See
F<examples/compilers> and F<examples/japh/japh16.pasm> for an external
compiler in a shared library.
=head1 Exceptions and Exception Handlers
Z<CHP-5-SECT-8>
X<exceptions>
X<exception handlers>
Exceptions provide a way of calling a piece of code outside the normal
flow of control. They are mainly used for error reporting or cleanup
tasks, but sometimes exceptions are just a funny way to branch from
one code location to another one. The design and implementation of
exceptions in Parrot isn't complete yet, but this section will give
you an idea where we're headed.
Exceptions are objects that hold all the information needed to handle
the exception: the error message, the severity and type of the error,
etc. The class of an exception object indicates the kind of exception
it is.
Exception handlers are derived from continuations. They are ordinary
subroutines that follow the Parrot calling conventions, but are never
explicitly called from within user code. User code pushes an exception
handler onto the control stack with the C<set_eh>X<set_eh opcode (PASM)>
opcode. The system calls the installed exception handler only when an
exception is thrown (perhaps because of code that does division by
zero or attempts to retrieve a global that wasn't stored.)
newsub P20, .ExceptionHandler, _handler
set_eh P20 # push handler on control stack
null P10 # set register to null
find_global P10, "none" # may throw exception
clear_eh # pop the handler off the stack
...
_handler: # if not, execution continues here
is_null P10, not_found # test P10
...
This example creates a new exception handler subroutine with the
C<newsub> opcode and installs it on the control stack with the
C<set_eh> opcode. It sets the C<P10> register to a null value (so it
can be checked later) and attempts to retrieve the global variable
named C<none>. If the global variable is found, the next statement
(C<clear_eh>) pops the exception handler off the control stack and
normal execution continues. If the C<find_global> call doesn't find
C<none> it throws an exception by pushing an exception object onto the
control stack. When Parrot sees that it has an exception, it pops it
off the control stack and calls the exception handler C<_handler>.
The first exception handler in the control stack sees every exception
thrown. The handler has to examine the exception object and decide
whether it can handle it (or discard it) or whether it should
C<rethrow> the exception to pass it along to an exception handler
deeper in the stack. The C<rethrow>X<rethrow opcode (PASM)> opcode is only
valid in exception handlers. It pushes the exception object back onto
the control stack so Parrot knows to search for the next exception
handler in the stack. The process continues until some exception
handler deals with the exception and returns normally, or until there
are no more exception handlers on the control stack. When the system
finds no installed exception handlers it defaults to a final action,
which normally means it prints an appropriate message and terminates
the program.
When the system installs an exception handler, it creates a return
continuation with a snapshot of the current interpreter context. If
the exception handler just returns (that is, if the exception is
cleanly caught) the return continuation restores the control stack
back to its state when the exception handler was called, cleaning up
the exception handler and any other changes that were made in the
process of handling the exception.
Exceptions thrown by standard Parrot opcodes (like the one thrown by
C<find_global> above or by the C<throw> opcode) are always resumable,
so when the exception handler function returns normally it continues
execution at the opcode immediately after the one that threw the
exception. Other exceptions at the run-loop level are also generally
resumable.
new P10, 'Exception' # create new Exception object
set P10, 'I die' # set message attribute
throw P10 # throw it
Exceptions are designed to work with the Parrot calling conventions.
Since the return addresses of C<bsr> subroutine calls and exception
handlers are both pushed onto the control stack, it's generally a bad
idea to combine the two.
=head1 Events
Z<CHP-5-SECT-9>
An event is a notification that something has happened: a timer
expired, an IO operation finished, a thread sent a message to
another thread, or the user pressed C<Ctrl-C> to interrupt program
execution.
What all of these events have in common is that they arrive
asynchronously. It's generally not safe to interrupt program flow at an
arbitrary point and continue at a different position, so the event is
placed in the
interpreter's task queue. The run loops code regularly checks whether
an event needs to be handled. Event handlers may be an internal piece
of code or a user-defined event handler subroutine.
Events are still experimental in Parrot, so the implementation and
design is subject to change.
=head2 Timers
Z<CHP-5-SECT-9.1>
C<Timer> objects are the replacement for Perl 5's C<alarm> handlers.
They are also a significant improvement. Timers can fire once or
repeatedly, and multiple timers can run independently. The precision
of a timer is limited by the OS Parrot runs on, but it is always more
fine-grained then a whole second. The final syntax isn't yet fixed, so
please consult the documentation for examples.
=head2 Signals
Z<CHP-5-SECT-9.2>
Signal handling is related to events. When Parrot gets a signal it
needs to handle from the OS, it converts that signal into an event and
broadcasts it to all running threads. Each thread independently
decides if it's interested in this signal and, if so, how to respond to it.
newsub P20, .ExceptionHandler, _handler
set_eh P20 # establish signal handler
print "send SIGINT:\n"
sleep 2 # press ^C after you saw start
print "no SIGINT\n"
end
_handler:
.include "signal.pasm" # get signal definitions
print "caught "
set I0, P5["type"] # if _type is negative, the ...
neg I0, I0 # ... negated type is the signal
ne I0, .SIGINT, nok
print "SIGINT\n"
nok:
end
This example creates a signal handler and pushes it on to the control
stack. It then prompts the user to send a C<SIGINT> from the shell
(this is usually C<Ctrl-C>, but it varies in different shells), and
waits for 2 seconds. If the user doesn't send a SIGINT in 2 seconds
the example just prints "no SIGINT" and ends. If the user does send a
SIGINT, the signal handler catches it, prints out "caught SIGINT" and
ends.N<Currently, only Linux installs a C<SIGINT> C<sigaction>
handler, so this example won't work on other platforms.>
=head1 Threads
Z<CHP-5-SECT-10>
Threads allow multiple pieces of code to run in parallel. This is
useful when you have multiple physical CPUs to share the load of
running individual threads. With a single processor, threads still
provide the feeling of parallelism, but without any improvement in
execution time. Even worse, sometimes using threads on a single
processor will actually slow down your program.
Still, many algorithms can be expressed more easily in terms of
parallel running pieces of code and many applications profit from
taking advantage of multiple CPUs. Threads can vastly simplify
asynchronous programs like internet servers: a thread splits off,
waits for some IO to happen, handles it, and relinquishes the
processor again when it's done.
Parrot compiles in thread support by default (at least, if the
platform provides some kind of support for it). Unlike Perl 5,
compiling with threading support doesn't impose any execution time
penalty for a non-threaded program. Like exceptions and events,
threads are still under development, so you can expect significant
changes in the near future.
As outlined in the previous chapter, Parrot implements three different
threading models. (B<Note>: As of version 1.0, the C<TQueue> PMC will be
deprecated, rendering the following discussion obsolete.) The following
example uses the third model, which takes advantage of shared data. It uses a
C<TQueue> (thread-safe queue) object to synchronize the two parallel running
threads. This is only a simple example to illustrate threads, not a typical
usage of threads (no-one really wants to spawn two threads just to print out a
simple string).
find_global P5, "_th1" # locate thread function
new P2, "ParrotThread" # create a new thread
find_method P0, P2, "thread3" # a shared thread's entry
new P7, "TQueue" # create a Queue object
new P8, "Int" # and a Int
push P7, P8 # push the Int onto queue
new P6, "String" # create new string
set P6, "Js nte artHce\n"
set I3, 3 # thread function gets 3 args
invoke # _th1.run(P5,P6,P7)
new P2, "ParrotThread" # same for a second thread
find_global P5, "_th2"
set P6, "utaohrPro akr" # set string to 2nd thread's
invoke # ... data, run 2nd thread too
end # Parrot joins both
.pcc_sub _th1: # 1st thread function
w1: sleep 0.001 # wait a bit and schedule
defined I1, P7 # check if queue entry is ...
unless I1, w1 # ... defined, yes: it's ours
set S5, P6 # get string param
substr S0, S5, I0, 1 # extract next char
print S0 # and print it
inc I0 # increment char pointer
shift P8, P7 # pull item off from queue
if S0, w1 # then wait again, if todo
invoke P1 # done with string
.pcc_sub _th2: # 2nd thread function
w2: sleep 0.001
defined I1, P7 # if queue entry is defined
if I1, w2 # then wait
set S5, P6
substr S0, S5, I0, 1 # if not print next char
print S0
inc I0
new P8, "Int" # and put a defined entry
push P7, P8 # onto the queue so that
if S0, w2 # the other thread will run
invoke P1 # done with string
This example creates a C<ParrotThread> object and calls its C<thread3>
method, passing three arguments: a PMC for the C<_th1> subroutine in
C<P5>, a string argument in C<P6>, and a C<TQueue> object in C<P7>
containing a single integer. Remember from the earlier section
A<CHP-5-SECT-7.1.3>"Parrot calling conventions" that registers 5-15
hold the arguments for a subroutine or method call and C<I3> stores
the number of arguments. The thread object is passed in C<P2>.
This call to the C<thread3> method spawns a new thread to run the
C<_th1> subroutine. The main body of the code then creates a second
C<ParrotThread> object in C<P2>, stores a different subroutine in
C<P5>, sets C<P6> to a new string value, and then calls the C<thread3>
method again, passing it the same C<TQueue> object as the first
thread. This method call spawns a second thread. The main body of code
then ends, leaving the two threads to do the work.
At this point the two threads have already started running. The first
thread (C<_th1>) starts off by sleeping for a 1000th of a second. It
then checks if the C<TQueue> object contains a value. Since it
contains a value when the thread is first called, it goes ahead and
runs the body of the subroutine. The first thing this does is shift
the element off the C<TQueue>. It then pulls one character off a copy
of the string parameter using C<substr>, prints the character,
increments the current position (C<I0>) in the string, and loops back
to the C<w1> label and sleeps. Since the queue doesn't have any
elements now, the subroutine keeps sleeping.
Meanwhile, the second thread (C<_th2>) also starts off by sleeping for
a 1000th of a second. It checks if the shared C<TQueue> object
contains a defined value but unlike the first thread it only continues
sleeping if the queue does contain a value. Since the queue contains a
value when the second thread is first called, the subroutine loops
back to the C<w2> label and continues sleeping. It keeps sleeping
until the first thread shifts the integer off the queue, then runs the
body of the subroutine. The body pulls one character off a copy of the
string parameter using C<substr>, prints the character, and increments
the current position in the string. It then creates a new
C<Int>, pushes it onto the shared queue, and loops back to the
C<w2> label again to sleep. The queue has an element now, so the
second thread keeps sleeping, but the first thread runs through its
loop again.
The two threads alternate like this, printing a character and marking
the queue so the next thread can run, until there are no more
characters in either string. At the end, each subroutine invokes the
return continuation in C<P1> which terminates the thread. The
interpreter waits for all threads to terminate in the cleanup phase
after the C<end> in the main body of code.
The final printed result (as you might have guessed) is:
Just another Parrot Hacker
The syntax for threads isn't carved in stone and the implementation
still isn't finished but as this example shows, threads are working
now and already useful.
Several methods are useful when working with threads. The C<join>
method belongs to the C<ParrotThread> class. When it's called on a
C<ParrotThread> object, the calling code waits until the thread
terminates.
new P2, "ParrotThread" # create a new thread
set I5, P2 # get thread ID
find_method P0, P2, "join" # get the join method...
invoke # ...and join (wait for) the thread
set P16, P5 # the return result of the thread
C<kill> and C<detach> are interpreter methods, so you have to grab the
current interpreter object before you can look up the method object.
set I5, P2 # get thread ID of thread P2
getinterp P3 # get this interpreter object
find_method P0, P3, "kill" # get kill method
invoke # kill thread with ID I5
find_method P0, P3, "detach"
invoke # detach thread with ID I5
By the time you read this, some of these combinations of statements
and much of the threading syntax above may be reduced to a simpler set
of opcodes.
=head1 Loading Bytecode
Z<CHP-5-SECT-11>
In addition to running Parrot bytecode on the command-line, you can
also load pre-compiled bytecode directly into your PASM source file.
The C<load_bytecode>X<load_bytecode opcode (PASM)> opcode takes a single
argument: the name of the bytecode file to load. So, if you create a
file named F<file.pasm> containing a single subroutine:
# file.pasm
.sub _sub2: # .sub stores a global sub
print "in sub2\n"
invoke P1
and compile it to bytecode using the C<-o> command-line switch:
$ parrot -o file.pbc file.pasm
You can then load the compiled bytecode into F<main.pasm> and directly
call the subroutine defined in F<file.pasm>:
# main.pasm
main:
load_bytecode "file.pbc" # compiled file.pasm
find_global P0, "_sub2"
invokecc
end
The C<load_bytecode> opcode also works with source files, as long as
Parrot has a compiler registered for that type of file:
# main2.pasm
main:
load_bytecode "file.pasm" # PASM source code
find_global P0, "_sub2"
invokecc
end
Subroutines marked with C<:load> run as soon as they're loaded (before
C<load_bytecode> returns), rather than waiting to be called. A
subroutine marked with C<:main> will always run first, no matter what
name you give it or where you define it in the file.
# file3.pasm
.sub :load # mark the sub as to be run
print "file3\n"
invoke P1 # return
# main3.pasm
first: # first is never invoked
print "never\n"
invoke P1
.sub :main # because _main is marked as the
print "main\n" # MAIN entry of program execution
load_bytecode "file3.pasm"
print "back\n"
end
This example uses both C<:load> and C<:main>. Because the C<main>
subroutine is defined with C<:main> it will execute first even though
another subroutine comes before it in the file. C<main> prints a
line, loads the PASM source file, and then prints another line.
Because C<_entry> in F<file3.pasm> is marked with C<:load> it runs
before C<load_bytecode> returns, so the final output is:
main
file3
back
=head1 Classes and Objects
Z<CHP-5-SECT-12>
This section revolves around one complete example that defines a
class, instantiates objects, and uses them. The whole example is
included at the end of the section.
=head2 Class declaration
Z<CHP-5-SECT-12.1>
X<classes;in PASM>
The C<newclass>X<newclass opcode (PASM)> opcode defines a new class.
It takes two arguments, the name of the class and the destination
register for the class PMC. All classes (and objects) inherit from the
C<ParrotClass> PMCX<ParrotClass PMC>, which is the core of the Parrot
object system.
newclass P1, "Foo"
To instantiate a new object of a particular class, you first look up
the integer value for the class type with the C<find_type> opcode,
then create an object of that type with the C<new> opcode:
find_type I1, "Foo"
new P3, I1
The C<new> opcode also checks to see if the class defines a
method named "__init" and calls it if it exists.
=head2 Attributes
Z<CHP-5-SECT-12.2>
X<attributes;in PASM>
X<classes;attributes>
The C<addattribute> opcode creates a slot in the class for an
attribute (sometimes known as an I<instance variable>) and associates
it with a name:
addattribute P1, ".i" # Foo.i
This chunk of code
from the C<__init> method looks up the position of the first
attribute, creates a C<Int> PMC, and stores it as the first
attribute:
classoffset I0, P2, "Foo" # first "Foo" attribute of object P2
new P6, "Int" # create storage for the attribute
setattribute P2, I0, P6 # store the first attribute
The C<classoffset> opcodeX<classoffset opcode (PASM)> takes a PMC
containing an object and the name of its class, and returns an integer
index for the position of the first attribute. The C<setattribute>
opcode uses the integer index to store a PMC value in one of the
object's attribute slots. This example initializes the first
attribute. The second attribute would be at C<I0 + 1>, the third
attribute at C<I0 + 2>, etc:
inc I0
setattribute P2, I0, P7 # store next attribute
...
There is also support for named parameters with fully qualified
parameter names (although this is a little bit slower than getting
the class offset once and accessing several attributes by index):
new P6, "Int"
setattribute P2, "Foo\x0.i", P6 # store the attribute
You use the same integer index to retrieve the value of an attribute.
The C<getattribute>X<getattribute opcode (PASM)> opcode takes an object and
an index as arguments and returns the attribute PMC at that position:
classoffset I0, P2, "Foo" # first "Foo" attribute of object P2
getattribute P10, P2, I0 # indexed get of attribute
or
getattribute P10, P2, "Foo\x0.i" # named get
To set the value of an attribute PMC, first retrieve it with
C<getattribute> and then assign to the returned PMC. Because PMC
registers are only pointers to values, you don't need to store the PMC
again after you modify its value:
getattribute P10, P2, I0
set P10, I5
=head2 Methods
Z<CHP-5-SECT-12.3>
X<methods;in PASM>
X<classes;methods>
X<classes;namespaces>
Methods in PASM are just subroutines installed in the namespace of the
class. You define a method with the C<.pcc_sub> directive before the
label:
.pcc_sub _half: # I5 = self."_half"()
classoffset I0, P2, "Foo"
getattribute P10, P2, I0
set I5, P10 # get value
div I5, 2
invoke P1
This routine returns half of the value of the first attribute of the
object. Method calls use the Parrot calling conventions so they always
pass the I<invocant> object (often called I<self>) in C<P2>. Invoking
the return continuation in C<P1> returns control to the caller.
The C<.pcc_sub> directive automatically stores the subroutine as a
global in the current namespace. The C<.namespace> directive sets the
current namespace:
.namespace [ "Foo" ]
If the namespace is explicitly set to an empty string or key, then the
subroutine is stored in the outermost namespace.
The C<callmethodcc>X<callmethodcc opcode (PASM)> opcode makes a method
call. It follows the Parrot calling conventions, so it expects to
find the invocant object in C<P2>, the method object in C<P0>, etc. It
adds one bit of magic, though. If you pass the name of the method in
C<S0>, C<callmethodcc> looks up that method name in the invocant
object and stores the method object in C<P0> for you:
set S0, "_half" # set method name
set P2, P3 # the object
callmethodcc # create return continuation, call
print I5 # result of method call
print "\n"
The C<callmethodcc> opcode also generates a return continuation and
stores it in C<P1>. The C<callmethod> opcode doesn't generate a return
continuation, but is otherwise identical to C<callmethodcc>. Just like
ordinary subroutine calls, you have to preserve and restore any
registers you want to keep after a method call. Whether you store
individual registers, register frames, or half register frames is up
to you.
=head3 Overriding vtable functions
Z<CHP-5-SECT-12.3.1>
Every object inherits a default set of I<vtable> functions from the
C<ParrotObject> PMC, but you can also override them with your own
methods. The vtable functions have predefined names that start with a
double underscore "__". The following code defines a method named
C<__init> in the C<Foo> class that initializes the first attribute of
the object with an integer:
.sub __init:
classoffset I0, P2, "Foo" # lookup first attribute position
new P6, "Int" # create storage for the attribute
setattribute P2, I0, P6 # store the first attribute
invoke P1 # return
Ordinary methods have to be called explicitly, but the vtable
functions are called implicitly in many different contexts. Parrot
saves and restores registers for you in these calls. The C<__init>
method is called whenever a new object is constructed:
find_type I1, "Foo"
new P3, I1 # call __init if it exists
A few other vtable functions in the complete code example for this
section are C<__set_integer_native>, C<__add>, C<__get_integer>,
C<__get_string>, and C<__increment>. The C<set> opcode calls Foo's
C<__set_integer_native> vtable function when its destination register
is a C<Foo> object and the source register is a native integer:
set P3, 30 # call __set_integer_native method
The C<add> opcode calls Foo's C<__add> vtable function when it adds
two C<Foo> objects:
new P4, I1 # same with P4
set P4, 12
new P5, I1 # create a new store for add
add P5, P3, P4 # __add method
The C<inc> opcode calls Foo's C<__increment> vtable function when it
increments a C<Foo> object:
inc P3 # __increment
Foo's C<__get_integer> and C<__get_string> vtable functions are called
whenever an integer or string value is retrieved from a C<Foo> object:
set I10, P5 # __get_integer
...
print P5 # calls __get_string, prints 'fortytwo'
=head2 Inheritance
Z<CHP-5-SECT-12.4>
X<inheritance;in PASM>
X<classes;inheritance>
The C<subclass>X<subclass opcode (PASM)> opcode creates a new class that
inherits methods and attributes from another class. It takes 3
arguments: the destination register for the new class, a register
containing the parent class, and the name of the new class:
subclass P3, P1, "Bar"
X<multiple inheritance; in PASM>
For multiple inheritance, the C<addparent>X<addparent opcode (PASM)>
opcode adds additional parents to a subclass.
newclass P4, "Baz"
addparent P3, P4
To override an inherited method, define a method with the same name in
the namespace of the subclass. The following code overrides Bar's
C<__increment> method so it decrements the value instead of
incrementing it:
.namespace [ "Bar" ]
.sub __increment:
classoffset I0, P2, "Foo" # get Foo's attribute slot offset
getattribute P10, P2, I0 # get the first Foo attribute
dec P10 # the evil line
invoke P1
Notice that the attribute inherited from C<Foo> can only be looked up
with the C<Foo> class name, not the C<Bar> class name. This preserves
the distinction between attributes that belong to the class and
inherited attributes.
Object creation for subclasses is the same as for ordinary classes:
find_type I1, "Bar"
new P5, I1
Calls to inherited methods are just like calls to methods defined in
the class:
set P5, 42 # inherited __set_integer_native
inc P5 # overridden __increment
print P5 # prints 41 as Bar's __increment decrements
print "\n"
set S0, "_half" # set method name
set P2, P5 # the object
callmethodcc # create return continuation, call
print I5
print "\n"
=head2 Additional Object Opcodes
Z<CHP-5-SECT-12.5>
The C<isa> and C<can> opcodes are also useful when working with
objects. C<isa>X<isa opcode (PASM)> checks whether an object belongs to or
inherits from a particular class. C<can>X<can opcode (PASM)> checks whether
an object has a particular method. Both return a true or false value.
isa I0, P3, "Foo" # 1
isa I0, P3, "Bar" # 1
can I0, P3, "__add" # 1
=head2 Complete Example
Z<CHP-5-SECT-12.6>
newclass P1, "Foo"
addattribute P1, "$.i" # Foo.i
find_type I1, "Foo"
new P3, I1 # call __init if it exists
set P3, 30 # call __set_integer_native method
new P4, I1 # same with P4
set P4, 12
new P5, I1 # create a new LHS for add
add P5, P3, P4 # __add method
set I10, P5 # __get_integer
print I10
print "\n"
print P5 # calls __get_string prints 'fortytwo'
print "\n"
inc P3 # __increment
add P5, P3, P4
print P5 # calls __get_string prints '43'
print "\n"
subclass P3, P1, "Bar"
find_type I1, "Bar"
new P3, I1
set P3, 100
new P4, I1
set P4, 200
new P5, I1
add P5, P3, P4
print P5 # prints 300
print "\n"
set P5, 42
print P5 # prints 'fortytwo'
print "\n"
inc P5
print P5 # prints 41 as Bar's
print "\n" # __increment decrements
set S0, "_half" # set method name
set P2, P3 # the object
callmethodcc # create return continuation, call
print I5 # prints 50
print "\n"
end
.namespace [ "Foo" ]
.sub __init:
classoffset I0, P2, "Foo" # lookup first attribute position
new P6, "Int" # create a store for the attribute
setattribute P2, I0, P6 # store the first attribute
invoke P1 # return
.sub __set_integer_native:
classoffset I0, P2, "Foo"
getattribute P10, P2, I0
set P10, I5 # assign passed in value
invoke P1
.sub __get_integer:
classoffset I0, P2, "Foo"
getattribute P10, P2, I0
set I5, P10 # return value
invoke P1
.sub __get_string:
classoffset I0, P2, "Foo"
getattribute P10, P2, I0
set I5, P10
set S5, P10 # get stringified value
ne I5, 42, ok
set S5, "fortytwo" # or return modified one
ok:
invoke P1
.sub __increment:
classoffset I0, P2, "Foo"
getattribute P10, P2, I0 # as with all aggregates, this
inc P10 # has reference semantics - no
invoke P1 # setattribute needed
.sub __add:
classoffset I0, P2, "Foo"
getattribute P10, P2, I0 # object
getattribute P11, P5, I0 # argument
getattribute P12, P6, I0 # destination
add P12, P10, P11
invoke P1
.sub _half: # I5 = _half(self)
classoffset I0, P2, "Foo"
getattribute P10, P2, I0
set I5, P10 # get value
div I5, 2
invoke P1
.namespace [ "Bar" ]
.sub __increment:
classoffset I0, P2, "Foo" # get Foo's attribute slot offset
getattribute P10, P2, I0 # get the first Foo attribute
dec P10 # the evil line
invoke P1
# end of object example
This example prints out:
42
fortytwo
43
300
fortytwo
41
50
=cut
# Local variables:
# c-file-style: "parrot"
# End:
# vim: expandtab shiftwidth=4: