Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions macros/src/main/scala/rise/macros/Primitive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ object Primitive {
case _: ${TypeName(className)} => true
case _ => false
}
override def toString: String = $name
}
"""
if (verbose) {
Expand Down Expand Up @@ -109,6 +110,7 @@ object Primitive {
override def primitive: ${TypeName(className)} = $makeInstance()
override def apply: rise.core.DSL.ToBeTyped[${TypeName(className)}] =
rise.core.DSL.toBeTyped($makeInstance())
override def toString: String = $name
}

object ${TermName(name)} {
Expand Down
200 changes: 200 additions & 0 deletions src/main/scala/apps/Tutorial.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package apps

import elevate.core._
import rise.core.DSL.Type._
import rise.core.DSL._
import rise.core._
import rise.core.primitives.{let => _, _}
import rise.core.types._
import rise.elevate._
import rise.elevate.rules.algorithmic._
import rise.elevate.rules.lowering._
import rise.elevate.strategies.predicate._
import rise.elevate.strategies.traversal._
import util.gen

object Tutorial extends scala.App {
/**
* Starting from a High-Level RISE Program and an ELEVATE Optimization
* Strategy the Shine compiler rewrites the high-level program as specified
* by the optimization strategy into a Low-Level RISE Program that encodes
* all implementation and optimization decisions explicitly.
*
* The code generator processes the low-level program to generate
* the final Optimized C, OpenMP, OpenCL, or CUDA Program.
*/
println("An overview of the RISE language and the Shine compiler")
println("--------------------------------------------------")

/**
* This is an example of a high-Level program written in RISE.
* The shown example is the multiplication of a nxk-matrix called `A`
* and a mxk-matrix called `B`.
*/
val highLevelProgram: ToBeTyped[Rise] =
depFun((n: Nat, m: Nat, k: Nat) =>
fun(n`.`k`.`f32)(A => fun(k`.`m`.`f32)(B =>
A |> map(fun(rowOfA =>
B |> transpose |> map(fun(colOfB =>
zip(rowOfA)(colOfB) |>
map(fun(x => fst(x) * snd(x))) |>
reduce(add)(l(0.0f)) )) )) )) )
// The matrix dimensions are represented as part of the type of the matrices:
// - the type of matrix `A` is `n.k.f32`
// - the type of matrix `B` is `k.m.f32`
// The identifies used in the type (here: `n`, `m`, and `k`) are introduced
Comment thread
Bastacyclop marked this conversation as resolved.
Outdated
// and scoped by `depFun`
//
// The matrix values are introduced and scoped as function parameters by two
// nested `fun`s
//
// The body of the nested functions represents the computation of the matrix
// matrix multiplication:
// - two nested `map` primitives apply the dot product to each combination
// of a `rowOfA` and a `colOfB`
// - the dot product computation is represented by a composition of the
// `zip`, `map`, and `reduce` primitives
//
// We often prefer the pipe notation `(x |> f)` over the equivalent function
// call notation `f(x)` as it allows expressions to be read from
// left-to-right and top-to-bottom.
//
// Primitives (such as `map`, `transpose`, `zip`, and `reduce`) are functions
// with types that explain their possible usage and with a clearly defined
// denotational semantics:
// - `[x1, ..., xn] |> map(f) == [f(x1), ..., f(xn)]`
// - `[ [x11, ...., x1n], ..., [xm1, ..., xmn] ] |> transpose
// == [ [x11, ...., xm1], ..., [x1n, ..., xmn] ]`
// - `zip([x1, ..., xn])([y1, ..., yn]) == [(x1, y1), ..., (xn, yn)]`
// - `[x1, ..., xn] |> reduce(op)(init) == init op x1 op ... op xn`
//
// The resulting RISE expression has the Scala type `ToBeTyped[Rise]`
// On conversion to the underlying Scala type `Rise` type inference will be
// performed automatically.

// We can easily print the internal representation of the high-level program:
println("High-Level RISE Program:")
println(highLevelProgram.toExpr)
println("--------------------------------------------------")

/**
* This is an example of an optimization strategy written in ELEVATE.
* It describes that the outermost map computation will be performed in
* parallel as well as that the nested map computation and the reduction
* will be performed sequentially.
*/
val optimizationStrategy: Strategy[Rise] =
(`map |-> mapPar` `@` outermost(isPrimitive(map))) `;`
(`map |-> mapSeq` `@` outermost(isPrimitive(map))) `;`
(`reduce |-> reduceSeq` `@` everywhere)
// The shown example demonstrates one possible way to rewrite the high-level
// RISE program above into a low-level RISE program from which code can be
// generated.
//
// Strategies in ELEVATE are functions with the following specific type:
// type Strategy[P] = P => RewriteResult[P]
//
// The return type `RewriteResult[P]` indicates the two possible outcomes of
// applying a rewrite strategy to a program of type `P`: either the program
// has been successfully rewritten, or the rewrite strategy failed.
//
// Strategies in ELEVATE are written as compositions of smaller strategies.
//
// The simplest strategies are rewrite rules that replace an expression
// with another expression. An example of such a rule is the `map |-> mapPar`
// strategy that replaces an occurrence of the `map` primitive with the
// `mapPar` primitive indicating that the computation of the map should be
// performed in parallel.
//
// The `outermost` and `everywhere` strategies are examples of traversals
// that describe where other strategies should be applied.
// We can use the `@` notation to compose them as shown in the example.

// We can also easily print the internal representation of the
// optimization strategy:
println("ELEVATE Optimization Strategy:")
println(s" $optimizationStrategy")
println("--------------------------------------------------")

/**
* This alternative strategy explicitly fused the innermost map and reduce
* patterns. It then turns every remaining map into a sequential map and the
* reduce into a sequential reduction.
*/
val anotherOptimizationStrategy: Strategy[Rise] =
(`map >> reduce |-> reduce` `@` everywhere) `;`
(`map |-> mapSeq` `@` everywhere) `;`
(`reduce |-> reduceSeq` `@` everywhere)

println("Another Optimization Strategy:")
println(s" $anotherOptimizationStrategy")
println("--------------------------------------------------")

/**
* This strategy vectorizes the computation of the innermost map pattern
* that itself will be performed sequentially and stores its temporary output
* as vectors. It will then use the optimization and implementation decisions
* described in the initial optimization strategy.
*/
val yetAnotherOptimizationStrategy: Strategy[Rise] =
innermost(isAppliedMap)(
`map(f) |-> asVector >> map(f_vec) >> asScalar`(4) `;`
(`map |-> mapSeq` `@` innermost(isPrimitive(map))) `;`
storeTempAsVectors
) `;`
optimizationStrategy

println("Yet Another Optimization Strategy:")
println(s" $yetAnotherOptimizationStrategy")
println("--------------------------------------------------")

/**
* This function performs the rewriting by applying the given
* optimization strategy to the given program.
*/
def rewriting(program: Rise, strategy: Strategy[Rise]): Rise = {
println("> Rewrite high-level program using the ")
println(s"> $strategy")
println("> optimization strategy")
println("--------------------------------------------------")

// we know that the shown strategies will always succeed, therefore, it is
// ok to unwrap the final RewriteResult using .get
strategy(program).get
}

/**
* This is the low-level RISE program that is produced by rewriting the
* high-level program using one of the optimization strategies
*/
val lowLevelProgram: Rise =
rewriting(highLevelProgram, optimizationStrategy)

println("Low-Level RISE Program:")
println(lowLevelProgram)
println("--------------------------------------------------")

/**
* This function performs the code generation translating the given
* low-level program to optimized code.
*/
def codeGeneration(program: Rise): String = {
println("> Generate code for the low-level program")
println("--------------------------------------------------")

gen.openmp.function.asStringFromExpr(program)
// similar API for generating C or OpenCL code exist:
// gen.c.function.asStringFromExpr(program)
// gen.opencl.kernel.asStringFromExpr(program)
}

/**
* The final optimized program in C, OpenMP, or OpenCL.
*/
val optimizedProgram: String =
codeGeneration(lowLevelProgram)

println("Optimized Program:")
println(optimizedProgram)
println("--------------------------------------------------")
}
1 change: 1 addition & 0 deletions src/main/scala/rise/elevate/rules/algorithmic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ object algorithmic {
Success(padEmpty(n+m)(in) !: e.t)
}

def `map >> reduce |-> reduce`: Strategy[Rise] = reduceMapFusion
// *g >> reduce f init -> reduce (acc, x => f acc (g x)) init
@rule def reduceMapFusion: Strategy[Rise] = {
case e @ App(App(App(r @ ReduceX(), f), init), App(App(map(), g), in)) =>
Expand Down
45 changes: 40 additions & 5 deletions src/main/scala/rise/elevate/rules/lowering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import elevate.core.strategies.predicate._
import elevate.core.strategies.traversal._
import elevate.core.{Failure, Strategy, Success}
import elevate.macros.RuleMacro.rule
import elevate.macros.StrategyMacro.strategy
import rise.core.DSL._
import rise.core.{primitives => p, _}
import rise.core.primitives.{not => _, _}
Expand All @@ -15,7 +16,8 @@ import rise.elevate._
import rise.elevate.rules.traversal._
import rise.elevate.strategies.normalForm.DFNF
import rise.elevate.strategies.predicate.{isVectorArray, _}
import rise.openMP.primitives.mapPar
import rise.elevate.strategies.traversal._
import rise.openMP.{primitives => omp, _}

object lowering {

Expand All @@ -29,34 +31,44 @@ object lowering {
case _ => false
}

def `map |-> mapSeq`: Strategy[Rise] = mapSeq
Comment thread
michel-steuwer marked this conversation as resolved.
Outdated
@rule def mapSeq: Strategy[Rise] = {
case m@map() => Success(p.mapSeq !: m.t)
}

def `map |-> mapPar`: Strategy[Rise] = mapPar
@rule def mapPar: Strategy[Rise] = {
case m@map() => Success(omp.mapPar !: m.t)
}

def `map |-> mapStream`: Strategy[Rise] = mapStream
@rule def mapStream: Strategy[Rise] = {
case m@map() => Success(p.mapStream !: m.t)
}

def `map |-> iterateStream`: Strategy[Rise] = iterateStream
@rule def iterateStream: Strategy[Rise] = {
case m@map() => Success(p.iterateStream !: m.t)
}

def `map |-> mapSeqUnroll`: Strategy[Rise] = mapSeqUnroll
@rule def mapSeqUnroll: Strategy[Rise] = {
case m@map() => Success(p.mapSeqUnroll !: m.t)
}

def `map |-> mapGlobal`(dim: Int = 0): Strategy[Rise] = mapGlobal(dim)
@rule def mapGlobal(dim: Int = 0): Strategy[Rise] = {
case m@map() => Success(rise.openCL.TypedDSL.mapGlobal(dim) !: m.t)
}

def `reduce |-> reduceSeq`: Strategy[Rise] = reduceSeq
@rule def reduceSeq: Strategy[Rise] = {
case e@reduce() => Success(p.reduceSeq !: e.t)
}

// TODO shall we allow lowering from an already lowered reduceSeq?
def `reduce |-> reduceSeqUnroll`: Strategy[Rise] = reduceSeqUnroll
@rule def reduceSeqUnroll: Strategy[Rise] = {
case e@reduce() => Success(p.reduceSeqUnroll !: e.t)
case e@p.reduceSeq() => Success(p.reduceSeqUnroll !: e.t)
}

// Specialized Lowering
Expand Down Expand Up @@ -236,6 +248,28 @@ object lowering {
}
}

@rule def toMemAfterAsScalar: Strategy[Rise] = {
case a@App(asScalar(), _) => Success((preserveType(a) |> p.toMem) !: a.t)
}

@rule def toMemAfter: Strategy[Rise] =
e => Success((preserveType(e) |> p.toMem) !: e.t)

@rule def toMemBefore: Strategy[Rise] = {
case a@App(f, e) => Success((p.toMem(e) |> preserveType(f)) !: a.t)
}

@strategy
def storeTempsAsScalars: Strategy[Rise] =
innermost(isApplied(isPrimitive(asScalar)))(toMemAfter)

@strategy
def storeTempAsVectors: Strategy[Rise] =
innermost(isApplied(isPrimitive(asScalar)))(toMemBefore)

def `map(f) |-> asVector >> map(f_vec) >> asScalar`(n: Nat): Strategy[Rise] =
vectorize(n)(default.RiseTraversable)

@rule def vectorize(n: Nat)(implicit ev: Traversable[Rise]): Strategy[Rise] = {
case a@App(App(map(), f), input) if
isComputation()(ev)(f) && !isVectorArray(a.t) =>
Expand Down Expand Up @@ -265,8 +299,9 @@ object lowering {

@rule def untype: Strategy[Rise] = p => Success(p.setType(TypePlaceholder))

@rule def parallel()(implicit ev: Traversable[Rise]): Strategy[Rise] = {
case e@App(map(), f) if containsComputation()(ev)(f) => Success(mapPar(f) !: e.t)
def parallel()(implicit ev: Traversable[Rise]): Strategy[Rise] = mapParCompute
@rule def mapParCompute()(implicit ev: Traversable[Rise]): Strategy[Rise] = {
case e@App(map(), f) if containsComputation()(ev)(f) => Success(omp.mapPar(f) !: e.t)
}

@rule def unroll: Strategy[Rise] = {
Expand Down
24 changes: 18 additions & 6 deletions src/main/scala/rise/elevate/rules/traversal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,24 @@ object traversal {
implicit object RiseTraversable extends implementation.DefaultTraversal {
override protected def oneHandlingState: Boolean => Strategy[Rise] => Strategy[Rise] =
carryOverState => s => {
// (option 1) traverse to argument first
case a @ App(f, e) => s(e) match {
case Success(x: Rise) => Success(App(f, x)(a.t))
case Failure(state) => if (carryOverState)
state(f).mapSuccess(App(_, e)(a.t)) else
s(f).mapSuccess(App(_, e)(a.t))
// To achieve a traversal that most closely corresponds to the execution order we ...
Comment thread
michel-steuwer marked this conversation as resolved.
case a @ App(f, e) => e.t match {
// ... traverse arguments with a function type after the called function ...
case FunType(_, _) | DepFunType(_, _) =>
s(f) match {
case Success(x: Rise) => Success(App(x, e)(a.t))
case Failure(state) => if (carryOverState)
state(e).mapSuccess(App(f, _)(a.t)) else
s(e).mapSuccess(App(f, _)(a.t))
}
// ... traverse arguments with a non-function type before the called function.
case _ =>
s(e) match {
case Success(x: Rise) => Success(App(f, x)(a.t))
case Failure(state) => if (carryOverState)
state(f).mapSuccess(App(_, e)(a.t)) else
s(f).mapSuccess(App(_, e)(a.t))
}
}

// Push s further down the AST.
Expand Down
Loading