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 }