2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
10 import static com.google.common.base.Verify.verify;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13 import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.THIS;
14 import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.getField;
15 import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.invokeMethod;
16 import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.putField;
18 import com.google.common.base.MoreObjects.ToStringHelper;
19 import com.google.common.base.Supplier;
20 import com.google.common.collect.ImmutableMap;
21 import com.google.common.collect.Maps;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.Map.Entry;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
30 import net.bytebuddy.ByteBuddy;
31 import net.bytebuddy.description.field.FieldDescription;
32 import net.bytebuddy.description.method.MethodDescription;
33 import net.bytebuddy.description.type.TypeDefinition;
34 import net.bytebuddy.description.type.TypeDescription;
35 import net.bytebuddy.description.type.TypeDescription.Generic;
36 import net.bytebuddy.dynamic.DynamicType.Builder;
37 import net.bytebuddy.dynamic.scaffold.InstrumentedType;
38 import net.bytebuddy.implementation.Implementation;
39 import net.bytebuddy.implementation.Implementation.Context;
40 import net.bytebuddy.implementation.bytecode.Addition;
41 import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
42 import net.bytebuddy.implementation.bytecode.Multiplication;
43 import net.bytebuddy.implementation.bytecode.StackManipulation;
44 import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
45 import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
46 import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
47 import net.bytebuddy.implementation.bytecode.constant.TextConstant;
48 import net.bytebuddy.implementation.bytecode.member.MethodReturn;
49 import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
50 import net.bytebuddy.jar.asm.Label;
51 import net.bytebuddy.jar.asm.MethodVisitor;
52 import net.bytebuddy.jar.asm.Opcodes;
53 import org.eclipse.jdt.annotation.NonNull;
54 import org.eclipse.jdt.annotation.Nullable;
55 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
56 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.ClassGenerator;
57 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult;
58 import org.opendaylight.yangtools.yang.binding.DataObject;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
63 * Private support for generating {@link CodecDataObject} and {@link AugmentableCodecDataObject} specializations.
66 * Code generation here is probably more involved than usual mainly due to the fact we *really* want to express the
67 * strong connection between a generated class and BindingCodecContext in terms of a true constant, which boils down to
68 * {@code private static final NodeContextSupplier NCS}. Having such constants provides significant boost to JITs
69 * ability to optimize code -- especially with inlining and constant propagation.
72 * The accessor mapping performance is critical due to users typically not taking care of storing the results acquired
73 * by an invocation, assuming the accessors are backed by a normal field -- which of course is not true, as the results
74 * are lazily computed.
77 * The design is such that for a particular structure like:
85 * we end up generating a class with the following layout:
87 * public final class Foo$$$codecImpl extends CodecDataObject implements Foo {
88 * private static final AtomicRefereceFieldUpdater<Foo$$$codecImpl, Object> getBar$$$A;
89 * private static final NodeContextSupplier getBar$$$C;
90 * private volatile Object getBar;
92 * public Foo$$$codecImpl(NormalizedNodeContainer data) {
96 * public Bar getBar() {
97 * return (Bar) codecMember(getBar$$$A, getBar$$$C);
103 * This strategy minimizes the bytecode footprint and follows the generally good idea of keeping common logic in a
104 * single place in a maintainable form. The glue code is extremely light (~6 instructions), which is beneficial on both
105 * sides of invocation:
106 * - generated method can readily be inlined into the caller
107 * - it forms a call site into which codeMember() can be inlined with both AtomicReferenceFieldUpdater and
108 * NodeContextSupplier being constant
111 * The second point is important here, as it allows the invocation logic around AtomicRefereceFieldUpdater to completely
112 * disappear, becoming synonymous with operations of a volatile field. NodeContextSupplier being constant also means
113 * it will resolve to one of its two implementations, allowing NodeContextSupplier.get() to be resolved to a constant
114 * (pointing to the supplier itself) or to a simple volatile read (which will be non-null after first access).
117 * The sticky point here is the NodeContextSupplier, as it is a heap object which cannot normally be looked up from the
118 * static context in which the static class initializer operates -- so we need perform some sort of a trick here.
121 * Eventhough ByteBuddy provides facilities for bridging references to type fields, those facilities operate on volatile
122 * fields -- hence they do not quite work for us.
125 * Another alternative, which we used in Javassist-generated DataObjectSerializers, is to muck with the static field
126 * using reflection -- which works, but requires redefinition of Field.modifiers, which is something Java 9 complains
127 * about quite noisily.
130 * We take a different approach here, which takes advantage of the fact we are in control of both code generation (here)
131 * and class loading (in {@link CodecClassLoader}). The process is performed in four steps:
133 * <li>During code generation, the context fields are pointed towards {@link CodecDataObjectBridge#resolve(String)} and
134 * {@link CodecDataObjectBridge#resolveKey(String)} methods, which are public and static, hence perfectly usable
135 * in the context of a class initializer.</li>
136 * <li>During class loading of generated byte code, the original instance of the generator is called to wrap the actual
137 * class loading operation. At this point the generator installs itself as the current generator for this thread via
138 * {@link CodecDataObjectBridge#setup(CodecDataObjectGenerator)} and allows the class to be loaded.
139 * <li>After the class has been loaded, but before the call returns, we will force the class to initialize, at which
140 * point the static invocations will be redirect to {@link #resolve(String)} and {@link #resolveKey(String)}
141 * methods, thus initializing the fields to the intended constants.</li>
142 * <li>Before returning from the class loading call, the generator will detach itself via
143 * {@link CodecDataObjectBridge#tearDown(CodecDataObjectGenerator)}.</li>
147 * This strategy works due to close cooperation with the target ClassLoader, as the entire code generation and loading
148 * block runs with the class loading lock for this FQCN and the reference is not leaked until the process completes.
150 final class CodecDataObjectGenerator<T extends CodecDataObject<?>> implements ClassGenerator<T> {
151 private static final Logger LOG = LoggerFactory.getLogger(CodecDataObjectGenerator.class);
152 private static final Generic BB_BOOLEAN = TypeDefinition.Sort.describe(boolean.class);
153 private static final Generic BB_DATAOBJECT = TypeDefinition.Sort.describe(DataObject.class);
154 private static final Generic BB_HELPER = TypeDefinition.Sort.describe(ToStringHelper.class);
155 private static final Generic BB_INT = TypeDefinition.Sort.describe(int.class);
156 private static final Generic BB_IIC = TypeDefinition.Sort.describe(IdentifiableItemCodec.class);
157 private static final Generic BB_NCS = TypeDefinition.Sort.describe(NodeContextSupplier.class);
159 private static final StackManipulation BRIDGE_RESOLVE = invokeMethod(CodecDataObjectBridge.class,
160 "resolve", String.class);
161 private static final StackManipulation BRIDGE_RESOLVE_KEY = invokeMethod(CodecDataObjectBridge.class,
162 "resolveKey", String.class);
163 private static final StackManipulation CODEC_MEMBER = invokeMethod(CodecDataObject.class,
164 "codecMember", AtomicReferenceFieldUpdater.class, NodeContextSupplier.class);
165 private static final StackManipulation CODEC_MEMBER_KEY = invokeMethod(CodecDataObject.class,
166 "codecMember", AtomicReferenceFieldUpdater.class, IdentifiableItemCodec.class);
168 private static final StackManipulation ARRAYS_EQUALS = invokeMethod(Arrays.class, "equals",
169 byte[].class, byte[].class);
170 private static final StackManipulation OBJECTS_EQUALS = invokeMethod(Objects.class, "equals",
171 Object.class, Object.class);
172 private static final StackManipulation HELPER_ADD = invokeMethod(ToStringHelper.class, "add",
173 String.class, Object.class);
175 private static final StackManipulation FIRST_ARG_REF = MethodVariableAccess.REFERENCE.loadFrom(1);
177 private static final int PROT_FINAL = Opcodes.ACC_PROTECTED | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
178 private static final int PUB_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
180 private static final Builder<?> CDO;
181 private static final Builder<?> ACDO;
184 final ByteBuddy bb = new ByteBuddy();
185 CDO = bb.subclass(CodecDataObject.class).visit(ByteBuddyUtils.computeFrames()).modifiers(PUB_FINAL);
186 ACDO = bb.subclass(AugmentableCodecDataObject.class).visit(ByteBuddyUtils.computeFrames()).modifiers(PUB_FINAL);
189 private final ImmutableMap<Method, NodeContextSupplier> properties;
190 private final Entry<Method, IdentifiableItemCodec> keyMethod;
191 private final Builder<?> template;
193 private CodecDataObjectGenerator(final Builder<?> template,
194 final ImmutableMap<Method, NodeContextSupplier> properties,
195 final @Nullable Entry<Method, IdentifiableItemCodec> keyMethod) {
196 this.template = requireNonNull(template);
197 this.properties = requireNonNull(properties);
198 this.keyMethod = keyMethod;
201 static <D extends DataObject, T extends CodecDataObject<T>> Class<T> generate(final CodecClassLoader loader,
202 final Class<D> bindingInterface, final ImmutableMap<Method, NodeContextSupplier> properties,
203 final Entry<Method, IdentifiableItemCodec> keyMethod) {
204 return loader.generateClass(bindingInterface, "codecImpl",
205 new CodecDataObjectGenerator<>(CDO, properties, keyMethod));
208 static <D extends DataObject, T extends CodecDataObject<T>> Class<T> generateAugmentable(
209 final CodecClassLoader loader, final Class<D> bindingInterface,
210 final ImmutableMap<Method, NodeContextSupplier> properties,
211 final Entry<Method, IdentifiableItemCodec> keyMethod) {
212 return loader.generateClass(bindingInterface, "codecImpl",
213 new CodecDataObjectGenerator<>(ACDO, properties, keyMethod));
217 public GeneratorResult<T> generateClass(final CodecClassLoader loeader, final String fqcn,
218 final Class<?> bindingInterface) {
219 LOG.trace("Generating class {}", fqcn);
221 @SuppressWarnings("unchecked")
222 Builder<T> builder = (Builder<T>) template.name(fqcn).implement(bindingInterface);
224 for (Method method : properties.keySet()) {
225 LOG.trace("Generating for method {}", method);
226 final String methodName = method.getName();
227 final TypeDescription retType = TypeDescription.ForLoadedType.of(method.getReturnType());
228 builder = builder.defineMethod(methodName, retType, PUB_FINAL)
229 .intercept(new MethodImplementation(BB_NCS, BRIDGE_RESOLVE, CODEC_MEMBER, methodName, retType));
232 if (keyMethod != null) {
233 LOG.trace("Generating for key {}", keyMethod);
234 final Method method = keyMethod.getKey();
235 final String methodName = method.getName();
236 final TypeDescription retType = TypeDescription.ForLoadedType.of(method.getReturnType());
237 builder = builder.defineMethod(methodName, retType, PUB_FINAL)
238 .intercept(new MethodImplementation(BB_IIC, BRIDGE_RESOLVE_KEY, CODEC_MEMBER_KEY, methodName,
242 // Index all property methods, turning them into "getFoo()" invocations, retaining order. We will be using
243 // those invocations in each of the three methods. Note that we do not glue the invocations to 'this', as we
244 // will be invoking them on 'other' in codecEquals()
245 final ImmutableMap<StackManipulation, Method> methods = Maps.uniqueIndex(properties.keySet(),
246 ByteBuddyUtils::invokeMethod);
249 return GeneratorResult.of(builder
250 // codecHashCode() ...
251 .defineMethod("codecHashCode", BB_INT, PROT_FINAL)
252 .intercept(new Implementation.Simple(new CodecHashCode(methods)))
253 // ... codecEquals() ...
254 .defineMethod("codecEquals", BB_BOOLEAN, PROT_FINAL).withParameter(BB_DATAOBJECT)
255 .intercept(codecEquals(methods))
256 // ... and codecFillToString() ...
257 .defineMethod("codecFillToString", BB_HELPER, PROT_FINAL).withParameter(BB_HELPER)
258 .intercept(codecFillToString(methods))
264 public Class<T> customizeLoading(final @NonNull Supplier<Class<T>> loader) {
265 final CodecDataObjectGenerator<?> prev = CodecDataObjectBridge.setup(this);
267 final Class<T> result = loader.get();
270 * This a bit of magic to support NodeContextSupplier constants. These constants need to be resolved while
271 * we have the information needed to find them -- that information is being held in this instance and we
272 * leak it to a thread-local variable held by CodecDataObjectBridge.
274 * By default the JVM will defer class initialization to first use, which unfortunately is too late for
275 * us, and hence we need to force class to initialize.
278 Class.forName(result.getName(), true, result.getClassLoader());
279 } catch (ClassNotFoundException e) {
280 throw new LinkageError("Failed to find newly-defined " + result, e);
285 CodecDataObjectBridge.tearDown(prev);
289 @NonNull NodeContextSupplier resolve(final @NonNull String methodName) {
290 final Optional<Entry<Method, NodeContextSupplier>> found = properties.entrySet().stream()
291 .filter(entry -> methodName.equals(entry.getKey().getName())).findAny();
292 verify(found.isPresent(), "Failed to find property for %s in %s", methodName, this);
293 return verifyNotNull(found.get().getValue());
296 @NonNull IdentifiableItemCodec resolveKey(final @NonNull String methodName) {
297 return verifyNotNull(verifyNotNull(keyMethod, "No key method attached for %s in %s", methodName, this)
301 private static Implementation codecEquals(final ImmutableMap<StackManipulation, Method> properties) {
302 // Label for 'return false;'
303 final Label falseLabel = new Label();
304 // Condition for 'if (!...)'
305 final StackManipulation ifFalse = ByteBuddyUtils.ifEq(falseLabel);
307 final List<StackManipulation> manipulations = new ArrayList<>(properties.size() * 6 + 5);
308 for (Entry<StackManipulation, Method> entry : properties.entrySet()) {
309 // if (!java.util.(Objects|Arrays).equals(getFoo(), other.getFoo())) {
312 manipulations.add(THIS);
313 manipulations.add(entry.getKey());
314 manipulations.add(FIRST_ARG_REF);
315 manipulations.add(entry.getKey());
316 manipulations.add(entry.getValue().getReturnType().isArray() ? ARRAYS_EQUALS : OBJECTS_EQUALS);
317 manipulations.add(ifFalse);
321 manipulations.add(IntegerConstant.ONE);
322 manipulations.add(MethodReturn.INTEGER);
324 manipulations.add(ByteBuddyUtils.markLabel(falseLabel));
325 manipulations.add(IntegerConstant.ZERO);
326 manipulations.add(MethodReturn.INTEGER);
328 return new Implementation.Simple(manipulations.toArray(new StackManipulation[0]));
331 private static Implementation codecFillToString(final ImmutableMap<StackManipulation, Method> properties) {
332 final List<StackManipulation> manipulations = new ArrayList<>(properties.size() * 4 + 2);
333 // push 'return helper' to stack...
334 manipulations.add(FIRST_ARG_REF);
335 for (Entry<StackManipulation, Method> entry : properties.entrySet()) {
336 // .add("getFoo", getFoo())
337 manipulations.add(new TextConstant(entry.getValue().getName()));
338 manipulations.add(THIS);
339 manipulations.add(entry.getKey());
340 manipulations.add(HELPER_ADD);
342 // ... execute 'return helper'
343 manipulations.add(MethodReturn.REFERENCE);
345 return new Implementation.Simple(manipulations.toArray(new StackManipulation[0]));
348 private static final class MethodImplementation implements Implementation {
349 private static final Generic BB_ARFU = TypeDefinition.Sort.describe(AtomicReferenceFieldUpdater.class);
350 private static final Generic BB_OBJECT = TypeDefinition.Sort.describe(Object.class);
351 private static final StackManipulation OBJECT_CLASS = ClassConstant.of(TypeDescription.OBJECT);
352 private static final StackManipulation ARFU_NEWUPDATER = invokeMethod(AtomicReferenceFieldUpdater.class,
353 "newUpdater", Class.class, Class.class, String.class);
355 private static final int PRIV_CONST = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL
356 | Opcodes.ACC_SYNTHETIC;
357 private static final int PRIV_VOLATILE = Opcodes.ACC_PRIVATE | Opcodes.ACC_VOLATILE | Opcodes.ACC_SYNTHETIC;
359 private final Generic contextType;
360 private final StackManipulation resolveMethod;
361 private final StackManipulation codecMember;
362 private final TypeDescription retType;
365 private final String methodName;
367 private final String arfuName;
369 private final String contextName;
371 MethodImplementation(final Generic contextType, final StackManipulation resolveMethod,
372 final StackManipulation codecMember, final String methodName, final TypeDescription retType) {
373 this.contextType = requireNonNull(contextType);
374 this.resolveMethod = requireNonNull(resolveMethod);
375 this.codecMember = requireNonNull(codecMember);
376 this.methodName = requireNonNull(methodName);
377 this.retType = requireNonNull(retType);
378 this.arfuName = methodName + "$$$A";
379 this.contextName = methodName + "$$$C";
383 public InstrumentedType prepare(final InstrumentedType instrumentedType) {
384 final InstrumentedType tmp = instrumentedType
385 // private static final AtomicReferenceFieldUpdater<This, Object> getFoo$$$A;
386 .withField(new FieldDescription.Token(arfuName, PRIV_CONST, BB_ARFU))
387 // private static final <CONTEXT_TYPE> getFoo$$$C;
388 .withField(new FieldDescription.Token(contextName, PRIV_CONST, contextType))
389 // private volatile Object getFoo;
390 .withField(new FieldDescription.Token(methodName, PRIV_VOLATILE, BB_OBJECT));
393 final TextConstant methodNameText = new TextConstant(methodName);
396 .withInitializer(new ByteCodeAppender.Simple(
397 // getFoo$$$A = AtomicReferenceFieldUpdater.newUpdater(This.class, Object.class, "getFoo");
398 ClassConstant.of(tmp),
402 putField(tmp, arfuName),
403 // getFoo$$$C = CodecDataObjectBridge.<RESOLVE_METHOD>("getFoo");
406 putField(tmp, contextName)));
410 public ByteCodeAppender appender(final Target implementationTarget) {
411 final TypeDescription instrumentedType = implementationTarget.getInstrumentedType();
412 return new ByteCodeAppender.Simple(
413 // return (FooType) codecMember(getFoo$$$A, getFoo$$$C);
415 getField(instrumentedType, arfuName),
416 getField(instrumentedType, contextName),
418 TypeCasting.to(retType),
419 MethodReturn.REFERENCE);
423 private static final class CodecHashCode implements ByteCodeAppender {
424 private static final StackManipulation THIRTY_ONE = IntegerConstant.forValue(31);
425 private static final StackManipulation LOAD_RESULT = MethodVariableAccess.INTEGER.loadFrom(1);
426 private static final StackManipulation STORE_RESULT = MethodVariableAccess.INTEGER.storeAt(1);
427 private static final StackManipulation ARRAYS_HASHCODE = invokeMethod(Arrays.class, "hashCode", byte[].class);
428 private static final StackManipulation OBJECTS_HASHCODE = invokeMethod(Objects.class, "hashCode", Object.class);
430 private final ImmutableMap<StackManipulation, Method> properties;
432 CodecHashCode(final ImmutableMap<StackManipulation, Method> properties) {
433 this.properties = requireNonNull(properties);
437 public Size apply(final MethodVisitor methodVisitor, final Context implementationContext,
438 final MethodDescription instrumentedMethod) {
439 final List<StackManipulation> manipulations = new ArrayList<>(properties.size() * 8 + 4);
441 manipulations.add(IntegerConstant.ONE);
442 manipulations.add(STORE_RESULT);
444 for (Entry<StackManipulation, Method> entry : properties.entrySet()) {
445 // result = 31 * result + java.util.(Objects,Arrays).hashCode(getFoo());
446 manipulations.add(THIRTY_ONE);
447 manipulations.add(LOAD_RESULT);
448 manipulations.add(Multiplication.INTEGER);
449 manipulations.add(THIS);
450 manipulations.add(entry.getKey());
451 manipulations.add(entry.getValue().getReturnType().isArray() ? ARRAYS_HASHCODE : OBJECTS_HASHCODE);
452 manipulations.add(Addition.INTEGER);
453 manipulations.add(STORE_RESULT);
456 manipulations.add(LOAD_RESULT);
457 manipulations.add(MethodReturn.INTEGER);
459 StackManipulation.Size operandStackSize = new StackManipulation.Compound(manipulations)
460 .apply(methodVisitor, implementationContext);
461 return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize() + 1);