Skip to content

Add arrays to collection strawman #1414

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 17 commits into from
Aug 14, 2016

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Jul 25, 2016

This PR investigates what it takes to extend CollectionStrawMan5 to
arrays. Review by @szeiger ?

This PR investigates what it takes to extend CollectionStrawMan5 to
arrays.
@odersky
Copy link
Contributor Author

odersky commented Jul 25, 2016

Actually, to see what changes it's best to diff CollectionStrawMan5 and CollectionStrawMan6.

odersky added 6 commits July 26, 2016 23:51
Demonstrates how to integrate lazy non-view collections
in the framework.
By making LinearSeq an IterableLike, we can use tail-recursion
on drop.
substDealias did not follow aliases when the prefix of a typeref changed
under substitution. This was exhibited by a bug in extensionMethods which was
first discovered in CollectionStrawMan6 and was minimized in extmethods.
Makes it clearer what it is. Also, fixed check file.
The closures generated by elimByName did not get the InSuperCall
flag set. This caused problems in lambda lift which led to a
verify error for the new version CollectionStrawMan6. That version
replaces explicit function parameters in class LazyList by
by-name parameters.

Also: Clarify logic for liftLocals in LambdaLift (liftLocals caused the immediate
problem but was in the end not to blame).
Test generated code before but fails with verify error at runtime.
Here's the message:

Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    D$.<init>()V @2: invokedynamic
  Reason:
    Type uninitializedThis (current frame, stack[1]) is not assignable to 'D$'
  Current Frame:
    bci: @2
    flags: { flagThisUninit }
    locals: { uninitializedThis }
    stack: { uninitializedThis, uninitializedThis }
  Bytecode:
    0x0000000: 2a2a ba00 1f00 00b7 0022 2ab3 0024 b1

	at Test$.main(t3048.scala:13)
	at Test.main(t3048.scala)

With the fix in last commit, test causes backend to crash with

java.lang.AssertionError: assertion failed: val <none>
	at scala.Predef$.assert(Predef.scala:165)
	at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen$class.assertClassNotArray(BCodeHelpers.scala:214)
	at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen$class.assertClassNotArrayNotPrimitive(BCodeHelpers.scala:219)
	at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen$class.getClassBTypeAndRegisterInnerClass(BCodeHelpers.scala:238)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.getClassBTypeAndRegisterInnerClass(BCodeSkelBuilder.scala:51)
	at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen$class.internalName(BCodeHelpers.scala:210)
@odersky
Copy link
Contributor Author

odersky commented Jul 27, 2016

@DarkDimius Can you take this over? We had a verify error before that turned into a crash
in backend with the fix I added in the last commit. See commit message for details.

@DarkDimius
Copy link
Contributor

@odersky, looking at it now.

@DarkDimius
Copy link
Contributor

DarkDimius commented Jul 27, 2016

It's a funny interaction between:

  • elim-by-name(and erasure specifically);
  • lift-static;
  • supercalls.
object E extends F2(new B {})

Here we have an anonymous class new B {} that looks like it is created by erasure.
For some reason this class forgets the link to original anonymous class: SymDenot(E$annon1).initial.phase == erasure. I guess this is a bug.

Additionally, the owner of E$annon1 is an anonymous method inside E, that is inSuperCall and thus we have an anonymous nested class that has enclosingClass be package. This class looks like a top-level anonymous class breaking a lot of assumptions in shared backend and taking multiple branches in unexpected ways.

I don't want to rush it, I need to think about what's the right way to do this.

It's a funny interaction between:

    elim-by-name(and erasure specifically);
    lift-static;
    supercalls.

object E extends F2(new B {})

Here we have an anonymous class new B {} that looks like it is created
by erasure.

For some reason this class forgets the link to original anonymous class:
SymDenot(E$annon1).initial.phase == erasure. I guess this is a bug.

Additionally, the owner of E$annon1 is an anonymous method inside E,
that is inSuperCall and thus we have an anonymous nested class that
has enclosingClass be package. This class looks like a top-level
anonymous class breaking a lot of assumptions in shared backend
and taking multiple branches in unexpected ways.

I'm not sure that this is a proper fix. I assume there's a bigger bug
around, but I don't quite understand it right now and I need to work on
other stuff.

Making a quick fix to unblock @odersky.
@odersky
Copy link
Contributor Author

odersky commented Jul 27, 2016

Thanks for the quick fix, @DarkDimius !

@DarkDimius
Copy link
Contributor

DarkDimius commented Jul 27, 2016

/rebuild
Needed to trigger CI by hand for some reason.

@odersky
Copy link
Contributor Author

odersky commented Jul 27, 2016

/rebuild

@odersky
Copy link
Contributor Author

odersky commented Jul 27, 2016

Not sure what's going on with CI today.

Following the other colltests, put each in a separate package.
@odersky
Copy link
Contributor Author

odersky commented Jul 28, 2016

I think we are on the right track here. Both Arrays (strict, external) and LazyLists (lazy, internal) were easy to integrate. I believe this is a good indication that the framework covers the right things.

@odersky
Copy link
Contributor Author

odersky commented Jul 28, 2016

As to review, I think it's best of @DarkDimius or @smarter could have a look at it, because the critical parts are compiler fixes in core and transforms.

@szeiger
Copy link
Contributor

