The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

Parrot assembly (PASM) is an assembly language written for Parrot's virtual CPU. PASM has an interesting mix of features. Because it's an assembly language, it has many low-level features, such as flow control based on branches and jumps, and direct manipulation of values in the software registers and on the stacks. Basic register operations or branches are generally a single CPU instruction.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.

Basics

PASM has a simple syntax. Each statement stands on its own line. Statements begin with a Parrot instruction code (commonly referred to as an "opcode"). 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. The arguments are either registers or constants, though only source arguments can be constants:

  LABEL:
      print "The answer is: "
      print 42
      print "\n"
      end                # halt the interpreter

A label names a line of code so other instructions can refer to it. Label names consist of letters, numbers, and underscores. Simple labels are often all caps to make them stand out more clearly. A label definition is simply the name of the label followed by a colon. It can be on its own line:

  LABEL:
      print "Norwegian Blue\n"

or before a statement on the same line:

  LABEL: print "Norwegian Blue\n"

Comments are marked with the hash sign (#) and continue to the end of the line.

POD (plain old documentation) markers are ignored by Parrot. An equals sign in the first column marks the start of a POD block, and a =cut marks the end of a POD block.

  =head1
  ...
  =cut

Constants

Integer constants are signed integers.The size of integers is defined when Parrot is configured. It's typically 32 bits on 32-bit machines (a range of -231 to +231-1) and twice that size on 64-bit processors. Integer constants can have a positive (+) or negative (-) sign in front. Binary integers are preceded by 0b or 0B, and hexadecimal integers are preceded by 0x or 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 e or 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 qq() operator: \t (tab), \n (newline), \r (return), \f (form feed), \\ (literal slash), \" (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'

Working with Registers

Parrot is a register-based virtual machine. It has 4 typed register sets with 32 registers in each set. The types are integers, floating-point numbers, strings, and Parrot objects. Register names consist of a capital letter indicating the register set and the number of the register, between 0 and 31. For example:

  I0   integer register #0
  N11  number or floating point register #11
  S2   string register #2
  P31  PMC register #31

Integer and number registers hold values, while string and PMC registers contain pointers to allocated memory for a string header or a Parrot object.

The length of strings is limited only by your system's virtual memory and by the size of integers on the particular platform. Parrot can work with strings of different character types and encodings. It automatically converts string operands with mixed characteristics to Unicode.This conversion isn't fully implemented yet. Parrot Magic Cookies (PMCs) are Parrot's low-level objects. They can represent data of any arbitrary type. The operations (methods) for each PMC class are defined in a fixed vtable, which is a structure containing function pointers that implement each operation.

Register assignment

The most basic operation on registers is assignment using the 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 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. Just after set S1, S0, both S0 and S1 point to the same string. But assigning a constant string to a string register allocates a new string. When "Zaphod" is assigned to 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. 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 new opcode creates an instance of the .String class. The class's vtable methods define how the PMC in P0 operates. The first set statement calls P0's vtable method set_string_native, which assigns the string "Ford" to the PMC. When P0 is assigned to P1:

  set P1, P0

it copies the pointer, so P1 and P0 are both aliases to the same PMC. Then, assigning the string "Zaphod" to P0 changes the underlying PMC, so printing P1 or P0 prints "Zaphod".Contrast this with assign in -CHP-9-SECT-3.2"PMC Assignment" later in this chapter.

PMC 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 typeof to look up the name of a type from its integer value or to look up the named type of a PMC. Use 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, typeof returns the name of the type:

  new P0, "String"
  typeof S0, P0               # S0 is "String"
  print S0
  print "\n"
  end

In this example, typeof returns the type name "String".

When the source argument is a PMC and the destination is an integer register, typeof returns the integer representation of the type:

  new P0, "String"
  typeof I0, P0               # I0 is 36
  print I0
  print "\n"
  end

This example returns the integer representation of String, which is 36.

When typeof's source argument is an integer, it returns the name of the type represented by that integer:

  set I1, -100
  typeof S0, I1               # S0 is "INTVAL"
  print S0
  print "\n"
  end

In this example typeof returns the type name "INTVAL" because the integer representation of a built-in integer value is -100.

The source argument to find_type is always a string containing a type name, and the destination register is always an integer. It returns the integer representation of the type with that name:

  find_type I1, "String"  # I1 is 36
  print I1
  print "\n"
  find_type I2, "INTVAL"      # I2 is -100
  print I2
  print "\n"
  end

Here, the name "String" returns 36, and the name "INTVAL" returns -100.

All Parrot classes inherit from the class default, which has the type number 0. The default 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). Type number 0 returns the type name "illegal", since no object should ever be created from the default class:

  find_type I1, "fancy_super_long_double" # I1 is 0
  print I1
  print "\n"
  typeof S0, I1                           # S0 is "illegal"
  print S0
  print "\n"
  end

The type numbers are not fixed values. They change whenever a new class is added to Parrot or when the class hierarchy is altered. An include file containing an enumeration of PMC types (runtime/parrot/include/pmctypes.pasm) is generated during the configuration of the Parrot source tree. Internal data types and their names are specified in runtime/parrot/include/datatypes.pasm.

