Skip to content

Commit

Permalink
Invoke Patterns through PatternBootstraps
Browse files Browse the repository at this point in the history
  • Loading branch information
biboudis committed Oct 22, 2024
1 parent 58bfec1 commit 1dd7f43
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 2 deletions.
172 changes: 172 additions & 0 deletions src/java.base/share/classes/java/lang/runtime/PatternBootstraps.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package java.lang.runtime;

import jdk.internal.constant.ConstantUtils;
import jdk.internal.constant.ReferenceClassDescImpl;
import jdk.internal.misc.PreviewFeatures;

import java.lang.Enum.EnumDesc;
import java.lang.classfile.ClassFile;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.*;
import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;

import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
import static java.util.Objects.requireNonNull;

/**
* Bootstrap methods for linking {@code invokedynamic} call sites that implement
* the selection functionality of the {@code switch} statement. The bootstraps
* take additional static arguments corresponding to the {@code case} labels
* of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}.
*
* @since 21
*/
public class PatternBootstraps {

private PatternBootstraps() {}

private static final Object SENTINEL = new Object();
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final boolean previewEnabled = PreviewFeatures.isEnabled();

private static class StaticHolders {
private static final MethodHandle SYNTHETIC_PATTERN;

static {
try {
SYNTHETIC_PATTERN = LOOKUP.findStatic(PatternBootstraps.class, "syntheticPattern",
MethodType.methodType(Object.class, Class.class, Object.class));
}
catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
}

/**
* Bootstrap method for linking an {@code invokedynamic} call site that
* implements a pattern invocation on a target of a reference type.
* <p>
* If the reference type is a record class without a deconstructor then a deconstructor is synthesized from the
* accessors of the method, otherwise the discovered deconstructor is invoked.
* <p>
* The static arguments are the component types of the record or the binding types if its a deconstructor pattern.
* <p>
* The type of the returned {@code CallSite}'s method handle will have
* a return type of {@code Object} (the Carrier Object).
* It has one parameter: the sole argument will be an {@code Object} instance ({@code target}).
* <p>
* If the {@code target} is {@code null}, then the method of the call site
* returns {@literal -1}.
*
* @param lookup Represents a lookup context with the accessibility
* privileges of the caller. When used with {@code invokedynamic},
* this is stacked automatically by the VM.
* @param invocationName unused
* @param invocationType The invocation type of the {@code CallSite} with one parameter,
* a reference type, and an {@code Object} as a return type.
* @param mangledName The mangled name of the method declaration that will act as a pattern.
*
* @return a {@code CallSite} returning the first matching element as described above
*
* @throws NullPointerException if any argument is {@code null}
* @throws IllegalArgumentException if the invocation type is not a method type of first parameter of a reference type,
* and with {@code Object} as its return type,
*
* @jvms 4.4.6 The CONSTANT_NameAndType_info Structure
* @jvms 4.4.10 The CONSTANT_Dynamic_info and CONSTANT_InvokeDynamic_info Structures
*/
public static CallSite invokePattern(MethodHandles.Lookup lookup,
String invocationName,
MethodType invocationType,
String mangledName) {
Class<?> selectorType = invocationType.parameterType(0);
if (invocationType.parameterCount() != 1
|| (!invocationType.returnType().equals(Object.class)))
throw new IllegalArgumentException("Illegal invocation type " + invocationType);

try {
MethodHandle target = lookup.findStatic(selectorType,
mangledName,
MethodType.methodType(
Object.class, selectorType));
return new ConstantCallSite(target);
} catch (Throwable t) {
MethodHandle methodHandle = MethodHandles.insertArguments(StaticHolders.SYNTHETIC_PATTERN, 0, selectorType);

return new ConstantCallSite(methodHandle);
}
}

/**
* Returns a carrier, initialized with the extracted data according to the record component list of
* {@code matchCandidateType}.
*
* @param matchCandidateInstance the receiver of a pattern
* @param matchCandidateType the type of the match candidate
* @return initialized carrier object
*
* @throws Throwable throws if invocation of synthetic pattern fails
*/
@SuppressWarnings("removal")
private static Object syntheticPattern(Class<?> matchCandidateType, Object matchCandidateInstance) throws Throwable {
final RecordComponent[] components = AccessController.doPrivileged(
(PrivilegedAction<RecordComponent[]>) matchCandidateType::getRecordComponents);

Class<?>[] ctypes = Arrays.stream(components).map(c -> c.getType()).toArray(Class<?>[]::new);

Carriers.CarrierElements carrierElements = Carriers.CarrierFactory.of(ctypes);

MethodHandle initializingConstructor = carrierElements.initializingConstructor();

Object[] extracted = Arrays.stream(components).map(c -> {
try {
Method accessor = c.getAccessor();
accessor.setAccessible(true);
return accessor.invoke(matchCandidateInstance);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}).toArray();

MethodHandle spreadedPatternInvoker = initializingConstructor.asSpreader(Object[].class, ctypes.length);

Object carrier = spreadedPatternInvoker.invoke(extracted);

return carrier;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ public static Symtab instance(Context context) {
public final Type typeDescriptorType;
public final Type recordType;
public final Type switchBootstrapsType;
public final Type patternBootstrapsType;
public final Type constantBootstrapsType;
public final Type valueBasedType;
public final Type valueBasedInternalType;
Expand Down Expand Up @@ -617,6 +618,7 @@ public <R, P> R accept(ElementVisitor<R, P> v, P p) {
typeDescriptorType = enterClass("java.lang.invoke.TypeDescriptor");
recordType = enterClass("java.lang.Record");
switchBootstrapsType = enterClass("java.lang.runtime.SwitchBootstraps");
patternBootstrapsType = enterClass("java.lang.runtime.PatternBootstraps");
constantBootstrapsType = enterClass("java.lang.invoke.ConstantBootstraps");
valueBasedType = enterClass("jdk.internal.ValueBased");
valueBasedInternalType = enterSyntheticAnnotation("jdk.internal.ValueBased+Annotation");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,7 @@ private UnrolledRecordPattern unrollRecordPattern(JCRecordPattern recordPattern)
currentMethodSym);
JCVariableDecl mVar = make.VarDef(mSymbol, null);
JCExpression nullCheck = make.TypeTest(
make.App(make.Select(make.Ident(recordPattern.patternDeclaration.owner), recordPattern.patternDeclaration),
List.of(make.Ident(tempBind))).setType(syms.objectType),
generatePatternCall(recordPattern, tempBind),
make.BindingPattern(mVar).setType(mSymbol.type)).setType(syms.booleanType);

firstLevelChecks = nullCheck;
Expand Down Expand Up @@ -506,6 +505,41 @@ private UnrolledRecordPattern unrollRecordPattern(JCRecordPattern recordPattern)
return new UnrolledRecordPattern((JCBindingPattern) make.BindingPattern(recordBindingVar).setType(recordBinding.type), guard);
}

private JCMethodInvocation generatePatternCall(JCRecordPattern recordPattern, BindingSymbol tempBind) {
var mangledName = ((MethodSymbol)(recordPattern.patternDeclaration.baseSymbol())).externalName(types).toString();
LoadableConstant[] staticArgValues = new LoadableConstant[] { LoadableConstant.String(mangledName) };

List<Type> staticArgTypes = List.of(syms.methodHandleLookupType,
syms.stringType,
syms.methodTypeType,
syms.stringType);

MethodSymbol bsm = rs.resolveInternalMethod(
recordPattern.pos(), env, syms.patternBootstrapsType,
names.invokePattern, staticArgTypes, List.nil());

MethodType indyType = new MethodType(
List.of(recordPattern.type),
syms.objectType,
List.nil(),
syms.methodClass
);

DynamicMethodSymbol dynSym = new DynamicMethodSymbol(names.invokePattern,
syms.noSymbol,
bsm.asHandle(),
indyType,
staticArgValues);

JCFieldAccess qualifier = make.Select(make.QualIdent(bsm.owner), dynSym.name);
qualifier.sym = dynSym;
qualifier.type = syms.objectType;
return make.Apply(List.nil(),
qualifier,
List.of(make.Ident(tempBind)))
.setType(syms.objectType);
}

record UnrolledRecordPattern(JCBindingPattern primaryPattern, JCExpression newGuard) {}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ public static Names instance(Context context) {
public final Name enumSwitch;
public final Name enumConstant;

// pattern invocation
public final Name invokePattern;

public final Name.Table table;

@SuppressWarnings("this-escape")
Expand Down Expand Up @@ -424,6 +427,9 @@ record = fromString("record");
typeSwitch = fromString("typeSwitch");
enumSwitch = fromString("enumSwitch");
enumConstant = fromString("enumConstant");

// pattern invocation
invokePattern = fromString("invokePattern");
}

protected Name.Table createTable(Options options) {
Expand Down
91 changes: 91 additions & 0 deletions test/jdk/java/lang/runtime/PatternBootstrapsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

import org.testng.annotations.Test;

import java.io.Serializable;
import java.lang.Enum.EnumDesc;
import java.lang.classfile.ClassFile;
import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDescs;
import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.AccessFlag;
import java.lang.runtime.Carriers;
import java.lang.runtime.PatternBootstraps;
import java.lang.runtime.SwitchBootstraps;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.testng.Assert.*;

/**
* @test
* @bug 8318144
* @enablePreview
* @modules java.base/jdk.internal.classfile
* @run testng/othervm PatternBootstrapsTest
*/
@Test
public class PatternBootstrapsTest {

public static final MethodHandle INVK_PATTERN;
static {
try {
INVK_PATTERN = MethodHandles.lookup().findStatic(PatternBootstraps.class, "invokePattern",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class));
}
catch (ReflectiveOperationException e) {
throw new AssertionError("Should not happen", e);
}
}

record R(int i, int j) {}

class R2 {
int x;
int y;
public R2(int x, int y) {
this.x = x;
this.y = y;
}
public pattern R2(int x, int y) {
match R2(this.x, this.y);
}
}

private void testPatternInvocation(Object target, Class<?> targetType, String mangledName, int componentNo, int result) throws Throwable {
MethodType dtorType = MethodType.methodType(Object.class, targetType);
MethodHandle indy = ((CallSite) INVK_PATTERN.invoke(MethodHandles.lookup(), "", dtorType, mangledName)).dynamicInvoker();
List<MethodHandle> components = Carriers.components(MethodType.methodType(Object.class, int.class, int.class));
assertEquals((int) components.get(componentNo).invokeExact(indy.invoke(target)), result);
}

public void testPatternInvocations() throws Throwable {
testPatternInvocation(new R(1, 2), R.class, "R:I:I", 0, 1);
testPatternInvocation(new R2(1, 2), R2.class, "R2:I:I", 0, 1);
}
}

0 comments on commit 1dd7f43

Please sign in to comment.