szeiger commented Jul 28, 2016

Also looking at it now, but I first have to familiarize myself with version 5 (and using dotty for the first time)

- Add proper :: to lists
- Move some methods to IterableOps in order to keep Iterable clean
- Rename knownLength to knownSize
- Add some implentations for performance and completeness
import CollectionStrawMan6._

/** Convert array to iterable via view. Lower priority than ArrayOps */
implicit def arrayToView[T](xs: Array[T]): ArrayView[T] = new ArrayView[T](xs)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need two separate classes? Could the array operations be implemented in ArrayView?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then they would not be considered in this scenario:

val x: Seq[Int] = Array(1, 2, 3)

@odersky
Copy link
Contributor Author

odersky commented Jul 28, 2016

OOM again.

@odersky
Copy link
Contributor Author

odersky commented Jul 28, 2016

/rebuild

}

/** A builder resulting from this builder my mapping the result using `f`. */
def mapResult[NewTo](f: To => NewTo) = new Builder[A, NewTo] {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a good abstraction for efficiency? As used in ListBuffer, it adds overhead for the copy-on-write semantics, which could be avoided with a special builder class.

Copy link
Contributor Author

@odersky odersky Jul 29, 2016

Choose a reason for hiding this comment

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

I am not sure I have understood where you see the inefficiency. When using a ListBuffer to build a List, there's one additional object created (namely the List builder which is the result of mapResult). True, we would could prevent allocation of that object by specialising.

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like ListBuffer only uses copy-on-write because it is used internally as a builder. COW may add overhead because it has to check aliased on every append (but I wouldn't be surprised if the impact turns out to be negligible in practice due to branch prediction doing the right thing).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ListBuffer always had copy on write semantics, and that's also heavily used in manual construction. Having a ListBuffer without COW would not gain us much I believe.


def this() = this(new Array[AnyRef](16), 0)

private var elems: Array[AnyRef] = initElems
Copy link
Contributor

Choose a reason for hiding this comment

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

Will the dotty linker be able to specialize this?

Copy link
Contributor

Choose a reason for hiding this comment

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

So far, no. If user insisted on AnyRef, it currently does nothing.

Copy link
Contributor

Choose a reason for hiding this comment

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

It would be great if this problem could be solved in a strawman proposal. Getting specialized arrays internally in the collection implementations would be a big improvement over the current collections library.

@odersky
Copy link
Contributor Author

odersky commented Jul 29, 2016

/rebuild

1 similar comment
@odersky
Copy link
Contributor Author

odersky commented Jul 29, 2016

/rebuild

odersky added 3 commits July 29, 2016 20:39
…alled on.

This is achieved by putting it into a new trait, LinearSeqLike.
Avoids missing member in tangledCompanion.scala, which is a minimization
of intermittent failures in CollectionStrawMan6. Intermittent, because it
depended on order of compilation. CollectionTests have to be compiled together with
but before CollectionStrawMan6 (this was _sometimes_ the case because partest did not
honor indicated compilation order so far). I.e.

    dotc CollectionTests_2.scala CollectionStrawMan6_1.scala

would trigger the error. tangledCompanion.scala captures the dependencies
in a single file.
When a member is missing, print whether we were looking for
a type or a value.
@odersky odersky force-pushed the add-array-strawman branch from df2b3b8 to dabf044 Compare July 29, 2016 18:44
@odersky
Copy link
Contributor Author

odersky commented Jul 29, 2016

After tracking down the intermittent problem with "member not found", and fixing it in 2902000, force pushed a cleaned up version without added debug info.

@odersky
Copy link
Contributor Author

odersky commented Jul 30, 2016

@szeiger I think you might be right about IndexedView. The added overhead seems quite reasonble after all. In the old collections it was hard because IndexedView had to duplicate all transforms of View including those that do not make sense (the latter were forced or treated in an ad-hoc way).

Followinf @szeiger's suggestion, equip IndexView with
optimized operations for map/drop/take.
@@ -156,6 +159,8 @@ object CollectionStrawMan6 extends LowPriority {
}
}

type IndexedSeq[+A] = Seq[A] { def view: IndexedView[A] }
Copy link
Member

Choose a reason for hiding this comment

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

If you do this then any call to view on a value with type IndexedSeq[Foo] will end up going through reflection, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On Sun, Jul 31, 2016 at 12:07 AM, Guillaume Martres <
[email protected]> wrote:

In src/strawman/collections/CollectionStrawMan6.scala
#1414 (comment):

@@ -156,6 +159,8 @@ object CollectionStrawMan6 extends LowPriority {
}
}

  • type IndexedSeq[+A] = Seq[A] { def view: IndexedView[A] }

If you do this then any call to view on a value with type IndexedSeq[Foo]
will end up going through reflection, no?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/lampepfl/dotty/pull/1414/files/dabf044485c60245e0fdf64c28eaa90285242a75..8db8c110a2b2a287875222a74511511d80be2b15#r72894931,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAwlVn1LM5xD42dSM8pPrJcmLIQls_Cjks5qa8s5gaJpZM4JUZlv
.

I don't think so, since view is already a method on Seq.

@odersky
Copy link
Contributor Author

odersky commented Aug 14, 2016

I am going to merge now, since I think it's good to have this one on record, and in the tests.

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