-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add TASTY pickling of quotes and implement ~
on quotes
#3662
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
nicolasstucki
merged 18 commits into
scala:master
from
dotty-staging:add-meta-with-tasty
Jan 8, 2018
+591
−50
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
86939bb
Avoid running sepparate compilation tests under legacy tests
nicolasstucki f021562
Rename quote test files
nicolasstucki ddb4449
Add TASTY quote pickling for trees
nicolasstucki c40a022
Fix bug while unpickling splices in quotes
nicolasstucki c80ea20
Implement primitive liftable
nicolasstucki 1674cd8
Add String to Liftable expressions and abstract over all primitives
nicolasstucki 10753bc
Re-enable valueTypeNameToJavaType
nicolasstucki a15fc90
Disable spourious `containsQuotesOrSplices = true`
nicolasstucki e4c2a33
Rename QuoteUnpickler to TastyUnpickler
nicolasstucki 29ef05d
Add comments to `RawQuote`s
nicolasstucki 65acf1c
Replace `PrimitiveExpr`s by the sinlge class `ConstantExpr`
nicolasstucki 2ff30d6
Avoid printing encoded TASTY in `TastyExpr` and `TastyType`
nicolasstucki 2bd3b6f
Simplify `RawQuoted` instantiations
nicolasstucki 990e3d1
Implement extractor for quoted trees
nicolasstucki 2aa05ef
Re-work Interpreter
nicolasstucki 8b94c86
Move quote unpickling to the core package
nicolasstucki 82d36bd
Encapsulate quoted types in top level `type`
nicolasstucki f86bcc5
Add environment to the interpreter
nicolasstucki File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package dotty.tools.dotc.core.quoted | ||
|
||
import dotty.tools.dotc.ast.Trees._ | ||
import dotty.tools.dotc.ast.{tpd, untpd} | ||
import dotty.tools.dotc.config.Printers._ | ||
import dotty.tools.dotc.core.Constants.Constant | ||
import dotty.tools.dotc.core.Contexts._ | ||
import dotty.tools.dotc.core.Decorators._ | ||
import dotty.tools.dotc.core.Flags._ | ||
import dotty.tools.dotc.core.Symbols._ | ||
import dotty.tools.dotc.core.tasty.{TastyPickler, TastyPrinter, TastyString} | ||
import dotty.tools.dotc.interpreter.RawQuoted | ||
|
||
object PickledQuotes { | ||
import tpd._ | ||
|
||
/** Pickle the quote into a TASTY string */ | ||
def pickleQuote(tree: Tree)(implicit ctx: Context): String = { | ||
if (ctx.reporter.hasErrors) "<error>" | ||
else { | ||
val encapsulated = encapsulateQuote(tree) | ||
val pickled = pickle(encapsulated) | ||
TastyString.tastyToString(pickled) | ||
} | ||
} | ||
|
||
/** Transform the expression into its fully spliced Tree */ | ||
def quotedToTree(expr: quoted.Quoted)(implicit ctx: Context): Tree = expr match { | ||
case expr: quoted.TastyQuoted => unpickleQuote(expr) | ||
case expr: quoted.Liftable.ConstantExpr[_] => Literal(Constant(expr.value)) | ||
case expr: RawQuoted => expr.tree | ||
} | ||
|
||
/** Unpickle the tree contained in the TastyQuoted */ | ||
private def unpickleQuote(expr: quoted.TastyQuoted)(implicit ctx: Context): Tree = { | ||
val tastyBytes = TastyString.stringToTasty(expr.tasty) | ||
val unpickled = unpickle(tastyBytes, expr.args) | ||
unpickled match { | ||
case PackageDef(_, (vdef: ValDef) :: Nil) => vdef.rhs | ||
case PackageDef(_, (tdef: TypeDef) :: Nil) => tdef.rhs | ||
} | ||
} | ||
|
||
/** Encapsulate the tree in a top level `val` or `type` | ||
* `<tree>` ==> `package _root_ { val ': Any = <tree> }` | ||
* or | ||
* `<type tree>` ==> `package _root_ { type ' = <tree tree> }` | ||
*/ | ||
private def encapsulateQuote(tree: Tree)(implicit ctx: Context): Tree = { | ||
def encapsulatedTerm = { | ||
val sym = ctx.newSymbol(ctx.owner, "'".toTermName, Synthetic, defn.AnyType, coord = tree.pos) | ||
ValDef(sym, tree).withPos(tree.pos) | ||
} | ||
|
||
def encapsulatedType = | ||
untpd.TypeDef("'".toTypeName, tree).withPos(tree.pos).withType(defn.AnyType) | ||
|
||
val quoted = if (tree.isTerm) encapsulatedTerm else encapsulatedType | ||
PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], quoted :: Nil).withPos(tree.pos) | ||
} | ||
|
||
// TASTY picklingtests/pos/quoteTest.scala | ||
|
||
/** Pickle tree into it's TASTY bytes s*/ | ||
private def pickle(tree: Tree)(implicit ctx: Context): Array[Byte] = { | ||
val pickler = new TastyPickler(defn.RootClass) | ||
val treePkl = pickler.treePkl | ||
treePkl.pickle(tree :: Nil) | ||
treePkl.compactify() | ||
pickler.addrOfTree = treePkl.buf.addrOfTree | ||
pickler.addrOfSym = treePkl.addrOfSym | ||
// if (tree.pos.exists) | ||
// new PositionPickler(pickler, treePkl.buf.addrOfTree).picklePositions(tree :: Nil) | ||
|
||
// other pickle sections go here. | ||
val pickled = pickler.assembleParts() | ||
|
||
if (pickling ne noPrinter) { | ||
println(i"**** pickled quote of \n${tree.show}") | ||
new TastyPrinter(pickled).printContents() | ||
} | ||
|
||
pickled | ||
} | ||
|
||
/** Unpickle TASTY bytes into it's tree */ | ||
private def unpickle(bytes: Array[Byte], splices: Seq[Any])(implicit ctx: Context): Tree = { | ||
val unpickler = new TastyUnpickler(bytes, splices) | ||
unpickler.enter(roots = Set(defn.RootPackage)) | ||
val tree = unpickler.body.head | ||
if (pickling ne noPrinter) { | ||
println(i"**** unpickled quote for \n${tree.show}") | ||
new TastyPrinter(bytes).printContents() | ||
} | ||
tree | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package dotty.tools.dotc.core.quoted | ||
|
||
import dotty.tools.dotc.ast.Trees.GenericApply | ||
import dotty.tools.dotc.ast.tpd | ||
import dotty.tools.dotc.core.Contexts.Context | ||
import dotty.tools.dotc.core.Types.Type | ||
import dotty.tools.dotc.transform.SymUtils._ | ||
|
||
/** Extractors for quotes */ | ||
object Quoted { | ||
|
||
/** Extracts the content of a quoted tree. | ||
* The result can be the contents of a term ot type quote, which | ||
* will return a term or type tree respectively. | ||
*/ | ||
def unapply(tree: tpd.Tree)(implicit ctx: Context): Option[tpd.Tree] = tree match { | ||
case tree: GenericApply[Type] if tree.symbol.isQuote => Some(tree.args.head) | ||
case _ => None | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
compiler/src/dotty/tools/dotc/core/quoted/TastyUnpickler.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package dotty.tools.dotc.core.quoted | ||
|
||
import dotty.tools.dotc.core.tasty._ | ||
import dotty.tools.dotc.core.tasty.TastyUnpickler.NameTable | ||
|
||
object TastyUnpickler { | ||
class QuotedTreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], splices: Seq[Any]) | ||
extends DottyUnpickler.TreeSectionUnpickler(posUnpickler) { | ||
override def unpickle(reader: TastyReader, nameAtRef: NameTable) = | ||
new TreeUnpickler(reader, nameAtRef, posUnpickler, splices) | ||
} | ||
} | ||
|
||
/** A class for unpickling quoted Tasty trees and symbols. | ||
* @param bytes the bytearray containing the Tasty file from which we unpickle | ||
* @param splices splices that will fill the holes in the quote | ||
*/ | ||
class TastyUnpickler(bytes: Array[Byte], splices: Seq[Any]) extends DottyUnpickler(bytes) { | ||
import DottyUnpickler._ | ||
import TastyUnpickler._ | ||
|
||
protected override def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler]): TreeSectionUnpickler = | ||
new QuotedTreeSectionUnpickler(posUnpicklerOpt, splices) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package dotty.tools.dotc.core.tasty | ||
|
||
/** Utils for String representation of TASTY */ | ||
object TastyString { | ||
|
||
/** Decode the TASTY String into TASTY bytes */ | ||
def stringToTasty(str: String): Array[Byte] = { | ||
val bytes = new Array[Byte](str.length) | ||
for (i <- str.indices) bytes(i) = str.charAt(i).toByte | ||
bytes | ||
} | ||
|
||
/** Encode TASTY bytes into a TASTY String */ | ||
def tastyToString(bytes: Array[Byte]): String = { | ||
val chars = new Array[Char](bytes.length) | ||
for (i <- bytes.indices) chars(i) = (bytes(i) & 0xff).toChar | ||
new String(chars) | ||
} | ||
|
||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new String(bytes)
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a tricky problem. Looking at Stackoverflow, people say you should use a Codec for this, typically Base64. The scheme of mapping all bytes to ranges 0..255 looks like it would work, but it's not optimal. Strings are represented in Classfiles as UTF8 characters, with one byte for ranges 0.127 and two bytes for ranges 128-255. This means that, assuming a uniform bit distribution you get an overhead of 50%. Doing a 8->7 bit codec would give an overhead of less than 15%.
There's another problem of string size. Strings are limited to 65365 characters. This might not be enough for a larger quoted program.
scalac solves both of these problems when serializing its pickles as annotations. I think we should copy that scheme. I tried to find it but could not. @retronym @lrytz @adriaanm does one of you have an idea where the code that serializes a Pickle as an annotation is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can leave it like this for this PR, but then we should open an issue for future improvements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will start looking at the alternatives. I also think we should start with this for now to unblock the next PRs and allow people to use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@odersky https://github.com/scala/scala/blob/2f3791c3079d998d29788d121552c27517f58a6c/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala#L1036-L1119
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lrytz thanks for the link. Could you also point me to the place where the
String
/Array[Strings]
are converted back into anArray[Byte]
. Thanks.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took me a while to find it.. Need to clean this up / document. Method
parseScalaSigBytes
callsConstantPool.getBytes
which goes throughByteCodecs.decode
.The encoding is explained here http://www.scala-lang.org/old/sites/default/files/sids/dubochet/Mon,%202010-05-31,%2015:25/Storage%20of%20pickled%20Scala%20signatures%20in%20class%20files.pdf
the reason for the incrementing by 1 that 0x7f is expected to be less common than 0x00, so the two byte encoding hits less often.
The confusing part is that the class
ScalaSigBytes
used in the backend to encode the signature usesByteCodecs.encode8to7
, but does the+1
itself. It doesn't need to map 0x00 to the two byte version because ASM will do it when writing the annotation to the classfile. However, in the unpickler, we don't use ASM to read the annotation, but just get the bytes from the classfile directly. So there we'll see the two byte encoding.ByteCodecs.decode
does the necessary work.