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 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/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/JavaMangler.java index fe3edbc7..b3976363 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) { @@ -116,40 +116,41 @@ public int hashCode() { return builder.toString(); } - public String mangleGeneratedLambdaName(final String interfaceName) { + public String mangleLambdaMethod(final String parentMethodName, final String interfaceName) { final class LambdaId { - final String target; + final String interfaceName; + final String method; - LambdaId(final String target) { - this.target = target; + 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.target.equals(((LambdaId) o).target); + 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.target.hashCode(); + return 17 * (this.interfaceName.hashCode() + 31 * this.method.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"; + final String sanitizedMethodName; + if (parentMethodName == null) { + sanitizedMethodName = "$null"; + } else if ("".equals(parentMethodName) || "".equals(parentMethodName)) { + sanitizedMethodName = "$_" + parentMethodName.substring(1, parentMethodName.length() - 1) + '_'; } else { - return "$" + parameterId; + sanitizedMethodName = parentMethodName; } + 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 + '$' + canonicalInterfaceTarget + '$' + this.mangleCounters.get(id); } private String mangleScriptName(final String rawName) { 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..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,17 +1,13 @@ 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; 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,15 +19,12 @@ 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; 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; @@ -51,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) { @@ -68,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; } @@ -474,113 +469,26 @@ 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 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); + FunctionHeader header = expression.original == null ? expression.header : expression.original; + interfaceName = context.getInternalName(new FunctionTypeID(header)); } - 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)); - - constructorWriter.load(type, i); - constructorWriter.putField(className, name, type.getDescriptor()); - } - - 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 - ) - ); - expression.body.accept(new JavaStatementVisitor(context, withCapturedExpressionVisitor, javaMangler)); + final JavaNativeMethod functionalMethod = context.getFunctionalInterface(expression.original == null ? expression.type : new FunctionTypeID(expression.original)); + final JavaNativeMethod methodInfo = functionalMethod.withModifiers(functionalMethod.modifiers & ~JavaModifiers.ABSTRACT); - functionWriter.ret(); - functionWriter.end(); - - lambdaCW.visitEnd(); - - context.register(className, lambdaCW.toByteArray()); + this.lambdaIndyCompiler.compileFunctionExpressionViaIndy(expression, interfaceName, methodInfo); return null; } - private String calcFunctionDescriptor(LambdaClosure closure) { - StringJoiner joiner = new StringJoiner("", "(", ")V"); - for (CapturedExpression capture : closure.captures) { - String descriptor = context.getDescriptor(capture.type); - joiner.add(descriptor); - } - return joiner.toString(); - } - @Override public Void visitGetField(GetFieldExpression expression) { JavaField field = context.getJavaField(expression.field); @@ -956,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; } @@ -993,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 + ); } } @@ -1068,141 +979,6 @@ public Void visitSetStaticField(SetStaticFieldExpression expression) { return null; } - private void visitFunctionalInterfaceWrapping(JavaFunctionInterfaceCastExpression expression) { - 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); @@ -1341,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/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/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..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; @@ -15,13 +17,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.*; @@ -295,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) { @@ -384,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"); @@ -1086,7 +1099,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 +1125,17 @@ public void invokeInterface(JavaNativeMethod method) { visitor.visitMethodInsn(INVOKEINTERFACE, method.cls.internalName, method.name, method.descriptor, true); } + public void invokeDynamic(UnaryOperator indyBuilder) { + invokeDynamic(JavaIndy.build(indyBuilder)); + } + + public void invokeDynamic(JavaIndy indy) { + if (debug) + logger.debug("invokeDynamic " + indy); + + indy.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/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/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/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..ab381aa8 --- /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 (CONSTRUCTOR_NAME.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 ConstantDynamic) { + return Type.getType(((ConstantDynamic) realObject).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/LambdaClosureInfo.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaClosureInfo.java new file mode 100644 index 00000000..7e5faa48 --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaClosureInfo.java @@ -0,0 +1,48 @@ +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.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 LambdaClosureInfo(final LambdaClosure closure, final TypeID thisCapture) { + this.closure = closure; + this.thisCapture = thisCapture; + } + + 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 = computeCapturedThisType(capturedThis); + return new LambdaClosureInfo(closure, thisType); + } + + private static TypeID computeCapturedThisType(final CapturedThisExpression expression) { + if (expression != null) { + return expression.type; + } + + // TODO("Make this default to void") + return null; + } + + public LambdaClosure closure() { + return this.closure; + } + + public TypeID thisType() { + return this.thisCapture; + } +} 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..716bb56f --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/LambdaIndyCompiler.java @@ -0,0 +1,260 @@ +package org.openzen.zenscript.javabytecode.compiler.lambda; + +import org.objectweb.asm.Label; +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.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.*; +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; + +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(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, + 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, JavaModifiers.PRIVATE | JavaModifiers.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, 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, closureInfo); + } + + private void generateLambdaMethodBody( + final JavaCompilingMethod method, + final JavaWriter writer, + final JavaStatementVisitor statementVisitor, + final FunctionExpression lambdaExpression, + final LambdaClosureInfo closureInfo + ) { + 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, closureInfo, begin, end); + + writer.end(); + } + + 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; + + 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 { + 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); + writer.addVariableInfo(info); + + localIndex += type.getSize(); + } + } + + private void generateIndyMethodCall( + 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); + } + + this.writer.invokeDynamic(indy -> indy + .callSite(methodInfo.name, this.computeIndyDescriptor(closureInfo, interfaceType)) + .bsm(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 Type 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 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))) + .bsm(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); + } +} 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/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/lambda/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java new file mode 100644 index 00000000..bb310c01 --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadCapturesOnIndyCapturedExpressionVisitor.java @@ -0,0 +1,34 @@ +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 final class JavaLoadCapturesOnIndyCapturedExpressionVisitor implements CapturedExpressionVisitor { + private final ExpressionVisitor visitor; + + public JavaLoadCapturesOnIndyCapturedExpressionVisitor(final ExpressionVisitor visitor) { + this.visitor = visitor; + } + + @Override + public Void visitCapturedThis(final CapturedThisExpression expression) { + return null; + } + + @Override + public Void visitCapturedParameter(final CapturedParameterExpression expression) { + return new GetFunctionParameterExpression(expression.position, expression.parameter).accept(this.visitor); + } + + @Override + public Void visitCapturedLocal(final CapturedLocalVariableExpression expression) { + return new GetLocalVariableExpression(expression.position, expression.variable).accept(this.visitor); + } + + @Override + public Void visitRecaptured(final CapturedClosureExpression expression) { + return expression.value.accept(this.visitor); + } +} diff --git a/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java new file mode 100644 index 00000000..0decf7c5 --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaLoadThisOnIndyCapturedExpressionVisitor.java @@ -0,0 +1,34 @@ +package org.openzen.zenscript.javabytecode.compiler.lambda.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/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 new file mode 100644 index 00000000..0996c4ae --- /dev/null +++ b/JavaBytecodeCompiler/src/main/java/org/openzen/zenscript/javabytecode/compiler/lambda/capturing/JavaRedirectCapturesCapturedExpressionVisitor.java @@ -0,0 +1,51 @@ +package org.openzen.zenscript.javabytecode.compiler.lambda.capturing; + +import org.openzen.zenscript.codemodel.expression.FunctionExpression; +import org.openzen.zenscript.codemodel.expression.captured.*; +import org.openzen.zenscript.javabytecode.JavaBytecodeContext; +import org.openzen.zenscript.javabytecode.compiler.JavaWriter; +import org.openzen.zenscript.javabytecode.compiler.lambda.LambdaClosureInfo; + +public final class JavaRedirectCapturesCapturedExpressionVisitor implements CapturedExpressionVisitor { + private final JavaWriter javaWriter; + private final JavaCaptureDataFinderCapturedExpressionVisitor captureDataFinder; + + public JavaRedirectCapturesCapturedExpressionVisitor( + final JavaWriter javaWriter, + final FunctionExpression functionExpression, + final LambdaClosureInfo closureInfo, + final JavaBytecodeContext context + ) { + this.javaWriter = javaWriter; + this.captureDataFinder = new JavaCaptureDataFinderCapturedExpressionVisitor(functionExpression, closureInfo, context); + } + + @Override + public Void visitCapturedThis(final CapturedThisExpression expression) { + return this.loadByCaptureData(expression); + } + + @Override + public Void visitCapturedParameter(CapturedParameterExpression expression) { + return this.loadByCaptureData(expression); + } + + @Override + public Void visitCapturedLocal(CapturedLocalVariableExpression expression) { + return this.loadByCaptureData(expression); + } + + @Override + public Void visitRecaptured(CapturedClosureExpression expression) { + return this.loadByCaptureData(expression); + } + + private Void loadByCaptureData(final CapturedExpression expression) { + return this.loadByCaptureData(expression.accept(this.captureDataFinder)); + } + + 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; + } +} 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 +} 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..79652609 --- /dev/null +++ b/JavaRuntime/src/main/java/org/openzen/zenscript/javart/factory/LambdaFactory.java @@ -0,0 +1,755 @@ +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; +import java.util.function.BinaryOperator; + +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; + + LambdaFlags(final int flags) { + this.generateBridge = (flags & FLAG_GENERATE_BRIDGE) != 0; + } + + boolean generateBridge() { + return this.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) + @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 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 = 0b1; + private static final int FLAG_ALL_FLAGS = FLAG_GENERATE_BRIDGE; + + 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 + // 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, + 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 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(); + + 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 (!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 > 1; --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 + 1]; + 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); + generateInterfaceClassConstructor(writer, callSiteSignature, className); + generateInterfaceClassFactory(writer, callSiteSignature, 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) { + 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); + + for (int i = 1, s = callSiteSignature.parameterCount(); i < s; ++i) { + final Class fieldType = callSiteSignature.parameterType(i); + 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 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 { + fieldName = (CAPTURE_FIELD_NAME_PREFIX + (i - 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 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); + + 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(); + } + + for (int i = 1, s = callSiteSignature.parameterCount(); i < s; ++i) { + final String fieldDescriptor = Type.getDescriptor(callSiteSignature.parameterType(i)); + final int captureIndex = i - 1; + 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)); + + writer.visitVarInsn(bridgeType.getOpcode(Opcodes.ILOAD), localIndex); + LambdaTypeConverters.convert(bridgeType, targetType, writer); + + 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()); + + LambdaTypeConverters.convert(targetReturnType, bridgeReturnType, writer); + writer.visitInsn(bridgeReturnType.getOpcode(Opcodes.IRETURN)); + + 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); + } +} 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('.')); 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())), 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(); 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