Tra*_*own 35 java macros jvm scala scala-macros
我正在用Scala宏替换Java程序中的一些代码生成组件,并且运行Java虚拟机对单个方法生成的字节代码大小的限制(64千字节).
例如,假设我们有一个大型的XML文件,它表示我们想要在程序中使用的从整数到整数的映射.我们希望避免在运行时解析此文件,因此我们将编写一个宏,它将在编译时进行解析并使用该文件的内容来创建方法的主体:
import scala.language.experimental.macros
import scala.reflect.macros.Context
object BigMethod {
// For this simplified example we'll just make some data up.
val mapping = List.tabulate(7000)(i => (i, i + 1))
def lookup(i: Int): Int = macro lookup_impl
def lookup_impl(c: Context)(i: c.Expr[Int]): c.Expr[Int] = {
import c.universe._
val switch = reify(new scala.annotation.switch).tree
val cases = mapping map {
case (k, v) => CaseDef(c.literal(k).tree, EmptyTree, c.literal(v).tree)
}
c.Expr(Match(Annotated(switch, i.tree), cases))
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,编译的方法将超过大小限制,但不是一个很好的错误说,我们给了一个巨大的堆栈跟踪与大量的调用,TreePrinter.printSeq并被告知我们已经杀死了编译器.
我有一个解决方案,涉及将案例拆分为固定大小的组,为每个组创建一个单独的方法,并添加一个顶级匹配,将输入值调度到适当的组的方法.它可以工作,但它很不愉快,而且我不想每次编写宏时都不必使用这种方法,其中生成的代码的大小取决于某些外部资源.
有没有更清洁的方法来解决这个问题?更重要的是,有没有办法更优雅地处理这种编译器错误?我不喜欢库用户得到一个难以理解的"该条目似乎已经杀死编译器"错误消息的想法只是因为宏正在处理的某些XML文件已超过一些(相当低的)大小阈值.
Ond*_*žka 10
Imo将数据放入.class并不是一个好主意.它们也被解析,它们只是二进制的.但是将它们存储在JVM中可能会对garbagge收集器和JIT编译器的性能产生负面影响.
在您的情况下,我会将XML预编译为适当格式的二进制文件并解析它.使用现有工具的可格式格式可以是例如FastRPC或良好的旧DBF.或者,如果您需要快速高级查找和搜索,可以预填充ElasticSearch存储库.后者的一些实现还可以提供基本索引,甚至可以使解析 - 应用程序只读取相应的偏移量.
由于有人必须说些什么,我按照 的说明Importers在返回树之前尝试编译树。
如果你给编译器足够的堆栈,它会正确地报告错误。
(它似乎不知道如何处理 switch 注释,留作以后的练习。)
apm@mara:~/tmp/bigmethod$ skalac bigmethod.scala ; skalac -J-Xss2m biguser.scala ; skala bigmethod.Test
Error is java.lang.RuntimeException: Method code too large!
Error is java.lang.RuntimeException: Method code too large!
biguser.scala:5: error: You ask too much of me.
Console println s"5 => ${BigMethod.lookup(5)}"
^
one error found
Run Code Online (Sandbox Code Playgroud)
相对于
apm@mara:~/tmp/bigmethod$ skalac -J-Xss1m biguser.scala
Error is java.lang.StackOverflowError
Error is java.lang.StackOverflowError
biguser.scala:5: error: You ask too much of me.
Console println s"5 => ${BigMethod.lookup(5)}"
^
Run Code Online (Sandbox Code Playgroud)
其中客户端代码如下:
package bigmethod
object Test extends App {
Console println s"5 => ${BigMethod.lookup(5)}"
}
Run Code Online (Sandbox Code Playgroud)
我第一次使用这个 API,但不是最后一次。谢谢你让我开始。
package bigmethod
import scala.language.experimental.macros
import scala.reflect.macros.Context
object BigMethod {
// For this simplified example we'll just make some data up.
//final val size = 700
final val size = 7000
val mapping = List.tabulate(size)(i => (i, i + 1))
def lookup(i: Int): Int = macro lookup_impl
def lookup_impl(c: Context)(i: c.Expr[Int]): c.Expr[Int] = {
def compilable[T](x: c.Expr[T]): Boolean = {
import scala.reflect.runtime.{ universe => ru }
import scala.tools.reflect._
//val mirror = ru.runtimeMirror(c.libraryClassLoader)
val mirror = ru.runtimeMirror(getClass.getClassLoader)
val toolbox = mirror.mkToolBox()
val importer0 = ru.mkImporter(c.universe)
type ruImporter = ru.Importer { val from: c.universe.type }
val importer = importer0.asInstanceOf[ruImporter]
val imported = importer.importTree(x.tree)
val tree = toolbox.resetAllAttrs(imported.duplicate)
try {
toolbox.compile(tree)
true
} catch {
case t: Throwable =>
Console println s"Error is $t"
false
}
}
import c.universe._
val switch = reify(new scala.annotation.switch).tree
val cases = mapping map {
case (k, v) => CaseDef(c.literal(k).tree, EmptyTree, c.literal(v).tree)
}
//val res = c.Expr(Match(Annotated(switch, i.tree), cases))
val res = c.Expr(Match(i.tree, cases))
// before returning a potentially huge tree, try compiling it
//import scala.tools.reflect._
//val x = c.Expr[Int](c.resetAllAttrs(res.tree.duplicate))
//val y = c.eval(x)
if (!compilable(res)) c.abort(c.enclosingPosition, "You ask too much of me.")
res
}
}
Run Code Online (Sandbox Code Playgroud)