[Next] [Previous] [Top]

"RTL Grammar"

4 Statements


RTL statements are the following:

rtl_stmts	::=	{ rtl_stmt ";" }
rtl_stmt	::=	alu
	|	field_op
	|	ptr_op
	|	allocation
	|	exception_scope
	|	label
	|	goto
	|	fn_call
	|	return
	|	fatal
	|	static_info

Many RTL statements may be prefaced with a declaration. These declarations indicate the representation of the variable. A given variable should be declared at the first textual place it is assigned or read, but the scope of the declaration is the entire method. A variable declared with the source keyword can be referenced from nested closures.

decl	::=	"decl" ["source"] representation

Local variable declarations, assignments, unary and binary operations, and conversions between different numeric representations (which specify the target representation to convert to).

alu	::=	local_decl | assign | unop | binop | conversion
local_decl	::=	[decl] name
assign	::=	[decl] name ":=" value
unop	::=	[decl] name ":=" unop value
binop	::=	[decl] name ":=" value binop value
conversion	::=	[decl] name ":=" "convert" value representation

Arguments to RTL operators can name RTL variables, can name undeclared variables (in which case they are assumed to name global variables of type void* that are defined by the run-time system and the header files included with the generated code), can mention integer, long, float, double, character, or string literals, and can mention language-specific void, true, false, and null-pointer values, and can mention target-machine-specific constants for the word size and the number of tag bits.

value 	::=	name | literal | "void" | "true" | "false" | "null" |
		"bytes_per_machine_word" |
		"bits_per_machine_word" | "log_bits_per_machine_word" |
		"num_tag_bits"
literal	::=	integer | long | float | double | character | string

Field (instance variable) operations. Stores or fetches from a declared field of an object. Field contents are considered immutable when the corresponding field declaration specified an immutable field. If the field names an indexed field, then an index expression should be used to name the desired element of the indexed field (origin 0).

field_op	::=	field_load | field_store
field_load 	::=	[decl] name ":=" field_location
field_store 	::=	[decl] field_location ":=" value
field_location	::=	name "." name "@" name ["[" index "]"]
index	::=	name | integer

Simple pointer operations. Used to access language-level arrays, structs, and so on, if they've been translated down into this low level. The representation specifies the representation of the pointer target value being loaded or stored, and the index is a byte offset from the base pointer. Pointer target locations can be marked immutable, as with vector locations.

ptr_op	::=	ptr_load | ptr_store | addr_op
ptr_load 	::=	[decl] name ":=" ptr_location
ptr_store 	::=	ptr_location ":=" value
ptr_location	::=	"*" "(" name ["+" index] ")" representation
		   ["is_immutable"]

Pointers can be created by taking the address of a variable or of a procedure.

addr_op	::=	[decl] name ":=" "&" name
	|	[decl] name ":=" "&_proc" name

High-level allocation of new data structures. Object allocation can be used for classes with associated field declarations specifying the class's structure and front_end_does_field_layout turned off; various class-testing operations and field access operations can be applied to these objects. The object allocator allocates an instance of the given class; such classes should not be unique, abstract, template, or contain indexed fields. The array allocator is used to allocate objects with an indexed field, given the name of the class and the length of the array. The named class must satisfy the same restrictions as for an object allocation, but must contain an indexed field, and not contain an elem_type field. The typed array allocator is like an array allocator except that it instantiates classes that do contain an elem_type field. The vector allocator operations are array allocators for particular Cecil built-in classes. The closure allocator creates new lexically-nested anonymous closures, which are invoked using language-specific message names. new_closure is the normal closure allocation keyword; new_LIFO_closure can be used to allocate a closure that is known not to outlive its lexically-enclosing environment.

allocation	::=	[decl] name ":=" allocator
allocator	::=	obj_allocator
	|	vec_allocator
	|	array_allocator
	|	typed_array_alloc
	|	closure_allocator
obj_allocator	::=	"new" name
array_allocator	::=	"new_array" name value
typed_array_alloc ::=	"new_typed_array" name value static_type_spec
vec_allocator	::=	vec_alloc_op value
vec_alloc_op	::=	"new_m_vector" | "new_i_vector"
	|	"new_m_string" | "new_i_string"
	|	"new_m_float_vector" | "new_i_float_vector"
	|	"new_m_word_vector" | "new_i_word_vector"
closure_allocator	::=	closure_alloc_op "(" {rtl_formals} ")"
		  ":" representation "{" rtl_stmts "}"
closure_alloc_op	::=	"new_closure"
	|	"new_LIFO_closure"

Exception scope control. Exceptions can be raised by calls, sends, and explicit raises. The scope of a handler for some exceptions is bracketed by enter_ and exit_exn_scope RTL statements. These statements name the label where the innermost-enclosing exception handler for this scope begins. For languages where exceptions_through_longjmp is set, the runtime system is assumed to select the right handler if an exception is raised; the enter_exn_scope provides enough information to the runtime system to make this possible. Otherwise, exceptions propagate outwards through exception scopes incrementally, and each exception handling scope is responsible for forwarding control to the enclosing exception handling scope if it is not the right handler.

