Switch NormalizedNode->Binding codegen to ByteBuddy
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / CodecDataObjectGenerator.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
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;
17
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;
61
62 /**
63  * Private support for generating {@link CodecDataObject} and {@link AugmentableCodecDataObject} specializations.
64  *
65  * <p>
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.
70  *
71  * <p>
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.
75  *
76  * <p>
77  * The design is such that for a particular structure like:
78  * <pre>
79  *     container foo {
80  *         leaf bar {
81  *             type string;
82  *         }
83  *     }
84  * </pre>
85  * we end up generating a class with the following layout:
86  * <pre>
87  *     public final class Foo$$$codecImpl extends CodecDataObject implements Foo {
88  *         private static final AtomicRefereceFieldUpdater&lt;Foo$$$codecImpl, Object&gt; getBar$$$A;
89  *         private static final NodeContextSupplier getBar$$$C;
90  *         private volatile Object getBar;
91  *
92  *         public Foo$$$codecImpl(NormalizedNodeContainer data) {
93  *             super(data);
94  *         }
95  *
96  *         public Bar getBar() {
97  *             return (Bar) codecMember(getBar$$$A, getBar$$$C);
98  *         }
99  *     }
100  * </pre>
101  *
102  * <p>
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
109  *
110  * <p>
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).
115  *
116  * <p>
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.
119  *
120  * <p>
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.
123  *
124  * <p>
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.
128  *
129  * <p>
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:
132  * <ul>
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>
144  * </ul>
145  *
146  * <p>
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.
149  */
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);
158
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);
167
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);
174
175     private static final StackManipulation FIRST_ARG_REF = MethodVariableAccess.REFERENCE.loadFrom(1);
176
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;
179
180     private static final Builder<?> CDO;
181     private static final Builder<?> ACDO;
182
183     static {
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);
187     }
188
189     private final ImmutableMap<Method, NodeContextSupplier> properties;
190     private final Entry<Method, IdentifiableItemCodec> keyMethod;
191     private final Builder<?> template;
192
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;
199     }
200
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));
206     }
207
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));
214     }
215
216     @Override
217     public GeneratorResult<T> generateClass(final CodecClassLoader loeader, final String fqcn,
218             final Class<?> bindingInterface) {
219         LOG.trace("Generating class {}", fqcn);
220
221         @SuppressWarnings("unchecked")
222         Builder<T> builder = (Builder<T>) template.name(fqcn).implement(bindingInterface);
223
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));
230         }
231
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,
239                         retType));
240         }
241
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);
247
248         // Final bits:
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))
259                 // ... and build it
260                 .make());
261     }
262
263     @Override
264     public Class<T> customizeLoading(final @NonNull Supplier<Class<T>> loader) {
265         final CodecDataObjectGenerator<?> prev = CodecDataObjectBridge.setup(this);
266         try {
267             final Class<T> result = loader.get();
268
269             /*
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.
273              *
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.
276              */
277             try {
278                 Class.forName(result.getName(), true, result.getClassLoader());
279             } catch (ClassNotFoundException e) {
280                 throw new LinkageError("Failed to find newly-defined " + result, e);
281             }
282
283             return result;
284         } finally {
285             CodecDataObjectBridge.tearDown(prev);
286         }
287     }
288
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());
294     }
295
296     @NonNull IdentifiableItemCodec resolveKey(final @NonNull String methodName) {
297         return verifyNotNull(verifyNotNull(keyMethod, "No key method attached for %s in %s", methodName, this)
298             .getValue());
299     }
300
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);
306
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())) {
310             //     return false;
311             // }
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);
318         }
319
320         // return true;
321         manipulations.add(IntegerConstant.ONE);
322         manipulations.add(MethodReturn.INTEGER);
323         // L0: return false;
324         manipulations.add(ByteBuddyUtils.markLabel(falseLabel));
325         manipulations.add(IntegerConstant.ZERO);
326         manipulations.add(MethodReturn.INTEGER);
327
328         return new Implementation.Simple(manipulations.toArray(new StackManipulation[0]));
329     }
330
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);
341         }
342         // ... execute 'return helper'
343         manipulations.add(MethodReturn.REFERENCE);
344
345         return new Implementation.Simple(manipulations.toArray(new StackManipulation[0]));
346     }
347
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);
354
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;
358
359         private final Generic contextType;
360         private final StackManipulation resolveMethod;
361         private final StackManipulation codecMember;
362         private final TypeDescription retType;
363
364         // getFoo
365         private final String methodName;
366         // getFoo$$$A
367         private final String arfuName;
368         // getFoo$$$C
369         private final String contextName;
370
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";
380         }
381
382         @Override
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));
391
392             // "getFoo"
393             final TextConstant methodNameText = new TextConstant(methodName);
394
395             return tmp
396                 .withInitializer(new ByteCodeAppender.Simple(
397                     // getFoo$$$A = AtomicReferenceFieldUpdater.newUpdater(This.class, Object.class, "getFoo");
398                     ClassConstant.of(tmp),
399                     OBJECT_CLASS,
400                     methodNameText,
401                     ARFU_NEWUPDATER,
402                     putField(tmp, arfuName),
403                     // getFoo$$$C = CodecDataObjectBridge.<RESOLVE_METHOD>("getFoo");
404                     methodNameText,
405                     resolveMethod,
406                     putField(tmp, contextName)));
407         }
408
409         @Override
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);
414                 THIS,
415                 getField(instrumentedType, arfuName),
416                 getField(instrumentedType, contextName),
417                 codecMember,
418                 TypeCasting.to(retType),
419                 MethodReturn.REFERENCE);
420         }
421     }
422
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);
429
430         private final ImmutableMap<StackManipulation, Method> properties;
431
432         CodecHashCode(final ImmutableMap<StackManipulation, Method> properties) {
433             this.properties = requireNonNull(properties);
434         }
435
436         @Override
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);
440             // int result = 1;
441             manipulations.add(IntegerConstant.ONE);
442             manipulations.add(STORE_RESULT);
443
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);
454             }
455             // return result;
456             manipulations.add(LOAD_RESULT);
457             manipulations.add(MethodReturn.INTEGER);
458
459             StackManipulation.Size operandStackSize = new StackManipulation.Compound(manipulations)
460                     .apply(methodVisitor, implementationContext);
461             return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize() + 1);
462         }
463     }
464 }