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 }