1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package hat.ifacemapper;
 27 
 28 import hat.ifacemapper.accessor.AccessorInfo;
 29 import hat.ifacemapper.accessor.ArrayInfo;
 30 import hat.ifacemapper.accessor.ScalarInfo;
 31 import jdk.internal.ValueBased;
 32 
 33 
 34 import java.lang.classfile.Annotation;
 35 import java.lang.classfile.ClassBuilder;
 36 import java.lang.classfile.CodeBuilder;
 37 import java.lang.classfile.Label;
 38 import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
 39 import java.lang.constant.ClassDesc;
 40 import java.lang.constant.DirectMethodHandleDesc;
 41 import java.lang.constant.DynamicCallSiteDesc;
 42 import java.lang.constant.DynamicConstantDesc;
 43 import java.lang.constant.MethodTypeDesc;
 44 import java.lang.foreign.GroupLayout;
 45 import java.lang.foreign.MemorySegment;
 46 import java.lang.foreign.ValueLayout;
 47 import java.lang.invoke.MethodHandle;
 48 import java.lang.invoke.StringConcatFactory;
 49 import java.util.ArrayList;
 50 import java.util.Collections;
 51 import java.util.List;
 52 import java.util.Objects;
 53 import java.util.stream.Collectors;
 54 
 55 import static hat.ifacemapper.MapperUtil.SECRET_BOUND_SCHEMA_METHOD_NAME;
 56 import static hat.ifacemapper.MapperUtil.SECRET_LAYOUT_METHOD_NAME;
 57 import static hat.ifacemapper.MapperUtil.SECRET_OFFSET_METHOD_NAME;
 58 import static hat.ifacemapper.MapperUtil.SECRET_SEGMENT_METHOD_NAME;
 59 import static hat.ifacemapper.MapperUtil.desc;
 60 import static java.lang.classfile.ClassFile.ACC_FINAL;
 61 import static java.lang.classfile.ClassFile.ACC_PRIVATE;
 62 import static java.lang.classfile.ClassFile.ACC_PUBLIC;
 63 import static java.lang.classfile.ClassFile.ACC_SUPER;
 64 import static java.lang.constant.ConstantDescs.BSM_CLASS_DATA_AT;
 65 import static java.lang.constant.ConstantDescs.CD_CallSite;
 66 import static java.lang.constant.ConstantDescs.CD_MethodHandle;
 67 import static java.lang.constant.ConstantDescs.CD_Object;
 68 import static java.lang.constant.ConstantDescs.CD_String;
 69 import static java.lang.constant.ConstantDescs.CD_boolean;
 70 import static java.lang.constant.ConstantDescs.CD_int;
 71 import static java.lang.constant.ConstantDescs.CD_long;
 72 import static java.lang.constant.ConstantDescs.CD_void;
 73 import static java.lang.constant.ConstantDescs.INIT_NAME;
 74 import static java.lang.constant.ConstantDescs.MTD_void;
 75 import static java.lang.constant.ConstantDescs.ofCallsiteBootstrap;
 76 
 77 @ValueBased
 78 final class ByteCodeGenerator {
 79 
 80     static final String SEGMENT_FIELD_NAME = "segment";
 81     static final String LAYOUT_FIELD_NAME = "layout";
 82     static final String OFFSET_FIELD_NAME = "offset";
 83     static final String BOUND_SCHEMA_FIELD_NAME = "boundSchema";
 84 
 85     private static final ClassDesc VALUE_LAYOUTS_CLASS_DESC = desc(ValueLayout.class);
 86     private static final ClassDesc MEMORY_SEGMENT_CLASS_DESC = desc(MemorySegment.class);
 87     private static final ClassDesc LAYOUT_CLASS_DESC = desc(GroupLayout.class);
 88     private static final ClassDesc BOUND_SCHEMA_CLASS_DESC = desc(BoundSchema.class);
 89 
 90 
 91     private final Class<?> type;
 92     private final ClassBuilder cb;
 93     private final ClassDesc classDesc;
 94     private final ClassDesc interfaceClassDesc;
 95 
 96     private ByteCodeGenerator(Class<?> type, ClassDesc classDesc, ClassBuilder cb) {
 97         this.type = type;
 98         this.cb = cb;
 99         this.classDesc = classDesc;
100         this.interfaceClassDesc = desc(type);
101     }
102 
103     void classDefinition() {
104         // @ValueBased
105         Annotation valueBased = Annotation.of(desc(ValueBased.class));
106         cb.with(RuntimeVisibleAnnotationsAttribute.of(valueBased));
107         // public final
108         cb.withFlags(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
109         // extends Object
110         cb.withSuperclass(CD_Object);
111         List<ClassDesc> interfaces = new ArrayList<>();
112         // implements "type"
113         interfaces.add(interfaceClassDesc);
114         if (SegmentMapper.Discoverable.class.isAssignableFrom(type)) {
115             // implements SegmentMapper.Discoverable
116             interfaces.add(desc(SegmentMapper.Discoverable.class));
117         }
118         cb.withInterfaceSymbols(interfaces);
119         // private final MemorySegment segment;
120         cb.withField(SEGMENT_FIELD_NAME, MEMORY_SEGMENT_CLASS_DESC, ACC_PRIVATE | ACC_FINAL);
121         // private final GroupLayout layout;
122         cb.withField(LAYOUT_FIELD_NAME, LAYOUT_CLASS_DESC, ACC_PRIVATE | ACC_FINAL);
123         // private final GroupLayout layout;
124         cb.withField(BOUND_SCHEMA_FIELD_NAME, BOUND_SCHEMA_CLASS_DESC, ACC_PRIVATE | ACC_FINAL);
125         // private final long offset;
126         cb.withField(OFFSET_FIELD_NAME, CD_long, ACC_PRIVATE | ACC_FINAL);
127     }
128 
129     void constructor(long layoutByteSize) {
130         final int THIS_VAR_SLOT = 0;
131         final int SEGMENT_VAR_SLOT = 1;
132         final int LAYOUT_VAR_SLOT = 2;
133         final int BOUND_SCHEMA_VAR_SLOT = 3;
134         final int OFFSET_VAR_SLOT = 4;
135         cb.withMethodBody(INIT_NAME, MethodTypeDesc.of(CD_void, MEMORY_SEGMENT_CLASS_DESC, LAYOUT_CLASS_DESC, BOUND_SCHEMA_CLASS_DESC, CD_long), ACC_PUBLIC,
136                 cob -> cob
137                         .aload(THIS_VAR_SLOT)
138                         .invokespecial(CD_Object, INIT_NAME, MTD_void, false) // Call Object's constructor
139 
140                         .aload(THIS_VAR_SLOT)
141                         .aload(SEGMENT_VAR_SLOT)
142                         .checkcast(MEMORY_SEGMENT_CLASS_DESC)
143                         .putfield(classDesc, SEGMENT_FIELD_NAME, MEMORY_SEGMENT_CLASS_DESC) // this.segment = segment
144 
145                         .aload(THIS_VAR_SLOT)
146                         .aload(LAYOUT_VAR_SLOT)
147                         .checkcast(LAYOUT_CLASS_DESC)
148                         .putfield(classDesc, LAYOUT_FIELD_NAME, LAYOUT_CLASS_DESC) // this.layout = layout
149 
150                         .aload(THIS_VAR_SLOT)
151                         .aload(BOUND_SCHEMA_VAR_SLOT)
152                         .checkcast(BOUND_SCHEMA_CLASS_DESC)
153                         .putfield(classDesc, BOUND_SCHEMA_FIELD_NAME, BOUND_SCHEMA_CLASS_DESC) // this.boundSchema = boundSchema
154 
155                         .aload(THIS_VAR_SLOT)
156                         .lload(OFFSET_VAR_SLOT) // offset
157                         .ldc(layoutByteSize)    // size
158                         .aload(SEGMENT_VAR_SLOT) // segment
159                         .invokeinterface(desc(MemorySegment.class), "byteSize", MethodTypeDesc.of(CD_long))
160 
161                         .invokestatic(desc(Objects.class), "checkFromIndexSize", MethodTypeDesc.of(CD_long, CD_long, CD_long, CD_long))
162 
163                         .putfield(classDesc, OFFSET_FIELD_NAME, CD_long) // this.offset = offset
164                         .return_()
165         );
166 
167     }
168 
169     void obscuredSegment() {
170         cb.withMethodBody(SECRET_SEGMENT_METHOD_NAME, MethodTypeDesc.of(MEMORY_SEGMENT_CLASS_DESC), ACC_PUBLIC, cob ->
171                 cob.aload(0)
172                         .getfield(classDesc, SEGMENT_FIELD_NAME, MEMORY_SEGMENT_CLASS_DESC)
173                         .areturn()
174         );
175     }
176 
177     void obscuredLayout() {
178         cb.withMethodBody(SECRET_LAYOUT_METHOD_NAME, MethodTypeDesc.of(LAYOUT_CLASS_DESC), ACC_PUBLIC, cob ->
179                 cob.aload(0)
180                         .getfield(classDesc, LAYOUT_FIELD_NAME, LAYOUT_CLASS_DESC)
181                         .areturn()
182         );
183     }
184 
185 
186     void obscuredBoundSchema() {
187         cb.withMethodBody(SECRET_BOUND_SCHEMA_METHOD_NAME, MethodTypeDesc.of(BOUND_SCHEMA_CLASS_DESC), ACC_PUBLIC, cob ->
188                 cob.aload(0)
189                         .getfield(classDesc, BOUND_SCHEMA_FIELD_NAME, BOUND_SCHEMA_CLASS_DESC)
190                         .areturn()
191         );
192     }
193 
194     void obscuredOffset() {
195         cb.withMethodBody(SECRET_OFFSET_METHOD_NAME, MethodTypeDesc.of(CD_long), ACC_PUBLIC, cob ->
196                 cob.aload(0)
197                         .getfield(classDesc, OFFSET_FIELD_NAME, CD_long)
198                         .lreturn()
199         );
200     }
201 
202     void valueGetter(AccessorInfo info) {
203         String name = info.method().getName();
204         ClassDesc returnDesc = desc(info.type());
205         ScalarInfo scalarInfo = info.layoutInfo().scalarInfo().orElseThrow();
206 
207         var getDesc = MethodTypeDesc.of(returnDesc, desc(scalarInfo.interfaceType()), CD_long);
208         List<ClassDesc> parameterDesc = parameterDesc(info);
209 
210         cb.withMethodBody(name, MethodTypeDesc.of(returnDesc, parameterDesc), ACC_PUBLIC, cob -> {
211                     cob.aload(0)
212                             .getfield(classDesc, SEGMENT_FIELD_NAME, MEMORY_SEGMENT_CLASS_DESC)
213                             .getstatic(VALUE_LAYOUTS_CLASS_DESC, scalarInfo.memberName(), desc(scalarInfo.interfaceType()));
214                     offsetBlock(cob, info, classDesc)
215                             .invokeinterface(MEMORY_SEGMENT_CLASS_DESC, "get", getDesc);
216                     // ireturn(), dreturn() etc.
217                     info.layoutInfo().returnOp().accept(cob);
218                 }
219         );
220     }
221 
222     void valueSetter(AccessorInfo info) {
223         String name = info.method().getName();
224         List<ClassDesc> parameterDesc = parameterDesc(info);
225 
226         ScalarInfo scalarInfo = info.layoutInfo().scalarInfo().orElseThrow();
227 
228         var setDesc = MethodTypeDesc.of(CD_void, desc(scalarInfo.interfaceType()), CD_long, desc(info.type()));
229 
230         cb.withMethodBody(name, MethodTypeDesc.of(CD_void, parameterDesc), ACC_PUBLIC, cob -> {
231                     cob.aload(0)
232                             .getfield(classDesc, SEGMENT_FIELD_NAME, MEMORY_SEGMENT_CLASS_DESC)
233                             .getstatic(VALUE_LAYOUTS_CLASS_DESC, scalarInfo.memberName(), desc(scalarInfo.interfaceType()));
234                     offsetBlock(cob, info, classDesc);
235                     // iload, dload, etc.
236                     info.layoutInfo().paramOp().accept(cob, valueSlotNumber(parameterDesc.size()));
237                     cob.invokeinterface(MEMORY_SEGMENT_CLASS_DESC, "set", setDesc)
238                             .return_();
239                 }
240         );
241     }
242 
243     void invokeVirtualGetter(AccessorInfo info,
244                              int boostrapIndex) {
245 
246         var name = info.method().getName();
247         var returnDesc = desc(info.type());
248 
249         DynamicConstantDesc<MethodHandle> desc = DynamicConstantDesc.of(
250                 BSM_CLASS_DATA_AT,
251                 boostrapIndex
252         );
253 
254         List<ClassDesc> parameterDesc = parameterDesc(info);
255 
256         /*
257         Was
258         public ArgList.Arg args(long var1) {
259             return (ArgList.Arg)((MethodHandle)"_").invokeExact(this.segment, this.offset + 8L + Objects.checkIndex(var1, 4L) * 4L);
260         }
261         Now
262         public ArgList.Arg args(long var1) {
263             MethodHandle var10000 = (MethodHandle)"_";
264             return (ArgList.Arg)this.segment.invokeExact((MethodHandle)"_", this.layout, this.offset + 8L + Objects.checkIndex(var1, 4L) * 4L);
265         }
266 
267          */
268 
269         cb.withMethodBody(name, MethodTypeDesc.of(returnDesc, parameterDesc), ACC_PUBLIC, cob -> {
270                     cob
271                             .ldc(desc)
272                             .checkcast(desc(MethodHandle.class)) // MethodHandle
273                             .aload(0)
274                             .getfield(classDesc, SEGMENT_FIELD_NAME, MEMORY_SEGMENT_CLASS_DESC) // MemorySegment
275                             .aload(0)
276                             .getfield(classDesc, LAYOUT_FIELD_NAME, LAYOUT_CLASS_DESC) // Layout
277                             .aload(0)
278                             .getfield(classDesc, BOUND_SCHEMA_FIELD_NAME, BOUND_SCHEMA_CLASS_DESC); // Layout
279 
280 
281             offsetBlock(cob, info, classDesc)
282                             .invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDesc.of(CD_Object, MEMORY_SEGMENT_CLASS_DESC,
283                                     LAYOUT_CLASS_DESC, BOUND_SCHEMA_CLASS_DESC, CD_long))
284                             .checkcast(returnDesc)
285                             .areturn();
286                 }
287         );
288     }
289 
290     void invokeVirtualSetter(AccessorInfo info,
291                              int boostrapIndex) {
292 
293         var name = info.method().getName();
294         List<ClassDesc> parameterDesc = parameterDesc(info);
295 
296         DynamicConstantDesc<MethodHandle> desc = DynamicConstantDesc.of(
297                 BSM_CLASS_DATA_AT,
298                 boostrapIndex
299         );
300 
301         cb.withMethodBody(name, MethodTypeDesc.of(CD_void, parameterDesc), ACC_PUBLIC, cob -> {
302                     cob.ldc(desc)
303                             .checkcast(desc(MethodHandle.class)) // MethodHandle
304                             .aload(0)
305                             .getfield(classDesc, SEGMENT_FIELD_NAME, MEMORY_SEGMENT_CLASS_DESC); // MemorySegment
306                     offsetBlock(cob, info, classDesc)
307                             .aload(valueSlotNumber(parameterDesc.size())) // Record
308                             .checkcast(desc(Record.class))
309                             .invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDesc.of(CD_void, MEMORY_SEGMENT_CLASS_DESC, CD_long, CD_Object))
310                             .return_();
311                 }
312         );
313     }
314 
315     void hashCode_() {
316         cb.withMethodBody("hashCode", MethodTypeDesc.of(CD_int), ACC_PUBLIC | ACC_FINAL, cob ->
317                 cob.aload(0)
318                         .invokestatic(desc(System.class), "identityHashCode", MethodTypeDesc.of(CD_int, CD_Object))
319                         .ireturn()
320         );
321     }
322 
323     void equals_() {
324         cb.withMethodBody("equals", MethodTypeDesc.of(CD_boolean, CD_Object), ACC_PUBLIC | ACC_FINAL, cob -> {
325                     Label l0 = cob.newLabel();
326                     Label l1 = cob.newLabel();
327                     cob.aload(0)
328                             .aload(1)
329                             .if_acmpne(l0)
330                             .iconst_1()
331                             .goto_(l1)
332                             .labelBinding(l0)
333                             .iconst_0()
334                             .labelBinding(l1)
335                             .ireturn()
336                     ;
337                 }
338         );
339     }
340 
341     void toString_(List<AccessorInfo> getters) {
342 
343         // Foo[g0()=\u0001, g1()=\u0001, ...]
344         var recipe = getters.stream()
345                 .map(m -> m.layoutInfo().arrayInfo()
346                         .map(ai -> String.format("%s()=%s%s", m.method().getName(), m.type().getSimpleName(), ai.dimensions()))
347                         .orElse(String.format("%s()=\u0001", m.method().getName()))
348                 )
349                 .collect(Collectors.joining(", ", type.getSimpleName() + "[", "]"));
350 
351         List<AccessorInfo> nonArrayGetters = getters.stream()
352                 .filter(i -> i.layoutInfo().arrayInfo().isEmpty())
353                 .toList();
354 
355         DirectMethodHandleDesc bootstrap = ofCallsiteBootstrap(
356                 desc(StringConcatFactory.class),
357                 "makeConcatWithConstants",
358                 CD_CallSite,
359                 CD_String, CD_Object.arrayType()
360         );
361 
362         List<ClassDesc> getDescriptions = nonArrayGetters.stream()
363                 .map(AccessorInfo::type)
364                 .map(MapperUtil::desc)
365                 .toList();
366 
367         DynamicCallSiteDesc desc = DynamicCallSiteDesc.of(
368                 bootstrap,
369                 "toString",
370                 MethodTypeDesc.of(CD_String, getDescriptions), // String, g0, g1, ...
371                 recipe
372         );
373 
374         cb.withMethodBody("toString",
375                 MethodTypeDesc.of(CD_String),
376                 ACC_PUBLIC | ACC_FINAL,
377                 cob -> {
378                     for (int i = 0; i < nonArrayGetters.size(); i++) {
379                         var name = nonArrayGetters.get(i).method().getName();
380                         cob.aload(0);
381                         // Method gi:()?
382                         cob.invokevirtual(classDesc, name, MethodTypeDesc.of(getDescriptions.get(i)));
383                     }
384                     cob.invokedynamic(desc);
385                     cob.areturn();
386                 });
387     }
388 
389     // Generate code that calculates:
390     // long indexOffset = f(dimensions, c1, c2, ..., long cN); // If an array, otherwise 0
391     // "this.offset + layoutOffset + indexOffset"
392     private static CodeBuilder offsetBlock(CodeBuilder cob,
393                                            AccessorInfo info,
394                                            ClassDesc classDesc) {
395         cob.aload(0)
396                 .getfield(classDesc, OFFSET_FIELD_NAME, CD_long); // long
397 
398         if (info.offset() != 0) {
399             cob.ldc(info.offset())
400                     .ladd();
401         }
402 
403         // If this is an array accessor, we need to
404         // compute the adjusted indices offset
405         info.layoutInfo().arrayInfo().ifPresent(
406                 ai -> reduceArrayIndexes(cob, ai)
407         );
408 
409         return cob;
410     }
411 
412     // Example:
413     //   The dimensions are [3, 4] and the element byte size is 8 bytes
414     //   reduce(2, 3) -> 2 * 4 * 8 + 3 * 8 = 88
415     // public static long reduce(long i1, long i2) {
416     //     long offset = Objects.checkIndex(i1, 3) * (8 * 4) +
417     //     Objects.checkIndex(i2, 4) * 8;
418     //     return offset;
419     // }
420     private static void reduceArrayIndexes(CodeBuilder cob,
421                                            ArrayInfo arrayInfo) {
422         long elementByteSize = arrayInfo.elementLayout().byteSize();
423         // Check parameters and push scaled offsets on the stack
424         for (int i = 0; i < arrayInfo.dimensions().size(); i++) {
425             long dimension = arrayInfo.dimensions().get(i);
426             long factor = arrayInfo.dimensions().stream()
427                     .skip(i + 1)
428                     .reduce(elementByteSize, Math::multiplyExact);
429 
430             cob.lload(1 + i * 2)
431                     .ldc(dimension)
432                     .invokestatic(desc(Objects.class), "checkIndex", MethodTypeDesc.of(CD_long, CD_long, CD_long))
433                     .ldc(factor)
434                     .lmul();
435         }
436         // Sum their values (including the value that existed *before* this method was invoked)
437         for (int i = 0; i < arrayInfo.dimensions().size(); i++) {
438             cob.ladd();
439         }
440     }
441 
442     private static int valueSlotNumber(int parameters) {
443         return (parameters - 1) * 2 + 1;
444     }
445 
446     private static List<ClassDesc> parameterDesc(AccessorInfo info) {
447         // If it is an array, there is a number of long parameters
448         List<ClassDesc> desc = info.layoutInfo().arrayInfo()
449                 .map(ai -> ai.dimensions().stream().map(_ -> CD_long).toList())
450                 .orElse(Collections.emptyList());
451 
452         if (info.key().accessorType() == AccessorInfo.AccessorType.SETTER) {
453             // Add the trailing setter type
454             desc = new ArrayList<>(desc);
455             desc.add(desc(info.type()));
456         }
457         return List.copyOf(desc);
458     }
459 
460     // Factory
461     static ByteCodeGenerator of(Class<?> type, ClassDesc classDesc, ClassBuilder cb) {
462         return new ByteCodeGenerator(type, classDesc, cb);
463     }
464 
465 }