stanford-ppl / spatial-lang

Spatial: "Specify Parameterized Accelerators Through Inordinately Abstract Language"
MIT License
100 stars 12 forks source link

Smart constructors as partial functions. No more overriding #54

Open rubenfiszel opened 7 years ago

rubenfiszel commented 7 years ago

We rely on overloading and linearization order for rewrite rules Another solution is to use partial functions:

from

trait B {
    def f(x: X) = ...
} 
trait A extends B {
   override def f(x: X) = {
      case ...
      case _ => super.f(x)
   }

to

trait B {
   var f_pf = List({ case ... })
   def f(x: X) = f_pf first apply x //pseudocode
}
trait A {
   self: B =>
   self.p_f += {
      case ...
}
dkoeplin commented 7 years ago

I had implemented a version of this a while ago. I ultimately dropped it because it made writing rewrite rules a tad more annoying.

Consider the possible syntax:

// Check the global rewrite rule list for rewrites on And given these inputs. Stage if none are available.
@internal def bool_and(x: Exp[Bool], y: Exp[Bool]) = rewrite[And](x, y).getOrElse(stage(And(x,y))(ctx))

// Create rewrite rule and append to the global list of rewrite rules
rewrite[And]{case (Const(x: Boolean), Const(y: Boolean)) => bool(x && y) }

One problem here is that the partial function doesn't have access to the SourceContext. I don't believe you can have implicit arguments to a partial function, so the only way to get this to work, so the actual syntax here would be:

rewrite[And]{case (Const(x: Boolean), Const(y: Boolean), ctx: SrcCtx) => bool(x && y)(ctx) }

As a more minor point, unlike overriding constructors, the case statements always have to explicitly state the types, as they can't be inferred otherwise.

dkoeplin commented 7 years ago

Alternatively, the partial functions can be of type PartialFunction[Any, SrcCtx => Exp[_]]. That might work? When I tried this before, I wound up creating two forms of rewrite rules, those of the form PartialFunction[Any, Exp[_]] and those of form PartialFunction[Any, SrcCtx => Exp[_]]. It worked, but it was just a tad awkward to separate like that.

rubenfiszel commented 7 years ago

We could always use an implicit conversion:

implicit def eatSC(x:Exp[_]) = (sc: SourceContext) => x

or a similar overloaded rewrite function

dkoeplin commented 7 years ago

It would probably have to be defined on PartialFunction, i.e.

implicit def addSC(f: PartialFunction[Any, Exp[_]]): PartialFunction[Any, SrcCtx => Exp[_]] = ...
rubenfiszel commented 7 years ago

Are you sure ?

scala> case class Const[T](x: T)
defined class Const

scala> implicit def toConst(x: Int):Const[Int] = Const(x)
toConst: (x: Int)Const[Int]

scala> var x = PartialFunction.empty[Any, Const[Int]]
x: PartialFunction[Any,Const[Int]] = <function1>

scala> x = x orElse { case Const(y) => 3 }
x: PartialFunction[Any,Const[Int]] = <function1>
dkoeplin commented 7 years ago

Ah ok, that may work then.

rubenfiszel commented 7 years ago

@dkoeplin More complete gist: http://scalakata.com/gist/4426add4aa90ecc0bbda0cf414e3cb2b Thoughts ?

dkoeplin commented 7 years ago

Makes sense. Not 100% sure which parts are expected to be generated by the macro here though

rubenfiszel commented 7 years ago

Everything below:

@rewrite def bools_and(x:Bool, y:Bool): Bool
 =====>
  var bools_and_pf = PartialFunction.empty[(Bool, Bool), SrcCtx => Bool]
  implicit def eatSC(b: Bool): SrcCtx => Bool = (x:SrcCtx) => b
  def bools_and(x:Bool, y: Bool)(ctx: SrcCtx): Bool = {
    val f: SrcCtx => Bool = (bools_and_pf.lift((x,y)).getOrElse(BoolAnd(x,y)))
    f(ctx)
  }
  def rewrite_bools_and(pf: PartialFunction[(Bool, Bool), SrcCtx => Bool]) =
    bools_and_pf = bools_and_pf orElse pf

@rewrite def bools_and(x:Bool, y:Bool): Bool =   { case (BoolConst(x), BoolConst(y)) => BoolConst(x && y)}
 =====>
same as before + 
rewrite_bools_and(    { case (BoolConst(x), BoolConst(y)) => BoolConst(x && y)} )
dkoeplin commented 7 years ago

Seems like we need two macros then, a "rewritable" default declaration and a "rewrite" rule declaration.

e.g.

implicit def addSC(b: Exp[_]): SrcCtx => Exp[_] = (x: SrcCtx) => b

@rewritable def xxx(args...): R = stage(And(x,y))
====> 
  var xxx_pf = PartialFunction.empty[(args...), SrcCtx => R]
  def xxx(args...)(implicit ctx: SrcCtx): R = {
    val f: SrcCtx => R = (xxx_pf.lift((x,y)).getOrElse(stage(And(x,y)))
    f(ctx)
  }
  def rewrite_xxx(pf: PartialFunction[(args...), SrcCtx => R]) =
    xxx_pf = xxx_pf orElse pf

@rewrite def xxx(args...) = { case args => ... }
====>
rewrite_xxx({ case args => ... })
rubenfiszel commented 7 years ago

My two macros were your rewritable. The first macro was just meant to be "no default provided" I originally thought people should use rewrite_bools_and directly, but your @rewrite make sense too :+1:

dkoeplin commented 7 years ago

Ohh I see, ok. Cool.