X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-dom-codec%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fdom%2Fcodec%2Fimpl%2FCodecDataObjectGenerator.java;h=1c4021ca66ac2dda4b83eb6a470b88d68ca4dde0;hb=d9a9901b2b9e33685d1702cc7105509c5369625d;hp=94b4803fbd3897851a49d8938a745c8e0236a4db;hpb=3aa65296b24cb2fe7042a99e581702278451bc35;p=mdsal.git diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectGenerator.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectGenerator.java index 94b4803fbd..1c4021ca66 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectGenerator.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CodecDataObjectGenerator.java @@ -10,51 +10,43 @@ package org.opendaylight.mdsal.binding.dom.codec.impl; import static com.google.common.base.Verify.verify; import static com.google.common.base.Verify.verifyNotNull; import static java.util.Objects.requireNonNull; -import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.THIS; +import static net.bytebuddy.implementation.bytecode.member.MethodVariableAccess.loadThis; import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.getField; import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.invokeMethod; import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.putField; -import com.google.common.base.MoreObjects.ToStringHelper; -import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.VarHandle; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.field.FieldDescription; -import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeDescription.ForLoadedType; import net.bytebuddy.description.type.TypeDescription.Generic; import net.bytebuddy.dynamic.DynamicType.Builder; import net.bytebuddy.dynamic.scaffold.InstrumentedType; import net.bytebuddy.implementation.Implementation; -import net.bytebuddy.implementation.Implementation.Context; -import net.bytebuddy.implementation.bytecode.Addition; import net.bytebuddy.implementation.bytecode.ByteCodeAppender; -import net.bytebuddy.implementation.bytecode.Multiplication; import net.bytebuddy.implementation.bytecode.StackManipulation; import net.bytebuddy.implementation.bytecode.assign.TypeCasting; import net.bytebuddy.implementation.bytecode.constant.ClassConstant; -import net.bytebuddy.implementation.bytecode.constant.IntegerConstant; import net.bytebuddy.implementation.bytecode.constant.TextConstant; import net.bytebuddy.implementation.bytecode.member.MethodReturn; import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess; -import net.bytebuddy.jar.asm.Label; -import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader; -import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.ClassGenerator; -import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult; +import org.opendaylight.mdsal.binding.dom.codec.impl.ClassGeneratorBridge.LocalNameProvider; +import org.opendaylight.mdsal.binding.dom.codec.impl.ClassGeneratorBridge.NodeContextSupplierProvider; +import org.opendaylight.mdsal.binding.dom.codec.impl.loader.CodecClassLoader; +import org.opendaylight.mdsal.binding.dom.codec.impl.loader.CodecClassLoader.ClassGenerator; +import org.opendaylight.mdsal.binding.dom.codec.impl.loader.CodecClassLoader.GeneratorResult; +import org.opendaylight.mdsal.binding.spec.naming.BindingMapping; import org.opendaylight.yangtools.yang.binding.DataObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,9 +56,11 @@ import org.slf4j.LoggerFactory; * *

* Code generation here is probably more involved than usual mainly due to the fact we *really* want to express the - * strong connection between a generated class and BindingCodecContext in terms of a true constant, which boils down to - * {@code private static final NodeContextSupplier NCS}. Having such constants provides significant boost to JITs - * ability to optimize code -- especially with inlining and constant propagation. + * strong connection between a generated class to the extent possible. In most cases (grouping-generated types) this + * involves one level of indirection, which is a safe approach. If we are dealing with a type generated outside of a + * grouping statement, though, we are guaranteed instantiation-invariance and hence can hard-wire to a runtime-constant + * {@link NodeContextSupplier} -- which provides significant boost to JITs ability to optimize code -- especially with + * inlining and constant propagation. * *

