Jump to: navigation, search


Revision as of 11:08, 17 February 2012 by Dennis.wagelaar.vub.ac.be (Talk | contribs) (Added section on multiple dispatch)

ATL EMF Transformation Virtual Machine (research VM)

Since 2011, the ATL tools include a research VM (EMFTVM), which allows for experimentation with advanced language features. Currently, these features include:


The EMF Transformation Virtual Machine (EMFTVM) is derived from the current ATL VMs and bytecode format. However, instead of using a proprietary XML format, it stores its bytecode as EMF models, such that they may be manipulated by model transformations. A special EMF resource implementation allows EMFTVM models to be stored in binary format, which is faster to load and save, and results in smaller files.

Apart from the standard ATL bytecode primitives, such as modules, fields, and operations, EMFTVM bytecode includes rules and code blocks. Fig. 1 shows the structure of rules and code blocks. Code blocks are executable lists of instructions, and have a number of local variables and a local stack space. Operation bodies and field initialisers are represented as code blocks in EMFTVM. Code blocks may also have nested code blocks, which can be manipulated and invoked from its containing block. These nested code blocks therefore effectively represent closures, which are nameless functions that can be passed as parameters to other functions. Closures are helpful for the implementation of OCL's higher-order operations, such as select and collect, which are parametrised by nested OCL expressions.

Structure of EMFTVM rules and code blocks
Fig. 1: Structure of EMFTVM rules and code blocks

Rules consist of input and output rule elements, a matcher code block, applier code block, and post-apply code block. The matcher code block takes potential input element candidates as parameters, and returns a boolean value, representing a match. The applier code block takes the input and (newly created) output elements as parameters, and assigns the bindings of the output elements. The post-apply code block also takes the input and output elements as parameters, and performs any (imperative) post-processing specified in the rule. Execution of rules is therefore done in three phases: (1) matching; only input elements are guaranteed to be present, (2) applying; all output elements and traces are guaranteed to exist, but no bindings may have been applied, (3) post-apply; all input and output elements, traces, and bindings are guaranteed to be present.

Rules can be invoked manually, automatically, and recursively automatically. Manual rules correspond to ATL lazy rules (and called rules). Automatic rules correspond to ATL matched rules. Recursively automatic rules do not apply to ATL, but can be used when compiling other transformation languages to EMFTVM. Rules can also be marked as default, which causes that rule to create default traces. Default traces can be looked up using ATL's implicit tracing mechanism, and only one default trace may exist for any given source pattern. Non-default traces are just stored in the trace model, and are not used by the EMFTVM transformation engine.

Rules can have a number of super-rules, which are stored by name. This decision allows EMFTVM to resolve and link the super-rules of each rule at load-time, whereas storing a super-rule reference would have hardcoded the super-rule in the bytecode. This is comparable to how the Java VM does super-class lookup. Finally, rules can be marked as abstract, which means that they are only applied as part of a non-abstract sub-rule, but never by themselves.

To summarise: by explicitly representing rules in the bytecode, rule inheritance can be resolved at load-time. As a consequence, rules stored in imported modules can be taken into account, and super-rules can be redefined by module superimposition before the reference to the super-rule is resolved in the sub-rules. This solves the historic mismatch between ATL's rule inheritance and module superimposition.

How to use EMFTVM?


EMFTVM is currently only available from ATL CVS, but will be included in the next ATL release (3.3). As a temporary measure, EMFTVM can be downloaded as an ATL add-on for ATL 3.1 or up from [3]

Compiling to EMFTVM

To use EMFTVM for your ATL transformations, just add "-- @atlcompiler emftvm" on the first line of your ATL module to compile them to EMFTVM. An example ATL file can be found at [4].

Running EMFTVM modules

EMFTVM includes a separate launch configuration dialog that looks very much like ATL's launch configuration dialog (see Fig. 2). It can be found via "Run -> Run Configurations...".

EMFTVM Launch Configuration Dialog
Fig. 2: EMFTVM Launch Configuration Dialog

EMFTVM also includes its own Ant tasks. See [5] to find out how to use the Ant tasks.

Rule Inheritance

ATL rule inheritance is limited to single inheritance between rules within the same module. This limitation is hardwired in the ATL grammar, and cannot easily be changed. Therefore, the EMFTVM compiler introduces support for a rule inheritance @extends annotation, which supports multiple inheritance between rules situated in any module:

-- @extends SuperRule1, SuperRule2
rule SubRule {

To use this annotation, the regular ATL extends keyword must not be used: the regular ATL extends keyword will override the @extends annotation.

Multiple dispatch

EMFTVM support multiple dispatch of ATL helpers. This means you can greatly reduce the amount of "oclIsKindOf()" checks in your code, because you can simply write a new helper with the required context/parameter type (not only the context is virtual, but also all parameters). To illustrate how this works, look at the following ATL code:

-- These helpers check for value equality (as opposed to object identity equality).
-- The base case checks meta-type equality, and covers
-- comparison of different element types, including OclUndefined.
helper context OclAny def : sameAs(other : OclAny) : Boolean =
	self.oclType() = other.oclType();

helper context OCL!"OclType" def : sameAs(other : OCL!"OclType") : Boolean =
	super.sameAs(other) and
	self.name = other.name;

helper context OCL!CollectionType def : sameAs(other : OCL!CollectionType) : Boolean =
	super.sameAs(other) and

In regular ATL, you cannot assume the parameter types to be correct: you must make sure you invoke the helpers with the correct parameter types. You certainly cannot expect ATL to invoke a different helper, based on the types of the parameters! In the case of the above code, regular ATL will invoke one of the three "sameAs" helpers based only on the context type (and the number of parameters). That means that it is pointless to change the type of the "other" parameter to "OCL!OclType" in the second "sameAs", because ATL will not use it.

EMFTVM does use the parameter types, however. EMFTVM will only invoke the second "sameAs" as soon as both the context and the "other" parameter are of type "OCL!OclType". Otherwise, it will invoke the first "sameAs". As a comparison, the second "sameAs" would have to look like this in regular ATL:

helper context OCL!"OclType" def : sameAs(other : OclAny) : Boolean =
	if other.oclIsKindOf(OCL!"OclType") then
		super.sameAs(other) and
		self.name = other.name

Secondly, OclUndefined is an instance of OclAny in EMFTVM, which means that you can invoke helpers on it. That's why the first "sameAs" in our example works! To compare, this is what you would have to write in regular ATL:

helper context OclAny def : sameAs(other : OclAny) : Boolean =
	if other.oclIsUndefined() then
		self.oclType() = other.oclType()