Skip to content
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

Implement @static annotation on singleton object fields. #894

Merged
merged 3 commits into from
Jul 20, 2012

Conversation

axel22
Copy link
Member

@axel22 axel22 commented Jul 12, 2012

This commit introduces the @static annotation on fields of static singleton objects.

A static singleton object is a top-level singleton object or an object nested within
a static singleton object.

The field annotated with @static is generated as a true static field in the companion
class of the object. The initialization of the field is done in the static initializer of the
companion class, instead of the object itself.

Here's an example. This:

object Foo {
  @static val FIELD = 123
}
class Foo

generates :

object Foo {
  def FIELD(): Int = Foo.FIELD
}
class Foo {
  <static> val FIELD = 123
}

The accessor in object Foo is changed to return the static
field in class Foo (the companion class is generated if it is
not already present, and the same is done for getters if FIELD
is a var).

Furthermore, all the callsites to accessor Foo.FIELD are rewritten
to access the static field directly.

It is illegal to annotate a field in the singleton object as @static if there is
already a member of the same name in class Foo.

This allows better Java interoperability with frameworks which rely on static fields being
present in the class.
The AtomicReferenceFieldUpdaters are one such example. Instead of having
a Java base class holding the volatile mutable field f and a static field containing
a reference to the AtomicReferenceFieldUpdater that mutates f atomically,
and extending this Java base class from Scala, we can now simply do:

object Foo {
  @static val arfu = AtomicReferenceUpdater.newUpdater(
    classOf[Foo], classOf[String], "f"
  )
}
class Foo {
  @volatile var f = null
  import Foo._
  def CAS(ov: String, nv: String) = arfu.compareAndSet(this, null, "")
}

In summary, this commit introduces the following:

  • adds the @static annotation
  • for objects without a companion class and @static members, creates a companion class
    (this is done in CleanUp)
  • checks for conflicting names and if @static is used on static singleton object member
  • creates the static field in the companion class for @static members
  • moves the field initialization from the companion object to the static initializer of
    the class (the initialization of @static members is bypassed in the Constructors phase,
    and added to static ctors in CleanUp)
  • all callsites to the accessors of @static are rewritten to access the static fields directly
    (this is done in GenICode)
  • getters and setters to @static fields access the static field directly, and the field in the
    singleton object is not emitted (this is done in GenICode)
  • changes in GenJVM, GenASM - now computing local var indices in static initializers
    as well - this was an oversight before, now is necessary

Future work: allow the @static annotation on methods as well - this will
be added next.

Review by @odersky
Review by @dragos

@axel22
Copy link
Member Author

axel22 commented Jul 12, 2012

@soc
Copy link
Member

soc commented Jul 13, 2012

Copying from the closed pull request:

I remember that there was an earlier hack included to fix an issue with Android compatibility, I think it would make sense to remove that hack now and check that "static" also works for this "real world use case".

Look for Parcelable in GenJVM.scala, JAndroidBuilder in GenASM.scala and the whole GenAndroid.scala file.

@retronym
Copy link
Member

@soc
Copy link
Member

soc commented Jul 13, 2012

Thinking about it again ... I guess it would make sense to check for Parcelable and issue a migration warning. Probably also just deprecate the functionality, so that Android keeps working in 2.10. But then special care would need to be taken that something like @static final val CREATOR: Parcelable.Creator[...] wouldn't emit code twice.

