Skip to content

Commit 3fbfa7b

Browse files
committed
Brought back docs
1 parent e76a27e commit 3fbfa7b

File tree

1 file changed

+240
-0
lines changed

1 file changed

+240
-0
lines changed

doc/goals.md

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
There are two goals:
2+
3+
- Encode anonymous functions in the same manner as Java 8 lambdas (LambdaMetafactory) without losing the benefits of specialization.
4+
- Enable Java code to treat `scala.Function1` as an functional interface
5+
6+
## `Function1` via `LambdaMetafactory`
7+
8+
Benefits: smaller bytecode, profit from ongoing JVM optimizations
9+
for lambda elision, inlining, etc.
10+
11+
This requires a functional interface for FunctionN, which is a bit
12+
harder than it sounds in the face of specialized variants of apply,
13+
as well as compose/andThen. But, without changing our library at all,
14+
this still look possible!
15+
16+
We need to create interfaces for all specialized variants of Function1.
17+
The abstract method is the specialized apply, and all other applies must
18+
forward to this.
19+
20+
To emit smaller code, we can create a base functional interface in which
21+
the generic apply is abstract, and all of the specialized variants forward
22+
to it. This way, each specialized functional interface need only reabstract
23+
one specialized apply and redirect the unspecialized apply to it.
24+
25+
Here's how they could look:
26+
27+
- `scala.Function1`
28+
- [`runtime.F1`](https://github.com/retronym/java-8-function1/blob/master/src/main/java/scala/runtime/F1.java)
29+
- [`runtime.F1$mcII$sps`](https://github.com/retronym/java-8-function1/blob/master/src/main/java/scala/runtime/F1%24mcII%24sp.java)
30+
- ... (other specialized variants)
31+
32+
We will then need to modify the backend of scalac to emit
33+
`invokedynamic` against the `LambdaMetafactory`, passing a method
34+
handle to the function-body-in-a-method that results from `-Ydelambdafy:method`
35+
lifted method body. This behaviour would be conditional on a flag, and require
36+
that you have `F1*` on the classpath at runtime. These could be shipped in a
37+
separate JAR.
38+
39+
We could actually do all of this without needing to emit any default methods ourselves; we can simply use a code generator and javac to generate `F1*`!
40+
41+
### Optimizer
42+
43+
We will need to modify `GenBCodeOpt`'s to understand `indy` calls to spin
44+
up lambdas so it can still recognize opportunities for closure inlining.
45+
46+
### Bridges
47+
48+
Today, in Scala:
49+
50+
```
51+
scala> ((s: String) => s).getClass.getDeclaredMethods.mkString("\n")
52+
res2: String =
53+
public final java.lang.String $anonfun$1.apply(java.lang.String)
54+
public final java.lang.Object $anonfun$1.apply(java.lang.Object)
55+
```
56+
57+
In Java8, the the metafactory just spins up a class with *just* the generic
58+
signature. This is safe as the class is anonymous and only ever
59+
called through invoke-interface, so no harm done. Seems like a leaner
60+
representation.
61+
62+
So, to emit only the generic `java.lang.Object $anonfun$1.apply(java.lang.Object)` version,
63+
we inline `java.lang.String $anonfun$1.apply(java.lang.String)` into it,
64+
so that, what ordinarly would be the bridge method, has the closure's body,
65+
with a prelude to do the unboxing.
66+
67+
To call this `apply` method we emit `invoke-interface Object runtime.F1(Object)`.
68+
This is very close to what we currently do, except that we use Function1 as the target.)
69+
70+
Furthermore, by *only* creating the generic signature for anonymous functions,
71+
we would avoid the rather brutal limitation imposed by erasure for value classes, [SI-6260](https://issues.scala-lang.org/browse/SI-6260).
72+
73+
LamdaMetaFactory does have an [advanced API](http://download.java.net/jdk8/docs/api/java/lang/invoke/LambdaMetafactory.html#FLAG_BRIDGES)
74+
that allows to create additional bridges, if needed. We can also mark
75+
the closure as serializable, which should be done to be compatible
76+
with what we do today.
77+
78+
## `scala.Function1` as a functional interface
79+
80+
To do this, we would need to pull up the defender methods from s.r.F1
81+
directly to the trait interface class in the standard library. We can
82+
definitely do this when we mandate Java 8. But is it safe to do it earlier?
83+
84+
I notice that `Function.{andThen, compose}` are still generated in
85+
specialized droves, despite the attempt to mark them as @unspecialized.
86+
This looks like a bug in scalac.
87+
88+
## `invokedynamic` calls, courtesy of javac
89+
90+
[`Test.java`](https://github.com/retronym/java-8-function1/blob/master/src/main/java/scala/runtime/Test.java) contains Java 8 lambdas against `F1` and `F1$mcII$sp`.
91+
92+
In the following decompilation, you can see the invokedynamic calls:
93+
94+
```
95+
% `java_home -v 1.8`/bin/javap -v -p -classpath target/scala-2.11.0-M7/classes Test
96+
Classfile /Users/jason/code/java-8-function1/target/scala-2.11.0-M7/classes/Test.class
97+
Last modified Jan 28, 2014; size 1890 bytes
98+
MD5 checksum 7b6665961e5a4a10440571d0fcbdd2b3
99+
Compiled from "Test.java"
100+
public class Test
101+
SourceFile: "Test.java"
102+
InnerClasses:
103+
static #2; //class Test$1
104+
public static final #88= #87 of #90; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
105+
BootstrapMethods:
106+
0: #49 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
107+
Method arguments:
108+
#50 (Ljava/lang/Object;)Ljava/lang/Object;
109+
#51 invokestatic Test.lambda$main$0:(Ljava/lang/String;)Ljava/lang/String;
110+
#52 (Ljava/lang/String;)Ljava/lang/String;
111+
1: #49 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
112+
Method arguments:
113+
#50 (Ljava/lang/Object;)Ljava/lang/Object;
114+
#57 invokestatic Test.lambda$main$1:(Ljava/lang/Integer;)Ljava/lang/Integer;
115+
#58 (Ljava/lang/Integer;)Ljava/lang/Integer;
116+
2: #49 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
117+
Method arguments:
118+
#62 (I)I
119+
#63 invokestatic Test.lambda$main$2:(I)I
120+
#62 (I)I
121+
minor version: 0
122+
major version: 52
123+
flags: ACC_PUBLIC, ACC_SUPER
124+
Constant pool:
125+
...
126+
{
127+
public Test();
128+
descriptor: ()V
129+
flags: ACC_PUBLIC
130+
Code:
131+
stack=1, locals=1, args_size=1
132+
0: aload_0
133+
1: invokespecial #1 // Method java/lang/Object."<init>":()V
134+
4: return
135+
LineNumberTable:
136+
line 1: 0
137+
LocalVariableTable:
138+
Start Length Slot Name Signature
139+
0 5 0 this LTest;
140+
141+
public static void main(java.lang.String[]);
142+
descriptor: ([Ljava/lang/String;)V
143+
flags: ACC_PUBLIC, ACC_STATIC
144+
Code:
145+
stack=2, locals=4, args_size=1
146+
0: new #2 // class Test$1
147+
3: dup
148+
4: invokespecial #3 // Method Test$1."<init>":()V
149+
7: pop
150+
8: invokedynamic #4, 0 // InvokeDynamic #0:apply:()Lscala/runtime/F1;
151+
13: astore_1
152+
14: aload_1
153+
15: ldc #5 // String
154+
17: invokeinterface #6, 2 // InterfaceMethod scala/runtime/F1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
155+
22: pop
156+
23: invokedynamic #7, 0 // InvokeDynamic #1:apply:()Lscala/runtime/F1;
157+
28: astore_2
158+
29: aload_2
159+
30: iconst_0
160+
31: invokestatic #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
161+
34: invokeinterface #6, 2 // InterfaceMethod scala/runtime/F1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
162+
39: pop
163+
40: aload_2
164+
41: iconst_0
165+
42: invokeinterface #9, 2 // InterfaceMethod scala/runtime/F1.apply$mcII$sp:(I)I
166+
47: pop
167+
48: invokedynamic #10, 0 // InvokeDynamic #2:apply$mcII$sp:()Lscala/runtime/F1$mcII$sp;
168+
53: astore_3
169+
54: aload_2
170+
55: iconst_1
171+
56: invokeinterface #9, 2 // InterfaceMethod scala/runtime/F1.apply$mcII$sp:(I)I
172+
61: pop
173+
62: aload_2
174+
63: iconst_1
175+
64: invokestatic #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
176+
67: invokeinterface #6, 2 // InterfaceMethod scala/runtime/F1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
177+
72: pop
178+
73: return
179+
LineNumberTable:
180+
line 3: 0
181+
line 6: 8
182+
line 8: 14
183+
line 10: 23
184+
line 11: 29
185+
line 12: 40
186+
line 14: 48
187+
line 16: 54
188+
line 17: 62
189+
line 18: 73
190+
LocalVariableTable:
191+
Start Length Slot Name Signature
192+
0 74 0 args [Ljava/lang/String;
193+
14 60 1 f1 Lscala/runtime/F1;
194+
29 45 2 f2 Lscala/runtime/F1;
195+
54 20 3 f3 Lscala/runtime/F1$mcII$sp;
196+
LocalVariableTypeTable:
197+
Start Length Slot Name Signature
198+
14 60 1 f1 Lscala/runtime/F1<Ljava/lang/String;Ljava/lang/String;>;
199+
29 45 2 f2 Lscala/runtime/F1<Ljava/lang/Integer;Ljava/lang/Integer;>;
200+
201+
private static int lambda$main$2(int);
202+
descriptor: (I)I
203+
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
204+
Code:
205+
stack=1, locals=1, args_size=1
206+
0: iload_0
207+
1: ireturn
208+
LineNumberTable:
209+
line 14: 0
210+
LocalVariableTable:
211+
Start Length Slot Name Signature
212+
0 2 0 i I
213+
214+
private static java.lang.Integer lambda$main$1(java.lang.Integer);
215+
descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
216+
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
217+
Code:
218+
stack=1, locals=1, args_size=1
219+
0: aload_0
220+
1: areturn
221+
LineNumberTable:
222+
line 10: 0
223+
LocalVariableTable:
224+
Start Length Slot Name Signature
225+
0 2 0 i Ljava/lang/Integer;
226+
227+
private static java.lang.String lambda$main$0(java.lang.String);
228+
descriptor: (Ljava/lang/String;)Ljava/lang/String;
229+
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
230+
Code:
231+
stack=1, locals=1, args_size=1
232+
0: aload_0
233+
1: areturn
234+
LineNumberTable:
235+
line 6: 0
236+
LocalVariableTable:
237+
Start Length Slot Name Signature
238+
0 2 0 s Ljava/lang/String;
239+
}
240+
```

0 commit comments

Comments
 (0)