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