You can generate a complete and current list of valid PMC types by running this command within the main Parrot source directory:

  $ perl classes/pmc2c2.pl --tree classes/*.pmc

which produces output like:

  Array
      default
  Boolean
      Int
          perlscalar
              scalar
                  default
  Closure
      Sub
          default
  ...

The output traces the class hierarchy for each class: Boolean inherits from Int, which is derived from the abstract perlscalar, scalar, and default classes (abstract classes are listed in lowercase). The actual classnames and their hierarchy may have changed by the time you read this.

Type morphing

The classes Undef, Int, Num, and String implement Perl's polymorphic scalar behavior. Assigning a string to a number PMC morphs it into a string PMC. Assigning an integer value morphs it to a Int, and assigning undef morphs it to 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 "Int"
  print "\n"
  end

P0 starts as a String, but when set assigns it an integer value 42 (replacing the old string value "Ford"), it changes type to Int.

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 add 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 I0, I1, 2          # I0 = I1 - 2
  sub N0, N1, 1.5        # N0 = N1 - 1.5

If the destination register is an integer register, like I0, the other arguments must be integer registers or integer constants. A floating-point destination, like 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

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 CHP-11-SECT-1"PASM Opcodes" in Chapter 11.

Unary math opcodes

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 inc (increment), dec (decrement), abs (absolute value), neg (negate), and 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

Binary math opcodes

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 add (addition), sub (subtraction), mul (multiplication), div (division), and pow (exponent) opcodes, as well as two different modulus operations. mod is Parrot's implementation of modulus, and cmod is the % operator from the C library. It also provides gcd (greatest common divisor) and lcm (least common multiple).

  div I0, 12, 5   # I0 = 12 / 5
  mod I0, 12, 5   # I0 = 12 % 5

Floating-point operations

Although most of the math operations work with both floating-point numbers and integers, a few require floating-point destination registers. Among these are ln (natural log), log2 (log base 2), log10 (log base 10), and exp (ex), as well as a full set of trigonometric opcodes such as sin (sine), cos (cosine), tan (tangent), sec (secant), cosh (hyperbolic cosine), tanh (hyperbolic tangent), sech (hyperbolic secant), asin (arc sine), acos (arc cosine), atan (arc tangent), asec (arc secant), exsec (exsecant), hav (haversine), and vers (versine). All angle arguments for the 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 atan opcode also has a three-argument variant that implements C's atan2():

  atan N0, 1, 1

Working with Strings

The string operations work with string registers and with PMCs that implement a string class.

Most operations on string registers generate new strings in the destination register. Some operations have an optimized form that modifies an existing string in place. These are denoted by an _r suffix, as in substr_r. Please note that substr_r has been deprecated.

String operations on PMC registers require all their string arguments to be PMCs.

Concatenating strings

Use the concat opcode to concatenate strings. With string register or string constant arguments, 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 concat concatenates the string "cd" onto the string "ab" in S0. It generates a new string "abcd" and changes S0 to point to the new string. The second concat concatenates "xy" onto the string "abcd" in S0 and stores the new string in S1.

For PMC registers, 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, concat concatenates the strings in P0 and P1 and stores the result in P2.

Repeating strings

The repeat 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, repeat generates a new string with "x" repeated five times and stores a pointer to it in S1.

Length of a string

The length 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

Currently, length doesn't have an equivalent for PMC strings, but it probably will be implemented in the future.

Substrings

The simplest version of the substr 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 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.

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 S1 with the string "XYZ", and returns "bc" in S0.

When the offset position in a replacing substr is one character beyond the original string length, substr appends the replacement string just like the 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 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 substr are not yet implemented.

Chopping strings

The chopn 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 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 S0 and removes the rest. 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

Copying strings

The clone 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 P0 and puts a pointer to it in P1. Later changes to P0 have no effect on P1.

With simple strings, the copy created by clone, as well as the results from 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.

Converting characters

The chr opcode takes an integer value and returns the corresponding character as a one-character string, while the ord 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

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

Formatting strings

The sprintf 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 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 sprintf function, but with some extensions for Parrot data types. Each format field in the string starts with a % and ends with a character specifying the output format. The output format characters are listed in CHP-9-TABLE-1Table 9-1.

Each format field can be specified with several options: flags, width, precision, and size. The format flags are listed in CHP-9-TABLE-2Table 9-2.

The width is a number defining the minimum width of the output from a field. The precision is the maximum width for strings or integers, and the number of decimal places for floating-point fields. If either width or precision is an asterisk (*), it takes its value from the next argument in the PMC.

The size modifier defines the type of the argument the field takes. The flags are listed in CHP-9-TABLE-3Table 9-3.

The values in the aggregate PMC must have a type compatible with the specified 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 Array with two elements: a Int and a Num. The format string of the sprintf has two format fields. The first, %#Px, takes a PMC argument from the aggregate (P) and formats it as a hexadecimal integer (x), with a leading 0x (#). The second format field, %+2.3Pf, takes a PMC argument (P) and formats it as a floating-point number (f), with a minimum of two whole digits and a maximum of three decimal places (2.3) and a leading sign (+).

The test files t/op/string.t and t/src/sprintf.t have many more examples of format strings.

Testing for substrings

The index 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

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.

Joining strings

The 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 Array in P0 with the values "hi", 0, 1, 0, and "parrot". It then joins those values (separated by the string "__") into a single string, and stores it in S0.

Splitting strings

Splitting a string yields a new array containing the resulting substrings of the original string. Since regular expressions aren't implemented yet, the current implementation of the split opcode just splits individual characters, much like Perl 5's split with an empty pattern.

  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 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.

I/O Operations

The I/O subsystem has at least one set of significant revisions ahead, so you can expect this section to change. It's worth an introduction, though, because the basic set of opcodes is likely to stay the same, even if their arguments and underlying functionality change.

Open and close a file

The open opcode opens a file for access. It takes three arguments: a destination register, the name of the file, and a modestring. It returns a ParrotIO object on success and a Undef object on failure. The ParrotIO object hides OS-specific details.

  open P0, "people.txt", "<"

The modestring specifies whether the file is opened in read-only (<), write-only (>), read-write (+<), or append mode (>>).

The close opcode closes a ParrotIO object:

  close P0        # close a PIO

Output operations

We already saw the print opcode in several examples above. The one argument form prints a register or constant to stdout. It also has a two-argument form: the first argument is the ParrotIO object where the value is printed.

  print P0, "xxx"         # print to PIO in P0

The getstdin, getstdout, and getstderr opcodes return ParrotIO objects for the stdio streams:

  getstdin P0
  gestdout P0
  getstderr P0

Printing to stderr has a shortcut:

  printerr "troubles"
  getstderr P10
  print P10, "troubles"   # same

Reading from files

The read opcode reads a specified number of bytes from either stdin or a ParrotIO object:

  read S0, I0             # read from stdin up to I0 bytes into S0
  read S0, P0, I0         # read from the PIO in P0 up to I0 bytes

readline is a variant of read that works with ParrotIO objects. It reads a whole line at a time, terminated by the newline character:

  getstdin P0
  readline S0, P0         # read a line from stdin

The seek opcode sets the current file position on a ParrotIO object. It takes four arguments: a destination register, a ParrotIO object, an offset, and a flag specifying the origin point:

  seek I0, P0, I1, I2

In this example, the position of P0 is set by an offset (I1) from an origin point (I2). 0 means the offset is from the start of the file, 1 means the offset is from the current position, and 2 means the offset is from the end of the file. The return value (in I0) is 0 when the position is successfully set and -1 when it fails. seek also has a five-argument form that seeks with a 64-bit offset, constructed from two 32-bit arguments.

Logical and Bitwise Operations

The 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 get_bool vtable method returns a nonzero value.

The and 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 or 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 and and 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 xor 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 not 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 bitwise opcodes operate on their values a single bit at a time. band, bor, and bxor 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. bnot 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

The bitwise opcodes also have string variants for AND, OR, and XOR: bors, bands, and bxors. 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

Working with PMCs

In most of the examples we've shown so far, 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.

Aggregates

PMCs can define complex types that hold multiple values. These are commonly called " 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.

Arrays

The Array PMC is an ordered aggregate with zero-baed integer keys. The syntax for 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 set operation sets a value for that key in the aggregate. A key on the source register of a set returns the value for that key. If you set P0 without a key, you set the length of the array, not one of its values.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 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 set to set or retrieve the allocated size of an array, and an elements opcode to set or retreive 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 push, pop, shift, and unshift (you'll find them in CHP-11-SECT-1"PASM Opcodes" in Chapter 11).

Hashes

The Hash 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 exists 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 delete opcode is also useful for working with hashes: it removes a key/value pair.

Iterators

Iterators extract values from an aggregate PMC. You create an iterator by creating a new Iterator PMC, and passing the array to new as an additional parameter:

      new P1, "Iterator", P2

The include file iterator.pasm defines some constants for working with iterators. The .ITERATE_FROM_START and .ITERATE_FROM_END constants are used to select whether an array iterator starts from the beginning or end of the array. The shift opcode extracts values from the array. An iterator PMC is true as long as it still has values to be retrieved (tested by 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

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 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.

PMC Assignment

We mentioned before that set on two PMCs simply aliases them both to the same object, and that clone creates a complete duplicate object. But if you just want to assign the value of one PMC to another PMC, you need the assign 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 Int PMCs: P0 and P1. It gives P0 a value of 42. It then uses set to give the same value to P2, but uses assign to give the value to P1. When P0 is incremented, P2 also changes, but P1 doesn't. The destination register for assign must have an existing object of the right type in it, since assign doesn't create a new object (as with clone) or reuse the source object (as with set).

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 setprop 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 getprop 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 String object in P0, and a Int object with the value 1 in P1. setprop sets a property named "constant" on the object in P0 and gives the property the value in P1.The "constant" property is ignored by PASM, but is significant to the Perl 6 code running on top of it. getprop retrieves the value of the property "constant" on P0 and stores it in 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 Undef.

delprop 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 prophash.

  prophash P0, P1         # set P0 to the property hash of P1

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 branch:

    branch L1                # branch 4
    print "skipped\n"
  L1:
    print "after branch\n"
    end

This example unconditionally branches to the location of the label L1, skipping over the first print statement.

Jump instructions transfer control to an absolute address. The jump opcode doesn't calculate an address from a label, so it's used together with set_addr:

    set_addr I0, L1
    jump I0
    print "skipped\n"
    end
  L1:
    print "after jump\n"
    end

The set_addr opcode takes a label or an integer offset and returns an absolute address.

You've probably noticed the end 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 end statement, execution just falls off the end of the bytecode segment, with a good chance of crashing the interpreter.

Conditional Branches

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 if and unless 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

if branches if its first argument is a true value, and unless branches if its first argument is a false value. In this case, the if doesn't branch because I0 is false, but the unless does branch. The comparison branching opcodes compare two values and branch if the stated relation holds true. These are eq (branch when equal), ne (when not equal), lt (when less than), gt (when greater than), le (when less than or equal), and ge (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, I0 and 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 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 cmp or 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 _num and _str suffixes.

  eq_str P0, P1, label     # always a string compare
  gt_num P0, P1, label     # always numerically

Finally, the eq_addr opcode branches if two PMCs or strings are actually the same object (have the same address), and the is_null opcode branches if a PMC is NULL (has no assigned address):

  eq_addr P0, P1, same_pmcs_found
  is_null P2, the_pmc_is_null

Iteration

PASM doesn't define high-level loop constructs. These are built up from a combination of conditional and unconditional branches. A do-while 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 lt statement. If the condition evaluates as true (I0 is less than I1) it branches to the 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

This example emulates a counter-controlled loop like Perl 6's loop keyword or C's for. The first time through the loop it sets the initial value of the counter in loop_init, tests that the loop condition is met in loop_test, and then executes the body of the loop in loop_body. If the test fails on the first iteration, the loop body will never execute. The end of loop_body branches to loop_continue, which increments the counter and then goes to loop_test again. The loop ends when the condition fails, and it branches to 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.

Stacks and Register Frames

Parrot provides 32 registers of each type: integer, floating-point number, string, and PMC. This is a generous number of registers, but it's still too restrictive for the average use. You can hardly limit your code to 32 integers at a time. This is especially true when you start working with subroutines and need a way to store the caller's values and the subroutine's values. So, Parrot also provides stacks for storing values outside the 32 registers. Parrot has seven basic stacks, each used for a different purpose: the user stack, the control stack, the pad stack, and the four register backing stacks.

User Stack

The user stack, also known as the general-purpose stack, stores individual values. The two main opcodes for working with the user stack are save, to push a value onto the stack, and restore, to pop one off the stack:

  save 42         # push onto user stack
  restore I1      # pop off user stack

The one argument to save can be either a constant or a register. The user stack is a typed stack, so restore will only pop a value into a register of the same type as the original value:

  save 1
  set I0, 4
  restore I0
  print I0        # prints 1
  end

If that restore were restore N0 instead of an integer register, you'd get an exception, "Wrong type on top of stack!"

A handful of other instructions are useful for manipulating the user stack. rotate_up rotates a given number of elements on the user stack to put a different element on the top of the stack. The depth opcode returns the number of entries currently on the stack. The entrytype opcode returns the type of the stack entry at a given depth, and lookback returns the value of the element at the given depth without popping the element off the stack:

  save 1
  save 2.3
  set S0, "hi\n"
  save S0
  save P0
  entrytype I0, 0
  print I0         # prints 4 (PMC)
  entrytype I0, 1
  print I0         # prints 3 (STRING)
  entrytype I0, 2
  print I0         # prints 2 (FLOATVAL)
  entrytype I0, 3
  print I0         # prints 1 (INTVAL)
  print "\n"
  depth I2         # get entries
  print I2         # prints 4
  print "\n"
  lookback S1, 1   # get entry at depth 1
  print S1         # prints "hi\n"
  depth I2         # unchanged
  print I2         # prints 4
  print "\n"
  end

This example pushes four elements onto the user stack: an integer, a floating-point number, a string, and a PMC. It checks the entrytype of all four elements and prints them out. It then checks the depth of the stack, gets the value of the second element with a lookback, and checks that the number of elements hasn't changed.

Control Stack

The control stack, also known as the call stack, stores return addresses for subroutines called by bsr and exception handlers. There are no instructions for directly manipulating the control stack.

Register Frames

The final set of stacks are the register backing stacks. Parrot has four backing stacks, one for each type of register. Instead of saving and restoring individual values, the backing stacks work with register frames. Each register frame is the full set of 32 registers for one type. Each frame is separated into two halves: the bottom half (registers 0-15) and the top half (registers 16-32). Some opcodes work with full frames while others work with half-frames. The backing stacks are commonly used for saving the contents of all the registers (or just the top half of each frame) before a subroutine call, so they can be restored when control returns to the caller.

PASM has five opcodes for storing full register frames, one for each register type and one that saves all four at once:

  pushi               # copy I-register frame
  pushn               # copy N-register frame
  pushs               # copy S-register frame
  pushp               # copy P-register frame
  saveall             # copy all register frames

Each pushi, pushn, pushs, or pushp pushes a register frame containing all the current values of one register type onto the backing stack of that type. saveall simply calls pushi, pushn, pushs, and pushp.

PASM also has five opcodes to restore full register frames. Again it has one for each register type and one that restores all four at once:

  popi                # restore I-register frame
  popn                # restore N-register frame
  pops                # restore S-register frame
  popp                # restore P-register frame
  restoreall          # restore all register frames

The popi, popn, pops, and popp opcodes pop a single register frame off a particular stack and replace the values in all 32 registers of that type with the values in the restored register frame. restoreall calls popi, popn, pops, and popp, restoring every register of every type to values saved earlier.

Saving a register frame to the backing stack doesn't alter the values stored in the registers; it simply copies the values:

  set I0, 1
  print I0            # prints 1
  pushi               # copy away I0..I31
  print I0            # unchanged, still 1
  inc I0
  print I0            # now 2
  popi                # restore registers to state of previous pushi
  print I0            # old value restored, now 1
  print "\n"
  end

This example sets the value of I0 to 1 and stores the complete set of integer registers. Before I0 is incremented, it has the same value as before the pushi.

In CHP-9-SECT-2.2"Working with Registers" earlier in this chapter we mentioned that string and PMC registers hold pointers to the actual objects. When string or PMC register frames are saved, only the pointers are copied, not the actual contents of the strings or PMCs. The same is true when string or PMC register frames are restored:

  set S0, "hello"          # set S0 to "hello"
  pushs
  substr S0, 0, 5, "world" # alter the string in S0
  set S0, "test"           # set S0 to a new string
  pops                     # restores the first string pointer
  print S0                 # prints "world"
  end

In this example, we first use the pushs opcode to copy the string pointer to the string register frame stack. This gives us two pointers to the same underlying string, with one currently stored in S0, and the other saved in the string register frame stack. If we then use substr to alter the contents of the string, both pointers will now point to the altered string, and so restoring our original pointer using pops does not restore the original string value.

Each of the above pushX and popX opcodes has a variant that will save or restore only the top or bottom half of one register set or all the register sets:

  pushtopi            # save I16..I31
  popbottoms          # restore S0..S15
  savetop             # save regs 16-31 in each frame
  restoretop          # restore regs 16-31 in each frame

PASM also has opcodes to clear individual register frames: cleari, clearn, clears, and clearp. These reset the numeric registers to 0 values and the string and PMC registers to null pointers, which is the same state that they have when the interpreter first starts.

The user stack can be useful for holding onto some values that would otherwise be obliterated by a restoreall:

  # ... coming from a subroutine
  save I5     # Push some registers
  save I6     # holding the return values
  save N5     # of the sub.
  restoreall  # restore registers to state before calling subroutine
  restore N0  # pop off last pushed
  restore I0  # pop 2nd
  restore I1  # and so on

Lexicals and Globals

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 unwieldy manipulation of the user stack.

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.

Globals

Global variables are stored in a Hash, so every variable name must be unique. PASM has two opcodes for globals, store_global and 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 Int in the PMC register P10 and give it the value 42. In the third statement, store_global stores that PMC as the named global variable $foo. At some later point in the program, find_global retrieves the PMC from the global variable by name, and stores it in P0 so it can be printed.

The store_global opcode only stores a reference to the object. If we add an increment statement:

  inc P10

after the store_global it increments the stored global, printing 43. If that's not what you want, you can 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 store_global again.

The two-argument forms of store_global and 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 store_global and 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.

Lexicals

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. Parrot stores the scratchpads for nested lexical scopes in a pad stack.

Basic instructions

The instructions for manipulating lexical scratchpads are new_pad to create a new pad, store_lex to store a variable in a pad, find_lex to retrieve a variable from a pad, push_pad to push a pad onto the pad stack, and pop_pad to remove a pad from the stack:

  new_pad 0                # create and push a pad with depth 0
  new P0, "Int"            # create a variable
  set P0, 10               # assign value to it
  store_lex 0, "$foo", P0  # store the var at depth 0 by name
  # ...
  find_lex P1, 0, "$foo"   # get the var into P1
  print P1
  print "\n"               # prints 10
  pop_pad                  # remove pad
  end

The first statement creates a new scratchpad and pushes it onto the pad stack. It's created with depth 0, which is the outermost lexical scope. The next two statements create a new PMC object in P0, and give it a value. The store_lex opcode stores the object in P0 as the named variable $foo in the scratchpad at depth 0. At some later point in the program, the find_lex opcode retrieves the value of $foo in the pad at depth 0 and stores it in the register P1 so it can be printed. At the very end, pop_pad removes the pad from the pad stack.

The new_pad opcode has two forms, one that creates a new scratchpad and stores it in a PMC, and another that creates a new scratchpad and immediately pushes it onto the pad stack. If the pad were stored in a PMC, you would have to push it onto the pad stack before you could use it:

  new_pad P10, 0                # create a new pad in P10
  push_pad P10                  # push it onto the pad stack

In a simple case like this, it really doesn't make sense to separate out the two instructions, but you'll see later in CHP-9-SECT-7"Subroutines" why it's valuable to have both.

The store_lex and find_lex opcodes can take an integer index in place of a name for the variable:

  store_lex 0, 0, P0  # store by index
  # ...
  find_lex P1, 0      # retrieve by index

With an index, the variable is stored in the scratchpad array, instead of the scratchpad hash.

Nested scratchpads

To create a nested scope, you create another scratchpad with a higher depth number and push it onto the pad stack. The outermost scope is always depth 0, and each nested scope is one higher. The pad stack won't allow you to push on a scratchpad that's more than one level higher than the current depth of the top of the stack:

  new_pad 0                  # outer scope
  new_pad 1                  # inner scope
  new P0, "Int"
  set P0, 10
  store_lex -1, "$foo", P0   # store in top pad
  new P1, "Int"
  set P1, 20
  store_lex -2, "$foo", P1   # store in next outer scope
  find_lex P2, "$foo"        # find in all scopes
  print P2                   # prints 10
  print "\n"
  find_lex P2, -1, "$foo"    # find in top pad
  print P2                   # prints 10
  print "\n"
  find_lex P2, -2, "$foo"    # find in next outer scope
  print P2                   # prints 20
  print "\n"
  pop_pad
  pop_pad
  end

The first two statements create two new scratchpads, one at depth 0 and one at depth 1, and push them onto the pad stack. When store_lex and find_lex have a negative number for the depth specifier, they count backward from the top pad on the stack, so -1 is the top pad, and -2 is the second pad back. In this case, the pad at depth 1 is the top pad, and the pad at depth 0 is the second pad. So:

  store_lex -1, "$foo", P0   # store in top pad

stores the object in P0 as the named variable $foo in the pad at depth 1. Then:

  store_lex -2, "$foo", P1   # store in next outer scope

stores the object in P1 as the named variable $foo in the pad at depth 0.

A find_lex statement with no depth specified searches every scratchpad in the stack from the top of the stack to the bottom:

  find_lex P2, "$foo"        # find in all scopes

Both pad 0 and pad 1 have variables named $foo, but only the value from the top pad is returned. store_lex also has a version with no depth specified, but it only works if the named lexical has already been created at a particular depth. It searches the stack from top to bottom and stores the object in the first lexical it finds with the right name.

The peek_pad instruction retrieves the top entry on the pad stack into a PMC register, but doesn't pop it off the stack.

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

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.

Reserved registers

A very simple system would be to declare that the caller uses registers through 15, and the subroutine uses 16 through 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.

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 only uses 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.

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 CHP-9-TABLE-4Table 9-4.

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.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 Px 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.

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 OS Parrot is running on.

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 CHP-9-TABLE-5Table 9-5.

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

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, "Int"          # 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, "Int"          # 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.

Coroutines

As we mentioned in the previous chapter, 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, "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

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.

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

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.

Exceptions and 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 set_eh 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, .Exception_Handler, _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 newsub opcode and installs it on the control stack with the set_eh opcode. It sets the P10 register to a null value (so it can be checked later) and attempts to retrieve the global variable named none. If the global variable is found, the next statement (clear_eh) pops the exception handler off the control stack and normal execution continues. If the find_global call doesn't find 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 _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 rethrow the exception to pass it along to an exception handler deeper in the stack. The rethrow 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 find_global above or by the 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["_message"], "I die"  # set message attribute
  throw P10                     # throw it

Exceptions are designed to work with the Parrot calling conventions. Since the return addresses of bsr subroutine calls and exception handlers are both pushed onto the control stack, it's generally a bad idea to combine the two.

Events

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 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.

Timers

Timer objects are the replacement for Perl 5's 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.

Signals

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, .Exception_Handler, _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 SIGINT from the shell (this is usually 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.Currently, only Linux installs a SIGINT sigaction handler, so this example won't work on other platforms.

Threads

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. The following example uses the third model, which takes advantage of shared data. It uses a 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 ParrotThread object and calls its thread3 method, passing three arguments: a PMC for the _th1 subroutine in P5, a string argument in P6, and a TQueue object in P7 containing a single integer. Remember from the earlier section CHP-9-SECT-7.1.3"Parrot calling conventions" that registers 5-15 hold the arguments for a subroutine or method call and I3 stores the number of arguments. The thread object is passed in P2.

This call to the thread3 method spawns a new thread to run the _th1 subroutine. The main body of the code then creates a second ParrotThread object in P2, stores a different subroutine in P5, sets P6 to a new string value, and then calls the thread3 method again, passing it the same 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 (_th1) starts off by sleeping for a 1000th of a second. It then checks if the 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 TQueue. It then pulls one character off a copy of the string parameter using substr, prints the character, increments the current position (I0) in the string, and loops back to the w1 label and sleeps. Since the queue doesn't have any elements now, the subroutine keeps sleeping.

Meanwhile, the second thread (_th2) also starts off by sleeping for a 1000th of a second. It checks if the shared 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 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 substr, prints the character, and increments the current position in the string. It then creates a new Int, pushes it onto the shared queue, and loops back to the 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 P1 which terminates the thread. The interpreter waits for all threads to terminate in the cleanup phase after the 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 join method belongs to the ParrotThread class. When it's called on a 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

kill and 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.

Loading Bytecode

In addition to running Parrot bytecode on the command-line, you can also load pre-compiled bytecode directly into your PASM source file. The load_bytecode opcode takes a single argument: the name of the bytecode file to load. So, if you create a file named file.pasm containing a single subroutine:

  # file.pasm
  .pcc_sub _sub2:               # .pcc_sub stores a global sub
     print "in sub2\n"
     invoke P1

and compile it to bytecode using the -o command-line switch:

  $ parrot -o file.pbc file.pasm

You can then load the compiled bytecode into main.pasm and directly call the subroutine defined in file.pasm:

  # main.pasm
  _main:
    load_bytecode "file.pbc"    # compiled file.pasm
    find_global P0, "_sub2"
    invokecc
    end

The 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 @LOAD run as soon as they're loaded (before load_bytecode returns), rather than waiting to be called. A subroutine marked with @MAIN will always run first, no matter what name you give it or where you define it in the file.

  # file3.pasm
  .pcc_sub @LOAD _entry:        # 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

  .pcc_sub @MAIN _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 @LOAD and @MAIN. Because the _main subroutine is defined with @MAIN it will execute first even though another subroutine comes before it in the file. _main prints a line, loads the PASM source file, and then prints another line. Because _entry in file3.pasm is marked with @LOAD it runs before load_bytecode returns, so the final output is:

  main
  file3
  back

Classes and Objects

Parrot's object system is a new addition in version 0.1.0. Objects still have some rough edges (for example, you currently can't add new attributes to a class after it has been instantiated), but they're functional for basic use.

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.

Class declaration

The newclass 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 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 find_type opcode, then create an object of that type with the new opcode:

    find_type I1, "Foo"
    new P3, I1

The new opcode also checks to see if the class defines a method named "__init" and calls it if it exists.

Attributes

The addattribute opcode creates a slot in the class for an attribute (sometimes known as an instance variable) and associates it with a name:

    addattribute P1, ".i"                # Foo.i

This chunk of code from the __init method looks up the position of the first attribute, creates a 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 classoffset opcode 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 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 I0 + 1, the third attribute at 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 getattribute 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 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

Methods

Methods in PASM are just subroutines installed in the namespace of the class. You define a method with the .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 invocant object (often called self) in P2. Invoking the return continuation in P1 returns control to the caller.

The .pcc_sub directive automatically stores the subroutine as a global in the current namespace. The .namespace directive sets the current namespace:

  .namespace [ "Foo" ]

If no namespace is set, or if the namespace is explicitly set to an empty string, then the subroutine is stored in the outermost namespace.

The callmethodcc opcode makes a method call. It follows the Parrot calling conventions, so it expects to find the invocant object in P2, the method object in P0, etc. It adds one bit of magic, though. If you pass the name of the method in S0, callmethodcc looks up that method name in the invocant object and stores the method object in P0 for you:

    set S0, "_half"             # set method name
    set P2, P3                  # the object
    savetop                     # preserve registers
    callmethodcc                # create return continuation, call
    restoretop
    print I5                    # result of method call
    print "\n"

The callmethodcc opcode also generates a return continuation and stores it in P1. The callmethod opcode doesn't generate a return continuation, but is otherwise identical to 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.

Overriding vtable functions

Every object inherits a default set of vtable functions from the 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 __init in the Foo class that initializes the first attribute of the object with an integer:

  .pcc_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 __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 __set_integer_native, __add, __get_integer, __get_string, and __increment. The set opcode calls Foo's __set_integer_native vtable function when its destination register is a Foo object and the source register is a native integer:

    set P3, 30          # call __set_integer_native method

The add opcode calls Foo's __add vtable function when it adds two 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 inc opcode calls Foo's __increment vtable function when it increments a Foo object:

    inc P3              # __increment

Foo's __get_integer and __get_string vtable functions are called whenever an integer or string value is retrieved from a Foo object:

    set I10, P5         # __get_integer
    ...
    print P5            # calls __get_string, prints 'fortytwo'

Inheritance

The subclass 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"

For multiple inheritance, the addparent 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 __increment method so it decrements the value instead of incrementing it:

  .namespace [ "Bar" ]

  .pcc_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 Foo can only be looked up with the Foo class name, not the 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
    savetop                     # preserve registers
    callmethodcc                # create return continuation, call
    restoretop
    print I5
    print "\n"

Additional Object Opcodes

The isa and can opcodes are also useful when working with objects. isa checks whether an object belongs to or inherits from a particular class. can 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

Complete Example

    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 P5                    # prints 41 as _bar_inc decrements
    print "\n"

    set S0, "_half"             # set method name
    set P2, P5                  # the object
    savetop                     # preserve registers
    callmethodcc                # create return continuation, call
    restoretop
    print I5
    print "\n"

Additional Object Opcodes

The isa and can opcodes are also useful when working with objects. isa checks whether an object belongs to or inherits from a particular class. can 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

Complete Example

    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 P5                    # prints 41 as _bar_inc decrements
    print "\n"

    set S0, "_half"             # set method name
    set P2, P5                  # the object
    savetop                     # preserve registers
    callmethodcc                # create return continuation, call
    restoretop
    print I5
    print "\n"

Additional Object Opcodes

The isa and can opcodes are also useful when working with objects. isa checks whether an object belongs to or inherits from a particular class. can 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

Complete Example

    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
    savetop                     # preserve registers
    callmethodcc                # create return continuation, call
    restoretop
    print I5                    # prints 50
    print "\n"

    end

  .namespace [ "Foo" ]

  .pcc_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

  .pcc_sub __set_integer_native:
    classoffset I0, P2, "Foo"
    getattribute P10, P2, I0
    set P10, I5                   # assign passed in value
    invoke P1

  .pcc_sub __get_integer:
    classoffset I0, P2, "Foo"
    getattribute P10, P2, I0
    set I5, P10                   # return value
    invoke P1

  .pcc_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

  .pcc_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

  .pcc_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

  .pcc_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" ]

  .pcc_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

97 POD Errors

The following errors were encountered while parsing the POD:

Around line 3:

Unknown directive: =head0

Around line 5:

A non-empty Z<>

Around line 7:

Deleting unknown formatting code N<>

Around line 23:

A non-empty Z<>

Around line 71:

A non-empty Z<>

Around line 73:

Deleting unknown formatting code N<>

Deleting unknown formatting code G<>

Deleting unknown formatting code G<>

Around line 109:

A non-empty Z<>

Around line 128:

Deleting unknown formatting code N<>

Around line 142:

A non-empty Z<>

Around line 198:

Deleting unknown formatting code N<>

Deleting unknown formatting code A<>

Around line 206:

A non-empty Z<>

Around line 321:

A non-empty Z<>

Around line 347:

A non-empty Z<>

Around line 392:

Deleting unknown formatting code A<>

Around line 398:

A non-empty Z<>

Around line 412:

A non-empty Z<>

Around line 433:

A non-empty Z<>

Around line 435:

Deleting unknown formatting code G<>

Around line 464:

A non-empty Z<>

Around line 481:

A non-empty Z<>

Around line 523:

A non-empty Z<>

Around line 540:

A non-empty Z<>

Around line 558:

A non-empty Z<>

Around line 613:

A non-empty Z<>

Around line 648:

A non-empty Z<>

Around line 676:

A non-empty Z<>

Around line 701:

A non-empty Z<>

Around line 714:

Deleting unknown formatting code A<>

Around line 835:

Deleting unknown formatting code R<>

Deleting unknown formatting code R<>

Deleting unknown formatting code R<>

Deleting unknown formatting code R<>

Deleting unknown formatting code A<>

Around line 885:

Deleting unknown formatting code R<>

Deleting unknown formatting code R<>

Deleting unknown formatting code R<>

Deleting unknown formatting code R<>

Around line 891:

Deleting unknown formatting code R<>

Deleting unknown formatting code A<>

Around line 952:

Deleting unknown formatting code R<>

Around line 983:

A non-empty Z<>

Around line 1055:

A non-empty Z<>

Around line 1065:

A non-empty Z<>

Around line 1085:

A non-empty Z<>

Around line 1109:

A non-empty Z<>

Around line 1141:

A non-empty Z<>

Around line 1229:

A non-empty Z<>

Around line 1239:

A non-empty Z<>

Around line 1255:

A non-empty Z<>

Around line 1269:

Deleting unknown formatting code N<>

Around line 1291:

Deleting unknown formatting code A<>

Around line 1297:

A non-empty Z<>

Around line 1325:

A non-empty Z<>

Around line 1379:

A non-empty Z<>

Around line 1409:

A non-empty Z<>

Around line 1441:

A non-empty Z<>

Around line 1468:

Deleting unknown formatting code N<>

Around line 1491:

A non-empty Z<>

Around line 1538:

A non-empty Z<>

Around line 1612:

A non-empty Z<>

Around line 1675:

A non-empty Z<>

Around line 1690:

A non-empty Z<>

Around line 1756:

A non-empty Z<>

Around line 1765:

A non-empty Z<>

Around line 1839:

Deleting unknown formatting code A<>

Around line 1895:

A non-empty Z<>

Around line 1915:

A non-empty Z<>

Around line 1971:

A non-empty Z<>

Around line 1981:

A non-empty Z<>

Around line 2019:

Deleting unknown formatting code A<>

Around line 2036:

A non-empty Z<>

Around line 2100:

A non-empty Z<>

Around line 2138:

A non-empty Z<>

Around line 2157:

A non-empty Z<>

Around line 2168:

A non-empty Z<>

Around line 2204:

A non-empty Z<>

Around line 2212:

Deleting unknown formatting code A<>

Around line 2321:

Deleting unknown formatting code N<>

Around line 2365:

Deleting unknown formatting code R<>

Around line 2394:

A non-empty Z<>

Around line 2424:

Deleting unknown formatting code A<>

Around line 2567:

A non-empty Z<>

Around line 2646:

A non-empty Z<>

Around line 2719:

A non-empty Z<>

Around line 2753:

A non-empty Z<>

Around line 2795:

A non-empty Z<>

Around line 2881:

A non-empty Z<>

Around line 2901:

A non-empty Z<>

Around line 2912:

A non-empty Z<>

Around line 2935:

Deleting unknown formatting code N<>

Around line 2947:

A non-empty Z<>

Around line 3019:

Deleting unknown formatting code A<>

Around line 3105:

A non-empty Z<>

Around line 3176:

A non-empty Z<>

Around line 3189:

A non-empty Z<>

Around line 3212:

A non-empty Z<>

Around line 3271:

A non-empty Z<>

Around line 3327:

A non-empty Z<>

Around line 3382:

A non-empty Z<>

Around line 3441:

A non-empty Z<>

Around line 3455:

A non-empty Z<>

Around line 3513:

A non-empty Z<>

Around line 3526:

A non-empty Z<>

Around line 3584:

A non-empty Z<>

Around line 3597:

A non-empty Z<>