Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
ACG
ACG (ATL VM Code Generator) is a domain specific language designed to express the compilation of a model transformation into ASM code executable by the ATL Virtual Machine (ATL VM).
Contents
Background
Use Case
The ATL language semantics defines operations on models (such as testing condition on an element, creating an element or setting an element feature to a value). These operations are implemented by the ATL VM which executes ASM program on source model(s) to produce the output model(s).
Getting the compiled ASM version of an ATL transformation is done in three stages:
- injecting ATL source code to get it into a model based representation (XMI whose structure conforms to the ATL model)
- transform this ATL model into an ASM model
- extracting the ASM model to get it into a textual representation conforming to its syntax (ASM code).
The second stage is actually done by a transformation in ACG defined in ATL.acg.
This process has been bootstrapped. This means that ACG semantic is defined in ACG. Thus an ACG script is now compiled using ACG to get the corresponding ASM version. This means that the compilation from a transformation model to ATL VM code is done by the ATL VM itself using ACG.acg.
Resulting requirements
The reader is assumed to have full understanding of the ATL VM specification (also described in a presentation).
Exemplification base
As, historically, the ATL VM (and thus ACG) has been created for the ATL language, the example given are excerpts from ATL.acg which can be found on ATL Eclipse repository
Language
Overall structure
ACG Header
An ACG transformation begins by its own declaration and a section which embodies the transformation description.
Definition:
acg <Transformation Language Name> startsWith <Start Element Template> { <Transformation description items> }
Example:
acg ATL startsWith Unit { ... }
- Every transformation defines an entry point (with the keyword startsWith). It is a production rule called at the beginning of the transformation. It defines thereby the top level element of the transformation.
Templates
The elements used to express the transformation are the templates. They are the code production rules defined in the context of an element of the transformation language model. They define what code should be generated for this kind of item. Rule execution may be limited to only several elements of the kind satisfying a given condition. The condition can be entered after the pipe symbol.
Defintion:
<Context ModelElement> { <Produced code definition> }
or
<Context ModelElement> | <call condition> { <Produced code definition> }
Example:
Helper { analyze self.definition.feature }
- This is a production rule defined from the object Helper which carries out only one operation (analyze). Accordingly, the production rule will be called corresponding to the property feature of the property definition of the current Helper model element
Functions
Functions are used as a programming facility. Like in an object oriented language, functions are assigned to model elements and have access to all properties of that model element. They provide the means to easily request derived information from a model element, like the following example shows. Functions are declaritive constructs and behave like functions in a functional language.
Definition:
function <Context>::<Function Name>(<Parameters>) = <Function Definition>;
Example:
function OclModelElement::encode() = 'M' + self.model.name + '!' + self.name+';';
- This function without parameter is defined on OclModelElement to build a string.
Attributes
Attributes can be used to decorate model elements with extra information.
Definition:
attribute <Context>::<Attribute Name> = <Attribute Definition>;
Example:
attribute Object::location = self.__xmiID__;
- From the comment in ATL.acg: "ACG uses the location to distinguish variable declarations but the QVT metamodel does not have this feature. Therefore, we emulate it using the xmi ID."
Code production helpers
Code Production Management Statements
If Statement
Clearly, the code generated depends on a given condition.
Defintion:
if(<condition>) { <Produced code definition> } or If Then Else Statement if(<condition>) { <Produced code definition> } else { <Produced code definition> }
Example:
asm Module name self.name { ... if(self.isRefining) { report error 'refining mode not supported by ATL 2006 yet' } ... }
- In this example, an error is raised if the isRefining property of the object Module is true.
For Each statement
For each element (accessible through a roaming variable) of the collection constructed by a given OCL definition, the corresponding code is generated (potentially using the roaming variable).
Defintion:
foreach(<roaming variable name> in <OCL construct of collection>) { <Produced code definition> }
Example:
foreach(p in self.parameters->select(p | p.kind='in')) { param p.varName : 'J' }
- This statement is defined in the context of an object having a multi-valued feature named parameters, of which only the only which property kind is equal to 'in' are filtered using the OCL select function. For each one of these parameters a param instruction is called with arguments:
- the varName feature of the parameters component in question
- the string “J”
- In one of the following sections it is explained how the param keyword is used in conjunction with an operation and what effects its parameters actually have.
Only Once Evaluation Statement
The corresponding rules will be evaluated only once. If it is defined inside a Foreach statement, the evaluation will be done after the last loop executed and is then inserted into the resulting assembly from the loop. This is useful for blocks interweaving.
Defintion:
[ <Produced code definition> ]
Example:
foreach(i in self.iterators) { ... iterate ... [ analyze self.body store self.result ] } enditerate ... }
- If there is three objects iterator, the result will be as below:
<iterate/> <iterate/> <iterate/> ... result of production rule defined for the body object... <store arg = “23”/> <enditerate/> <enditerate/> <enditerate/>
- If there is a zero objects iterator, only once will not be evaluated:
Analyze statement
Call the matching template for the model element(s) resulting from the OCL evaluation in the current context. Between braces we can put the production code that should be executed after each temple calling (not used in example).
Definition:
analyze <OCL navigation to model element>
or
analyze <OCL navigation to model element> { <produce code definition> }
Example:
analyze self.inPattern.filter
- From within the current object, this will fetch the object pointed by the inPattern reference, and then will follow its filter reference to get the object which will lead in the launch of the production rule corresponding to the objects type.
Offsets management
The procedural style of ACG (defining templates as units of processing) leaves the replacing of a template call by corresponding code to the compiler. This results in a lack of a priori knowledge of the amount of code generated by an analyze statement and thereby in the difficulty to handle code offsets (needed by some instructions of the ATL VM). To fix this difficulty, ACG provides a mechanism of labels.
Definition:
<label name>:
This way, the ACG compilation replaces any argument of jump instructions referencing a label by the correct offset. Of course, no trace of label is left in the ASM code.
Example:
code IfStat { analyze self.condition if thn analyze self.elseStatements goto eoi thn: analyze self.thenStatements eoi: }
- This code starts with an analyze statement followed by a if statement. We can assume that the result of the evaluation of self.condition will lead to instructions pushing their result (a boolean) on the stack. The if statement will ask an evaluation of this variable and will make the execution continue at a point depending on the result. If there is true on the top of the stack, the execution pointer will jump over the instructions produced by the production rules before thn:. Otherwise it will continue on the next line. The goto statement will always make the execution pointer jump to the location of the eoi: label.
Inside foreachstatements a parameter can begiven to the label, so the compiler knows it should recalculate the offset for each iteration.
Example:
foreach(subRule in self.subRules()) { <Production code> unmatched(subRule): }
- Here we would find jumps inside the production code of the foreach loop that act as forward/continue statements. Without the extra parameter the offset will be calculated only once and later iterations will jump backward, possibly creating infinite loops
Simple code factoring
The let statement allows a programmer to write an OCL expression only and use it several times. The resulting value is bound to a literal. This way, anywhere inside the let statement scope, the compiler will replace this literal by the corresponding OCL expression.
Definition:
Let <literal binding> = <OCL expression> in <expression using literal binding>
or
Let <literal binding> = <OCL expression> { <code using literal binding> }
Example:
let ep = self.elements ->select(e | e isa CalledRule) ->select(e | e.isEntrypoint).first() { if(not ep.oclIsUndefined()) { getasm call 'A.' + ep.name + '():V' } }
- The outer brackets define the let statement's scope (the code in which ep will be recognized). The code inside uses this binding resulting in a clearer and smarter code.
Usage of ACG structures
Calling Templates
Templates can be assigned call condition which allows to define several behaviours for a single type depending on a condition.
When an analyze statement is found, a template defined for the element's true type is called. Consider there exist multiple conditional templates for this type, then the conditions are evaluated within the context of the element that is being analyzed. As conditions must define a partition of candidate model elements, at most one conditional template can be called. In case that no conditional template is defined or no condition is matched, the default template (the one with no condition call on it) is called.
Calling Functions
Functions can be called on model elements if they are of the type (or a subtype of) of the function context. A function defined on type foo, named bar and with no parameter would be called this way:
Definition:
<template type> { <instruction> <OCL navigation leading to a foo element>.bar() }
Example:
MyObject { push self.foo.bar() }
Parameters can be put in a comma-separated fashion between the parenthesis. They are passed by reference so can be used as return values.
Example:
MyObject { <Production rules with a declaration of variable x> push self.foo.bar2(x) }
Calling Attribute Values
Much the same as for function calling, but without parenthesis.
Operation based instructions
To define an operation in the compilation outcome, ACG provides the following instructions:
operation
Declares a context for the other operation based instructions.
Definition:
operation
context
Sets the type on which the current operation can be applied.
Definition:
context <encoded type>
name
Sets the name of the current operation.
Definition:
name <operation name>
param
Adds a parameter at the current operation.
Definition:
param <parameter name> : <parameter type>
Example:
operation context 'A' name 'resolveTemp' { param 'value' : 'J' param 'name' : 'S' ... }
- This is the definition of an operation called resolveTemp on the ATL context module with two parameters (an Object value and a String name). This will generate an assembly operation as demonstrated below.
<operation name="resolveTemp"> <context type="A"/> <parameters> <parameter name="value" type="J"/> <parameter name="name" type="S"/> </parameters> <code>
variable
Allocates a slot in the operation. The scope of the the variable is defined by the brackets. It also calls a store instruction at its place (the result is an immediate initialization of the variable with the value on the top of the stack (popped).
Definition:
variable <identifier> name <variable name> { <Produced code definition for which the variable is defined> }
The variable instruction is defined on a model element but also needs a name. The combination of these two allows ACG to maintain variables across severall rules. Several variables with the same name can coexists in the same code scope.
Example:
variable self named 'e' { getasm load self call 'A.__resolve__(J):J' call 'QJ.including(J):QJ' }
- The variable definition (line 1) allows the load call (line 3). Remember that a variable declaration also immediately stores a value, so we have to be sure a value is available on the stack before we can declare a variable. For a case where the value can't be known prior to declarationm one can use OclUndefined
field
As normal variables are local to operations, we also need a way to specify global variables. This can be done with the field keyword. They can be specified in the ASM context or within rules, so they can be generated from the input model.
Definition:
field <field name> : <field type>
Example:
field 'links' : 'NTransientLinkSet;'
- This generates a global variable of type NTransientLinkSet. Later more on this type.
Since fields reside in the global asm context, they are referenced as a property of this context. The following example illustrates this.
Example:
getasm get 'links'
- This code will pop the content of links a NTransientLinkSet on the stack.
VM Instructions
There exists one statement per ATL VM instruction. Each of these statements have the same name than the instruction and, of course, leads to the production of this instruction in the target model.
An instruction is followed by its operand as defined in the VM specification. For some arguments defined in ACG, the compiler performs a rewriting (or more exactly an interpretation -read transformation) given that that ASM code is not convenient to write by hand and that ACG could help by making things more "human compliant". It is the case for :
- the load instruction: Its argument is a variable reference. In ACG, this reference is:
- the identifier of a common variable
- the name of a parameter.
- → It is rewritten in corresponding slot number.
- the if instruction: Its argument is a referrence to a label.
- → It is replaced by the offset from the location of the label after code generation.
- the goto instruction
- → See if
Things to Keep in Mind
When writing ACG code, we specify compile-time calculation to generate runtime code. We have to keep in mind that the only interface between compile-time and runtime is the generated assembly file. At runtime we can't access the language model, so any complex model structure we want to referrence, has to be specified in the generated assembly explicitly. We can circumvent this problem to a certain extend by making use of some higher level model queries provided by EMF, shown in the following section.
Usefull Functionality
Transient Links
Writing model transformations, we often have to keep track of the tracibility links created between source and target models. Model transformation languages like ATL and QVT keep track of these links behind the screens, they can then either be explicitly referenced in the language (as is the case in QVT Operational Mappings) or they can be used by the language to automaticly transform links between model elements (as is done in ATL and QVT relational). Either means that, implementing these languages, we need to keep track of the links. The ATLVM therefore provides the TransientLink object. It can be referenced in ACG.
Example:
push 'TransientLink' push '#native' new dup push self.name call 'NTransientLink;.setRule(MATL!Rule;):V' foreach(ipe in self.inPattern.elements) { dup push ipe.varName load ipe.actualDeclaration() call 'NTransientLink;.addSourceElement(SJ):V' } ...
- In this example a TransientLink is created, given a reference to the executing rule and some source model elements are added. We can also add target elements and variables and later store the link in a TransientLinkSet. More on this can be found in ATL VM specification.
NOTE: Maintaining trace information can also be done by specifying another input model to store these informations.
EMF Model Query Functions
As noted before we cannot access the language model at runtime, making it dificult to implement lookaheads and things like that. EMF however provides facilities to reference the model via queries on the metametamodel. Here some examples that might be useful for your compiler design. These functionscan be called runtime and compile-time on any model element.
Example:
self.refImmediateComposite()
- Will get all the parent of the model element.
Example:
self.eContents()
- Will get a Set of all the children (referrences) of the model element.
Example:
self.eClass()
- Will get implicit type of the model element as resolved by the metamodel.
We can use the above functions to search the entire tree.
Example:
function Object::getObjects(s, result) = let leafs = self.__all in let newresult = result.union(leafs->collect(l|l.eClass().name = s)) in leafs->collect(l | l.getObjects(s, newresult)); --use of getObjects function Transformation { let res = Sequence{} { let self.getObjects('Variable', res) { foreach(var in res) { .. } } } }
Others
ACG also does not allow us to define (static) global compile-time variables. We can emulate these by adding an attribute to the Object model elements, the super type of all model elements. The following example uses this to be able to switch from a debug to normal compiler.
Example:
attribute Object::__debug = true; --either true or false
- The debug variable can now easily be accessed in any context.
Use
Once the ACG file is defined for the transformation language model, it has to be compiled so that every transformation written is this language could be compiled into ASM.
This is done thanks to an AMMA tool set including several Ant scripts. Those scripts are intended to provide all the facilities to build a Domain Specific Language (editor, megamodel, concrete and abstract syntax and a compiler if the semantics relies on the ATL VM).
Source
The source of ACG is provided as the ACG language project, which contains:
- The ACG metamodel in KM3.
- The ACG concrete syntax in TCS.
- The ACG compiler in ACG.
List of Existing ACG Specifications
This section lists some ACG specifications: