Parrot Virtual Machine/Running Parrot
Parrot can be run from the command line in a number of modes with a number of different options. There are three forms of input that Parrot can work with directly: Parrot Assembly Language (PASM), which is a low-level human readable assembly language for the virtual machine, Parrot Intermediate Representation (PIR) which is a syntactic overlay on PASM with nicer syntax for some expressions, and Parrot Bytecode (PBC) which is a compiled binary input format.
PIR and PASM are converted to PBC during normal execution. Only PBC can be executed by Parrot directly. The compilation stage to convert PIR or PASM to PBC takes some time, and can be done separately. We'll be talking about these processes a little later.
To get information about the current Parrot version, type:
To get a list of command-line options and their purposes, type:
We'll discuss all the various command-line options later in this book, but it's always good to have multiple resources when a question pops up.
Files that end in
.pbc are treated as parrot bytecode files and are executed immediately. Files that end in
.pasm are treated as PIR or PASM source code files, respectively, and interpreted. To compile PIR or PASM into bytecode, use the
-o switch, as such:
parrot -o output.pbc input.pir
parrot -o output.pbc input.pasm
Notice that if we use a .pasm file extension, we can output to PASM instead of PBC:
parrot -o output.pasm input.pir
To force output of PBC even if the output file does not have a .pbc extension, use the
--output-pbc switch. To run the generated PBC file after you generate it, use the -r switch.
To force a file to be run as PASM regardless of the file extension, use the -a switch.
To force a file to be run as a PBC file, regardless of the file extension, use the -c switch.
Parrot can operate with a number of additional options too.
Optimizations can take time to perform, but increase the execution speed of the resulting program. For simple programs, short and sloppy one-time programs, extensive optimizations might not make much sense. You would spend more time optimizing a piece of software then you even spend executing it. However, for programs which are run frequently, or for very large programs, or programs which must run continuously with good performance, optimizations can be a valuable thing. Compile a program once with optimizations, and the output optimized bytecode can be saved to disk, never needing to be optimized again (unless Parrot integrates better optimizations).
Parrot has multiple optimization options, depending on the extensiveness of the optimizations to be performed. Each can be activated using different commandline switches in the form
-Ox where the
x is a character representing the type of optimization to perform:
||no optimizations, this is the default mode|
||optimizations without life info (e.g. branches)|
||optimizations with life info|
||rewrite I and N PASM registers most used first|
||select fastest runcore (default with -O1 and -O2)|
||turns on the optional/experimental tail call optimizations|
Life info is an analysis step where code and data is traced to determine control flow patterns and lifetimes of local variables. Knowing the areas where certain variables are used and not used enables registers to be reused instead of having to allocate new ones. Knowing when certain code is unreachable enables the optimizer to ignore it completely.
The run core is the central loop of the Parrot program, and there are several different runcores available that specify the performance and capabilities of Parrot. Runcores determine how parrot executes the bytecode instructions that are passed into the interpreter. Runcores can perform certain tasks such as bounds-checking, testing, or debugging. Other runcores have been optimized to operate extremely quickly. Implementation details about the various cores can be found in
Different cores can be activated by passing particular switches at the command-line. The sections below will discuss the various runcores, what they do, how they work, and how to activate them.
The default "slow" core treats all ops as individual C functions. Each function is called, and returns the address of the next instruction operation. Many cores, such as the tracing and debugging cores, are based on the slow core design.
The fast core is a bare-bones core that does not perform any special operations such as tracing, debugging, or bounds-checking.
Computed Goto Core
Computed goto is a feature of some compilers that allows a
goto instruction to target a variable containing the address of a label, not necessarily directly to a label. By caching the addresses of all labels into an array, a jump can be made directly to the necessary instructions. This avoids the overhead of multiple subroutine calls, and can be very quick on platforms that support it. For more information about the workings of the computed-goto runcore, see the generated file
The switch core uses the standard C
case structure to select the next operation to run. At each iteration, a switch is performed, and each case represents one of the ops. After the op has been performed, control flow jumps back to the top of the switch and the cycle repeats.
Switch statements, especially those that use many consecutive values, are typically converted by the compiler into jump tables which perform very similarly to computed-goto jumps.
The above cores are the basic designs upon which other specialized cores are based.
Some members of the Parrot team have developed an extension for the Apache webserver that allows Parrot to be used to generate server-side content. The result of this work is
mod_parrot, which can be used to produce web sites using PIR or PASM. This has limited usefulness by itself. However, mod_parrot allows the creation of additional modules for languages with compilers that target parrot. One notable module like this,
mod_perl6 is a bytecode module that runs on top of mod_parrot.
More information about mod_parrot is available at its website: http://www.parrot.org/mod_parrot