On this page we will create a simple example project and demonstrate it being passed through the AnvilJ refactoring process.

The example project

The example project is an implementation of simple JPEG-style compression. It is not written to be efficient, but to show the features of the model transformation tools. It uses three threads:
  • readThread: Reads the input image
  • dctThread: Computes the Discrete Cosine Transformation (DCT) of the image
  • quantizeThread: Reduces the information in the image, thereby compressing it. It then dequantizes, applies the inverse DCT, and displays the output image so that the effects of the compression can be observed.
exampleblock.jpg

Download the example project from here and import it into Eclipse. To import it select File | Import from the Eclipse main menu and set the import source to Existing Projects into Workspace (found under General) and click Next. Click Select archive file and select the downloaded zip file. One project should appear in the import box, called example. Click finish and the project will appear in your workspace.

Test the project by selecting the 'example' project in the Workspace and selecting Run | Run from the menus. The application should run with the following output:

Image loaded. Passing to dctThread...
DCT thread recieved image. Processing DCT...
Quantize thread recieved image. Quantizing...
Image recieved at output stage. Displaying...
All done.

It will also display an output image that is blocky. Compare this to the original image by double clicking on image.jpg in the example project.
ajexampleimage.jpg

Refactoring the example project

In this example, we are going to split the processing of the image up so that it takes place over two separate JVMs with no shared memory. Standard Java does not let us do this, but AnvilJ will allow us to make some very straightforward mapping decisions and then leave it to ensure that it all works.

For this example we will perform the following allocation:
allocation.jpg
Main() thread refers to the application entry point, which in the example is the example.Main class. Open this class by double-clicking it in Eclipse's Package Explorer on the left of the screen. Lines 8 to 16 declare and instantiate the elements mentioned in the above allocation image. Note that they are all declared as static final fields. This is because, whilst AnvilJ can in general work with any input Java, the code elements which are going to be explicitly mapped are subject to a few restrictions (detailed here). This will be covered in detail later.

Before we can run AnvilJ, we need to tell it two things. One, what the target hardware looks like; and two, how the source code should be placed throughout it. This is done with an architecture definition file. These files are described in more detail later, this example will just give a brief introduction. In Eclipse's Package Explorer, right-click the example project and select New | File.... Enter the file name as architecture.xml. Ensure that the parent folder is exampe, the top-level folder of the example project, and click Finish. This will create an XML file in the example project. Eclipse may open it with a dedicated XML editor. You are free to use this editor but this document will describe editing the file as plain text. To use a plain text editor, right-click the architecture.xml file and select Open With | Text Editor.

Copy and paste the following text into the architecture definition:
<architecture name="Example Dual CPU Architecture" mainclass="example.Main" maincpuid="0">
    <cpu name="cpu1" id="0">
        <thread binding="Lexample/Main;.readThread)Lexample/ReadThread;"/>
    </cpu>
    <cpu name="cpu2" id="1">
        <thread binding="Lexample/Main;.quantizeThread)Lexample/QuantizeThread;"/>
        <sharedobject binding="Lexample/Main;.outputStage)Lexample/OutputStage;"/>
        <thread binding="Lexample/Main;.dctThread)Lexample/DCTThread;"/>
    </cpu>
    <channel name="chan1">
        <endpoint cpu="cpu1"/>
        <endpoint cpu="cpu2"/>
    </channel>
</architecture>
The format of this file is detailed here, but it should be easy to see that the above text declares two CPUs and a channel which connects them. It then assigns four source code elements to the CPUs, one to cpu1 and three to cpu2. Note that the first line also assigns the main class to cpu1. You might be asking what the "binding" strings are. These are strings uniquely describe elements in the source code and are used to provide a link between the input Java source and the architecture definition file. Again, these are described in more detail later. Save and close architecture.xml.

With the architecture definition in place you can run AnvilJ. Simply right-click the example project in the Package Explorer and select AnvilJ - Perform Refactoring. AnvilJ will refactor the example project into two output projects called "exam-ple_CPU1" and "example_CPU2". These execute on the CPUs CPU1 and CPU2 respectively.

Testing the output projects

AnvilJ cannot automatically create the low-level driver code that is needed to handle on-chip communications in the real hardware; the Java developer will add this themselves. However the generated projects include a simple TCP-based communications driver that is intended for simulation and functional verification. As a result, the output projects can be built and executed in separate JVMs but they will still communicate over TCP to complete their work. When testing the output projects, run the project for the main CPU last (in this case this is example_CPU1) so that the rest of the system is ready when it starts. (This is not vital as it will wait until it can see the other projects on the network, but this prevents the main project busy-waiting and consuming networking resources.) Select example_CPU2 and click Run | Run. If asked, run it as a Java application. Now do the same with example_CPU1.

