From f80409b1a73a5c9f1794c3dac325d7b1b8d82c0f Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Wed, 19 Feb 2025 18:45:08 +0100 Subject: [PATCH 01/17] Rework mangling to use $ for methods This ensures Fernflower doesn't complain about invalid method names. This change might be reverted in the future. Signed-off-by: TheSilkMiner --- .../java/org/openzen/zenscript/javabytecode/JavaMangler.java | 2 +- .../javashared/prepare/JavaPrepareClassMethodVisitor.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java index fe3edbc7..11024e60 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java @@ -53,7 +53,7 @@ public String mangleScriptName(final SourceFile sourceFile) { } public String mangleScriptBodyMethod(final int methodsAmount) { - return "script-body" + (methodsAmount == 0? "" : ('-' + Integer.toString(methodsAmount))); + return "$body" + (methodsAmount == 0? "" : ('$' + Integer.toString(methodsAmount))); } public String mangleSourceFileName(final HighLevelDefinition definition) { diff --git a/JavaShared/src/main/java/org/openzen/zenscript/javashared/prepare/JavaPrepareClassMethodVisitor.java b/JavaShared/src/main/java/org/openzen/zenscript/javashared/prepare/JavaPrepareClassMethodVisitor.java index 507c0b32..bce4f4a4 100644 --- a/JavaShared/src/main/java/org/openzen/zenscript/javashared/prepare/JavaPrepareClassMethodVisitor.java +++ b/JavaShared/src/main/java/org/openzen/zenscript/javashared/prepare/JavaPrepareClassMethodVisitor.java @@ -261,7 +261,7 @@ private void visitFunctional(FunctionalMember member, FunctionHeader header, Str method = new JavaCompilingMethod(new JavaNativeMethod( class_.compiled, getKind(member), - "implicit-constructor", + "$init$implicit", true, context.getMethodDescriptor(header), (JavaModifiers.getJavaModifiers(member.getEffectiveModifiers())), From 5aaf4f593b81f7bb3e687324d8ab1c4ba90a685a Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Mon, 24 Feb 2025 12:35:50 +0100 Subject: [PATCH 02/17] Add shared runs for tests Signed-off-by: TheSilkMiner --- .gitignore | 1 + .run/tests_gradle.run.xml | 24 ++++++++++++++++++++++++ .run/tests_intellij.run.xml | 13 +++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 .run/tests_gradle.run.xml create mode 100644 .run/tests_intellij.run.xml diff --git a/.gitignore b/.gitignore index 8ecf1a2f..1bafd868 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ out/ *.xslt *.xml +!.run/*.run.xml diff --git a/.run/tests_gradle.run.xml b/.run/tests_gradle.run.xml new file mode 100644 index 00000000..c979b2b3 --- /dev/null +++ b/.run/tests_gradle.run.xml @@ -0,0 +1,24 @@ + + + + + + + false + true + false + true + + + \ No newline at end of file diff --git a/.run/tests_intellij.run.xml b/.run/tests_intellij.run.xml new file mode 100644 index 00000000..44a3a741 --- /dev/null +++ b/.run/tests_intellij.run.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file From a0c3b1f28ac6e86d1f0af2ce76ad72456bdf60a0 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Mon, 24 Feb 2025 12:45:28 +0100 Subject: [PATCH 03/17] Introduce JavaRuntime module This module will house all runtime-executing components for the Java integration of the language. In other words, if any compiler class needs to be present at runtime for scripts or other code to be executed (NOT compiled), then it will be located into this module. Examples include reflection utilities, custom classes that might be used by Java reflection to inspect scripts, INDY factories etc. Essentially, assuming a script has already been compiled to bytecode, this module should contain the code needed to allow the script to run (excluding any additional environment-provided classes). Signed-off-by: TheSilkMiner --- JavaBytecodeCompiler/build.gradle | 1 + JavaRuntime/build.gradle | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 JavaRuntime/build.gradle diff --git a/JavaBytecodeCompiler/build.gradle b/JavaBytecodeCompiler/build.gradle index 730f4ea9..a828caa5 100644 --- a/JavaBytecodeCompiler/build.gradle +++ b/JavaBytecodeCompiler/build.gradle @@ -8,4 +8,5 @@ dependencies { api libs.asm.commons api project(':CodeModel') api project(':JavaShared') + implementation project(':JavaRuntime') } diff --git a/JavaRuntime/build.gradle b/JavaRuntime/build.gradle new file mode 100644 index 00000000..89ccc8db --- /dev/null +++ b/JavaRuntime/build.gradle @@ -0,0 +1,8 @@ +plugins { + id 'zencode-common' + id 'zencode-publish' +} + +dependencies { + api libs.asm +} From 0e37129525e060df4c93714a7983d690989dd5db Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Mon, 24 Feb 2025 12:51:52 +0100 Subject: [PATCH 04/17] 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 --- .../zenscript/javabytecode/JavaMangler.java | 35 ++ .../compiler/JavaExpressionVisitor.java | 171 +++--- .../compiler/LambdaClosureInfo.java | 47 ++ .../compiler/ShiftingJavaCompiledModule.java | 176 ++++++ ...ressionVisitorLoadIndyCapturesVisitor.java | 49 ++ ...ressionVisitorLocalRedirectionVisitor.java | 147 +++++ .../definitions/JavaMemberVisitor.java | 10 + .../javart/factory/LambdaFactory.java | 514 ++++++++++++++++++ 8 files changed, 1076 insertions(+), 73 deletions(-) create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/ShiftingJavaCompiledModule.java create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLoadIndyCapturesVisitor.java create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java create mode 100644 JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java index 11024e60..31ee06d2 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java @@ -116,6 +116,41 @@ public int hashCode() { return builder.toString(); } + public String mangleLambdaMethod(final String parentMethodName, final String interfaceName) { + final class LambdaId { + final String interfaceName; + final String method; + + LambdaId(final String interfaceName, final String method) { + this.interfaceName = interfaceName; + this.method = method; + } + + @Override + public boolean equals(final Object o) { + return this == o || o instanceof LambdaId && this.interfaceName.equals(((LambdaId) o).interfaceName) && this.method.equals(((LambdaId) o).method); + } + + @Override + public int hashCode() { + return 17 * (this.interfaceName.hashCode() + 31 * this.method.hashCode()); + } + } + + final String sanitizedMethodName; + if (parentMethodName == null) { + sanitizedMethodName = "$null"; + } else if ("".equals(parentMethodName) || "".equals(parentMethodName)) { + sanitizedMethodName = "$_" + parentMethodName.substring(1, parentMethodName.length() - 1) + '_'; + } else { + sanitizedMethodName = parentMethodName; + } + final String interfaceTarget = interfaceName.replace('/', '_').replace('.', '_'); + final LambdaId id = new LambdaId(interfaceName, sanitizedMethodName); + + return "$lambda$" + sanitizedMethodName + '$' + interfaceTarget + '$' + this.mangleCounters.get(id); + } + public String mangleGeneratedLambdaName(final String interfaceName) { final class LambdaId { final String target; diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java index 6e89710d..455a2f94 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java @@ -490,95 +490,113 @@ public Void visitFunction(FunctionExpression expression) { } final JavaNativeMethod methodInfo; - final String className = this.javaMangler.mangleGeneratedLambdaName(interfaces[0]); { final JavaNativeMethod m = context.getFunctionalInterface(expression.original == null ? expression.type : new FunctionTypeID(expression.original)); methodInfo = m.withModifiers(m.modifiers & ~JavaModifiers.ABSTRACT); } - final ClassWriter lambdaCW = new JavaClassWriter(ClassWriter.COMPUTE_FRAMES); - JavaClass lambdaClass = JavaClass.fromInternalName(className, JavaClass.Kind.CLASS); - lambdaCW.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", interfaces); - - JavaCompilingMethod actualCompiling = JavaMemberVisitor.compileBridgeableMethod( - context, - expression.position, - lambdaCW, - lambdaClass, - methodInfo, - expression.header, - null - ); - javaWriter.newObject(className); - javaWriter.dup(); - - // ToDo: Is this fine or do we need the actual signature here? - // To check: write a test where the ctor desc and signature would differ and make sure the program compiles/executes - final String constructorDescriptorAndSignature = calcFunctionDescriptor(expression.closure); - JavaNativeMethod constructor = JavaNativeMethod.getConstructor(lambdaClass, constructorDescriptorAndSignature, Opcodes.ACC_PUBLIC); - JavaCompilingMethod constructorCompiling = new JavaCompilingMethod(constructor, constructorDescriptorAndSignature); - final JavaWriter constructorWriter = new JavaWriter(context.logger, expression.position, lambdaCW, constructorCompiling, null); - constructorWriter.start(); - constructorWriter.loadObject(0); - constructorWriter.dup(); - constructorWriter.invokeSpecial(Object.class, "", "()V"); - int i = 0; - for (CapturedExpression capture : expression.closure.captures) { - constructorWriter.dup(); - Type type = context.getType(capture.type); - i++; - String name = this.javaMangler.mangleCapturedParameter(i, capture instanceof CapturedThisExpression); - lambdaCW.visitField(Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE, name, type.getDescriptor(), null, null).visitEnd(); - - capture.accept(new JavaCapturedExpressionVisitorToPutCapturesOnTheStackBeforeCallingTheCtor(this)); + final JavaClass thisClass = javaWriter.method.class_; + final String lambdaName = this.javaMangler.mangleLambdaMethod(javaWriter.method.compiled.name, interfaces[0]); + final LambdaClosureInfo closureInfo = LambdaClosureInfo.from(context, expression.closure, thisClass); + final String lambdaDescriptor = calcFunctionDescriptor(thisClass, closureInfo, expression.header); + final JavaNativeMethod lambdaMethod = JavaNativeMethod.getStatic(thisClass, lambdaName, lambdaDescriptor, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); + final JavaCompilingMethod lambdaMethodCompiling = JavaMemberVisitor.compileBridgeableMethodNoSideEffect(lambdaMethod, lambdaDescriptor); // TODO("Restore signatures") + final JavaWriter lambdaWriter = new JavaWriter(context.logger, expression.position, javaWriter.clazzVisitor, lambdaMethodCompiling, null); + final CapturedExpressionVisitor lambdaCapturesVisitor = new JavaCapturedExpressionVisitorLocalRedirectionVisitor(lambdaWriter, expression, closureInfo); + final JavaCompiledModule lambdaModule = new ShiftingJavaCompiledModule(module, closureInfo, context); + final JavaExpressionVisitor lambdaExpressionVisitor = new JavaExpressionVisitor(context, lambdaModule, lambdaWriter, javaMangler, lambdaCapturesVisitor); + final JavaStatementVisitor lambdaStatementVisitor = new JavaStatementVisitor(context, lambdaExpressionVisitor, javaMangler); + + lambdaWriter.start(); + expression.body.accept(lambdaStatementVisitor); + lambdaWriter.ret(); + lambdaWriter.end(); + + if (java.lang.reflect.Modifier.isStatic(javaWriter.method.compiled.modifiers) || thisClass.kind == JavaClass.Kind.EXPANSION) { + javaWriter.aConstNull(); + } else { + javaWriter.loadObject(0); + } - constructorWriter.load(type, i); - constructorWriter.putField(className, name, type.getDescriptor()); + if (closureInfo.isDifferentThis()) { + for (final CapturedExpression capture : expression.closure.captures) { + capture.accept(new JavaCapturedExpressionVisitorLoadIndyCapturesVisitor(this, true)); + } + } + for (final CapturedExpression capture : expression.closure.captures) { + capture.accept(new JavaCapturedExpressionVisitorLoadIndyCapturesVisitor(this, false)); } - constructorWriter.pop(); - - javaWriter.invokeSpecial(className, "", constructorDescriptorAndSignature); - - constructorWriter.ret(); - constructorWriter.end(); - - JavaWriter functionWriter = new JavaWriter(context.logger, expression.position, lambdaCW, actualCompiling, null); - functionWriter.clazzVisitor.visitSource(expression.position.getFilename(), null); - functionWriter.start(); - - JavaExpressionVisitor withCapturedExpressionVisitor = new JavaExpressionVisitor( - context, - module, - functionWriter, - javaMangler, - new JavaCapturedExpressionVisitorToAccessCapturesInsideTheLambda( - context, - functionWriter, - javaMangler, - className, - expression - ) + // TODO("Have this in JavaWriter") + javaWriter.getVisitor().visitInvokeDynamicInsn( + methodInfo.name, + calcIndyDescriptor(thisClass, closureInfo, interfaces[0]), + new org.objectweb.asm.Handle( + Opcodes.H_INVOKESTATIC, + Type.getInternalName(org.openzen.zenscript.javart.factory.LambdaFactory.class), + "buildLambda", + Type.getMethodDescriptor( + Type.getType(java.lang.invoke.CallSite.class), + Type.getType(java.lang.invoke.MethodHandles.Lookup.class), + Type.getType(String.class), + Type.getType(java.lang.invoke.MethodType.class), + Type.getType(java.lang.invoke.MethodHandle.class), + Type.getType(java.lang.invoke.MethodType.class), + Type.INT_TYPE, + Type.getType(java.lang.invoke.MethodType.class) + ), + false + ), + new org.objectweb.asm.Handle( + Opcodes.H_INVOKESTATIC, + thisClass.internalName, + lambdaName, + lambdaDescriptor, + false + ), + Type.getMethodType(JavaMemberVisitor.compileBridgeableMethodNoSideEffect(methodInfo, context.getMethodDescriptor(expression.header)).compiled.descriptor), + org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_GENERATE_BRIDGE | (closureInfo.isDifferentThis()? org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_DIFFERENTIATE_RECEIVER : 0), + Type.getMethodType(methodInfo.descriptor) ); - expression.body.accept(new JavaStatementVisitor(context, withCapturedExpressionVisitor, javaMangler)); - functionWriter.ret(); - functionWriter.end(); + return null; + } - lambdaCW.visitEnd(); + private String calcFunctionDescriptor(final JavaClass thisClass, final LambdaClosureInfo closureInfo, final FunctionHeader header) { + final StringJoiner joiner = new StringJoiner(""); + for (final CapturedExpression capture : closureInfo.closure().captures) { + if (!(capture instanceof CapturedThisExpression)) { + final String descriptor = context.getDescriptor(capture.type); + joiner.add(descriptor); + } + } - context.register(className, lambdaCW.toByteArray()); + final String thisDescriptor = closureInfo.isDifferentThis()? context.getDescriptor(closureInfo.thisType()) : ""; - return null; + final StringBuilder builder = new StringBuilder(context.getMethodDescriptor(header)); + builder.insert(builder.indexOf("(") + 1, 'L' + thisClass.internalName + ';' + thisDescriptor); + builder.insert(builder.lastIndexOf(")"), joiner); + return builder.toString(); } - private String calcFunctionDescriptor(LambdaClosure closure) { - StringJoiner joiner = new StringJoiner("", "(", ")V"); - for (CapturedExpression capture : closure.captures) { - String descriptor = context.getDescriptor(capture.type); - joiner.add(descriptor); + private String calcIndyDescriptor(final JavaClass thisClass, final LambdaClosureInfo closureInfo, final String targetInterface) { + final StringBuilder builder = new StringBuilder("(L"); + builder.append(thisClass.internalName).append(';'); + + if (closureInfo.isDifferentThis()) { + builder.append(context.getDescriptor(closureInfo.thisType())); + } + + final StringJoiner joiner = new StringJoiner(""); + for (final CapturedExpression capture : closureInfo.closure().captures) { + if (!(capture instanceof CapturedThisExpression)) { + final String descriptor = context.getDescriptor(capture.type); + joiner.add(descriptor); + } } - return joiner.toString(); + + builder.append(joiner).append(")L").append(targetInterface).append(';'); + return builder.toString(); } @Override @@ -1069,6 +1087,13 @@ public Void visitSetStaticField(SetStaticFieldExpression expression) { } private void visitFunctionalInterfaceWrapping(JavaFunctionInterfaceCastExpression expression) { + // TODO("Move to LambdaFactory") + // To do the above, we simply need to be able to "extract" this into a lambda form. + // In other words, if we have to convert (OurThing -> Function), as of now: + // 1. Generate a lambda method with the signature (ThisClass, ..., OurThing) + // 2. Fill the lambda method with simply $capture.invoke(...) + // 3. Invoke LambdaFactory passing the current OurThing instance as a capture; ThisClass can be simply always loaded as null as we always ignore it + // In the above, ... denotes parameters, **never** captures final FunctionCastWrapperClass wrapper = generateFunctionCastWrapperClass( expression.position, (FunctionTypeID) expression.value.type, diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java new file mode 100644 index 00000000..5fbd818b --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java @@ -0,0 +1,47 @@ +package org.openzen.zenscript.javabytecode.compiler; + +import org.openzen.zenscript.codemodel.expression.LambdaClosure; +import org.openzen.zenscript.codemodel.expression.captured.CapturedExpression; +import org.openzen.zenscript.codemodel.expression.captured.CapturedThisExpression; +import org.openzen.zenscript.codemodel.type.TypeID; +import org.openzen.zenscript.javabytecode.JavaBytecodeContext; +import org.openzen.zenscript.javashared.JavaClass; + +public final class LambdaClosureInfo { + private final LambdaClosure closure; + private final TypeID thisCapture; + private final boolean isDifferent; + + private LambdaClosureInfo(final LambdaClosure closure, final TypeID thisCapture, final boolean isDifferent) { + this.closure = closure; + this.thisCapture = thisCapture; + this.isDifferent = isDifferent; + } + + static LambdaClosureInfo from(final JavaBytecodeContext context, final LambdaClosure closure, final JavaClass thisClass) { + CapturedThisExpression capturedThis = null; + for (final CapturedExpression capture : closure.captures) { + if (capture instanceof CapturedThisExpression) { + capturedThis = (CapturedThisExpression) capture; + break; + } + } + + final TypeID thisType = capturedThis == null? null : capturedThis.type; + final boolean isDifferent = capturedThis != null && !thisClass.internalName.equals(context.getInternalName(capturedThis.type)); + + return new LambdaClosureInfo(closure, thisType, isDifferent); + } + + public LambdaClosure closure() { + return this.closure; + } + + public TypeID thisType() { + return this.thisCapture; + } + + public boolean isDifferentThis() { + return this.isDifferent; + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/ShiftingJavaCompiledModule.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/ShiftingJavaCompiledModule.java new file mode 100644 index 00000000..427f003c --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/ShiftingJavaCompiledModule.java @@ -0,0 +1,176 @@ +package org.openzen.zenscript.javabytecode.compiler; + +import org.openzen.zenscript.codemodel.FunctionParameter; +import org.openzen.zenscript.codemodel.definition.VariantDefinition; +import org.openzen.zenscript.codemodel.generic.TypeParameter; +import org.openzen.zenscript.codemodel.identifiers.*; +import org.openzen.zenscript.codemodel.member.ImplementationMember; +import org.openzen.zenscript.codemodel.type.TypeID; +import org.openzen.zenscript.javabytecode.JavaBytecodeContext; +import org.openzen.zenscript.javashared.*; + +import java.util.List; + +final class ShiftingJavaCompiledModule extends JavaCompiledModule { + private final JavaCompiledModule delegate; + private final LambdaClosureInfo closureInfo; + private final JavaBytecodeContext context; + + ShiftingJavaCompiledModule(final JavaCompiledModule module, final LambdaClosureInfo closureInfo, final JavaBytecodeContext context) { + super(module.module, module.scriptParameters); + this.delegate = module; + this.closureInfo = closureInfo; + this.context = context; + } + + @Override + public void addExpansion(final ExpansionSymbol expansion, final JavaClass cls) { + this.delegate.addExpansion(expansion, cls); + } + + @Override + public String generateMappings() { + return this.delegate.generateMappings(); + } + + @Override + public void setClassInfo(final DefinitionSymbol definition, final JavaClass cls) { + this.delegate.setClassInfo(definition, cls); + } + + @Override + public void setExpansionClassInfo(final DefinitionSymbol definition, final JavaClass cls) { + this.delegate.setExpansionClassInfo(definition, cls); + } + + @Override + public JavaClass getClassInfo(final DefinitionSymbol definition) { + return this.delegate.getClassInfo(definition); + } + + @Override + public JavaClass getExpansionClassInfo(final DefinitionSymbol definition) { + return this.delegate.getExpansionClassInfo(definition); + } + + @Override + public JavaClass optClassInfo(final TypeSymbol definition) { + return this.delegate.optClassInfo(definition); + } + + @Override + public boolean hasClassInfo(final TypeSymbol definition) { + return this.delegate.hasClassInfo(definition); + } + + @Override + public void setNativeClassInfo(final TypeSymbol definition, final JavaNativeClass cls) { + this.delegate.setNativeClassInfo(definition, cls); + } + + @Override + public JavaNativeClass getNativeClassInfo(final TypeSymbol definition) { + return this.delegate.getNativeClassInfo(definition); + } + + @Override + public void setVariantOption(final VariantDefinition.Option option, final JavaVariantOption value) { + this.delegate.setVariantOption(option, value); + } + + @Override + public JavaVariantOption getVariantOption(final VariantDefinition.Option option) { + return this.delegate.getVariantOption(option); + } + + @Override + public JavaEnumMapper getEnumMapper() { + return this.delegate.getEnumMapper(); + } + + @Override + public void setImplementationInfo(final ImplementationMember member, final JavaImplementation implementation) { + this.delegate.setImplementationInfo(member, implementation); + } + + @Override + public JavaImplementation getImplementationInfo(final ImplementationMember member) { + return this.delegate.getImplementationInfo(member); + } + + @Override + public void setFieldInfo(final FieldSymbol member, final JavaField field) { + this.delegate.setFieldInfo(member, field); + } + + @Override + public void setFieldInfo(final MethodSymbol getterOrSetter, final JavaField field) { + this.delegate.setFieldInfo(getterOrSetter, field); + } + + @Override + public JavaField optFieldInfo(final FieldSymbol member) { + return this.delegate.optFieldInfo(member); + } + + @Override + public JavaField optFieldInfo(final MethodSymbol getterOrSetter) { + return this.delegate.optFieldInfo(getterOrSetter); + } + + @Override + public JavaField getFieldInfo(final FieldSymbol member) { + return this.delegate.getFieldInfo(member); + } + + @Override + public void setMethodInfo(final MethodSymbol member, final JavaMethod method) { + this.delegate.setMethodInfo(member, method); + } + + @Override + public JavaMethod optMethodInfo(final MethodSymbol member) { + return this.delegate.optMethodInfo(member); + } + + @Override + public JavaMethod getMethodInfo(final MethodSymbol member) { + return this.delegate.getMethodInfo(member); + } + + @Override + public void setTypeParameterInfo(final TypeParameter parameter, final JavaTypeParameterInfo info) { + this.delegate.setTypeParameterInfo(parameter, info); + } + + @Override + public JavaTypeParameterInfo getTypeParameterInfo(final TypeParameter parameter) { + return this.delegate.getTypeParameterInfo(parameter); + } + + @Override + public void setParameterInfo(final FunctionParameter parameter, final JavaParameterInfo info) { + this.delegate.setParameterInfo(parameter, info); + } + + @Override + public JavaParameterInfo getParameterInfo(final FunctionParameter parameter) { + final JavaParameterInfo info = this.delegate.getParameterInfo(parameter); + if (this.closureInfo.isDifferentThis()) { + final TypeID type = this.closureInfo.thisType(); + final int size = this.context.getType(type).getSize(); + return new JavaParameterInfo(info.index + size, info.typeDescriptor); + } + return info; + } + + @Override + public void addAllFrom(final JavaCompiledModule compiled) { + this.delegate.addAllFrom(compiled); + } + + @Override + public List getExpansions() { + return this.delegate.getExpansions(); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLoadIndyCapturesVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLoadIndyCapturesVisitor.java new file mode 100644 index 00000000..10c0c96c --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLoadIndyCapturesVisitor.java @@ -0,0 +1,49 @@ +package org.openzen.zenscript.javabytecode.compiler.capturing; + +import org.openzen.zenscript.codemodel.expression.ExpressionVisitor; +import org.openzen.zenscript.codemodel.expression.GetFunctionParameterExpression; +import org.openzen.zenscript.codemodel.expression.GetLocalVariableExpression; +import org.openzen.zenscript.codemodel.expression.ThisExpression; +import org.openzen.zenscript.codemodel.expression.captured.*; + +public class JavaCapturedExpressionVisitorLoadIndyCapturesVisitor implements CapturedExpressionVisitor { + private final ExpressionVisitor visitor; + private final boolean onlyThis; + + public JavaCapturedExpressionVisitorLoadIndyCapturesVisitor(final ExpressionVisitor visitor, final boolean onlyThis) { + this.visitor = visitor; + this.onlyThis = onlyThis; + } + + @Override + public Void visitCapturedThis(final CapturedThisExpression expression) { + if (!this.onlyThis) { + return null; + } + return new ThisExpression(expression.position, expression.type).accept(this.visitor); + } + + @Override + public Void visitCapturedParameter(final CapturedParameterExpression expression) { + if (this.onlyThis) { + return null; + } + return new GetFunctionParameterExpression(expression.position, expression.parameter).accept(this.visitor); + } + + @Override + public Void visitCapturedLocal(final CapturedLocalVariableExpression expression) { + if (this.onlyThis) { + return null; + } + return new GetLocalVariableExpression(expression.position, expression.variable).accept(this.visitor); + } + + @Override + public Void visitRecaptured(final CapturedClosureExpression expression) { + if (this.onlyThis) { + return null; + } + return expression.value.accept(this.visitor); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java new file mode 100644 index 00000000..fdd949de --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java @@ -0,0 +1,147 @@ +package org.openzen.zenscript.javabytecode.compiler.capturing; + +import org.objectweb.asm.Type; +import org.openzen.zenscript.codemodel.FunctionParameter; +import org.openzen.zenscript.codemodel.expression.FunctionExpression; +import org.openzen.zenscript.codemodel.expression.GetLocalVariableExpression; +import org.openzen.zenscript.codemodel.expression.captured.*; +import org.openzen.zenscript.codemodel.type.BasicTypeID; +import org.openzen.zenscript.codemodel.type.TypeID; +import org.openzen.zenscript.javabytecode.compiler.JavaWriter; +import org.openzen.zenscript.javabytecode.compiler.LambdaClosureInfo; + +import java.util.function.Predicate; + +public class JavaCapturedExpressionVisitorLocalRedirectionVisitor implements CapturedExpressionVisitor { + private static final class MemberData { + private final int position; + private final Type type; + + MemberData(final int position, final Type type) { + this.position = position; + this.type = type; + } + + void load(final JavaWriter writer) { + writer.load(this.type, this.position); + } + } + + private final FunctionExpression functionExpression; + private final JavaWriter javaWriter; + private final LambdaClosureInfo closureInfo; + + public JavaCapturedExpressionVisitorLocalRedirectionVisitor(final JavaWriter javaWriter, final FunctionExpression functionExpression, final LambdaClosureInfo closureInfo) { + this.javaWriter = javaWriter; + this.functionExpression = functionExpression; + this.closureInfo = closureInfo; + } + + @Override + public Void visitCapturedThis(final CapturedThisExpression expression) { + return this.loadByMemberData( + this.closureInfo.isDifferentThis()? new MemberData(1, this.getTypeFrom(this.closureInfo.thisType())) : new MemberData(0, Type.getType(Object.class)) + ); + } + + @Override + public Void visitCapturedParameter(CapturedParameterExpression expression) { + return this.loadByMemberData(this.calculateMemberData(expression, this.functionExpression)); + } + + @Override + public Void visitCapturedLocal(CapturedLocalVariableExpression expression) { + return this.loadByMemberData(this.calculateMemberData(new GetLocalVariableExpression(expression.position, expression.variable), this.functionExpression)); + } + + @Override + public Void visitRecaptured(CapturedClosureExpression expression) { + return this.loadByMemberData(this.findIndex(expression, this.functionExpression)); + } + + private Void loadByMemberData(final MemberData memberData) { + memberData.load(this.javaWriter); + return null; + } + + private MemberData findIndex(final CapturedExpression capturedExpression, final FunctionExpression expression) { + return this.calculateMemberData( + expression, + capturedExpression.type, + capturedExpression::equals + ); + } + + private MemberData calculateMemberData(final GetLocalVariableExpression localVariableExpression, final FunctionExpression expression) { + return this.calculateMemberData( + expression, + localVariableExpression.type, + capture -> { + if (capture instanceof CapturedLocalVariableExpression) { + return ((CapturedLocalVariableExpression) capture).variable == localVariableExpression.variable; + } else if (capture instanceof CapturedClosureExpression) { + final CapturedExpression value = ((CapturedClosureExpression) capture).value; + return value instanceof CapturedLocalVariableExpression && ((CapturedLocalVariableExpression) value).variable == localVariableExpression.variable; + } + return false; + }); + } + + private MemberData calculateMemberData(final CapturedParameterExpression functionParameterExpression, final FunctionExpression expression) { + return this.calculateMemberData( + expression, + functionParameterExpression.parameter.type, + capture -> capture instanceof CapturedParameterExpression && ((CapturedParameterExpression) capture).parameter == functionParameterExpression.parameter + ); + } + + private MemberData calculateMemberData(final FunctionExpression expression, final TypeID varType, final Predicate predicate) { + final Type type = this.getTypeFrom(varType); + int h = this.findFirstValidCaptureIndex(expression); + for (final CapturedExpression capture : expression.closure.captures) { + if (predicate.test(capture)) { + return new MemberData(h, type); + } + h += type.getSize(); + } + throw new IllegalStateException(expression.position.toString() + ": Captured Statement error"); + } + + private int findFirstValidCaptureIndex(final FunctionExpression expression) { + int h = 1; + + if (this.closureInfo.isDifferentThis()) { + h += this.getTypeFrom(this.closureInfo.thisType()).getSize(); + } + + for (final FunctionParameter parameter : expression.header.parameters) { + h += this.getTypeFrom(parameter.type).getSize(); + } + return h; + } + + private Type getTypeFrom(final TypeID type) { + if (type instanceof BasicTypeID) { + switch ((BasicTypeID) type) { + case VOID: return Type.VOID_TYPE; + case BOOL: return Type.BOOLEAN_TYPE; + case BYTE: return Type.BYTE_TYPE; + case SBYTE: return Type.BYTE_TYPE; // TODO() + case SHORT: return Type.SHORT_TYPE; + case USHORT: return Type.SHORT_TYPE; // TODO() + case INT: return Type.INT_TYPE; + case UINT: return Type.INT_TYPE; // TODO() + case LONG: return Type.LONG_TYPE; + case ULONG: return Type.LONG_TYPE; // TODO() + case USIZE: return Type.INT_TYPE; + case FLOAT: return Type.FLOAT_TYPE; + case DOUBLE: return Type.DOUBLE_TYPE; + case CHAR: return Type.CHAR_TYPE; + } + } + + // We don't care about the actual class contained in the type here, we only care that Java treats it as an + // object, so we simply grab a random Java class + return Type.getType(Object.class); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/definitions/JavaMemberVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/definitions/JavaMemberVisitor.java index 5606a35d..cb2b3485 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/definitions/JavaMemberVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/definitions/JavaMemberVisitor.java @@ -432,6 +432,16 @@ public void end() { clinitStatementVisitor.end(); } + public static JavaCompilingMethod compileBridgeableMethodNoSideEffect(final JavaNativeMethod overriddenMethodInfo, final String implementationDescriptor) { + // TODO("Restore signatures") + if (!Objects.equals(overriddenMethodInfo.descriptor, implementationDescriptor)) { + final JavaNativeMethod actualMethod = overriddenMethodInfo.createBridge(implementationDescriptor); + return new JavaCompilingMethod(actualMethod, null); + } else { + return new JavaCompilingMethod(overriddenMethodInfo, null); + } + } + public static JavaCompilingMethod compileBridgeableMethod( JavaBytecodeContext context, CodePosition position, diff --git a/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java b/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java new file mode 100644 index 00000000..2c66885c --- /dev/null +++ b/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java @@ -0,0 +1,514 @@ +package org.openzen.zenscript.javart.factory; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.invoke.*; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public final class LambdaFactory { + private static final class LambdaClassLoader extends ClassLoader { + static { + ClassLoader.registerAsParallelCapable(); + } + + private final String name; + private final byte[] data; + + private LambdaClassLoader(final String name, final byte[] data, final ClassLoader parent) { + super(parent); + this.name = name; + this.data = data; + } + + static ClassLoader spinLoader(final String name, final byte[] data, final MethodHandles.Lookup lookup) { + return new LambdaClassLoader(name, data, lookup.lookupClass().getClassLoader()); + } + + @Override + protected Class findClass(final String name) throws ClassNotFoundException { + if (this.name.equals(name)) { + return this.defineClass(name, this.data, 0, this.data.length); + } + return super.findClass(name); + } + + + } + + private static final class LambdaCounters { + private static final ConcurrentMap COUNTERS = new ConcurrentHashMap<>(); + + static int get(final Class owner, final Class funType) { + final String key = owner.getName() + '/' + funType.getName(); + // TODO("Verify concurrency") + return COUNTERS.compute(key, (k, old) -> old == null? 0 : (old + 1)); + } + } + + private static final class LambdaFlags { + private final boolean generateBridge; + private final boolean differentiateReceiver; + + LambdaFlags(final int flags) { + this.generateBridge = (flags & FLAG_GENERATE_BRIDGE) != 0; + this.differentiateReceiver = (flags & FLAG_DIFFERENTIATE_RECEIVER) != 0; + } + + boolean generateBridge() { + return this.generateBridge; + } + + boolean differentiateReceiver() { + return this.differentiateReceiver; + } + } + + public interface LambdaMarker {} + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface LambdaForwarder {} + + private static final String HANDLE_FIELD_NAME = "$handle"; + private static final String THIS_FIELD_NAME = "$this"; + private static final String RECEIVER_FIELD_NAME = "$receiver"; + private static final String CAPTURE_FIELD_NAME_PREFIX = "$capture$"; + + private static final String CONSTRUCTOR_NAME = ""; + private static final String FACTORY_METHOD_NAME = "$init"; + + public static final int FLAG_GENERATE_BRIDGE = 0b01; + public static final int FLAG_DIFFERENTIATE_RECEIVER = 0b10; + private static final int FLAG_ALL_FLAGS = FLAG_GENERATE_BRIDGE | FLAG_DIFFERENTIATE_RECEIVER; + + private LambdaFactory() {} + + // Assume a lambda such as (foo) => foo + bar, where bar is a captured variable and this is implicitly captured, + // and that the target "Functional Interface" is meant to be an on-the-fly Function1 with the following + // definition: + // interface Function1 { R invoke(T t); } + // + // The parameter types + // will be filled according to the following table: + // targetMethodName -> invoke + // callSiteSignature -> (ThisType,BarType)Function1 + // lambdaMethod -> handle to the method that "foo + bar" has been compiled to in its declaring class + // expected signature: (ThisType,FooType,BarType)FooBarType + // interfaceSignature -> (FooType)FooBarType + // packedFlags -> a set of information representing special behavior of this method + // FLAG_GENERATE_BRIDGE => generate a method bridge if necessary + // FLAG_DIFFERENTIATE_RECEIVER => differentiates the 'this' context from the 'this' receiver; used for expansions + // bridgeInterfaceSignature -> (Object)Object --> ignored if FLAG_GENERATE_BRIDGE unset, otherwise used only if it differs from interfaceSignature + public static CallSite buildLambda( + final MethodHandles.Lookup callerLookup, + final String targetMethodName, + final MethodType callSiteSignature, + final MethodHandle lambdaMethod, + final MethodType interfaceSignature, + final int packedFlags, + final MethodType bridgeInterfaceSignature + ) { + final LambdaFlags flags = checkArgumentValidity(callerLookup, targetMethodName, callSiteSignature, lambdaMethod, interfaceSignature, packedFlags, bridgeInterfaceSignature); + final Class lambdaImplementation = generateInterfaceImplementation(callerLookup, targetMethodName, callSiteSignature, lambdaMethod, interfaceSignature, flags, bridgeInterfaceSignature); + final MethodHandle handle = constructClassHandle(callerLookup, lambdaImplementation, lambdaMethod, callSiteSignature); + return bindCallSite(handle); + } + + private static LambdaFlags checkArgumentValidity( + final MethodHandles.Lookup callerLookup, + final String targetMethodName, + final MethodType callSiteSignature, + final MethodHandle lambdaMethod, + final MethodType interfaceSignature, + final int packedFlags, + final MethodType bridgeInterfaceSignature + ) { + Objects.requireNonNull(callerLookup, "Caller lookup is required to generate lambda class"); + Objects.requireNonNull(targetMethodName, "Name of the method that should be implemented must be provided"); + Objects.requireNonNull(callSiteSignature, "Signature of the expected CallSite object must be provided"); + Objects.requireNonNull(lambdaMethod, "Handle to the lambda method is missing"); + Objects.requireNonNull(interfaceSignature, "Signature of method in the target interface must be provided"); + Objects.requireNonNull(bridgeInterfaceSignature, "Signature of bridge method must be provided"); + + if ((callerLookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) { + throw new IllegalArgumentException("Caller lookup does not have private access in the target class: cannot be used to invoke lambdas"); + } + + if ((packedFlags & ~FLAG_ALL_FLAGS) != 0) { + throw new IllegalArgumentException("Unknown flag set: " + packedFlags); + } + + final Class lookupClass = callerLookup.lookupClass(); + final MethodType lambdaMethodType = lambdaMethod.type(); + final LambdaFlags flags = new LambdaFlags(packedFlags); + + final Class targetInterface = callSiteSignature.returnType(); + final Class[] callSiteParams = callSiteSignature.parameterArray(); + + final Class lambdaReturn = lambdaMethodType.returnType(); + final Class[] lambdaArguments = lambdaMethodType.parameterArray(); + + final Class interfaceReturn = interfaceSignature.returnType(); + final Class[] interfaceArguments = interfaceSignature.parameterArray(); + + final int lastReceiver = flags.differentiateReceiver()? 2 : 1; + + if (!targetInterface.isInterface()) { + throw new IllegalArgumentException("Return type of the call site was " + targetInterface.getName() + ", which is not an interface"); + } + + if (callSiteParams.length == 0) { + throw new IllegalArgumentException("Call site needs to have at least one param, namely the receiver of the lambda"); + } + + if (callSiteParams[0] != lookupClass) { + throw new IllegalArgumentException("Argument 0 of call site must be the same class as the lookup class"); + } + + if (!interfaceReturn.isAssignableFrom(lambdaReturn)) { + throw new IllegalArgumentException("Lambda does not return a subtype of the interface return type"); + } + + final int totalArguments = interfaceArguments.length + callSiteParams.length; + + if (lambdaArguments.length != totalArguments) { + throw new IllegalArgumentException("Lambda argument quantity is different than expected: " + lambdaArguments.length + " instead of " + totalArguments); + } + + // TODO("Relax validation requirements for assignment compatibilities between the various signatures") + for (int i = callSiteParams.length - 1, d = lambdaArguments.length - callSiteParams.length; i > lastReceiver; --i) { + final Class callSiteTarget = callSiteParams[i]; + final Class lambdaTarget = lambdaArguments[d + i]; + if (callSiteTarget != lambdaTarget) { + throw new IllegalArgumentException("Captured argument does not match expected type between call site and lambda"); + } + } + + for (int i = 0, s = interfaceArguments.length; i < s; ++i) { + final Class interfaceTarget = interfaceArguments[i]; + final Class lambdaTarget = lambdaArguments[i + lastReceiver]; + if (interfaceTarget != lambdaTarget) { + throw new IllegalArgumentException("Interface argument does not match expected type between interface and lambda"); + } + } + + if (flags.generateBridge()) { + final Class[] bridgeInterfaceArguments = bridgeInterfaceSignature.parameterArray(); + + if (bridgeInterfaceArguments.length != interfaceArguments.length) { + throw new IllegalArgumentException("Interface bridge has a different amount of arguments than the interface"); + } + } + + return flags; + } + + private static Class generateInterfaceImplementation( + final MethodHandles.Lookup callerLookup, + final String targetMethodName, + final MethodType callSiteSignature, + final MethodHandle lambdaMethod, + final MethodType interfaceSignature, + final LambdaFlags flags, + final MethodType bridgeInterfaceSignature + ) { + final String className = obtainLambdaClassName(callerLookup, callSiteSignature); + final String binaryName = className.replace('/', '.'); + final byte[] classData = generateInterfaceClassData(targetMethodName, callSiteSignature, lambdaMethod, interfaceSignature, flags, bridgeInterfaceSignature, className); + + // Java 8 forces us to use a custom classloader for this; ideally we'd be leveraging hidden classes + final ClassLoader lambdaLoader = LambdaClassLoader.spinLoader(binaryName, classData, callerLookup); + try { + return Class.forName(binaryName, false, lambdaLoader); + } catch (final ClassNotFoundException e) { + throw new IllegalStateException("An error occurred while trying to load lambda class", e); + } + } + + private static String obtainLambdaClassName(final MethodHandles.Lookup callerLookup, final MethodType callSiteSignature) { + final Class owner = callerLookup.lookupClass(); + final Class interfaceType = callSiteSignature.returnType(); + + // $$ is required because such a construct is impossible to obtain via normal means in source, so no conflict + // can arise + return String.format( + "%s$$Lambda$%s$%s", + owner.getName().replace('.', '/'), + interfaceType.getName().replace('.', '_'), + LambdaCounters.get(owner, interfaceType) + ); + } + + private static byte[] generateInterfaceClassData( + final String targetMethodName, + final MethodType callSiteSignature, + final MethodHandle lambdaMethod, + final MethodType interfaceSignature, + final LambdaFlags flags, + final MethodType bridgeInterfaceSignature, + final String className + ) { + final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); // No if jumps means we don't need frames + generateInterfaceClass(writer, targetMethodName, callSiteSignature, lambdaMethod, interfaceSignature, flags, bridgeInterfaceSignature, className); + return writer.toByteArray(); + } + + private static void generateInterfaceClass( + final ClassWriter writer, + final String targetMethodName, + final MethodType callSiteSignature, + final MethodHandle lambdaMethod, + final MethodType interfaceSignature, + final LambdaFlags flags, + final MethodType bridgeInterfaceSignature, + final String className + ) { + writer.visit( + 52, // Java 8 + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER | Opcodes.ACC_SYNTHETIC, + className, + null, + Type.getInternalName(Object.class), + new String[] { Type.getInternalName(callSiteSignature.returnType()), Type.getInternalName(LambdaMarker.class) } + ); + writer.visitSource("dynamically generated by ZenCode", null); + + generateInterfaceClassFields(writer, callSiteSignature, flags); + generateInterfaceClassConstructor(writer, callSiteSignature, flags, className); + generateInterfaceClassFactory(writer, callSiteSignature, className); + generateInterfaceClassLambdaInvoker(writer, targetMethodName, callSiteSignature, interfaceSignature, lambdaMethod, flags, className); + generateInterfaceClassBridge(writer, targetMethodName, bridgeInterfaceSignature, interfaceSignature, flags, className); + + writer.visitEnd(); + } + + private static void generateInterfaceClassFields(final ClassWriter classWriter, final MethodType callSiteSignature, final LambdaFlags flags) { + classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, HANDLE_FIELD_NAME, Type.getDescriptor(MethodHandle.class), null, null); + + final Class ownerField = callSiteSignature.parameterType(0); + classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, THIS_FIELD_NAME, Type.getDescriptor(ownerField), null, null); + + if (flags.differentiateReceiver()) { + final Class receiverField = callSiteSignature.parameterType(1); + classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, RECEIVER_FIELD_NAME, Type.getDescriptor(receiverField), null, null); + } + + final int captureBeginIndex = flags.differentiateReceiver()? 2 : 1; + for (int i = captureBeginIndex, s = callSiteSignature.parameterCount(); i < s; ++i) { + final Class fieldType = callSiteSignature.parameterType(i); + final int captureIndex = i - captureBeginIndex; + classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, CAPTURE_FIELD_NAME_PREFIX + captureIndex, Type.getDescriptor(fieldType), null, null); + } + } + + private static void generateInterfaceClassConstructor(final ClassWriter classWriter, final MethodType callSiteSignature, final LambdaFlags flags, final String className) { + final Type[] constructorArguments = new Type[callSiteSignature.parameterCount() + 1]; + constructorArguments[0] = Type.getType(MethodHandle.class); + for (int i = 0, s = callSiteSignature.parameterCount(); i < s; ++i) { + constructorArguments[i + 1] = Type.getType(callSiteSignature.parameterType(i)); + } + + final String constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, constructorArguments); + final MethodVisitor constructorWriter = classWriter.visitMethod(Opcodes.ACC_PRIVATE, CONSTRUCTOR_NAME, constructorDescriptor, null, null); + constructorWriter.visitCode(); + + constructorWriter.visitVarInsn(Opcodes.ALOAD, 0); + constructorWriter.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), CONSTRUCTOR_NAME, "()V", false); + + int localIndex = 1; + for (int i = 0, s = constructorArguments.length; i < s; ++i) { + final Type fieldType = constructorArguments[i]; + final String fieldName; + if (i == 0) { + fieldName = HANDLE_FIELD_NAME; + } else if (i == 1) { + fieldName = THIS_FIELD_NAME; + } else if (flags.differentiateReceiver() && i == 2) { + fieldName = RECEIVER_FIELD_NAME; + } else { + fieldName = (CAPTURE_FIELD_NAME_PREFIX + (i - (flags.differentiateReceiver()? 3 : 2))); + } + + constructorWriter.visitVarInsn(Opcodes.ALOAD, 0); + constructorWriter.visitVarInsn(fieldType.getOpcode(Opcodes.ILOAD), localIndex); + constructorWriter.visitFieldInsn(Opcodes.PUTFIELD, className, fieldName, fieldType.getDescriptor()); + + localIndex += fieldType.getSize(); + } + + constructorWriter.visitInsn(Opcodes.RETURN); + constructorWriter.visitMaxs(3, localIndex); + constructorWriter.visitEnd(); + } + + private static void generateInterfaceClassFactory(final ClassWriter classWriter, final MethodType callSiteSignature, final String className) { + final Type[] factoryArguments = new Type[callSiteSignature.parameterCount() + 1]; + factoryArguments[0] = Type.getType(MethodHandle.class); + for (int i = 0, s = callSiteSignature.parameterCount(); i < s; ++i) { + factoryArguments[i + 1] = Type.getType(callSiteSignature.parameterType(i)); + } + + final String factoryDescriptor = Type.getMethodDescriptor(Type.getType(callSiteSignature.returnType()), factoryArguments); + final String constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, factoryArguments); + final MethodVisitor factoryWriter = classWriter.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, FACTORY_METHOD_NAME, factoryDescriptor, null, null); + factoryWriter.visitCode(); + + factoryWriter.visitTypeInsn(Opcodes.NEW, className); + factoryWriter.visitInsn(Opcodes.DUP); + + int localIndex = 0; + for (final Type fieldType : factoryArguments) { + factoryWriter.visitVarInsn(fieldType.getOpcode(Opcodes.ILOAD), localIndex); + localIndex += fieldType.getSize(); + } + + factoryWriter.visitMethodInsn(Opcodes.INVOKESPECIAL, className, CONSTRUCTOR_NAME, constructorDescriptor, false); + factoryWriter.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(callSiteSignature.returnType())); + factoryWriter.visitInsn(Opcodes.ARETURN); + factoryWriter.visitMaxs(localIndex, localIndex); + factoryWriter.visitEnd(); + } + + private static void generateInterfaceClassLambdaInvoker( + final ClassWriter classWriter, + final String targetMethodName, + final MethodType callSiteSignature, + final MethodType interfaceSignature, + final MethodHandle lambdaMethod, + final LambdaFlags flags, + final String className + ) { + final String descriptor = interfaceSignature.toMethodDescriptorString(); + final MethodVisitor writer = classWriter.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, targetMethodName, descriptor, null, null); + writer.visitAnnotation(Type.getDescriptor(LambdaForwarder.class), true); // For inspection via reflection in case someone needs to + + writer.visitCode(); + + writer.visitVarInsn(Opcodes.ALOAD, 0); + writer.visitFieldInsn(Opcodes.GETFIELD, className, HANDLE_FIELD_NAME, Type.getDescriptor(MethodHandle.class)); + + final String thisTypeDescriptor = Type.getDescriptor(callSiteSignature.parameterType(0)); + writer.visitVarInsn(Opcodes.ALOAD, 0); + writer.visitFieldInsn(Opcodes.GETFIELD, className, THIS_FIELD_NAME, thisTypeDescriptor); + + if (flags.differentiateReceiver()) { + final String receiverTypeDescriptor = Type.getDescriptor(callSiteSignature.parameterType(1)); + writer.visitVarInsn(Opcodes.ALOAD, 0); + writer.visitFieldInsn(Opcodes.GETFIELD, className, RECEIVER_FIELD_NAME, receiverTypeDescriptor); + } + + int localIndex = 1; + for (int i = 0, s = interfaceSignature.parameterCount(); i < s; ++i) { + final Type type = Type.getType(interfaceSignature.parameterType(i)); + writer.visitVarInsn(type.getOpcode(Opcodes.ILOAD), localIndex); + localIndex += type.getSize(); + } + + final int firstCaptureIndex = flags.differentiateReceiver()? 2 : 1; + for (int i = firstCaptureIndex, s = callSiteSignature.parameterCount(); i < s; ++i) { + final String fieldDescriptor = Type.getDescriptor(callSiteSignature.parameterType(i)); + final int captureIndex = i - firstCaptureIndex; + writer.visitVarInsn(Opcodes.ALOAD, 0); + writer.visitFieldInsn(Opcodes.GETFIELD, className, CAPTURE_FIELD_NAME_PREFIX + captureIndex, fieldDescriptor); + } + + writer.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(MethodHandle.class), "invoke", lambdaMethod.type().toMethodDescriptorString(), false); + + final Class returnType = interfaceSignature.returnType(); + if (returnType == void.class) { + writer.visitInsn(Opcodes.RETURN); + } else { + writer.visitInsn(Type.getType(returnType).getOpcode(Opcodes.IRETURN)); + } + + writer.visitMaxs(lambdaMethod.type().parameterCount() + 1, interfaceSignature.parameterCount()); + writer.visitEnd(); + } + + private static void generateInterfaceClassBridge( + final ClassWriter classWriter, + final String targetMethodName, + final MethodType bridgeInterfaceSignature, + final MethodType interfaceSignature, + final LambdaFlags flags, + final String className + ) { + if (!flags.generateBridge()) { + return; + } + + if (bridgeInterfaceSignature.equals(interfaceSignature)) { + return; + } + + final String descriptor = bridgeInterfaceSignature.toMethodDescriptorString(); + final MethodVisitor writer = classWriter.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_BRIDGE, targetMethodName, descriptor, null, null); + writer.visitCode(); + + writer.visitVarInsn(Opcodes.ALOAD, 0); + + int localIndex = 1; + for (int i = 0, s = bridgeInterfaceSignature.parameterCount(); i < s; ++i) { + final Type bridgeType = Type.getType(bridgeInterfaceSignature.parameterType(i)); + final Type targetType = Type.getType(interfaceSignature.parameterType(i)); + + if (bridgeType.getSort() == targetType.getSort()) { + writer.visitVarInsn(bridgeType.getOpcode(Opcodes.ILOAD), localIndex); + + if (bridgeType.getSort() == Type.OBJECT) { + writer.visitTypeInsn(Opcodes.CHECKCAST, targetType.getInternalName()); + } + } else { + throw new UnsupportedOperationException("Not yet implemented"); + } + + localIndex += bridgeType.getSize(); + } + + writer.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, targetMethodName, interfaceSignature.toMethodDescriptorString(), false); + + final Type bridgeReturnType = Type.getType(bridgeInterfaceSignature.returnType()); + final Type targetReturnType = Type.getType(interfaceSignature.returnType()); + + if (bridgeReturnType.getSort() == targetReturnType.getSort()) { + if (targetReturnType.getSort() == Type.OBJECT) { + writer.visitTypeInsn(Opcodes.CHECKCAST, bridgeReturnType.getInternalName()); + } + + writer.visitInsn(targetReturnType.getOpcode(Opcodes.IRETURN)); + } else { + throw new UnsupportedOperationException("Not yet implemented"); + } + + writer.visitMaxs(localIndex, localIndex); + writer.visitEnd(); + } + + private static MethodHandle constructClassHandle( + final MethodHandles.Lookup lookup, + final Class clazz, + final MethodHandle lambdaMethod, + final MethodType callSite + ) { + final MethodType factoryType = callSite.insertParameterTypes(0, MethodHandle.class); + try { + final MethodHandle type = lookup.findStatic(clazz, FACTORY_METHOD_NAME, factoryType); + return MethodHandles.insertArguments(type, 0, lambdaMethod); + } catch (final NoSuchMethodException | IllegalAccessException e) { + throw new IllegalStateException("Unable to find factory method for lambda", e); + } + } + + private static CallSite bindCallSite(final MethodHandle methodHandle) { + return new ConstantCallSite(methodHandle); + } +} From 6df0696c6b8523f48e726ed56cde665476c586a7 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Thu, 27 Feb 2025 16:52:48 +0100 Subject: [PATCH 05/17] Massively simplify this capturing in lambdas Signed-off-by: TheSilkMiner --- .../compiler/JavaExpressionVisitor.java | 48 ++--- .../compiler/LambdaClosureInfo.java | 23 ++- .../compiler/ShiftingJavaCompiledModule.java | 176 ------------------ ...ressionVisitorLocalRedirectionVisitor.java | 13 +- ...turesOnIndyCapturedExpressionVisitor.java} | 21 +-- ...adThisOnIndyCapturedExpressionVisitor.java | 34 ++++ .../javart/factory/LambdaFactory.java | 61 ++---- 7 files changed, 95 insertions(+), 281 deletions(-) delete mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/ShiftingJavaCompiledModule.java rename JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/{JavaCapturedExpressionVisitorLoadIndyCapturesVisitor.java => JavaLoadCapturesOnIndyCapturedExpressionVisitor.java} (63%) create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java index 455a2f94..72404e92 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java @@ -498,13 +498,12 @@ public Void visitFunction(FunctionExpression expression) { final JavaClass thisClass = javaWriter.method.class_; final String lambdaName = this.javaMangler.mangleLambdaMethod(javaWriter.method.compiled.name, interfaces[0]); final LambdaClosureInfo closureInfo = LambdaClosureInfo.from(context, expression.closure, thisClass); - final String lambdaDescriptor = calcFunctionDescriptor(thisClass, closureInfo, expression.header); + final String lambdaDescriptor = calcFunctionDescriptor(closureInfo, expression.header); final JavaNativeMethod lambdaMethod = JavaNativeMethod.getStatic(thisClass, lambdaName, lambdaDescriptor, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); final JavaCompilingMethod lambdaMethodCompiling = JavaMemberVisitor.compileBridgeableMethodNoSideEffect(lambdaMethod, lambdaDescriptor); // TODO("Restore signatures") final JavaWriter lambdaWriter = new JavaWriter(context.logger, expression.position, javaWriter.clazzVisitor, lambdaMethodCompiling, null); final CapturedExpressionVisitor lambdaCapturesVisitor = new JavaCapturedExpressionVisitorLocalRedirectionVisitor(lambdaWriter, expression, closureInfo); - final JavaCompiledModule lambdaModule = new ShiftingJavaCompiledModule(module, closureInfo, context); - final JavaExpressionVisitor lambdaExpressionVisitor = new JavaExpressionVisitor(context, lambdaModule, lambdaWriter, javaMangler, lambdaCapturesVisitor); + final JavaExpressionVisitor lambdaExpressionVisitor = new JavaExpressionVisitor(context, module, lambdaWriter, javaMangler, lambdaCapturesVisitor); final JavaStatementVisitor lambdaStatementVisitor = new JavaStatementVisitor(context, lambdaExpressionVisitor, javaMangler); lambdaWriter.start(); @@ -512,25 +511,28 @@ public Void visitFunction(FunctionExpression expression) { lambdaWriter.ret(); lambdaWriter.end(); - if (java.lang.reflect.Modifier.isStatic(javaWriter.method.compiled.modifiers) || thisClass.kind == JavaClass.Kind.EXPANSION) { - javaWriter.aConstNull(); - } else { - javaWriter.loadObject(0); + final JavaLoadThisOnIndyCapturedExpressionVisitor thisVisitor = new JavaLoadThisOnIndyCapturedExpressionVisitor(this); + final JavaLoadCapturesOnIndyCapturedExpressionVisitor othersVisitor = new JavaLoadCapturesOnIndyCapturedExpressionVisitor(this); + + boolean hasLoadedThis = false; + for (final CapturedExpression capture : expression.closure.captures) { + // Note: we want this to be |= to ensure NO short-circuiting behavior + hasLoadedThis |= capture.accept(thisVisitor); } - if (closureInfo.isDifferentThis()) { - for (final CapturedExpression capture : expression.closure.captures) { - capture.accept(new JavaCapturedExpressionVisitorLoadIndyCapturesVisitor(this, true)); - } + // If no this was loaded, then we don't care about its value; just load null + if (!hasLoadedThis) { + javaWriter.aConstNull(); } + for (final CapturedExpression capture : expression.closure.captures) { - capture.accept(new JavaCapturedExpressionVisitorLoadIndyCapturesVisitor(this, false)); + capture.accept(othersVisitor); } // TODO("Have this in JavaWriter") javaWriter.getVisitor().visitInvokeDynamicInsn( methodInfo.name, - calcIndyDescriptor(thisClass, closureInfo, interfaces[0]), + calcIndyDescriptor(closureInfo, interfaces[0]), new org.objectweb.asm.Handle( Opcodes.H_INVOKESTATIC, Type.getInternalName(org.openzen.zenscript.javart.factory.LambdaFactory.class), @@ -555,14 +557,14 @@ public Void visitFunction(FunctionExpression expression) { false ), Type.getMethodType(JavaMemberVisitor.compileBridgeableMethodNoSideEffect(methodInfo, context.getMethodDescriptor(expression.header)).compiled.descriptor), - org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_GENERATE_BRIDGE | (closureInfo.isDifferentThis()? org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_DIFFERENTIATE_RECEIVER : 0), + org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_GENERATE_BRIDGE, Type.getMethodType(methodInfo.descriptor) ); return null; } - private String calcFunctionDescriptor(final JavaClass thisClass, final LambdaClosureInfo closureInfo, final FunctionHeader header) { + private String calcFunctionDescriptor(final LambdaClosureInfo closureInfo, final FunctionHeader header) { final StringJoiner joiner = new StringJoiner(""); for (final CapturedExpression capture : closureInfo.closure().captures) { if (!(capture instanceof CapturedThisExpression)) { @@ -571,19 +573,21 @@ private String calcFunctionDescriptor(final JavaClass thisClass, final LambdaClo } } - final String thisDescriptor = closureInfo.isDifferentThis()? context.getDescriptor(closureInfo.thisType()) : ""; - + // TODO("Remove null check: the method should always return non-null") + final String thisType = closureInfo.thisType() == null? "Ljava/lang/Void;" : context.getDescriptor(closureInfo.thisType()); final StringBuilder builder = new StringBuilder(context.getMethodDescriptor(header)); - builder.insert(builder.indexOf("(") + 1, 'L' + thisClass.internalName + ';' + thisDescriptor); + builder.insert(builder.indexOf("(") + 1, thisType); builder.insert(builder.lastIndexOf(")"), joiner); return builder.toString(); } - private String calcIndyDescriptor(final JavaClass thisClass, final LambdaClosureInfo closureInfo, final String targetInterface) { - final StringBuilder builder = new StringBuilder("(L"); - builder.append(thisClass.internalName).append(';'); + private String calcIndyDescriptor(final LambdaClosureInfo closureInfo, final String targetInterface) { + final StringBuilder builder = new StringBuilder("("); - if (closureInfo.isDifferentThis()) { + // TODO("Remove null check as this method should never return null") + if (closureInfo.thisType() == null) { + builder.append("Ljava/lang/Void;"); + } else { builder.append(context.getDescriptor(closureInfo.thisType())); } diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java index 5fbd818b..8c50303a 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java @@ -3,6 +3,8 @@ import org.openzen.zenscript.codemodel.expression.LambdaClosure; import org.openzen.zenscript.codemodel.expression.captured.CapturedExpression; import org.openzen.zenscript.codemodel.expression.captured.CapturedThisExpression; +import org.openzen.zenscript.codemodel.type.BasicTypeID; +import org.openzen.zenscript.codemodel.type.OptionalTypeID; import org.openzen.zenscript.codemodel.type.TypeID; import org.openzen.zenscript.javabytecode.JavaBytecodeContext; import org.openzen.zenscript.javashared.JavaClass; @@ -10,12 +12,10 @@ public final class LambdaClosureInfo { private final LambdaClosure closure; private final TypeID thisCapture; - private final boolean isDifferent; - private LambdaClosureInfo(final LambdaClosure closure, final TypeID thisCapture, final boolean isDifferent) { + private LambdaClosureInfo(final LambdaClosure closure, final TypeID thisCapture) { this.closure = closure; this.thisCapture = thisCapture; - this.isDifferent = isDifferent; } static LambdaClosureInfo from(final JavaBytecodeContext context, final LambdaClosure closure, final JavaClass thisClass) { @@ -27,10 +27,17 @@ static LambdaClosureInfo from(final JavaBytecodeContext context, final LambdaClo } } - final TypeID thisType = capturedThis == null? null : capturedThis.type; - final boolean isDifferent = capturedThis != null && !thisClass.internalName.equals(context.getInternalName(capturedThis.type)); + final TypeID thisType = computeCapturedThisType(capturedThis); + return new LambdaClosureInfo(closure, thisType); + } + + private static TypeID computeCapturedThisType(final CapturedThisExpression expression) { + if (expression != null) { + return expression.type; + } - return new LambdaClosureInfo(closure, thisType, isDifferent); + // TODO("Make this default to void") + return null; } public LambdaClosure closure() { @@ -40,8 +47,4 @@ public LambdaClosure closure() { public TypeID thisType() { return this.thisCapture; } - - public boolean isDifferentThis() { - return this.isDifferent; - } } diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/ShiftingJavaCompiledModule.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/ShiftingJavaCompiledModule.java deleted file mode 100644 index 427f003c..00000000 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/ShiftingJavaCompiledModule.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.openzen.zenscript.javabytecode.compiler; - -import org.openzen.zenscript.codemodel.FunctionParameter; -import org.openzen.zenscript.codemodel.definition.VariantDefinition; -import org.openzen.zenscript.codemodel.generic.TypeParameter; -import org.openzen.zenscript.codemodel.identifiers.*; -import org.openzen.zenscript.codemodel.member.ImplementationMember; -import org.openzen.zenscript.codemodel.type.TypeID; -import org.openzen.zenscript.javabytecode.JavaBytecodeContext; -import org.openzen.zenscript.javashared.*; - -import java.util.List; - -final class ShiftingJavaCompiledModule extends JavaCompiledModule { - private final JavaCompiledModule delegate; - private final LambdaClosureInfo closureInfo; - private final JavaBytecodeContext context; - - ShiftingJavaCompiledModule(final JavaCompiledModule module, final LambdaClosureInfo closureInfo, final JavaBytecodeContext context) { - super(module.module, module.scriptParameters); - this.delegate = module; - this.closureInfo = closureInfo; - this.context = context; - } - - @Override - public void addExpansion(final ExpansionSymbol expansion, final JavaClass cls) { - this.delegate.addExpansion(expansion, cls); - } - - @Override - public String generateMappings() { - return this.delegate.generateMappings(); - } - - @Override - public void setClassInfo(final DefinitionSymbol definition, final JavaClass cls) { - this.delegate.setClassInfo(definition, cls); - } - - @Override - public void setExpansionClassInfo(final DefinitionSymbol definition, final JavaClass cls) { - this.delegate.setExpansionClassInfo(definition, cls); - } - - @Override - public JavaClass getClassInfo(final DefinitionSymbol definition) { - return this.delegate.getClassInfo(definition); - } - - @Override - public JavaClass getExpansionClassInfo(final DefinitionSymbol definition) { - return this.delegate.getExpansionClassInfo(definition); - } - - @Override - public JavaClass optClassInfo(final TypeSymbol definition) { - return this.delegate.optClassInfo(definition); - } - - @Override - public boolean hasClassInfo(final TypeSymbol definition) { - return this.delegate.hasClassInfo(definition); - } - - @Override - public void setNativeClassInfo(final TypeSymbol definition, final JavaNativeClass cls) { - this.delegate.setNativeClassInfo(definition, cls); - } - - @Override - public JavaNativeClass getNativeClassInfo(final TypeSymbol definition) { - return this.delegate.getNativeClassInfo(definition); - } - - @Override - public void setVariantOption(final VariantDefinition.Option option, final JavaVariantOption value) { - this.delegate.setVariantOption(option, value); - } - - @Override - public JavaVariantOption getVariantOption(final VariantDefinition.Option option) { - return this.delegate.getVariantOption(option); - } - - @Override - public JavaEnumMapper getEnumMapper() { - return this.delegate.getEnumMapper(); - } - - @Override - public void setImplementationInfo(final ImplementationMember member, final JavaImplementation implementation) { - this.delegate.setImplementationInfo(member, implementation); - } - - @Override - public JavaImplementation getImplementationInfo(final ImplementationMember member) { - return this.delegate.getImplementationInfo(member); - } - - @Override - public void setFieldInfo(final FieldSymbol member, final JavaField field) { - this.delegate.setFieldInfo(member, field); - } - - @Override - public void setFieldInfo(final MethodSymbol getterOrSetter, final JavaField field) { - this.delegate.setFieldInfo(getterOrSetter, field); - } - - @Override - public JavaField optFieldInfo(final FieldSymbol member) { - return this.delegate.optFieldInfo(member); - } - - @Override - public JavaField optFieldInfo(final MethodSymbol getterOrSetter) { - return this.delegate.optFieldInfo(getterOrSetter); - } - - @Override - public JavaField getFieldInfo(final FieldSymbol member) { - return this.delegate.getFieldInfo(member); - } - - @Override - public void setMethodInfo(final MethodSymbol member, final JavaMethod method) { - this.delegate.setMethodInfo(member, method); - } - - @Override - public JavaMethod optMethodInfo(final MethodSymbol member) { - return this.delegate.optMethodInfo(member); - } - - @Override - public JavaMethod getMethodInfo(final MethodSymbol member) { - return this.delegate.getMethodInfo(member); - } - - @Override - public void setTypeParameterInfo(final TypeParameter parameter, final JavaTypeParameterInfo info) { - this.delegate.setTypeParameterInfo(parameter, info); - } - - @Override - public JavaTypeParameterInfo getTypeParameterInfo(final TypeParameter parameter) { - return this.delegate.getTypeParameterInfo(parameter); - } - - @Override - public void setParameterInfo(final FunctionParameter parameter, final JavaParameterInfo info) { - this.delegate.setParameterInfo(parameter, info); - } - - @Override - public JavaParameterInfo getParameterInfo(final FunctionParameter parameter) { - final JavaParameterInfo info = this.delegate.getParameterInfo(parameter); - if (this.closureInfo.isDifferentThis()) { - final TypeID type = this.closureInfo.thisType(); - final int size = this.context.getType(type).getSize(); - return new JavaParameterInfo(info.index + size, info.typeDescriptor); - } - return info; - } - - @Override - public void addAllFrom(final JavaCompiledModule compiled) { - this.delegate.addAllFrom(compiled); - } - - @Override - public List getExpansions() { - return this.delegate.getExpansions(); - } -} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java index fdd949de..efa79c28 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java @@ -39,9 +39,9 @@ public JavaCapturedExpressionVisitorLocalRedirectionVisitor(final JavaWriter jav @Override public Void visitCapturedThis(final CapturedThisExpression expression) { - return this.loadByMemberData( - this.closureInfo.isDifferentThis()? new MemberData(1, this.getTypeFrom(this.closureInfo.thisType())) : new MemberData(0, Type.getType(Object.class)) - ); + // TODO("Remove null-check as this method should never return null") + final Type type = this.closureInfo.thisType() == null? Type.getType(Void.class) : this.getTypeFrom(this.closureInfo.thisType()); + return this.loadByMemberData(new MemberData(0, type)); } @Override @@ -109,11 +109,6 @@ private MemberData calculateMemberData(final FunctionExpression expression, fina private int findFirstValidCaptureIndex(final FunctionExpression expression) { int h = 1; - - if (this.closureInfo.isDifferentThis()) { - h += this.getTypeFrom(this.closureInfo.thisType()).getSize(); - } - for (final FunctionParameter parameter : expression.header.parameters) { h += this.getTypeFrom(parameter.type).getSize(); } @@ -142,6 +137,6 @@ private Type getTypeFrom(final TypeID type) { // We don't care about the actual class contained in the type here, we only care that Java treats it as an // object, so we simply grab a random Java class - return Type.getType(Object.class); + return Type.getType(Void.class); } } diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLoadIndyCapturesVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java similarity index 63% rename from JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLoadIndyCapturesVisitor.java rename to JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java index 10c0c96c..b7ea92c2 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLoadIndyCapturesVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java @@ -3,47 +3,32 @@ import org.openzen.zenscript.codemodel.expression.ExpressionVisitor; import org.openzen.zenscript.codemodel.expression.GetFunctionParameterExpression; import org.openzen.zenscript.codemodel.expression.GetLocalVariableExpression; -import org.openzen.zenscript.codemodel.expression.ThisExpression; import org.openzen.zenscript.codemodel.expression.captured.*; -public class JavaCapturedExpressionVisitorLoadIndyCapturesVisitor implements CapturedExpressionVisitor { +public class JavaLoadCapturesOnIndyCapturedExpressionVisitor implements CapturedExpressionVisitor { private final ExpressionVisitor visitor; - private final boolean onlyThis; - public JavaCapturedExpressionVisitorLoadIndyCapturesVisitor(final ExpressionVisitor visitor, final boolean onlyThis) { + public JavaLoadCapturesOnIndyCapturedExpressionVisitor(final ExpressionVisitor visitor) { this.visitor = visitor; - this.onlyThis = onlyThis; } @Override public Void visitCapturedThis(final CapturedThisExpression expression) { - if (!this.onlyThis) { - return null; - } - return new ThisExpression(expression.position, expression.type).accept(this.visitor); + return null; } @Override public Void visitCapturedParameter(final CapturedParameterExpression expression) { - if (this.onlyThis) { - return null; - } return new GetFunctionParameterExpression(expression.position, expression.parameter).accept(this.visitor); } @Override public Void visitCapturedLocal(final CapturedLocalVariableExpression expression) { - if (this.onlyThis) { - return null; - } return new GetLocalVariableExpression(expression.position, expression.variable).accept(this.visitor); } @Override public Void visitRecaptured(final CapturedClosureExpression expression) { - if (this.onlyThis) { - return null; - } return expression.value.accept(this.visitor); } } diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java new file mode 100644 index 00000000..6244eb54 --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java @@ -0,0 +1,34 @@ +package org.openzen.zenscript.javabytecode.compiler.capturing; + +import org.openzen.zenscript.codemodel.expression.ExpressionVisitor; +import org.openzen.zenscript.codemodel.expression.ThisExpression; +import org.openzen.zenscript.codemodel.expression.captured.*; + +public final class JavaLoadThisOnIndyCapturedExpressionVisitor implements CapturedExpressionVisitor { + private final ExpressionVisitor visitor; + + public JavaLoadThisOnIndyCapturedExpressionVisitor(final ExpressionVisitor visitor) { + this.visitor = visitor; + } + + @Override + public Boolean visitCapturedThis(final CapturedThisExpression expression) { + new ThisExpression(expression.position, expression.type).accept(this.visitor); + return true; + } + + @Override + public Boolean visitCapturedParameter(final CapturedParameterExpression expression) { + return false; + } + + @Override + public Boolean visitCapturedLocal(final CapturedLocalVariableExpression expression) { + return false; + } + + @Override + public Boolean visitRecaptured(final CapturedClosureExpression expression) { + return false; + } +} diff --git a/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java b/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java index 2c66885c..614c2e44 100644 --- a/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java +++ b/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java @@ -56,20 +56,14 @@ static int get(final Class owner, final Class funType) { private static final class LambdaFlags { private final boolean generateBridge; - private final boolean differentiateReceiver; LambdaFlags(final int flags) { this.generateBridge = (flags & FLAG_GENERATE_BRIDGE) != 0; - this.differentiateReceiver = (flags & FLAG_DIFFERENTIATE_RECEIVER) != 0; } boolean generateBridge() { return this.generateBridge; } - - boolean differentiateReceiver() { - return this.differentiateReceiver; - } } public interface LambdaMarker {} @@ -80,15 +74,13 @@ public interface LambdaMarker {} private static final String HANDLE_FIELD_NAME = "$handle"; private static final String THIS_FIELD_NAME = "$this"; - private static final String RECEIVER_FIELD_NAME = "$receiver"; private static final String CAPTURE_FIELD_NAME_PREFIX = "$capture$"; private static final String CONSTRUCTOR_NAME = ""; private static final String FACTORY_METHOD_NAME = "$init"; - public static final int FLAG_GENERATE_BRIDGE = 0b01; - public static final int FLAG_DIFFERENTIATE_RECEIVER = 0b10; - private static final int FLAG_ALL_FLAGS = FLAG_GENERATE_BRIDGE | FLAG_DIFFERENTIATE_RECEIVER; + public static final int FLAG_GENERATE_BRIDGE = 0b1; + private static final int FLAG_ALL_FLAGS = FLAG_GENERATE_BRIDGE; private LambdaFactory() {} @@ -106,8 +98,8 @@ private LambdaFactory() {} // interfaceSignature -> (FooType)FooBarType // packedFlags -> a set of information representing special behavior of this method // FLAG_GENERATE_BRIDGE => generate a method bridge if necessary - // FLAG_DIFFERENTIATE_RECEIVER => differentiates the 'this' context from the 'this' receiver; used for expansions // bridgeInterfaceSignature -> (Object)Object --> ignored if FLAG_GENERATE_BRIDGE unset, otherwise used only if it differs from interfaceSignature + @SuppressWarnings("unused") // Used via Indy public static CallSite buildLambda( final MethodHandles.Lookup callerLookup, final String targetMethodName, @@ -147,7 +139,6 @@ private static LambdaFlags checkArgumentValidity( throw new IllegalArgumentException("Unknown flag set: " + packedFlags); } - final Class lookupClass = callerLookup.lookupClass(); final MethodType lambdaMethodType = lambdaMethod.type(); final LambdaFlags flags = new LambdaFlags(packedFlags); @@ -160,8 +151,6 @@ private static LambdaFlags checkArgumentValidity( final Class interfaceReturn = interfaceSignature.returnType(); final Class[] interfaceArguments = interfaceSignature.parameterArray(); - final int lastReceiver = flags.differentiateReceiver()? 2 : 1; - if (!targetInterface.isInterface()) { throw new IllegalArgumentException("Return type of the call site was " + targetInterface.getName() + ", which is not an interface"); } @@ -170,10 +159,6 @@ private static LambdaFlags checkArgumentValidity( throw new IllegalArgumentException("Call site needs to have at least one param, namely the receiver of the lambda"); } - if (callSiteParams[0] != lookupClass) { - throw new IllegalArgumentException("Argument 0 of call site must be the same class as the lookup class"); - } - if (!interfaceReturn.isAssignableFrom(lambdaReturn)) { throw new IllegalArgumentException("Lambda does not return a subtype of the interface return type"); } @@ -185,7 +170,7 @@ private static LambdaFlags checkArgumentValidity( } // TODO("Relax validation requirements for assignment compatibilities between the various signatures") - for (int i = callSiteParams.length - 1, d = lambdaArguments.length - callSiteParams.length; i > lastReceiver; --i) { + for (int i = callSiteParams.length - 1, d = lambdaArguments.length - callSiteParams.length; i > 1; --i) { final Class callSiteTarget = callSiteParams[i]; final Class lambdaTarget = lambdaArguments[d + i]; if (callSiteTarget != lambdaTarget) { @@ -195,7 +180,7 @@ private static LambdaFlags checkArgumentValidity( for (int i = 0, s = interfaceArguments.length; i < s; ++i) { final Class interfaceTarget = interfaceArguments[i]; - final Class lambdaTarget = lambdaArguments[i + lastReceiver]; + final Class lambdaTarget = lambdaArguments[i + 1]; if (interfaceTarget != lambdaTarget) { throw new IllegalArgumentException("Interface argument does not match expected type between interface and lambda"); } @@ -282,35 +267,29 @@ private static void generateInterfaceClass( ); writer.visitSource("dynamically generated by ZenCode", null); - generateInterfaceClassFields(writer, callSiteSignature, flags); - generateInterfaceClassConstructor(writer, callSiteSignature, flags, className); + generateInterfaceClassFields(writer, callSiteSignature); + generateInterfaceClassConstructor(writer, callSiteSignature, className); generateInterfaceClassFactory(writer, callSiteSignature, className); - generateInterfaceClassLambdaInvoker(writer, targetMethodName, callSiteSignature, interfaceSignature, lambdaMethod, flags, className); + generateInterfaceClassLambdaInvoker(writer, targetMethodName, callSiteSignature, interfaceSignature, lambdaMethod, className); generateInterfaceClassBridge(writer, targetMethodName, bridgeInterfaceSignature, interfaceSignature, flags, className); writer.visitEnd(); } - private static void generateInterfaceClassFields(final ClassWriter classWriter, final MethodType callSiteSignature, final LambdaFlags flags) { + private static void generateInterfaceClassFields(final ClassWriter classWriter, final MethodType callSiteSignature) { classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, HANDLE_FIELD_NAME, Type.getDescriptor(MethodHandle.class), null, null); final Class ownerField = callSiteSignature.parameterType(0); classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, THIS_FIELD_NAME, Type.getDescriptor(ownerField), null, null); - if (flags.differentiateReceiver()) { - final Class receiverField = callSiteSignature.parameterType(1); - classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, RECEIVER_FIELD_NAME, Type.getDescriptor(receiverField), null, null); - } - - final int captureBeginIndex = flags.differentiateReceiver()? 2 : 1; - for (int i = captureBeginIndex, s = callSiteSignature.parameterCount(); i < s; ++i) { + for (int i = 1, s = callSiteSignature.parameterCount(); i < s; ++i) { final Class fieldType = callSiteSignature.parameterType(i); - final int captureIndex = i - captureBeginIndex; + final int captureIndex = i - 1; classWriter.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, CAPTURE_FIELD_NAME_PREFIX + captureIndex, Type.getDescriptor(fieldType), null, null); } } - private static void generateInterfaceClassConstructor(final ClassWriter classWriter, final MethodType callSiteSignature, final LambdaFlags flags, final String className) { + private static void generateInterfaceClassConstructor(final ClassWriter classWriter, final MethodType callSiteSignature, final String className) { final Type[] constructorArguments = new Type[callSiteSignature.parameterCount() + 1]; constructorArguments[0] = Type.getType(MethodHandle.class); for (int i = 0, s = callSiteSignature.parameterCount(); i < s; ++i) { @@ -332,10 +311,8 @@ private static void generateInterfaceClassConstructor(final ClassWriter classWri fieldName = HANDLE_FIELD_NAME; } else if (i == 1) { fieldName = THIS_FIELD_NAME; - } else if (flags.differentiateReceiver() && i == 2) { - fieldName = RECEIVER_FIELD_NAME; } else { - fieldName = (CAPTURE_FIELD_NAME_PREFIX + (i - (flags.differentiateReceiver()? 3 : 2))); + fieldName = (CAPTURE_FIELD_NAME_PREFIX + (i - 2)); } constructorWriter.visitVarInsn(Opcodes.ALOAD, 0); @@ -384,7 +361,6 @@ private static void generateInterfaceClassLambdaInvoker( final MethodType callSiteSignature, final MethodType interfaceSignature, final MethodHandle lambdaMethod, - final LambdaFlags flags, final String className ) { final String descriptor = interfaceSignature.toMethodDescriptorString(); @@ -400,12 +376,6 @@ private static void generateInterfaceClassLambdaInvoker( writer.visitVarInsn(Opcodes.ALOAD, 0); writer.visitFieldInsn(Opcodes.GETFIELD, className, THIS_FIELD_NAME, thisTypeDescriptor); - if (flags.differentiateReceiver()) { - final String receiverTypeDescriptor = Type.getDescriptor(callSiteSignature.parameterType(1)); - writer.visitVarInsn(Opcodes.ALOAD, 0); - writer.visitFieldInsn(Opcodes.GETFIELD, className, RECEIVER_FIELD_NAME, receiverTypeDescriptor); - } - int localIndex = 1; for (int i = 0, s = interfaceSignature.parameterCount(); i < s; ++i) { final Type type = Type.getType(interfaceSignature.parameterType(i)); @@ -413,10 +383,9 @@ private static void generateInterfaceClassLambdaInvoker( localIndex += type.getSize(); } - final int firstCaptureIndex = flags.differentiateReceiver()? 2 : 1; - for (int i = firstCaptureIndex, s = callSiteSignature.parameterCount(); i < s; ++i) { + for (int i = 1, s = callSiteSignature.parameterCount(); i < s; ++i) { final String fieldDescriptor = Type.getDescriptor(callSiteSignature.parameterType(i)); - final int captureIndex = i - firstCaptureIndex; + final int captureIndex = i - 1; writer.visitVarInsn(Opcodes.ALOAD, 0); writer.visitFieldInsn(Opcodes.GETFIELD, className, CAPTURE_FIELD_NAME_PREFIX + captureIndex, fieldDescriptor); } From 154590a03bc5ecdfd1a2e2529e1d16969b8ebb45 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Thu, 27 Feb 2025 16:55:29 +0100 Subject: [PATCH 06/17] Make lambda method names a bit shorter Signed-off-by: TheSilkMiner --- .../org/openzen/zenscript/javabytecode/JavaMangler.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java index 31ee06d2..7186a43a 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java @@ -145,10 +145,12 @@ public int hashCode() { } else { sanitizedMethodName = parentMethodName; } - final String interfaceTarget = interfaceName.replace('/', '_').replace('.', '_'); - final LambdaId id = new LambdaId(interfaceName, sanitizedMethodName); + final String interfaceTarget = interfaceName.replace('/', '.'); + final int lastDot = interfaceTarget.lastIndexOf('.'); + final String canonicalInterfaceTarget = lastDot == -1 ? interfaceTarget : interfaceTarget.substring(lastDot + 1); + final LambdaId id = new LambdaId(canonicalInterfaceTarget, sanitizedMethodName); - return "$lambda$" + sanitizedMethodName + '$' + interfaceTarget + '$' + this.mangleCounters.get(id); + return "$lambda$" + sanitizedMethodName + '$' + canonicalInterfaceTarget + '$' + this.mangleCounters.get(id); } public String mangleGeneratedLambdaName(final String interfaceName) { From 46658d905ff52d974b7a4bd1dc87eab926624884 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Thu, 27 Feb 2025 17:41:32 +0100 Subject: [PATCH 07/17] Move lambda compilation into a separate file Signed-off-by: TheSilkMiner --- .../compiler/JavaExpressionVisitor.java | 129 +--------- ...isitorToAccessCapturesInsideTheLambda.java | 102 -------- ...apturesOnTheStackBeforeCallingTheCtor.java | 40 --- .../{ => lambda}/LambdaClosureInfo.java | 4 +- .../compiler/lambda/LambdaIndyCompiler.java | 230 ++++++++++++++++++ .../JavaInvalidCapturedExpressionVisitor.java | 4 +- ...pturesOnIndyCapturedExpressionVisitor.java | 4 +- ...adThisOnIndyCapturedExpressionVisitor.java | 2 +- ...ectCapturesCapturedExpressionVisitor.java} | 8 +- 9 files changed, 250 insertions(+), 273 deletions(-) delete mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorToAccessCapturesInsideTheLambda.java delete mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorToPutCapturesOnTheStackBeforeCallingTheCtor.java rename JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/{ => lambda}/LambdaClosureInfo.java (89%) create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java rename JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/{ => lambda}/capturing/JavaInvalidCapturedExpressionVisitor.java (82%) rename JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/{ => lambda}/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java (86%) rename JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/{ => lambda}/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java (93%) rename JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/{capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java => lambda/capturing/JavaRedirectCapturesCapturedExpressionVisitor.java} (92%) diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java index 72404e92..3dbd6185 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java @@ -11,7 +11,6 @@ import org.openzen.zenscript.codemodel.definition.ExpansionDefinition; import org.openzen.zenscript.codemodel.expression.captured.CapturedExpression; import org.openzen.zenscript.codemodel.expression.captured.CapturedExpressionVisitor; -import org.openzen.zenscript.codemodel.expression.captured.CapturedThisExpression; import org.openzen.zenscript.codemodel.expression.modifiable.ModifiableExpression; import org.openzen.zenscript.codemodel.identifiers.MethodID; import org.openzen.zenscript.codemodel.identifiers.ModuleSymbol; @@ -23,8 +22,8 @@ import org.openzen.zenscript.javabytecode.JavaLocalVariableInfo; import org.openzen.zenscript.javabytecode.JavaMangler; import org.openzen.zenscript.javabytecode.compiler.JavaModificationExpressionVisitor.PushOption; -import org.openzen.zenscript.javabytecode.compiler.capturing.*; -import org.openzen.zenscript.javabytecode.compiler.definitions.JavaMemberVisitor; +import org.openzen.zenscript.javabytecode.compiler.lambda.LambdaIndyCompiler; +import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.JavaInvalidCapturedExpressionVisitor; import org.openzen.zenscript.javashared.*; import org.openzen.zenscript.javashared.compiling.JavaCompilingMethod; import org.openzen.zenscript.javashared.expressions.JavaFunctionInterfaceCastExpression; @@ -474,135 +473,27 @@ public Void visitFunction(FunctionExpression expression) { return null; }*/ - final String[] interfaces; - FunctionHeader header = expression.original == null ? expression.header : expression.original; - + final String interfaceName; if (expression.type instanceof JavaFunctionalInterfaceTypeID) { //Let's implement the functional Interface instead JavaFunctionalInterfaceTypeID type = (JavaFunctionalInterfaceTypeID) expression.type; - final Method functionalInterfaceMethod = type.functionalInterfaceMethod; - //Should be the same, should it not? - interfaces = new String[]{Type.getInternalName(functionalInterfaceMethod.getDeclaringClass())}; + interfaceName = Type.getInternalName(type.functionalInterfaceMethod.getDeclaringClass()); } else { //Normal way, no casting to functional interface - interfaces = new String[]{context.getInternalName(new FunctionTypeID(header))}; - } - - final JavaNativeMethod methodInfo; - { - final JavaNativeMethod m = context.getFunctionalInterface(expression.original == null ? expression.type : new FunctionTypeID(expression.original)); - methodInfo = m.withModifiers(m.modifiers & ~JavaModifiers.ABSTRACT); - } - - final JavaClass thisClass = javaWriter.method.class_; - final String lambdaName = this.javaMangler.mangleLambdaMethod(javaWriter.method.compiled.name, interfaces[0]); - final LambdaClosureInfo closureInfo = LambdaClosureInfo.from(context, expression.closure, thisClass); - final String lambdaDescriptor = calcFunctionDescriptor(closureInfo, expression.header); - final JavaNativeMethod lambdaMethod = JavaNativeMethod.getStatic(thisClass, lambdaName, lambdaDescriptor, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); - final JavaCompilingMethod lambdaMethodCompiling = JavaMemberVisitor.compileBridgeableMethodNoSideEffect(lambdaMethod, lambdaDescriptor); // TODO("Restore signatures") - final JavaWriter lambdaWriter = new JavaWriter(context.logger, expression.position, javaWriter.clazzVisitor, lambdaMethodCompiling, null); - final CapturedExpressionVisitor lambdaCapturesVisitor = new JavaCapturedExpressionVisitorLocalRedirectionVisitor(lambdaWriter, expression, closureInfo); - final JavaExpressionVisitor lambdaExpressionVisitor = new JavaExpressionVisitor(context, module, lambdaWriter, javaMangler, lambdaCapturesVisitor); - final JavaStatementVisitor lambdaStatementVisitor = new JavaStatementVisitor(context, lambdaExpressionVisitor, javaMangler); - - lambdaWriter.start(); - expression.body.accept(lambdaStatementVisitor); - lambdaWriter.ret(); - lambdaWriter.end(); - - final JavaLoadThisOnIndyCapturedExpressionVisitor thisVisitor = new JavaLoadThisOnIndyCapturedExpressionVisitor(this); - final JavaLoadCapturesOnIndyCapturedExpressionVisitor othersVisitor = new JavaLoadCapturesOnIndyCapturedExpressionVisitor(this); - - boolean hasLoadedThis = false; - for (final CapturedExpression capture : expression.closure.captures) { - // Note: we want this to be |= to ensure NO short-circuiting behavior - hasLoadedThis |= capture.accept(thisVisitor); + FunctionHeader header = expression.original == null ? expression.header : expression.original; + interfaceName = context.getInternalName(new FunctionTypeID(header)); } - // If no this was loaded, then we don't care about its value; just load null - if (!hasLoadedThis) { - javaWriter.aConstNull(); - } + final JavaNativeMethod functionalMethod = context.getFunctionalInterface(expression.original == null ? expression.type : new FunctionTypeID(expression.original)); + final JavaNativeMethod methodInfo = functionalMethod.withModifiers(functionalMethod.modifiers & ~JavaModifiers.ABSTRACT); - for (final CapturedExpression capture : expression.closure.captures) { - capture.accept(othersVisitor); - } - - // TODO("Have this in JavaWriter") - javaWriter.getVisitor().visitInvokeDynamicInsn( - methodInfo.name, - calcIndyDescriptor(closureInfo, interfaces[0]), - new org.objectweb.asm.Handle( - Opcodes.H_INVOKESTATIC, - Type.getInternalName(org.openzen.zenscript.javart.factory.LambdaFactory.class), - "buildLambda", - Type.getMethodDescriptor( - Type.getType(java.lang.invoke.CallSite.class), - Type.getType(java.lang.invoke.MethodHandles.Lookup.class), - Type.getType(String.class), - Type.getType(java.lang.invoke.MethodType.class), - Type.getType(java.lang.invoke.MethodHandle.class), - Type.getType(java.lang.invoke.MethodType.class), - Type.INT_TYPE, - Type.getType(java.lang.invoke.MethodType.class) - ), - false - ), - new org.objectweb.asm.Handle( - Opcodes.H_INVOKESTATIC, - thisClass.internalName, - lambdaName, - lambdaDescriptor, - false - ), - Type.getMethodType(JavaMemberVisitor.compileBridgeableMethodNoSideEffect(methodInfo, context.getMethodDescriptor(expression.header)).compiled.descriptor), - org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_GENERATE_BRIDGE, - Type.getMethodType(methodInfo.descriptor) - ); + final LambdaIndyCompiler lambdaCompiler = LambdaIndyCompiler.of(this.javaWriter, this.javaMangler, this.context, this.module, this); + lambdaCompiler.compileFunctionExpressionViaIndy(expression, interfaceName, methodInfo); return null; } - private String calcFunctionDescriptor(final LambdaClosureInfo closureInfo, final FunctionHeader header) { - final StringJoiner joiner = new StringJoiner(""); - for (final CapturedExpression capture : closureInfo.closure().captures) { - if (!(capture instanceof CapturedThisExpression)) { - final String descriptor = context.getDescriptor(capture.type); - joiner.add(descriptor); - } - } - - // TODO("Remove null check: the method should always return non-null") - final String thisType = closureInfo.thisType() == null? "Ljava/lang/Void;" : context.getDescriptor(closureInfo.thisType()); - final StringBuilder builder = new StringBuilder(context.getMethodDescriptor(header)); - builder.insert(builder.indexOf("(") + 1, thisType); - builder.insert(builder.lastIndexOf(")"), joiner); - return builder.toString(); - } - - private String calcIndyDescriptor(final LambdaClosureInfo closureInfo, final String targetInterface) { - final StringBuilder builder = new StringBuilder("("); - - // TODO("Remove null check as this method should never return null") - if (closureInfo.thisType() == null) { - builder.append("Ljava/lang/Void;"); - } else { - builder.append(context.getDescriptor(closureInfo.thisType())); - } - - final StringJoiner joiner = new StringJoiner(""); - for (final CapturedExpression capture : closureInfo.closure().captures) { - if (!(capture instanceof CapturedThisExpression)) { - final String descriptor = context.getDescriptor(capture.type); - joiner.add(descriptor); - } - } - - builder.append(joiner).append(")L").append(targetInterface).append(';'); - return builder.toString(); - } - @Override public Void visitGetField(GetFieldExpression expression) { JavaField field = context.getJavaField(expression.field); diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorToAccessCapturesInsideTheLambda.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorToAccessCapturesInsideTheLambda.java deleted file mode 100644 index fd034bda..00000000 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorToAccessCapturesInsideTheLambda.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.openzen.zenscript.javabytecode.compiler.capturing; - -import org.openzen.zenscript.codemodel.expression.FunctionExpression; -import org.openzen.zenscript.codemodel.expression.GetLocalVariableExpression; -import org.openzen.zenscript.codemodel.expression.captured.*; -import org.openzen.zenscript.javabytecode.JavaBytecodeContext; -import org.openzen.zenscript.javabytecode.JavaMangler; -import org.openzen.zenscript.javabytecode.compiler.JavaWriter; - -/** - * {@link CapturedExpressionVisitor} used to access captured values inside a lambda expression. - */ -public class JavaCapturedExpressionVisitorToAccessCapturesInsideTheLambda implements CapturedExpressionVisitor { - - private final String lambdaClassName; - private final FunctionExpression functionExpression; - private final JavaMangler javaMangler; - private final JavaBytecodeContext context; - private final JavaWriter javaWriter; - - public JavaCapturedExpressionVisitorToAccessCapturesInsideTheLambda( - final JavaBytecodeContext context, - final JavaWriter javaWriter, - final JavaMangler javaMangler, - final String lambdaClassName, - final FunctionExpression functionExpression - ) { - this.context = context; - this.javaWriter = javaWriter; - this.lambdaClassName = lambdaClassName; - this.functionExpression = functionExpression; - this.javaMangler = javaMangler; - } - @Override - public Void visitCapturedThis(CapturedThisExpression expression) { - javaWriter.loadObject(0); - javaWriter.getField(lambdaClassName, this.javaMangler.mangleCapturedParameter(1, true), context.getDescriptor(expression.type)); - return null; - } - - @Override - public Void visitCapturedParameter(CapturedParameterExpression expression) { - final int position = calculateMemberPosition(expression, this.functionExpression); - - javaWriter.loadObject(0); - javaWriter.getField(lambdaClassName, this.javaMangler.mangleCapturedParameter(position, false), context.getDescriptor(expression.parameter.type)); - return null; - } - - @Override - public Void visitCapturedLocal(CapturedLocalVariableExpression expression) { - final int position = calculateMemberPosition(new GetLocalVariableExpression(expression.position, expression.variable), this.functionExpression); - - javaWriter.loadObject(0); - javaWriter.getField(lambdaClassName, this.javaMangler.mangleCapturedParameter(position, false), context.getDescriptor(expression.type)); - return null; - } - - @Override - public Void visitRecaptured(CapturedClosureExpression expression) { - final int position = findIndex(expression); - - javaWriter.loadObject(0); - javaWriter.getField(lambdaClassName, this.javaMangler.mangleCapturedParameter(position, false), context.getDescriptor(expression.type)); - return null; - } - - private static int findIndex(CapturedExpression expression) { - int h = 1; - for (CapturedExpression capture : expression.closure.captures) { - if(capture.equals(expression)) { - return h; - } - h++; - } - - throw new IllegalStateException(expression.position.toString() + ": Captured Statement error"); - } - - private static int calculateMemberPosition(GetLocalVariableExpression localVariableExpression, FunctionExpression expression) { - int h = 1; - for (CapturedExpression capture : expression.closure.captures) { - if (capture instanceof CapturedLocalVariableExpression && ((CapturedLocalVariableExpression) capture).variable == localVariableExpression.variable) - return h; - if (capture instanceof CapturedClosureExpression && ((CapturedClosureExpression) capture).value instanceof CapturedLocalVariableExpression && ((CapturedLocalVariableExpression) ((CapturedClosureExpression) capture).value).variable == localVariableExpression.variable) - return h; - h++; - } - throw new IllegalStateException(localVariableExpression.position.toString() + ": Captured Statement error"); - } - - private static int calculateMemberPosition(CapturedParameterExpression functionParameterExpression, FunctionExpression expression) { - int h = 1; - - for (CapturedExpression capture : expression.closure.captures) { - if (capture instanceof CapturedParameterExpression && ((CapturedParameterExpression) capture).parameter == functionParameterExpression.parameter) - return h; - h++; - } - throw new IllegalStateException(functionParameterExpression.position.toString() + ": Captured Statement error"); - } -} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorToPutCapturesOnTheStackBeforeCallingTheCtor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorToPutCapturesOnTheStackBeforeCallingTheCtor.java deleted file mode 100644 index 84f12bdf..00000000 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorToPutCapturesOnTheStackBeforeCallingTheCtor.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.openzen.zenscript.javabytecode.compiler.capturing; - -import org.openzen.zenscript.codemodel.expression.ExpressionVisitor; -import org.openzen.zenscript.codemodel.expression.GetFunctionParameterExpression; -import org.openzen.zenscript.codemodel.expression.GetLocalVariableExpression; -import org.openzen.zenscript.codemodel.expression.ThisExpression; -import org.openzen.zenscript.codemodel.expression.captured.*; - -/** - * {@link CapturedExpressionVisitor} used to put the captured expressions on the stack. - * The {@link org.openzen.zenscript.javabytecode.compiler.JavaExpressionVisitor} will use this class just before it invokes - * the generated lambda class' constructor. - */ -public class JavaCapturedExpressionVisitorToPutCapturesOnTheStackBeforeCallingTheCtor implements CapturedExpressionVisitor { - private final ExpressionVisitor expressionVisitor; - - public JavaCapturedExpressionVisitorToPutCapturesOnTheStackBeforeCallingTheCtor(ExpressionVisitor expressionVisitor) { - this.expressionVisitor = expressionVisitor; - } - - @Override - public Void visitCapturedThis(CapturedThisExpression expression) { - return new ThisExpression(expression.position, expression.type).accept(expressionVisitor); - } - - @Override - public Void visitCapturedParameter(CapturedParameterExpression expression) { - return new GetFunctionParameterExpression(expression.position, expression.parameter).accept(expressionVisitor); - } - - @Override - public Void visitCapturedLocal(CapturedLocalVariableExpression expression) { - return new GetLocalVariableExpression(expression.position, expression.variable).accept(expressionVisitor); - } - - @Override - public Void visitRecaptured(CapturedClosureExpression expression) { - return expression.value.accept(expressionVisitor); - } -} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaClosureInfo.java similarity index 89% rename from JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java rename to JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaClosureInfo.java index 8c50303a..7e5faa48 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/LambdaClosureInfo.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaClosureInfo.java @@ -1,10 +1,8 @@ -package org.openzen.zenscript.javabytecode.compiler; +package org.openzen.zenscript.javabytecode.compiler.lambda; import org.openzen.zenscript.codemodel.expression.LambdaClosure; import org.openzen.zenscript.codemodel.expression.captured.CapturedExpression; import org.openzen.zenscript.codemodel.expression.captured.CapturedThisExpression; -import org.openzen.zenscript.codemodel.type.BasicTypeID; -import org.openzen.zenscript.codemodel.type.OptionalTypeID; import org.openzen.zenscript.codemodel.type.TypeID; import org.openzen.zenscript.javabytecode.JavaBytecodeContext; import org.openzen.zenscript.javashared.JavaClass; diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java new file mode 100644 index 00000000..2120c7ce --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java @@ -0,0 +1,230 @@ +package org.openzen.zenscript.javabytecode.compiler.lambda; + +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.openzen.zenscript.codemodel.FunctionHeader; +import org.openzen.zenscript.codemodel.FunctionParameter; +import org.openzen.zenscript.codemodel.expression.FunctionExpression; +import org.openzen.zenscript.codemodel.expression.captured.CapturedExpression; +import org.openzen.zenscript.codemodel.expression.captured.CapturedExpressionVisitor; +import org.openzen.zenscript.codemodel.expression.captured.CapturedThisExpression; +import org.openzen.zenscript.javabytecode.JavaBytecodeContext; +import org.openzen.zenscript.javabytecode.JavaLocalVariableInfo; +import org.openzen.zenscript.javabytecode.JavaMangler; +import org.openzen.zenscript.javabytecode.compiler.JavaExpressionVisitor; +import org.openzen.zenscript.javabytecode.compiler.JavaStatementVisitor; +import org.openzen.zenscript.javabytecode.compiler.JavaWriter; +import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.JavaRedirectCapturesCapturedExpressionVisitor; +import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.JavaLoadCapturesOnIndyCapturedExpressionVisitor; +import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.JavaLoadThisOnIndyCapturedExpressionVisitor; +import org.openzen.zenscript.javabytecode.compiler.definitions.JavaMemberVisitor; +import org.openzen.zenscript.javashared.JavaClass; +import org.openzen.zenscript.javashared.JavaCompiledModule; +import org.openzen.zenscript.javashared.JavaNativeMethod; +import org.openzen.zenscript.javashared.compiling.JavaCompilingMethod; + +import java.util.StringJoiner; + +public final class LambdaIndyCompiler { + private final JavaWriter writer; + private final JavaMangler mangler; + private final JavaBytecodeContext context; + private final JavaCompiledModule module; + private final JavaExpressionVisitor visitor; + + private LambdaIndyCompiler(final JavaWriter writer, final JavaMangler mangler, final JavaBytecodeContext context, final JavaCompiledModule module, final JavaExpressionVisitor visitor) { + this.writer = writer; + this.mangler = mangler; + this.context = context; + this.module = module; + this.visitor = visitor; + } + + public static LambdaIndyCompiler of(final JavaWriter writer, final JavaMangler mangler, final JavaBytecodeContext context, final JavaCompiledModule module, final JavaExpressionVisitor visitor) { + return new LambdaIndyCompiler(writer, mangler, context, module, visitor); + } + + public void compileFunctionExpressionViaIndy(final FunctionExpression expression, final String interfaceName, final JavaNativeMethod methodInfo) { + final JavaClass owner = this.writer.method.class_; + final LambdaClosureInfo closureInfo = LambdaClosureInfo.from(this.context, expression.closure, owner); + + final JavaNativeMethod lambdaMethod = this.createLambdaMethod(owner, interfaceName, closureInfo, expression); + final JavaCompilingMethod lambdaMethodCompiling = JavaMemberVisitor.compileBridgeableMethodNoSideEffect(lambdaMethod, lambdaMethod.descriptor); // TODO("Restore signatures") + + this.generateIndyMethodCall(owner, lambdaMethodCompiling, expression, methodInfo, closureInfo, interfaceName); + this.generateLambdaMethod(lambdaMethodCompiling, expression, closureInfo); + } + + private JavaNativeMethod createLambdaMethod( + final JavaClass owner, + final String interfaceName, + final LambdaClosureInfo closureInfo, + final FunctionExpression lambdaExpression + ) { + // TODO("Figure out why IntelliJ complains about NPE") + final String lambdaName = this.mangler.mangleLambdaMethod(this.writer.method.compiled.name, interfaceName); + final String lambdaDescriptor = this.computeLambdaDescriptor(closureInfo, lambdaExpression.header); + return JavaNativeMethod.getStatic(owner, lambdaName, lambdaDescriptor, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); + } + + private String computeLambdaDescriptor(final LambdaClosureInfo closureInfo, final FunctionHeader header) { + final StringJoiner joiner = new StringJoiner(""); + for (final CapturedExpression capture : closureInfo.closure().captures) { + if (!(capture instanceof CapturedThisExpression)) { + final String descriptor = context.getDescriptor(capture.type); + joiner.add(descriptor); + } + } + + // TODO("Remove null check: the method should always return non-null") + final String thisType = closureInfo.thisType() == null? "Ljava/lang/Void;" : context.getDescriptor(closureInfo.thisType()); + final StringBuilder builder = new StringBuilder(context.getMethodDescriptor(header)); + builder.insert(builder.indexOf("(") + 1, thisType); + builder.insert(builder.lastIndexOf(")"), joiner); + return builder.toString(); + } + + private void generateLambdaMethod(final JavaCompilingMethod compilingLambdaMethod, final FunctionExpression lambdaExpression, final LambdaClosureInfo closureInfo) { + final JavaWriter lambdaWriter = new JavaWriter(this.context.logger, lambdaExpression.position, this.writer.clazzVisitor, compilingLambdaMethod, null); + final CapturedExpressionVisitor lambdaCapturesVisitor = new JavaRedirectCapturesCapturedExpressionVisitor(lambdaWriter, lambdaExpression, closureInfo); + final JavaExpressionVisitor lambdaExpressionVisitor = new JavaExpressionVisitor(this.context, this.module, lambdaWriter, this.mangler, lambdaCapturesVisitor); + final JavaStatementVisitor lambdaStatementVisitor = new JavaStatementVisitor(this.context, lambdaExpressionVisitor, this.mangler); + + this.generateLambdaMethodBody(compilingLambdaMethod, lambdaWriter, lambdaStatementVisitor, lambdaExpression); + } + + private void generateLambdaMethodBody( + final JavaCompilingMethod method, + final JavaWriter writer, + final JavaStatementVisitor statementVisitor, + final FunctionExpression lambdaExpression + ) { + final Label begin = new Label(); + final Label end = new Label(); + + writer.start(); + writer.label(begin); + + lambdaExpression.body.accept(statementVisitor); + writer.ret(); + writer.label(end); + + this.loadLambdaVariables(writer, method, lambdaExpression, begin, end); + + writer.end(); + } + + private void loadLambdaVariables( + final JavaWriter writer, + final JavaCompilingMethod method, + final FunctionExpression lambdaExpression, + final Label begin, + final Label end + ) { + final Type[] methodArguments = Type.getArgumentTypes(method.compiled.descriptor); + final FunctionParameter[] lambdaParameters = lambdaExpression.header.parameters; + + for (int i = 0, l = methodArguments.length, p = lambdaParameters.length + 1, localIndex = 0; i < l; ++i) { + final Type type = methodArguments[i]; + + final String name; + if (i == 0) { + name = "$this"; + } else if (i < p) { + final String lambdaName = lambdaParameters[i - 1].name; + name = lambdaName == null || lambdaName.isEmpty()? "$param" + (i - 1) : lambdaName; + } else { + // TODO("Maybe find the actual name of the variable among the captures?") + name = "$capture$" + (i - p); + } + + final JavaLocalVariableInfo info = new JavaLocalVariableInfo(type, localIndex, begin, name, end); + writer.addVariableInfo(info); + + localIndex += type.getSize(); + } + } + + private void generateIndyMethodCall( + final JavaClass owner, + final JavaCompilingMethod method, + final FunctionExpression lambdaExpression, + final JavaNativeMethod methodInfo, + final LambdaClosureInfo closureInfo, + final String interfaceType + ) { + final JavaLoadThisOnIndyCapturedExpressionVisitor thisVisitor = new JavaLoadThisOnIndyCapturedExpressionVisitor(this.visitor); + final JavaLoadCapturesOnIndyCapturedExpressionVisitor othersVisitor = new JavaLoadCapturesOnIndyCapturedExpressionVisitor(this.visitor); + + boolean hasLoadedThis = false; + for (final CapturedExpression capture : lambdaExpression.closure.captures) { + // Note: we want this to be |= to ensure NO short-circuiting behavior + hasLoadedThis |= capture.accept(thisVisitor); + } + + // If no this was loaded, then we don't care about its value; just load null + if (!hasLoadedThis) { + this.writer.aConstNull(); + } + + for (final CapturedExpression capture : lambdaExpression.closure.captures) { + capture.accept(othersVisitor); + } + + // TODO("Have this in JavaWriter") + this.writer.getVisitor().visitInvokeDynamicInsn( + methodInfo.name, + this.computeIndyDescriptor(closureInfo, interfaceType), + new org.objectweb.asm.Handle( + Opcodes.H_INVOKESTATIC, + Type.getInternalName(org.openzen.zenscript.javart.factory.LambdaFactory.class), + "buildLambda", + Type.getMethodDescriptor( + Type.getType(java.lang.invoke.CallSite.class), + Type.getType(java.lang.invoke.MethodHandles.Lookup.class), + Type.getType(String.class), + Type.getType(java.lang.invoke.MethodType.class), + Type.getType(java.lang.invoke.MethodHandle.class), + Type.getType(java.lang.invoke.MethodType.class), + Type.INT_TYPE, + Type.getType(java.lang.invoke.MethodType.class) + ), + false + ), + new org.objectweb.asm.Handle( + Opcodes.H_INVOKESTATIC, + owner.internalName, + method.compiled.name, + method.compiled.descriptor, + false + ), + Type.getMethodType(JavaMemberVisitor.compileBridgeableMethodNoSideEffect(methodInfo, context.getMethodDescriptor(lambdaExpression.header)).compiled.descriptor), + org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_GENERATE_BRIDGE, + Type.getMethodType(methodInfo.descriptor) + ); + } + + private String computeIndyDescriptor(final LambdaClosureInfo closureInfo, final String targetInterface) { + final StringBuilder builder = new StringBuilder("("); + + // TODO("Remove null check as this method should never return null") + if (closureInfo.thisType() == null) { + builder.append("Ljava/lang/Void;"); + } else { + builder.append(context.getDescriptor(closureInfo.thisType())); + } + + final StringJoiner joiner = new StringJoiner(""); + for (final CapturedExpression capture : closureInfo.closure().captures) { + if (!(capture instanceof CapturedThisExpression)) { + final String descriptor = context.getDescriptor(capture.type); + joiner.add(descriptor); + } + } + + builder.append(joiner).append(")L").append(targetInterface).append(';'); + return builder.toString(); + } + +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaInvalidCapturedExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaInvalidCapturedExpressionVisitor.java similarity index 82% rename from JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaInvalidCapturedExpressionVisitor.java rename to JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaInvalidCapturedExpressionVisitor.java index cb35e004..e8d53fa7 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaInvalidCapturedExpressionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaInvalidCapturedExpressionVisitor.java @@ -1,11 +1,11 @@ -package org.openzen.zenscript.javabytecode.compiler.capturing; +package org.openzen.zenscript.javabytecode.compiler.lambda.capturing; import org.openzen.zenscript.codemodel.expression.captured.*; /** * Fallback {@link CapturedExpressionVisitor} used by the JavaExpressionVisitor whenever we are outside any lambda context */ -public class JavaInvalidCapturedExpressionVisitor implements CapturedExpressionVisitor { +public final class JavaInvalidCapturedExpressionVisitor implements CapturedExpressionVisitor { @Override public Void visitCapturedThis(CapturedThisExpression expression) { diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java similarity index 86% rename from JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java rename to JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java index b7ea92c2..bb310c01 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java @@ -1,11 +1,11 @@ -package org.openzen.zenscript.javabytecode.compiler.capturing; +package org.openzen.zenscript.javabytecode.compiler.lambda.capturing; import org.openzen.zenscript.codemodel.expression.ExpressionVisitor; import org.openzen.zenscript.codemodel.expression.GetFunctionParameterExpression; import org.openzen.zenscript.codemodel.expression.GetLocalVariableExpression; import org.openzen.zenscript.codemodel.expression.captured.*; -public class JavaLoadCapturesOnIndyCapturedExpressionVisitor implements CapturedExpressionVisitor { +public final class JavaLoadCapturesOnIndyCapturedExpressionVisitor implements CapturedExpressionVisitor { private final ExpressionVisitor visitor; public JavaLoadCapturesOnIndyCapturedExpressionVisitor(final ExpressionVisitor visitor) { diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java similarity index 93% rename from JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java rename to JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java index 6244eb54..0decf7c5 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java @@ -1,4 +1,4 @@ -package org.openzen.zenscript.javabytecode.compiler.capturing; +package org.openzen.zenscript.javabytecode.compiler.lambda.capturing; import org.openzen.zenscript.codemodel.expression.ExpressionVisitor; import org.openzen.zenscript.codemodel.expression.ThisExpression; diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaRedirectCapturesCapturedExpressionVisitor.java similarity index 92% rename from JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java rename to JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaRedirectCapturesCapturedExpressionVisitor.java index efa79c28..a33a2f03 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/capturing/JavaCapturedExpressionVisitorLocalRedirectionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaRedirectCapturesCapturedExpressionVisitor.java @@ -1,4 +1,4 @@ -package org.openzen.zenscript.javabytecode.compiler.capturing; +package org.openzen.zenscript.javabytecode.compiler.lambda.capturing; import org.objectweb.asm.Type; import org.openzen.zenscript.codemodel.FunctionParameter; @@ -8,11 +8,11 @@ import org.openzen.zenscript.codemodel.type.BasicTypeID; import org.openzen.zenscript.codemodel.type.TypeID; import org.openzen.zenscript.javabytecode.compiler.JavaWriter; -import org.openzen.zenscript.javabytecode.compiler.LambdaClosureInfo; +import org.openzen.zenscript.javabytecode.compiler.lambda.LambdaClosureInfo; import java.util.function.Predicate; -public class JavaCapturedExpressionVisitorLocalRedirectionVisitor implements CapturedExpressionVisitor { +public final class JavaRedirectCapturesCapturedExpressionVisitor implements CapturedExpressionVisitor { private static final class MemberData { private final int position; private final Type type; @@ -31,7 +31,7 @@ void load(final JavaWriter writer) { private final JavaWriter javaWriter; private final LambdaClosureInfo closureInfo; - public JavaCapturedExpressionVisitorLocalRedirectionVisitor(final JavaWriter javaWriter, final FunctionExpression functionExpression, final LambdaClosureInfo closureInfo) { + public JavaRedirectCapturesCapturedExpressionVisitor(final JavaWriter javaWriter, final FunctionExpression functionExpression, final LambdaClosureInfo closureInfo) { this.javaWriter = javaWriter; this.functionExpression = functionExpression; this.closureInfo = closureInfo; From c66aad9182123abea6f8a4ae35a99911514ca21c Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Thu, 27 Feb 2025 18:14:56 +0100 Subject: [PATCH 08/17] Properly name lambda captures too Signed-off-by: TheSilkMiner --- .../compiler/lambda/LambdaIndyCompiler.java | 28 ++-- ...reDataFinderCapturedExpressionVisitor.java | 134 ++++++++++++++++++ ...rectCapturesCapturedExpressionVisitor.java | 129 +++-------------- .../lambda/capturing/LambdaCaptureData.java | 31 ++++ 4 files changed, 203 insertions(+), 119 deletions(-) create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaCaptureDataFinderCapturedExpressionVisitor.java create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/LambdaCaptureData.java diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java index 2120c7ce..dea66f5c 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java @@ -15,9 +15,7 @@ import org.openzen.zenscript.javabytecode.compiler.JavaExpressionVisitor; import org.openzen.zenscript.javabytecode.compiler.JavaStatementVisitor; import org.openzen.zenscript.javabytecode.compiler.JavaWriter; -import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.JavaRedirectCapturesCapturedExpressionVisitor; -import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.JavaLoadCapturesOnIndyCapturedExpressionVisitor; -import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.JavaLoadThisOnIndyCapturedExpressionVisitor; +import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.*; import org.openzen.zenscript.javabytecode.compiler.definitions.JavaMemberVisitor; import org.openzen.zenscript.javashared.JavaClass; import org.openzen.zenscript.javashared.JavaCompiledModule; @@ -87,18 +85,19 @@ private String computeLambdaDescriptor(final LambdaClosureInfo closureInfo, fina private void generateLambdaMethod(final JavaCompilingMethod compilingLambdaMethod, final FunctionExpression lambdaExpression, final LambdaClosureInfo closureInfo) { final JavaWriter lambdaWriter = new JavaWriter(this.context.logger, lambdaExpression.position, this.writer.clazzVisitor, compilingLambdaMethod, null); - final CapturedExpressionVisitor lambdaCapturesVisitor = new JavaRedirectCapturesCapturedExpressionVisitor(lambdaWriter, lambdaExpression, closureInfo); + final CapturedExpressionVisitor lambdaCapturesVisitor = new JavaRedirectCapturesCapturedExpressionVisitor(lambdaWriter, lambdaExpression, closureInfo, this.context); final JavaExpressionVisitor lambdaExpressionVisitor = new JavaExpressionVisitor(this.context, this.module, lambdaWriter, this.mangler, lambdaCapturesVisitor); final JavaStatementVisitor lambdaStatementVisitor = new JavaStatementVisitor(this.context, lambdaExpressionVisitor, this.mangler); - this.generateLambdaMethodBody(compilingLambdaMethod, lambdaWriter, lambdaStatementVisitor, lambdaExpression); + this.generateLambdaMethodBody(compilingLambdaMethod, lambdaWriter, lambdaStatementVisitor, lambdaExpression, closureInfo); } private void generateLambdaMethodBody( final JavaCompilingMethod method, final JavaWriter writer, final JavaStatementVisitor statementVisitor, - final FunctionExpression lambdaExpression + final FunctionExpression lambdaExpression, + final LambdaClosureInfo closureInfo ) { final Label begin = new Label(); final Label end = new Label(); @@ -110,7 +109,7 @@ private void generateLambdaMethodBody( writer.ret(); writer.label(end); - this.loadLambdaVariables(writer, method, lambdaExpression, begin, end); + this.loadLambdaVariables(writer, method, lambdaExpression, closureInfo, begin, end); writer.end(); } @@ -119,9 +118,13 @@ private void loadLambdaVariables( final JavaWriter writer, final JavaCompilingMethod method, final FunctionExpression lambdaExpression, + final LambdaClosureInfo closureInfo, final Label begin, final Label end ) { + final JavaCaptureDataFinderCapturedExpressionVisitor captureDataFinder = new JavaCaptureDataFinderCapturedExpressionVisitor(lambdaExpression, closureInfo, this.context); + final LambdaCaptureData[] captures = lambdaExpression.closure.captures.stream().map(it -> it.accept(captureDataFinder)).toArray(LambdaCaptureData[]::new); + final Type[] methodArguments = Type.getArgumentTypes(method.compiled.descriptor); final FunctionParameter[] lambdaParameters = lambdaExpression.header.parameters; @@ -135,8 +138,15 @@ private void loadLambdaVariables( final String lambdaName = lambdaParameters[i - 1].name; name = lambdaName == null || lambdaName.isEmpty()? "$param" + (i - 1) : lambdaName; } else { - // TODO("Maybe find the actual name of the variable among the captures?") - name = "$capture$" + (i - p); + LambdaCaptureData data = null; + for (final LambdaCaptureData d : captures) { + if (d.position() == i) { + data = d; + break; + } + } + + name = "$capture$" + (data == null || data.name().isEmpty()? (i - p) : data.name()); } final JavaLocalVariableInfo info = new JavaLocalVariableInfo(type, localIndex, begin, name, end); diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaCaptureDataFinderCapturedExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaCaptureDataFinderCapturedExpressionVisitor.java new file mode 100644 index 00000000..4430fb1f --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaCaptureDataFinderCapturedExpressionVisitor.java @@ -0,0 +1,134 @@ +package org.openzen.zenscript.javabytecode.compiler.lambda.capturing; + +import org.objectweb.asm.Type; +import org.openzen.zenscript.codemodel.FunctionParameter; +import org.openzen.zenscript.codemodel.expression.FunctionExpression; +import org.openzen.zenscript.codemodel.expression.captured.*; +import org.openzen.zenscript.codemodel.type.TypeID; +import org.openzen.zenscript.javabytecode.JavaBytecodeContext; +import org.openzen.zenscript.javabytecode.compiler.lambda.LambdaClosureInfo; + +import java.util.function.Predicate; +import java.util.function.Supplier; + +public final class JavaCaptureDataFinderCapturedExpressionVisitor implements CapturedExpressionVisitor { + private static final class ExtractedInfo { + private final boolean matches; + private final String name; + + private ExtractedInfo(final boolean matches, final String name) { + this.matches = matches; + this.name = name; + } + + static ExtractedInfo noMatch() { + return new ExtractedInfo(false, null); + } + + static ExtractedInfo match(final String name) { + return new ExtractedInfo(true, name); + } + + boolean matches() { + return this.matches; + } + + String name() { + return this.name; + } + } + + @FunctionalInterface + private interface InfoExtractor { + static InfoExtractor byPredicating(final Supplier name, final Predicate predicate) { + return it -> predicate.test(it)? ExtractedInfo.match(name.get()) : ExtractedInfo.noMatch(); + } + + ExtractedInfo extractInfo(final T t); + } + + private final FunctionExpression functionExpression; + private final LambdaClosureInfo closureInfo; + private final JavaBytecodeContext context; + + public JavaCaptureDataFinderCapturedExpressionVisitor(final FunctionExpression functionExpression, final LambdaClosureInfo closureInfo, final JavaBytecodeContext context) { + this.functionExpression = functionExpression; + this.closureInfo = closureInfo; + this.context = context; + } + + @Override + public LambdaCaptureData visitCapturedThis(final CapturedThisExpression expression) { + // TODO("Remove null-check as this method should never return null") + final Type type = this.closureInfo.thisType() == null? Type.getType(Void.class) : this.getTypeFrom(this.closureInfo.thisType()); + return LambdaCaptureData.of(0, type, "$this"); + } + + @Override + public LambdaCaptureData visitCapturedParameter(final CapturedParameterExpression expression) { + return this.computeData( + this.functionExpression, + expression.parameter.type, + InfoExtractor.byPredicating( + () -> expression.parameter.name, + capture -> capture instanceof CapturedParameterExpression && ((CapturedParameterExpression) capture).parameter == expression.parameter + ) + ); + } + + @Override + public LambdaCaptureData visitCapturedLocal(final CapturedLocalVariableExpression expression) { + final Predicate matchVariable = + capture -> capture instanceof CapturedLocalVariableExpression && ((CapturedLocalVariableExpression) capture).variable == expression.variable; + + return this.computeData( + this.functionExpression, + expression.variable.type, + InfoExtractor.byPredicating( + () -> expression.variable.name, + matchVariable.or(capture -> capture instanceof CapturedClosureExpression && matchVariable.test(((CapturedClosureExpression) capture).value)) + ) + ); + } + + @Override + public LambdaCaptureData visitRecaptured(final CapturedClosureExpression expression) { + return this.computeData( + this.functionExpression, + expression.type, + InfoExtractor.byPredicating( + () -> expression.value.accept(this).name(), + expression::equals + ) + ); + } + + private LambdaCaptureData computeData( + final FunctionExpression expression, + final TypeID varType, + final InfoExtractor extractor + ) { + final Type type = this.getTypeFrom(varType); + int h = this.findFirstValidCaptureIndex(expression); + for (final CapturedExpression capture : expression.closure.captures) { + final ExtractedInfo info = extractor.extractInfo(capture); + if (info.matches()) { + return LambdaCaptureData.of(h, type, info.name()); + } + h += type.getSize(); + } + throw new IllegalStateException(expression.position.toString() + ": Captured Statement error"); + } + + private int findFirstValidCaptureIndex(final FunctionExpression expression) { + int h = 1; + for (final FunctionParameter parameter : expression.header.parameters) { + h += this.getTypeFrom(parameter.type).getSize(); + } + return h; + } + + private Type getTypeFrom(final TypeID type) { + return this.context.getType(type); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaRedirectCapturesCapturedExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaRedirectCapturesCapturedExpressionVisitor.java index a33a2f03..0996c4ae 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaRedirectCapturesCapturedExpressionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaRedirectCapturesCapturedExpressionVisitor.java @@ -1,142 +1,51 @@ package org.openzen.zenscript.javabytecode.compiler.lambda.capturing; -import org.objectweb.asm.Type; -import org.openzen.zenscript.codemodel.FunctionParameter; import org.openzen.zenscript.codemodel.expression.FunctionExpression; -import org.openzen.zenscript.codemodel.expression.GetLocalVariableExpression; import org.openzen.zenscript.codemodel.expression.captured.*; -import org.openzen.zenscript.codemodel.type.BasicTypeID; -import org.openzen.zenscript.codemodel.type.TypeID; +import org.openzen.zenscript.javabytecode.JavaBytecodeContext; import org.openzen.zenscript.javabytecode.compiler.JavaWriter; import org.openzen.zenscript.javabytecode.compiler.lambda.LambdaClosureInfo; -import java.util.function.Predicate; - public final class JavaRedirectCapturesCapturedExpressionVisitor implements CapturedExpressionVisitor { - private static final class MemberData { - private final int position; - private final Type type; - - MemberData(final int position, final Type type) { - this.position = position; - this.type = type; - } - - void load(final JavaWriter writer) { - writer.load(this.type, this.position); - } - } - - private final FunctionExpression functionExpression; private final JavaWriter javaWriter; - private final LambdaClosureInfo closureInfo; - - public JavaRedirectCapturesCapturedExpressionVisitor(final JavaWriter javaWriter, final FunctionExpression functionExpression, final LambdaClosureInfo closureInfo) { + private final JavaCaptureDataFinderCapturedExpressionVisitor captureDataFinder; + + public JavaRedirectCapturesCapturedExpressionVisitor( + final JavaWriter javaWriter, + final FunctionExpression functionExpression, + final LambdaClosureInfo closureInfo, + final JavaBytecodeContext context + ) { this.javaWriter = javaWriter; - this.functionExpression = functionExpression; - this.closureInfo = closureInfo; + this.captureDataFinder = new JavaCaptureDataFinderCapturedExpressionVisitor(functionExpression, closureInfo, context); } @Override public Void visitCapturedThis(final CapturedThisExpression expression) { - // TODO("Remove null-check as this method should never return null") - final Type type = this.closureInfo.thisType() == null? Type.getType(Void.class) : this.getTypeFrom(this.closureInfo.thisType()); - return this.loadByMemberData(new MemberData(0, type)); + return this.loadByCaptureData(expression); } @Override public Void visitCapturedParameter(CapturedParameterExpression expression) { - return this.loadByMemberData(this.calculateMemberData(expression, this.functionExpression)); + return this.loadByCaptureData(expression); } @Override public Void visitCapturedLocal(CapturedLocalVariableExpression expression) { - return this.loadByMemberData(this.calculateMemberData(new GetLocalVariableExpression(expression.position, expression.variable), this.functionExpression)); + return this.loadByCaptureData(expression); } @Override public Void visitRecaptured(CapturedClosureExpression expression) { - return this.loadByMemberData(this.findIndex(expression, this.functionExpression)); + return this.loadByCaptureData(expression); } - private Void loadByMemberData(final MemberData memberData) { - memberData.load(this.javaWriter); - return null; + private Void loadByCaptureData(final CapturedExpression expression) { + return this.loadByCaptureData(expression.accept(this.captureDataFinder)); } - private MemberData findIndex(final CapturedExpression capturedExpression, final FunctionExpression expression) { - return this.calculateMemberData( - expression, - capturedExpression.type, - capturedExpression::equals - ); - } - - private MemberData calculateMemberData(final GetLocalVariableExpression localVariableExpression, final FunctionExpression expression) { - return this.calculateMemberData( - expression, - localVariableExpression.type, - capture -> { - if (capture instanceof CapturedLocalVariableExpression) { - return ((CapturedLocalVariableExpression) capture).variable == localVariableExpression.variable; - } else if (capture instanceof CapturedClosureExpression) { - final CapturedExpression value = ((CapturedClosureExpression) capture).value; - return value instanceof CapturedLocalVariableExpression && ((CapturedLocalVariableExpression) value).variable == localVariableExpression.variable; - } - return false; - }); - } - - private MemberData calculateMemberData(final CapturedParameterExpression functionParameterExpression, final FunctionExpression expression) { - return this.calculateMemberData( - expression, - functionParameterExpression.parameter.type, - capture -> capture instanceof CapturedParameterExpression && ((CapturedParameterExpression) capture).parameter == functionParameterExpression.parameter - ); - } - - private MemberData calculateMemberData(final FunctionExpression expression, final TypeID varType, final Predicate predicate) { - final Type type = this.getTypeFrom(varType); - int h = this.findFirstValidCaptureIndex(expression); - for (final CapturedExpression capture : expression.closure.captures) { - if (predicate.test(capture)) { - return new MemberData(h, type); - } - h += type.getSize(); - } - throw new IllegalStateException(expression.position.toString() + ": Captured Statement error"); - } - - private int findFirstValidCaptureIndex(final FunctionExpression expression) { - int h = 1; - for (final FunctionParameter parameter : expression.header.parameters) { - h += this.getTypeFrom(parameter.type).getSize(); - } - return h; - } - - private Type getTypeFrom(final TypeID type) { - if (type instanceof BasicTypeID) { - switch ((BasicTypeID) type) { - case VOID: return Type.VOID_TYPE; - case BOOL: return Type.BOOLEAN_TYPE; - case BYTE: return Type.BYTE_TYPE; - case SBYTE: return Type.BYTE_TYPE; // TODO() - case SHORT: return Type.SHORT_TYPE; - case USHORT: return Type.SHORT_TYPE; // TODO() - case INT: return Type.INT_TYPE; - case UINT: return Type.INT_TYPE; // TODO() - case LONG: return Type.LONG_TYPE; - case ULONG: return Type.LONG_TYPE; // TODO() - case USIZE: return Type.INT_TYPE; - case FLOAT: return Type.FLOAT_TYPE; - case DOUBLE: return Type.DOUBLE_TYPE; - case CHAR: return Type.CHAR_TYPE; - } - } - - // We don't care about the actual class contained in the type here, we only care that Java treats it as an - // object, so we simply grab a random Java class - return Type.getType(Void.class); + private Void loadByCaptureData(final LambdaCaptureData data) { + this.javaWriter.load(data.type(), data.position()); + return null; } } diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/LambdaCaptureData.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/LambdaCaptureData.java new file mode 100644 index 00000000..2280cceb --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/LambdaCaptureData.java @@ -0,0 +1,31 @@ +package org.openzen.zenscript.javabytecode.compiler.lambda.capturing; + +import org.objectweb.asm.Type; + +public final class LambdaCaptureData { + private final int position; + private final Type type; + private final String name; + + private LambdaCaptureData(final int position, final Type type, final String name) { + this.position = position; + this.type = type; + this.name = name; + } + + static LambdaCaptureData of(final int position, final Type type, final String name) { + return new LambdaCaptureData(position, type, name); + } + + public int position() { + return this.position; + } + + public Type type() { + return this.type; + } + + public String name() { + return this.name; + } +} From e1bf706cd704e7aecb1e4d627ab51c67740ec930 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Thu, 27 Feb 2025 20:52:49 +0100 Subject: [PATCH 09/17] Allow constructing a JavaClass from a Class reference Signed-off-by: TheSilkMiner --- .../compiler/JavaMethodBytecodeCompiler.java | 2 +- .../zenscript/javashared/JavaClass.java | 64 ++++++++++++------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaMethodBytecodeCompiler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaMethodBytecodeCompiler.java index 9ad94499..3e6edfad 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaMethodBytecodeCompiler.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaMethodBytecodeCompiler.java @@ -124,7 +124,7 @@ public class JavaMethodBytecodeCompiler implements JavaMethodCompiler { private static final JavaNativeMethod OBJECTS_EQUALS = new JavaNativeMethod(JavaClass.fromInternalName("java/util/Objects", JavaClass.Kind.CLASS), JavaNativeMethod.Kind.STATIC, "equals", false, "(Ljava/lang/Object;Ljava/lang/Object;)Z", 0, false); - private static final JavaNativeMethod STRINGBUILDER_LENGTH = JavaNativeMethod.getNativeVirtual(JavaClass.STRINGBUILDER, "length", "()I"); + private static final JavaNativeMethod STRINGBUILDER_LENGTH = JavaNativeMethod.getNativeVirtual(JavaClass.STRING_BUILDER, "length", "()I"); private final JavaWriter javaWriter; private final JavaExpressionVisitor expressionVisitor; diff --git a/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaClass.java b/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaClass.java index 5896856e..6868ee3e 100644 --- a/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaClass.java +++ b/JavaShared/src/main/java/org/openzen/zenscript/javashared/JavaClass.java @@ -7,35 +7,38 @@ import stdlib.Strings; -import java.util.Arrays; +import java.io.Closeable; +import java.lang.reflect.Array; +import java.util.*; /** * @author Hoofdgebruiker */ public class JavaClass implements Comparable { - public static final JavaClass CLASS = new JavaClass("java.lang", "Class", Kind.CLASS); - public static final JavaClass ENUM = new JavaClass("java.lang", "Enum", Kind.CLASS); - public static final JavaClass OBJECT = new JavaClass("java.lang", "Object", Kind.CLASS); - public static final JavaClass STRING = new JavaClass("java.lang", "String", Kind.CLASS); - public static final JavaClass CLOSEABLE = new JavaClass("java.lang", "AutoCloseable", Kind.INTERFACE); - public static final JavaClass MAP = new JavaClass("java.util", "Map", JavaClass.Kind.INTERFACE); - public static final JavaClass HASHMAP = new JavaClass("java.util", "HashMap", JavaClass.Kind.CLASS); - public static final JavaClass ITERATOR = new JavaClass("java.util", "Iterator", JavaClass.Kind.INTERFACE); - public static final JavaClass ITERABLE = new JavaClass("java.lang", "Iterable", Kind.INTERFACE); - public static final JavaClass ARRAYS = new JavaClass("java.util", "Arrays", Kind.CLASS); - - public static final JavaClass BOOLEAN = new JavaClass("java.lang", "Boolean", Kind.CLASS); - public static final JavaClass BYTE = new JavaClass("java.lang", "Byte", Kind.CLASS); - public static final JavaClass SHORT = new JavaClass("java.lang", "Short", Kind.CLASS); - public static final JavaClass INTEGER = new JavaClass("java.lang", "Integer", Kind.CLASS); - public static final JavaClass LONG = new JavaClass("java.lang", "Long", Kind.CLASS); - public static final JavaClass FLOAT = new JavaClass("java.lang", "Float", Kind.CLASS); - public static final JavaClass DOUBLE = new JavaClass("java.lang", "Double", Kind.CLASS); - public static final JavaClass CHARACTER = new JavaClass("java.lang", "Character", Kind.CLASS); - public static final JavaClass COLLECTION = new JavaClass("java.util", "Collection", Kind.INTERFACE); - public static final JavaClass COLLECTIONS = new JavaClass("java.util", "Collections", Kind.CLASS); - public static final JavaClass STRINGBUILDER = new JavaClass("java.lang", "StringBuilder", Kind.CLASS); - public static final JavaClass ARRAY = new JavaClass("java.lang.reflect", "Array", Kind.CLASS); + public static final JavaClass CLASS = fromJavaClass(Class.class); + public static final JavaClass ENUM = fromJavaClass(Enum.class); + public static final JavaClass OBJECT = fromJavaClass(Object.class); + public static final JavaClass STRING = fromJavaClass(String.class); + public static final JavaClass AUTO_CLOSEABLE = fromJavaClass(AutoCloseable.class); + public static final JavaClass CLOSEABLE = fromJavaClass(Closeable.class); + public static final JavaClass MAP = fromJavaClass(Map.class); + public static final JavaClass HASHMAP = fromJavaClass(HashMap.class); + public static final JavaClass ITERATOR = fromJavaClass(Iterator.class); + public static final JavaClass ITERABLE = fromJavaClass(Iterable.class); + public static final JavaClass ARRAYS = fromJavaClass(Arrays.class); + + public static final JavaClass BOOLEAN = fromJavaClass(Boolean.class); + public static final JavaClass BYTE = fromJavaClass(Byte.class); + public static final JavaClass SHORT = fromJavaClass(Short.class); + public static final JavaClass INTEGER = fromJavaClass(Integer.class); + public static final JavaClass LONG = fromJavaClass(Long.class); + public static final JavaClass FLOAT = fromJavaClass(Float.class); + public static final JavaClass DOUBLE = fromJavaClass(Double.class); + public static final JavaClass CHARACTER = fromJavaClass(Character.class); + public static final JavaClass COLLECTION = fromJavaClass(Collection.class); + public static final JavaClass COLLECTIONS = fromJavaClass(Collections.class); + public static final JavaClass STRING_BUILDER = fromJavaClass(StringBuilder.class); + public static final JavaClass ARRAY = fromJavaClass(Array.class); public static final JavaClass SHARED = new JavaClass("zsynthetic", "Shared", Kind.CLASS); public final JavaClass outer; @@ -97,6 +100,19 @@ public static JavaClass fromInternalName(String internalName, Kind kind) { return new JavaClass(pkg, internalName, kind, nameParts); } + public static JavaClass fromJavaClass(final Class clazz) { + if (clazz.isArray()) { + return JavaClass.fromInternalName(clazz.getName(), Kind.ARRAY); + } + if (clazz.isPrimitive()) { + throw new IllegalStateException("JavaClass cannot represent primitive types"); + } + + final String internalName = clazz.getName().replace('.', '/'); + final Kind kind = clazz.isInterface()? Kind.INTERFACE : clazz.isEnum()? Kind.ENUM : Kind.CLASS; + return JavaClass.fromInternalName(internalName, kind); + } + public static String getNameFromFile(String filename) { if (filename.indexOf('.') > 0) return filename.substring(0, filename.lastIndexOf('.')); From 5fd860f0a6fda2e71ee17b3f01275ae685cefb41 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Thu, 27 Feb 2025 20:53:10 +0100 Subject: [PATCH 10/17] Provide a way to encode Indy instructions directly in JavaWriter Signed-off-by: TheSilkMiner --- .../javabytecode/compiler/JavaIndyHelper.java | 468 ++++++++++++++++++ .../javabytecode/compiler/JavaWriter.java | 16 +- .../compiler/lambda/LambdaIndyCompiler.java | 45 +- 3 files changed, 493 insertions(+), 36 deletions(-) create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaIndyHelper.java diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaIndyHelper.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaIndyHelper.java new file mode 100644 index 00000000..37351d6d --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaIndyHelper.java @@ -0,0 +1,468 @@ +package org.openzen.zenscript.javabytecode.compiler; + +import org.objectweb.asm.ConstantDynamic; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.openzen.zenscript.javashared.JavaClass; +import org.openzen.zenscript.javashared.JavaModifiers; +import org.openzen.zenscript.javashared.JavaNativeMethod; +import org.openzen.zenscript.javashared.compiling.JavaCompilingMethod; + +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +public final class JavaIndyHelper { + public static final class Builder { + static final BsmData BUILDING = new BsmData(null, null); + + private final Constructor constructor; + + private String callSiteMethodName; + private String callSiteMethodDesc; + private BsmData bsm; + + Builder(final Constructor constructor) { + this.constructor = constructor; + this.callSiteMethodName = null; + this.callSiteMethodDesc = null; + this.bsm = null; + } + + public Builder callSite(final String name, final Type desc) { + Objects.requireNonNull(name, "Call site name cannot be null"); + Objects.requireNonNull(desc, "Call site descriptor cannot be null"); + if (desc.getSort() != Type.METHOD) { + throw new IllegalArgumentException("Call site type must be a valid method descriptor"); + } + if (this.callSiteMethodName != null) { + throw new IllegalStateException("Call site has already been set"); + } + this.callSiteMethodName = name; + this.callSiteMethodDesc = desc.getDescriptor(); + return this; + } + + public Builder bootstrapMethod(final UnaryOperator bsmDataBuilder) { + if (this.bsm != null) { + throw new IllegalStateException("BSM has already been specified or is being currently built"); + } + this.bsm = BUILDING; + return this.bootstrapMethod(bsmDataBuilder.apply(new BsmDataBuilder(BsmDataBuilder.Kind.METHOD)).build()); + } + + private Builder bootstrapMethod(final BsmData data) { + if (this.bsm != BUILDING) { + throw new IllegalStateException("Unable to set BSM without building it"); + } + this.bsm = data; + return this; + } + + JavaIndyHelper build() { + if (this.callSiteMethodName == null) { + throw new IllegalStateException("Call site has not been specified"); + } + if (this.bsm == null || this.bsm == BUILDING) { + throw new IllegalStateException("BSM data has not been specified or has not been built"); + } + return this.constructor.of(this.callSiteMethodName, this.callSiteMethodDesc, this.bsm.method(), this.bsm.args()); + } + } + + public static final class BsmDataBuilder { + public enum InvocationKind { + STATIC, + VIRTUAL, + SPECIAL, + INTERFACE + } + + private enum Kind { + METHOD(it -> IndyHelpers.isValidBsm(it, false), it -> IndyHelpers.autoFillBsmDesc(false, it)), + CONSTANT(it -> IndyHelpers.isValidBsm(it, true), it -> IndyHelpers.autoFillBsmDesc(true, it)); + + private final Predicate bsmVerifier; + private final Function, String> descAutoFiller; + + Kind(final Predicate bsmVerifier, final Function, String> descAutoFiller) { + this.bsmVerifier = bsmVerifier; + this.descAutoFiller = descAutoFiller; + } + + boolean verify(final JavaNativeMethod method) { + return this.bsmVerifier.test(method); + } + + String autoFillDesc(final List args) { + return this.descAutoFiller.apply(args); + } + } + + private final Kind kind; + private final List args; + + private JavaNativeMethod bsmMethod; + private JavaClass owner; + private String name; + + BsmDataBuilder(final Kind kind) { + this.kind = kind; + this.args = new ArrayList<>(); + + this.bsmMethod = null; + this.owner = null; + this.name = null; + } + + public BsmDataBuilder method(final JavaNativeMethod bsmMethod) { + Objects.requireNonNull(bsmMethod, "BSM Method specified cannot be null"); + if (!this.kind.verify(bsmMethod)) { + throw new IllegalArgumentException("Provided BSM must be a valid BSM"); + } + if (this.owner != null) { + throw new IllegalStateException("BSM has already been specified in 'autodetect' mode"); + } + if (this.bsmMethod != null) { + throw new IllegalStateException("BSM has already been specified"); + } + this.bsmMethod = bsmMethod; + return this; + } + + public BsmDataBuilder method(final JavaClass owner, final String name) { + Objects.requireNonNull(owner, "BSM owner cannot be null"); + Objects.requireNonNull(name, "BSM name cannot be null"); + if (this.bsmMethod != null) { + throw new IllegalStateException("BSM has already been specified in 'fixed' mode"); + } + if (this.owner != null) { + throw new IllegalStateException("BSM has already been specified"); + } + this.owner = owner; + this.name = name; + return this; + } + + public BsmDataBuilder arg(final int argument) { + return this.arg((Object) argument); + } + + public BsmDataBuilder arg(final float argument) { + return this.arg((Object) argument); + } + + public BsmDataBuilder arg(final long argument) { + return this.arg((Object) argument); + } + + public BsmDataBuilder arg(final double argument) { + return this.arg((Object) argument); + } + + public BsmDataBuilder arg(final String argument) { + return this.arg((Object) argument); + } + + public BsmDataBuilder arg(final JavaClass clazz) { + return this.arg((Object) clazz); + } + + public BsmDataBuilder arg(final Class clazz) { + return this.arg((Object) clazz); + } + + public BsmDataBuilder arg(final Type type) { + return this.arg((Object) type); + } + + public BsmDataBuilder arg(final Type returnType, final Type... arguments) { + return this.arg(Type.getMethodType(returnType, arguments)); + } + + public BsmDataBuilder arg(final String returnType, final String... arguments) { + return this.arg(Type.getType(returnType), Stream.of(arguments).map(Type::getType).toArray(Type[]::new)); + } + + public BsmDataBuilder arg(final JavaCompilingMethod method) { + return this.arg((Object) method); + } + + public BsmDataBuilder arg(final JavaNativeMethod method) { + return this.arg((Object) method); + } + + public BsmDataBuilder arg(final InvocationKind kind, final JavaClass owner, final String name, final Type desc) { + switch (kind) { + case STATIC: return this.arg(JavaNativeMethod.getStatic(owner, name, desc.getDescriptor(), JavaModifiers.STATIC)); + case SPECIAL: { + if ("".equals(name)) { + return this.arg(JavaNativeMethod.getConstructor(owner, desc.getDescriptor(), 0)); + } else { + return this.arg(JavaNativeMethod.getVirtual(owner, name, desc.getDescriptor(), 0)); + } + } + case VIRTUAL: return this.arg(JavaNativeMethod.getVirtual(owner, name, desc.getDescriptor(), 0)); + case INTERFACE: return this.arg(JavaNativeMethod.getInterface(owner, name, desc.getDescriptor())); + } + throw new IllegalArgumentException(String.valueOf(kind)); + } + + public BsmDataBuilder arg(final String name, final JavaClass type, final UnaryOperator constantBuilder) { + return this.arg(name, Type.getType(type.internalName), constantBuilder); + } + + public BsmDataBuilder arg(final String name, final Class type, final UnaryOperator constantBuilder) { + return this.arg(name, Type.getType(type), constantBuilder); + } + + public BsmDataBuilder arg(final String name, final Type type, final UnaryOperator constantBuilder) { + return this.arg(new ConstantDynamicData(name, type, constantBuilder.apply(new BsmDataBuilder(Kind.CONSTANT)).build())); + } + + private BsmDataBuilder arg(final Object argument) { + Objects.requireNonNull(argument, "Null arguments are not supported when invoking BSMs"); + this.args.add(IndyHelpers.toBsmArg(argument)); + return this; + } + + BsmData build() { + if (this.bsmMethod == null && this.owner == null) { + throw new IllegalStateException("BSM has not been specified"); + } + final JavaNativeMethod method = this.findBsmMethod(); + return new BsmData(method, this.args); + } + + private JavaNativeMethod findBsmMethod() { + if (this.bsmMethod != null) { + return this.bsmMethod; + } + + return JavaNativeMethod.getStatic(this.owner, this.name, this.kind.autoFillDesc(this.args), JavaModifiers.PUBLIC | JavaModifiers.STATIC); + } + } + + private static final class BsmData { + private final JavaNativeMethod method; + private final List args; + + BsmData(final JavaNativeMethod method, final List args) { + this.method = method; + this.args = args; + } + + JavaNativeMethod method() { + return this.method; + } + + List args() { + return this.args; + } + } + + private static final class ConstantDynamicData { + private final String name; + private final Type type; + private final BsmData bsmData; + + ConstantDynamicData(final String name, final Type type, final BsmData bsmData) { + this.name = name; + this.type = type; + this.bsmData = bsmData; + } + + String name() { + return this.name; + } + + Type type() { + return this.type; + } + + BsmData bsm() { + return this.bsmData; + } + } + + @FunctionalInterface + interface IndyVisitor { + void visitInvokeDynamic(final String methodName, final String methodDesc, final Handle bsmMethod, final Object... bsmArgs); + } + + @FunctionalInterface + private interface Constructor { + JavaIndyHelper of(final String callSiteMethodName, final String callSiteMethodDesc, final JavaNativeMethod bsmMethod, final List bsmArgs); + } + + private static final class IndyHelpers { + private static final Type CALL_SITE_TYPE = Type.getType(CallSite.class); + private static final Type METHOD_HANDLES_LOOKUP_TYPE = Type.getType(MethodHandles.Lookup.class); + private static final Type STRING_TYPE = Type.getType(String.class); + private static final Type METHOD_TYPE_TYPE = Type.getType(MethodType.class); + private static final Type CLASS_TYPE = Type.getType(Class.class); + private static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); + + private IndyHelpers() {} + + static boolean isValidBsm(final JavaNativeMethod method, final boolean constant) { + final int modifiers = method.modifiers; + if ((modifiers & JavaModifiers.PUBLIC) == 0) { + return false; + } + if ((modifiers & JavaModifiers.STATIC) == 0) { + return false; + } + + final Type type = Type.getMethodType(method.descriptor); + if (!CALL_SITE_TYPE.equals(type.getReturnType())) { + return false; + } + + final Type[] args = type.getArgumentTypes(); + final Type lastType = constant? CLASS_TYPE : METHOD_TYPE_TYPE; + return args.length >= 3 && METHOD_HANDLES_LOOKUP_TYPE.equals(args[0]) && STRING_TYPE.equals(args[1]) && lastType.equals(args[2]); + } + + static String autoFillBsmDesc(final boolean constant, final List args) { + return Type.getMethodDescriptor( + CALL_SITE_TYPE, + Stream.concat( + Stream.of(METHOD_HANDLES_LOOKUP_TYPE, STRING_TYPE, constant? CLASS_TYPE : METHOD_TYPE_TYPE), + args.stream().map(IndyHelpers::toBsmArg).map(IndyHelpers::toBsmArgType) + ).toArray(Type[]::new) + ); + } + + static Handle toBsmHandle(final JavaNativeMethod method) { + return new Handle( + "".equals(method.name)? Opcodes.H_INVOKESPECIAL : Opcodes.H_INVOKESTATIC, + method.cls.internalName, + method.name, + method.descriptor, + method.cls.isInterface() + ); + } + + static Object[] toBsmArgs(final List args) { + return args.stream().map(IndyHelpers::toBsmArg).toArray(Object[]::new); + } + + static Object toBsmArg(final Object object) { + if (object instanceof Integer || object instanceof Float || object instanceof Double || object instanceof Long || object instanceof String) { + return object; + } + if (object instanceof Type || object instanceof Handle || object instanceof ConstantDynamic) { + return object; + } + if (object instanceof JavaClass) { + return Type.getType(((JavaClass) object).internalName); + } + if (object instanceof Class) { + return Type.getType((Class) object); + } + if (object instanceof JavaNativeMethod || object instanceof JavaCompilingMethod) { + final JavaNativeMethod method = object instanceof JavaCompilingMethod? ((JavaCompilingMethod) object).compiled : (JavaNativeMethod) object; + + final int invokeType; + if ((method.modifiers & JavaModifiers.STATIC) != 0) { + invokeType = Opcodes.H_INVOKESTATIC; + } else if (method.cls.isInterface()) { + invokeType = Opcodes.H_INVOKEINTERFACE; + } else if ("".equals(method.name) || (method.modifiers & JavaModifiers.PRIVATE) != 0) { + invokeType = Opcodes.H_INVOKESPECIAL; + } else { + invokeType = Opcodes.H_INVOKEVIRTUAL; + } + + return new Handle( + invokeType, + method.cls.internalName, + method.name, + method.descriptor, + method.cls.isInterface() + ); + } + if (object instanceof ConstantDynamicData) { + final ConstantDynamicData data = (ConstantDynamicData) object; + final BsmData bsmData = data.bsm(); + return new ConstantDynamic(data.name(), data.type().getDescriptor(), toBsmHandle(bsmData.method()), toBsmArgs(bsmData.args())); + } + throw new IllegalArgumentException("Unrecognized or incompatible indy construct " + object.getClass().getName()); + } + + static Type toBsmArgType(final Object object) { + final Object realObject = toBsmArg(object); + if (realObject instanceof Integer) { + return Type.INT_TYPE; + } + if (realObject instanceof Float) { + return Type.FLOAT_TYPE; + } + if (realObject instanceof Double) { + return Type.DOUBLE_TYPE; + } + if (realObject instanceof Long) { + return Type.LONG_TYPE; + } + if (realObject instanceof String) { + return STRING_TYPE; + } + if (realObject instanceof Type) { + final int sort = ((Type) realObject).getSort(); + if (sort == Type.METHOD) { + return METHOD_TYPE_TYPE; + } else { + return CLASS_TYPE; + } + } + if (realObject instanceof Handle) { + return METHOD_HANDLE_TYPE; + } + if (realObject instanceof ConstantDynamicData) { + final ConstantDynamicData data = (ConstantDynamicData) realObject; + return data.type(); + } + throw new IllegalArgumentException("Unrecognized or incompatible indy construct " + realObject.getClass().getName()); + } + } + + private final String callSiteMethodName; + private final String callSiteMethodDesc; + private final JavaNativeMethod bsmMethod; + private final List bsmArgs; + + private JavaIndyHelper(final String callSiteMethodName, final String callSiteMethodDesc, final JavaNativeMethod bsmMethod, final List bsmArgs) { + this.callSiteMethodName = callSiteMethodName; + this.callSiteMethodDesc = callSiteMethodDesc; + this.bsmMethod = bsmMethod; + this.bsmArgs = bsmArgs; + } + + static JavaIndyHelper.Builder builder() { + return new Builder(JavaIndyHelper::new); + } + + void visit(final IndyVisitor visitor) { + visitor.visitInvokeDynamic( + this.callSiteMethodName, + this.callSiteMethodDesc, + IndyHelpers.toBsmHandle(this.bsmMethod), + IndyHelpers.toBsmArgs(this.bsmArgs) + ); + } + + @Override + public String toString() { + return this.callSiteMethodName + this.callSiteMethodDesc + " (through " + this.bsmMethod.cls.internalName + '.' + this.bsmMethod.name + this.bsmMethod.descriptor + ')'; + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaWriter.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaWriter.java index 0c0b7473..d648f1f0 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaWriter.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaWriter.java @@ -15,13 +15,12 @@ import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.UnaryOperator; import static org.objectweb.asm.Opcodes.*; @@ -1086,7 +1085,7 @@ public void invokeSpecial(String ownerInternalName, String name, String descript visitor.visitMethodInsn(INVOKESPECIAL, ownerInternalName, name, descriptor, false); } - public void invokeSpecial(Class owner, String name, String descriptor) { + public void invokeSpecial(Class owner, String name, String descriptor) { invokeSpecial(Type.getInternalName(owner), name, descriptor); } @@ -1112,6 +1111,17 @@ public void invokeInterface(JavaNativeMethod method) { visitor.visitMethodInsn(INVOKEINTERFACE, method.cls.internalName, method.name, method.descriptor, true); } + public void invokeDynamic(UnaryOperator indyDataBuilder) { + invokeDynamic(indyDataBuilder.apply(JavaIndyHelper.builder()).build()); + } + + public void invokeDynamic(JavaIndyHelper indyData) { + if (debug) + logger.debug("invokeDynamic " + indyData); + + indyData.visit(visitor::visitInvokeDynamicInsn); + } + public void newObject(String internalName) { if (debug) logger.debug("newObject " + internalName); diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java index dea66f5c..6fc9a8f0 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java @@ -13,10 +13,12 @@ import org.openzen.zenscript.javabytecode.JavaLocalVariableInfo; import org.openzen.zenscript.javabytecode.JavaMangler; import org.openzen.zenscript.javabytecode.compiler.JavaExpressionVisitor; +import org.openzen.zenscript.javabytecode.compiler.JavaIndyHelper; import org.openzen.zenscript.javabytecode.compiler.JavaStatementVisitor; import org.openzen.zenscript.javabytecode.compiler.JavaWriter; import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.*; import org.openzen.zenscript.javabytecode.compiler.definitions.JavaMemberVisitor; +import org.openzen.zenscript.javart.factory.LambdaFactory; import org.openzen.zenscript.javashared.JavaClass; import org.openzen.zenscript.javashared.JavaCompiledModule; import org.openzen.zenscript.javashared.JavaNativeMethod; @@ -182,40 +184,17 @@ private void generateIndyMethodCall( capture.accept(othersVisitor); } - // TODO("Have this in JavaWriter") - this.writer.getVisitor().visitInvokeDynamicInsn( - methodInfo.name, - this.computeIndyDescriptor(closureInfo, interfaceType), - new org.objectweb.asm.Handle( - Opcodes.H_INVOKESTATIC, - Type.getInternalName(org.openzen.zenscript.javart.factory.LambdaFactory.class), - "buildLambda", - Type.getMethodDescriptor( - Type.getType(java.lang.invoke.CallSite.class), - Type.getType(java.lang.invoke.MethodHandles.Lookup.class), - Type.getType(String.class), - Type.getType(java.lang.invoke.MethodType.class), - Type.getType(java.lang.invoke.MethodHandle.class), - Type.getType(java.lang.invoke.MethodType.class), - Type.INT_TYPE, - Type.getType(java.lang.invoke.MethodType.class) - ), - false - ), - new org.objectweb.asm.Handle( - Opcodes.H_INVOKESTATIC, - owner.internalName, - method.compiled.name, - method.compiled.descriptor, - false - ), - Type.getMethodType(JavaMemberVisitor.compileBridgeableMethodNoSideEffect(methodInfo, context.getMethodDescriptor(lambdaExpression.header)).compiled.descriptor), - org.openzen.zenscript.javart.factory.LambdaFactory.FLAG_GENERATE_BRIDGE, - Type.getMethodType(methodInfo.descriptor) - ); + this.writer.invokeDynamic(indy -> indy + .callSite(methodInfo.name, this.computeIndyDescriptor(closureInfo, interfaceType)) + .bootstrapMethod(bsm -> bsm + .method(JavaClass.fromJavaClass(LambdaFactory.class), "buildLambda") + .arg(method) + .arg(Type.getMethodType(JavaMemberVisitor.compileBridgeableMethodNoSideEffect(methodInfo, context.getMethodDescriptor(lambdaExpression.header)).compiled.descriptor)) + .arg(LambdaFactory.FLAG_GENERATE_BRIDGE) + .arg(Type.getMethodType(methodInfo.descriptor)))); } - private String computeIndyDescriptor(final LambdaClosureInfo closureInfo, final String targetInterface) { + private Type computeIndyDescriptor(final LambdaClosureInfo closureInfo, final String targetInterface) { final StringBuilder builder = new StringBuilder("("); // TODO("Remove null check as this method should never return null") @@ -234,7 +213,7 @@ private String computeIndyDescriptor(final LambdaClosureInfo closureInfo, final } builder.append(joiner).append(")L").append(targetInterface).append(';'); - return builder.toString(); + return Type.getType(builder.toString()); } } From 3be9cf7479695b643bfeaa8840d5a70e8b437d65 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Thu, 27 Feb 2025 21:46:28 +0100 Subject: [PATCH 11/17] Allow stacktrace inspection in debug mode Signed-off-by: TheSilkMiner --- .../java_native/FunctionalInterfaceTests.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/FunctionalInterfaceTests.java b/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/FunctionalInterfaceTests.java index ea1daff3..6f6e0d6b 100644 --- a/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/FunctionalInterfaceTests.java +++ b/ScriptingExample/src/test/java/org/openzen/zenscript/scriptingexample/tests/actual_test/java_native/FunctionalInterfaceTests.java @@ -29,7 +29,7 @@ public List getRequiredStdLibModules() { @Test void testFunctionalInterface() { addScript( - "var modified = modifyString('test', (strings, context) => { return strings; });\n" + + "var modified = modifyString('test', (strings, context) => { return straightUpItself(strings); });\n" + "println(modified.length);", "FunctionalInterfaceTests_testFunctionalInterface.zs"); @@ -44,7 +44,7 @@ void testFunctionalInterface() { @Test void testBiFunction() { addScript( - "var modified = stringFunction('test', (strings, context) => {return strings;});\n" + + "var modified = stringFunction('test', (strings, context) => { return straightUpItself(strings); });\n" + "println(modified.length);", "FunctionalInterfaceTests_testBiFunction.zs"); @@ -67,6 +67,11 @@ public static List modifyString(String baseString, StringModifier modifi public static List stringFunction(String baseString, BiFunction, Boolean, List> function) { return function.apply(Collections.singletonList(baseString), false); } + + @ZenCodeGlobals.Global + public static List straightUpItself(List list) { + return list; + } } @FunctionalInterface From c90f9dc81da39a70e3a17e5f2f39926fabb80e3c Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Thu, 27 Feb 2025 21:59:42 +0100 Subject: [PATCH 12/17] Move FunctionalInterface conversion to Indy too Signed-off-by: TheSilkMiner --- .../zenscript/javabytecode/JavaMangler.java | 36 ---- .../compiler/JavaExpressionVisitor.java | 190 ++---------------- .../compiler/lambda/LambdaIndyCompiler.java | 57 +++++- 3 files changed, 67 insertions(+), 216 deletions(-) diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java index 7186a43a..b3976363 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java @@ -153,42 +153,6 @@ public int hashCode() { return "$lambda$" + sanitizedMethodName + '$' + canonicalInterfaceTarget + '$' + this.mangleCounters.get(id); } - public String mangleGeneratedLambdaName(final String interfaceName) { - final class LambdaId { - final String target; - - LambdaId(final String target) { - this.target = target; - } - - @Override - public boolean equals(final Object o) { - return this == o || o instanceof LambdaId && this.target.equals(((LambdaId) o).target); - } - - @Override - public int hashCode() { - return 17 * this.target.hashCode(); - } - } - - final String interfaceTarget = interfaceName.replace('/', '_').replace('.', '_'); - // TODO("Rework package structure") - return "zsynthetic/$Lambda$" + interfaceTarget + '$' + this.mangleCounters.get(new LambdaId(interfaceTarget)); - } - - public String mangleGeneratedLambdaName(final FunctionHeader header) { - return this.mangleGeneratedLambdaName("$Generated" + EXP_TAR_MANGLE_FUNCTION_ID + this.encodeLengthNameFormat(this.mangleFunctionHeader(header))); - } - - public String mangleCapturedParameter(final int parameterId, final boolean isThis) { - if (isThis) { - return "$this"; - } else { - return "$" + parameterId; - } - } - private String mangleScriptName(final String rawName) { if (rawName == null) { class GeneratedBlock {} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java index 3dbd6185..d13320cc 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaExpressionVisitor.java @@ -1,10 +1,7 @@ package org.openzen.zenscript.javabytecode.compiler; -import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import org.openzen.zencode.shared.CodePosition; import org.openzen.zenscript.codemodel.CompareType; import org.openzen.zenscript.codemodel.FunctionHeader; import org.openzen.zenscript.codemodel.OperatorType; @@ -25,12 +22,9 @@ import org.openzen.zenscript.javabytecode.compiler.lambda.LambdaIndyCompiler; import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.JavaInvalidCapturedExpressionVisitor; import org.openzen.zenscript.javashared.*; -import org.openzen.zenscript.javashared.compiling.JavaCompilingMethod; import org.openzen.zenscript.javashared.expressions.JavaFunctionInterfaceCastExpression; import org.openzen.zenscript.javashared.types.JavaFunctionalInterfaceTypeID; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.*; import static org.openzen.zenscript.javabytecode.compiler.JavaMethodBytecodeCompiler.OBJECT_HASHCODE; @@ -50,6 +44,7 @@ public class JavaExpressionVisitor implements ExpressionVisitor { private final JavaUnboxingTypeVisitor optionalUnwrappingTypeVisitor; private final JavaFieldBytecodeCompiler fieldCompiler; public final JavaMethodBytecodeCompiler methodCompiler; + private final LambdaIndyCompiler lambdaIndyCompiler; private final CapturedExpressionVisitor capturedExpressionVisitor; public JavaExpressionVisitor(JavaBytecodeContext context, JavaCompiledModule module, JavaWriter javaWriter, JavaMangler javaMangler) { @@ -67,6 +62,7 @@ public JavaExpressionVisitor(JavaBytecodeContext context, JavaCompiledModule mod optionalUnwrappingTypeVisitor = JavaUnboxingTypeVisitor.forOptionalUnwrapping(javaWriter); fieldCompiler = new JavaFieldBytecodeCompiler(javaWriter, this, true); methodCompiler = new JavaMethodBytecodeCompiler(javaWriter, this, context, module); + this.lambdaIndyCompiler = LambdaIndyCompiler.of(this.javaWriter, this.javaMangler, this.context, this.module, this); this.capturedExpressionVisitor = capturedExpressionVisitor; } @@ -488,8 +484,7 @@ public Void visitFunction(FunctionExpression expression) { final JavaNativeMethod functionalMethod = context.getFunctionalInterface(expression.original == null ? expression.type : new FunctionTypeID(expression.original)); final JavaNativeMethod methodInfo = functionalMethod.withModifiers(functionalMethod.modifiers & ~JavaModifiers.ABSTRACT); - final LambdaIndyCompiler lambdaCompiler = LambdaIndyCompiler.of(this.javaWriter, this.javaMangler, this.context, this.module, this); - lambdaCompiler.compileFunctionExpressionViaIndy(expression, interfaceName, methodInfo); + this.lambdaIndyCompiler.compileFunctionExpressionViaIndy(expression, interfaceName, methodInfo); return null; } @@ -869,16 +864,17 @@ private void modify(ModifiableExpression source, Runnable modification, PushOpti @Override public Void visitPlatformSpecific(Expression expression) { - if (expression instanceof JavaFunctionInterfaceCastExpression) { - JavaFunctionInterfaceCastExpression jficExpression = (JavaFunctionInterfaceCastExpression) expression; - if (jficExpression.value.type instanceof JavaFunctionalInterfaceTypeID) { - jficExpression.value.accept(this); - } else { - visitFunctionalInterfaceWrapping(jficExpression); - } - } else { - throw new AssertionError("Unrecognized platform expression: " + expression); + if (!(expression instanceof JavaFunctionInterfaceCastExpression)) { + throw new AssertionError("Unrecognized platform expression " + expression.getClass().getName() + ": " + expression); + } + + final JavaFunctionInterfaceCastExpression jficExpression = (JavaFunctionInterfaceCastExpression) expression; + + if (jficExpression.value.type instanceof JavaFunctionalInterfaceTypeID) { + return jficExpression.value.accept(this); } + + this.lambdaIndyCompiler.convertTypeOfFunctionExpressionViaIndy(jficExpression); return null; } @@ -906,9 +902,11 @@ public void modify(ModificationExpression expression, PushOption pushOption) { BuiltinMethodSymbol builtin = (BuiltinMethodSymbol) expression.method.method; modify(expression.target, builtin, pushOption); } else { - modify(expression.target, () -> { - context.getJavaMethod(expression.method).compileVirtualWithTargetOnTopOfStack(methodCompiler, expression.type, CallArguments.EMPTY); - }, pushOption); + modify( + expression.target, + () -> context.getJavaMethod(expression.method).compileVirtualWithTargetOnTopOfStack(methodCompiler, expression.type, CallArguments.EMPTY), + pushOption + ); } } @@ -981,148 +979,6 @@ public Void visitSetStaticField(SetStaticFieldExpression expression) { return null; } - private void visitFunctionalInterfaceWrapping(JavaFunctionInterfaceCastExpression expression) { - // TODO("Move to LambdaFactory") - // To do the above, we simply need to be able to "extract" this into a lambda form. - // In other words, if we have to convert (OurThing -> Function), as of now: - // 1. Generate a lambda method with the signature (ThisClass, ..., OurThing) - // 2. Fill the lambda method with simply $capture.invoke(...) - // 3. Invoke LambdaFactory passing the current OurThing instance as a capture; ThisClass can be simply always loaded as null as we always ignore it - // In the above, ... denotes parameters, **never** captures - final FunctionCastWrapperClass wrapper = generateFunctionCastWrapperClass( - expression.position, - (FunctionTypeID) expression.value.type, - expression.functionType); - - expression.value.accept(this); - javaWriter.newObject(wrapper.className); - javaWriter.dupX1(); - javaWriter.swap(); - javaWriter.invokeSpecial(wrapper.className, "", wrapper.constructorDesc); - } - - private FunctionCastWrapperClass generateFunctionCastWrapperClass(CodePosition position, FunctionTypeID fromType, FunctionTypeID toType) { - final String className = this.javaMangler.mangleGeneratedLambdaName(fromType.header); - final JavaClass classInfo = JavaClass.fromInternalName(className, JavaClass.Kind.CLASS); - - String[] interfaces; - String wrappedFromSignature = context.getDescriptor(fromType); - String methodDescriptor; - String methodSignature; - Type[] methodParameterTypes; - JavaNativeMethod implementationMethod; - if (toType instanceof JavaFunctionalInterfaceTypeID) { - JavaNativeMethod javaMethod = ((JavaFunctionalInterfaceTypeID) toType).method; - implementationMethod = new JavaNativeMethod( - classInfo, - JavaNativeMethod.Kind.COMPILED, - javaMethod.name, - true, - javaMethod.descriptor, - javaMethod.modifiers & ~JavaModifiers.ABSTRACT, - javaMethod.genericResult, - javaMethod.typeParameterArguments); - - final Method functionalInterfaceMethod = ((JavaFunctionalInterfaceTypeID) toType).functionalInterfaceMethod; - - methodDescriptor = Type.getMethodDescriptor(functionalInterfaceMethod); - // ToDo: Is signature===descriptor fine here or do we need the actual signature here? - methodSignature = methodDescriptor; - interfaces = new String[]{Type.getInternalName(functionalInterfaceMethod.getDeclaringClass())}; - - final Class[] methodParameterClasses = functionalInterfaceMethod.getParameterTypes(); - methodParameterTypes = new Type[methodParameterClasses.length]; - for (int i = 0; i < methodParameterClasses.length; i++) { - final Class methodParameterType = methodParameterClasses[i]; - methodParameterTypes[i] = Type.getType(methodParameterType); - } - } else { - wrappedFromSignature = context.getMethodSignature(toType.header, true); - methodDescriptor = context.getMethodDescriptor(toType.header); - methodSignature = context.getMethodSignature(toType.header); - interfaces = new String[]{context.getInternalName(toType)}; - - JavaSynthesizedFunctionInstance function = context.getFunction(toType); - - implementationMethod = new JavaNativeMethod( - classInfo, - JavaNativeMethod.Kind.COMPILED, - function.getMethod(), - true, - methodDescriptor, - JavaModifiers.PUBLIC, - false // TODO: generic result or not - ); - - methodParameterTypes = new Type[toType.header.parameters.length]; - for (int i = 0; i < methodParameterTypes.length; i++) { - methodParameterTypes[i] = context.getType(toType.header.parameters[i].type); - } - } - - final JavaNativeMethod wrappedMethod = context.getFunctionalInterface(fromType); - final String constructorDescriptor = "(" + wrappedFromSignature + ")V"; - // ToDo: Is signature===descriptor fine here or do we need the actual signature here? - final String constructorSignature = "(" + wrappedFromSignature + ")V"; - - final ClassWriter lambdaCW = new JavaClassWriter(ClassWriter.COMPUTE_FRAMES); - lambdaCW.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", interfaces); - - //The field storing the wrapped object - { - lambdaCW.visitField(Modifier.PRIVATE | Modifier.FINAL, "wrapped", wrappedFromSignature, null, null).visitEnd(); - } - - //Constructor - { - JavaNativeMethod constructor = JavaNativeMethod.getConstructor(classInfo, constructorDescriptor, Opcodes.ACC_PUBLIC); - JavaCompilingMethod compiling = new JavaCompilingMethod(constructor, constructorSignature); - final JavaWriter constructorWriter = new JavaWriter(context.logger, position, lambdaCW, compiling, null); - constructorWriter.start(); - constructorWriter.loadObject(0); - constructorWriter.dup(); - constructorWriter.invokeSpecial(Object.class, "", "()V"); - - constructorWriter.loadObject(1); - constructorWriter.putField(className, "wrapped", wrappedFromSignature); - - constructorWriter.ret(); - constructorWriter.end(); - } - - //The actual method - { - JavaCompilingMethod compiling = new JavaCompilingMethod(implementationMethod, methodSignature); - final JavaWriter functionWriter = new JavaWriter(context.logger, position, lambdaCW, compiling, null); - functionWriter.start(); - - //this.wrapped - functionWriter.loadObject(0); - functionWriter.getField(className, "wrapped", wrappedFromSignature); - - //Load all function parameters - for (int i = 0, currentLocalPosition = 1; i < methodParameterTypes.length; i++) { - functionWriter.load(methodParameterTypes[i], currentLocalPosition); - currentLocalPosition += methodParameterTypes[i].getSize(); - } - - //Invokes the wrapped interface's method and returns the result - functionWriter.invokeInterface(wrappedMethod); - final TypeID returnType = fromType.header.getReturnType(); - final Type rtype = context.getType(returnType); - if (!CompilerUtils.isPrimitive(returnType)) { - functionWriter.checkCast(rtype); - } - functionWriter.returnType(rtype); - functionWriter.end(); - } - - lambdaCW.visitEnd(); - context.register(className, lambdaCW.toByteArray()); - - return new FunctionCastWrapperClass(className, constructorDescriptor); - } - @Override public Void visitSupertypeCast(SupertypeCastExpression expression) { expression.value.accept(this); @@ -1261,14 +1117,4 @@ private Void tagVariableAndUpdateLastUsage(VariableID id) { public JavaWriter getJavaWriter() { return javaWriter; } - - private static class FunctionCastWrapperClass { - final String className; - final String constructorDesc; - - FunctionCastWrapperClass(String className, String constructorDesc) { - this.className = className; - this.constructorDesc = constructorDesc; - } - } } diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java index 6fc9a8f0..cb425d8c 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java @@ -1,28 +1,28 @@ package org.openzen.zenscript.javabytecode.compiler.lambda; import org.objectweb.asm.Label; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.openzen.zenscript.codemodel.FunctionHeader; import org.openzen.zenscript.codemodel.FunctionParameter; +import org.openzen.zenscript.codemodel.expression.Expression; import org.openzen.zenscript.codemodel.expression.FunctionExpression; import org.openzen.zenscript.codemodel.expression.captured.CapturedExpression; import org.openzen.zenscript.codemodel.expression.captured.CapturedExpressionVisitor; import org.openzen.zenscript.codemodel.expression.captured.CapturedThisExpression; +import org.openzen.zenscript.codemodel.type.FunctionTypeID; import org.openzen.zenscript.javabytecode.JavaBytecodeContext; import org.openzen.zenscript.javabytecode.JavaLocalVariableInfo; import org.openzen.zenscript.javabytecode.JavaMangler; import org.openzen.zenscript.javabytecode.compiler.JavaExpressionVisitor; -import org.openzen.zenscript.javabytecode.compiler.JavaIndyHelper; import org.openzen.zenscript.javabytecode.compiler.JavaStatementVisitor; import org.openzen.zenscript.javabytecode.compiler.JavaWriter; import org.openzen.zenscript.javabytecode.compiler.lambda.capturing.*; import org.openzen.zenscript.javabytecode.compiler.definitions.JavaMemberVisitor; import org.openzen.zenscript.javart.factory.LambdaFactory; -import org.openzen.zenscript.javashared.JavaClass; -import org.openzen.zenscript.javashared.JavaCompiledModule; -import org.openzen.zenscript.javashared.JavaNativeMethod; +import org.openzen.zenscript.javashared.*; import org.openzen.zenscript.javashared.compiling.JavaCompilingMethod; +import org.openzen.zenscript.javashared.expressions.JavaFunctionInterfaceCastExpression; +import org.openzen.zenscript.javashared.types.JavaFunctionalInterfaceTypeID; import java.util.StringJoiner; @@ -52,10 +52,17 @@ public void compileFunctionExpressionViaIndy(final FunctionExpression expression final JavaNativeMethod lambdaMethod = this.createLambdaMethod(owner, interfaceName, closureInfo, expression); final JavaCompilingMethod lambdaMethodCompiling = JavaMemberVisitor.compileBridgeableMethodNoSideEffect(lambdaMethod, lambdaMethod.descriptor); // TODO("Restore signatures") - this.generateIndyMethodCall(owner, lambdaMethodCompiling, expression, methodInfo, closureInfo, interfaceName); + this.generateIndyMethodCall(lambdaMethodCompiling, expression, methodInfo, closureInfo, interfaceName); this.generateLambdaMethod(lambdaMethodCompiling, expression, closureInfo); } + public void convertTypeOfFunctionExpressionViaIndy(final JavaFunctionInterfaceCastExpression expression) { + final FunctionTypeID fromType = (FunctionTypeID) expression.value.type; + final FunctionTypeID toType = expression.functionType; + + this.generateConversionInstructionsForLambdaType(expression.value, fromType, toType); + } + private JavaNativeMethod createLambdaMethod( final JavaClass owner, final String interfaceName, @@ -65,7 +72,7 @@ private JavaNativeMethod createLambdaMethod( // TODO("Figure out why IntelliJ complains about NPE") final String lambdaName = this.mangler.mangleLambdaMethod(this.writer.method.compiled.name, interfaceName); final String lambdaDescriptor = this.computeLambdaDescriptor(closureInfo, lambdaExpression.header); - return JavaNativeMethod.getStatic(owner, lambdaName, lambdaDescriptor, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); + return JavaNativeMethod.getStatic(owner, lambdaName, lambdaDescriptor, JavaModifiers.PRIVATE | JavaModifiers.STATIC); } private String computeLambdaDescriptor(final LambdaClosureInfo closureInfo, final FunctionHeader header) { @@ -159,7 +166,6 @@ private void loadLambdaVariables( } private void generateIndyMethodCall( - final JavaClass owner, final JavaCompilingMethod method, final FunctionExpression lambdaExpression, final JavaNativeMethod methodInfo, @@ -216,4 +222,39 @@ private Type computeIndyDescriptor(final LambdaClosureInfo closureInfo, final St return Type.getType(builder.toString()); } + private void generateConversionInstructionsForLambdaType(final Expression body, final FunctionTypeID fromType, final FunctionTypeID toType) { + // To do the above, we simply need to be able to "extract" this into a lambda form. + // In other words, if we have to convert (OurThing -> Function), we can invoke LambdaFactory with the following + // parameters: + // call site -> Function(OurThing) + // lambda method -> virtual invocation on OurThing (virtual means we'll automatically consume the "this" capture) + // interfaceSignature -> the signature of the method as specified by OurThing + // flags -> generate bridge + // bridgeInterfaceSignature -> the signature of the method as specified by Function + //final JavaNativeMethod fromNativeDescription = this.findNativeMethodDescriptionOfMethod(fromType); + final JavaNativeMethod fromNativeDescription = this.context.getFunctionalInterface(fromType); + final JavaNativeMethod toNativeDescription = this.findNativeMethodDescriptionOfMethod(toType); + + body.accept(this.visitor); + this.writer.invokeDynamic(indy -> indy + .callSite(toNativeDescription.name, Type.getMethodType(this.context.getType(toType), this.context.getType(fromType))) + .bootstrapMethod(bsm -> bsm + .method(JavaClass.fromJavaClass(LambdaFactory.class), "buildLambda") + .arg(fromNativeDescription) + .arg(Type.getMethodType(fromNativeDescription.descriptor)) + .arg(LambdaFactory.FLAG_GENERATE_BRIDGE) + .arg(Type.getMethodType(toNativeDescription.descriptor)))); + } + + private JavaNativeMethod findNativeMethodDescriptionOfMethod(final FunctionTypeID type) { + if (type instanceof JavaFunctionalInterfaceTypeID) { + final JavaFunctionalInterfaceTypeID functionalType = ((JavaFunctionalInterfaceTypeID) type); + final String descriptor = Type.getMethodDescriptor(functionalType.functionalInterfaceMethod); + return JavaNativeMethod.getVirtual(functionalType.method.cls, functionalType.method.name, descriptor, JavaModifiers.PUBLIC); + } + + final JavaSynthesizedFunctionInstance function = this.context.getFunction(type); + final String descriptor = this.context.getMethodDescriptor(type.header); + return JavaNativeMethod.getVirtual(function.getCls(), function.getMethod(), descriptor, JavaModifiers.PUBLIC); + } } From e2ca9589b6eb4814a12e7a796b9317f578ffe8dc Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Thu, 27 Feb 2025 22:34:46 +0100 Subject: [PATCH 13/17] Add private lambda test Signed-off-by: TheSilkMiner --- .../zencode_tests/lambdas/private_lambda_1.zc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 ScriptingEngineTester/src/main/resources/zencode_tests/lambdas/private_lambda_1.zc diff --git a/ScriptingEngineTester/src/main/resources/zencode_tests/lambdas/private_lambda_1.zc b/ScriptingEngineTester/src/main/resources/zencode_tests/lambdas/private_lambda_1.zc new file mode 100644 index 00000000..b71bd302 --- /dev/null +++ b/ScriptingEngineTester/src/main/resources/zencode_tests/lambdas/private_lambda_1.zc @@ -0,0 +1,17 @@ +#output: 99 bottles of beer on the wall! + +public class Foo { + foo(): void { + lambda(() => bar()); + } + + private bar(): void { + println("99 bottles of beer on the wall!"); + } + + private lambda(block: function() as void): void { + block(); + } +} + +(new Foo()).foo(); From 254a7ba8d6c3c425d92ccd2aa5e64b07783426e6 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Fri, 28 Feb 2025 11:52:06 +0100 Subject: [PATCH 14/17] Rework indy support - Fully support CONDY - Move them to their own package Signed-off-by: TheSilkMiner --- .../javabytecode/compiler/JavaIndyHelper.java | 468 ------------------ .../javabytecode/compiler/JavaWriter.java | 26 +- .../javabytecode/compiler/indy/BsmData.java | 203 ++++++++ .../compiler/indy/IndyHelpers.java | 140 ++++++ .../compiler/indy/IndyTarget.java | 51 ++ .../javabytecode/compiler/indy/JavaCondy.java | 107 ++++ .../javabytecode/compiler/indy/JavaIndy.java | 102 ++++ .../compiler/lambda/LambdaIndyCompiler.java | 4 +- 8 files changed, 625 insertions(+), 476 deletions(-) delete mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaIndyHelper.java create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/BsmData.java create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyTarget.java create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/JavaCondy.java create mode 100644 JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/JavaIndy.java diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaIndyHelper.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaIndyHelper.java deleted file mode 100644 index 37351d6d..00000000 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaIndyHelper.java +++ /dev/null @@ -1,468 +0,0 @@ -package org.openzen.zenscript.javabytecode.compiler; - -import org.objectweb.asm.ConstantDynamic; -import org.objectweb.asm.Handle; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.openzen.zenscript.javashared.JavaClass; -import org.openzen.zenscript.javashared.JavaModifiers; -import org.openzen.zenscript.javashared.JavaNativeMethod; -import org.openzen.zenscript.javashared.compiling.JavaCompilingMethod; - -import java.lang.invoke.CallSite; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.UnaryOperator; -import java.util.stream.Stream; - -public final class JavaIndyHelper { - public static final class Builder { - static final BsmData BUILDING = new BsmData(null, null); - - private final Constructor constructor; - - private String callSiteMethodName; - private String callSiteMethodDesc; - private BsmData bsm; - - Builder(final Constructor constructor) { - this.constructor = constructor; - this.callSiteMethodName = null; - this.callSiteMethodDesc = null; - this.bsm = null; - } - - public Builder callSite(final String name, final Type desc) { - Objects.requireNonNull(name, "Call site name cannot be null"); - Objects.requireNonNull(desc, "Call site descriptor cannot be null"); - if (desc.getSort() != Type.METHOD) { - throw new IllegalArgumentException("Call site type must be a valid method descriptor"); - } - if (this.callSiteMethodName != null) { - throw new IllegalStateException("Call site has already been set"); - } - this.callSiteMethodName = name; - this.callSiteMethodDesc = desc.getDescriptor(); - return this; - } - - public Builder bootstrapMethod(final UnaryOperator bsmDataBuilder) { - if (this.bsm != null) { - throw new IllegalStateException("BSM has already been specified or is being currently built"); - } - this.bsm = BUILDING; - return this.bootstrapMethod(bsmDataBuilder.apply(new BsmDataBuilder(BsmDataBuilder.Kind.METHOD)).build()); - } - - private Builder bootstrapMethod(final BsmData data) { - if (this.bsm != BUILDING) { - throw new IllegalStateException("Unable to set BSM without building it"); - } - this.bsm = data; - return this; - } - - JavaIndyHelper build() { - if (this.callSiteMethodName == null) { - throw new IllegalStateException("Call site has not been specified"); - } - if (this.bsm == null || this.bsm == BUILDING) { - throw new IllegalStateException("BSM data has not been specified or has not been built"); - } - return this.constructor.of(this.callSiteMethodName, this.callSiteMethodDesc, this.bsm.method(), this.bsm.args()); - } - } - - public static final class BsmDataBuilder { - public enum InvocationKind { - STATIC, - VIRTUAL, - SPECIAL, - INTERFACE - } - - private enum Kind { - METHOD(it -> IndyHelpers.isValidBsm(it, false), it -> IndyHelpers.autoFillBsmDesc(false, it)), - CONSTANT(it -> IndyHelpers.isValidBsm(it, true), it -> IndyHelpers.autoFillBsmDesc(true, it)); - - private final Predicate bsmVerifier; - private final Function, String> descAutoFiller; - - Kind(final Predicate bsmVerifier, final Function, String> descAutoFiller) { - this.bsmVerifier = bsmVerifier; - this.descAutoFiller = descAutoFiller; - } - - boolean verify(final JavaNativeMethod method) { - return this.bsmVerifier.test(method); - } - - String autoFillDesc(final List args) { - return this.descAutoFiller.apply(args); - } - } - - private final Kind kind; - private final List args; - - private JavaNativeMethod bsmMethod; - private JavaClass owner; - private String name; - - BsmDataBuilder(final Kind kind) { - this.kind = kind; - this.args = new ArrayList<>(); - - this.bsmMethod = null; - this.owner = null; - this.name = null; - } - - public BsmDataBuilder method(final JavaNativeMethod bsmMethod) { - Objects.requireNonNull(bsmMethod, "BSM Method specified cannot be null"); - if (!this.kind.verify(bsmMethod)) { - throw new IllegalArgumentException("Provided BSM must be a valid BSM"); - } - if (this.owner != null) { - throw new IllegalStateException("BSM has already been specified in 'autodetect' mode"); - } - if (this.bsmMethod != null) { - throw new IllegalStateException("BSM has already been specified"); - } - this.bsmMethod = bsmMethod; - return this; - } - - public BsmDataBuilder method(final JavaClass owner, final String name) { - Objects.requireNonNull(owner, "BSM owner cannot be null"); - Objects.requireNonNull(name, "BSM name cannot be null"); - if (this.bsmMethod != null) { - throw new IllegalStateException("BSM has already been specified in 'fixed' mode"); - } - if (this.owner != null) { - throw new IllegalStateException("BSM has already been specified"); - } - this.owner = owner; - this.name = name; - return this; - } - - public BsmDataBuilder arg(final int argument) { - return this.arg((Object) argument); - } - - public BsmDataBuilder arg(final float argument) { - return this.arg((Object) argument); - } - - public BsmDataBuilder arg(final long argument) { - return this.arg((Object) argument); - } - - public BsmDataBuilder arg(final double argument) { - return this.arg((Object) argument); - } - - public BsmDataBuilder arg(final String argument) { - return this.arg((Object) argument); - } - - public BsmDataBuilder arg(final JavaClass clazz) { - return this.arg((Object) clazz); - } - - public BsmDataBuilder arg(final Class clazz) { - return this.arg((Object) clazz); - } - - public BsmDataBuilder arg(final Type type) { - return this.arg((Object) type); - } - - public BsmDataBuilder arg(final Type returnType, final Type... arguments) { - return this.arg(Type.getMethodType(returnType, arguments)); - } - - public BsmDataBuilder arg(final String returnType, final String... arguments) { - return this.arg(Type.getType(returnType), Stream.of(arguments).map(Type::getType).toArray(Type[]::new)); - } - - public BsmDataBuilder arg(final JavaCompilingMethod method) { - return this.arg((Object) method); - } - - public BsmDataBuilder arg(final JavaNativeMethod method) { - return this.arg((Object) method); - } - - public BsmDataBuilder arg(final InvocationKind kind, final JavaClass owner, final String name, final Type desc) { - switch (kind) { - case STATIC: return this.arg(JavaNativeMethod.getStatic(owner, name, desc.getDescriptor(), JavaModifiers.STATIC)); - case SPECIAL: { - if ("".equals(name)) { - return this.arg(JavaNativeMethod.getConstructor(owner, desc.getDescriptor(), 0)); - } else { - return this.arg(JavaNativeMethod.getVirtual(owner, name, desc.getDescriptor(), 0)); - } - } - case VIRTUAL: return this.arg(JavaNativeMethod.getVirtual(owner, name, desc.getDescriptor(), 0)); - case INTERFACE: return this.arg(JavaNativeMethod.getInterface(owner, name, desc.getDescriptor())); - } - throw new IllegalArgumentException(String.valueOf(kind)); - } - - public BsmDataBuilder arg(final String name, final JavaClass type, final UnaryOperator constantBuilder) { - return this.arg(name, Type.getType(type.internalName), constantBuilder); - } - - public BsmDataBuilder arg(final String name, final Class type, final UnaryOperator constantBuilder) { - return this.arg(name, Type.getType(type), constantBuilder); - } - - public BsmDataBuilder arg(final String name, final Type type, final UnaryOperator constantBuilder) { - return this.arg(new ConstantDynamicData(name, type, constantBuilder.apply(new BsmDataBuilder(Kind.CONSTANT)).build())); - } - - private BsmDataBuilder arg(final Object argument) { - Objects.requireNonNull(argument, "Null arguments are not supported when invoking BSMs"); - this.args.add(IndyHelpers.toBsmArg(argument)); - return this; - } - - BsmData build() { - if (this.bsmMethod == null && this.owner == null) { - throw new IllegalStateException("BSM has not been specified"); - } - final JavaNativeMethod method = this.findBsmMethod(); - return new BsmData(method, this.args); - } - - private JavaNativeMethod findBsmMethod() { - if (this.bsmMethod != null) { - return this.bsmMethod; - } - - return JavaNativeMethod.getStatic(this.owner, this.name, this.kind.autoFillDesc(this.args), JavaModifiers.PUBLIC | JavaModifiers.STATIC); - } - } - - private static final class BsmData { - private final JavaNativeMethod method; - private final List args; - - BsmData(final JavaNativeMethod method, final List args) { - this.method = method; - this.args = args; - } - - JavaNativeMethod method() { - return this.method; - } - - List args() { - return this.args; - } - } - - private static final class ConstantDynamicData { - private final String name; - private final Type type; - private final BsmData bsmData; - - ConstantDynamicData(final String name, final Type type, final BsmData bsmData) { - this.name = name; - this.type = type; - this.bsmData = bsmData; - } - - String name() { - return this.name; - } - - Type type() { - return this.type; - } - - BsmData bsm() { - return this.bsmData; - } - } - - @FunctionalInterface - interface IndyVisitor { - void visitInvokeDynamic(final String methodName, final String methodDesc, final Handle bsmMethod, final Object... bsmArgs); - } - - @FunctionalInterface - private interface Constructor { - JavaIndyHelper of(final String callSiteMethodName, final String callSiteMethodDesc, final JavaNativeMethod bsmMethod, final List bsmArgs); - } - - private static final class IndyHelpers { - private static final Type CALL_SITE_TYPE = Type.getType(CallSite.class); - private static final Type METHOD_HANDLES_LOOKUP_TYPE = Type.getType(MethodHandles.Lookup.class); - private static final Type STRING_TYPE = Type.getType(String.class); - private static final Type METHOD_TYPE_TYPE = Type.getType(MethodType.class); - private static final Type CLASS_TYPE = Type.getType(Class.class); - private static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); - - private IndyHelpers() {} - - static boolean isValidBsm(final JavaNativeMethod method, final boolean constant) { - final int modifiers = method.modifiers; - if ((modifiers & JavaModifiers.PUBLIC) == 0) { - return false; - } - if ((modifiers & JavaModifiers.STATIC) == 0) { - return false; - } - - final Type type = Type.getMethodType(method.descriptor); - if (!CALL_SITE_TYPE.equals(type.getReturnType())) { - return false; - } - - final Type[] args = type.getArgumentTypes(); - final Type lastType = constant? CLASS_TYPE : METHOD_TYPE_TYPE; - return args.length >= 3 && METHOD_HANDLES_LOOKUP_TYPE.equals(args[0]) && STRING_TYPE.equals(args[1]) && lastType.equals(args[2]); - } - - static String autoFillBsmDesc(final boolean constant, final List args) { - return Type.getMethodDescriptor( - CALL_SITE_TYPE, - Stream.concat( - Stream.of(METHOD_HANDLES_LOOKUP_TYPE, STRING_TYPE, constant? CLASS_TYPE : METHOD_TYPE_TYPE), - args.stream().map(IndyHelpers::toBsmArg).map(IndyHelpers::toBsmArgType) - ).toArray(Type[]::new) - ); - } - - static Handle toBsmHandle(final JavaNativeMethod method) { - return new Handle( - "".equals(method.name)? Opcodes.H_INVOKESPECIAL : Opcodes.H_INVOKESTATIC, - method.cls.internalName, - method.name, - method.descriptor, - method.cls.isInterface() - ); - } - - static Object[] toBsmArgs(final List args) { - return args.stream().map(IndyHelpers::toBsmArg).toArray(Object[]::new); - } - - static Object toBsmArg(final Object object) { - if (object instanceof Integer || object instanceof Float || object instanceof Double || object instanceof Long || object instanceof String) { - return object; - } - if (object instanceof Type || object instanceof Handle || object instanceof ConstantDynamic) { - return object; - } - if (object instanceof JavaClass) { - return Type.getType(((JavaClass) object).internalName); - } - if (object instanceof Class) { - return Type.getType((Class) object); - } - if (object instanceof JavaNativeMethod || object instanceof JavaCompilingMethod) { - final JavaNativeMethod method = object instanceof JavaCompilingMethod? ((JavaCompilingMethod) object).compiled : (JavaNativeMethod) object; - - final int invokeType; - if ((method.modifiers & JavaModifiers.STATIC) != 0) { - invokeType = Opcodes.H_INVOKESTATIC; - } else if (method.cls.isInterface()) { - invokeType = Opcodes.H_INVOKEINTERFACE; - } else if ("".equals(method.name) || (method.modifiers & JavaModifiers.PRIVATE) != 0) { - invokeType = Opcodes.H_INVOKESPECIAL; - } else { - invokeType = Opcodes.H_INVOKEVIRTUAL; - } - - return new Handle( - invokeType, - method.cls.internalName, - method.name, - method.descriptor, - method.cls.isInterface() - ); - } - if (object instanceof ConstantDynamicData) { - final ConstantDynamicData data = (ConstantDynamicData) object; - final BsmData bsmData = data.bsm(); - return new ConstantDynamic(data.name(), data.type().getDescriptor(), toBsmHandle(bsmData.method()), toBsmArgs(bsmData.args())); - } - throw new IllegalArgumentException("Unrecognized or incompatible indy construct " + object.getClass().getName()); - } - - static Type toBsmArgType(final Object object) { - final Object realObject = toBsmArg(object); - if (realObject instanceof Integer) { - return Type.INT_TYPE; - } - if (realObject instanceof Float) { - return Type.FLOAT_TYPE; - } - if (realObject instanceof Double) { - return Type.DOUBLE_TYPE; - } - if (realObject instanceof Long) { - return Type.LONG_TYPE; - } - if (realObject instanceof String) { - return STRING_TYPE; - } - if (realObject instanceof Type) { - final int sort = ((Type) realObject).getSort(); - if (sort == Type.METHOD) { - return METHOD_TYPE_TYPE; - } else { - return CLASS_TYPE; - } - } - if (realObject instanceof Handle) { - return METHOD_HANDLE_TYPE; - } - if (realObject instanceof ConstantDynamicData) { - final ConstantDynamicData data = (ConstantDynamicData) realObject; - return data.type(); - } - throw new IllegalArgumentException("Unrecognized or incompatible indy construct " + realObject.getClass().getName()); - } - } - - private final String callSiteMethodName; - private final String callSiteMethodDesc; - private final JavaNativeMethod bsmMethod; - private final List bsmArgs; - - private JavaIndyHelper(final String callSiteMethodName, final String callSiteMethodDesc, final JavaNativeMethod bsmMethod, final List bsmArgs) { - this.callSiteMethodName = callSiteMethodName; - this.callSiteMethodDesc = callSiteMethodDesc; - this.bsmMethod = bsmMethod; - this.bsmArgs = bsmArgs; - } - - static JavaIndyHelper.Builder builder() { - return new Builder(JavaIndyHelper::new); - } - - void visit(final IndyVisitor visitor) { - visitor.visitInvokeDynamic( - this.callSiteMethodName, - this.callSiteMethodDesc, - IndyHelpers.toBsmHandle(this.bsmMethod), - IndyHelpers.toBsmArgs(this.bsmArgs) - ); - } - - @Override - public String toString() { - return this.callSiteMethodName + this.callSiteMethodDesc + " (through " + this.bsmMethod.cls.internalName + '.' + this.bsmMethod.name + this.bsmMethod.descriptor + ')'; - } -} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaWriter.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaWriter.java index d648f1f0..4215f05e 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaWriter.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/JavaWriter.java @@ -7,6 +7,8 @@ import org.openzen.zenscript.codemodel.HighLevelDefinition; import org.openzen.zenscript.codemodel.statement.VariableID; import org.openzen.zenscript.javabytecode.JavaLocalVariableInfo; +import org.openzen.zenscript.javabytecode.compiler.indy.JavaCondy; +import org.openzen.zenscript.javabytecode.compiler.indy.JavaIndy; import org.openzen.zenscript.javashared.JavaClass; import org.openzen.zenscript.javashared.compiling.JavaCompilingMethod; import org.openzen.zenscript.javashared.JavaNativeField; @@ -294,7 +296,11 @@ public void ldc(Object value) { if (debug) logger.debug("ldc " + value); - visitor.visitLdcInsn(value); + if (value instanceof JavaCondy) { + ((JavaCondy) value).visit(visitor::visitLdcInsn); + } else { + visitor.visitLdcInsn(value); + } } public void constant(byte value) { @@ -383,6 +389,14 @@ public void constant(JavaClass cls) { this.ldc(Type.getObjectType(cls.internalName)); } + public void constant(UnaryOperator valueBuilder) { + this.constant(JavaCondy.build(valueBuilder)); + } + + public void constant(JavaCondy value) { + value.visit(this::ldc); + } + public void pop() { if (debug) logger.debug("pop"); @@ -1111,15 +1125,15 @@ public void invokeInterface(JavaNativeMethod method) { visitor.visitMethodInsn(INVOKEINTERFACE, method.cls.internalName, method.name, method.descriptor, true); } - public void invokeDynamic(UnaryOperator indyDataBuilder) { - invokeDynamic(indyDataBuilder.apply(JavaIndyHelper.builder()).build()); + public void invokeDynamic(UnaryOperator indyBuilder) { + invokeDynamic(JavaIndy.build(indyBuilder)); } - public void invokeDynamic(JavaIndyHelper indyData) { + public void invokeDynamic(JavaIndy indy) { if (debug) - logger.debug("invokeDynamic " + indyData); + logger.debug("invokeDynamic " + indy); - indyData.visit(visitor::visitInvokeDynamicInsn); + indy.visit(visitor::visitInvokeDynamicInsn); } public void newObject(String internalName) { diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/BsmData.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/BsmData.java new file mode 100644 index 00000000..f53e86da --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/BsmData.java @@ -0,0 +1,203 @@ +package org.openzen.zenscript.javabytecode.compiler.indy; + +import org.objectweb.asm.Type; +import org.openzen.zenscript.javashared.JavaClass; +import org.openzen.zenscript.javashared.JavaModifiers; +import org.openzen.zenscript.javashared.JavaNativeMethod; +import org.openzen.zenscript.javashared.compiling.JavaCompilingMethod; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +public final class BsmData { + public enum InvokeType { + STATIC, + VIRTUAL, + SPECIAL, + INTERFACE + } + + public static final class Builder { + // TODO("Think about this") + private static final boolean ALLOW_RELAX = false; + + private final IndyTarget indyTarget; + private final Constructor constructor; + private final List args; + + private JavaNativeMethod bsmMethod; + private JavaClass owner; + private String name; + + private Builder(final IndyTarget indyTarget, final Constructor constructor) { + this.indyTarget = indyTarget; + this.constructor = constructor; + this.args = new ArrayList<>(); + this.bsmMethod = null; + this.owner = null; + this.name = null; + } + + static Builder of(final IndyTarget indyTarget, final Constructor constructor) { + return new Builder(indyTarget, constructor); + } + + public Builder method(final JavaNativeMethod bsmMethod) { + Objects.requireNonNull(bsmMethod, "BSM Method specified cannot be null"); + if (!IndyHelpers.isValidBsm(bsmMethod, this.indyTarget, ALLOW_RELAX)) { + throw new IllegalArgumentException("Provided BSM must be a valid BSM"); + } + if (this.owner != null) { + throw new IllegalStateException("BSM has already been specified in 'autodetect' mode"); + } + if (this.bsmMethod != null) { + throw new IllegalStateException("BSM has already been specified"); + } + this.bsmMethod = bsmMethod; + return this; + } + + public Builder method(final JavaClass owner, final String name) { + Objects.requireNonNull(owner, "BSM owner cannot be null"); + Objects.requireNonNull(name, "BSM name cannot be null"); + if (this.bsmMethod != null) { + throw new IllegalStateException("BSM has already been specified in 'fixed' mode"); + } + if (this.owner != null) { + throw new IllegalStateException("BSM has already been specified"); + } + this.owner = owner; + this.name = name; + return this; + } + + public Builder arg(final int argument) { + return this.arg((Object) argument); + } + + public Builder arg(final float argument) { + return this.arg((Object) argument); + } + + public Builder arg(final long argument) { + return this.arg((Object) argument); + } + + public Builder arg(final double argument) { + return this.arg((Object) argument); + } + + public Builder arg(final String argument) { + return this.arg((Object) argument); + } + + public Builder arg(final JavaClass clazz) { + return this.arg((Object) clazz); + } + + public Builder arg(final Class clazz) { + return this.arg((Object) clazz); + } + + public Builder arg(final Type type) { + return this.arg((Object) type); + } + + public Builder arg(final Type returnType, final Type... arguments) { + return this.arg(Type.getMethodType(returnType, arguments)); + } + + public Builder arg(final String returnType, final String... arguments) { + return this.arg(Type.getType(returnType), Stream.of(arguments).map(Type::getType).toArray(Type[]::new)); + } + + public Builder arg(final JavaCompilingMethod method) { + return this.arg((Object) method); + } + + public Builder arg(final JavaNativeMethod method) { + return this.arg((Object) method); + } + + public Builder arg(final InvokeType invokeType, final JavaClass owner, final String name, final Type desc) { + switch (invokeType) { + case STATIC: return this.arg(JavaNativeMethod.getStatic(owner, name, desc.getDescriptor(), JavaModifiers.STATIC)); + case SPECIAL: { + if (IndyHelpers.CONSTRUCTOR_NAME.equals(name)) { + return this.arg(JavaNativeMethod.getConstructor(owner, desc.getDescriptor(), 0)); + } else { + return this.arg(JavaNativeMethod.getVirtual(owner, name, desc.getDescriptor(), 0)); + } + } + case VIRTUAL: return this.arg(JavaNativeMethod.getVirtual(owner, name, desc.getDescriptor(), 0)); + case INTERFACE: return this.arg(JavaNativeMethod.getInterface(owner, name, desc.getDescriptor())); + } + throw new IllegalArgumentException(String.valueOf(invokeType)); + } + + @SuppressWarnings("SpellCheckingInspection") + public Builder arg(final UnaryOperator condyBuilder) { + return this.arg(condyBuilder.apply(JavaCondy.builder()).build()); + } + + @SuppressWarnings("SpellCheckingInspection") + public Builder arg(final JavaCondy condy) { + return this.arg((Object) condy); + } + + private Builder arg(final Object argument) { + Objects.requireNonNull(argument, "Null arguments are not supported when invoking BSMs"); + this.args.add(argument); + return this; + } + + BsmData build() { + if (this.bsmMethod == null && this.owner == null) { + throw new IllegalStateException("BSM has not been specified"); + } + final JavaNativeMethod method = this.findBsmMethod(); + return this.constructor.of(method, this.args); + } + + private JavaNativeMethod findBsmMethod() { + if (this.bsmMethod != null) { + return this.bsmMethod; + } + + return JavaNativeMethod.getStatic(this.owner, this.name, IndyHelpers.autoFillBsmDesc(this.indyTarget, this.args), JavaModifiers.PUBLIC | JavaModifiers.STATIC); + } + } + + @FunctionalInterface + interface Constructor { + BsmData of(final JavaNativeMethod bsm, final List args); + } + + private final JavaNativeMethod bsm; + private final List args; + + private BsmData(final JavaNativeMethod bsm, final List args) { + this.bsm = bsm; + this.args = args; + } + + static BsmData.Builder builder(final IndyTarget target) { + return Builder.of(target, BsmData::new); + } + + JavaNativeMethod bsm() { + return this.bsm; + } + + List args() { + return this.args; + } + + @Override + public String toString() { + return String.format("%s.%s%s, invoked with %s", this.bsm.cls.internalName, this.bsm.name, this.bsm.descriptor, this.args); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java new file mode 100644 index 00000000..bfdb44b0 --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java @@ -0,0 +1,140 @@ +package org.openzen.zenscript.javabytecode.compiler.indy; + +import org.objectweb.asm.ConstantDynamic; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.openzen.zenscript.javashared.JavaClass; +import org.openzen.zenscript.javashared.JavaModifiers; +import org.openzen.zenscript.javashared.JavaNativeMethod; +import org.openzen.zenscript.javashared.compiling.JavaCompilingMethod; + +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.List; + +final class IndyHelpers { + static final Type CALL_SITE_TYPE = Type.getType(CallSite.class); + static final Type CLASS_TYPE = Type.getType(Class.class); + static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); + static final Type METHOD_HANDLES_LOOKUP_TYPE = Type.getType(MethodHandles.Lookup.class); + static final Type METHOD_TYPE_TYPE = Type.getType(MethodType.class); + static final Type OBJECT_TYPE = Type.getType(Object.class); + static final Type STRING_TYPE = Type.getType(String.class); + + static final String CONSTRUCTOR_NAME = ""; + + private IndyHelpers() {} + + static boolean isValidBsm(final JavaNativeMethod method, final IndyTarget target, final boolean relax) { + final int modifiers = method.modifiers; + if ((modifiers & JavaModifiers.PUBLIC) == 0) { + return false; + } + if ((modifiers & JavaModifiers.STATIC) == 0 && !CONSTRUCTOR_NAME.equals(method.name)) { + return false; + } + + final Type type = Type.getMethodType(method.descriptor); + return target.verifySignature(type, relax); + } + + static String autoFillBsmDesc(final IndyTarget target, final List args) { + return target.appendTypeArguments(args).getDescriptor(); + } + + static Handle toBsmHandle(final JavaNativeMethod method) { + return new Handle( + CONSTRUCTOR_NAME.equals(method.name)? Opcodes.H_INVOKESPECIAL : Opcodes.H_INVOKESTATIC, + method.cls.internalName, + method.name, + method.descriptor, + method.cls.isInterface() + ); + } + + static Object[] toBsmArgs(final List args) { + return args.stream().map(IndyHelpers::toBsmArg).toArray(Object[]::new); + } + + static Type[] toBsmArgTypes(final List args) { + return args.stream().map(IndyHelpers::toBsmArgType).toArray(Type[]::new); + } + + private static Object toBsmArg(final Object object) { + if (object instanceof Integer || object instanceof Float || object instanceof Double || object instanceof Long || object instanceof String) { + return object; + } + if (object instanceof Type || object instanceof Handle || object instanceof ConstantDynamic) { + return object; + } + if (object instanceof JavaClass) { + return Type.getType(((JavaClass) object).internalName); + } + if (object instanceof Class) { + return Type.getType((Class) object); + } + if (object instanceof JavaNativeMethod || object instanceof JavaCompilingMethod) { + final JavaNativeMethod method = object instanceof JavaCompilingMethod? ((JavaCompilingMethod) object).compiled : (JavaNativeMethod) object; + + final int invokeType; + if ((method.modifiers & JavaModifiers.STATIC) != 0) { + invokeType = Opcodes.H_INVOKESTATIC; + } else if (method.cls.isInterface()) { + invokeType = Opcodes.H_INVOKEINTERFACE; + } else if ("".equals(method.name) || (method.modifiers & JavaModifiers.PRIVATE) != 0) { + invokeType = Opcodes.H_INVOKESPECIAL; + } else { + invokeType = Opcodes.H_INVOKEVIRTUAL; + } + + return new Handle( + invokeType, + method.cls.internalName, + method.name, + method.descriptor, + method.cls.isInterface() + ); + } + if (object instanceof JavaCondy) { + return ((JavaCondy) object).asConstantDynamic(); + } + throw new IllegalArgumentException("Unrecognized or incompatible indy construct " + object.getClass().getName()); + } + + private static Type toBsmArgType(final Object object) { + final Object realObject = toBsmArg(object); + if (realObject instanceof Integer) { + return Type.INT_TYPE; + } + if (realObject instanceof Float) { + return Type.FLOAT_TYPE; + } + if (realObject instanceof Double) { + return Type.DOUBLE_TYPE; + } + if (realObject instanceof Long) { + return Type.LONG_TYPE; + } + if (realObject instanceof String) { + return STRING_TYPE; + } + if (realObject instanceof Type) { + final int sort = ((Type) realObject).getSort(); + if (sort == Type.METHOD) { + return METHOD_TYPE_TYPE; + } else { + return CLASS_TYPE; + } + } + if (realObject instanceof Handle) { + return METHOD_HANDLE_TYPE; + } + if (realObject instanceof JavaCondy) { + return Type.getType(((JavaCondy) realObject).asConstantDynamic().getDescriptor()); + } + throw new IllegalArgumentException("Unrecognized or incompatible indy construct " + realObject.getClass().getName()); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyTarget.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyTarget.java new file mode 100644 index 00000000..bb610021 --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyTarget.java @@ -0,0 +1,51 @@ +package org.openzen.zenscript.javabytecode.compiler.indy; + +import org.objectweb.asm.Type; + +import java.util.List; +import java.util.stream.Stream; + +@SuppressWarnings("SpellCheckingInspection") +enum IndyTarget { + INDY(Type.getMethodType(IndyHelpers.CALL_SITE_TYPE, IndyHelpers.METHOD_HANDLES_LOOKUP_TYPE, IndyHelpers.STRING_TYPE, IndyHelpers.METHOD_TYPE_TYPE), 0), + CONDY(Type.getMethodType(IndyHelpers.OBJECT_TYPE, IndyHelpers.METHOD_HANDLES_LOOKUP_TYPE, IndyHelpers.STRING_TYPE, IndyHelpers.CLASS_TYPE), 1); + + private final Type expectedBasicSignature; + private final int requiredParamCount; + + IndyTarget(final Type expectedBasicSignature, final int requiredParamCount) { + this.expectedBasicSignature = expectedBasicSignature; + this.requiredParamCount = requiredParamCount; + } + + boolean verifySignature(final Type methodType, final boolean relax) { + if (!this.expectedBasicSignature.getReturnType().equals(methodType.getReturnType())) { + return false; + } + + final Type[] expectedArgs = this.expectedBasicSignature.getArgumentTypes(); + final Type[] methodArgs = methodType.getArgumentTypes(); + if (expectedArgs.length > methodArgs.length && !relax) { + return false; + } + if (methodArgs.length < this.requiredParamCount) { + return false; + } + + // If we want all expected arguments, we'd not be relaxing, thus we would have bailed beforehand + for (int i = 0, m = Math.min(expectedArgs.length, methodArgs.length); i < m; ++i) { + if (!expectedArgs[i].equals(methodArgs[i])) { + return false; + } + } + + return true; + } + + Type appendTypeArguments(final List args) { + return Type.getMethodType( + this.expectedBasicSignature.getReturnType(), + Stream.concat(Stream.of(this.expectedBasicSignature.getArgumentTypes()), Stream.of(IndyHelpers.toBsmArgTypes(args))).toArray(Type[]::new) + ); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/JavaCondy.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/JavaCondy.java new file mode 100644 index 00000000..452a223d --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/JavaCondy.java @@ -0,0 +1,107 @@ +package org.openzen.zenscript.javabytecode.compiler.indy; + +import org.objectweb.asm.ConstantDynamic; +import org.objectweb.asm.Type; + +import java.util.Objects; +import java.util.function.UnaryOperator; + +@SuppressWarnings("SpellCheckingInspection") +public final class JavaCondy { + public static final class Builder { + private final Constructor constructor; + + private String fieldName; + private Type fieldDesc; + private BsmData bsm; + + private Builder(final Constructor constructor) { + this.constructor = constructor; + this.fieldName = null; + this.fieldDesc = null; + this.bsm = null; + } + + static Builder of(final Constructor constructor) { + return new Builder(constructor); + } + + public Builder field(final String name, final Type desc) { + Objects.requireNonNull(name, "Field name cannot be null"); + Objects.requireNonNull(desc, "Field descriptor cannot be null"); + if (desc.getSort() == Type.METHOD) { + throw new IllegalArgumentException("Field type must be a valid field descriptor"); + } + if (this.fieldName != null) { + throw new IllegalStateException("Field has already been set"); + } + this.fieldName = name; + this.fieldDesc = desc; + return this; + } + + public Builder bsm(final UnaryOperator bsmDataBuilder) { + if (this.bsm != null) { + throw new IllegalStateException("BSM has already been specified"); + } + this.bsm = bsmDataBuilder.apply(BsmData.builder(IndyTarget.CONDY)).build(); + return this; + } + + JavaCondy build() { + if (this.fieldName == null) { + throw new IllegalStateException("Field has not been specified"); + } + if (this.bsm == null) { + throw new IllegalStateException("BSM data has not been specified"); + } + return this.constructor.of(this.fieldName, this.fieldDesc, this.bsm); + } + } + + @FunctionalInterface + public interface CondyVisitor { + void visitLdc(final ConstantDynamic dynamic); + } + + @FunctionalInterface + interface Constructor { + JavaCondy of(final String fieldName, final Type fieldDesc, final BsmData bsm); + } + + private final String fieldName; + private final Type fieldDesc; + private final BsmData bsm; + + private JavaCondy(final String fieldName, final Type fieldDesc, final BsmData bsm) { + this.fieldName = fieldName; + this.fieldDesc = fieldDesc; + this.bsm = bsm; + } + + public static JavaCondy build(final UnaryOperator builder) { + return builder.apply(builder()).build(); + } + + static JavaCondy.Builder builder() { + return Builder.of(JavaCondy::new); + } + + public void visit(final CondyVisitor visitor) { + visitor.visitLdc(this.asConstantDynamic()); + } + + ConstantDynamic asConstantDynamic() { + return new ConstantDynamic( + this.fieldName, + this.fieldDesc.getDescriptor(), + IndyHelpers.toBsmHandle(this.bsm.bsm()), + IndyHelpers.toBsmArgs(this.bsm.args()) + ); + } + + @Override + public String toString() { + return String.format("%s:%s via %s", this.fieldName, this.fieldDesc.getDescriptor(), this.bsm); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/JavaIndy.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/JavaIndy.java new file mode 100644 index 00000000..f9e8d71b --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/JavaIndy.java @@ -0,0 +1,102 @@ +package org.openzen.zenscript.javabytecode.compiler.indy; + +import org.objectweb.asm.Handle; +import org.objectweb.asm.Type; + +import java.util.Objects; +import java.util.function.UnaryOperator; + +public final class JavaIndy { + public static final class Builder { + private final Constructor constructor; + + private String callSiteName; + private Type callSiteDesc; + private BsmData bsm; + + private Builder(final Constructor constructor) { + this.constructor = constructor; + this.callSiteName = null; + this.callSiteDesc = null; + this.bsm = null; + } + + static Builder of(final Constructor constructor) { + return new Builder(constructor); + } + + public Builder callSite(final String name, final Type desc) { + Objects.requireNonNull(name, "Call site name cannot be null"); + Objects.requireNonNull(desc, "Call site descriptor cannot be null"); + if (desc.getSort() != Type.METHOD) { + throw new IllegalArgumentException("Call site type must be a valid method descriptor"); + } + if (this.callSiteName != null) { + throw new IllegalStateException("Call site has already been set"); + } + this.callSiteName = name; + this.callSiteDesc = desc; + return this; + } + + public Builder bsm(final UnaryOperator bsmDataBuilder) { + if (this.bsm != null) { + throw new IllegalStateException("BSM has already been specified"); + } + this.bsm = bsmDataBuilder.apply(BsmData.builder(IndyTarget.INDY)).build(); + return this; + } + + JavaIndy build() { + if (this.callSiteName == null) { + throw new IllegalStateException("Call site has not been specified"); + } + if (this.bsm == null) { + throw new IllegalStateException("BSM data has not been specified"); + } + return this.constructor.of(this.callSiteName, this.callSiteDesc, this.bsm); + } + } + + @FunctionalInterface + public interface IndyVisitor { + void visitInvokeDynamic(final String methodName, final String methodDesc, final Handle bsmMethod, final Object... bsmArgs); + } + + @FunctionalInterface + interface Constructor { + JavaIndy of(final String callSiteName, final Type callSiteDesc, final BsmData bsm); + } + + private final String callSiteName; + private final Type callSiteDesc; + private final BsmData bsm; + + private JavaIndy(final String callSiteName, final Type callSiteDesc, final BsmData bsm) { + this.callSiteName = callSiteName; + this.callSiteDesc = callSiteDesc; + this.bsm = bsm; + } + + public static JavaIndy build(final UnaryOperator builder) { + return builder.apply(builder()).build(); + } + + static JavaIndy.Builder builder() { + return Builder.of(JavaIndy::new); + } + + public void visit(final IndyVisitor visitor) { + visitor.visitInvokeDynamic( + this.callSiteName, + this.callSiteDesc.getDescriptor(), + IndyHelpers.toBsmHandle(this.bsm.bsm()), + IndyHelpers.toBsmArgs(this.bsm.args()) + ); + } + + @Override + public String toString() { + return String.format("%s%s via %s", this.callSiteName, this.callSiteDesc.getDescriptor(), this.bsm); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java index cb425d8c..716bb56f 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java @@ -192,7 +192,7 @@ private void generateIndyMethodCall( this.writer.invokeDynamic(indy -> indy .callSite(methodInfo.name, this.computeIndyDescriptor(closureInfo, interfaceType)) - .bootstrapMethod(bsm -> bsm + .bsm(bsm -> bsm .method(JavaClass.fromJavaClass(LambdaFactory.class), "buildLambda") .arg(method) .arg(Type.getMethodType(JavaMemberVisitor.compileBridgeableMethodNoSideEffect(methodInfo, context.getMethodDescriptor(lambdaExpression.header)).compiled.descriptor)) @@ -238,7 +238,7 @@ private void generateConversionInstructionsForLambdaType(final Expression body, body.accept(this.visitor); this.writer.invokeDynamic(indy -> indy .callSite(toNativeDescription.name, Type.getMethodType(this.context.getType(toType), this.context.getType(fromType))) - .bootstrapMethod(bsm -> bsm + .bsm(bsm -> bsm .method(JavaClass.fromJavaClass(LambdaFactory.class), "buildLambda") .arg(fromNativeDescription) .arg(Type.getMethodType(fromNativeDescription.descriptor)) From 3e2d1b840657a864f32b989f09c41d5b220c1b75 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Fri, 28 Feb 2025 13:49:09 +0100 Subject: [PATCH 15/17] Add support for all type conversions Signed-off-by: TheSilkMiner --- .../javart/factory/LambdaFactory.java | 308 +++++++++++++++++- 1 file changed, 290 insertions(+), 18 deletions(-) diff --git a/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java b/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java index 614c2e44..79652609 100644 --- a/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java +++ b/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java @@ -13,6 +13,7 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.BinaryOperator; public final class LambdaFactory { private static final class LambdaClassLoader extends ClassLoader { @@ -66,6 +67,291 @@ boolean generateBridge() { } } + private static final class LambdaTypeConverters { + @FunctionalInterface + interface Operation { + void run(final MethodVisitor visitor, final Type fromType, final Type toType); + } + + private static final class TypedOperation { + private final int fromSort; + private final int toSort; + private final Operation operation; + + TypedOperation(final int fromSort, final int toSort, final Operation operation) { + this.fromSort = fromSort; + this.toSort = toSort; + this.operation = operation; + } + + int from() { + return this.fromSort; + } + + int to() { + return this.toSort; + } + + Operation op() { + return this.operation; + } + } + + private static final Operation[][] OPERATIONS = build( + conv(Type.VOID, Type.VOID, nop()), + conv(Type.VOID, Type.BOOLEAN, load(Opcodes.ICONST_0)), + conv(Type.VOID, Type.CHAR, load(Opcodes.ICONST_0)), + conv(Type.VOID, Type.BYTE, load(Opcodes.ICONST_0)), + conv(Type.VOID, Type.SHORT, load(Opcodes.ICONST_0)), + conv(Type.VOID, Type.INT, load(Opcodes.ICONST_0)), + conv(Type.VOID, Type.FLOAT, load(Opcodes.FCONST_0)), + conv(Type.VOID, Type.LONG, load(Opcodes.LCONST_0)), + conv(Type.VOID, Type.DOUBLE, load(Opcodes.DCONST_0)), + conv(Type.VOID, Type.ARRAY, load(Opcodes.ACONST_NULL)), + conv(Type.VOID, Type.OBJECT, load(Opcodes.ACONST_NULL)), + conv(Type.VOID, Type.METHOD, nonsense()), + + conv(Type.BOOLEAN, Type.VOID, pop()), + conv(Type.BOOLEAN, Type.BOOLEAN, nop()), + conv(Type.BOOLEAN, Type.CHAR, nop()), + conv(Type.BOOLEAN, Type.BYTE, nop()), + conv(Type.BOOLEAN, Type.SHORT, nop()), + conv(Type.BOOLEAN, Type.INT, nop()), + conv(Type.BOOLEAN, Type.FLOAT, widen(Opcodes.I2F)), + conv(Type.BOOLEAN, Type.LONG, widen(Opcodes.I2L)), + conv(Type.BOOLEAN, Type.DOUBLE, widen(Opcodes.I2D)), + conv(Type.BOOLEAN, Type.ARRAY, nonsense()), + conv(Type.BOOLEAN, Type.OBJECT, all(box(Boolean.class, "valueOf"), cast(toType()))), + conv(Type.BOOLEAN, Type.METHOD, nonsense()), + + conv(Type.CHAR, Type.VOID, pop()), + conv(Type.CHAR, Type.BOOLEAN, all(load(Opcodes.ICONST_1), opcode(Opcodes.IAND))), + conv(Type.CHAR, Type.CHAR, nop()), + conv(Type.CHAR, Type.BYTE, narrow(Opcodes.I2B)), + conv(Type.CHAR, Type.SHORT, nop()), + conv(Type.CHAR, Type.INT, nop()), + conv(Type.CHAR, Type.FLOAT, widen(Opcodes.I2F)), + conv(Type.CHAR, Type.LONG, widen(Opcodes.I2L)), + conv(Type.CHAR, Type.DOUBLE, widen(Opcodes.I2D)), + conv(Type.CHAR, Type.ARRAY, nonsense()), + conv(Type.CHAR, Type.OBJECT, all(box(Character.class, "valueOf"), cast(toType()))), + conv(Type.CHAR, Type.METHOD, nonsense()), + + conv(Type.BYTE, Type.VOID, pop()), + conv(Type.BYTE, Type.BOOLEAN, all(load(Opcodes.ICONST_1), opcode(Opcodes.IAND))), + conv(Type.BYTE, Type.CHAR, nop()), + conv(Type.BYTE, Type.BYTE, nop()), + conv(Type.BYTE, Type.SHORT, nop()), + conv(Type.BYTE, Type.INT, nop()), + conv(Type.BYTE, Type.FLOAT, widen(Opcodes.I2F)), + conv(Type.BYTE, Type.LONG, widen(Opcodes.I2L)), + conv(Type.BYTE, Type.DOUBLE, widen(Opcodes.I2D)), + conv(Type.BYTE, Type.ARRAY, nonsense()), + conv(Type.BYTE, Type.OBJECT, all(box(Byte.class, "valueOf"), cast(toType()))), + conv(Type.BYTE, Type.METHOD, nonsense()), + + conv(Type.SHORT, Type.VOID, pop()), + conv(Type.SHORT, Type.BOOLEAN, all(load(Opcodes.ICONST_1), opcode(Opcodes.IAND))), + conv(Type.SHORT, Type.CHAR, nop()), + conv(Type.SHORT, Type.BYTE, narrow(Opcodes.I2B)), + conv(Type.SHORT, Type.SHORT, nop()), + conv(Type.SHORT, Type.INT, nop()), + conv(Type.SHORT, Type.FLOAT, widen(Opcodes.I2F)), + conv(Type.SHORT, Type.LONG, widen(Opcodes.I2L)), + conv(Type.SHORT, Type.DOUBLE, widen(Opcodes.I2D)), + conv(Type.SHORT, Type.ARRAY, nonsense()), + conv(Type.SHORT, Type.OBJECT, all(box(Short.class, "valueOf"), cast(toType()))), + conv(Type.SHORT, Type.METHOD, nonsense()), + + conv(Type.INT, Type.VOID, pop()), + conv(Type.INT, Type.BOOLEAN, all(load(Opcodes.ICONST_1), opcode(Opcodes.IAND))), + conv(Type.INT, Type.CHAR, narrow(Opcodes.I2C)), + conv(Type.INT, Type.BYTE, narrow(Opcodes.I2B)), + conv(Type.INT, Type.SHORT, narrow(Opcodes.I2S)), + conv(Type.INT, Type.INT, nop()), + conv(Type.INT, Type.FLOAT, widen(Opcodes.I2F)), + conv(Type.INT, Type.LONG, widen(Opcodes.I2L)), + conv(Type.INT, Type.DOUBLE, widen(Opcodes.I2D)), + conv(Type.INT, Type.ARRAY, nonsense()), + conv(Type.INT, Type.OBJECT, all(box(Integer.class, "valueOf"), cast(toType()))), + conv(Type.INT, Type.METHOD, nonsense()), + + conv(Type.FLOAT, Type.VOID, pop()), + conv(Type.FLOAT, Type.BOOLEAN, all(narrow(Opcodes.F2I), load(Opcodes.ICONST_1), opcode(Opcodes.IAND))), + conv(Type.FLOAT, Type.CHAR, all(narrow(Opcodes.F2I), narrow(Opcodes.I2C))), + conv(Type.FLOAT, Type.BYTE, all(narrow(Opcodes.F2I), narrow(Opcodes.I2B))), + conv(Type.FLOAT, Type.SHORT, all(narrow(Opcodes.F2I), narrow(Opcodes.I2S))), + conv(Type.FLOAT, Type.INT, narrow(Opcodes.F2I)), + conv(Type.FLOAT, Type.FLOAT, nop()), + conv(Type.FLOAT, Type.LONG, narrow(Opcodes.F2L)), + conv(Type.FLOAT, Type.DOUBLE, widen(Opcodes.F2D)), + conv(Type.FLOAT, Type.ARRAY, nonsense()), + conv(Type.FLOAT, Type.OBJECT, all(box(Float.class, "valueOf"), cast(toType()))), + conv(Type.FLOAT, Type.METHOD, nonsense()), + + conv(Type.LONG, Type.VOID, pop2()), + conv(Type.LONG, Type.BOOLEAN, all(narrow(Opcodes.L2I), load(Opcodes.ICONST_1), opcode(Opcodes.IAND))), + conv(Type.LONG, Type.CHAR, all(narrow(Opcodes.L2I), narrow(Opcodes.I2C))), + conv(Type.LONG, Type.BYTE, all(narrow(Opcodes.L2I), narrow(Opcodes.I2B))), + conv(Type.LONG, Type.SHORT, all(narrow(Opcodes.L2I), narrow(Opcodes.I2S))), + conv(Type.LONG, Type.INT, narrow(Opcodes.L2I)), + conv(Type.LONG, Type.FLOAT, widen(Opcodes.L2F)), + conv(Type.LONG, Type.LONG, nop()), + conv(Type.LONG, Type.DOUBLE, widen(Opcodes.L2D)), + conv(Type.LONG, Type.ARRAY, nonsense()), + conv(Type.LONG, Type.OBJECT, all(box(Long.class, "valueOf"), cast(toType()))), + conv(Type.LONG, Type.METHOD, nonsense()), + + conv(Type.DOUBLE, Type.VOID, pop2()), + conv(Type.DOUBLE, Type.BOOLEAN, all(narrow(Opcodes.D2I), load(Opcodes.ICONST_1), opcode(Opcodes.IAND))), + conv(Type.DOUBLE, Type.CHAR, all(narrow(Opcodes.D2I), narrow(Opcodes.I2C))), + conv(Type.DOUBLE, Type.BYTE, all(narrow(Opcodes.D2I), narrow(Opcodes.I2B))), + conv(Type.DOUBLE, Type.SHORT, all(narrow(Opcodes.D2I), narrow(Opcodes.I2S))), + conv(Type.DOUBLE, Type.INT, narrow(Opcodes.D2I)), + conv(Type.DOUBLE, Type.FLOAT, narrow(Opcodes.D2F)), + conv(Type.DOUBLE, Type.LONG, narrow(Opcodes.D2L)), + conv(Type.DOUBLE, Type.DOUBLE, nop()), + conv(Type.DOUBLE, Type.OBJECT, all(box(Double.class, "valueOf"), cast(toType()))), + conv(Type.DOUBLE, Type.METHOD, nonsense()), + + conv(Type.ARRAY, Type.VOID, pop()), + conv(Type.ARRAY, Type.BOOLEAN, nonsense()), + conv(Type.ARRAY, Type.CHAR, nonsense()), + conv(Type.ARRAY, Type.BYTE, nonsense()), + conv(Type.ARRAY, Type.SHORT, nonsense()), + conv(Type.ARRAY, Type.INT, nonsense()), + conv(Type.ARRAY, Type.FLOAT, nonsense()), + conv(Type.ARRAY, Type.LONG, nonsense()), + conv(Type.ARRAY, Type.DOUBLE, nonsense()), + conv(Type.ARRAY, Type.OBJECT, cast(toType())), + conv(Type.ARRAY, Type.ARRAY, cast(toType())), + conv(Type.ARRAY, Type.METHOD, nonsense()), + + conv(Type.OBJECT, Type.VOID, pop()), + conv(Type.OBJECT, Type.BOOLEAN, all(cast(Boolean.class), unbox(Boolean.class, "booleanValue"))), + conv(Type.OBJECT, Type.CHAR, all(cast(Character.class), unbox(Character.class, "charValue"))), + conv(Type.OBJECT, Type.BYTE, all(cast(Number.class), unbox(Number.class, "byteValue"))), + conv(Type.OBJECT, Type.SHORT, all(cast(Number.class), unbox(Number.class, "shortValue"))), + conv(Type.OBJECT, Type.INT, all(cast(Number.class), unbox(Number.class, "intValue"))), + conv(Type.OBJECT, Type.FLOAT, all(cast(Number.class), unbox(Number.class, "floatValue"))), + conv(Type.OBJECT, Type.LONG, all(cast(Number.class), unbox(Number.class, "longValue"))), + conv(Type.OBJECT, Type.DOUBLE, all(cast(Number.class), unbox(Number.class, "doubleValue"))), + conv(Type.OBJECT, Type.OBJECT, cast(toType())), + conv(Type.OBJECT, Type.ARRAY, cast(toType())), + conv(Type.OBJECT, Type.METHOD, nonsense()), + + conv(Type.METHOD, Type.VOID, nonsense()), + conv(Type.METHOD, Type.BOOLEAN, nonsense()), + conv(Type.METHOD, Type.CHAR, nonsense()), + conv(Type.METHOD, Type.BYTE, nonsense()), + conv(Type.METHOD, Type.SHORT, nonsense()), + conv(Type.METHOD, Type.INT, nonsense()), + conv(Type.METHOD, Type.FLOAT, nonsense()), + conv(Type.METHOD, Type.LONG, nonsense()), + conv(Type.METHOD, Type.DOUBLE, nonsense()), + conv(Type.METHOD, Type.OBJECT, nonsense()), + conv(Type.METHOD, Type.ARRAY, nonsense()), + conv(Type.METHOD, Type.METHOD, nop()) + ); + + private LambdaTypeConverters() {} + + static void convert(final Type fromType, final Type toType, final MethodVisitor visitor) { + OPERATIONS[fromType.getSort()][toType.getSort()].run(visitor, fromType, toType); + } + + private static Operation[][] build(final TypedOperation... ops) { + final Operation[][] operations = new Operation[12][12]; + for (final TypedOperation op : ops) { + operations[op.from()][op.to()] = op.op(); + } + return operations; + } + + private static TypedOperation conv(final int from, final int to, final Operation operation) { + return new TypedOperation(from, to, operation); + } + + private static Operation nop() { + return (visitor, fromType, toType) -> {}; + } + + private static Operation widen(final int opcode) { + return opcode(opcode); + } + + private static Operation narrow(final int opcode) { + return opcode(opcode); + } + + private static Operation load(final int opcode) { + return opcode(opcode); + } + + private static Operation pop() { + return opcode(Opcodes.POP); + } + + private static Operation pop2() { + return opcode(Opcodes.POP2); + } + + private static Operation opcode(final int opcode) { + return (visitor, fromType, toType) -> visitor.visitInsn(opcode); + } + + private static Operation nonsense() { + return (visitor, fromType, toType) -> { + throw new IllegalStateException("Conversion between " + fromType + " to " + toType + " cannot be meaningfully carried out"); + }; + } + + private static Operation box(final Class owner, @SuppressWarnings("SameParameterValue") final String name) { + return (visitor, fromType, toType) -> visitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getInternalName(owner), + name, + Type.getMethodDescriptor(Type.getType(owner), fromType), + owner.isInterface() + ); + } + + private static Operation cast(final Class target) { + return cast((a, b) -> Type.getType(target)); + } + + private static Operation cast(final BinaryOperator chooser) { + return (visitor, fromType, toType) -> visitor.visitTypeInsn(Opcodes.CHECKCAST, chooser.apply(fromType, toType).getInternalName()); + } + + @SuppressWarnings("unused") + private static BinaryOperator fromType() { + return (a, b) -> a; + } + + private static BinaryOperator toType() { + return (a, b) -> b; + } + + private static Operation unbox(final Class owner, final String name) { + return (visitor, fromType, toType) -> visitor.visitMethodInsn( + owner.isInterface()? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, + Type.getInternalName(owner), + name, + Type.getMethodDescriptor(toType), + false + ); + } + + private static Operation all(final Operation... ops) { + return (visitor, fromType, toType) -> { + for (final Operation op : ops) { + op.run(visitor, fromType, toType); + } + }; + } + } + public interface LambdaMarker {} @Retention(RetentionPolicy.RUNTIME) @@ -430,15 +716,8 @@ private static void generateInterfaceClassBridge( final Type bridgeType = Type.getType(bridgeInterfaceSignature.parameterType(i)); final Type targetType = Type.getType(interfaceSignature.parameterType(i)); - if (bridgeType.getSort() == targetType.getSort()) { - writer.visitVarInsn(bridgeType.getOpcode(Opcodes.ILOAD), localIndex); - - if (bridgeType.getSort() == Type.OBJECT) { - writer.visitTypeInsn(Opcodes.CHECKCAST, targetType.getInternalName()); - } - } else { - throw new UnsupportedOperationException("Not yet implemented"); - } + writer.visitVarInsn(bridgeType.getOpcode(Opcodes.ILOAD), localIndex); + LambdaTypeConverters.convert(bridgeType, targetType, writer); localIndex += bridgeType.getSize(); } @@ -448,15 +727,8 @@ private static void generateInterfaceClassBridge( final Type bridgeReturnType = Type.getType(bridgeInterfaceSignature.returnType()); final Type targetReturnType = Type.getType(interfaceSignature.returnType()); - if (bridgeReturnType.getSort() == targetReturnType.getSort()) { - if (targetReturnType.getSort() == Type.OBJECT) { - writer.visitTypeInsn(Opcodes.CHECKCAST, bridgeReturnType.getInternalName()); - } - - writer.visitInsn(targetReturnType.getOpcode(Opcodes.IRETURN)); - } else { - throw new UnsupportedOperationException("Not yet implemented"); - } + LambdaTypeConverters.convert(targetReturnType, bridgeReturnType, writer); + writer.visitInsn(bridgeReturnType.getOpcode(Opcodes.IRETURN)); writer.visitMaxs(localIndex, localIndex); writer.visitEnd(); From 6503b3110e182a71d2f8ef4ea8588b87a6b96f1f Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Fri, 28 Feb 2025 13:53:47 +0100 Subject: [PATCH 16/17] Fix wrong object type being used in IndyHelpers Signed-off-by: TheSilkMiner --- .../zenscript/javabytecode/compiler/indy/IndyHelpers.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java index bfdb44b0..caf39749 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java @@ -132,8 +132,8 @@ private static Type toBsmArgType(final Object object) { if (realObject instanceof Handle) { return METHOD_HANDLE_TYPE; } - if (realObject instanceof JavaCondy) { - return Type.getType(((JavaCondy) realObject).asConstantDynamic().getDescriptor()); + if (realObject instanceof ConstantDynamic) { + return Type.getType(((ConstantDynamic) realObject).getDescriptor()); } throw new IllegalArgumentException("Unrecognized or incompatible indy construct " + realObject.getClass().getName()); } From 82f21172d663cfdae5d5e6438e747b7c52a5e788 Mon Sep 17 00:00:00 2001 From: TheSilkMiner Date: Fri, 28 Feb 2025 13:55:07 +0100 Subject: [PATCH 17/17] Use constant instead of raw literal Signed-off-by: TheSilkMiner --- .../zenscript/javabytecode/compiler/indy/IndyHelpers.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java index caf39749..ab381aa8 100644 --- a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/indy/IndyHelpers.java @@ -84,7 +84,7 @@ private static Object toBsmArg(final Object object) { invokeType = Opcodes.H_INVOKESTATIC; } else if (method.cls.isInterface()) { invokeType = Opcodes.H_INVOKEINTERFACE; - } else if ("".equals(method.name) || (method.modifiers & JavaModifiers.PRIVATE) != 0) { + } else if (CONSTRUCTOR_NAME.equals(method.name) || (method.modifiers & JavaModifiers.PRIVATE) != 0) { invokeType = Opcodes.H_INVOKESPECIAL; } else { invokeType = Opcodes.H_INVOKEVIRTUAL;