Skip to content

Commit a6ccc82

Browse files
committed
Rework lambda method compilation
- Compile them as just another private method in the same class This means lambdas cannot be invoked (if not through reflection) and they have access to all public, default, private, protected members without any access restrictions. - Use INDY to generate the actual lambda implementation class at runtime This allows us to change the algorithm at any point without having to touch the compiler, allowing for an easier language evolution over time. Moreover, it also allows us to put the various classes in the correct package and, potentially, as nest-mates of the owner, bypassing all access restrictions. Furthermore, it gives us flexibility for compatibility with newer Java versions. Signed-off-by: TheSilkMiner <thesilkminer@outlook.com>
1 parent 8b80696 commit a6ccc82

File tree

8 files changed

+1076
-73
lines changed

8 files changed

+1076
-73
lines changed

JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,41 @@ public int hashCode() {
116116
return builder.toString();
117117
}
118118

119+
public String mangleLambdaMethod(final String parentMethodName, final String interfaceName) {
120+
final class LambdaId {
121+
final String interfaceName;
122+
final String method;
123+
124+
LambdaId(final String interfaceName, final String method) {
125+
this.interfaceName = interfaceName;
126+
this.method = method;
127+
}
128+
129+
@Override
130+
public boolean equals(final Object o) {
131+
return this == o || o instanceof LambdaId && this.interfaceName.equals(((LambdaId) o).interfaceName) && this.method.equals(((LambdaId) o).method);
132+
}
133+
134+
@Override
135+
public int hashCode() {
136+
return 17 * (this.interfaceName.hashCode() + 31 * this.method.hashCode());
137+
}
138+
}
139+
140+
final String sanitizedMethodName;
141+
if (parentMethodName == null) {
142+
sanitizedMethodName = "$null";
143+
} else if ("<init>".equals(parentMethodName) || "<clinit>".equals(parentMethodName)) {
144+
sanitizedMethodName = "$_" + parentMethodName.substring(1, parentMethodName.length() - 1) + '_';
145+
} else {
146+
sanitizedMethodName = parentMethodName;
147+
}
148+
final String interfaceTarget = interfaceName.replace('/', '_').replace('.', '_');
149+
final LambdaId id = new LambdaId(interfaceName, sanitizedMethodName);
150+
151+
return "$lambda$" + sanitizedMethodName + '$' + interfaceTarget + '$' + this.mangleCounters.get(id);
152+
}
153+
119154
public String mangleGeneratedLambdaName(final String interfaceName) {
120155
final class LambdaId {
121156
final String target;

JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java

Lines changed: 98 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -490,95 +490,113 @@ public Void visitFunction(FunctionExpression expression) {
490490
}
491491

492492
final JavaNativeMethod methodInfo;
493-
final String className = this.javaMangler.mangleGeneratedLambdaName(interfaces[0]);
494493
{
495494
final JavaNativeMethod m = context.getFunctionalInterface(expression.original == null ? expression.type : new FunctionTypeID(expression.original));
496495
methodInfo = m.withModifiers(m.modifiers & ~JavaModifiers.ABSTRACT);
497496
}
498-
final ClassWriter lambdaCW = new JavaClassWriter(ClassWriter.COMPUTE_FRAMES);
499-
JavaClass lambdaClass = JavaClass.fromInternalName(className, JavaClass.Kind.CLASS);
500-
lambdaCW.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", interfaces);
501-
502-
JavaCompilingMethod actualCompiling = JavaMemberVisitor.compileBridgeableMethod(
503-
context,
504-
expression.position,
505-
lambdaCW,
506-
lambdaClass,
507-
methodInfo,
508-
expression.header,
509-
null
510-
);
511-
javaWriter.newObject(className);
512-
javaWriter.dup();
513-
514-
// ToDo: Is this fine or do we need the actual signature here?
515-
// To check: write a test where the ctor desc and signature would differ and make sure the program compiles/executes
516-
final String constructorDescriptorAndSignature = calcFunctionDescriptor(expression.closure);
517-
JavaNativeMethod constructor = JavaNativeMethod.getConstructor(lambdaClass, constructorDescriptorAndSignature, Opcodes.ACC_PUBLIC);
518-
JavaCompilingMethod constructorCompiling = new JavaCompilingMethod(constructor, constructorDescriptorAndSignature);
519-
final JavaWriter constructorWriter = new JavaWriter(context.logger, expression.position, lambdaCW, constructorCompiling, null);
520-
constructorWriter.start();
521-
constructorWriter.loadObject(0);
522-
constructorWriter.dup();
523-
constructorWriter.invokeSpecial(Object.class, "<init>", "()V");
524497

525-
int i = 0;
526-
for (CapturedExpression capture : expression.closure.captures) {
527-
constructorWriter.dup();
528-
Type type = context.getType(capture.type);
529-
i++;
530-
String name = this.javaMangler.mangleCapturedParameter(i, capture instanceof CapturedThisExpression);
531-
lambdaCW.visitField(Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE, name, type.getDescriptor(), null, null).visitEnd();
532-
533-
capture.accept(new JavaCapturedExpressionVisitorToPutCapturesOnTheStackBeforeCallingTheCtor(this));
498+
final JavaClass thisClass = javaWriter.method.class_;
499+
final String lambdaName = this.javaMangler.mangleLambdaMethod(javaWriter.method.compiled.name, interfaces[0]);
500+
final LambdaClosureInfo closureInfo = LambdaClosureInfo.from(context, expression.closure, thisClass);
501+
final String lambdaDescriptor = calcFunctionDescriptor(thisClass, closureInfo, expression.header);
502+
final JavaNativeMethod lambdaMethod = JavaNativeMethod.getStatic(thisClass, lambdaName, lambdaDescriptor, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC);
503+
final JavaCompilingMethod lambdaMethodCompiling = JavaMemberVisitor.compileBridgeableMethodNoSideEffect(lambdaMethod, lambdaDescriptor); // TODO("Restore signatures")
504+
final JavaWriter lambdaWriter = new JavaWriter(context.logger, expression.position, javaWriter.clazzVisitor, lambdaMethodCompiling, null);
505+
final CapturedExpressionVisitor<Void> lambdaCapturesVisitor = new JavaCapturedExpressionVisitorLocalRedirectionVisitor(lambdaWriter, expression, closureInfo);
506+
final JavaCompiledModule lambdaModule = new ShiftingJavaCompiledModule(module, closureInfo, context);
507+
final JavaExpressionVisitor lambdaExpressionVisitor = new JavaExpressionVisitor(context, lambdaModule, lambdaWriter, javaMangler, lambdaCapturesVisitor);
508+
final JavaStatementVisitor lambdaStatementVisitor = new JavaStatementVisitor(context, lambdaExpressionVisitor, javaMangler);
509+
510+
lambdaWriter.start();
511+
expression.body.accept(lambdaStatementVisitor);
512+
lambdaWriter.ret();
513+
lambdaWriter.end();
514+
515+
if (java.lang.reflect.Modifier.isStatic(javaWriter.method.compiled.modifiers) || thisClass.kind == JavaClass.Kind.EXPANSION) {
516+
javaWriter.aConstNull();
517+
} else {
518+
javaWriter.loadObject(0);
519+
}
534520

535-
constructorWriter.load(type, i);
536-
constructorWriter.putField(className, name, type.getDescriptor());
521+
if (closureInfo.isDifferentThis()) {
522+
for (final CapturedExpression capture : expression.closure.captures) {
523+
capture.accept(new JavaCapturedExpressionVisitorLoadIndyCapturesVisitor(this, true));
524+
}
525+
}
526+
for (final CapturedExpression capture : expression.closure.captures) {
527+
capture.accept(new JavaCapturedExpressionVisitorLoadIndyCapturesVisitor(this, false));
537528
}
538529

539-
constructorWriter.pop();
540-
541-
javaWriter.invokeSpecial(className, "<init>", constructorDescriptorAndSignature);
542-
543-
constructorWriter.ret();
544-
constructorWriter.end();
545-
546-
JavaWriter functionWriter = new JavaWriter(context.logger, expression.position, lambdaCW, actualCompiling, null);
547-
functionWriter.clazzVisitor.visitSource(expression.position.getFilename(), null);
548-
functionWriter.start();
549-
550-
JavaExpressionVisitor withCapturedExpressionVisitor = new JavaExpressionVisitor(
551-
context,
552-
module,
553-
functionWriter,
554-
javaMangler,
555-
new JavaCapturedExpressionVisitorToAccessCapturesInsideTheLambda(
556-
context,
557-
functionWriter,
558-
javaMangler,
559-
className,
560-
expression
561-
)
530+
// TODO("Have this in JavaWriter")
531+
javaWriter.getVisitor().visitInvokeDynamicInsn(
532+
methodInfo.name,
533+
calcIndyDescriptor(thisClass, closureInfo, interfaces[0]),
534+
new org.objectweb.asm.Handle(
535+
Opcodes.H_INVOKESTATIC,
536+
Type.getInternalName(org.openzen.zenscript.javart.factory.LambdaFactory.class),
537+
"buildLambda",
538+
Type.getMethodDescriptor(
539+
Type.getType(java.lang.invoke.CallSite.class),
540+
Type.getType(java.lang.invoke.MethodHandles.Lookup.class),
541+
Type.getType(String.class),
542+
Type.getType(java.lang.invoke.MethodType.class),
543+
Type.getType(java.lang.invoke.MethodHandle.class),
544+
Type.getType(java.lang.invoke.MethodType.class),
545+
Type.INT_TYPE,
546+
Type.getType(java.lang.invoke.MethodType.class)
547+
),
548+
false
549+
),
550+
new org.objectweb.asm.Handle(
551+
Opcodes.H_INVOKESTATIC,
552+
thisClass.internalName,
553+
lambdaName,
554+
lambdaDescriptor,
555+
false
556+
),
557+
Type.getMethodType(JavaMemberVisitor.compileBridgeableMethodNoSideEffect(methodInfo, context.getMethodDescriptor(expression.header)).compiled.descriptor),
558+
org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_GENERATE_BRIDGE | (closureInfo.isDifferentThis()? org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_DIFFERENTIATE_RECEIVER : 0),
559+
Type.getMethodType(methodInfo.descriptor)
562560
);
563-
expression.body.accept(new JavaStatementVisitor(context, withCapturedExpressionVisitor, javaMangler));
564561

565-
functionWriter.ret();
566-
functionWriter.end();
562+
return null;
563+
}
567564

568-
lambdaCW.visitEnd();
565+
private String calcFunctionDescriptor(final JavaClass thisClass, final LambdaClosureInfo closureInfo, final FunctionHeader header) {
566+
final StringJoiner joiner = new StringJoiner("");
567+
for (final CapturedExpression capture : closureInfo.closure().captures) {
568+
if (!(capture instanceof CapturedThisExpression)) {
569+
final String descriptor = context.getDescriptor(capture.type);
570+
joiner.add(descriptor);
571+
}
572+
}
569573

570-
context.register(className, lambdaCW.toByteArray());
574+
final String thisDescriptor = closureInfo.isDifferentThis()? context.getDescriptor(closureInfo.thisType()) : "";
571575

572-
return null;
576+
final StringBuilder builder = new StringBuilder(context.getMethodDescriptor(header));
577+
builder.insert(builder.indexOf("(") + 1, 'L' + thisClass.internalName + ';' + thisDescriptor);
578+
builder.insert(builder.lastIndexOf(")"), joiner);
579+
return builder.toString();
573580
}
574581

575-
private String calcFunctionDescriptor(LambdaClosure closure) {
576-
StringJoiner joiner = new StringJoiner("", "(", ")V");
577-
for (CapturedExpression capture : closure.captures) {
578-
String descriptor = context.getDescriptor(capture.type);
579-
joiner.add(descriptor);
582+
private String calcIndyDescriptor(final JavaClass thisClass, final LambdaClosureInfo closureInfo, final String targetInterface) {
583+
final StringBuilder builder = new StringBuilder("(L");
584+
builder.append(thisClass.internalName).append(';');
585+
586+
if (closureInfo.isDifferentThis()) {
587+
builder.append(context.getDescriptor(closureInfo.thisType()));
588+
}
589+
590+
final StringJoiner joiner = new StringJoiner("");
591+
for (final CapturedExpression capture : closureInfo.closure().captures) {
592+
if (!(capture instanceof CapturedThisExpression)) {
593+
final String descriptor = context.getDescriptor(capture.type);
594+
joiner.add(descriptor);
595+
}
580596
}
581-
return joiner.toString();
597+
598+
builder.append(joiner).append(")L").append(targetInterface).append(';');
599+
return builder.toString();
582600
}
583601

584602
@Override
@@ -1069,6 +1087,13 @@ public Void visitSetStaticField(SetStaticFieldExpression expression) {
10691087
}
10701088

10711089
private void visitFunctionalInterfaceWrapping(JavaFunctionInterfaceCastExpression expression) {
1090+
// TODO("Move to LambdaFactory")
1091+
// To do the above, we simply need to be able to "extract" this into a lambda form.
1092+
// In other words, if we have to convert (OurThing -> Function), as of now:
1093+
// 1. Generate a lambda method with the signature (ThisClass, ..., OurThing)
1094+
// 2. Fill the lambda method with simply $capture.invoke(...)
1095+
// 3. Invoke LambdaFactory passing the current OurThing instance as a capture; ThisClass can be simply always loaded as null as we always ignore it
1096+
// In the above, ... denotes parameters, **never** captures
10721097
final FunctionCastWrapperClass wrapper = generateFunctionCastWrapperClass(
10731098
expression.position,
10741099
(FunctionTypeID) expression.value.type,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.openzen.zenscript.javabytecode.compiler;
2+
3+
import org.openzen.zenscript.codemodel.expression.LambdaClosure;
4+
import org.openzen.zenscript.codemodel.expression.captured.CapturedExpression;
5+
import org.openzen.zenscript.codemodel.expression.captured.CapturedThisExpression;
6+
import org.openzen.zenscript.codemodel.type.TypeID;
7+
import org.openzen.zenscript.javabytecode.JavaBytecodeContext;
8+
import org.openzen.zenscript.javashared.JavaClass;
9+
10+
public final class LambdaClosureInfo {
11+
private final LambdaClosure closure;
12+
private final TypeID thisCapture;
13+
private final boolean isDifferent;
14+
15+
private LambdaClosureInfo(final LambdaClosure closure, final TypeID thisCapture, final boolean isDifferent) {
16+
this.closure = closure;
17+
this.thisCapture = thisCapture;
18+
this.isDifferent = isDifferent;
19+
}
20+
21+
static LambdaClosureInfo from(final JavaBytecodeContext context, final LambdaClosure closure, final JavaClass thisClass) {
22+
CapturedThisExpression capturedThis = null;
23+
for (final CapturedExpression capture : closure.captures) {
24+
if (capture instanceof CapturedThisExpression) {
25+
capturedThis = (CapturedThisExpression) capture;
26+
break;
27+
}
28+
}
29+
30+
final TypeID thisType = capturedThis == null? null : capturedThis.type;
31+
final boolean isDifferent = capturedThis != null && !thisClass.internalName.equals(context.getInternalName(capturedThis.type));
32+
33+
return new LambdaClosureInfo(closure, thisType, isDifferent);
34+
}
35+
36+
public LambdaClosure closure() {
37+
return this.closure;
38+
}
39+
40+
public TypeID thisType() {
41+
return this.thisCapture;
42+
}
43+
44+
public boolean isDifferentThis() {
45+
return this.isDifferent;
46+
}
47+
}

0 commit comments

Comments
 (0)