Two console applications will be running inside Eclipse (you can switch between them using the 'Display Selected Console' button on the Console tab. Note that threads running in example_CPU2 performed the image filtering:
exout1.jpg
whereas threads running in example_CPU1 loaded the image and coordinated execution of the entire application:
exout2.jpg

You just correctly executed a Java program that was written expecting a single-processor, uniform memory model over a dual CPU architecture with no shared memory and no code edits! Because the applications are designed for long-running embedded systems, they both leave message handling threads running in the background. Close the applications with the red Terminate button on the Console tab.


So what is going on?

So what is the refactoring engine actually doing to your code? The refactoring process proceeds as follows:
  • An initial analysis phase checks the input software, ensuring that various restrictions are obeyed, that the architecture specification is the correct format etc.
  • If these checks pass, an output project is created for each node of the architecture specification. Initially, the output projects are a complete copy of the input project.
  • A package called anvilj.refactored is created in each project. This package will contain all the node-specific, auto-generated parts of the AnvilJ runtime.
  • The refactoring then sets up the main class entry point (the public void main(String[] args) method of each project. The architecture specification defines a "main node", which the node responsible for executing the initial thread of the application.
  • For the main node, the already existing main method is refactored to include the code below. For the other nodes, the main method is removed and a new type anvilj.refactored.AnvilJMain is created which defines the entry point for this node and includes the code below:
anvilj.Settings _anviljsettings = new anvilj.Settings(true, false, false);
 
anvilj.refactored.Globals.om = new anvilj.ObjectManager(
    anviljsettings,
    0,
    new anvilj.socketcomms.SocketComms(0, anvilj.refactored.HostnameMapFactory.getMap()),
    new anvilj.refactored.Architecture(),
    new anvilj.refactored.ThreadCreator(),
    new anvilj.refactored.SharedMessages(),
    new anvilj.refactored.Routing(),
    new anvilj.sharedmessagehandlers.FullSerialization(null),
    new anvilj.Port());
 
anvilj.refactored.Globals.omThread =
    new Thread(anvilj.refactored.Globals.om);
 
anvilj.refactored.Globals.omThread.start();
This code creates an instance of anvilj.ObjectManager, passes it to a new Thread and starts that thread. The AnvilJ runtime requires a thread for processing incoming messages.
  • The refactoring engine then begins creating the types in anvilj.refactored(which are instantiated by the above instantiation of the Object Manager).
    • anvilj.refactored.Architecture contains methods that describe the target architecture, from the perspective of the current node. This includes data structures which detail where the AnvilJ Instances of the system are mapped and methods for getting this information.
    • anvilj.refactored.Globals contains global references to the Object Manager and the thread used to execute it.
    • anvilj.refactored.Routing informs the node how to communicate with the other nodes of the system. Specifically, it contains a data structure of "next hops", that informs the node which node to send messages to based on the final destination node. In a totally-connected system these are the same, but the runtime and refactoring engine support offline, multi-hop routing.
    • anvilj.refactored.SharedMessages is a type which is called by the Object Manager when a thread on a remote node calls a shared method of a local AnvilJ Instance. It checks the various IDs which identify the target AnvilJ Instance and method, calls the method, and returns any return value.
    • anvilj.refactored.SharedMessageStubs is a set of stubs for all shared methods of remote AnvilJ Instances. The user code which used to call these shared methods is refactored to call these stubs instead. The stubs send the appropriate messages to the target node, and handle any exceptions which may be thrown by the shared method.
    • anvilj.refactored.ThreadCreator is a type which is called by the Object Manager when a thread on a remote node creates an AnvilJ Thread which is hosted by the current node.
  • The refactoring engine then rewrites all shared method calls and shared field accesses of AnvilJ Instances to use the types in anvilj.refactored.
  • Certain special-case methods (such as Thread.start()) for AnvilJ Instances are refactored to call the Object Manager.
  • Dead code removal is run to remove all types that are not referenced by the current node.

Once this process is complete, AnvilJ output projects are executed with anvilj.refactored.AnvilJMain as the main class, except for the output project for the main node which is executed as it originally was before refactoring.

Note that in the code sample above an instance of anvilj.socketcomms.SocketComms is passed into the ObjectManager. SocketComms is the default implementation of the communications drivers (the IComms interface) which makes use of TCP sockets to convey messages between nodes. This will work on most operating systems and is useful for testing the refactored projects. In an embedded system this will likely be needed to be replaced with a set of hardware drivers. For information on this see this page.