Skip to content

Tutorial ~ Example (Part 2)

Vlad Ureche edited this page Jun 10, 2015 · 15 revisions
ildl logo We have seen that even on a small example, rewriting the code to its low-level equivalent can easily become a nightmare. In this part we will define a transformation using the `ildl` plugin.

Let us return to the original example:

object Test {
  // "define" type complex based on integer pairs
  type Complex = (Int, Int)

  // add the addition and multiplication operation to complex numbers
  implicit class IntPairAsComplex(val p1: Complex) extends AnyVal {
    def +(p2: Complex): Complex = (p1._1 + p2._1 , p1._2 + p2._2)
    def *(p2: Complex): Complex = (p1._1 * p2._1 - p1._2 * p2._2,
                                   p1._1 * p2._2 + p1._2 * p2._1)
    // we could define other operations here as well...
  }

  // test the output
  def main(args: Array[String]): Unit = {
    val x1: Complex = (3, 5)
    val x2: Complex = (2, 8)
    println(x1 + x2)
    println(x1 * x2)
  }
}

And let us define an ildl data transformation:

  import ildl._
  object IntPairAsComplex extends TransformationDescription {
    def toRepr(p: (Int, Int)): Long @high = 
      (p._1.toLong << 32l) | (p._2.toLong & 0xFFFFFFFFl)
    def toHigh(l: Long @high): (Int, Int) = 
      ((l >>> 32).toInt, (l & 0xFFFFFFFF).toInt)
  }

The transformation object looks quite similar to the two implicit conversions we defined in part one. Still, once this transformation is defined, it is well encapsulated and can easily compose with other transformations. While we won't talk about transformation composition here, there is an advanced section on this.

One thing that we introduced without an explanation is the @high annotation. The @high annotation marks the types that represent a transformed object: in our case, Long has the long integer semantics while Long @high has the pair of integer semantics. We won't go into details here, since the @high annotation is better described later on, but each time we transform a type (called the high-level type, (Int, Int) in our case) to another type (called data representation type, Long in this case), then the representation type carries the @high annotation. Oh, yes, you just realized why the two conversion methods are called toHigh and to toRepr -- that's right, because the convert the high-level type to the data representation type and back 😄.

So far, we just defined a reusable transformation, but we haven't applied it yet. Let us also apply it. Applying the transformation is done using the adrt keyword. In the following code snippet, we wrapped the main method in the adrt keyword parametrized with the IntPairAsComplex transformation object:

object Test {
  // "define" type complex based on integer pairs
  type Complex = (Int, Int)

  // add the addition and multiplication operation to complex numbers
  implicit class IntPairAsComplex(val p1: Complex) extends AnyVal {
    def +(p2: Complex): Complex = (p1._1 + p2._1 , p1._2 + p2._2)
    def *(p2: Complex): Complex = (p1._1 * p2._1 - p1._2 * p2._2,
                                   p1._1 * p2._2 + p1._2 * p2._1)
    // we could define other operations here as well...
  }

  import ildl._
  object IntPairAsComplex extends TransformationDescription {
    def toRepr(p: (Int, Int)): Long @high = 
      (p._1.toLong << 32l) | (p._2.toLong & 0xFFFFFFFFl)
    def toHigh(l: Long @high): (Int, Int) = 
      ((l >>> 32).toInt, (l & 0xFFFFFFFF).toInt)
  }

  adrt(IntPairAsComplex) {
    // test the output
    def main(args: Array[String]): Unit = {
      val x1: Complex = (3, 5)
      val x2: Complex = (2, 8)
      println(x1 + x2)
      println(x1 * x2)
    }
  }
}

You will notice that we compile both the transformation and the transformed code in the same run. While this is true for data-centric optimization rules, it is not generally the case for meta-programming. This makes yet another case for the well-behaved nature of data-centric meta-programming -- since it can't transform any random aspect of the program, it is not a Turing-complete transformation, and we see this as an advantage, although some people like the Turing completeness of C++ templates and Scala macros.

Okay, back to the example. Compiling and running the code produces the following result:

$ ildl-scalac example.scala
example.scala:24: warning: The new operator can be optimized if you define a public,
non-overloaded and matching constructor method for it in object IntPairAsComplex, 
with the name ctor_Tuple2:
      val x1: Complex = (3, 5)
                        ^
example.scala:25: warning: The new operator can be optimized if you define a public,
non-overloaded and matching constructor method for it in object IntPairAsComplex,
with the name ctor_Tuple2:
      val x2: Complex = (2, 8)
                        ^
example.scala:26: warning: The method + can be optimized if you define a public,
non-overloaded and matching exension method for it in object IntPairAsComplex,
with the name implicit_IntPairAsComplex_+:
      println(x1 + x2)
                 ^
example.scala:27: warning: The method * can be optimized if you define a public,
non-overloaded and matching exension method for it in object IntPairAsComplex,
with the name implicit_IntPairAsComplex_*:
      println(x1 * x2)
                 ^
four warnings found

$ ildl-scala  Test
(5,13)
(-34,34)

While four warnings appeared out of nowhere, the program produced the correct result. In the next part of the example, we will see why the warnings appeared and how to eliminate them.

From Here:

Frog Work Ahead: Some of the pages of this wiki are in flux. If you can't find what you are looking for, please [file a bug](https://github.com/miniboxing/ildl-plugin/issues).
Clone this wiki locally