Assembly Language
ParaVM has a very simple assembly language that can be written by humans or tools and then be processed by the ParaVM tools. This language is called PVA (Parallella Virtual Assembly). It is defined in terms of Unicode code points encoded as UTF-8. The file extension typically used for PVA source files is .pva
.
The grammar is:
Program ::= { FunDecl | ArgDecl | RegDecl | BlkDecl | UnwDecl | Insn }
Some common grammar elements that will be used:
DecDigit ::= "0" .. "9"
IntLit ::= DecDigit { DecDigit }
FltPart ::= DecDigit { DecDigit }
FltExp ::= ( "e" | "E" ) [ "+" | "-" ] FltPart
FltLit ::= FltPart "." FltPart [ FltExp ]
StrEscChar ::= "\\"" | "'" | "\\\\"
StrEscSeq ::= "\\\\" StrEscChar
StrLit ::= "\\"" { ? any character except "\\"" and "\\" ? | StrEscSeq } "\\""
AtomEscChar ::= "\\"" | "'" | "\\\\"
AtomEscSeq ::= "\\\\" AtomEscChar
AtomLit ::= "'" { ? any character except "'" and "\\" ? | AtomEscSeq } "'"
BinDigit ::= "0" .. "1"
BinLit ::= ":" { BinDigit } ":"
IdentChar ::= "a" .. "z"
Ident ::= IdentChar { IdentChar | "." }
Functions are declared with the .fun
directive:
FunDecl ::= ".fun" StrLit
The string literal following the directive is the function name. It must be unique in the module.
Arguments are declared with the .arg
directive:
ArgDecl ::= ".arg" StrLit
The string literal following the directive is the argument name. It must be unique in the function. Arguments must be declared after a .fun
directive and before any .reg
and .blk
directives.
When calling a function, the call site must use as many arguments as the target function has .arg
directives, or an exception will be raised.
Registers are declared with the .reg
directive:
RegDecl ::= ".reg" StrLit
The string literal following the directive is the register name. It must be unique in the function (with respect to arguments too). Registers must be declared after a .fun
directive and before any .blk
directive.
A function can have a (theoretically) infinite amount of registers.
All registers start out with the nil value.
Basic blocks are declared with the .blk
directive:
BlkDecl ::= ".blk" StrLit
The string literal following the directive is the basic block name. It must be unique in the function. Basic blocks must be declared after a .fun
directive.
A basic block contains a linear sequence of instructions followed by a so-called terminator instruction. A terminator instruction can return from the function, raise an exception, branch to another block, etc. As such, basic blocks are used to model control flow.
Unwind blocks are declared with the .unw
directive:
UnwDecl ::= ".unw" StrLit StrLit
The string literal following the directive is the name of the block to unwind to. The second string literal is the register to assign the exception value to. Unwind blocks must be declared after a .blk
directive and before any instructions.
The purpose of an unwind block is to handle exceptions that are raised in the block(s) that they are attached to. The unwind block can decide what to do with an exception and either handle it gracefully or resume the in-flight exception.
Instructions encode the actual logic of a function. They have the following grammar:
RegRef ::= StrLit
BlkRef ::= StrLit
Operand ::= "(" ( IntLit | FltLit | AtomLit | BinLit | BlkRef | ( BlkRef BlkRef ) | { RegRef } ) ")"
Insn ::= Ident [ RegRef [ RegRef [ RegRef ] ] ] [ Operand ]
How many registers an instruction expects and whether it takes an operand depends on the particular instruction. Instructions must follow a .blk
or .unw
directive.