/**
* An annotation that marks a member in the companion object as static
* and ensures that the compiler generates static fields/methods for it.
* This is important for Java interoperability and performance reasons.
Copy link
Member

Choose a reason for hiding this comment

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

performance reasons

As much I this can be true, I fear that people will start sprinkling it over their code to get "magically" faster performance. Maybe it would be better to leave that part of the documentation out?

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe we should. In fact, maybe we should also put it behind a language import.

Copy link
Member

Choose a reason for hiding this comment

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

Good idea! Yes, seems to be reasonable. I fear the day were beginners start to write code like @specialized @inline @static def foo .... :-)

@rkuhn
Copy link
Contributor

rkuhn commented Jul 14, 2012

One question: why move the initialization out of the object’s constructor? The object is initialized from the static initializer anyway (at least in 2.10.0-M5 as I verified looking at some byte code), so which problems would ensue by just having the constructor happen “in sequence”? It would for sure make understanding the code flow easier, since the non-static parts are intermingled lexically with the static ones. And you could remove some code, which is always good ;-)

@axel22
Copy link
Member Author

axel22 commented Jul 14, 2012

That's exactly the first thing I've intended to do. The problem is that AtomicReferenceUpdaters will (on creation) check to see if the caller is the owner of the field they're supposed to modify, and throw an exception if it's not. This is why it's imperative that the creation of the arfu occurs in the static initializer of the class itself.

@rkuhn
Copy link
Contributor

rkuhn commented Jul 15, 2012

What a pity that this very special and extremely quirky use-case should force such a questionable design choice. If someone goes to the trouble of putting an ARFU into a static field, why don’t they go the full nine yards and use Unsafe? The whole j.u.c. package has been moved this way for JDK7 and onward.

@adriaanm
Copy link
Contributor

I don't think this should go into 2.10.x since it's not a critical bugfix AFAICT.
Please feel free to argue otherwise and reopen.

@adriaanm adriaanm closed this Jul 16, 2012
@axel22
Copy link
Member Author

axel22 commented Jul 16, 2012

I think Martin was very interested to get this into 2.10. It was his idea to do this.

@heathermiller
Copy link
Member

I can vouch for that ^

@adriaanm
Copy link
Contributor

I see. I didn't know, sorry. It still feels like an improvement instead of a critical bug fix.

Let's discuss it at the meeting tomorrow.

@adriaanm adriaanm reopened this Jul 16, 2012
@axel22
Copy link
Member Author

axel22 commented Jul 16, 2012

Sure!

@adriaanm
Copy link
Contributor

@axel22 could you please rebase on 2.10.x and push -f?

@axel22
Copy link
Member Author

axel22 commented Jul 18, 2012

Yes, I'll do this now.

This commit introduces the `@static` annotation on fields of static singleton objects.

A static singleton object is a top-level singleton object or an object nested within
a static singleton object.

The field annotated with `@static` is generated as a true static field in the companion
class of the object. The initialization of the field is done in the static initializer of the
companion class, instead of the object itself.

Here's an example. This:

    object Foo {
      @static val FIELD = 123
    }
    class Foo

generates :

   object Foo {
     def FIELD(): Int = Foo.FIELD
   }
   class Foo {
     <static> val FIELD = 123
   }

The accessor in `object Foo` is changed to return the static
field in `class Foo` (the companion class is generated if it is
not already present, and the same is done for getters if `FIELD`
is a `var`).

Furthermore, all the callsites to accessor `Foo.FIELD` are rewritten
to access the static field directly.

It is illegal to annotate a field in the singleton object as `@static` if there is
already a member of the same name in `class Foo`.

This allows better Java interoperability with frameworks which rely on static fields being
present in the class.
The `AtomicReferenceFieldUpdater`s are one such example. Instead of having
a Java base class holding the `volatile` mutable field `f` and a static field containing
a reference to the `AtomicReferenceFieldUpdater` that mutates `f` atomically,
and extending this Java base class from Scala, we can now simply do:

    object Foo {
      @static val arfu = AtomicReferenceUpdater.newUpdater(
        classOf[Foo], classOf[String], "f"
      )
    }
    class Foo {
      @volatile var f = null
      import Foo._
      def CAS(ov: String, nv: String) = arfu.compareAndSet(this, null, "")
    }

In summary, this commit introduces the following:
- adds the `@static` annotation
- for objects without a companion class and `@static` members, creates a companion class
(this is done in `CleanUp`)
- checks for conflicting names and if `@static` is used on static singleton object member
- creates the `static` field in the companion class for `@static` members
- moves the field initialization from the companion object to the static initializer of
the class (the initialization of `@static` members is bypassed in the `Constructors` phase,
and added to static ctors in `CleanUp`)
- all callsites to the accessors of `@static` are rewritten to access the static fields directly
(this is done in `GenICode`)
- getters and setters to `@static` fields access the static field directly, and the field in the
singleton object is not emitted (this is done in `GenICode`)
- changes in `GenJVM`, `GenASM` - now computing local var indices in static initializers
as well - this was an oversight before, now is necessary

Future work: allow the `@static` annotation on methods as well - this will
be added in a future commit.

Review by @odersky, @dragos, @paulp, @heathermiller.
@axel22
Copy link
Member Author

axel22 commented Jul 18, 2012

I did a push -f, should be ok for merge now.

PLS REBUILD ALL

@scala-jenkins
Copy link

Started jenkins job pr-scala-testsuite-linux-opt at https://scala-webapps.epfl.ch/jenkins/job/pr-scala-testsuite-linux-opt/520/

@scala-jenkins
Copy link

jenkins job pr-scala-testsuite-linux-opt: Success - https://scala-webapps.epfl.ch/jenkins/job/pr-scala-testsuite-linux-opt/520/

@adriaanm
Copy link
Contributor

Ok, so now we wait for some one to sign off :-)

if (m.symbol.isAccessor && m.symbol.accessed.hasStaticAnnotation) {
// in companion object accessors to @static fields, we access the static field directly
val hostClass = m.symbol.owner.companionClass
val staticfield = hostClass.info.decls.find(_.name.toString.trim == m.symbol.accessed.name.toString.trim)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't you store the static symbol somewhere? I'm worried linear search might be too slow, plus it's comparing strings, which isn't fast either.

Copy link
Member Author

Choose a reason for hiding this comment

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

I could store it somewhere during cleanup, but I'm not sure where so that it's visible later during icode. Will look into it.

Also, maybe findMember could help.

@axel22
Copy link
Member Author

axel22 commented Jul 19, 2012

I will address these issues and add a commit once I do.

@scala-jenkins
Copy link

Started jenkins job pr-scala-testsuite-linux-opt at https://scala-webapps.epfl.ch/jenkins/job/pr-scala-testsuite-linux-opt/607/

@scala-jenkins
Copy link

jenkins job pr-scala-testsuite-linux-opt: Success - https://scala-webapps.epfl.ch/jenkins/job/pr-scala-testsuite-linux-opt/607/

@adriaanm
Copy link
Contributor

@dragos, if the added commits address your feedback to your liking, could you sign off so that we can send @axel22 off to his vacation destination? :-)

The upcoming `findMember` optimizations should ensure that this
is fast enough.
@axel22
Copy link
Member Author

axel22 commented Jul 20, 2012

Thanks @adriaanm :))
I think that if the last commit passes the build, pretty much everything in the feedback should be addressed.

@axel22
Copy link
Member Author

axel22 commented Jul 20, 2012

PLS REBUILD ALL

@scala-jenkins
Copy link

Started jenkins job pr-scala-testsuite-linux-opt at https://scala-webapps.epfl.ch/jenkins/job/pr-scala-testsuite-linux-opt/623/

@scala-jenkins
Copy link

jenkins job pr-scala-testsuite-linux-opt: Success - https://scala-webapps.epfl.ch/jenkins/job/pr-scala-testsuite-linux-opt/623/

lrytz added a commit that referenced this pull request Jul 20, 2012
Implement @static annotation on singleton object fields.
@lrytz lrytz merged commit b67828d into scala:2.10.x Jul 20, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
9 participants