Skip to content

Commit

Permalink
Use VarHandles in generated code (#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-manes committed Feb 16, 2021
1 parent 929e301 commit b503380
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 127 deletions.
Expand Up @@ -140,10 +140,10 @@ private void writeJavaFile() throws IOException {

for (TypeSpec typeSpec : factoryTypes) {
JavaFile.builder(getClass().getPackage().getName(), typeSpec)
.addFileComment(header, Year.now(timeZone))
.indent(" ")
.build()
.writeTo(directory);
.addFileComment(header, Year.now(timeZone))
.indent(" ")
.build()
.writeTo(directory);
}
}

Expand Down
Expand Up @@ -22,7 +22,6 @@

import java.lang.reflect.Constructor;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.TypeName;

Expand All @@ -35,8 +34,8 @@ public final class LocalCacheSelectorCode {

private LocalCacheSelectorCode() {
block = CodeBlock.builder()
.addStatement("$1T sb = new $1T(\"$2N.\")", StringBuilder.class,
((ClassName)LOCAL_CACHE_FACTORY).packageName());
.addStatement("$1T sb = new $1T(\"$2N.\")",
StringBuilder.class, LOCAL_CACHE_FACTORY.packageName());
}

private LocalCacheSelectorCode keys() {
Expand Down
Expand Up @@ -66,6 +66,7 @@
import com.github.benmanes.caffeine.cache.node.Finalize;
import com.github.benmanes.caffeine.cache.node.NodeContext;
import com.github.benmanes.caffeine.cache.node.NodeRule;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
Expand Down Expand Up @@ -142,10 +143,10 @@ private void writeJavaFile() throws IOException {

for (TypeSpec node : nodeTypes) {
JavaFile.builder(getClass().getPackage().getName(), node)
.addFileComment(header, Year.now(timeZone))
.indent(" ")
.build()
.writeTo(directory);
.addFileComment(header, Year.now(timeZone))
.indent(" ")
.build()
.writeTo(directory);
}
}

Expand All @@ -172,7 +173,16 @@ private void addClassJavaDoc() {
}

private void addConstants() {
Modifier[] modifiers = {Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL};
List<String> constants = ImmutableList.of("key", "value", "accessTime", "writeTime");
for (String constant : constants) {
String name = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, constant);
nodeFactory.addField(FieldSpec.builder(String.class, name)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", constant)
.build());
}

Modifier[] modifiers = { Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL };
nodeFactory.addField(FieldSpec.builder(Object.class, RETIRED_STRONG_KEY, modifiers)
.initializer("new Object()")
.build());
Expand Down
Expand Up @@ -15,13 +15,11 @@
*/
package com.github.benmanes.caffeine.cache;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.ref.ReferenceQueue;

import javax.lang.model.element.Modifier;

import com.google.common.base.CaseFormat;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
Expand Down Expand Up @@ -63,10 +61,11 @@ public final class Specifications {
public static final ParameterSpec valueRefQueueSpec =
ParameterSpec.builder(vRefQueueType, "valueReferenceQueue").build();

public static final TypeName UNSAFE_ACCESS =
ClassName.get("com.github.benmanes.caffeine.cache", "UnsafeAccess");
public static final TypeName METHOD_HANDLES = ClassName.get(MethodHandles.class);
public static final TypeName LOOKUP = ClassName.get(MethodHandles.Lookup.class);
public static final TypeName VAR_HANDLE = ClassName.get(VarHandle.class);

public static final TypeName LOCAL_CACHE_FACTORY =
public static final ClassName LOCAL_CACHE_FACTORY =
ClassName.get(PACKAGE_NAME, "LocalCacheFactory");
public static final ParameterizedTypeName NODE_FACTORY = ParameterizedTypeName.get(
ClassName.get(PACKAGE_NAME, "NodeFactory"), kTypeVar, vTypeVar);
Expand Down Expand Up @@ -109,20 +108,4 @@ public final class Specifications {
ClassName.get(PACKAGE_NAME, "FrequencySketch"), kTypeVar);

private Specifications() {}

/** Returns the offset constant to this variable. */
public static String offsetName(String varName) {
return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, varName) + "_OFFSET";
}

/** Creates a public static field with an Unsafe address offset. */
public static FieldSpec newFieldOffset(String className, String varName) {
String fieldName = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, varName);
return FieldSpec
.builder(long.class, offsetName(varName),
Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL)
.initializer("$T.objectFieldOffset($T.class, $L.$L)", UNSAFE_ACCESS,
ClassName.bestGuess(className), LOCAL_CACHE_FACTORY, fieldName)
.build();
}
}
Expand Up @@ -15,11 +15,9 @@
*/
package com.github.benmanes.caffeine.cache.node;

import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS;
import static com.github.benmanes.caffeine.cache.Specifications.keyRefQueueSpec;
import static com.github.benmanes.caffeine.cache.Specifications.keyRefSpec;
import static com.github.benmanes.caffeine.cache.Specifications.keySpec;
import static com.github.benmanes.caffeine.cache.Specifications.offsetName;
import static com.github.benmanes.caffeine.cache.Specifications.valueRefQueueSpec;
import static com.github.benmanes.caffeine.cache.Specifications.valueSpec;

Expand Down Expand Up @@ -91,14 +89,14 @@ private void callSiblingConstructor() {
}

private void assignKeyRefAndValue() {
context.constructorByKeyRef.addStatement("$T.UNSAFE.putObject(this, $N, $N)",
UNSAFE_ACCESS, offsetName("key"), "keyReference");
context.constructorByKeyRef.addStatement("$L.set(this, $N)",
varHandleName("key"), "keyReference");
if (isStrongValues()) {
context.constructorByKeyRef.addStatement("$T.UNSAFE.putObject(this, $N, $N)",
UNSAFE_ACCESS, offsetName("value"), "value");
context.constructorByKeyRef.addStatement("$L.set(this, $N)",
varHandleName("value"), "value");
} else {
context.constructorByKeyRef.addStatement("$T.UNSAFE.putObject(this, $N, new $T($N, $N, $N))",
UNSAFE_ACCESS, offsetName("value"), valueReferenceType(), "keyReference",
context.constructorByKeyRef.addStatement("$L.set(this, new $T($N, $N, $N))",
varHandleName("value"), valueReferenceType(), "keyReference",
"value", "valueReferenceQueue");
}
}
Expand Down
Expand Up @@ -16,9 +16,6 @@
package com.github.benmanes.caffeine.cache.node;

import static com.github.benmanes.caffeine.cache.Specifications.NODE;
import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS;
import static com.github.benmanes.caffeine.cache.Specifications.newFieldOffset;
import static com.github.benmanes.caffeine.cache.Specifications.offsetName;
import static org.apache.commons.lang3.StringUtils.capitalize;

import javax.lang.model.element.Modifier;
Expand Down Expand Up @@ -86,23 +83,21 @@ private void addLink(String method, String varName) {
private void addVariableTime(String varName) {
MethodSpec getter = MethodSpec.methodBuilder("getVariableTime")
.addModifiers(Modifier.PUBLIC)
.addStatement("return $T.UNSAFE.getLong(this, $N)",
UNSAFE_ACCESS, offsetName(varName))
.addStatement("return (long) $L.get(this)", varHandleName(varName))
.returns(long.class)
.build();
MethodSpec setter = MethodSpec.methodBuilder("setVariableTime")
.addModifiers(Modifier.PUBLIC)
.addParameter(long.class, varName)
.addStatement("$T.UNSAFE.putLong(this, $N, $N)",
UNSAFE_ACCESS, offsetName(varName), varName)
.addStatement("$L.set(this, $N)", varHandleName(varName), varName)
.build();
MethodSpec cas = MethodSpec.methodBuilder("casVariableTime")
.addModifiers(Modifier.PUBLIC)
.addParameter(long.class, "expect")
.addParameter(long.class, "update")
.returns(boolean.class)
.addStatement("return ($N == $N)\n&& $T.UNSAFE.compareAndSwapLong(this, $N, $N, $N)",
varName, "expect", UNSAFE_ACCESS, offsetName(varName), "expect", "update")
.addStatement("return ($N == $N)\n&& $L.compareAndSet(this, $N, $N)",
varName, "expect", varHandleName(varName), "expect", "update")
.build();
context.nodeSubtype
.addMethod(getter)
Expand All @@ -114,21 +109,24 @@ private void addAccessExpiration() {
if (!context.generateFeatures.contains(Feature.EXPIRE_ACCESS)) {
return;
}
context.nodeSubtype.addField(newFieldOffset(context.className, "accessTime"))

context.nodeSubtype
.addField(long.class, "accessTime", Modifier.VOLATILE)
.addMethod(newGetter(Strength.STRONG, TypeName.LONG, "accessTime", Visibility.LAZY))
.addMethod(newSetter(TypeName.LONG, "accessTime", Visibility.LAZY));
.addMethod(newGetter(Strength.STRONG, TypeName.LONG, "accessTime", Visibility.PLAIN))
.addMethod(newSetter(TypeName.LONG, "accessTime", Visibility.PLAIN));
addVarHandle("accessTime", long.class);
addTimeConstructorAssignment(context.constructorByKey, "accessTime");
addTimeConstructorAssignment(context.constructorByKeyRef, "accessTime");
}

private void addWriteExpiration() {
if (!Feature.useWriteTime(context.parentFeatures)
&& Feature.useWriteTime(context.generateFeatures)) {
context.nodeSubtype.addField(newFieldOffset(context.className, "writeTime"))
context.nodeSubtype
.addField(long.class, "writeTime", Modifier.VOLATILE)
.addMethod(newGetter(Strength.STRONG, TypeName.LONG, "writeTime", Visibility.LAZY))
.addMethod(newSetter(TypeName.LONG, "writeTime", Visibility.LAZY));
.addMethod(newGetter(Strength.STRONG, TypeName.LONG, "writeTime", Visibility.PLAIN))
.addMethod(newSetter(TypeName.LONG, "writeTime", Visibility.PLAIN));
addVarHandle("writeTime", long.class);
addTimeConstructorAssignment(context.constructorByKey, "writeTime");
addTimeConstructorAssignment(context.constructorByKeyRef, "writeTime");
}
Expand All @@ -143,14 +141,13 @@ private void addRefreshExpiration() {
.addParameter(long.class, "expect")
.addParameter(long.class, "update")
.returns(boolean.class)
.addStatement("return ($N == $N)\n&& $T.UNSAFE.compareAndSwapLong(this, $N, $N, $N)",
"writeTime", "expect", UNSAFE_ACCESS, offsetName("writeTime"), "expect", "update")
.addStatement("return ($N == $N)\n&& $L.compareAndSet(this, $N, $N)",
"writeTime", "expect", varHandleName("writeTime"), "expect", "update")
.build());
}

/** Adds a long constructor assignment. */
private void addTimeConstructorAssignment(MethodSpec.Builder constructor, String field) {
constructor.addStatement("$T.UNSAFE.putLong(this, $N, $N)",
UNSAFE_ACCESS, offsetName(field), "now");
constructor.addStatement("$L.set(this, $N)", varHandleName(field), "now");
}
}
Expand Up @@ -19,8 +19,6 @@
import static com.github.benmanes.caffeine.cache.Specifications.DEAD_WEAK_KEY;
import static com.github.benmanes.caffeine.cache.Specifications.RETIRED_STRONG_KEY;
import static com.github.benmanes.caffeine.cache.Specifications.RETIRED_WEAK_KEY;
import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS;
import static com.github.benmanes.caffeine.cache.Specifications.offsetName;

import java.lang.ref.Reference;

Expand Down Expand Up @@ -76,13 +74,12 @@ private void addState(String checkName, String actionName, String arg, boolean f
// Set the value to null only when dead, as otherwise the explicit removal of an expired async
// value will be notified as explicit rather than expired due to the isComputingAsync() check
if (finalized) {
action.addStatement("$T.UNSAFE.putObject(this, $N, null)",
UNSAFE_ACCESS, offsetName("value"));
action.addStatement("$L.set(this, null)", varHandleName("value"));
}
} else {
action.addStatement("(($T<V>) getValueReference()).clear()", Reference.class);
}
action.addStatement("$T.UNSAFE.putObject(this, $N, $N)", UNSAFE_ACCESS, offsetName("key"), arg);
action.addStatement("$L.set(this, $N)", varHandleName("key"), arg);
context.nodeSubtype.addMethod(action.build());
}
}
Expand Up @@ -16,7 +16,6 @@
package com.github.benmanes.caffeine.cache.node;

import static com.github.benmanes.caffeine.cache.Specifications.kTypeVar;
import static com.github.benmanes.caffeine.cache.Specifications.newFieldOffset;

import javax.lang.model.element.Modifier;

Expand All @@ -38,10 +37,10 @@ protected boolean applies() {
@Override
protected void execute() {
context.nodeSubtype
.addField(newFieldOffset(context.className, "key"))
.addField(newKeyField())
.addMethod(newGetter(keyStrength(), kTypeVar, "key", Visibility.LAZY))
.addMethod(newGetter(keyStrength(), kTypeVar, "key", Visibility.PLAIN))
.addMethod(newGetRef("key"));
addVarHandle("key", Object.class);
}

private FieldSpec newKeyField() {
Expand Down
Expand Up @@ -15,9 +15,6 @@
*/
package com.github.benmanes.caffeine.cache.node;

import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS;
import static com.github.benmanes.caffeine.cache.Specifications.newFieldOffset;
import static com.github.benmanes.caffeine.cache.Specifications.offsetName;
import static com.github.benmanes.caffeine.cache.Specifications.vRefQueueType;
import static com.github.benmanes.caffeine.cache.Specifications.vTypeVar;

Expand Down Expand Up @@ -45,12 +42,12 @@ protected boolean applies() {
@Override
protected void execute() {
context.nodeSubtype
.addField(newFieldOffset(context.className, "value"))
.addField(newValueField())
.addMethod(newGetter(valueStrength(), vTypeVar, "value", Visibility.LAZY))
.addMethod(newGetter(valueStrength(), vTypeVar, "value", Visibility.PLAIN))
.addMethod(newGetRef("value"))
.addMethod(makeSetValue())
.addMethod(makeContainsValue());
addVarHandle("value", Object.class);
}

private FieldSpec newValueField() {
Expand All @@ -68,12 +65,11 @@ private MethodSpec makeSetValue() {
.addParameter(vRefQueueType, "referenceQueue");

if (isStrongValues()) {
setter.addStatement("$T.UNSAFE.putObject(this, $N, $N)",
UNSAFE_ACCESS, offsetName("value"), "value");
setter.addStatement("$L.set(this, $N)", varHandleName("value"), "value");
} else {
setter.addStatement("(($T<V>) getValueReference()).clear()", Reference.class);
setter.addStatement("$T.UNSAFE.putObject(this, $N, new $T($L, $N, referenceQueue))",
UNSAFE_ACCESS, offsetName("value"), valueReferenceType(), "getKeyReference()", "value");
setter.addStatement("$L.set(this, new $T($L, $N, referenceQueue))",
varHandleName("value"), valueReferenceType(), "getKeyReference()", "value");
}

return setter.build();
Expand Down
Expand Up @@ -15,6 +15,11 @@
*/
package com.github.benmanes.caffeine.cache.node;

import static com.github.benmanes.caffeine.cache.Specifications.LOOKUP;
import static com.github.benmanes.caffeine.cache.Specifications.METHOD_HANDLES;

import com.squareup.javapoet.CodeBlock;

/**
* Finishes construction of the node.
*
Expand All @@ -33,5 +38,23 @@ protected void execute() {
.addMethod(context.constructorDefault.build())
.addMethod(context.constructorByKey.build())
.addMethod(context.constructorByKeyRef.build());
addStaticBlock();
}

public void addStaticBlock() {
if (context.varHandles.isEmpty()) {
return;
}
var codeBlock = CodeBlock.builder()
.addStatement("$T lookup = $T.lookup()", LOOKUP, METHOD_HANDLES)
.beginControlFlow("try");
for (var varHandle : context.varHandles) {
varHandle.accept(codeBlock);
}
codeBlock
.nextControlFlow("catch ($T e)", ReflectiveOperationException.class)
.addStatement("throw new ExceptionInInitializerError(e)")
.endControlFlow();
context.nodeSubtype.addStaticBlock(codeBlock.build());
}
}

0 comments on commit b503380

Please sign in to comment.