From df0d105f90816960b711c35a3289ff460295d1cc Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 29 Oct 2015 16:49:22 +1000 Subject: Use invokedynamic for structural calls, symbol literals, lamba ser. The previous encodings created static fields in the enclosing class to host caches. However, this isn't an option once emit code in default methods of interfaces, as Java interfaces don't allow private static fields. We could continue to emit fields, and make them public when added to traits. Or, as chosen in this commit, we can emulate a call-site specific static field by using invokedynamic: when the call site is linked, our bootstrap methid can perform one-time computation, and we can capture the result in the CallSite. To implement this, I've allowed encoding of arbitrary invokedynamic calls in ApplyDynamic. The encoding is: ApplyDynamic( NoSymbol.newTermSymbol(TermName("methodName")).setInfo(invokedType) Literal(Constant(bootstrapMethodSymbol)) :: ( Literal(Constant(staticArg0)) :: Literal(Constant(staticArgN)) :: Nil ) ::: (dynArg0 :: dynArgN :: Nil) ) So far, static args may be `MethodType`, numeric or string literals, or method symbols, all of which can be converted to constant pool entries. `MethodTypes` are transformed to the erased JVM type and are converted to descriptors as String constants. I've taken advantage of this for symbol literal caching and for the structural call site cache. I've also included a test case that shows how a macro could target this (albeit using private APIs) to cache compiled regexes. I haven't managed to use this for LambdaMetafactory yet, not sure if the facility is general enough. --- test/files/instrumented/indy-symbol-literal.scala | 19 +++++++++ .../Bootstrap.java | 17 ++++++++ .../indy-via-macro-with-dynamic-args/Test_2.scala | 6 +++ .../indy-via-macro-with-dynamic-args/macro_1.scala | 33 +++++++++++++++ test/files/run/indy-via-macro/Bootstrap.java | 16 ++++++++ test/files/run/indy-via-macro/Test_2.scala | 5 +++ test/files/run/indy-via-macro/macro_1.scala | 32 +++++++++++++++ test/files/run/t7974.check | 47 +++++++++++----------- 8 files changed, 152 insertions(+), 23 deletions(-) create mode 100644 test/files/instrumented/indy-symbol-literal.scala create mode 100644 test/files/run/indy-via-macro-with-dynamic-args/Bootstrap.java create mode 100644 test/files/run/indy-via-macro-with-dynamic-args/Test_2.scala create mode 100644 test/files/run/indy-via-macro-with-dynamic-args/macro_1.scala create mode 100644 test/files/run/indy-via-macro/Bootstrap.java create mode 100644 test/files/run/indy-via-macro/Test_2.scala create mode 100644 test/files/run/indy-via-macro/macro_1.scala (limited to 'test/files') diff --git a/test/files/instrumented/indy-symbol-literal.scala b/test/files/instrumented/indy-symbol-literal.scala new file mode 100644 index 0000000000..a1c333cf95 --- /dev/null +++ b/test/files/instrumented/indy-symbol-literal.scala @@ -0,0 +1,19 @@ +import scala.tools.partest.instrumented._ +import scala.tools.partest.instrumented.Instrumentation._ + +object Test { + def main(args: Array[String]): Unit = { + 'warmup + startProfiling() + var i = 0; + while (i < 2) { + 'foo.name + i += 1 + } + stopProfiling() + // Only expect a single call to lookup the interned Symbol at each call site the defines + // a single literal. + val Symbol_apply = MethodCallTrace("scala/Symbol$", "apply", "(Ljava/lang/String;)Lscala/Symbol;") + assert(getStatistics.get(Symbol_apply) == Some(1), getStatistics); + } +} diff --git a/test/files/run/indy-via-macro-with-dynamic-args/Bootstrap.java b/test/files/run/indy-via-macro-with-dynamic-args/Bootstrap.java new file mode 100644 index 0000000000..5c9ce01cf4 --- /dev/null +++ b/test/files/run/indy-via-macro-with-dynamic-args/Bootstrap.java @@ -0,0 +1,17 @@ +package test; + +import java.lang.invoke.*; +import java.util.regex.Pattern; + +public final class Bootstrap { + private Bootstrap() { + } + + /** Pre-compile a regex */ + public static CallSite bootstrap(MethodHandles.Lookup lookup, String invokedName, + MethodType invokedType, + String value) throws Throwable { + MethodHandle Pattern_matcher = MethodHandles.lookup().findVirtual(java.util.regex.Pattern.class, "matcher", MethodType.fromMethodDescriptorString("(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;", lookup.lookupClass().getClassLoader())); + return new ConstantCallSite(Pattern_matcher.bindTo(Pattern.compile(value))); + } +} diff --git a/test/files/run/indy-via-macro-with-dynamic-args/Test_2.scala b/test/files/run/indy-via-macro-with-dynamic-args/Test_2.scala new file mode 100644 index 0000000000..77c2b522c7 --- /dev/null +++ b/test/files/run/indy-via-macro-with-dynamic-args/Test_2.scala @@ -0,0 +1,6 @@ +object Test { + def main(args: Array[String]) { + val s = "foo!bar" + assert(Macro.matcher("foo.bar", s).matches == true) + } +} diff --git a/test/files/run/indy-via-macro-with-dynamic-args/macro_1.scala b/test/files/run/indy-via-macro-with-dynamic-args/macro_1.scala new file mode 100644 index 0000000000..cb8719a235 --- /dev/null +++ b/test/files/run/indy-via-macro-with-dynamic-args/macro_1.scala @@ -0,0 +1,33 @@ +import java.util.regex._ + +import scala.reflect.internal.SymbolTable +import scala.reflect.macros.blackbox._ +import language.experimental.macros + +object Macro { + /** + * Equivalent to Pattern.compile(pat).matcher(text), but caches the compiled regex (using invokedynamic) if + * `pat` is a literal. + */ + def matcher(pat: String, text: CharSequence): Matcher = macro Macro.impl + def impl(c: Context)(pat: c.Tree, text: c.Tree): c.Tree = { + def Indy(bootstrapMethod: c.Symbol, bootstrapArgs: List[c.universe.Literal], dynArgs: List[c.Tree]): c.Tree = { + val symtab = c.universe.asInstanceOf[SymbolTable] + import symtab._ + val paramSym = NoSymbol.newTermSymbol(TermName("x")).setInfo(typeOf[CharSequence]) + val dummySymbol = NoSymbol.newTermSymbol(TermName("matcher")).setInfo(internal.methodType(paramSym :: Nil, typeOf[java.util.regex.Matcher])) + val bootstrapArgTrees: List[Tree] = Literal(Constant(bootstrapMethod)).setType(NoType) :: bootstrapArgs.asInstanceOf[List[Tree]] + val result = ApplyDynamic(Ident(dummySymbol).setType(dummySymbol.info), bootstrapArgTrees ::: dynArgs.asInstanceOf[List[Tree]]) + result.setType(dummySymbol.info.resultType) + result.asInstanceOf[c.Tree] + } + import c.universe._ + pat match { + case l @ Literal(Constant(pat: String)) => + val boostrapSym = typeOf[test.Bootstrap].companion.member(TermName("bootstrap")) + Indy(boostrapSym, l :: Nil, text :: Nil) + case _ => + q"_root_.java.util.regex.Pattern.compile($pat).matcher($text)" + } + } +} diff --git a/test/files/run/indy-via-macro/Bootstrap.java b/test/files/run/indy-via-macro/Bootstrap.java new file mode 100644 index 0000000000..af4f5dfd4f --- /dev/null +++ b/test/files/run/indy-via-macro/Bootstrap.java @@ -0,0 +1,16 @@ +package test; + +import java.lang.invoke.*; +import java.util.regex.Pattern; + +public final class Bootstrap { + private Bootstrap() { + } + + /** Pre-compile a regex */ + public static CallSite bootstrap(MethodHandles.Lookup lookup, String invokedName, + MethodType invokedType, + String value) throws Throwable { + return new ConstantCallSite(MethodHandles.constant(Pattern.class, Pattern.compile(value))); + } +} diff --git a/test/files/run/indy-via-macro/Test_2.scala b/test/files/run/indy-via-macro/Test_2.scala new file mode 100644 index 0000000000..830947a46b --- /dev/null +++ b/test/files/run/indy-via-macro/Test_2.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]) { + assert(Macro.compilePattern("foo.bar").matcher("foo!bar").matches) + } +} \ No newline at end of file diff --git a/test/files/run/indy-via-macro/macro_1.scala b/test/files/run/indy-via-macro/macro_1.scala new file mode 100644 index 0000000000..66e319e262 --- /dev/null +++ b/test/files/run/indy-via-macro/macro_1.scala @@ -0,0 +1,32 @@ +import java.util.regex.Pattern + +import scala.reflect.internal.SymbolTable +import scala.reflect.macros.blackbox._ +import language.experimental.macros + +object Macro { + /** + * Equivalent to Pattern.compile(s), but caches the compiled regex (using invokedynamic) if + * `s` is a literal. + */ + def compilePattern(s: String): Pattern = macro Macro.impl + def impl(c: Context)(s: c.Tree): c.Tree = { + def Indy(bootstrapMethod: c.Symbol, bootstrapArgs: List[c.universe.Literal]): c.Tree = { + val symtab = c.universe.asInstanceOf[SymbolTable] + import symtab._ + val dummySymbol = NoSymbol.newTermSymbol(TermName("compile")).setInfo(NullaryMethodType(typeOf[Pattern])) + val args: List[Tree] = Literal(Constant(bootstrapMethod)).setType(NoType) :: bootstrapArgs.asInstanceOf[List[Tree]] + val result = ApplyDynamic(Ident(dummySymbol).setType(dummySymbol.info), args) + result.setType(dummySymbol.info.resultType) + result.asInstanceOf[c.Tree] + } + import c.universe._ + s match { + case l @ Literal(Constant(s: String)) => + val boostrapSym = typeOf[test.Bootstrap].companion.member(TermName("bootstrap")) + Indy(boostrapSym, l :: Nil) + case _ => + q"_root_.java.util.regex.Pattern.compile($s)" + } + } +} diff --git a/test/files/run/t7974.check b/test/files/run/t7974.check index 4eae5eb152..f649161ae9 100644 --- a/test/files/run/t7974.check +++ b/test/files/run/t7974.check @@ -1,26 +1,12 @@ - // access flags 0x9 - public static ()V - GETSTATIC scala/Symbol$.MODULE$ : Lscala/Symbol$; - LDC "Symbolic1" - INVOKEVIRTUAL scala/Symbol$.apply (Ljava/lang/String;)Lscala/Symbol; - PUTSTATIC Symbols.symbol$1 : Lscala/Symbol; - GETSTATIC scala/Symbol$.MODULE$ : Lscala/Symbol$; - LDC "Symbolic2" - INVOKEVIRTUAL scala/Symbol$.apply (Ljava/lang/String;)Lscala/Symbol; - PUTSTATIC Symbols.symbol$2 : Lscala/Symbol; - GETSTATIC scala/Symbol$.MODULE$ : Lscala/Symbol$; - LDC "Symbolic3" - INVOKEVIRTUAL scala/Symbol$.apply (Ljava/lang/String;)Lscala/Symbol; - PUTSTATIC Symbols.symbol$3 : Lscala/Symbol; - RETURN - MAXSTACK = 2 - MAXLOCALS = 0 - - // access flags 0x1 public someSymbol1()Lscala/Symbol; - GETSTATIC Symbols.symbol$1 : Lscala/Symbol; + INVOKEDYNAMIC apply()Lscala/Symbol; [ + // handle kind 0x6 : INVOKESTATIC + scala/runtime/SymbolLiteral.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "Symbolic1" + ] ARETURN MAXSTACK = 1 MAXLOCALS = 1 @@ -28,7 +14,12 @@ // access flags 0x1 public someSymbol2()Lscala/Symbol; - GETSTATIC Symbols.symbol$2 : Lscala/Symbol; + INVOKEDYNAMIC apply()Lscala/Symbol; [ + // handle kind 0x6 : INVOKESTATIC + scala/runtime/SymbolLiteral.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "Symbolic2" + ] ARETURN MAXSTACK = 1 MAXLOCALS = 1 @@ -36,7 +27,12 @@ // access flags 0x1 public sameSymbol1()Lscala/Symbol; - GETSTATIC Symbols.symbol$1 : Lscala/Symbol; + INVOKEDYNAMIC apply()Lscala/Symbol; [ + // handle kind 0x6 : INVOKESTATIC + scala/runtime/SymbolLiteral.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "Symbolic1" + ] ARETURN MAXSTACK = 1 MAXLOCALS = 1 @@ -56,7 +52,12 @@ ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 - GETSTATIC Symbols.symbol$3 : Lscala/Symbol; + INVOKEDYNAMIC apply()Lscala/Symbol; [ + // handle kind 0x6 : INVOKESTATIC + scala/runtime/SymbolLiteral.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite; + // arguments: + "Symbolic3" + ] PUTFIELD Symbols.someSymbol3 : Lscala/Symbol; RETURN MAXSTACK = 2 -- cgit v1.2.3