exception_scope	::=	enter_exn_scope | exit_exn_scope
enter_exn_scope	::=	"enter_exn_scope" label_name name ["(" name ")"]
		  ["[" value "," value "," value "]"]
exit_exn_scope	::=	"exit_exn_scope" label_name

The enter_exn_scope operator implicitly declares the variable to which the "name" of the exception being raised will be bound, and optionally declares the name which will be bound to the argument of the raised exception; these names are expected to be used in the exception handling code. For Modula-3, three additional parameters are needed, giving the address of the exception record, the address of the module's exception handler list, and the class of the exception. (Other languages will presumably need extensions to support arguments appropriate for their runtime system implementation of exceptions.)

Labels. Labels that start exception handlers must be flagged as such.

label	::=	("label" | "exn_handler_label") label_name
label_name	::=	name

Various kinds of unconditional and conditional branches. is_class is true iff the value is an instance of the named class; inherits_from extends this test to include subclasses as well. inherits_from can take a variable name (a computed CecilMap*) as an argument instead of a class name constant. check_typed_array(array,value,fld@obj) implements the necessary checking to ensure that value is in the element type of typed array array, where the element type is stored in field fld@obj.

goto	::=	cond_goto | uncond_goto | case_goto | raise
cond_goto 	::=	"if" test "goto" label_name
test	::=	unop value
	|	value binop value
	|	value "inherits_from" name
	|	value "is_class" name
	|	"check_typed_array"
		    "(" value "," value "," name "@" name ")"
uncond_goto	::=	"goto" label_name

case_goto implements an N-way branch. The argument value should be an integer in the range [0..N-1], and one of the N target labels is jumped to based on this value.

case_goto	::=	"case" value "goto" "[" label_names "]"
label_names	::=	label_name {"," label_name}

Exceptions are raised using the raise command. The first operand evaluates to the exception "name" (or exception object, for e.g. C++ and Java), and the second optional value is the argument to the exception. Control transfers unconditionally to the appropriate enclosing exception handler when the raise is executed.

raise	::=	"raise_exn" value ["(" value ")"]

Various kinds of procedure calls. The result of a call can be omitted (e.g. if the callee returns no value). All kinds of calls allow a marker blocking inlining of the call as well as an indication of whether the call can raise exceptions (a.k.a. produce a non-local return). (If sends_can_raise is set, then sends, table sends, and calls to declared methods (as opposed to external procedures) default to raising exceptions.)

fn_call	::=	[[decl] name ":="] (call | send | table_send)
		  ["no_inline"] ["can_raise_exns" | "no_exns"]

Calls are regular direct calls. The callee is either a regular named procedure (potentially one defined in RTL with a method decl, in which case it can be inlined; call_external blocks this check), or a value that evaluates to the address of the procedure to call.

call	::=	call_desc "(" [values] ")"
call_desc	::=	"call" name
	|	"call_external" name
	|	"call_indirect" value
values	::=	value {"," value}

Sends are messages, and search (using Cecil's inheritance rules) the named method set for the most specific applicable method, based on the run-time classes of the send arguments. Send RTLs can only be used if uses_PIC_based_runtime is set.

send	::=	"send" name "(" [values] ")"

Table sends are similar to sends, except that they get translated into the appropriate table lookup sequence (precomputed by the language front-ends) if they cannot be statically bound.

table_send	::=	"table_send" name "(" [values] ")"
		  "[" table_index "]" [ "id_offset" "(" int_literal ")" ]
		  [static_type_info]
table_index	::= int_literal

The address of a lookup table is fetched from the object specified as the first argument of the send, at the byte offset specified by the id_offset clause. If no id_offset is specified, the table address is assumed to be in the first word of the object. The table_index specifies the index into the table, giving a table entry to use for dispatching this send. For example:

t1 := table_send my_message(t2, t3) [2] id_offset(4)

This means: fetch the address of the table from table_base := *(t2+4), then use the table entry at table_base+(2*sizeof(table_entry)) to dispatch this send. The precise way in which the table is used to dispatch the send is language dependent. Currently, Vortex supports two kinds of tables: a simple table with address-sized entries that contain the address of a routine to call, and a more complicated, C++-style table format that contains both the address of a routine and an adjustment value that should be added to the first argument before entering the callee.

The static type of the receiver argument of a table_send can be specified by naming the class that the receiver is known to inherit from.

static_type_info	::=	"static_type" "(" static_type ")"
static_type	::=	name

Return statements. Non-local returns return from the lexically-enclosing non-closure method.

return	::= ("return" | "non_local_return") [value]

Fatal errors. Abort execution with a string message.

fatal	::=	"fatal" "(" string ")"

Assertions about the static properties of a value.

static_info	::=	"assert_type" name static_type_info

Currently, static information specifies either an exact class for the variable, or specifies a cone of possible classes (and the value could be an instance of the class or any subclass of the class), or gives another variable whose type to copy. The optimizer uses this information to reason about the possible values that could be stored in the given variable.

static_type_info	::=	static_type_spec
	|	"same_as" "(" name ")"
static_type_spec	::=	"cone" "(" name ")"
	|	"map" "(" name ")"
	|	"typed_array_map" "("
		   name "[" static_type_spec "]" ")"