details:
SELECT kind: NodeKind
FROM
var => [
flags: VariableFlags ¬ nullVariableFlags, -- flags for the variable
id: VariableId ¬ nullVariableId, -- the unique id
location: Location ¬
NIL],
-- the location
(
NIL if not yet known)
Var nodes represent variables, parts of variables, or even composite variables.
const => [
data:
SELECT kind: ConstKind
FROM
word => [word: Word],
For constants that are 32 bits (or less)
bytes => [align: Align, bytes: ByteSequence],
For constants that are more than 32 bits (with specified alignment)
refLiteral => [litKind: RefLitKind, contents: ByteSequence],
For ROPE, ATOM, or REF TEXT literals
numLiteral => [class: ArithClass, contents: ByteSequence],
For number literals (where conversion is target dependent)
ENDCASE],
block => [nodes: NodeList],
The value of a block node is the value of the last node in the node list.
All local variables declared inside of a block disappear at the end of the block. This is a fast special case for live/dead variable analysis, and may be the only one supported for naive code generation.
decl => [var: Var, init: Node],
A decl node declares a variable in the current context (local or global) and assigns it from the initialization. The variable is assigned in the same way that assignment is performed in the assign node.
For Dragon (and maybe some other machines) a local variable can be reserved and initialized with the same instruction. Using the init field can save us some analysis to discover this fact. This declaration lasts until the end of the enclosing block (there must be one).
enable => [handle: Handler, scope: NodeList],
Whenever a signal or error is raised in the given scope the node given by the handle is called via the signaller (the node should be a label node for a procedure). The scope list may contain decls, since it is treated as a block. All declarations local to the scope disappear at the end of the scope.
assign => [lhs: Var, rhs: Node],
An assignment moves the value calculated by rhs into the variable named by lhs. This node is not for counted assignments, which are handled by cedar oper nodes.
cond => [cases: CaseList],
The cond node is used for conditional statements and expressions, including those resulting from IF, SELECT, AND, OR. To simplify the encoding for IF statements and Mesa SELECT statements, there is an implicit jump to the node after the cond node from the end of every case in the cases list.
label => [label: Label],
Labels are the only nodes that can introduce cycles into the program graph.The value of a label node is the value of label.label.node.
goto => [dest: Label, backwards:
BOOL ¬
FALSE],
The backwards field is used to indicate a LOOP or RETRY, which is information we know early and might like to retain to help the code generator. It does not necessarily denote the direction of the branch in the machine code, since the code generators are free to emit code in any legitimate order.
The jump indicated by a goto node can cross block boundaries, but not frame boundaries. Uplevel GO TO statements are currently limited to signal handlers, and must use the unwind primitive to cause the signaller to remove the frames. If we allow uplevel GO TO statements to be outside of signal handlers, then we will probably implement them through signals anyway.
apply => [
proc: Node, -- designates the procedure to be applied
args: NodeList ¬ NIL, -- the values to which the lambda is applied
handler: Handler ¬
NIL],
-- the handler to call on an error
The apply node is used to apply procedures or system primitives to arguments. The handler has a scope that only applies to the procedure call itself, not to the argument evaluation. A few special cases do not fully evaluate their arguments before the application.
lambda => [
parent: Label, -- the proc parent (NIL if outer level)
descBody: Var, -- the proc desc body (NIL if outer level or catch phrase)
kind: LambdaKind, -- the kind of procedure
bitsOut: INT, -- bits returned
formalArgs: VarList, -- the formal argument values
body: NodeList],
-- the body that yields a value (via return nodes)
Lambda nodes are used to represent procedure bodies. If a procedure is nested (parent # NIL) then it uses its last argument (the closure link) as a means to get to the static link (by subtracting the offset at entry). If not nested (parent = NIL), a procedure will discard the closure link if called through its indirect entry, and will have no closure link if called directly.
return => [rets: NodeList],
procedure return {no value}
The rets list always specifies all words being returned.
oper => [oper: Oper],
the given primitive operation as a value {value is applicable}
Oper exprs are used to encode system primitives, including arithmetic. They are usually applied, only a few may have any implementation as first-class values.
machineCode => [bytes: ByteSequence],
specify the machine code directly {value is applicable, but not storable}
MACHINE CODE procedures look much like INLINE procedures. They don't exist as first-class values. They expect their arguments uniformly as values, and in standard positions (e.g. on the stack for PrincOps & Dragon).
Applying a machine code node causes the arguments to be put in a standard place and the returns taken from a standard place (as if for a procedure call). A machine code node occuring in any other place just causes the bytes to be emitted verbatim.
For the C target the bytes specify the name of the C procedure to call.
module => [vars: VarList, procs: NodeList],
defines a module {value is a PROGRAM value}
A module node is used for a PROGRAM or MONITOR. The vars are the variables in the global frame. The procs list is a sequence of outermost procedure declarations (the first one is always the initialization procedure).
source => [source: SourceRange, nodes: NodeList],
nodes associated with a given source range {value is value of last node}
If we choose to retain only the granularity of current Cedar/Mesa then we only need one of these around every leaf-level statement/declaration. We use a list of nodes since a single leaf-level source statement may expand into several nodes.
comment => [bytes: ByteSequence],
a comment (no value)
This is useful during the debugging phase, since we can add arbitrary comments as debugging information in the tree and still get them printed out. For now, comments only occur in lambda bodies, module proc lists, and block lists.
ENDCASE];
NodeKind:
TYPE = {
var, const, block, decl, enable, assign, cond, label, goto, apply,
lambda, return, oper, machineCode, module, source, comment};
LambdaKind:
TYPE = {
outer, -- normal outer-level procedure
inner, -- normal inner-level procedure
install, -- installation procedure
init, -- initialization procedure
catch, -- catch procedure
scope, -- enable scope procedure
fork, -- fork base procedure
unknown}; -- unknown kind of procedure