* The accessor mapping performance is critical due to users typically not taking care of storing the results acquired @@ -85,16 +79,15 @@ import org.slf4j.LoggerFactory; * we end up generating a class with the following layout: *

  *     public final class Foo$$$codecImpl extends CodecDataObject implements Foo {
- *         private static final AtomicRefereceFieldUpdater<Foo$$$codecImpl, Object> getBar$$$A;
- *         private static final NodeContextSupplier getBar$$$C;
+ *         private static final VarHandle getBar$$$V;
  *         private volatile Object getBar;
  *
- *         public Foo$$$codecImpl(NormalizedNodeContainer data) {
+ *         public Foo$$$codecImpl(DistinctNodeContainer data) {
  *             super(data);
  *         }
  *
  *         public Bar getBar() {
- *             return (Bar) codecMember(getBar$$$A, getBar$$$C);
+ *             return (Bar) codecMember(getBar$$$V, "bar");
  *         }
  *     }
  * 
@@ -103,362 +96,417 @@ import org.slf4j.LoggerFactory; * This strategy minimizes the bytecode footprint and follows the generally good idea of keeping common logic in a * single place in a maintainable form. The glue code is extremely light (~6 instructions), which is beneficial on both * sides of invocation: - * - generated method can readily be inlined into the caller - * - it forms a call site into which codeMember() can be inlined with both AtomicReferenceFieldUpdater and - * NodeContextSupplier being constant + * * *

- * The second point is important here, as it allows the invocation logic around AtomicRefereceFieldUpdater to completely - * disappear, becoming synonymous with operations of a volatile field. NodeContextSupplier being constant also means - * it will resolve to one of its two implementations, allowing NodeContextSupplier.get() to be resolved to a constant - * (pointing to the supplier itself) or to a simple volatile read (which will be non-null after first access). + * The second point is important here, as it allows the invocation logic around VarHandle to completely disappear, + * becoming synonymous with operations on a field. Even though the field itself is declared as volatile, it is only ever + * accessed through helper method using VarHandles -- and those helpers are using relaxed field ordering + * of {@code getAcquire()}/{@code setRelease()} memory semantics. * *

- * The sticky point here is the NodeContextSupplier, as it is a heap object which cannot normally be looked up from the - * static context in which the static class initializer operates -- so we need perform some sort of a trick here. + * Furthermore there are distinct {@code codecMember} methods, each of which supports a different invocation style: + *

+ * The third mode of operation requires that the object being implemented is not defined in a {@code grouping}, because + * it welds the object to a particular namespace -- hence it trades namespace mobility for access speed. * *

- * Eventhough ByteBuddy provides facilities for bridging references to type fields, those facilities operate on volatile - * fields -- hence they do not quite work for us. + * The sticky point here is the NodeContextSupplier, as it is a heap object which cannot normally be looked up from the + * static context in which the static class initializer operates -- so we need perform some sort of a trick here. + * Even though ByteBuddy provides facilities for bridging references to type fields, those facilities operate on + * volatile fields -- hence they do not quite work for us. * *

* Another alternative, which we used in Javassist-generated DataObjectSerializers, is to muck with the static field - * using reflection -- which works, but requires redefinition of Field.modifiers, which is something Java 9 complains + * using reflection -- which works, but requires redefinition of Field.modifiers, which is something Java 9+ complains * about quite noisily. * *

* We take a different approach here, which takes advantage of the fact we are in control of both code generation (here) * and class loading (in {@link CodecClassLoader}). The process is performed in four steps: *

* *

* This strategy works due to close cooperation with the target ClassLoader, as the entire code generation and loading * block runs with the class loading lock for this FQCN and the reference is not leaked until the process completes. */ -final class CodecDataObjectGenerator> implements ClassGenerator { +abstract class CodecDataObjectGenerator> implements ClassGenerator { + // Not reusable definition: we can inline NodeContextSuppliers without a problem + // FIXME: MDSAL-443: wire this implementation, which requires that BindingRuntimeTypes provides information about + // types being generated from within a grouping + private static final class Fixed> extends CodecDataObjectGenerator + implements NodeContextSupplierProvider { + private final ImmutableMap properties; + + Fixed(final TypeDescription superClass, final ImmutableMap properties, + final @Nullable Method keyMethod) { + super(superClass, keyMethod); + this.properties = requireNonNull(properties); + } + + @Override + Builder generateGetters(final Builder builder) { + Builder tmp = builder; + for (Method method : properties.keySet()) { + LOG.trace("Generating for fixed method {}", method); + final String methodName = method.getName(); + final TypeDescription retType = TypeDescription.ForLoadedType.of(method.getReturnType()); + tmp = tmp.defineMethod(methodName, retType, PUB_FINAL).intercept( + new SupplierGetterMethodImplementation(methodName, retType)); + } + return tmp; + } + + @Override + public NodeContextSupplier resolveNodeContextSupplier(final String methodName) { + final Optional> found = properties.entrySet().stream() + .filter(entry -> methodName.equals(entry.getKey().getName())).findAny(); + verify(found.isPresent(), "Failed to find property for %s in %s", methodName, this); + return verifyNotNull(found.get().getValue()); + } + } + + // Reusable definition: we have to rely on context lookups + private static final class Reusable> extends CodecDataObjectGenerator + implements LocalNameProvider { + private final ImmutableMap simpleProperties; + private final Map> daoProperties; + + Reusable(final TypeDescription superClass, final ImmutableMap simpleProperties, + final Map> daoProperties, final @Nullable Method keyMethod) { + super(superClass, keyMethod); + this.simpleProperties = requireNonNull(simpleProperties); + this.daoProperties = requireNonNull(daoProperties); + } + + @Override + Builder generateGetters(final Builder builder) { + Builder tmp = builder; + for (Method method : simpleProperties.keySet()) { + LOG.trace("Generating for simple method {}", method); + final String methodName = method.getName(); + final TypeDescription retType = TypeDescription.ForLoadedType.of(method.getReturnType()); + tmp = tmp.defineMethod(methodName, retType, PUB_FINAL).intercept( + new SimpleGetterMethodImplementation(methodName, retType)); + } + for (Entry> entry : daoProperties.entrySet()) { + final Method method = entry.getKey(); + LOG.trace("Generating for structured method {}", method); + final String methodName = method.getName(); + final TypeDescription retType = TypeDescription.ForLoadedType.of(method.getReturnType()); + tmp = tmp.defineMethod(methodName, retType, PUB_FINAL).intercept( + new StructuredGetterMethodImplementation(methodName, retType, entry.getValue())); + } + + return tmp; + } + + @Override + public String resolveLocalName(final String methodName) { + final Optional> found = simpleProperties.entrySet().stream() + .filter(entry -> methodName.equals(entry.getKey().getName())).findAny(); + verify(found.isPresent(), "Failed to find property for %s in %s", methodName, this); + return found.get().getValue().getSchema().getQName().getLocalName(); + } + } + private static final Logger LOG = LoggerFactory.getLogger(CodecDataObjectGenerator.class); private static final Generic BB_BOOLEAN = TypeDefinition.Sort.describe(boolean.class); - private static final Generic BB_DATAOBJECT = TypeDefinition.Sort.describe(DataObject.class); - private static final Generic BB_HELPER = TypeDefinition.Sort.describe(ToStringHelper.class); + private static final Generic BB_OBJECT = TypeDefinition.Sort.describe(Object.class); private static final Generic BB_INT = TypeDefinition.Sort.describe(int.class); - private static final Generic BB_IIC = TypeDefinition.Sort.describe(IdentifiableItemCodec.class); - private static final Generic BB_NCS = TypeDefinition.Sort.describe(NodeContextSupplier.class); - - private static final StackManipulation BRIDGE_RESOLVE = invokeMethod(CodecDataObjectBridge.class, - "resolve", String.class); - private static final StackManipulation BRIDGE_RESOLVE_KEY = invokeMethod(CodecDataObjectBridge.class, - "resolveKey", String.class); - private static final StackManipulation CODEC_MEMBER = invokeMethod(CodecDataObject.class, - "codecMember", AtomicReferenceFieldUpdater.class, NodeContextSupplier.class); - private static final StackManipulation CODEC_MEMBER_KEY = invokeMethod(CodecDataObject.class, - "codecMember", AtomicReferenceFieldUpdater.class, IdentifiableItemCodec.class); - - private static final StackManipulation ARRAYS_EQUALS = invokeMethod(Arrays.class, "equals", - byte[].class, byte[].class); - private static final StackManipulation OBJECTS_EQUALS = invokeMethod(Objects.class, "equals", - Object.class, Object.class); - private static final StackManipulation HELPER_ADD = invokeMethod(ToStringHelper.class, "add", - String.class, Object.class); + private static final Generic BB_STRING = TypeDefinition.Sort.describe(String.class); + private static final TypeDescription BB_CDO = ForLoadedType.of(CodecDataObject.class); + private static final TypeDescription BB_ACDO = ForLoadedType.of(AugmentableCodecDataObject.class); private static final StackManipulation FIRST_ARG_REF = MethodVariableAccess.REFERENCE.loadFrom(1); private static final int PROT_FINAL = Opcodes.ACC_PROTECTED | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC; private static final int PUB_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC; - private static final Builder CDO; - private static final Builder ACDO; - - static { - final ByteBuddy bb = new ByteBuddy(); - CDO = bb.subclass(CodecDataObject.class).visit(ByteBuddyUtils.computeFrames()); - ACDO = bb.subclass(AugmentableCodecDataObject.class).visit(ByteBuddyUtils.computeFrames()); - } + private static final ByteBuddy BB = new ByteBuddy(); - private final ImmutableMap properties; - private final Entry keyMethod; - private final Builder template; + private final TypeDescription superClass; + private final Method keyMethod; - private CodecDataObjectGenerator(final Builder template, - final ImmutableMap properties, - final @Nullable Entry keyMethod) { - this.template = requireNonNull(template); - this.properties = requireNonNull(properties); + CodecDataObjectGenerator(final TypeDescription superClass, final @Nullable Method keyMethod) { + this.superClass = requireNonNull(superClass); this.keyMethod = keyMethod; } static > Class generate(final CodecClassLoader loader, - final Class bindingInterface, final ImmutableMap properties, - final Entry keyMethod) { + final Class bindingInterface, final ImmutableMap simpleProperties, + final Map> daoProperties, final Method keyMethod) { return loader.generateClass(bindingInterface, "codecImpl", - new CodecDataObjectGenerator<>(CDO, properties, keyMethod)); + new Reusable<>(BB_CDO, simpleProperties, daoProperties, keyMethod)); } static > Class generateAugmentable( final CodecClassLoader loader, final Class bindingInterface, - final ImmutableMap properties, - final Entry keyMethod) { + final ImmutableMap simpleProperties, + final Map> daoProperties, final Method keyMethod) { return loader.generateClass(bindingInterface, "codecImpl", - new CodecDataObjectGenerator<>(ACDO, properties, keyMethod)); + new Reusable<>(BB_ACDO, simpleProperties, daoProperties, keyMethod)); } @Override - public GeneratorResult generateClass(final CodecClassLoader loeader, final String fqcn, + public final GeneratorResult generateClass(final CodecClassLoader loeader, final String fqcn, final Class bindingInterface) { LOG.trace("Generating class {}", fqcn); + final Generic bindingDef = TypeDefinition.Sort.describe(bindingInterface); @SuppressWarnings("unchecked") - Builder builder = (Builder) template.name(fqcn).implement(bindingInterface); - - for (Method method : properties.keySet()) { - LOG.trace("Generating for method {}", method); - final String methodName = method.getName(); - final TypeDescription retType = TypeDescription.ForLoadedType.of(method.getReturnType()); - builder = builder.defineMethod(methodName, retType, PUB_FINAL) - .intercept(new MethodImplementation(BB_NCS, BRIDGE_RESOLVE, CODEC_MEMBER, methodName, retType)); - } + Builder builder = (Builder) BB.subclass(Generic.Builder.parameterizedType(superClass, bindingDef).build()) + .name(fqcn).implement(bindingDef); + + builder = generateGetters(builder); if (keyMethod != null) { LOG.trace("Generating for key {}", keyMethod); - final Method method = keyMethod.getKey(); - final String methodName = method.getName(); - final TypeDescription retType = TypeDescription.ForLoadedType.of(method.getReturnType()); - builder = builder.defineMethod(methodName, retType, PUB_FINAL) - .intercept(new MethodImplementation(BB_IIC, BRIDGE_RESOLVE_KEY, CODEC_MEMBER_KEY, methodName, - retType)); + final String methodName = keyMethod.getName(); + final TypeDescription retType = TypeDescription.ForLoadedType.of(keyMethod.getReturnType()); + builder = builder.defineMethod(methodName, retType, PUB_FINAL).intercept( + new KeyMethodImplementation(methodName, retType)); } - // Index all property methods, turning them into "getFoo()" invocations, retaining order. We will be using - // those invocations in each of the three methods. Note that we do not glue the invocations to 'this', as we - // will be invoking them on 'other' in codecEquals() - final ImmutableMap methods = Maps.uniqueIndex(properties.keySet(), - ByteBuddyUtils::invokeMethod); - // Final bits: return GeneratorResult.of(builder // codecHashCode() ... .defineMethod("codecHashCode", BB_INT, PROT_FINAL) - .intercept(new Implementation.Simple(new CodecHashCode(methods))) - // ... codecEquals() ... - .defineMethod("codecEquals", BB_BOOLEAN, PROT_FINAL).withParameter(BB_DATAOBJECT) - .intercept(codecEquals(methods)) - // ... and codecFillToString() ... - .defineMethod("codecFillToString", BB_HELPER, PROT_FINAL).withParameter(BB_HELPER) - .intercept(codecFillToString(methods)) + .intercept(codecHashCode(bindingInterface)) + // ... equals(Object) ... + .defineMethod("codecEquals", BB_BOOLEAN, PROT_FINAL).withParameter(BB_OBJECT) + .intercept(codecEquals(bindingInterface)) + // ... toString() ... + .defineMethod("toString", BB_STRING, PUB_FINAL) + .intercept(toString(bindingInterface)) // ... and build it .make()); } - @Override - public Class customizeLoading(final @NonNull Supplier> loader) { - final CodecDataObjectGenerator prev = CodecDataObjectBridge.setup(this); - try { - final Class result = loader.get(); - - /* - * This a bit of magic to support NodeContextSupplier constants. These constants need to be resolved while - * we have the information needed to find them -- that information is being held in this instance and we - * leak it to a thread-local variable held by CodecDataObjectBridge. - * - * By default the JVM will defer class initialization to first use, which unfortunately is too late for - * us, and hence we need to force class to initialize. - */ - try { - Class.forName(result.getName(), true, result.getClassLoader()); - } catch (ClassNotFoundException e) { - throw new LinkageError("Failed to find newly-defined " + result, e); - } - - return result; - } finally { - CodecDataObjectBridge.tearDown(prev); - } - } - - @NonNull NodeContextSupplier resolve(final @NonNull String methodName) { - final Optional> found = properties.entrySet().stream() - .filter(entry -> methodName.equals(entry.getKey().getName())).findAny(); - verify(found.isPresent(), "Failed to find property for %s in %s", methodName, this); - return verifyNotNull(found.get().getValue()); - } + abstract Builder generateGetters(Builder builder); - @NonNull IdentifiableItemCodec resolveKey(final @NonNull String methodName) { - return verifyNotNull(verifyNotNull(keyMethod, "No key method attached for %s in %s", methodName, this) - .getValue()); + private static Implementation codecHashCode(final Class bindingInterface) { + return new Implementation.Simple( + // return Foo.bindingHashCode(this); + loadThis(), + invokeMethod(bindingInterface, BindingMapping.BINDING_HASHCODE_NAME, bindingInterface), + MethodReturn.INTEGER); } - private static Implementation codecEquals(final ImmutableMap properties) { - // Label for 'return false;' - final Label falseLabel = new Label(); - // Condition for 'if (!...)' - final StackManipulation ifFalse = ByteBuddyUtils.ifEq(falseLabel); - - final List manipulations = new ArrayList<>(properties.size() * 6 + 5); - for (Entry entry : properties.entrySet()) { - // if (!java.util.(Objects|Arrays).equals(getFoo(), other.getFoo())) { - // return false; - // } - manipulations.add(THIS); - manipulations.add(entry.getKey()); - manipulations.add(FIRST_ARG_REF); - manipulations.add(entry.getKey()); - manipulations.add(entry.getValue().getReturnType().isArray() ? ARRAYS_EQUALS : OBJECTS_EQUALS); - manipulations.add(ifFalse); - } - - // return true; - manipulations.add(IntegerConstant.ONE); - manipulations.add(MethodReturn.INTEGER); - // L0: return false; - manipulations.add(ByteBuddyUtils.markLabel(falseLabel)); - manipulations.add(IntegerConstant.ZERO); - manipulations.add(MethodReturn.INTEGER); - - return new Implementation.Simple(manipulations.toArray(new StackManipulation[0])); + private static Implementation codecEquals(final Class bindingInterface) { + return new Implementation.Simple( + // return Foo.bindingEquals(this, obj); + loadThis(), + FIRST_ARG_REF, + invokeMethod(bindingInterface, BindingMapping.BINDING_EQUALS_NAME, bindingInterface, Object.class), + MethodReturn.INTEGER); } - private static Implementation codecFillToString(final ImmutableMap properties) { - final List manipulations = new ArrayList<>(properties.size() * 4 + 2); - // push 'return helper' to stack... - manipulations.add(FIRST_ARG_REF); - for (Entry entry : properties.entrySet()) { - // .add("getFoo", getFoo()) - manipulations.add(new TextConstant(entry.getValue().getName())); - manipulations.add(THIS); - manipulations.add(entry.getKey()); - manipulations.add(HELPER_ADD); - } - // ... execute 'return helper' - manipulations.add(MethodReturn.REFERENCE); - - return new Implementation.Simple(manipulations.toArray(new StackManipulation[0])); + private static Implementation toString(final Class bindingInterface) { + return new Implementation.Simple( + // return Foo.bindingToString(this); + loadThis(), + invokeMethod(bindingInterface, BindingMapping.BINDING_TO_STRING_NAME, bindingInterface), + MethodReturn.REFERENCE); } - private static final class MethodImplementation implements Implementation { - private static final Generic BB_ARFU = TypeDefinition.Sort.describe(AtomicReferenceFieldUpdater.class); + private abstract static class AbstractMethodImplementation implements Implementation { + private static final Generic BB_HANDLE = TypeDefinition.Sort.describe(VarHandle.class); private static final Generic BB_OBJECT = TypeDefinition.Sort.describe(Object.class); private static final StackManipulation OBJECT_CLASS = ClassConstant.of(TypeDescription.OBJECT); - private static final StackManipulation ARFU_NEWUPDATER = invokeMethod(AtomicReferenceFieldUpdater.class, - "newUpdater", Class.class, Class.class, String.class); + private static final StackManipulation LOOKUP = invokeMethod(MethodHandles.class, "lookup"); + private static final StackManipulation FIND_VAR_HANDLE = invokeMethod(Lookup.class, + "findVarHandle", Class.class, String.class, Class.class); - private static final int PRIV_CONST = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL + static final int PRIV_CONST = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC; private static final int PRIV_VOLATILE = Opcodes.ACC_PRIVATE | Opcodes.ACC_VOLATILE | Opcodes.ACC_SYNTHETIC; - private final Generic contextType; - private final StackManipulation resolveMethod; - private final StackManipulation codecMember; - private final TypeDescription retType; - + final TypeDescription retType; // getFoo - private final String methodName; - // getFoo$$$A - private final String arfuName; - // getFoo$$$C - private final String contextName; + final String methodName; + // getFoo$$$V + final String handleName; - MethodImplementation(final Generic contextType, final StackManipulation resolveMethod, - final StackManipulation codecMember, final String methodName, final TypeDescription retType) { - this.contextType = requireNonNull(contextType); - this.resolveMethod = requireNonNull(resolveMethod); - this.codecMember = requireNonNull(codecMember); + AbstractMethodImplementation(final String methodName, final TypeDescription retType) { this.methodName = requireNonNull(methodName); this.retType = requireNonNull(retType); - this.arfuName = methodName + "$$$A"; - this.contextName = methodName + "$$$C"; + this.handleName = methodName + "$$$V"; } @Override public InstrumentedType prepare(final InstrumentedType instrumentedType) { final InstrumentedType tmp = instrumentedType - // private static final AtomicReferenceFieldUpdater getFoo$$$A; - .withField(new FieldDescription.Token(arfuName, PRIV_CONST, BB_ARFU)) - // private static final getFoo$$$C; - .withField(new FieldDescription.Token(contextName, PRIV_CONST, contextType)) + // private static final VarHandle getFoo$$$V; + .withField(new FieldDescription.Token(handleName, PRIV_CONST, BB_HANDLE)) // private volatile Object getFoo; .withField(new FieldDescription.Token(methodName, PRIV_VOLATILE, BB_OBJECT)); - // "getFoo" - final TextConstant methodNameText = new TextConstant(methodName); - - return tmp - .withInitializer(new ByteCodeAppender.Simple( - // getFoo$$$A = AtomicReferenceFieldUpdater.newUpdater(This.class, Object.class, "getFoo"); - ClassConstant.of(tmp), - OBJECT_CLASS, - methodNameText, - ARFU_NEWUPDATER, - putField(tmp, arfuName), - // getFoo$$$C = CodecDataObjectBridge.("getFoo"); - methodNameText, - resolveMethod, - putField(tmp, contextName))); + return tmp.withInitializer(new ByteCodeAppender.Simple( + // TODO: acquiring lookup is expensive, we should share it across all initialization + // getFoo$$$V = MethodHandles.lookup().findVarHandle(This.class, "getFoo", Object.class); + LOOKUP, + ClassConstant.of(tmp), + new TextConstant(methodName), + OBJECT_CLASS, + FIND_VAR_HANDLE, + putField(tmp, handleName))); + } + } + + private static final class KeyMethodImplementation extends AbstractMethodImplementation { + private static final StackManipulation CODEC_KEY = invokeMethod(CodecDataObject.class, + "codecKey", VarHandle.class); + + KeyMethodImplementation(final String methodName, final TypeDescription retType) { + super(methodName, retType); + } + + @Override + public ByteCodeAppender appender(final Target implementationTarget) { + return new ByteCodeAppender.Simple( + // return (FooType) codecKey(getFoo$$$V); + loadThis(), + getField(implementationTarget.getInstrumentedType(), handleName), + CODEC_KEY, + TypeCasting.to(retType), + MethodReturn.REFERENCE); + } + } + + /* + * A simple leaf method, which looks up child by a String constant. This is slightly more complicated because we + * want to make sure we are using the same String instance as the one stored in associated DataObjectCodecContext, + * so that during lookup we perform an identity check instead of comparing content -- speeding things up as well + * as minimizing footprint. Since that string is not guaranteed to be interned in the String Pool, we cannot rely + * on the constant pool entry to resolve to the same object. + */ + private static final class SimpleGetterMethodImplementation extends AbstractMethodImplementation { + private static final StackManipulation CODEC_MEMBER = invokeMethod(CodecDataObject.class, + "codecMember", VarHandle.class, String.class); + private static final StackManipulation BRIDGE_RESOLVE = invokeMethod(ClassGeneratorBridge.class, + "resolveLocalName", String.class); + private static final Generic BB_STRING = TypeDefinition.Sort.describe(String.class); + + // getFoo$$$S + private final String stringName; + + SimpleGetterMethodImplementation(final String methodName, final TypeDescription retType) { + super(methodName, retType); + this.stringName = methodName + "$$$S"; + } + + @Override + public InstrumentedType prepare(final InstrumentedType instrumentedType) { + final InstrumentedType tmp = super.prepare(instrumentedType) + // private static final String getFoo$$$S; + .withField(new FieldDescription.Token(stringName, PRIV_CONST, BB_STRING)); + + return tmp.withInitializer(new ByteCodeAppender.Simple( + // getFoo$$$S = CodecDataObjectBridge.resolveString("getFoo"); + new TextConstant(methodName), + BRIDGE_RESOLVE, + putField(tmp, stringName))); } @Override public ByteCodeAppender appender(final Target implementationTarget) { final TypeDescription instrumentedType = implementationTarget.getInstrumentedType(); return new ByteCodeAppender.Simple( - // return (FooType) codecMember(getFoo$$$A, getFoo$$$C); - THIS, - getField(instrumentedType, arfuName), - getField(instrumentedType, contextName), - codecMember, + // return (FooType) codecMember(getFoo$$$V, getFoo$$$S); + loadThis(), + getField(instrumentedType, handleName), + getField(instrumentedType, stringName), + CODEC_MEMBER, TypeCasting.to(retType), MethodReturn.REFERENCE); } } - private static final class CodecHashCode implements ByteCodeAppender { - private static final StackManipulation THIRTY_ONE = IntegerConstant.forValue(31); - private static final StackManipulation LOAD_RESULT = MethodVariableAccess.INTEGER.loadFrom(1); - private static final StackManipulation STORE_RESULT = MethodVariableAccess.INTEGER.storeAt(1); - private static final StackManipulation ARRAYS_HASHCODE = invokeMethod(Arrays.class, "hashCode", byte[].class); - private static final StackManipulation OBJECTS_HASHCODE = invokeMethod(Objects.class, "hashCode", Object.class); + private static final class StructuredGetterMethodImplementation extends AbstractMethodImplementation { + private static final StackManipulation CODEC_MEMBER = invokeMethod(CodecDataObject.class, + "codecMember", VarHandle.class, Class.class); - private final ImmutableMap properties; + private final Class bindingClass; - CodecHashCode(final ImmutableMap properties) { - this.properties = requireNonNull(properties); + StructuredGetterMethodImplementation(final String methodName, final TypeDescription retType, + final Class bindingClass) { + super(methodName, retType); + this.bindingClass = requireNonNull(bindingClass); } @Override - public Size apply(final MethodVisitor methodVisitor, final Context implementationContext, - final MethodDescription instrumentedMethod) { - final List manipulations = new ArrayList<>(properties.size() * 8 + 4); - // int result = 1; - manipulations.add(IntegerConstant.ONE); - manipulations.add(STORE_RESULT); - - for (Entry entry : properties.entrySet()) { - // result = 31 * result + java.util.(Objects,Arrays).hashCode(getFoo()); - manipulations.add(THIRTY_ONE); - manipulations.add(LOAD_RESULT); - manipulations.add(Multiplication.INTEGER); - manipulations.add(THIS); - manipulations.add(entry.getKey()); - manipulations.add(entry.getValue().getReturnType().isArray() ? ARRAYS_HASHCODE : OBJECTS_HASHCODE); - manipulations.add(Addition.INTEGER); - manipulations.add(STORE_RESULT); - } - // return result; - manipulations.add(LOAD_RESULT); - manipulations.add(MethodReturn.INTEGER); + public ByteCodeAppender appender(final Target implementationTarget) { + return new ByteCodeAppender.Simple( + // return (FooType) codecMember(getFoo$$$V, FooType.class); + loadThis(), + getField(implementationTarget.getInstrumentedType(), handleName), + ClassConstant.of(TypeDefinition.Sort.describe(bindingClass).asErasure()), + CODEC_MEMBER, + TypeCasting.to(retType), + MethodReturn.REFERENCE); + } + } + + private static final class SupplierGetterMethodImplementation extends AbstractMethodImplementation { + private static final StackManipulation CODEC_MEMBER = invokeMethod(CodecDataObject.class, + "codecMember", VarHandle.class, NodeContextSupplier.class); + private static final StackManipulation BRIDGE_RESOLVE = invokeMethod(ClassGeneratorBridge.class, + "resolveNodeContextSupplier", String.class); + private static final Generic BB_NCS = TypeDefinition.Sort.describe(NodeContextSupplier.class); + + // getFoo$$$C + private final String contextName; + + SupplierGetterMethodImplementation(final String methodName, final TypeDescription retType) { + super(methodName, retType); + contextName = methodName + "$$$C"; + } - StackManipulation.Size operandStackSize = new StackManipulation.Compound(manipulations) - .apply(methodVisitor, implementationContext); - return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize() + 1); + @Override + public InstrumentedType prepare(final InstrumentedType instrumentedType) { + final InstrumentedType tmp = super.prepare(instrumentedType) + // private static final NodeContextSupplier getFoo$$$C; + .withField(new FieldDescription.Token(contextName, PRIV_CONST, BB_NCS)); + + return tmp.withInitializer(new ByteCodeAppender.Simple( + // getFoo$$$C = CodecDataObjectBridge.resolve("getFoo"); + new TextConstant(methodName), + BRIDGE_RESOLVE, + putField(tmp, contextName))); + } + + @Override + public ByteCodeAppender appender(final Target implementationTarget) { + final TypeDescription instrumentedType = implementationTarget.getInstrumentedType(); + return new ByteCodeAppender.Simple( + // return (FooType) codecMember(getFoo$$$V, getFoo$$$C); + loadThis(), + getField(instrumentedType, handleName), + getField(instrumentedType, contextName), + CODEC_MEMBER, + TypeCasting.to(retType), + MethodReturn.REFERENCE); } } }