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