javajvminlining

How to force a new instantiation of a lambda-definition


The Java-Spec guarantees that a given lambda-definition, e.g. () -> "Hello World", is compiled/converted to exactly one implementation class (every definition, not every occurence that "looks" the same).

Is there any way I can force the java-compiler/jvm to generate a new lambda-definition instead of sharing a common one? I am currently implementing a library that weaves multiple function parts into a BiFunction which suffers from mega-morphic call-sites because of the guarantees given by the java-spec (EDIT: I stand corrected: the Java-Spec does not guarantee a single shared class - the current reference implementation does this though):

        public <In, Out, A> BiFunction<In, Out, Out> weave(
             Function<? super In, A> getter,
             BiConsumer<? super Out, ? super A> consumer
        ) {
            return (in, out) -> {
                consumer.accept(out, getter.apply(in));
                return out;
            };
        }

Every lambda generated through this code shares the same lambda-definition and is thus mostly uninlineable / unoptimizeable.


Solution

  • In the current implementation, the caching of generated classes (or even instances for the non capturing lambda expressions), is a property of the invokedynamic instruction which will reuse the result of the bootstrapping done on the first execution.

    The bootstrap method itself, hosted in the LambdaMetafactory class will generate a new class each time it is invoked. So when you use this factory directly, you’ll get a new class on each invocation, under the current implementation.

    public <In, Out, A> BiFunction<In, Out, Out> weave(
         Function<? super In, A> getter,
         BiConsumer<? super Out, ? super A> consumer) {
    
        MethodHandles.Lookup l = MethodHandles.lookup();
        try {
            MethodHandle target = l.findStatic(l.lookupClass(), "weaveLambdaBody",
                MethodType.methodType(Object.class, Function.class, BiConsumer.class,
                    Object.class, Object.class));
            MethodType t = target.type().dropParameterTypes(0, 2);
            return (BiFunction<In, Out, Out>)LambdaMetafactory.metafactory(l, "apply",
                target.type().dropParameterTypes(2, 4).changeReturnType(BiFunction.class),
                t, target, t) .getTarget().invokeExact(getter, consumer);
        }
        catch(RuntimeException | Error e) {
            throw e;
        }
        catch(Throwable t) {
            throw new IllegalStateException(t);
        }
    }
    private static <In, Out, A> Out weaveLambdaBody(
        Function<? super In, A> getter,
        BiConsumer<? super Out, ? super A> consumer,
        In in, Out out) {
    
        consumer.accept(out, getter.apply(in));
        return out;
    }
    

    First, you have to desugar the lambda body into a method. The captured values come first in its parameter list, followed by the parameters of the functional interface type. The LambdaMetafactory has an exhaustive documentation about its usage.

    While I kept the type parameters for documentation purposes, it should be clear that you lose the compile-time safety here, with such an operation.