Skip to content

Experimental Streams using quotes #4317

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jun 13, 2018
Merged

Conversation

biboudis
Copy link
Contributor

@biboudis biboudis commented Apr 13, 2018

Port of the strymonas library as described in O. Kiselyov et al., Stream fusion, to completeness (POPL 2017) using Dotty's staging facility.

Ideas:

  • design a cleaner API, possibly investigate if it could be part of collections
  • experiment with alternative code generation schemes, e.g., generate fused code run wtih OpenCL/CUDA bindings
  • multidimensional types

producer.init(st => '{
Var('{ (_: Unit) => ()}){ advf => {
Var('{ true }) { hasNext => {
// Var('{ null.asInstanceOf[A] }) { curr => {
Copy link
Contributor Author

@biboudis biboudis Apr 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to generate the following line:

var curr: A = null.asInstanceOf[A]

so I guess the only way is:

Var('{ null.asInstanceOf[A] }) { curr => ....

The minimal standalone example that corresponds to the code above is:

trait Bar[T: Type] { def meth(): Unit }
class Foo[T: Type] {
  new Bar[T] {
    def meth() = {
      Var('(null.asInstanceOf[T])) { curr => '() }
    }
  }
}

The problem is that I get the following error message and I don't know if thats a bug or I am doing something horribly wrong.

25 |    new Bar[T] {
   |        ^^^^^^
   |        constructor Bar in trait Bar does not take parameters

ping @nicolasstucki

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  trait Bar { def meth(): Unit }
  class Foo[T: Type] {
    new Bar {
      def meth() = {
        Var('(null.asInstanceOf[T])) { curr => '() }
      }
    }
  }
Var('(null.asInstanceOf[T])) { curr => '() }
   |                                ^
   |                            access to Foo.this from wrong staging level:
   |                             - the definition is at level 0,
   |                             - but the access is at level 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #4350 after investigation

@biboudis biboudis force-pushed the streams-dev branch 4 times, most recently from 55b5913 to 087a191 Compare April 29, 2018 15:45
@biboudis
Copy link
Contributor Author

biboudis commented May 2, 2018

// Fixed: Note to self: the call to adv is not generated correctly in the nested case. Have to investigate

@biboudis
Copy link
Contributor Author

biboudis commented May 3, 2018

@nicolasstucki parallel tests failed. I will restart the build.

@biboudis biboudis requested a review from odersky May 4, 2018 09:09
}

object Stream {
def of[A: Type](arr: Expr[Array[A]]): Stream[A] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try changing it to

def of[A: Type: Liftable](arr0: Array[A]): Stream[A] = {
  val arr = arr0.toExpr
  ...

You will need some lifter for Arrays from the library.

}

/** A stream containing the first `n` elements of this stream. */
def take(n: Expr[Int]): Stream[A] = Stream(takeRaw[Expr[A]](n, stream))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def take(n: Int): Stream[A] = Stream(takeRaw[Expr[A]](n.toExpr, stream))

Copy link
Contributor

@julienrf julienrf May 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicolasstucki what’s the advantage of your proposal? At first sight it seems less powerful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aim is to hide the Expr from the end user.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then do we want fold to have the following signature?

def fold[W : Type](z: W, f: (W, A) => W): W

I’m not sure this is what we want?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, it is not exactly what I want. We need to hide the Expr behind a macro. I will test my signature idea and then document it here if it works

@biboudis biboudis requested a review from julienrf May 9, 2018 11:57
Copy link
Contributor

@nicolasstucki nicolasstucki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should merge like this. It will serve a great regression test. We should refine it further based on this work afterward.

@biboudis
Copy link
Contributor Author

I have the changes you proposed ready but I was stuck in something during the refactoring. I will post here, maybe discuss and merge.

Thanks @nicolasstucki

* {{{
* /* initialization for stream 1 */
*
* var curr = null.asInstanceOf[Int] // keeps each element from reified stream
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be 0 instead of null. Also, it seems that curr is never updated.

Copy link
Contributor Author

@biboudis biboudis May 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

I was "updating" it as a comment. Fixing it to illustrate it better!

producer.init(st =>
Var('{ (_: Unit) => ()}){ nadv => {
Var('{ true }) { hasNext => {
Var('{ null.asInstanceOf[A] }) { curr => '{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m wondering if this can cause subtle bugs, since null.asInstanceOf[Int] produces 0, which is undistinguishable from the value 0 (by contrast with references, where null can always be distinguished from an object reference).

Why don’t you use Option[A] instead?

Var(z) { s: Var[W] => '{
~foldRaw[Expr[A]]((a: Expr[A]) => '{
~s.update(f(s.get, a))
}, stream)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any difference between '{ ~s.update(…) } and s.update(…)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the same

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I would not change it. This kind of small additions have been useful to find bugs in the implementation. I would keep it like this for as a regression test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, nice catch @julienrf!

Copy link
Contributor

@julienrf julienrf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks interesting! Do you have more insights on the generated code or on performance?

}
}

private def foldRaw[A](consumer: A => Expr[Unit], stream: StagedStream[A]): Expr[Unit] = {
Copy link
Contributor

@julienrf julienrf May 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that we don’t need to take stream as a parameter since it is already a parameter of the Stream class. (btw name shadowing is dangerous!)


def init(k: St => Expr[Unit]): Expr[Unit] = {
producer.init(st =>
Var('{ (_: Unit) => ()}){ nadv => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicolasstucki this is the line that triggers the exception after the last rebase

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix in #4592

@biboudis biboudis merged commit 78a5e09 into scala:master Jun 13, 2018
@allanrenucci allanrenucci deleted the streams-dev branch June 13, 2018 14:21
@LPTK
Copy link
Contributor

LPTK commented Jun 13, 2018

@biboudis very cool! Has this implementation been benchmarked? Does it expand full stream pipelines into low-level loops, as in the paper?

@biboudis
Copy link
Contributor Author

Thanks @LPTK. Yes, by inspection, the code generates the expected imperative low-level loops. If needed we will benchmark it in the future. For now this is merely a big test case for us.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants