1 /*
  2  * Copyright (c) 2023, 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 package jdk.internal.foreign.abi.fallback;
 26 
 27 import jdk.internal.foreign.Utils;
 28 
 29 import java.lang.foreign.Arena;
 30 import java.lang.foreign.GroupLayout;
 31 import java.lang.foreign.MemoryLayout;
 32 import java.lang.foreign.MemorySegment;
 33 import java.lang.foreign.PaddingLayout;
 34 import java.lang.foreign.SequenceLayout;
 35 import java.lang.foreign.StructLayout;
 36 import java.lang.foreign.UnionLayout;
 37 import java.lang.foreign.ValueLayout;
 38 import java.lang.invoke.VarHandle;
 39 import java.util.Collections;
 40 import java.util.List;
 41 import java.util.Map;
 42 import java.util.Objects;
 43 import java.util.function.Predicate;
 44 
 45 import static java.lang.foreign.ValueLayout.ADDRESS;
 46 import static java.lang.foreign.ValueLayout.JAVA_BYTE;
 47 import static java.lang.foreign.ValueLayout.JAVA_INT;
 48 import static java.lang.foreign.ValueLayout.JAVA_LONG;
 49 import static java.lang.foreign.ValueLayout.JAVA_SHORT;
 50 
 51 /**
 52  * typedef struct _ffi_type
 53  * {
 54  *   size_t size;
 55  *   unsigned short alignment;
 56  *   unsigned short type;
 57  *   struct _ffi_type **elements;
 58  * } ffi_type;
 59  */
 60 class FFIType {
 61     static final ValueLayout SIZE_T = layoutFor((int)ADDRESS.byteSize());
 62     private static final ValueLayout UNSIGNED_SHORT = JAVA_SHORT;
 63     private static final StructLayout LAYOUT = Utils.computePaddedStructLayout(
 64             SIZE_T, UNSIGNED_SHORT, UNSIGNED_SHORT.withName("type"), ADDRESS.withName("elements"));
 65 
 66     private static final VarHandle VH_TYPE = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("type"));
 67     private static final VarHandle VH_ELEMENTS = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("elements"));
 68     private static final VarHandle VH_SIZE_T = SIZE_T.varHandle();
 69 
 70     private static MemorySegment make(List<MemoryLayout> elements, FFIABI abi, Arena scope) {
 71         MemorySegment elementsSeg = scope.allocate((elements.size() + 1) * ADDRESS.byteSize());
 72         int i = 0;
 73         for (; i < elements.size(); i++) {
 74             MemoryLayout elementLayout = elements.get(i);
 75             MemorySegment elementType = toFFIType(elementLayout, abi, scope);
 76             elementsSeg.setAtIndex(ADDRESS, i, elementType);
 77         }
 78         // elements array is null-terminated
 79         elementsSeg.setAtIndex(ADDRESS, i, MemorySegment.NULL);
 80 
 81         MemorySegment ffiType = scope.allocate(LAYOUT);
 82         VH_TYPE.set(ffiType, 0L, LibFallback.structTag());
 83         VH_ELEMENTS.set(ffiType, 0L, elementsSeg);
 84 
 85         return ffiType;
 86     }
 87 
 88     private static final Map<Class<?>, MemorySegment> CARRIER_TO_TYPE = Map.of(
 89         boolean.class, LibFallback.uint8Type(),
 90         byte.class, LibFallback.sint8Type(),
 91         short.class, LibFallback.sint16Type(),
 92         char.class, LibFallback.uint16Type(),
 93         int.class, LibFallback.sint32Type(),
 94         long.class, LibFallback.sint64Type(),
 95         float.class, LibFallback.floatType(),
 96         double.class, LibFallback.doubleType(),
 97         MemorySegment.class, LibFallback.pointerType()
 98     );
 99 
100     static MemorySegment toFFIType(MemoryLayout layout, FFIABI abi, Arena scope) {
101         if (layout instanceof GroupLayout grpl) {
102             if (grpl instanceof StructLayout strl) {
103                 // libffi doesn't want our padding
104                 List<MemoryLayout> filteredLayouts = strl.memberLayouts().stream()
105                         .filter(Predicate.not(PaddingLayout.class::isInstance))
106                         .toList();
107                 MemorySegment structType = make(filteredLayouts, abi, scope);
108                 verifyStructType(strl, filteredLayouts, structType, abi);
109                 return structType;
110             }
111             assert grpl instanceof UnionLayout;
112             // JDK-8301800
113             throw new IllegalArgumentException("Fallback linker does not support by-value unions: " + grpl);
114         } else if (layout instanceof SequenceLayout sl) {
115             List<MemoryLayout> elements = Collections.nCopies(Math.toIntExact(sl.elementCount()), sl.elementLayout());
116             return make(elements, abi, scope);
117         }
118         return Objects.requireNonNull(CARRIER_TO_TYPE.get(((ValueLayout) layout).carrier()));
119     }
120 
121     // verify layout against what libffi sets
122     private static void verifyStructType(StructLayout structLayout, List<MemoryLayout> filteredLayouts, MemorySegment structType,
123                                          FFIABI abi) {
124         try (Arena verifyArena = Arena.ofConfined()) {
125             MemorySegment offsetsOut = verifyArena.allocate(SIZE_T.byteSize() * filteredLayouts.size());
126             LibFallback.getStructOffsets(structType, offsetsOut, abi);
127             long expectedOffset = 0;
128             int offsetIdx = 0;
129             for (MemoryLayout element : structLayout.memberLayouts()) {
130                 if (!(element instanceof PaddingLayout)) {
131                     long ffiOffset = sizeTAtIndex(offsetsOut, offsetIdx++);
132                     if (ffiOffset != expectedOffset) {
133                         throw new IllegalArgumentException("Invalid group layout." +
134                                 " Offset of '" + element.name().orElse("<unnamed>")
135                                 + "': " + expectedOffset + " != " + ffiOffset);
136                     }
137                 }
138                 expectedOffset += element.byteSize();
139             }
140         }
141     }
142 
143     static ValueLayout layoutFor(int byteSize) {
144         return switch (byteSize) {
145             case 1 -> JAVA_BYTE;
146             case 2 -> JAVA_SHORT;
147             case 4 -> JAVA_INT;
148             case 8 -> JAVA_LONG;
149             default -> throw new IllegalStateException("Unsupported size: " + byteSize);
150         };
151     }
152 
153     private static long sizeTAtIndex(MemorySegment segment, int index) {
154         long offset = SIZE_T.scale(0, index);
155         if (VH_SIZE_T.varType() == long.class) {
156             return (long) VH_SIZE_T.get(segment, offset);
157         } else {
158             return (int) VH_SIZE_T.get(segment, offset); // 'erase' to long
159         }
160     }
161 }