diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/constant/CConstant.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/constant/CConstant.java index 024289c25792..7c54c0645e85 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/constant/CConstant.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/constant/CConstant.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -89,8 +89,7 @@ private ValueAccess() { * This method is useful during native image generation, when the annotated method cannot be * called. * - * @param declaringClass The class that contains the method annotated with {@link CConstant} - * . + * @param declaringClass The class that contains the method. * @param methodName The name of the method annotated with {@link CConstant}. * @param returnType The desired type of the returned value. For integer-kind constants, the * supported types are {@link Long}, {@link Integer}, and {@link Boolean}. For diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/constant/CEnum.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/constant/CEnum.java index a6d1a6e5488a..4c835148cabb 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/constant/CEnum.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/constant/CEnum.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -63,9 +63,13 @@ * enumeration value. This annotation is optional and only needed if an attribute has a non-default * value. *

- * Note that C enumeration are merely a type-safe way to specify integer constants in C. Therefore, - * C enumeration values can be imported to Java as a regular {@link CConstant}; and {@link CEnum} - * can be used to import regular integer C constants as a Java enumeration. + * Note that in C, the keyword "enum" is merely a type-safe way to specify integer constants. + * Therefore, it is also possible to import C enumeration values as regular {@link CConstant}s into + * Java. + *

+ * {@link CEnum} can also be used to import multiple (potentially unrelated) regular integer C + * constants into a single Java enumeration. However, please note that each C constant will be + * converted to the C type that {@link CEnum} specifies. *

* The annotated class, or an outer class that contains the class, must be annotated with * {@link CContext}. @@ -77,8 +81,8 @@ public @interface CEnum { /** - * Specifies the name of the imported C enum type. If no name is provided, int is - * used instead. + * Specifies the name of the imported C enum type. If no name is provided, the C data type + * int is used instead. * * @since 19.0 */ diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/struct/CBitfield.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/struct/CBitfield.java index a056b803e12e..00fdf5eb553d 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/struct/CBitfield.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/struct/CBitfield.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -65,7 +65,7 @@ * The receiver is the pointer to the struct that is accessed, i.e., the base address of the memory * access. *

- * The {@code FieldType} must be must be a primitive integer type or a {@link WordBase word type}. + * The {@code FieldType} must be a primitive integer type or a {@link WordBase word type}. *

* The optional parameter {@code locationIdentity} specifies the {@link LocationIdentity} to be used * for the memory access. Two memory accesses with two different location identities are guaranteed diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/struct/CTypedefOfInfo.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/struct/CTypedefOfInfo.java index e892358528fd..7de4ee628eec 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/struct/CTypedefOfInfo.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/struct/CTypedefOfInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -46,9 +46,9 @@ import java.lang.annotation.Target; /** - * Informational only. Used to provide information about the base type of a type mentioned in a - * System Java annotation. Currently use with {@link CPointerTo} to inform about the base primitive - * type of the type pointed to. + * Informational only. Used to document the base type of a type mentioned in a C interface + * annotation. Currently use with {@link CPointerTo} to inform about the base primitive type of the + * type pointed to. * * @since 19.0 */ @@ -57,7 +57,7 @@ public @interface CTypedefOfInfo { /** - * Specifies the base type the C type of this annotated System Java class is a typedef of. + * Specifies the base type the C type of this annotated C interface class is a typedef of. * (i.e., typedef a1 a0; typedef a2 a1; => base type of a2 is a0) * * @since 19.0 diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Dlfcn.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Dlfcn.java index 8e9dc7e07ab8..05d564150cbb 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Dlfcn.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Dlfcn.java @@ -24,25 +24,25 @@ */ package com.oracle.svm.core.posix.headers; -import com.oracle.svm.core.c.libc.GLibC; -import com.oracle.svm.core.c.libc.LibCSpecific; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.CContext; import org.graalvm.nativeimage.c.constant.CConstant; import org.graalvm.nativeimage.c.function.CFunction; -import org.graalvm.nativeimage.c.function.CLibrary; import org.graalvm.nativeimage.c.function.CFunction.Transition; +import org.graalvm.nativeimage.c.function.CLibrary; import org.graalvm.nativeimage.c.struct.CField; import org.graalvm.nativeimage.c.struct.CPointerTo; import org.graalvm.nativeimage.c.struct.CStruct; -import org.graalvm.nativeimage.c.struct.CTypedefOfInfo; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; import org.graalvm.word.SignedWord; import org.graalvm.word.WordBase; +import com.oracle.svm.core.c.libc.GLibC; +import com.oracle.svm.core.c.libc.LibCSpecific; + // Checkstyle: stop /** @@ -106,7 +106,6 @@ public static class GNUExtensions { public interface Lmid_t extends SignedWord { } - @CTypedefOfInfo("long int") @CPointerTo(nameOfCType = "Lmid_t") public interface Lmid_tPointer extends PointerBase { Lmid_t read(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumArrayLookup.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumArrayLookup.java similarity index 81% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumArrayLookup.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumArrayLookup.java index 16953838b8f1..37adfdd5b05f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumArrayLookup.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumArrayLookup.java @@ -26,20 +26,19 @@ import jdk.graal.compiler.core.common.calc.UnsignedMath; -public class EnumArrayLookup extends EnumRuntimeData { - +public class CEnumArrayLookup extends CEnumRuntimeData { private final long cOffset; - private Enum[] cToJava; + private final Enum[] cToJava; - public EnumArrayLookup(long[] javaToC, long cOffset, Enum[] cToJava) { - super(javaToC); + public CEnumArrayLookup(long[] javaToC, int bytesInC, boolean isCValueUnsigned, long cOffset, Enum[] cToJava) { + super(javaToC, bytesInC, isCValueUnsigned); this.cOffset = cOffset; this.cToJava = cToJava; } @Override - protected Enum convertCToJava(long cValue) { - long arrayIdx = cValue - cOffset; + protected Enum lookupEnum(long value) { + long arrayIdx = value - cOffset; if (UnsignedMath.aboveOrEqual(arrayIdx, cToJava.length)) { return null; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumMapLookup.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumMapLookup.java similarity index 79% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumMapLookup.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumMapLookup.java index d7e8d1039dec..fcdc987db266 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumMapLookup.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumMapLookup.java @@ -26,17 +26,16 @@ import java.util.Map; -public class EnumMapLookup extends EnumRuntimeData { +public class CEnumMapLookup extends CEnumRuntimeData { + private final Map> cToJava; - private Map> cToJava; - - public EnumMapLookup(long[] javaToC, Map> cToJava) { - super(javaToC); + public CEnumMapLookup(long[] javaToC, int bytesInC, boolean isCValueUnsigned, Map> cToJava) { + super(javaToC, bytesInC, isCValueUnsigned); this.cToJava = cToJava; } @Override - protected Enum convertCToJava(long cValue) { - return cToJava.get(cValue); + protected Enum lookupEnum(long value) { + return cToJava.get(value); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumNoLookup.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumNoLookup.java similarity index 84% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumNoLookup.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumNoLookup.java index a4295688a96c..30fe0a15ad16 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumNoLookup.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumNoLookup.java @@ -24,14 +24,13 @@ */ package com.oracle.svm.core.c.enums; -public class EnumNoLookup extends EnumRuntimeData { - - public EnumNoLookup(long[] javaToC) { - super(javaToC); +public class CEnumNoLookup extends CEnumRuntimeData { + public CEnumNoLookup(long[] javaToC, int bytesInC, boolean isCValueUnsigned) { + super(javaToC, bytesInC, isCValueUnsigned); } @Override - protected Enum convertCToJava(long cValue) { + protected Enum lookupEnum(long value) { return null; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumRuntimeData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumRuntimeData.java new file mode 100644 index 000000000000..f13120e72fe5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/CEnumRuntimeData.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.c.enums; + +import org.graalvm.word.SignedWord; +import org.graalvm.word.UnsignedWord; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jdk.UninterruptibleUtils.CodeUtil; + +public abstract class CEnumRuntimeData { + private static final NullPointerException CACHED_NULL_EXCEPTION = new NullPointerException( + "null enum object cannot be converted to C enum integer (typically for automatic conversions on return to C code)"); + + /** Stores the sign- or zero-extended C values (depending on the signedness of the C enum). */ + private final long[] javaToC; + private final int bytesInC; + private final boolean isCEnumTypeUnsigned; + + protected CEnumRuntimeData(long[] javaToC, int bytesInC, boolean isCEnumTypeUnsigned) { + this.javaToC = javaToC; + this.bytesInC = bytesInC; + this.isCEnumTypeUnsigned = isCEnumTypeUnsigned; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public final boolean enumToBoolean(Enum value) { + return enumToLong(value) != 0L; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public final byte enumToByte(Enum value) { + return (byte) enumToLong(value); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public final int enumToShort(Enum value) { + return (short) enumToLong(value); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public final char enumToChar(Enum value) { + return (char) CodeUtil.zeroExtend(enumToLong(value), bytesInC * Byte.SIZE); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public final int enumToInt(Enum value) { + return (int) enumToLong(value); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public final long enumToLong(Enum value) { + if (value == null) { + throw CACHED_NULL_EXCEPTION; + } + return javaToC[value.ordinal()]; + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public final SignedWord enumToSignedWord(Enum value) { + long result = enumToLong(value); + return WordFactory.signed(result); + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public final UnsignedWord enumToUnsignedWord(Enum value) { + long result = CodeUtil.zeroExtend(enumToLong(value), bytesInC * Byte.SIZE); + return WordFactory.unsigned(result); + } + + /** + * Reduce the C value to the bits that fit into the C enum type. Then, sign- or zero-extend that + * value to 64-bit so that it can be compared to the data in the image heap. + */ + public final Enum longToEnum(long rawValue) { + long lookupValue; + if (isCEnumTypeUnsigned) { + lookupValue = CodeUtil.zeroExtend(rawValue, bytesInC * Byte.SIZE); + } else { + lookupValue = CodeUtil.signExtend(rawValue, bytesInC * Byte.SIZE); + } + return lookupEnum(lookupValue); + } + + protected abstract Enum lookupEnum(long value); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumRuntimeData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumRuntimeData.java deleted file mode 100644 index f8695b54609b..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/enums/EnumRuntimeData.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.c.enums; - -import org.graalvm.nativeimage.c.constant.CEnumValue; - -import com.oracle.svm.core.Uninterruptible; - -public abstract class EnumRuntimeData { - private static final NullPointerException CACHED_NULL_EXCEPTION = new NullPointerException( - "null enum object cannot be converted to C enum integer (typically for automatic conversions on return to C code)"); - - private final long[] javaToC; - - protected EnumRuntimeData(long[] javaToC) { - this.javaToC = javaToC; - } - - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected long convertJavaToCLong(Enum javaValue) { - if (javaValue == null) { - throw CACHED_NULL_EXCEPTION; - } - return javaToC[javaValue.ordinal()]; - } - - /** - * We need a separate method for single-slot return values. Since a call to this method replaces - * a call to the {@link CEnumValue} annotated method, we must match the slot-count of the - * original method otherwise frame state handling gets confused. - */ - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - protected int convertJavaToCInt(Enum javaValue) { - return (int) convertJavaToCLong(javaValue); - } - - protected abstract Enum convertCToJava(long cValue); -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index c1ec189f76b2..f7c432322b09 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -713,4 +713,28 @@ public interface CharReplacer { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) char replace(char val); } + + public static class CodeUtil { + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static long signExtend(long value, int inputBits) { + if (inputBits < 64) { + if ((value >>> (inputBits - 1) & 1) == 1) { + return value | (-1L << inputBits); + } else { + return value & ~(-1L << inputBits); + } + } else { + return value; + } + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static long zeroExtend(long value, int inputBits) { + if (inputBits < 64) { + return value & ~(-1L << inputBits); + } else { + return value; + } + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/MutablePerfDataEntry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/MutablePerfDataEntry.java index 59f128a57401..ff9991e581c1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/MutablePerfDataEntry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jvmstat/MutablePerfDataEntry.java @@ -32,8 +32,8 @@ * well). However, this is fairly tricky as the performance data memory is only reserved after the * heap was already started up. So, it could happen that the GC (especially G1) tries to write to * the performance data memory before it is reserved. This could probably be solved by porting the - * code that maps the performance data memory to system Java so that the performance data memory - * could be initialized after the image heap was mapped but before the GC is started up. Same + * code that maps the performance data memory to uninterruptible code so that the performance data + * memory could be initialized after the image heap was mapped but before the GC is started up. Same * applies to the teardown. See GR-40601. */ public interface MutablePerfDataEntry extends PerfDataEntry { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/CPUFeatureAccessFeatureBase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/CPUFeatureAccessFeatureBase.java index 1e3afd80e1b4..a44772ee01ae 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/CPUFeatureAccessFeatureBase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/CPUFeatureAccessFeatureBase.java @@ -31,7 +31,6 @@ import java.util.EnumSet; import org.graalvm.collections.EconomicMap; -import jdk.graal.compiler.debug.GraalError; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -46,6 +45,8 @@ import com.oracle.svm.hosted.c.info.StructFieldInfo; import com.oracle.svm.hosted.c.info.StructInfo; +import jdk.graal.compiler.debug.GraalError; + public abstract class CPUFeatureAccessFeatureBase { /** * Initializes global data required at run time by {@link CPUFeatureAccess}. This method will @@ -80,7 +81,7 @@ protected > void initializeCPUFeatureAccessData(Enum[] allC var field = ((StructFieldInfo) entry); var fieldName = field.getName(); int offset = field.getOffsetInfo().getProperty(); - int size = field.getSizeInfo().getProperty(); + int size = field.getSizeInBytes(); GraalError.guarantee(size == Byte.BYTES, "Expected field %s to be byte sized, but was %s", field.getName(), size); GraalError.guarantee(fieldName.startsWith("f"), "Unexpected field name in %s: %s", cpuFeatureStructClass.getName(), fieldName); fieldToOffset.put(fieldName.substring(1), offset); @@ -89,7 +90,7 @@ protected > void initializeCPUFeatureAccessData(Enum[] allC // Initialize CPUFeatures struct. // Over-allocate to a multiple of 64 bit. - int structSize = ((cpuFeatureStructInfo.getSizeInfo().getProperty() + Long.BYTES - 1) / Long.BYTES) * Long.BYTES; + int structSize = ((cpuFeatureStructInfo.getSizeInBytes() + Long.BYTES - 1) / Long.BYTES) * Long.BYTES; byte[] requiredFeaturesStruct = new byte[structSize]; // Data is stored in bitwise negated form, thus initialize to all 1s. Arrays.fill(requiredFeaturesStruct, (byte) 0xff); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 1477e5374118..a27200774269 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -976,8 +976,9 @@ protected void setupNativeImage(OptionValues options, Map feature.duringSetup(config)); @@ -1221,17 +1222,17 @@ private static Inflation createBigBang(DebugContext debug, OptionValues options, } @SuppressWarnings("try") - protected NativeLibraries setupNativeLibraries(ConstantReflectionProvider aConstantReflection, MetaAccessProvider aMetaAccess, - SnippetReflectionProvider aSnippetReflection, CEnumCallWrapperSubstitutionProcessor cEnumProcessor, ClassInitializationSupport classInitializationSupport, DebugContext debug) { + protected NativeLibraries setupNativeLibraries(HostedProviders providers, CEnumCallWrapperSubstitutionProcessor cEnumProcessor, ClassInitializationSupport classInitializationSupport, + DebugContext debug) { try (StopTimer ignored = TimerCollection.createTimerAndStart("(cap)")) { - NativeLibraries nativeLibs = new NativeLibraries(aConstantReflection, aMetaAccess, aSnippetReflection, ConfigurationValues.getTarget(), classInitializationSupport, + NativeLibraries nativeLibs = new NativeLibraries(providers, ConfigurationValues.getTarget(), classInitializationSupport, ImageSingletons.lookup(TemporaryBuildDirectoryProvider.class).getTemporaryBuildDirectory(), debug); cEnumProcessor.setNativeLibraries(nativeLibs); - processNativeLibraryImports(nativeLibs, aMetaAccess, classInitializationSupport); + processNativeLibraryImports(nativeLibs, classInitializationSupport); - ImageSingletons.add(SizeOfSupport.class, new SizeOfSupportImpl(nativeLibs, aMetaAccess)); - ImageSingletons.add(OffsetOf.Support.class, new OffsetOfSupportImpl(nativeLibs, aMetaAccess)); - ImageSingletons.add(CConstantValueSupport.class, new CConstantValueSupportImpl(nativeLibs, aMetaAccess)); + ImageSingletons.add(SizeOfSupport.class, new SizeOfSupportImpl(nativeLibs)); + ImageSingletons.add(OffsetOf.Support.class, new OffsetOfSupportImpl(nativeLibs)); + ImageSingletons.add(CConstantValueSupport.class, new CConstantValueSupportImpl(nativeLibs)); if (CAnnotationProcessorCache.Options.ExitAfterQueryCodeGeneration.getValue()) { throw new InterruptImageBuilding("Exiting image generation because of " + SubstrateOptionsParser.commandArgument(CAnnotationProcessorCache.Options.ExitAfterQueryCodeGeneration, "+")); @@ -1725,8 +1726,8 @@ public static String checkName(String name) { } @SuppressWarnings("try") - protected void processNativeLibraryImports(NativeLibraries nativeLibs, MetaAccessProvider metaAccess, ClassInitializationSupport classInitializationSupport) { - + protected void processNativeLibraryImports(NativeLibraries nativeLibs, ClassInitializationSupport classInitializationSupport) { + MetaAccessProvider metaAccess = nativeLibs.getMetaAccess(); for (Method method : loader.findAnnotatedMethods(CConstant.class)) { if (HostedLibCBase.isMethodProvidedInCurrentLibc(method)) { initializeAtBuildTime(method.getDeclaringClass(), classInitializationSupport, CConstant.class); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CConstantValueSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CConstantValueSupportImpl.java index e33555d5c232..cb9d180cc2ed 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CConstantValueSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/CConstantValueSupportImpl.java @@ -24,70 +24,65 @@ */ package com.oracle.svm.hosted.c; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.constant.CConstant; import org.graalvm.nativeimage.impl.CConstantValueSupport; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.c.info.ConstantInfo; +import com.oracle.svm.hosted.phases.CInterfaceInvocationPlugin; +import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; +@Platforms(Platform.HOSTED_ONLY.class) public final class CConstantValueSupportImpl implements CConstantValueSupport { private final NativeLibraries nativeLibraries; private final MetaAccessProvider metaAccess; - public CConstantValueSupportImpl(NativeLibraries nativeLibraries, MetaAccessProvider metaAccess) { + public CConstantValueSupportImpl(NativeLibraries nativeLibraries) { this.nativeLibraries = nativeLibraries; - this.metaAccess = metaAccess; + this.metaAccess = nativeLibraries.getMetaAccess(); } @Override + @SuppressWarnings("unchecked") public T getCConstantValue(Class declaringClass, String methodName, Class returnType) { + ResolvedJavaMethod method = getAnnotatedMethod(declaringClass, methodName); + ConstantInfo constantInfo = (ConstantInfo) nativeLibraries.findElementInfo(method); + Object value = constantInfo.getValue(); + + switch (constantInfo.getKind()) { + case INTEGER, POINTER -> { + return (T) CInterfaceInvocationPlugin.convertCIntegerToMethodReturnType(nativeLibraries, returnType, (long) value, constantInfo.getSizeInBytes() * Byte.SIZE, + constantInfo.isUnsigned()); + } + case FLOAT -> { + if (returnType == Float.class) { + return returnType.cast(((Double) value).floatValue()); + } + return returnType.cast(value); + } + case STRING, BYTEARRAY -> { + return returnType.cast(value); + } + default -> throw VMError.shouldNotReachHere("Unexpected returnType: " + returnType.getName()); + } + } + + private ResolvedJavaMethod getAnnotatedMethod(Class declaringClass, String methodName) { ResolvedJavaMethod method; try { - method = metaAccess.lookupJavaMethod(declaringClass.getMethod(methodName)); - } catch (NoSuchMethodException | SecurityException e) { + method = metaAccess.lookupJavaMethod(ReflectionUtil.lookupMethod(declaringClass, methodName)); + } catch (ReflectionUtil.ReflectionUtilError e) { throw VMError.shouldNotReachHere("Method not found: " + declaringClass.getName() + "." + methodName); } + if (method.getAnnotation(CConstant.class) == null) { throw VMError.shouldNotReachHere("Method " + declaringClass.getName() + "." + methodName + " is not annotated with @" + CConstant.class.getSimpleName()); } - - ConstantInfo constantInfo = (ConstantInfo) nativeLibraries.findElementInfo(method); - Object value = constantInfo.getValueInfo().getProperty(); - switch (constantInfo.getKind()) { - case INTEGER: - case POINTER: - Long longValue = (Long) value; - if (returnType == Boolean.class) { - return returnType.cast(Boolean.valueOf(longValue.longValue() != 0)); - } else if (returnType == Integer.class) { - return returnType.cast(Integer.valueOf((int) longValue.longValue())); - } else if (returnType == Long.class) { - return returnType.cast(value); - } - break; - - case FLOAT: - if (returnType == Double.class) { - return returnType.cast(value); - } - break; - - case STRING: - if (returnType == String.class) { - return returnType.cast(value); - } - break; - - case BYTEARRAY: - if (returnType == byte[].class) { - return returnType.cast(value); - } - break; - } - - throw VMError.shouldNotReachHere("Unexpected returnType: " + returnType.getName()); + return method; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java index 780c8ec3b54b..07a12c3fa930 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java @@ -48,12 +48,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.hotspot.JVMCIVersionCheck; -import jdk.graal.compiler.word.BarrieredAccess; -import jdk.graal.compiler.word.ObjectAccess; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.c.CContext; @@ -75,6 +69,7 @@ import com.oracle.graal.pointsto.infrastructure.WrappedElement; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport; import com.oracle.svm.core.util.UserError; @@ -86,6 +81,13 @@ import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.hotspot.JVMCIVersionCheck; +import jdk.graal.compiler.word.BarrieredAccess; +import jdk.graal.compiler.word.ObjectAccess; +import jdk.graal.compiler.word.Word; +import jdk.graal.compiler.word.WordTypes; import jdk.vm.ci.code.TargetDescription; import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.MetaAccessProvider; @@ -95,17 +97,18 @@ public final class NativeLibraries { private final MetaAccessProvider metaAccess; + private final WordTypes wordTypes; private final SnippetReflectionProvider snippetReflection; private final TargetDescription target; - private ClassInitializationSupport classInitializationSupport; + private final ClassInitializationSupport classInitializationSupport; private final Map elementToInfo; private final Map, NativeCodeContext> compilationUnitToContext; private final ResolvedJavaType wordBaseType; - private final ResolvedJavaType signedType; - private final ResolvedJavaType unsignedType; + private final ResolvedJavaType signedWordType; + private final ResolvedJavaType unsignedWordType; private final ResolvedJavaType pointerBaseType; private final ResolvedJavaType stringType; private final ResolvedJavaType byteArrayType; @@ -219,11 +222,11 @@ public String toString() { } } - public NativeLibraries(ConstantReflectionProvider constantReflection, MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, TargetDescription target, - ClassInitializationSupport classInitializationSupport, Path tempDirectory, DebugContext debug) { - this.metaAccess = metaAccess; - this.constantReflection = constantReflection; - this.snippetReflection = snippetReflection; + public NativeLibraries(HostedProviders providers, TargetDescription target, ClassInitializationSupport classInitializationSupport, Path tempDirectory, DebugContext debug) { + this.metaAccess = providers.getMetaAccess(); + this.constantReflection = providers.getConstantReflection(); + this.snippetReflection = providers.getSnippetReflection(); + this.wordTypes = providers.getWordTypes(); this.target = target; this.classInitializationSupport = classInitializationSupport; this.tempDirectory = tempDirectory; @@ -234,8 +237,8 @@ public NativeLibraries(ConstantReflectionProvider constantReflection, MetaAccess compilationUnitToContext = new HashMap<>(); wordBaseType = lookupAndRegisterType(WordBase.class); - signedType = lookupAndRegisterType(SignedWord.class); - unsignedType = lookupAndRegisterType(UnsignedWord.class); + signedWordType = lookupAndRegisterType(SignedWord.class); + unsignedWordType = lookupAndRegisterType(UnsignedWord.class); pointerBaseType = lookupAndRegisterType(PointerBase.class); stringType = lookupAndRegisterType(String.class); byteArrayType = lookupAndRegisterType(byte[].class); @@ -266,6 +269,10 @@ public NativeLibraries(ConstantReflectionProvider constantReflection, MetaAccess this.cache = new CAnnotationProcessorCache(); } + public static NativeLibraries singleton() { + return ImageSingletons.lookup(NativeLibraries.class); + } + private ResolvedJavaType lookupAndRegisterType(Class clazz) { AnalysisType type = (AnalysisType) metaAccess.lookupJavaType(clazz); type.registerAsReachable("is native library type"); @@ -276,6 +283,10 @@ public MetaAccessProvider getMetaAccess() { return metaAccess; } + public WordTypes getWordTypes() { + return wordTypes; + } + public SnippetReflectionProvider getSnippetReflection() { return snippetReflection; } @@ -571,19 +582,27 @@ public boolean isPointerBase(ResolvedJavaType type) { } public boolean isSigned(ResolvedJavaType type) { + if (type.isPrimitive()) { + return !type.getJavaKind().isUnsigned(); + } + /* - * No assignable check, we only go for exact match since Word (which implements Signed, - * Unsigned, and Pointer) should not match here. + * Explicitly filter types that implement UnsignedWord or PointerBase (e.g., Word implements + * SignedWord, UnsignedWord, and Pointer). */ - return signedType.equals(type); + return signedWordType.isAssignableFrom(type) && !unsignedWordType.isAssignableFrom(type) && !pointerBaseType.isAssignableFrom(type); } - public boolean isUnsigned(ResolvedJavaType type) { + public boolean isIntegerType(ResolvedJavaType type) { + if (type.isPrimitive()) { + return type.getJavaKind().isNumericInteger(); + } + /* - * No assignable check, we only go for exact match since Word (which implements Signed, - * Unsigned, and Pointer) should not match here. + * Explicitly filter types that implement PointerBase (e.g., Word implements SignedWord, + * UnsignedWord, and Pointer). */ - return unsignedType.equals(type); + return (signedWordType.isAssignableFrom(type) || unsignedWordType.isAssignableFrom(type)) && !pointerBaseType.isAssignableFrom(type); } public boolean isString(ResolvedJavaType type) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/OffsetOfSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/OffsetOfSupportImpl.java index 8bd115b1d842..3873f88e5493 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/OffsetOfSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/OffsetOfSupportImpl.java @@ -33,21 +33,18 @@ import com.oracle.svm.hosted.c.info.StructFieldInfo; import com.oracle.svm.hosted.c.info.StructInfo; -import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaType; public final class OffsetOfSupportImpl implements OffsetOf.Support { private final NativeLibraries nativeLibraries; - private final MetaAccessProvider metaAccess; - public OffsetOfSupportImpl(NativeLibraries nativeLibraries, MetaAccessProvider metaAccess) { + public OffsetOfSupportImpl(NativeLibraries nativeLibraries) { this.nativeLibraries = nativeLibraries; - this.metaAccess = metaAccess; } @Override public int offsetOf(Class clazz, String fieldName) { - ResolvedJavaType type = metaAccess.lookupJavaType(clazz); + ResolvedJavaType type = nativeLibraries.getMetaAccess().lookupJavaType(clazz); ElementInfo typeInfo = nativeLibraries.findElementInfo(type); VMError.guarantee(typeInfo instanceof StructInfo, "Class parameter %s of call to %s is not an annotated C struct", type, SizeOf.class.getSimpleName()); StructInfo structInfo = (StructInfo) typeInfo; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/SizeOfSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/SizeOfSupportImpl.java index 6e3e4f3cf8ac..6444968630dd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/SizeOfSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/SizeOfSupportImpl.java @@ -33,26 +33,23 @@ import com.oracle.svm.hosted.c.info.SizableInfo; import com.oracle.svm.hosted.c.info.StructInfo; -import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaType; public final class SizeOfSupportImpl implements SizeOfSupport { private final NativeLibraries nativeLibraries; - private final MetaAccessProvider metaAccess; - public SizeOfSupportImpl(NativeLibraries nativeLibraries, MetaAccessProvider metaAccess) { + public SizeOfSupportImpl(NativeLibraries nativeLibraries) { this.nativeLibraries = nativeLibraries; - this.metaAccess = metaAccess; } @Override public int sizeof(Class clazz) { - ResolvedJavaType type = metaAccess.lookupJavaType(clazz); + ResolvedJavaType type = nativeLibraries.getMetaAccess().lookupJavaType(clazz); ElementInfo typeInfo = nativeLibraries.findElementInfo(type); if (typeInfo instanceof StructInfo && ((StructInfo) typeInfo).isIncomplete()) { throw UserError.abort("Class parameter %s of call to %s is an incomplete structure, so no size is available", type.toJavaName(true), SizeOf.class.getSimpleName()); } else if (typeInfo instanceof SizableInfo) { - return ((SizableInfo) typeInfo).getSizeInfo().getProperty(); + return ((SizableInfo) typeInfo).getSizeInBytes(); } else { throw UserError.abort("Class parameter %s of call to %s is not an annotated C interface type", type.toJavaName(true), SizeOf.class.getSimpleName()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/QueryCodeWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/QueryCodeWriter.java index 710506b4ebd6..acb043129620 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/QueryCodeWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/QueryCodeWriter.java @@ -39,6 +39,7 @@ import com.oracle.svm.hosted.c.info.ConstantInfo; import com.oracle.svm.hosted.c.info.ElementInfo; import com.oracle.svm.hosted.c.info.EnumConstantInfo; +import com.oracle.svm.hosted.c.info.EnumInfo; import com.oracle.svm.hosted.c.info.InfoTreeVisitor; import com.oracle.svm.hosted.c.info.NativeCodeInfo; import com.oracle.svm.hosted.c.info.PointerToInfo; @@ -279,7 +280,7 @@ protected void visitStructBitfieldInfo(StructBitfieldInfo bitfieldInfo) { @Override protected void visitPointerToInfo(PointerToInfo pointerToInfo) { - String sizeOfExpr = sizeOf(pointerToInfo); + String sizeOfExpr; if (pointerToInfo.getKind() == ElementKind.POINTER && pointerToInfo.getName().startsWith("struct ")) { /* Eliminate need for struct forward declarations */ sizeOfExpr = "sizeof(void *)"; @@ -307,6 +308,24 @@ protected void visitRawPointerToInfo(RawPointerToInfo pointerToInfo) { /* Nothing to do, do not visit children. */ } + @Override + protected void visitEnumInfo(EnumInfo enumInfo) { + assert enumInfo.getKind() == ElementKind.INTEGER; + printUnsignedLong(enumInfo.getSizeInfo(), sizeOf(enumInfo)); + + registerElementForCurrentLine(enumInfo.getAnnotatedElement()); + writer.indents().appendln("{"); + writer.indent(); + writer.indents().appendln(uInt64 + " all_bits_set = -1;"); + writer.indents().appendln(enumInfo.getName() + " enumHolder = all_bits_set;"); + writer.indents().appendln("int is_unsigned = enumHolder > 0;"); + printIsUnsigned(enumInfo.getSignednessInfo(), "is_unsigned"); + writer.outdent(); + writer.indents().appendln("}"); + + super.visitEnumInfo(enumInfo); + } + @Override protected void visitEnumConstantInfo(EnumConstantInfo constantInfo) { assert constantInfo.getKind() == ElementKind.INTEGER; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/ConstantInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/ConstantInfo.java index 826ef5f311bb..b744abd84988 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/ConstantInfo.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/ConstantInfo.java @@ -38,6 +38,10 @@ public ConstantInfo(String name, ElementKind kind, ResolvedJavaMethod annotatedM this.valueInfo = adoptChild(new PropertyInfo<>("value")); } + public Object getValue() { + return valueInfo.getProperty(); + } + public PropertyInfo getValueInfo() { return valueInfo; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumConstantInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumConstantInfo.java index d49cabedb34a..4115d5357e60 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumConstantInfo.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumConstantInfo.java @@ -42,6 +42,10 @@ public EnumConstantInfo(String name, ResolvedJavaField annotatedField, boolean i this.valueInfo = adoptChild(new PropertyInfo<>("value")); } + public long getValue() { + return (long) valueInfo.getProperty(); + } + public PropertyInfo getValueInfo() { return valueInfo; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumInfo.java index 940b142bdcf9..b748c4cfb96c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumInfo.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumInfo.java @@ -24,43 +24,63 @@ */ package com.oracle.svm.hosted.c.info; -import com.oracle.svm.core.c.enums.EnumRuntimeData; +import java.util.ArrayList; + +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.svm.core.c.enums.CEnumRuntimeData; import jdk.vm.ci.meta.ResolvedJavaType; -public class EnumInfo extends ElementInfo { +public class EnumInfo extends SizableInfo { private final ResolvedJavaType annotatedType; + private final ArrayList valueMethods = new ArrayList<>(); + private final ArrayList lookupMethods = new ArrayList<>(); - protected boolean needsLookup; - private EnumRuntimeData runtimeData; + private CEnumRuntimeData runtimeData; public EnumInfo(String name, ResolvedJavaType annotatedType) { - super(name); + super(name, ElementKind.INTEGER); this.annotatedType = annotatedType; } @Override - public ResolvedJavaType getAnnotatedElement() { - return annotatedType; + public void accept(InfoTreeVisitor visitor) { + visitor.visitEnumInfo(this); } - public boolean getNeedsLookup() { - return needsLookup; + @Override + public ResolvedJavaType getAnnotatedElement() { + return annotatedType; } - public EnumRuntimeData getRuntimeData() { + public CEnumRuntimeData getRuntimeData() { assert runtimeData != null; return runtimeData; } - public void setRuntimeData(EnumRuntimeData runtimeData) { + public void setRuntimeData(CEnumRuntimeData runtimeData) { assert this.runtimeData == null; this.runtimeData = runtimeData; } - @Override - public void accept(InfoTreeVisitor visitor) { - visitor.visitEnumInfo(this); + public Iterable getCEnumValueMethods() { + return valueMethods; + } + + public Iterable getCEnumLookupMethods() { + return lookupMethods; + } + + public void addCEnumValueMethod(AnalysisMethod method) { + valueMethods.add(method); + } + + public void addCEnumLookupMethod(AnalysisMethod method) { + lookupMethods.add(method); + } + + public boolean hasCEnumLookupMethods() { + return !lookupMethods.isEmpty(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumLookupInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumLookupInfo.java deleted file mode 100644 index 35d08109ae28..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumLookupInfo.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.c.info; - -import jdk.vm.ci.meta.ResolvedJavaMethod; - -public class EnumLookupInfo extends ElementInfo { - - private final ResolvedJavaMethod annotatedMethod; - - public EnumLookupInfo(ResolvedJavaMethod annotatedMethod) { - super(annotatedMethod.getName()); - this.annotatedMethod = annotatedMethod; - } - - @Override - public ResolvedJavaMethod getAnnotatedElement() { - return annotatedMethod; - } - - @Override - public void accept(InfoTreeVisitor visitor) { - visitor.visitEnumLookupInfo(this); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumValueInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumValueInfo.java deleted file mode 100644 index 15ed824a2be7..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/EnumValueInfo.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.c.info; - -import jdk.vm.ci.meta.ResolvedJavaMethod; - -public class EnumValueInfo extends ElementInfo { - - private final ResolvedJavaMethod annotatedMethod; - - public EnumValueInfo(ResolvedJavaMethod annotatedMethod) { - super(annotatedMethod.getName()); - this.annotatedMethod = annotatedMethod; - } - - @Override - public ResolvedJavaMethod getAnnotatedElement() { - return annotatedMethod; - } - - @Override - public void accept(InfoTreeVisitor visitor) { - visitor.visitEnumValueInfo(this); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/InfoTreeBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/InfoTreeBuilder.java index 27efaa2fc74c..74d77cfa74f5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/InfoTreeBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/InfoTreeBuilder.java @@ -32,8 +32,6 @@ import java.util.Map; import java.util.TreeMap; -import jdk.graal.compiler.bytecode.BridgeMethodUtils; -import jdk.graal.compiler.phases.util.Providers; import org.graalvm.nativeimage.c.constant.CConstant; import org.graalvm.nativeimage.c.constant.CEnum; import org.graalvm.nativeimage.c.constant.CEnumConstant; @@ -68,6 +66,8 @@ import com.oracle.svm.hosted.cenum.CEnumCallWrapperMethod; import com.oracle.svm.util.ClassUtil; +import jdk.graal.compiler.bytecode.BridgeMethodUtils; +import jdk.graal.compiler.phases.util.Providers; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; @@ -139,7 +139,7 @@ protected void createConstantInfo(ResolvedJavaMethod method) { ResolvedJavaType returnType = AccessorInfo.getReturnType(method); if (returnType.getJavaKind() == JavaKind.Void || (returnType.getJavaKind() == JavaKind.Object && !nativeLibs.isString(returnType) && !nativeLibs.isByteArray(returnType) && !nativeLibs.isWordBase(returnType))) { - nativeLibs.addError("Wrong return type: expected a primitive type, String, or a Word type; found " + returnType.toJavaName(true), method); + nativeLibs.addError("Wrong return type: expected a primitive type, String, byte[], or a Word type; found " + returnType.toJavaName(true), method); return; } @@ -256,11 +256,7 @@ private void createStructInfo(ResolvedJavaType type) { structAccessorInfos.add(accessorInfo); } else { Map> map = bitfieldAnnotation != null ? bitfieldAccessorInfos : fieldAccessorInfos; - List accessorInfos = map.get(fieldName); - if (accessorInfos == null) { - accessorInfos = new ArrayList<>(); - map.put(fieldName, accessorInfos); - } + List accessorInfos = map.computeIfAbsent(fieldName, k -> new ArrayList<>()); accessorInfos.add(accessorInfo); } @@ -326,12 +322,7 @@ private void createRawStructInfo(ResolvedJavaType type) { if (fieldName == null) { structAccessorInfos.add(accessorInfo); } else { - Map> map = fieldAccessorInfos; - List accessorInfos = map.get(fieldName); - if (accessorInfos == null) { - accessorInfos = new ArrayList<>(); - map.put(fieldName, accessorInfos); - } + List accessorInfos = fieldAccessorInfos.computeIfAbsent(fieldName, k -> new ArrayList<>()); accessorInfos.add(accessorInfo); } @@ -398,16 +389,13 @@ private ElementKind elementKind(Collection accessorInfos) { AccessorInfo overallKindAccessor = null; for (AccessorInfo accessorInfo : accessorInfos) { - final ResolvedJavaType type; + ResolvedJavaType type; switch (accessorInfo.getAccessorKind()) { - case GETTER: - type = accessorInfo.getReturnType(); - break; - case SETTER: - type = accessorInfo.getValueParameterType(); - break; - default: + case GETTER -> type = accessorInfo.getReturnType(); + case SETTER -> type = accessorInfo.getValueParameterType(); + default -> { continue; + } } ResolvedJavaMethod method = accessorInfo.getAnnotatedElement(); @@ -424,18 +412,14 @@ private ElementKind elementKind(Collection accessorInfos) { private ElementKind elementKind(ResolvedJavaType type, boolean isPinnedObject) { switch (type.getJavaKind()) { - case Boolean: - case Byte: - case Char: - case Short: - case Int: - case Long: + case Boolean, Byte, Char, Short, Int, Long -> { return ElementKind.INTEGER; - case Float: - case Double: + } + case Float, Double -> { return ElementKind.FLOAT; - case Object: - if (nativeLibs.isSigned(type) || nativeLibs.isUnsigned(type)) { + } + case Object -> { + if (nativeLibs.isIntegerType(type)) { return ElementKind.INTEGER; } else if (isPinnedObject) { return ElementKind.OBJECT; @@ -446,8 +430,10 @@ private ElementKind elementKind(ResolvedJavaType type, boolean isPinnedObject) { } else { return ElementKind.POINTER; } - default: + } + default -> { return ElementKind.UNKNOWN; + } } } @@ -467,8 +453,8 @@ private boolean accessorValid(AccessorInfo accessorInfo) { if (accessorInfo.isIndexed()) { ResolvedJavaType paramType = accessorInfo.getParameterType(accessorInfo.indexParameterNumber(false)); - if (paramType.getJavaKind() != JavaKind.Int && paramType.getJavaKind() != JavaKind.Long && !nativeLibs.isSigned(paramType)) { - nativeLibs.addError("Wrong type of index parameter 0: expected int, long, or Signed; found " + paramType.toJavaName(true), method); + if (!nativeLibs.isIntegerType(paramType)) { + nativeLibs.addError("Wrong type of index parameter 0: expected an integer type; found " + paramType.toJavaName(true), method); return false; } } @@ -489,31 +475,39 @@ private boolean accessorValid(AccessorInfo accessorInfo) { return false; } switch (accessorInfo.getAccessorKind()) { - case ADDRESS: - if (!nativeLibs.isPointerBase(returnType) || nativeLibs.isSigned(returnType) || nativeLibs.isUnsigned(returnType)) { + case ADDRESS -> { + if (!nativeLibs.isPointerBase(returnType) || nativeLibs.isIntegerType(returnType)) { nativeLibs.addError("Wrong return type: expected a pointer type; found " + returnType.toJavaName(true), method); return false; } - break; - case OFFSET: - if (!(returnType.getJavaKind().isNumericInteger() || nativeLibs.isUnsigned(returnType))) { - nativeLibs.addError("Wrong return type: expected an integer numeric type or Unsigned; found " + returnType.toJavaName(true), method); + } + case OFFSET -> { + if (!nativeLibs.isIntegerType(returnType)) { + nativeLibs.addError("Wrong return type: expected an integer type; found " + returnType.toJavaName(true), method); return false; } - break; - case SETTER: + } + case SETTER -> { if (!checkObjectType(accessorInfo.getValueParameterType(), method)) { return false; } - break; + } } return true; } - private boolean checkObjectType(ResolvedJavaType returnType, ResolvedJavaMethod method) { - if (returnType.getJavaKind() == JavaKind.Object && !nativeLibs.isWordBase(returnType) && !isPinnedObjectFieldAccessor(method)) { - nativeLibs.addError("Wrong type: expected a primitive type or a Word type; found " + returnType.toJavaName(true) + ". Use the annotation @" + PinnedObjectField.class.getSimpleName() + - " if you know what you are doing.", method); + private boolean checkObjectType(ResolvedJavaType type, ResolvedJavaMethod method) { + if (type.getJavaKind() == JavaKind.Void) { + return true; + } else if (type.getJavaKind() == JavaKind.Object && !nativeLibs.isWordBase(type)) { + if (!isPinnedObjectFieldAccessor(method)) { + nativeLibs.addError("Wrong type: expected a primitive type or a Word type; found " + type.toJavaName(true) + ". Use the annotation @" + PinnedObjectField.class.getSimpleName() + + " if you know what you are doing.", method); + return false; + } + } else if (isPinnedObjectFieldAccessor(method)) { + nativeLibs.addError("Wrong type: expected an object type; found " + type.toJavaName(true) + ". The annotation @" + PinnedObjectField.class.getSimpleName() + + " may only be used for object fields.", method); return false; } return true; @@ -664,13 +658,12 @@ private void createEnumInfo(ResolvedJavaType type) { CEnum annotation = type.getAnnotation(CEnum.class); String name = annotation.value(); - if (!name.isEmpty()) { - if (annotation.addEnumKeyword()) { - name = "enum " + name; - } - } else { + if (name.isEmpty()) { name = "int"; + } else if (annotation.addEnumKeyword()) { + name = "enum " + name; } + EnumInfo enumInfo = new EnumInfo(name, type); /* Use the wrapped type to avoid registering all CEnum annotated classes as reachable. */ @@ -681,14 +674,21 @@ private void createEnumInfo(ResolvedJavaType type) { createEnumConstantInfo(enumInfo, field); } } + for (ResolvedJavaMethod method : type.getDeclaredMethods(false)) { - if (getMethodAnnotation(method, CEnumValue.class) != null) { - createEnumValueInfo(enumInfo, method); + AnalysisMethod analysisMethod = (AnalysisMethod) method; + + CEnumValue cEnumValue = getMethodAnnotation(method, CEnumValue.class); + if (cEnumValue != null) { + addCEnumValueMethod(enumInfo, analysisMethod); } - if (getMethodAnnotation(method, CEnumLookup.class) != null) { - createEnumLookupInfo(enumInfo, method); + + CEnumLookup cEnumLookup = getMethodAnnotation(method, CEnumLookup.class); + if (cEnumLookup != null) { + addCEnumLookupMethod(enumInfo, analysisMethod); } } + nativeCodeInfo.adoptChild(enumInfo); nativeLibs.registerElementInfo(type, enumInfo); } @@ -714,58 +714,44 @@ private void createEnumConstantInfo(EnumInfo enumInfo, ResolvedJavaField field) enumInfo.adoptChild(constantInfo); } - private static ResolvedJavaMethod originalMethod(ResolvedJavaMethod method) { - assert method instanceof AnalysisMethod; - AnalysisMethod analysisMethod = (AnalysisMethod) method; - assert analysisMethod.getWrapped() instanceof CEnumCallWrapperMethod; - CEnumCallWrapperMethod wrapperMethod = (CEnumCallWrapperMethod) analysisMethod.getWrapped(); - return wrapperMethod.getOriginal(); - } - - private void createEnumValueInfo(EnumInfo enumInfo, ResolvedJavaMethod method) { - + private void addCEnumValueMethod(EnumInfo enumInfo, AnalysisMethod method) { /* Check the modifiers of the original method. The synthetic method is not native. */ ResolvedJavaMethod originalMethod = originalMethod(method); if (!Modifier.isNative(originalMethod.getModifiers()) || Modifier.isStatic(originalMethod.getModifiers())) { nativeLibs.addError("Method annotated with @" + CEnumValue.class.getSimpleName() + " must be a non-static native method", method); return; - } - if (getParameterCount(method) != 0) { + } else if (getParameterCount(method) != 0) { nativeLibs.addError("Method annotated with @" + CEnumValue.class.getSimpleName() + " cannot have parameters", method); return; - } - ElementKind elementKind = elementKind(AccessorInfo.getReturnType(method), false); - if (elementKind != ElementKind.INTEGER) { - nativeLibs.addError("Method annotated with @" + CEnumValue.class.getSimpleName() + " must have an integer return type", method); + } else if (!nativeLibs.isIntegerType(AccessorInfo.getReturnType(method))) { + nativeLibs.addError("Method annotated with @" + CEnumValue.class.getSimpleName() + " must return an integer type", method); return; } - EnumValueInfo valueInfo = new EnumValueInfo(method); - enumInfo.adoptChild(valueInfo); - nativeLibs.registerElementInfo(method, valueInfo); + enumInfo.addCEnumValueMethod(method); } - private void createEnumLookupInfo(EnumInfo enumInfo, ResolvedJavaMethod method) { - + private void addCEnumLookupMethod(EnumInfo enumInfo, AnalysisMethod method) { /* Check the modifiers of the original method. The synthetic method is not native. */ ResolvedJavaMethod originalMethod = originalMethod(method); if (!Modifier.isNative(originalMethod.getModifiers()) || !Modifier.isStatic(originalMethod.getModifiers())) { nativeLibs.addError("Method annotated with @" + CEnumLookup.class.getSimpleName() + " must be a static native method", method); return; - } - if (getParameterCount(method) != 1 || elementKind(AccessorInfo.getParameterType(method, 0), false) != ElementKind.INTEGER) { + } else if (getParameterCount(method) != 1 || elementKind(AccessorInfo.getParameterType(method, 0), false) != ElementKind.INTEGER) { nativeLibs.addError("Method annotated with @" + CEnumLookup.class.getSimpleName() + " must have exactly one integer parameter", method); return; - } - if (!returnsDeclaringClass(method)) { + } else if (!returnsDeclaringClass(method)) { nativeLibs.addError("Return type of method annotated with @" + CEnumLookup.class.getSimpleName() + " must be the annotation type", method); return; } - enumInfo.needsLookup = true; - EnumLookupInfo lookupInfo = new EnumLookupInfo(method); - enumInfo.adoptChild(lookupInfo); - nativeLibs.registerElementInfo(method, lookupInfo); + enumInfo.addCEnumLookupMethod(method); + } + + private static ResolvedJavaMethod originalMethod(AnalysisMethod method) { + assert method.getWrapped() instanceof CEnumCallWrapperMethod; + CEnumCallWrapperMethod wrapperMethod = (CEnumCallWrapperMethod) method.getWrapped(); + return wrapperMethod.getOriginal(); } private static T getMethodAnnotation(ResolvedJavaMethod method, Class annotationClass) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/InfoTreeVisitor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/InfoTreeVisitor.java index 68e15c2fa498..fa754d2be472 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/InfoTreeVisitor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/InfoTreeVisitor.java @@ -75,7 +75,7 @@ protected final void processChildren(ElementInfo info) { try { child.accept(this); } catch (NumberFormatException e) { - throw UserError.abort("Missing CAP cache value for: %s", child.getUniqueID()); + throw UserError.abort(e, "Missing CAP cache value for: %s", child.getUniqueID()); } } } @@ -127,12 +127,4 @@ protected void visitEnumInfo(EnumInfo info) { protected void visitEnumConstantInfo(EnumConstantInfo info) { processChildren(info); } - - protected void visitEnumValueInfo(EnumValueInfo info) { - processChildren(info); - } - - protected void visitEnumLookupInfo(EnumLookupInfo info) { - processChildren(info); - } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/SizableInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/SizableInfo.java index 50bb5086069f..5423585eb959 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/SizableInfo.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/SizableInfo.java @@ -81,9 +81,9 @@ public enum SignednessValue { public SizableInfo(String name, ElementKind kind) { super(name); this.kind = kind; - this.sizeInfo = adoptChild(new PropertyInfo("size")); + this.sizeInfo = adoptChild(new PropertyInfo<>("size")); if (kind == ElementKind.INTEGER) { - this.signednessInfo = adoptChild(new PropertyInfo("signedness")); + this.signednessInfo = adoptChild(new PropertyInfo<>("signedness")); } else { this.signednessInfo = null; } @@ -93,6 +93,11 @@ public final ElementKind getKind() { return kind; } + public final int getSizeInBytes() { + return getSizeInfo().getProperty(); + } + + /** Size in bytes. */ public final PropertyInfo getSizeInfo() { return sizeInfo; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/StructBitfieldInfo.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/StructBitfieldInfo.java index e67904c791b4..65412e4effff 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/StructBitfieldInfo.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/info/StructBitfieldInfo.java @@ -41,10 +41,10 @@ public class StructBitfieldInfo extends ElementInfo { @SuppressWarnings("this-escape") public StructBitfieldInfo(String name) { super(name); - this.byteOffset = adoptChild(new PropertyInfo("byteOffset")); - this.startBit = adoptChild(new PropertyInfo("startBit")); - this.endBit = adoptChild(new PropertyInfo("endBit")); - this.signednessInfo = adoptChild(new PropertyInfo("signedness")); + this.byteOffset = adoptChild(new PropertyInfo<>("byteOffset")); + this.startBit = adoptChild(new PropertyInfo<>("startBit")); + this.endBit = adoptChild(new PropertyInfo<>("endBit")); + this.signednessInfo = adoptChild(new PropertyInfo<>("signedness")); } public PropertyInfo getByteOffsetInfo() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/NativeInfoTreeVisitor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/NativeInfoTreeVisitor.java index 99339b4be089..2c3c6f40cd0c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/NativeInfoTreeVisitor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/NativeInfoTreeVisitor.java @@ -26,6 +26,7 @@ import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.c.info.InfoTreeVisitor; + import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaType; @@ -47,8 +48,4 @@ protected final int getSizeInBytes(JavaKind kind) { protected final int getSizeInBytes(ResolvedJavaType type) { return getSizeInBytes(type.getJavaKind()); } - - protected final boolean isSigned(ResolvedJavaType type) { - return nativeLibs.isSigned(type); - } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/QueryParserUtil.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/QueryParserUtil.java deleted file mode 100644 index e0b4ca7b95ae..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/QueryParserUtil.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.c.query; - -import com.oracle.svm.hosted.c.info.PropertyInfo; - -public class QueryParserUtil { - - // The size of hex string the represents a long integer - static final int SIZE_OF_LONG_HEX = 16; - - static void parseSigned(PropertyInfo info, String hex) { - if (isNegative(hex)) { - if (hex.length() < SIZE_OF_LONG_HEX) { - String extended = signedExtend(hex); - parseHexToLong(info, extended); - } else { - parseHexToLong(info, hex); - } - } else { - parseHexToLong(info, hex); - } - } - - /** - * Only checks the most significant digit in the hexadecimal representation of the integer - * number. This method converts the most significant digit from String to int and check if its - * value if greater or equals to 8, if it is the most significant bit is set. Therefore, the - * integer number is negative. - *

- * A unsigned integer should not use this method. - * - * @param hex hexadecimal string representation of the integer being checked - * @return true if the hex represents a negative signed integer; no if it does not - */ - static boolean isNegative(String hex) { - int mostSignificantDigit = Integer.parseInt(hex.substring(0, 1), 16); - - if (mostSignificantDigit >= 8) { - return true; - } - - return false; - } - - static String unsignedExtend(String hex) { - int numOfMissingHexDigit = SIZE_OF_LONG_HEX - hex.length(); - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < numOfMissingHexDigit; i++) { - sb.append('0'); - } - - return sb.append(hex).toString(); - } - - static String unsignedExtendToSize(int sizeInByte, String hex) { - int numOfMissingHexDigit = sizeInByte * 2 - hex.length(); - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < numOfMissingHexDigit; i++) { - sb.append('0'); - } - - return sb.append(hex).toString(); - } - - static String signedExtend(String hex) { - int numOfMissingHexDigit = SIZE_OF_LONG_HEX - hex.length(); - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < numOfMissingHexDigit; i++) { - sb.append('F'); - } - - return sb.append(hex).toString(); - } - - /** - * Extends short or int hex to long hex by assigning most significant bits to 0, and then parses - * the hex string in chunks to avoid the problem that { Long.parseLong } does not deal with - * two's complement. - *

- * Signed negative hex should already be signed extended before calling { parseHexToLong }. - * - * @param info ValueInfo to be updated - * @param hex Hex string to be parsed - */ - static void parseHexToLong(PropertyInfo info, String hex) { - String extended = unsignedExtend(hex); - - try { - String msb = extended.substring(0, 8); - String lsb = extended.substring(8); - long msbValue = Long.parseLong(msb, 16); - long lsbValue = Long.parseLong(lsb, 16); - long value = msbValue << 32 | lsbValue; - info.setProperty(value); - } catch (NumberFormatException e) { - parseHexToLongUnsafe(info, hex); - } - } - - static void parseHexToLongUnsafe(PropertyInfo info, String hex) { - long value = 0; - - for (int i = 0; i < hex.length(); i++) { - char digit = hex.charAt(i); - value = 16 * value + Character.getNumericValue(digit); - } - - info.setProperty(value); - } - -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/QueryResultParser.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/QueryResultParser.java index 74a4f0a1b7c4..85529c30b19b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/QueryResultParser.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/QueryResultParser.java @@ -25,21 +25,23 @@ package com.oracle.svm.hosted.c.query; import static com.oracle.svm.core.util.VMError.shouldNotReachHereUnexpectedInput; -import static com.oracle.svm.hosted.c.query.QueryParserUtil.parseHexToLong; -import static com.oracle.svm.hosted.c.query.QueryParserUtil.parseSigned; -import static com.oracle.svm.hosted.c.query.QueryParserUtil.unsignedExtendToSize; import java.io.InputStream; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.graalvm.nativeimage.c.constant.CConstant; +import org.graalvm.nativeimage.c.struct.AllowNarrowingCast; +import org.graalvm.nativeimage.c.struct.AllowWideningCast; + import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.c.info.AccessorInfo; import com.oracle.svm.hosted.c.info.ConstantInfo; import com.oracle.svm.hosted.c.info.ElementInfo; import com.oracle.svm.hosted.c.info.EnumConstantInfo; +import com.oracle.svm.hosted.c.info.EnumInfo; import com.oracle.svm.hosted.c.info.NativeCodeInfo; import com.oracle.svm.hosted.c.info.PointerToInfo; import com.oracle.svm.hosted.c.info.PropertyInfo; @@ -53,7 +55,9 @@ import com.oracle.svm.hosted.c.info.StructInfo; import com.oracle.svm.hosted.c.util.FileUtils; +import jdk.vm.ci.code.CodeUtil; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaType; /** * Parses query result described in {@link QueryResultFormat}. @@ -83,47 +87,55 @@ public static List parse(NativeLibraries nativeLibs, NativeCodeInfo nati @Override protected void visitConstantInfo(ConstantInfo constantInfo) { switch (constantInfo.getKind()) { - case INTEGER: + case INTEGER -> { parseIntegerProperty(constantInfo.getSizeInfo()); parseSignedness(constantInfo.getSignednessInfo()); parseIntegerConstantValue(constantInfo.getValueInfo()); - - /* - * From the point of view of the C compiler, plain #define constants have the type - * int and therefore size 4. But sometimes we want to access such values as short or - * byte to avoid casts. Check the actual value of the constant, and if it fits the - * declared type of the constant, then change the actual size to the declared size. - */ - JavaKind returnKind = AccessorInfo.getReturnType(constantInfo.getAnnotatedElement()).getJavaKind(); - if (returnKind == JavaKind.Object) { - returnKind = nativeLibs.getTarget().wordJavaKind; - } - int declaredSize = getSizeInBytes(returnKind); - int actualSize = constantInfo.getSizeInfo().getProperty(); - if (declaredSize != actualSize) { - long value = (long) constantInfo.getValueInfo().getProperty(); - if (value >= returnKind.getMinValue() && value <= returnKind.getMaxValue()) { - constantInfo.getSizeInfo().setProperty(declaredSize); - } - } - - break; - case POINTER: + tryChangeSizeToDeclaredReturnType(constantInfo); + } + case POINTER -> { parseIntegerProperty(constantInfo.getSizeInfo()); parseIntegerConstantValue(constantInfo.getValueInfo()); - break; - case FLOAT: + } + case FLOAT -> { parseIntegerProperty(constantInfo.getSizeInfo()); parseFloatValue(constantInfo.getValueInfo()); - break; - case STRING: - parseStringValue(constantInfo.getValueInfo()); - break; - case BYTEARRAY: - parseByteArrayValue(constantInfo.getValueInfo()); - break; - default: - throw shouldNotReachHereUnexpectedInput(constantInfo.getKind()); // ExcludeFromJacocoGeneratedReport + } + case STRING -> parseStringValue(constantInfo.getValueInfo()); + case BYTEARRAY -> parseByteArrayValue(constantInfo.getValueInfo()); + default -> throw shouldNotReachHereUnexpectedInput(constantInfo.getKind()); // ExcludeFromJacocoGeneratedReport + } + } + + /** + * When a {@link CConstant} is accessed with a non-matching Java type (e.g., a C int is accessed + * as a Java short or Java long), then we try to implicitly cast the constant to the Java type. + * This implicit cast avoids the need for {@link AllowNarrowingCast}/{@link AllowWideningCast}. + */ + private void tryChangeSizeToDeclaredReturnType(ConstantInfo constantInfo) { + ResolvedJavaType javaReturnType = AccessorInfo.getReturnType(constantInfo.getAnnotatedElement()); + JavaKind javaReturnKind = nativeLibs.getWordTypes().asKind(javaReturnType); + int bytesInJava = javaReturnKind.getByteCount(); + + int bytesInC = constantInfo.getSizeInBytes(); + long cValue = (long) constantInfo.getValue(); + if (javaReturnKind == JavaKind.Boolean) { + /* Casts to boolean are always allowed because we convert the value to 0 or 1. */ + long newValue = cValue != 0L ? 1L : 0L; + constantInfo.getValueInfo().setProperty(newValue); + constantInfo.getSizeInfo().setProperty(bytesInJava); + } else if (bytesInJava != bytesInC) { + /* + * Only allow casts if the constant can be represented accurately. Note that a simple + * range check is not enough because we store the value of the C constant as a Java + * long, which will overflow if we have a large unsigned 64-bit value. + */ + boolean withinRange = cValue >= javaReturnKind.getMinValue() && cValue <= javaReturnKind.getMaxValue(); + boolean negativeCValueAsUnsigned64BitValue = !constantInfo.isUnsigned() && cValue < 0 && !nativeLibs.isSigned(javaReturnType) && javaReturnKind.getBitCount() == Long.SIZE; + boolean largeUnsignedCValue = constantInfo.isUnsigned() && cValue < 0; + if (withinRange && !negativeCValueAsUnsigned64BitValue && !largeUnsignedCValue) { + constantInfo.getSizeInfo().setProperty(bytesInJava); + } } } @@ -172,6 +184,15 @@ public void visitRawPointerToInfo(RawPointerToInfo pointerToInfo) { /* Nothing to do, do not visit children. */ } + @Override + protected void visitEnumInfo(EnumInfo info) { + assert info.getKind() == ElementKind.INTEGER; + parseIntegerProperty(info.getSizeInfo()); + parseSignedness(info.getSignednessInfo()); + + super.visitEnumInfo(info); + } + @Override protected void visitEnumConstantInfo(EnumConstantInfo constantInfo) { assert constantInfo.getKind() == ElementKind.INTEGER; @@ -185,20 +206,29 @@ private void parseSignedness(PropertyInfo info) { } private void parseIntegerConstantValue(PropertyInfo info) { - boolean isUnsigned = ((SizableInfo) info.getParent()).isUnsigned(); - int size = ((SizableInfo) info.getParent()).getSizeInfo().getProperty(); String hex = idToResult.get(info.getUniqueID()); - int hexSize = hex.length() / 2; + long value = parseHexToLong(hex); - if (hexSize < size) { - hex = unsignedExtendToSize(size, hex); + SizableInfo parent = (SizableInfo) info.getParent(); + int bitsInC = parent.getSizeInBytes() * Byte.SIZE; + if (bitsInC < Long.SIZE) { + value = parent.isUnsigned() ? CodeUtil.zeroExtend(value, bitsInC) : CodeUtil.signExtend(value, bitsInC); } - if (isUnsigned) { - parseHexToLong(info, hex); - } else { - parseSigned(info, hex); + info.setProperty(value); + } + + private static long parseHexToLong(String hex) { + assert hex.length() <= 16; + + if (hex.length() > 8) { + String msb = hex.substring(0, hex.length() - 8); + String lsb = hex.substring(hex.length() - 8); + long msbValue = Long.parseLong(msb, 16); + long lsbValue = Long.parseLong(lsb, 16); + return msbValue << 32 | lsbValue; } + return Long.parseLong(hex, 16); } private void parseFloatValue(PropertyInfo info) { @@ -228,7 +258,7 @@ private void parseByteArrayValue(PropertyInfo info) { private byte[] byteArrayLiteral(ElementInfo info) { String str = stringLiteral(info); if (!str.isEmpty()) { - return str.getBytes(Charset.forName("UTF-8")); + return str.getBytes(StandardCharsets.UTF_8); } else { return new byte[0]; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/RawStructureLayoutPlanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/RawStructureLayoutPlanner.java index 752cec68f8af..5d49627caff3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/RawStructureLayoutPlanner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/RawStructureLayoutPlanner.java @@ -86,11 +86,10 @@ protected void visitRawStructureInfo(RawStructureInfo info) { } ElementInfo einfo = nativeLibs.findElementInfo(t); - if (!(einfo instanceof RawStructureInfo)) { + if (!(einfo instanceof RawStructureInfo rinfo)) { throw UserError.abort(new CInterfaceError("Illegal super type " + t + " found", type).getMessage()); } - RawStructureInfo rinfo = (RawStructureInfo) einfo; rinfo.accept(this); assert rinfo.isPlanned(); @@ -101,8 +100,7 @@ protected void visitRawStructureInfo(RawStructureInfo info) { } for (ElementInfo child : new ArrayList<>(info.getChildren())) { - if (child instanceof StructFieldInfo) { - StructFieldInfo fieldInfo = (StructFieldInfo) child; + if (child instanceof StructFieldInfo fieldInfo) { StructFieldInfo parentFieldInfo = findParentFieldInfo(fieldInfo, info.getParentInfo()); if (parentFieldInfo != null) { fieldInfo.mergeChildrenAndDelete(parentFieldInfo); @@ -130,20 +128,16 @@ private void computeSize(StructFieldInfo info) { * offsets are not calculated before visiting all StructFieldInfos and collecting all * field types. */ - final ResolvedJavaType fieldType; AccessorInfo accessor = info.getAccessorInfoWithSize(); - switch (accessor.getAccessorKind()) { - case GETTER: - fieldType = accessor.getReturnType(); - break; - case SETTER: - fieldType = accessor.getValueParameterType(); - break; - default: - throw shouldNotReachHere("Unexpected accessor kind " + accessor.getAccessorKind()); - } + ResolvedJavaType fieldType = switch (accessor.getAccessorKind()) { + case GETTER -> accessor.getReturnType(); + case SETTER -> accessor.getValueParameterType(); + default -> throw shouldNotReachHere("Unexpected accessor kind " + accessor.getAccessorKind()); + }; + if (info.getKind() == ElementKind.INTEGER) { - info.getSignednessInfo().setProperty(isSigned(fieldType) ? SignednessValue.SIGNED : SignednessValue.UNSIGNED); + SignednessValue signedness = nativeLibs.isSigned(fieldType) ? SignednessValue.SIGNED : SignednessValue.UNSIGNED; + info.getSignednessInfo().setProperty(signedness); } declaredSize = getSizeInBytes(fieldType); } @@ -153,9 +147,9 @@ private void computeSize(StructFieldInfo info) { /** * Compute the offsets of each field. */ - private void planLayout(RawStructureInfo info) { + private static void planLayout(RawStructureInfo info) { /* Inherit from the parent type. */ - int currentOffset = info.getParentInfo() != null ? info.getParentInfo().getSizeInfo().getProperty() : 0; + int currentOffset = info.getParentInfo() != null ? info.getParentInfo().getSizeInBytes() : 0; List fields = new ArrayList<>(); for (ElementInfo child : info.getChildren()) { @@ -170,11 +164,11 @@ private void planLayout(RawStructureInfo info) { * Sort fields in field size descending order. Note that prior to this, the fields are * already sorted in alphabetical order. */ - fields.sort((f1, f2) -> f2.getSizeInfo().getProperty() - f1.getSizeInfo().getProperty()); + fields.sort((f1, f2) -> f2.getSizeInBytes() - f1.getSizeInBytes()); for (StructFieldInfo finfo : fields) { assert findParentFieldInfo(finfo, info.getParentInfo()) == null; - int fieldSize = finfo.getSizeInfo().getProperty(); + int fieldSize = finfo.getSizeInBytes(); currentOffset = alignOffset(currentOffset, fieldSize); assert currentOffset % fieldSize == 0; finfo.getOffsetInfo().setProperty(currentOffset); @@ -207,7 +201,7 @@ private void planLayout(RawStructureInfo info) { info.setPlanned(); } - private StructFieldInfo findParentFieldInfo(StructFieldInfo fieldInfo, RawStructureInfo parentInfo) { + private static StructFieldInfo findParentFieldInfo(StructFieldInfo fieldInfo, RawStructureInfo parentInfo) { if (parentInfo == null) { return null; } @@ -220,8 +214,7 @@ private StructFieldInfo findParentFieldInfo(StructFieldInfo fieldInfo, RawStruct } for (ElementInfo child : parentInfo.getChildren()) { - if (child instanceof StructFieldInfo) { - StructFieldInfo parentFieldInfo = (StructFieldInfo) child; + if (child instanceof StructFieldInfo parentFieldInfo) { if (fieldInfo.getName().equals(parentFieldInfo.getName())) { return parentFieldInfo; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/SizeAndSignednessVerifier.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/SizeAndSignednessVerifier.java index 0dcb8a6cab7f..8c3b4db14d0d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/SizeAndSignednessVerifier.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/SizeAndSignednessVerifier.java @@ -25,27 +25,30 @@ package com.oracle.svm.hosted.c.query; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; +import java.math.BigInteger; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.graalvm.nativeimage.AnnotationAccess; +import org.graalvm.nativeimage.c.constant.CEnum; +import org.graalvm.nativeimage.c.constant.CEnumConstant; import org.graalvm.nativeimage.c.struct.AllowNarrowingCast; import org.graalvm.nativeimage.c.struct.AllowWideningCast; import org.graalvm.nativeimage.c.struct.UniqueLocationIdentity; -import com.oracle.svm.core.c.enums.EnumArrayLookup; -import com.oracle.svm.core.c.enums.EnumMapLookup; -import com.oracle.svm.core.c.enums.EnumNoLookup; -import com.oracle.svm.core.c.enums.EnumRuntimeData; +import com.oracle.svm.core.c.enums.CEnumArrayLookup; +import com.oracle.svm.core.c.enums.CEnumMapLookup; +import com.oracle.svm.core.c.enums.CEnumNoLookup; +import com.oracle.svm.core.c.enums.CEnumRuntimeData; import com.oracle.svm.core.c.struct.CInterfaceLocationIdentity; +import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.c.info.AccessorInfo; import com.oracle.svm.hosted.c.info.ConstantInfo; import com.oracle.svm.hosted.c.info.ElementInfo; import com.oracle.svm.hosted.c.info.EnumConstantInfo; import com.oracle.svm.hosted.c.info.EnumInfo; -import com.oracle.svm.hosted.c.info.EnumValueInfo; import com.oracle.svm.hosted.c.info.NativeCodeInfo; import com.oracle.svm.hosted.c.info.SizableInfo; import com.oracle.svm.hosted.c.info.SizableInfo.ElementKind; @@ -53,6 +56,10 @@ import com.oracle.svm.hosted.c.info.StructFieldInfo; import com.oracle.svm.util.ClassUtil; +import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionType; +import jdk.vm.ci.code.CodeUtil; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -73,7 +80,7 @@ protected void visitConstantInfo(ConstantInfo constantInfo) { if (constantInfo.getKind() != ElementKind.STRING && constantInfo.getKind() != ElementKind.BYTEARRAY) { ResolvedJavaMethod method = constantInfo.getAnnotatedElement(); ResolvedJavaType returnType = AccessorInfo.getReturnType(method); - checkSizeAndSignedness(constantInfo, returnType, method, true); + verifySize(constantInfo, returnType, method, true); } } @@ -95,8 +102,7 @@ protected void visitStructBitfieldInfo(StructBitfieldInfo structBitfieldInfo) { private void checkAccessorLocationIdentity(List children) { AccessorInfo firstAccessorInfo = null; for (ElementInfo child : children) { - if (child instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) child; + if (child instanceof AccessorInfo accessorInfo) { if (accessorInfo.getAccessorKind() != AccessorInfo.AccessorKind.OFFSET) { if (firstAccessorInfo == null) { firstAccessorInfo = accessorInfo; @@ -118,64 +124,60 @@ protected void visitAccessorInfo(AccessorInfo accessorInfo) { ResolvedJavaType returnType = accessorInfo.getReturnType(); if (accessorInfo.getParent() instanceof StructBitfieldInfo) { - StructBitfieldInfo bitfieldInfo = (StructBitfieldInfo) accessorInfo.getParent(); - switch (accessorInfo.getAccessorKind()) { - case GETTER: - case SETTER: - checkSignedness(bitfieldInfo.isUnsigned(), returnType, method); - break; - default: - assert false; - } + assert accessorInfo.getAccessorKind() == AccessorInfo.AccessorKind.GETTER || accessorInfo.getAccessorKind() == AccessorInfo.AccessorKind.SETTER; } else { SizableInfo sizableInfo = (SizableInfo) accessorInfo.getParent(); switch (accessorInfo.getAccessorKind()) { - case ADDRESS: + case ADDRESS -> { assert nativeLibs.isPointerBase(returnType); - break; - case OFFSET: - assert returnType.getJavaKind().isNumericInteger() || nativeLibs.isUnsigned(returnType); - break; - case GETTER: - checkSizeAndSignedness(sizableInfo, returnType, method, true); - break; - case SETTER: + } + case OFFSET -> { + assert nativeLibs.isIntegerType(returnType); + } + case GETTER -> { + verifySize(sizableInfo, returnType, method, true); + verifySignedness(sizableInfo, returnType, method); + } + case SETTER -> { assert returnType.getJavaKind() == JavaKind.Void; ResolvedJavaType valueType = accessorInfo.getValueParameterType(); - checkSizeAndSignedness(sizableInfo, valueType, method, false); - break; + verifySize(sizableInfo, valueType, method, false); + verifySignedness(sizableInfo, valueType, method); + } } } } @Override protected void visitEnumInfo(EnumInfo enumInfo) { + verifyCEnumValueMethodReturnTypes(enumInfo); + verifyCEnumLookupMethodArguments(enumInfo); + + CEnumRuntimeData runtimeData = createCEnumRuntimeData(enumInfo); + enumInfo.setRuntimeData(runtimeData); + super.visitEnumInfo(enumInfo); + } + private CEnumRuntimeData createCEnumRuntimeData(EnumInfo enumInfo) { Map, Long> javaToC = new HashMap<>(); Map> cToJava = new HashMap<>(); - @SuppressWarnings("rawtypes") - Class enumClass = null; long minLookupValue = Long.MAX_VALUE; long maxLookupValue = Long.MIN_VALUE; + /* Collect all the CEnumConstants. */ for (ElementInfo child : enumInfo.getChildren()) { - if (child instanceof EnumConstantInfo) { - EnumConstantInfo valueInfo = (EnumConstantInfo) child; - long cValue = (Long) valueInfo.getValueInfo().getProperty(); + if (child instanceof EnumConstantInfo valueInfo) { + long cValue = limitValueToEnumBytes(enumInfo, valueInfo); Enum javaValue = valueInfo.getEnumValue(); + assert !javaToC.containsKey(javaValue); + javaToC.put(javaValue, cValue); - assert enumClass == null || enumClass == javaValue.getClass(); - enumClass = javaValue.getClass(); - - assert javaToC.get(javaValue) == null; - javaToC.put(javaValue, Long.valueOf(cValue)); - - if (enumInfo.getNeedsLookup() && valueInfo.getIncludeInLookup()) { - if (cToJava.get(cValue) != null) { - addError("C value is not unique, so reverse lookup from C to Java is not possible: " + cToJava.get(cValue) + " and " + javaValue + " hava same C value " + cValue, - valueInfo.getAnnotatedElement()); + if (enumInfo.hasCEnumLookupMethods() && valueInfo.getIncludeInLookup()) { + if (cToJava.containsKey(cValue)) { + addError("C value is not unique, so reverse lookup from C to Java is not possible: " + cToJava.get(cValue) + " and " + javaValue + " have the same C value " + cValue + ". " + + "Please exclude one of the values from the lookup (see @" + CEnumConstant.class.getSimpleName() + " for more details).", valueInfo.getAnnotatedElement()); } cToJava.put(cValue, javaValue); minLookupValue = Math.min(minLookupValue, cValue); @@ -184,6 +186,7 @@ protected void visitEnumInfo(EnumInfo enumInfo) { } } + /* Create a long[] that contains the values for the C enum constants. */ long[] javaToCArray = new long[javaToC.size()]; for (Map.Entry, Long> entry : javaToC.entrySet()) { int idx = entry.getKey().ordinal(); @@ -191,76 +194,126 @@ protected void visitEnumInfo(EnumInfo enumInfo) { javaToCArray[idx] = entry.getValue(); } - EnumRuntimeData runtimeData; - if (cToJava.size() > 0) { - assert minLookupValue <= maxLookupValue; - long spread = maxLookupValue - minLookupValue; - assert spread >= cToJava.size() - 1; - - /* - * We have a choice between an array-based lookup and keeping the HashMap. Since HashMap - * has a quite high memory footprint, an array is more compact even when most array - * elements are null. - */ - if (spread < cToJava.size() * 5L && spread >= 0 && spread < Integer.MAX_VALUE) { - long offset = minLookupValue; - Enum[] cToJavaArray = (Enum[]) Array.newInstance(enumClass, (int) spread + 1); - - for (Map.Entry> entry : cToJava.entrySet()) { - long idx = entry.getKey() - offset; - assert idx >= 0 && idx < cToJavaArray.length; - assert cToJavaArray[(int) idx] == null; - cToJavaArray[(int) idx] = entry.getValue(); - } + int enumBytesInC = enumInfo.getSizeInBytes(); + boolean isCEnumUnsigned = enumInfo.isUnsigned(); + if (cToJava.isEmpty()) { + return new CEnumNoLookup(javaToCArray, enumBytesInC, isCEnumUnsigned); + } - runtimeData = new EnumArrayLookup(javaToCArray, offset, cToJavaArray); - } else { - runtimeData = new EnumMapLookup(javaToCArray, cToJava); + /* Compute how spread out the C enum values are. */ + assert minLookupValue <= maxLookupValue; + BigInteger bigIntSpread = BigInteger.valueOf(maxLookupValue).subtract(BigInteger.valueOf(minLookupValue)); + assert bigIntSpread.compareTo(BigInteger.valueOf(cToJava.size() - 1)) >= 0; + long spread = bigIntSpread.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0 ? Long.MAX_VALUE : bigIntSpread.longValue(); + + /* + * Decide between array- and hash-map-based lookup. A hash map has a high memory footprint, + * so an array is more compact even if most elements are null. + */ + if (spread < cToJava.size() * 5L && spread < Integer.MAX_VALUE - 10) { + int arraySize = NumUtil.safeToInt(spread + 1); + Enum[] cToJavaArray = new Enum[arraySize]; + for (Map.Entry> entry : cToJava.entrySet()) { + int idx = NumUtil.safeToInt(entry.getKey() - minLookupValue); + assert idx >= 0 && idx < cToJavaArray.length; + assert cToJavaArray[idx] == null; + cToJavaArray[idx] = entry.getValue(); } - } else { - runtimeData = new EnumNoLookup(javaToCArray); + return new CEnumArrayLookup(javaToCArray, enumBytesInC, isCEnumUnsigned, minLookupValue, cToJavaArray); } - enumInfo.setRuntimeData(runtimeData); + return new CEnumMapLookup(javaToCArray, enumBytesInC, isCEnumUnsigned, Map.copyOf(cToJava)); } - @Override - protected void visitEnumValueInfo(EnumValueInfo valueInfo) { - ResolvedJavaMethod method = valueInfo.getAnnotatedElement(); - ResolvedJavaType returnType = AccessorInfo.getReturnType(method); - - EnumInfo enumInfo = (EnumInfo) valueInfo.getParent(); - for (ElementInfo info : enumInfo.getChildren()) { - if (info instanceof EnumConstantInfo) { - EnumConstantInfo constantInfo = (EnumConstantInfo) info; - checkSizeAndSignedness(constantInfo, returnType, method, true); - } + private void verifyCEnumValueMethodReturnTypes(EnumInfo enumInfo) { + for (ResolvedJavaMethod method : enumInfo.getCEnumValueMethods()) { + ResolvedJavaType returnType = AccessorInfo.getReturnType(method); + verifySize(enumInfo, returnType, method, true); } } - private void checkSizeAndSignedness(SizableInfo sizableInfo, ResolvedJavaType type, ResolvedJavaMethod method, boolean isReturn) { + private void verifyCEnumLookupMethodArguments(EnumInfo enumInfo) { + for (ResolvedJavaMethod method : enumInfo.getCEnumLookupMethods()) { + ResolvedJavaType arg0 = AccessorInfo.getParameterType(method, 0); + verifySize(enumInfo, arg0, method, false); + } + } + + private void verifySize(SizableInfo sizableInfo, ResolvedJavaType type, ResolvedJavaMethod method, boolean isReturn) { int declaredSize = getSizeInBytes(type); - int actualSize = sizableInfo.isObject() ? getSizeInBytes(JavaKind.Object) : sizableInfo.getSizeInfo().getProperty(); + + boolean allowNarrowingCast = AnnotationAccess.isAnnotationPresent(method, AllowNarrowingCast.class); + if (allowNarrowingCast) { + if (sizableInfo.isObject()) { + addError(ClassUtil.getUnqualifiedName(AllowNarrowingCast.class) + " cannot be used on fields that have an object type.", method); + } else if (sizableInfo.getKind() == ElementKind.FLOAT) { + addError(ClassUtil.getUnqualifiedName(AllowNarrowingCast.class) + " cannot be used on fields of type float or double.", method); + } + } + + boolean allowWideningCast = AnnotationAccess.isAnnotationPresent(method, AllowWideningCast.class); + if (allowWideningCast) { + if (sizableInfo.isObject()) { + addError(ClassUtil.getUnqualifiedName(AllowWideningCast.class) + " cannot be used on fields that have an object type.", method); + } else if (sizableInfo.getKind() == ElementKind.FLOAT) { + addError(ClassUtil.getUnqualifiedName(AllowWideningCast.class) + " cannot be used on fields of type float or double.", method); + } + } + + int actualSize = sizableInfo.isObject() ? getSizeInBytes(JavaKind.Object) : sizableInfo.getSizeInBytes(); if (declaredSize != actualSize) { - Class supressionAnnotation = (declaredSize > actualSize) ^ isReturn ? AllowNarrowingCast.class : AllowWideningCast.class; - if (method.getAnnotation(supressionAnnotation) == null) { + boolean narrow = declaredSize > actualSize; + if (isReturn) { + narrow = !narrow; + } + + Class suppressionAnnotation = narrow ? AllowNarrowingCast.class : AllowWideningCast.class; + if (method.getAnnotation(suppressionAnnotation) == null) { addError("Type " + type.toJavaName(false) + " has a size of " + declaredSize + " bytes, but accessed C value has a size of " + actualSize + - " bytes; to suppress this error, use the annotation @" + ClassUtil.getUnqualifiedName(supressionAnnotation), method); + " bytes; to suppress this error, use the annotation @" + ClassUtil.getUnqualifiedName(suppressionAnnotation), method); } } - - checkSignedness(sizableInfo.isUnsigned(), type, method); } - private void checkSignedness(boolean isUnsigned, ResolvedJavaType type, ResolvedJavaMethod method) { - if (isSigned(type)) { - if (isUnsigned) { - addError("Type " + type.toJavaName(false) + " is signed, but accessed C value is unsigned", method); - } - } else if (nativeLibs.isWordBase(type)) { - /* every Word type other than Signed is assumed to be unsigned. */ - if (!isUnsigned) { - addError("Type " + type.toJavaName(false) + " is unsigned, but accessed C value is signed", method); + private void verifySignedness(SizableInfo sizableInfo, ResolvedJavaType type, ResolvedJavaMethod method) { + if (Options.VerifyCInterfaceSignedness.getValue() && sizableInfo.getKind() == ElementKind.INTEGER) { + boolean isJavaTypeSigned = nativeLibs.isSigned(type); + boolean isCTypeSigned = sizableInfo.getSignednessInfo().getProperty() == SizableInfo.SignednessValue.SIGNED; + if (isJavaTypeSigned != isCTypeSigned) { + addError("Java type " + type.toJavaName(false) + " is " + (isJavaTypeSigned ? "signed" : "unsigned") + + ", while the accessed C value is " + (isCTypeSigned ? "signed" : "unsigned"), method); } } } + + /** + * The value of the constant was already sign- or zero-extended to 64-bit when it was parsed + * (based on the size and signedness of the constant itself). However, it can happen that the + * data type of the C enum is smaller than the data type of the constant. Therefore, we + * explicitly limit the bits of the constant to the bits that fit into the type of the C enum. + */ + private long limitValueToEnumBytes(EnumInfo enumInfo, EnumConstantInfo constantInfo) { + int enumBits = enumInfo.getSizeInBytes() * Byte.SIZE; + + long result = constantInfo.getValue(); + if (constantInfo.isUnsigned()) { + result = CodeUtil.zeroExtend(result, enumBits); + } else { + result = CodeUtil.signExtend(result, enumBits); + } + + if (result != constantInfo.getValue()) { + addError("The value of a C constant does not fit into the C data type of the @" + CEnum.class.getSimpleName() + " ('" + enumInfo.getName() + "'). " + + "Please specify a larger C data type in @" + CEnum.class.getSimpleName() + "(value = \"...\").", enumInfo, constantInfo); + } + return result; + } + + public static class Options { + /** + * Only verifies the signedness of accessors at the moment. The signedness of constants and + * enums in C is often just up to the compiler and therefore not particularly useful. + */ + @Option(help = "Verify the signedness of Java and C types that are used in the C interface.", type = OptionType.Debug)// + public static final HostedOptionKey VerifyCInterfaceSignedness = new HostedOptionKey<>(false); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/package-info.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/package-info.java deleted file mode 100644 index 8b38f07bf5a1..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/query/package-info.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -@Platforms(Platform.HOSTED_ONLY.class) -package com.oracle.svm.hosted.c.query; - -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/util/package-info.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/util/package-info.java deleted file mode 100644 index 61218bffdf0b..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/util/package-info.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -@Platforms(Platform.HOSTED_ONLY.class) -package com.oracle.svm.hosted.c.util; - -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperMethod.java index 2c41f1f186da..85625c6b5a86 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperMethod.java @@ -40,10 +40,7 @@ import com.oracle.svm.hosted.phases.CInterfaceInvocationPlugin; import com.oracle.svm.hosted.phases.HostedGraphKit; -import jdk.graal.compiler.core.common.type.Stamp; -import jdk.graal.compiler.core.common.type.StampFactory; import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.nodes.NodeView; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; import jdk.vm.ci.meta.JavaKind; @@ -51,8 +48,8 @@ import jdk.vm.ci.meta.ResolvedJavaType; /** - * Generated code for patching {@link CEnumLookup} annotated methods and calling - * EnumRuntimeData.convertCToJava(long). + * Create a synthetic graph to substitute native methods that are annotated with {@link CEnumLookup} + * or {@link CEnumValue}. */ public class CEnumCallWrapperMethod extends CustomSubstitutionMethod { @@ -76,33 +73,30 @@ public boolean isSynthetic() { @Override public StructuredGraph buildGraph(DebugContext debug, AnalysisMethod method, HostedProviders providers, Purpose purpose) { HostedGraphKit kit = new HostedGraphKit(debug, providers, method); - AnalysisType returnType = method.getSignature().getReturnType(); ValueNode arg = kit.getInitialArguments().get(0); - CInterfaceEnumTool tool = new CInterfaceEnumTool(kit.getMetaAccess()); + ValueNode returnValue = createInvoke(method, kit, returnType, arg); JavaKind pushKind = CInterfaceInvocationPlugin.pushKind(method); - ValueNode returnValue; + kit.getFrameState().push(pushKind, returnValue); + kit.createReturn(returnValue, pushKind); + + return kit.finalizeGraph(); + } + + private ValueNode createInvoke(AnalysisMethod method, HostedGraphKit kit, AnalysisType returnType, ValueNode arg) { if (method.getAnnotation(CEnumLookup.class) != null) { + /* Call a method that converts the primitive value to a Java enum. */ EnumInfo enumInfo = (EnumInfo) nativeLibraries.findElementInfo(returnType); - JavaKind parameterKind = JavaKind.Int; - returnValue = tool.createEnumLookupInvoke(kit, returnType, enumInfo, parameterKind, arg); + return CInterfaceEnumTool.singleton().createInvokeLookupEnum(kit, returnType, enumInfo, arg); } else if (method.getAnnotation(CEnumValue.class) != null) { + /* Call a method that converts a Java enum to a primitive value. */ ResolvedJavaType declaringType = method.getDeclaringClass(); EnumInfo enumInfo = (EnumInfo) nativeLibraries.findElementInfo(declaringType); - ValueNode invoke = tool.createEnumValueInvoke(kit, enumInfo, returnType.getJavaKind(), arg); - - ValueNode adapted = CInterfaceInvocationPlugin.adaptPrimitiveType(kit.getGraph(), invoke, invoke.stamp(NodeView.DEFAULT).getStackKind(), returnType.getJavaKind(), false); - Stamp originalStamp = StampFactory.forKind(returnType.getJavaKind()); - returnValue = CInterfaceInvocationPlugin.adaptPrimitiveType(kit.getGraph(), adapted, returnType.getJavaKind(), originalStamp.getStackKind(), false); - } else { - throw VMError.shouldNotReachHereUnexpectedInput(method); // ExcludeFromJacocoGeneratedReport + return CInterfaceEnumTool.singleton().createInvokeEnumToValue(kit, enumInfo, returnType, arg); } - kit.getFrameState().push(pushKind, returnValue); - kit.createReturn(returnValue, pushKind); - - return kit.finalizeGraph(); + throw VMError.shouldNotReachHereUnexpectedInput(method); // ExcludeFromJacocoGeneratedReport } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperSubstitutionProcessor.java index 7a17b8abdcbd..68b423f16a96 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperSubstitutionProcessor.java @@ -38,7 +38,7 @@ /** * Substitutes methods declared as {@code native} with {@link CEnumLookup} annotation with a - * synthetic graph that calls the appropriate EnumRuntimeData.convertCToJava(long) method. + * synthetic graph (see {@link CEnumCallWrapperMethod}). */ public class CEnumCallWrapperSubstitutionProcessor extends SubstitutionProcessor { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/package-info.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/package-info.java deleted file mode 100644 index 6f2e01a41ca6..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/package-info.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -@Platforms(Platform.HOSTED_ONLY.class) -package com.oracle.svm.hosted.cenum; - -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CCallStubMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CCallStubMethod.java index d8acd0017412..a3910fbfb645 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CCallStubMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CCallStubMethod.java @@ -42,7 +42,6 @@ import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.c.info.ElementInfo; import com.oracle.svm.hosted.c.info.EnumInfo; -import com.oracle.svm.hosted.c.info.EnumLookupInfo; import com.oracle.svm.hosted.phases.CInterfaceEnumTool; import com.oracle.svm.hosted.phases.HostedGraphKit; @@ -50,13 +49,9 @@ import jdk.graal.compiler.java.FrameStateBuilder; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaMethod; public abstract class CCallStubMethod extends CustomSubstitutionMethod { - private static final JavaKind cEnumKind = JavaKind.Int; - protected final int newThreadStatus; CCallStubMethod(ResolvedJavaMethod original, int newThreadStatus) { @@ -68,7 +63,7 @@ public abstract class CCallStubMethod extends CustomSubstitutionMethod { @Override public StructuredGraph buildGraph(DebugContext debug, AnalysisMethod method, HostedProviders providers, Purpose purpose) { - NativeLibraries nativeLibraries = CEntryPointCallStubSupport.singleton().getNativeLibraries(); + NativeLibraries nativeLibraries = NativeLibraries.singleton(); boolean deoptimizationTarget = MultiMethod.isDeoptTarget(method); HostedGraphKit kit = new HostedGraphKit(debug, providers, method); FrameStateBuilder state = kit.getFrameState(); @@ -96,57 +91,67 @@ public StructuredGraph buildGraph(DebugContext debug, AnalysisMethod method, Hos protected abstract ValueNode createTargetAddressNode(HostedGraphKit kit, List arguments); - protected static boolean isPrimitiveOrWord(HostedGraphKit kit, JavaType type) { - return type.getJavaKind().isPrimitive() || kit.getWordTypes().isWord(type); - } - + /** + * The signature may contain Java object types. The only Java object types that we support at + * the moment are Java enums (annotated with @CEnum). This method replaces all Java enums with + * suitable primitive types. + */ protected ResolvedSignature adaptSignatureAndConvertArguments(NativeLibraries nativeLibraries, HostedGraphKit kit, @SuppressWarnings("unused") AnalysisMethod method, AnalysisType returnType, AnalysisType[] parameterTypes, List arguments) { - for (int i = 0; i < parameterTypes.length; i++) { - if (!isPrimitiveOrWord(kit, parameterTypes[i])) { - ElementInfo typeInfo = nativeLibraries.findElementInfo(parameterTypes[i]); - if (typeInfo instanceof EnumInfo) { - ValueNode argumentValue = kit.maybeCreateExplicitNullCheck(arguments.get(i)); - CInterfaceEnumTool tool = new CInterfaceEnumTool(kit.getMetaAccess()); - argumentValue = tool.createEnumValueInvoke(kit, (EnumInfo) typeInfo, cEnumKind, argumentValue); - - arguments.set(i, argumentValue); - parameterTypes[i] = kit.getMetaAccess().lookupJavaType(cEnumKind.toJavaClass()); - } else { - throw UserError.abort("@%s parameter types are restricted to primitive types, word types and enumerations (@%s): %s", - getCorrespondingAnnotationName(), CEnum.class.getSimpleName(), getOriginal()); - } + if (!CInterfaceEnumTool.isPrimitiveOrWord(parameterTypes[i])) { + /* Replace the CEnum with the corresponding primitive type. */ + EnumInfo enumInfo = getEnumInfo(nativeLibraries, parameterTypes[i], false); + ValueNode argumentValue = kit.maybeCreateExplicitNullCheck(arguments.get(i)); + + AnalysisType cValueType = CInterfaceEnumTool.getCEnumValueType(enumInfo, kit.getMetaAccess()); + argumentValue = CInterfaceEnumTool.singleton().createInvokeEnumToValue(kit, enumInfo, cValueType, argumentValue); + + arguments.set(i, argumentValue); + parameterTypes[i] = cValueType; } } - /* Actual checks and conversion are in adaptReturnValue() */ - AnalysisType actualReturnType = isPrimitiveOrWord(kit, returnType) ? returnType : (AnalysisType) kit.getWordTypes().getWordImplType(); - return ResolvedSignature.fromArray(parameterTypes, actualReturnType); + + AnalysisType patchedReturnType = returnType; + if (!CInterfaceEnumTool.isPrimitiveOrWord(patchedReturnType)) { + /* + * The return type is a @CEnum. Change the return type to Word because the C function + * call will return some primitive value (and checks expect Word type replacements). + * adaptReturnValue() below does the actual conversion from the primitive value to the + * Java object. + */ + assert getEnumInfo(nativeLibraries, patchedReturnType, true) != null; + patchedReturnType = (AnalysisType) kit.getWordTypes().getWordImplType(); + } + return ResolvedSignature.fromArray(parameterTypes, patchedReturnType); } - private ValueNode adaptReturnValue(AnalysisMethod method, NativeLibraries nativeLibraries, HostedGraphKit kit, ValueNode invokeValue) { - ValueNode returnValue = invokeValue; + private ValueNode adaptReturnValue(AnalysisMethod method, NativeLibraries nativeLibraries, HostedGraphKit kit, ValueNode value) { AnalysisType declaredReturnType = method.getSignature().getReturnType(); - if (isPrimitiveOrWord(kit, declaredReturnType)) { - return returnValue; + if (CInterfaceEnumTool.isPrimitiveOrWord(declaredReturnType)) { + return value; } - ElementInfo typeInfo = nativeLibraries.findElementInfo(declaredReturnType); - if (typeInfo instanceof EnumInfo) { - UserError.guarantee(typeInfo.getChildren().stream().anyMatch(EnumLookupInfo.class::isInstance), - "Enum class %s needs a method that is annotated with @%s because it is used as the return type of a method annotated with @%s: %s", - declaredReturnType, - CEnumLookup.class.getSimpleName(), - getCorrespondingAnnotationName(), - getOriginal()); - - // We take a word return type because checks expect word type replacements, but it is - // narrowed to cEnumKind here. - CInterfaceEnumTool tool = new CInterfaceEnumTool(kit.getMetaAccess()); - returnValue = tool.createEnumLookupInvoke(kit, declaredReturnType, (EnumInfo) typeInfo, cEnumKind, returnValue); - } else { + + /* The C function call returned a primitive value. Now, we convert it to a Java enum. */ + EnumInfo enumInfo = getEnumInfo(nativeLibraries, declaredReturnType, true); + UserError.guarantee(enumInfo.hasCEnumLookupMethods(), + "Enum class %s needs a method that is annotated with @%s because it is used as the return type of a method annotated with @%s: %s.", + declaredReturnType, CEnumLookup.class.getSimpleName(), getCorrespondingAnnotationName(), getOriginal()); + + return CInterfaceEnumTool.singleton().createInvokeLookupEnum(kit, declaredReturnType, enumInfo, value); + } + + private EnumInfo getEnumInfo(NativeLibraries nativeLibraries, AnalysisType type, boolean isReturnType) { + ElementInfo typeInfo = nativeLibraries.findElementInfo(type); + if (typeInfo instanceof EnumInfo enumInfo) { + return enumInfo; + } + + if (isReturnType) { throw UserError.abort("Return types of methods annotated with @%s are restricted to primitive types, word types and enumerations (@%s): %s", getCorrespondingAnnotationName(), CEnum.class.getSimpleName(), getOriginal()); } - return returnValue; + throw UserError.abort("@%s parameter types are restricted to primitive types, word types and enumerations (@%s): %s", + getCorrespondingAnnotationName(), CEnum.class.getSimpleName(), getOriginal()); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointCallStubMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointCallStubMethod.java index 24027256ec0f..efa51ff354c2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointCallStubMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointCallStubMethod.java @@ -38,6 +38,7 @@ import org.graalvm.nativeimage.c.function.CEntryPoint.IsolateContext; import org.graalvm.nativeimage.c.function.CEntryPoint.IsolateThreadContext; +import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.infrastructure.ResolvedSignature; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; @@ -63,7 +64,6 @@ import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.c.info.ElementInfo; import com.oracle.svm.hosted.c.info.EnumInfo; -import com.oracle.svm.hosted.c.info.EnumLookupInfo; import com.oracle.svm.hosted.phases.CInterfaceEnumTool; import com.oracle.svm.hosted.phases.HostedGraphKit; @@ -93,46 +93,53 @@ import jdk.vm.ci.meta.ResolvedJavaType; public final class CEntryPointCallStubMethod extends EntryPointCallStubMethod { - static CEntryPointCallStubMethod create(AnalysisMethod targetMethod, CEntryPointData entryPointData, AnalysisMetaAccess aMetaAccess) { + static CEntryPointCallStubMethod create(BigBang bb, AnalysisMethod targetMethod, CEntryPointData entryPointData) { MetaAccessProvider originalMetaAccess = GraalAccess.getOriginalProviders().getMetaAccess(); ResolvedJavaType declaringClass = originalMetaAccess.lookupJavaType(IsolateEnterStub.class); ConstantPool constantPool = IsolateEnterStub.getConstantPool(originalMetaAccess); - return new CEntryPointCallStubMethod(entryPointData, targetMethod, declaringClass, constantPool, aMetaAccess.getUniverse().getWordKind(), originalMetaAccess); + return new CEntryPointCallStubMethod(entryPointData, targetMethod, declaringClass, constantPool, bb.getMetaAccess()); } - private static final JavaKind cEnumParameterKind = JavaKind.Int; - private final CEntryPointData entryPointData; private final ResolvedJavaMethod targetMethod; private final ResolvedSignature targetSignature; - private CEntryPointCallStubMethod(CEntryPointData entryPointData, AnalysisMethod targetMethod, ResolvedJavaType holderClass, ConstantPool holderConstantPool, JavaKind wordKind, - MetaAccessProvider metaAccess) { - super(SubstrateUtil.uniqueStubName(targetMethod.getWrapped()), holderClass, createSignature(targetMethod, wordKind, metaAccess), holderConstantPool); + private CEntryPointCallStubMethod(CEntryPointData entryPointData, AnalysisMethod targetMethod, ResolvedJavaType holderClass, ConstantPool holderConstantPool, AnalysisMetaAccess metaAccess) { + super(SubstrateUtil.uniqueStubName(targetMethod.getWrapped()), holderClass, createSignature(targetMethod, metaAccess), holderConstantPool); this.entryPointData = entryPointData; this.targetMethod = targetMethod.getWrapped(); this.targetSignature = targetMethod.getSignature(); } /** - * This method creates a new signature for the stub in which all @CEnum values are converted - * into their corresponding primitive type. In correspondence to how the @CEnum values are - * actually handled, parameters are transformed to the type specified by cEnumParameterKind and - * return type is transformed into the word type. - * - * @see CEnum - * @see CEntryPointCallStubMethod#adaptParameterTypes - * @see CEntryPointCallStubMethod#adaptReturnValue(HostedGraphKit, ValueNode) + * This method creates a new signature for the stub in which all {@link CEnum} values are + * converted to suitable primitive types. */ - private static ResolvedSignature createSignature(AnalysisMethod targetMethod, JavaKind wordKind, MetaAccessProvider metaAccess) { - ResolvedJavaType[] paramTypes = targetMethod.toParameterList().stream() - .map(type -> type.getAnnotation(CEnum.class) != null ? metaAccess.lookupJavaType(cEnumParameterKind.toJavaClass()) : type.getWrapped()) - .toArray(ResolvedJavaType[]::new); - ResolvedJavaType returnType = targetMethod.getSignature().getReturnType().getWrapped(); - if (returnType.getAnnotation(CEnum.class) != null) { - returnType = metaAccess.lookupJavaType(wordKind.toJavaClass()); + private static ResolvedSignature createSignature(AnalysisMethod method, AnalysisMetaAccess metaAccess) { + NativeLibraries nativeLibraries = NativeLibraries.singleton(); + AnalysisType[] args = method.toParameterList().toArray(AnalysisType[]::new); + + ResolvedJavaType[] patchedArgs = new ResolvedJavaType[args.length]; + for (int i = 0; i < args.length; i++) { + if (CInterfaceEnumTool.isPrimitiveOrWord(args[i])) { + patchedArgs[i] = args[i].getWrapped(); + } else { + /* Replace the CEnum with the corresponding primitive type. */ + EnumInfo enumInfo = getEnumInfo(nativeLibraries, method, args[i], false); + patchedArgs[i] = CInterfaceEnumTool.getCEnumValueType(enumInfo, metaAccess).getWrapped(); + } + } + + AnalysisType patchedReturnType = method.getSignature().getReturnType(); + if (!CInterfaceEnumTool.isPrimitiveOrWord(patchedReturnType)) { + /* + * The return type is a @CEnum. Change the return type to Word because the C entry point + * will return some primitive value. + */ + assert getEnumInfo(nativeLibraries, method, patchedReturnType, true) != null; + patchedReturnType = (AnalysisType) nativeLibraries.getWordTypes().getWordImplType(); } - return ResolvedSignature.fromArray(paramTypes, returnType); + return ResolvedSignature.fromArray(patchedArgs, patchedReturnType.getWrapped()); } @Override @@ -151,14 +158,12 @@ public StructuredGraph buildGraph(DebugContext debug, AnalysisMethod method, Hos } HostedGraphKit kit = new HostedGraphKit(debug, providers, method); - NativeLibraries nativeLibraries = CEntryPointCallStubSupport.singleton().getNativeLibraries(); + NativeLibraries nativeLibraries = NativeLibraries.singleton(); List parameterTypes = new ArrayList<>(targetSignature.toParameterList(null)); List parameterLoadTypes = new ArrayList<>(parameterTypes); - EnumInfo[] parameterEnumInfos; - - parameterEnumInfos = adaptParameterTypes(nativeLibraries, kit, parameterTypes, parameterLoadTypes); + EnumInfo[] parameterEnumInfos = adaptParameterTypes(nativeLibraries, kit, parameterTypes, parameterLoadTypes); ValueNode[] args = kit.getInitialArguments().toArray(ValueNode.EMPTY_ARRAY); if (ImageSingletons.contains(CInterfaceWrapper.class)) { @@ -331,47 +336,60 @@ private StructuredGraph buildBuiltinGraph(DebugContext debug, AnalysisMethod met return kit.finalizeGraph(); } + /** + * The signature may contain Java object types. The only Java object types that we support at + * the moment are Java enums (annotated with @CEnum). This method replaces all Java enums with + * suitable primitive types. + */ private EnumInfo[] adaptParameterTypes(NativeLibraries nativeLibraries, HostedGraphKit kit, List parameterTypes, List parameterLoadTypes) { EnumInfo[] parameterEnumInfos = null; for (int i = 0; i < parameterTypes.size(); i++) { - if (!parameterTypes.get(i).getJavaKind().isPrimitive() && !kit.getWordTypes().isWord(parameterTypes.get(i))) { - ElementInfo typeInfo = nativeLibraries.findElementInfo(parameterTypes.get(i)); - if (typeInfo instanceof EnumInfo) { - UserError.guarantee(typeInfo.getChildren().stream().anyMatch(EnumLookupInfo.class::isInstance), - "Enum class %s needs a method that is annotated with @%s because it is used as a parameter of an entry point method: %s", - parameterTypes.get(i), - CEnumLookup.class.getSimpleName(), - targetMethod); - - if (parameterEnumInfos == null) { - parameterEnumInfos = new EnumInfo[parameterTypes.size()]; - } - parameterEnumInfos[i] = (EnumInfo) typeInfo; - - parameterLoadTypes.set(i, kit.getMetaAccess().lookupJavaType(cEnumParameterKind.toJavaClass())); - - final int parameterIndex = i; - FrameState initialState = kit.getGraph().start().stateAfter(); - Iterator matchingNodes = initialState.values().filter(node -> ((ParameterNode) node).index() == parameterIndex).iterator(); - ValueNode parameterNode = matchingNodes.next(); - assert !matchingNodes.hasNext() && parameterNode.usages().filter(n -> n != initialState).isEmpty(); - parameterNode.setStamp(StampFactory.forKind(cEnumParameterKind)); - } else { - throw UserError.abort("Entry point method parameter types are restricted to primitive types, word types and enumerations (@%s): %s, given type was %s", - CEnum.class.getSimpleName(), targetMethod, parameterTypes.get(i)); + if (!CInterfaceEnumTool.isPrimitiveOrWord(parameterTypes.get(i))) { + EnumInfo enumInfo = getEnumInfo(nativeLibraries, targetMethod, parameterTypes.get(i), false); + UserError.guarantee(enumInfo.hasCEnumLookupMethods(), + "Enum class %s needs a method that is annotated with @%s because it is used as a parameter of an entry point method: %s", + parameterTypes.get(i), CEnumLookup.class.getSimpleName(), targetMethod); + + if (parameterEnumInfos == null) { + parameterEnumInfos = new EnumInfo[parameterTypes.size()]; } + parameterEnumInfos[i] = enumInfo; + + /* The argument is a @CEnum, so change the type to a primitive type. */ + AnalysisType paramType = CInterfaceEnumTool.getCEnumValueType(enumInfo, kit.getMetaAccess()); + parameterLoadTypes.set(i, paramType); + + final int parameterIndex = i; + FrameState initialState = kit.getGraph().start().stateAfter(); + Iterator matchingNodes = initialState.values().filter(node -> ((ParameterNode) node).index() == parameterIndex).iterator(); + ValueNode parameterNode = matchingNodes.next(); + assert !matchingNodes.hasNext() && parameterNode.usages().filter(n -> n != initialState).isEmpty(); + parameterNode.setStamp(StampFactory.forKind(paramType.getJavaKind())); } } return parameterEnumInfos; } + private static EnumInfo getEnumInfo(NativeLibraries nativeLibraries, ResolvedJavaMethod method, AnalysisType type, boolean isReturnType) { + ElementInfo typeInfo = nativeLibraries.findElementInfo(type); + if (typeInfo instanceof EnumInfo enumInfo) { + return enumInfo; + } + + if (isReturnType) { + throw UserError.abort("Entry point method return types are restricted to primitive types, word types and enumerations (@%s): %s, given type was %s", + CEnum.class.getSimpleName(), method, type); + } + throw UserError.abort("Entry point method parameter types are restricted to primitive types, word types and enumerations (@%s): %s, given type was %s", + CEnum.class.getSimpleName(), method, type); + } + private static void adaptArgumentValues(HostedGraphKit kit, List parameterTypes, EnumInfo[] parameterEnumInfos, ValueNode[] args) { if (parameterEnumInfos != null) { // These methods must be called after the prologue established a safe context for (int i = 0; i < parameterEnumInfos.length; i++) { if (parameterEnumInfos[i] != null) { - CInterfaceEnumTool tool = new CInterfaceEnumTool(kit.getMetaAccess()); - args[i] = tool.createEnumLookupInvoke(kit, parameterTypes.get(i), parameterEnumInfos[i], cEnumParameterKind, args[i]); + args[i] = CInterfaceEnumTool.singleton().createInvokeLookupEnum(kit, parameterTypes.get(i), parameterEnumInfos[i], args[i]); } } } @@ -566,31 +584,23 @@ private void generateExceptionHandler(ResolvedJavaMethod method, HostedGraphKit } private ValueNode adaptReturnValue(HostedGraphKit kit, ValueNode value) { - ValueNode returnValue = value; - if (returnValue.getStackKind().isPrimitive()) { - return returnValue; + if (value.getStackKind().isPrimitive()) { + return value; } + + /* The method returns a Java enum, so we need to convert the enum to a primitive value. */ AnalysisType returnType = targetSignature.getReturnType(); - NativeLibraries nativeLibraries = CEntryPointCallStubSupport.singleton().getNativeLibraries(); - ElementInfo typeInfo = nativeLibraries.findElementInfo(returnType); - if (typeInfo instanceof EnumInfo) { - // Always return enum values as a signed word because it should never be a problem if - // the caller expects a narrower integer type and the various checks already handle - // replacements with word types. - CInterfaceEnumTool tool = new CInterfaceEnumTool(kit.getMetaAccess()); - JavaKind cEnumReturnType = kit.getWordTypes().getWordKind(); - assert !cEnumReturnType.isUnsigned() : "requires correct representation of signed values"; - returnValue = tool.startEnumValueInvokeWithException(kit, (EnumInfo) typeInfo, cEnumReturnType, returnValue); - kit.exceptionPart(); - kit.append(new CEntryPointLeaveNode(LeaveAction.ExceptionAbort, kit.exceptionObject())); - kit.append(new LoweredDeadEndNode()); - kit.endInvokeWithException(); + NativeLibraries nativeLibraries = NativeLibraries.singleton(); - } else { - throw UserError.abort("Entry point method return types are restricted to primitive types, word types and enumerations (@%s): %s", - CEnum.class.getSimpleName(), targetMethod); - } - return returnValue; + EnumInfo enumInfo = getEnumInfo(nativeLibraries, targetMethod, returnType, true); + ValueNode result = CInterfaceEnumTool.singleton().startInvokeWithExceptionEnumToValue(kit, enumInfo, CInterfaceEnumTool.getCEnumValueType(enumInfo, kit.getMetaAccess()), value); + result = kit.getGraph().unique(new ZeroExtendNode(result, kit.getWordTypes().getWordKind().getBitCount())); + + kit.exceptionPart(); + kit.append(new CEntryPointLeaveNode(LeaveAction.ExceptionAbort, kit.exceptionObject())); + kit.append(new LoweredDeadEndNode()); + kit.endInvokeWithException(); + return result; } private void generateEpilogue(HostedGraphKit kit) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointCallStubSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointCallStubSupport.java index 0ff44e1ea8ae..2f5bbc51cb32 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointCallStubSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointCallStubSupport.java @@ -40,18 +40,11 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.meta.MethodPointer; -import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; -import com.oracle.svm.hosted.c.NativeLibraries; import jdk.vm.ci.meta.ResolvedJavaType; public final class CEntryPointCallStubSupport { - - static void initialize(BigBang bb) { - ImageSingletons.add(CEntryPointCallStubSupport.class, new CEntryPointCallStubSupport(bb)); - } - public static CEntryPointCallStubSupport singleton() { return ImageSingletons.lookup(CEntryPointCallStubSupport.class); } @@ -59,7 +52,6 @@ public static CEntryPointCallStubSupport singleton() { private final BigBang bb; private final Map methodToStub = new ConcurrentHashMap<>(); private final Map methodToJavaStub = new ConcurrentHashMap<>(); - private NativeLibraries nativeLibraries; /** * Cache the BoxedRelocatedPointer objects to ensure that the same constant is seen during @@ -67,7 +59,7 @@ public static CEntryPointCallStubSupport singleton() { */ private final ConcurrentHashMap cFunctionPointerCache = new ConcurrentHashMap<>(); - private CEntryPointCallStubSupport(BigBang bb) { + CEntryPointCallStubSupport(BigBang bb) { this.bb = bb; } @@ -95,7 +87,7 @@ public AnalysisMethod registerStubForMethod(AnalysisMethod method, Supplier new BoxedRelocatedPointer(t)); + return cFunctionPointerCache.computeIfAbsent(cFunctionPointer, BoxedRelocatedPointer::new); } } @@ -139,12 +122,6 @@ class CEntryPointCallStubFeature implements InternalFeature { @Override public void duringSetup(DuringSetupAccess arg) { DuringSetupAccessImpl access = (DuringSetupAccessImpl) arg; - CEntryPointCallStubSupport.initialize(access.getBigBang()); - } - - @Override - public void beforeAnalysis(BeforeAnalysisAccess arg) { - BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) arg; - CEntryPointCallStubSupport.singleton().setNativeLibraries(access.getNativeLibraries()); + ImageSingletons.add(CEntryPointCallStubSupport.class, new CEntryPointCallStubSupport(access.getBigBang())); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointJavaCallStubMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointJavaCallStubMethod.java index 5db0aa2c2a72..2f7c9454e005 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointJavaCallStubMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/CEntryPointJavaCallStubMethod.java @@ -74,7 +74,6 @@ protected String getCorrespondingAnnotationName() { @Override protected ValueNode createTargetAddressNode(HostedGraphKit kit, List arguments) { - /* * We currently cannot handle {@link MethodPointer} as a constant in the code, so we use an * indirection with a non-final field load from an object of BoxedRelocatedPointer. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java index 5ad266cdcde2..8eaa2d42c410 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java @@ -770,7 +770,7 @@ private class NativeImageDebugForeignFieldInfo extends NativeImageDebugFileInfo @Override public int size() { - return structFieldInfo.getSizeInfo().getProperty(); + return structFieldInfo.getSizeInBytes(); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java index f644131b417c..005ae7f518cb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java @@ -335,13 +335,10 @@ protected static boolean fieldTypeIsEmbedded(StructFieldInfo field) { } protected static int elementSize(ElementInfo elementInfo) { - if (elementInfo == null || !(elementInfo instanceof SizableInfo)) { + if (!(elementInfo instanceof SizableInfo) || elementInfo instanceof StructInfo structInfo && structInfo.isIncomplete()) { return 0; } - if (elementInfo instanceof StructInfo && ((StructInfo) elementInfo).isIncomplete()) { - return 0; - } - Integer size = ((SizableInfo) elementInfo).getSizeInfo().getProperty(); + Integer size = ((SizableInfo) elementInfo).getSizeInBytes(); assert size != null; return size; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNILibraryLoadFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNILibraryLoadFeature.java index 88686b4d7cba..112b668273b9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNILibraryLoadFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNILibraryLoadFeature.java @@ -31,11 +31,10 @@ import com.oracle.svm.core.jdk.NativeLibrarySupport; import com.oracle.svm.core.jni.JNILibraryInitializer; import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.code.CEntryPointCallStubSupport; public class JNILibraryLoadFeature implements Feature { - private JNILibraryInitializer jniLibraryInitializer = new JNILibraryInitializer(); + private final JNILibraryInitializer jniLibraryInitializer = new JNILibraryInitializer(); @Override public void duringSetup(DuringSetupAccess access) { @@ -44,7 +43,7 @@ public void duringSetup(DuringSetupAccess access) { @Override public void duringAnalysis(DuringAnalysisAccess access) { - NativeLibraries nativeLibraries = CEntryPointCallStubSupport.singleton().getNativeLibraries(); + NativeLibraries nativeLibraries = NativeLibraries.singleton(); List staticLibNames = nativeLibraries.getJniStaticLibraries(); boolean isChanged = jniLibraryInitializer.fillCGlobalDataMap(staticLibNames); if (isChanged) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceEnumTool.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceEnumTool.java index a34798c9e094..426cceb807a0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceEnumTool.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceEnumTool.java @@ -26,12 +26,27 @@ import java.lang.reflect.Modifier; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.constant.CEnum; +import org.graalvm.nativeimage.c.constant.CEnumLookup; +import org.graalvm.nativeimage.c.constant.CEnumValue; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.word.PointerBase; +import org.graalvm.word.SignedWord; +import org.graalvm.word.UnsignedWord; + import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.svm.core.c.enums.EnumRuntimeData; +import com.oracle.svm.core.c.enums.CEnumRuntimeData; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.c.info.EnumInfo; +import com.oracle.svm.hosted.cenum.CEnumCallWrapperMethod; +import com.oracle.svm.hosted.code.CCallStubMethod; +import com.oracle.svm.hosted.code.CEntryPointCallStubMethod; import jdk.graal.compiler.core.common.type.ObjectStamp; import jdk.graal.compiler.core.common.type.StampFactory; @@ -43,14 +58,36 @@ import jdk.graal.compiler.nodes.InvokeWithExceptionNode; import jdk.graal.compiler.nodes.LogicNode; import jdk.graal.compiler.nodes.PiNode; +import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.calc.ZeroExtendNode; import jdk.graal.compiler.nodes.extended.BytecodeExceptionNode; import jdk.graal.compiler.nodes.extended.GuardingNode; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderTool; import jdk.graal.compiler.nodes.java.InstanceOfNode; import jdk.graal.compiler.nodes.java.MethodCallTargetNode; +import jdk.graal.compiler.word.WordTypes; import jdk.vm.ci.meta.JavaKind; +/** + * Implements some shared parts for converting Java enums (see {@link CEnum}) to primitive values + * and vice versa. This class is used in the following scenarios: + * + *
    + *
  • A {@link CEnum} is used in the signature of a {@link CEntryPoint}, see + * {@link CEntryPointCallStubMethod}.
  • + *
  • A {@link CEnum} is used in the signature of a C function call, see + * {@link CCallStubMethod}.
  • + *
  • A method is called that is annotated with {@link CEnumValue} or {@link CEnumLookup}, see + * {@link CEnumCallWrapperMethod}.
  • + * + * {@link CEnumValue} and {@link CEnumLookup} methods may use arbitrary primitive Java data types + * that are completely unrelated to the actual type of the C enum. However, note that the specified + * Java types only have an effect if those methods are called explicitly. When implicitly converting + * between C values and Java enums (e.g., for C call arguments and return types), we do not use the + * annotated methods. So, for implicit conversion, only the type of the C enum is relevant and the + * semantics are similar to reading/writing a C value. + */ public class CInterfaceEnumTool { interface CallTargetFactory { MethodCallTargetNode createMethodCallTarget(InvokeKind invokeKind, AnalysisMethod targetMethod, ValueNode[] args, StampPair returnStamp, int bci); @@ -64,51 +101,92 @@ static CallTargetFactory from(HostedGraphKit kit) { } } - private final AnalysisMethod convertJavaToCLongMethod; - private final AnalysisMethod convertJavaToCIntMethod; - private final AnalysisMethod convertCToJavaMethod; + private final AnalysisMethod enumToBooleanMethod; + private final AnalysisMethod enumToByteMethod; + private final AnalysisMethod enumToShortMethod; + private final AnalysisMethod enumToCharMethod; + private final AnalysisMethod enumToIntMethod; + private final AnalysisMethod enumToLongMethod; + private final AnalysisMethod enumToUnsignedWordMethod; + private final AnalysisMethod enumToSignedWordMethod; + + private final AnalysisMethod longToEnumMethod; - public CInterfaceEnumTool(AnalysisMetaAccess metaAccess) { + CInterfaceEnumTool(AnalysisMetaAccess metaAccess) { try { - convertJavaToCLongMethod = metaAccess.lookupJavaMethod(EnumRuntimeData.class.getDeclaredMethod("convertJavaToCLong", Enum.class)); - convertJavaToCIntMethod = metaAccess.lookupJavaMethod(EnumRuntimeData.class.getDeclaredMethod("convertJavaToCInt", Enum.class)); - convertCToJavaMethod = metaAccess.lookupJavaMethod(EnumRuntimeData.class.getDeclaredMethod("convertCToJava", long.class)); + enumToBooleanMethod = metaAccess.lookupJavaMethod(CEnumRuntimeData.class.getDeclaredMethod("enumToBoolean", Enum.class)); + enumToByteMethod = metaAccess.lookupJavaMethod(CEnumRuntimeData.class.getDeclaredMethod("enumToByte", Enum.class)); + enumToShortMethod = metaAccess.lookupJavaMethod(CEnumRuntimeData.class.getDeclaredMethod("enumToShort", Enum.class)); + enumToCharMethod = metaAccess.lookupJavaMethod(CEnumRuntimeData.class.getDeclaredMethod("enumToChar", Enum.class)); + enumToIntMethod = metaAccess.lookupJavaMethod(CEnumRuntimeData.class.getDeclaredMethod("enumToInt", Enum.class)); + enumToLongMethod = metaAccess.lookupJavaMethod(CEnumRuntimeData.class.getDeclaredMethod("enumToLong", Enum.class)); + enumToSignedWordMethod = metaAccess.lookupJavaMethod(CEnumRuntimeData.class.getDeclaredMethod("enumToSignedWord", Enum.class)); + enumToUnsignedWordMethod = metaAccess.lookupJavaMethod(CEnumRuntimeData.class.getDeclaredMethod("enumToUnsignedWord", Enum.class)); + + longToEnumMethod = metaAccess.lookupJavaMethod(CEnumRuntimeData.class.getDeclaredMethod("longToEnum", long.class)); } catch (NoSuchMethodException e) { throw VMError.shouldNotReachHere(e); } } - private AnalysisMethod getValueMethodForKind(JavaKind kind) { - return (kind == JavaKind.Long) ? convertJavaToCLongMethod : convertJavaToCIntMethod; + public static CInterfaceEnumTool singleton() { + return ImageSingletons.lookup(CInterfaceEnumTool.class); } - public ValueNode createEnumValueInvoke(HostedGraphKit kit, EnumInfo enumInfo, JavaKind resultKind, ValueNode arg) { + public ValueNode createInvokeEnumToValue(HostedGraphKit kit, EnumInfo enumInfo, AnalysisType returnType, ValueNode arg) { int invokeBci = kit.bci(); - MethodCallTargetNode callTarget = invokeEnumValue(kit, CallTargetFactory.from(kit), invokeBci, enumInfo, resultKind, arg); + MethodCallTargetNode callTarget = createInvokeEnumToValue(kit, CallTargetFactory.from(kit), invokeBci, enumInfo, returnType, arg); return kit.createInvokeWithExceptionAndUnwind(callTarget, kit.getFrameState(), invokeBci); } - public ValueNode startEnumValueInvokeWithException(HostedGraphKit kit, EnumInfo enumInfo, JavaKind resultKind, ValueNode arg) { + public ValueNode startInvokeWithExceptionEnumToValue(HostedGraphKit kit, EnumInfo enumInfo, AnalysisType returnType, ValueNode arg) { int invokeBci = kit.bci(); - MethodCallTargetNode callTarget = invokeEnumValue(kit, CallTargetFactory.from(kit), invokeBci, enumInfo, resultKind, arg); + MethodCallTargetNode callTarget = createInvokeEnumToValue(kit, CallTargetFactory.from(kit), invokeBci, enumInfo, returnType, arg); return kit.startInvokeWithException(callTarget, kit.getFrameState(), invokeBci); } - private MethodCallTargetNode invokeEnumValue(GraphBuilderTool b, CallTargetFactory callTargetFactory, int bci, EnumInfo enumInfo, JavaKind resultKind, ValueNode arg) { - AnalysisMethod valueMethod = getValueMethodForKind(resultKind); - AnalysisType returnType = valueMethod.getSignature().getReturnType(); + private MethodCallTargetNode createInvokeEnumToValue(GraphBuilderTool b, CallTargetFactory callTargetFactory, int bci, EnumInfo enumInfo, AnalysisType returnType, ValueNode arg) { + AnalysisMethod method = getEnumToValueMethod(b, returnType); + assert !Modifier.isStatic(method.getModifiers()) && method.getSignature().getParameterCount(true) == 2; + ValueNode[] args = new ValueNode[2]; args[0] = ConstantNode.forConstant(b.getSnippetReflection().forObject(enumInfo.getRuntimeData()), b.getMetaAccess(), b.getGraph()); args[1] = arg; - StampPair returnStamp = StampFactory.forDeclaredType(null, returnType, false); - return b.append(callTargetFactory.createMethodCallTarget(InvokeKind.Virtual, valueMethod, args, returnStamp, bci)); + WordTypes wordTypes = b.getWordTypes(); + StampPair returnStamp = wordTypes.isWord(returnType) ? StampPair.createSingle(wordTypes.getWordStamp(returnType)) : StampFactory.forDeclaredType(null, returnType, false); + return b.append(callTargetFactory.createMethodCallTarget(InvokeKind.Special, method, args, returnStamp, bci)); + } + + private AnalysisMethod getEnumToValueMethod(GraphBuilderTool b, AnalysisType returnType) { + if (b.getWordTypes().isWord(returnType)) { + assert !b.getMetaAccess().lookupJavaType(PointerBase.class).isAssignableFrom(returnType) : "only integer types are allowed"; + + if (b.getMetaAccess().lookupJavaType(UnsignedWord.class).isAssignableFrom(returnType)) { + return enumToUnsignedWordMethod; + } else if (b.getMetaAccess().lookupJavaType(SignedWord.class).isAssignableFrom(returnType)) { + return enumToSignedWordMethod; + } else { + throw VMError.shouldNotReachHere("Unexpected return type: " + returnType); + } + } + + JavaKind returnKind = returnType.getJavaKind(); + return switch (returnKind) { + case Boolean -> enumToBooleanMethod; + case Byte -> enumToByteMethod; + case Short -> enumToShortMethod; + case Char -> enumToCharMethod; + case Int -> enumToIntMethod; + case Long -> enumToLongMethod; + default -> throw VMError.shouldNotReachHere("Unexpected return kind: " + returnKind); + }; } - public ValueNode createEnumLookupInvoke(HostedGraphKit kit, AnalysisType enumType, EnumInfo enumInfo, JavaKind parameterKind, ValueNode arg) { - // Create the invoke to the actual target method: EnumRuntimeData.convertCToJava + public ValueNode createInvokeLookupEnum(HostedGraphKit kit, AnalysisType enumType, EnumInfo enumInfo, ValueNode arg) { + // Create the invocation of the actual target method: EnumRuntimeData.convertCToJava int invokeBci = kit.bci(); - MethodCallTargetNode callTarget = invokeEnumLookup(kit, CallTargetFactory.from(kit), invokeBci, enumInfo, parameterKind, arg); + MethodCallTargetNode callTarget = createInvokeLongToEnum(kit, CallTargetFactory.from(kit), invokeBci, enumInfo, arg); InvokeWithExceptionNode invoke = kit.createInvokeWithExceptionAndUnwind(callTarget, kit.getFrameState(), invokeBci); // Create the instanceof guard to narrow the return type for the analysis @@ -121,15 +199,46 @@ public ValueNode createEnumLookupInvoke(HostedGraphKit kit, AnalysisType enumTyp return kit.unique(new PiNode(invoke, resultStamp, guard.asNode())); } - private MethodCallTargetNode invokeEnumLookup(GraphBuilderTool b, CallTargetFactory callTargetFactory, int bci, EnumInfo enumInfo, JavaKind parameterKind, ValueNode arg) { + private MethodCallTargetNode createInvokeLongToEnum(GraphBuilderTool b, CallTargetFactory callTargetFactory, int bci, EnumInfo enumInfo, ValueNode arg) { + assert !Modifier.isStatic(longToEnumMethod.getModifiers()) && longToEnumMethod.getSignature().getParameterCount(false) == 1; + + StructuredGraph graph = b.getGraph(); ValueNode[] args = new ValueNode[2]; - args[0] = ConstantNode.forConstant(b.getSnippetReflection().forObject(enumInfo.getRuntimeData()), b.getMetaAccess(), b.getGraph()); - assert !Modifier.isStatic(convertCToJavaMethod.getModifiers()) && convertCToJavaMethod.getSignature().getParameterCount(false) == 1; - JavaKind expectedKind = convertCToJavaMethod.getSignature().getParameterType(0).getJavaKind(); - args[1] = CInterfaceInvocationPlugin.adaptPrimitiveType(b.getGraph(), arg, parameterKind, expectedKind, false); + args[0] = ConstantNode.forConstant(b.getSnippetReflection().forObject(enumInfo.getRuntimeData()), b.getMetaAccess(), graph); + /* The exact size and signedness only matters at a later point. */ + args[1] = graph.unique(new ZeroExtendNode(arg, 64)); + + AnalysisType enumReturnType = longToEnumMethod.getSignature().getReturnType(); + StampPair returnStamp = StampFactory.forDeclaredType(null, enumReturnType, false); + return b.append(callTargetFactory.createMethodCallTarget(InvokeKind.Special, longToEnumMethod, args, returnStamp, bci)); + } + + public static boolean isPrimitiveOrWord(AnalysisType type) { + return type.getStorageKind().isPrimitive(); + } + + /** + * For method signatures, only the size (but not the signedness) matters for {@link CEnum} + * values. So, when we convert {@link CEnum}s in the signature to primitive types, we always use + * signed primitive Java data types that have the same size as the enum data type in C. + */ + public static AnalysisType getCEnumValueType(EnumInfo enumInfo, AnalysisMetaAccess metaAccess) { + int sizeInBytes = enumInfo.getSizeInBytes(); + return switch (sizeInBytes) { + case 1 -> metaAccess.lookupJavaType(byte.class); + case 2 -> metaAccess.lookupJavaType(short.class); + case 4 -> metaAccess.lookupJavaType(int.class); + case 8 -> metaAccess.lookupJavaType(long.class); + default -> throw VMError.shouldNotReachHere("CEnum has unexpected size: " + sizeInBytes); + }; + } +} - AnalysisType convertReturnType = convertCToJavaMethod.getSignature().getReturnType(); - StampPair returnStamp = StampFactory.forDeclaredType(null, convertReturnType, false); - return b.append(callTargetFactory.createMethodCallTarget(InvokeKind.Virtual, convertCToJavaMethod, args, returnStamp, bci)); +@AutomaticallyRegisteredFeature +class CInterfaceEnumToolFeature implements InternalFeature { + @Override + public void beforeAnalysis(BeforeAnalysisAccess a) { + BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) a; + ImageSingletons.add(CInterfaceEnumTool.class, new CInterfaceEnumTool(access.getMetaAccess())); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceInvocationPlugin.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceInvocationPlugin.java index 3e28179b9d8c..ac0ab43d48a7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceInvocationPlugin.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/CInterfaceInvocationPlugin.java @@ -34,6 +34,8 @@ import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; import org.graalvm.word.LocationIdentity; +import org.graalvm.word.WordBase; +import org.graalvm.word.impl.WordBoxFactory; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; @@ -44,6 +46,7 @@ import com.oracle.svm.core.graal.nodes.CInterfaceReadNode; import com.oracle.svm.core.graal.nodes.CInterfaceWriteNode; import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.c.CInterfaceError; import com.oracle.svm.hosted.c.CInterfaceWrapper; import com.oracle.svm.hosted.c.NativeLibraries; @@ -60,7 +63,6 @@ import com.oracle.svm.hosted.code.CEntryPointJavaCallStubMethod; import com.oracle.svm.hosted.code.CFunctionPointerCallStubSupport; -import jdk.graal.compiler.core.common.calc.FloatConvert; import jdk.graal.compiler.core.common.memory.BarrierType; import jdk.graal.compiler.core.common.memory.MemoryOrderMode; import jdk.graal.compiler.core.common.type.IntegerStamp; @@ -72,12 +74,12 @@ import jdk.graal.compiler.nodes.ConstantNode; import jdk.graal.compiler.nodes.IndirectCallTargetNode; import jdk.graal.compiler.nodes.LogicNode; +import jdk.graal.compiler.nodes.NodeView; import jdk.graal.compiler.nodes.StructuredGraph; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.calc.AddNode; import jdk.graal.compiler.nodes.calc.AndNode; import jdk.graal.compiler.nodes.calc.ConditionalNode; -import jdk.graal.compiler.nodes.calc.FloatConvertNode; import jdk.graal.compiler.nodes.calc.IntegerEqualsNode; import jdk.graal.compiler.nodes.calc.LeftShiftNode; import jdk.graal.compiler.nodes.calc.MulNode; @@ -85,12 +87,14 @@ import jdk.graal.compiler.nodes.calc.OrNode; import jdk.graal.compiler.nodes.calc.RightShiftNode; import jdk.graal.compiler.nodes.calc.SignExtendNode; +import jdk.graal.compiler.nodes.calc.UnsignedRightShiftNode; import jdk.graal.compiler.nodes.calc.ZeroExtendNode; import jdk.graal.compiler.nodes.extended.JavaReadNode; import jdk.graal.compiler.nodes.extended.JavaWriteNode; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin; import jdk.graal.compiler.nodes.memory.address.OffsetAddressNode; +import jdk.vm.ci.code.CodeUtil; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.MetaAccessProvider; @@ -156,77 +160,86 @@ private static boolean replaceOffsetOf(GraphBuilderContext b, AnalysisMethod met return true; } - private static boolean replaceAccessor(GraphBuilderContext b, AnalysisMethod method, ValueNode[] args, AccessorInfo accessorInfo, int displacement) { - StructuredGraph graph = b.getGraph(); - SizableInfo sizableInfo = (SizableInfo) accessorInfo.getParent(); - int elementSize = sizableInfo.getSizeInfo().getProperty(); - boolean isUnsigned = sizableInfo.isUnsigned(); - boolean isPinnedObject = sizableInfo.isObject(); - + private boolean replaceAccessor(GraphBuilderContext b, AnalysisMethod method, ValueNode[] args, AccessorInfo accessorInfo, int displacement) { assert args.length == accessorInfo.parameterCount(true); + SizableInfo typeInC = (SizableInfo) accessorInfo.getParent(); ValueNode base = args[AccessorInfo.baseParameterNumber(true)]; assert base.getStackKind() == ConfigurationValues.getWordKind(); switch (accessorInfo.getAccessorKind()) { - case ADDRESS: { - ValueNode address = graph.addOrUniqueWithInputs(new AddNode(base, makeOffset(graph, args, accessorInfo, displacement, elementSize))); - b.addPush(pushKind(method), address); - return true; - } - case GETTER: { - JavaKind resultKind = b.getWordTypes().asKind(b.getInvokeReturnType()); - JavaKind readKind = kindFromSize(elementSize, resultKind); - if (readKind == JavaKind.Object) { - assert resultKind == JavaKind.Object; - } else if (readKind.getBitCount() > resultKind.getBitCount() && !readKind.isNumericFloat() && resultKind != JavaKind.Boolean) { - readKind = resultKind; - } - OffsetAddressNode offsetAddress = makeOffsetAddress(graph, args, accessorInfo, base, displacement, elementSize); - LocationIdentity locationIdentity = makeLocationIdentity(b, method, args, accessorInfo); - final Stamp stamp; - if (readKind == JavaKind.Object) { - stamp = b.getInvokeReturnStamp(null).getTrustedStamp(); - } else if (readKind == JavaKind.Float || readKind == JavaKind.Double) { - stamp = StampFactory.forKind(readKind); - } else { - stamp = IntegerStamp.create(readKind.getBitCount()); - } - final ValueNode node; - if (isPinnedObject) { - node = b.add(new JavaReadNode(stamp, readKind, offsetAddress, locationIdentity, BarrierType.NONE, MemoryOrderMode.PLAIN, true)); - } else { - ValueNode read = readPrimitive(b, offsetAddress, locationIdentity, stamp, accessorInfo, method); - node = adaptPrimitiveType(graph, read, readKind, resultKind == JavaKind.Boolean ? resultKind : resultKind.getStackKind(), isUnsigned); - } - b.push(pushKind(method), node); - return true; - } - case SETTER: { - ValueNode value = args[accessorInfo.valueParameterNumber(true)]; - JavaKind valueKind = value.getStackKind(); - JavaKind writeKind = kindFromSize(elementSize, valueKind); - OffsetAddressNode offsetAddress = makeOffsetAddress(graph, args, accessorInfo, base, displacement, elementSize); - LocationIdentity locationIdentity = makeLocationIdentity(b, method, args, accessorInfo); - if (isPinnedObject) { - b.add(new JavaWriteNode(writeKind, offsetAddress, locationIdentity, value, BarrierType.NONE, true)); - } else { - ValueNode adaptedValue = adaptPrimitiveType(graph, value, valueKind, writeKind, isUnsigned); - writePrimitive(b, offsetAddress, locationIdentity, adaptedValue, accessorInfo, method); - } - return true; - } - default: - throw shouldNotReachHereUnexpectedInput(accessorInfo.getAccessorKind()); // ExcludeFromJacocoGeneratedReport + case ADDRESS -> replaceWithAddress(b, method, base, args, accessorInfo, displacement, typeInC); + case GETTER -> replaceWithRead(b, method, base, args, accessorInfo, displacement, typeInC); + case SETTER -> replaceWithWrite(b, method, base, args, accessorInfo, displacement, typeInC); + default -> throw shouldNotReachHereUnexpectedInput(accessorInfo.getAccessorKind()); // ExcludeFromJacocoGeneratedReport + } + return true; + } + + private static void replaceWithAddress(GraphBuilderContext b, AnalysisMethod method, ValueNode base, ValueNode[] args, AccessorInfo accessorInfo, int displacement, SizableInfo typeInC) { + StructuredGraph graph = b.getGraph(); + ValueNode address = graph.addOrUniqueWithInputs(new AddNode(base, makeOffset(graph, args, accessorInfo, displacement, typeInC.getSizeInBytes()))); + b.addPush(pushKind(method), address); + } + + private void replaceWithRead(GraphBuilderContext b, AnalysisMethod method, ValueNode base, ValueNode[] args, AccessorInfo accessorInfo, int displacement, SizableInfo typeInC) { + StructuredGraph graph = b.getGraph(); + OffsetAddressNode offsetAddress = makeOffsetAddress(graph, args, accessorInfo, base, displacement, typeInC.getSizeInBytes()); + LocationIdentity locationIdentity = makeLocationIdentity(b, method, args, accessorInfo); + + ValueNode node; + JavaKind returnKind = b.getWordTypes().asKind(b.getInvokeReturnType()); + if (returnKind == JavaKind.Object) { + Stamp stamp = b.getInvokeReturnStamp(null).getTrustedStamp(); + node = b.add(new JavaReadNode(stamp, returnKind, offsetAddress, locationIdentity, BarrierType.NONE, MemoryOrderMode.PLAIN, true)); + } else if (returnKind == JavaKind.Float || returnKind == JavaKind.Double) { + Stamp stamp = StampFactory.forKind(returnKind); + node = readPrimitive(b, offsetAddress, locationIdentity, stamp, accessorInfo, method); + } else { + IntegerStamp stamp = IntegerStamp.create(typeInC.getSizeInBytes() * Byte.SIZE); + ValueNode read = readPrimitive(b, offsetAddress, locationIdentity, stamp, accessorInfo, method); + node = convertCIntegerToMethodReturnType(graph, method, read, typeInC.isUnsigned()); + } + b.push(pushKind(method), node); + } + + private static void replaceWithWrite(GraphBuilderContext b, AnalysisMethod method, ValueNode base, ValueNode[] args, AccessorInfo accessorInfo, int displacement, SizableInfo typeInC) { + StructuredGraph graph = b.getGraph(); + OffsetAddressNode offsetAddress = makeOffsetAddress(graph, args, accessorInfo, base, displacement, typeInC.getSizeInBytes()); + LocationIdentity locationIdentity = makeLocationIdentity(b, method, args, accessorInfo); + + ValueNode value = args[accessorInfo.valueParameterNumber(true)]; + JavaKind valueKind = value.getStackKind(); + if (valueKind == JavaKind.Object) { + b.add(new JavaWriteNode(valueKind, offsetAddress, locationIdentity, value, BarrierType.NONE, true)); + } else if (valueKind == JavaKind.Float || valueKind == JavaKind.Double) { + writePrimitive(b, offsetAddress, locationIdentity, value, accessorInfo, method); + } else { + ValueNode adaptedValue = adaptPrimitiveTypeForWrite(graph, value, typeInC.getSizeInBytes() * Byte.SIZE, typeInC.isUnsigned()); + writePrimitive(b, offsetAddress, locationIdentity, adaptedValue, accessorInfo, method); } } - private static boolean replaceBitfieldAccessor(GraphBuilderContext b, AnalysisMethod method, ValueNode[] args, StructBitfieldInfo bitfieldInfo, AccessorInfo accessorInfo) { + private static ValueNode adaptPrimitiveTypeForWrite(StructuredGraph graph, ValueNode value, int toBits, boolean isCValueUnsigned) { + JavaKind valueKind = value.getStackKind(); + int valueBits = valueKind.getBitCount(); + if (valueBits == toBits) { + return value; + } else if (valueBits > toBits) { + return graph.unique(new NarrowNode(value, toBits)); + } else if (isCValueUnsigned) { + return graph.unique(new ZeroExtendNode(value, toBits)); + } else { + return graph.unique(new SignExtendNode(value, toBits)); + } + } + + private boolean replaceBitfieldAccessor(GraphBuilderContext b, AnalysisMethod method, ValueNode[] args, StructBitfieldInfo bitfieldInfo, AccessorInfo accessorInfo) { int byteOffset = bitfieldInfo.getByteOffsetInfo().getProperty(); int startBit = bitfieldInfo.getStartBitInfo().getProperty(); int endBit = bitfieldInfo.getEndBitInfo().getProperty(); - boolean isUnsigned = bitfieldInfo.isUnsigned(); - assert byteOffset >= 0 && byteOffset < ((SizableInfo) bitfieldInfo.getParent()).getSizeInfo().getProperty(); + boolean isCValueUnsigned = bitfieldInfo.isUnsigned(); + assert byteOffset >= 0 && byteOffset < ((SizableInfo) bitfieldInfo.getParent()).getSizeInBytes(); assert startBit >= 0 && startBit < 8; assert endBit >= startBit && endBit < 64; @@ -234,110 +247,121 @@ private static boolean replaceBitfieldAccessor(GraphBuilderContext b, AnalysisMe * The startBit is always in the first byte. Therefore, the endBit tells us how many bytes * we actually have to read and write. */ - JavaKind memoryKind; + JavaKind readKind; if (endBit < 8) { - memoryKind = JavaKind.Byte; + readKind = JavaKind.Byte; } else if (endBit < 16) { - memoryKind = JavaKind.Short; + readKind = JavaKind.Short; } else if (endBit < 32) { - memoryKind = JavaKind.Int; + readKind = JavaKind.Int; } else { - memoryKind = JavaKind.Long; + readKind = JavaKind.Long; } - int numBytes = memoryKind.getByteCount(); /* - * Try to align the byteOffset to be a multiple of numBytes. That should always be possible, - * but we don't trust the C compiler and memory layout enough to make it an assertion. + * Try to align the byteOffset to be a multiple of readBytes. That should always be + * possible, but we don't trust the C compiler and memory layout enough to make it an + * assertion. */ - int alignmentCorrection = byteOffset % numBytes; - if (alignmentCorrection > 0 && endBit + alignmentCorrection * 8 < numBytes * 8) { + int readBytes = readKind.getByteCount(); + int alignmentCorrection = byteOffset % readBytes; + if (alignmentCorrection > 0 && endBit + alignmentCorrection * Byte.SIZE < readKind.getBitCount()) { byteOffset -= alignmentCorrection; - startBit += alignmentCorrection * 8; - endBit += alignmentCorrection * 8; + startBit += alignmentCorrection * Byte.SIZE; + endBit += alignmentCorrection * Byte.SIZE; } - assert byteOffset >= 0 && byteOffset < ((SizableInfo) bitfieldInfo.getParent()).getSizeInfo().getProperty(); - assert startBit >= 0 && startBit < numBytes * 8; - assert endBit >= startBit && endBit < numBytes * 8; - - int numBits = endBit - startBit + 1; - assert numBits > 0 && numBits <= numBytes * 8; + assert byteOffset >= 0 && byteOffset < ((SizableInfo) bitfieldInfo.getParent()).getSizeInBytes(); + assert startBit >= 0 && startBit < readKind.getBitCount(); + assert endBit >= startBit && endBit < readKind.getBitCount(); /* - * The bit-operations on the value are either performed on Int or Long. We do not perform 8 - * or 16 bit arithmetic operations. + * Read the C memory. This is also necessary for writes, since we need to keep the bits + * around the written bitfield unchanged. */ - JavaKind computeKind = memoryKind.getStackKind(); - Stamp computeStamp = StampFactory.forKind(computeKind); - int computeBits = computeKind.getBitCount(); - assert startBit >= 0 && startBit < computeBits; - assert endBit >= startBit && endBit < computeBits; - assert computeBits >= numBits; - assert args.length == accessorInfo.parameterCount(true); ValueNode base = args[AccessorInfo.baseParameterNumber(true)]; StructuredGraph graph = b.getGraph(); - /* - * Read the memory location. This is also necessary for writes, since we need to keep the - * bits around the written bitfield unchanged. - */ OffsetAddressNode address = makeOffsetAddress(graph, args, accessorInfo, base, byteOffset, -1); LocationIdentity locationIdentity = makeLocationIdentity(b, method, args, accessorInfo); - Stamp stamp = IntegerStamp.create(memoryKind.getBitCount()); - ValueNode cur = readPrimitive(b, address, locationIdentity, stamp, accessorInfo, method); - cur = adaptPrimitiveType(graph, cur, memoryKind, computeKind, true); + Stamp stamp = IntegerStamp.create(readKind.getBitCount()); + ValueNode cValue = readPrimitive(b, address, locationIdentity, stamp, accessorInfo, method); + + /* Arithmetic operations are always performed on 32 or 64-bit. */ + JavaKind computeKind = readKind.getStackKind(); + int computeBits = computeKind.getBitCount(); + assert startBit >= 0 && startBit < computeBits; + assert endBit >= startBit && endBit < computeBits; + + int accessedBits = endBit - startBit + 1; + assert accessedBits > 0 && accessedBits <= readKind.getBitCount() && accessedBits <= 64; + assert computeBits >= accessedBits; + + /* Zero-extend the C value if it is less than 32-bit so that we can do arithmetics. */ + if (readKind.getBitCount() < 32) { + cValue = graph.unique(new ZeroExtendNode(cValue, computeBits)); + } switch (accessorInfo.getAccessorKind()) { - case GETTER: { - if (isUnsigned) { - /* - * Unsigned reads: shift the bitfield to the right and mask out the unnecessary - * high-order bits. - */ - cur = graph.unique(new RightShiftNode(cur, ConstantNode.forInt(startBit, graph))); - cur = graph.unique(new AndNode(cur, ConstantNode.forIntegerStamp(computeStamp, (1L << numBits) - 1, graph))); + case GETTER -> { + /* Reduce the C value to the bits that we really wanted to read. */ + cValue = graph.unique(new LeftShiftNode(cValue, ConstantNode.forInt(computeBits - endBit - 1, graph))); + if (isCValueUnsigned) { + cValue = graph.unique(new UnsignedRightShiftNode(cValue, ConstantNode.forInt(computeBits - accessedBits, graph))); } else { - /* - * Signed reads: shift the bitfield to the right end to get the sign bit in - * place, then do a signed left shift to have a proper sign extension. - */ - cur = graph.unique(new LeftShiftNode(cur, ConstantNode.forInt(computeBits - endBit - 1, graph))); - cur = graph.unique(new RightShiftNode(cur, ConstantNode.forInt(computeBits - numBits, graph))); + cValue = graph.unique(new RightShiftNode(cValue, ConstantNode.forInt(computeBits - accessedBits, graph))); } - JavaKind resultKind = b.getWordTypes().asKind(b.getInvokeReturnType()); - b.push(pushKind(method), adaptPrimitiveType(graph, cur, computeKind, resultKind == JavaKind.Boolean ? resultKind : resultKind.getStackKind(), isUnsigned)); - return true; - } - case SETTER: { - /* Zero out the bits of our bitfields, i.e., the bits we are going to change. */ - long mask = ~(((1L << numBits) - 1) << startBit); - cur = graph.unique(new AndNode(cur, ConstantNode.forIntegerStamp(computeStamp, mask, graph))); + /* Narrow the value so that its size matches the size of a C data type. */ + int targetBits = 1 << CodeUtil.log2(accessedBits); + if (targetBits < accessedBits) { + targetBits = targetBits * 2; + } + if (targetBits < 8) { + targetBits = 8; + } + + if (computeBits > targetBits) { + cValue = graph.unique(new NarrowNode(cValue, targetBits)); + } /* - * Mask the unnecessary high-order bits of the value to be written, and shift it to - * its place. + * Now, the value looks as if we actually read targetBits from C memory. So, we can + * finally convert this value to the method's declared return type. */ - ValueNode value = args[accessorInfo.valueParameterNumber(true)]; - value = adaptPrimitiveType(graph, value, value.getStackKind(), computeKind, isUnsigned); - value = graph.unique(new AndNode(value, ConstantNode.forIntegerStamp(computeStamp, (1L << numBits) - 1, graph))); - value = graph.unique(new LeftShiftNode(value, ConstantNode.forInt(startBit, graph))); - - /* Combine the leftover bits of the original memory word with the new value. */ - cur = graph.unique(new OrNode(cur, value)); - - /* Narrow value to the number of bits we need to write. */ - cur = adaptPrimitiveType(graph, cur, computeKind, memoryKind, true); - /* Perform the write (bitcount is taken from the stamp of the written value). */ - writePrimitive(b, address, locationIdentity, cur, accessorInfo, method); + ValueNode result = convertCIntegerToMethodReturnType(graph, method, cValue, isCValueUnsigned); + b.push(pushKind(method), result); + return true; + } + case SETTER -> { + /* Convert the new value to at least 32-bit so that we can do arithmetics. */ + ValueNode newValue = args[accessorInfo.valueParameterNumber(true)]; + newValue = adaptPrimitiveTypeForWrite(graph, newValue, computeBits, isCValueUnsigned); + + if (accessedBits == 64) { + /* The new value replaces all 64 bits of the C memory. */ + assert startBit == 0; + cValue = newValue; + } else { + /* Reduce the new value to the bits that we want and shift the bits in place. */ + newValue = graph.unique(new LeftShiftNode(newValue, ConstantNode.forInt(computeBits - accessedBits, graph))); + newValue = graph.unique(new UnsignedRightShiftNode(newValue, ConstantNode.forInt(computeBits - accessedBits - startBit, graph))); + + /* Replace the bits in the old value with the bits from the new value. */ + long mask = ~(((1L << accessedBits) - 1) << startBit); + cValue = graph.unique(new AndNode(cValue, ConstantNode.forIntegerBits(computeBits, mask, graph))); + cValue = graph.unique(new OrNode(cValue, newValue)); + } + + /* Narrow the value to the bytes that we need and write those to C memory. */ + cValue = adaptPrimitiveTypeForWrite(graph, cValue, readKind.getBitCount(), isCValueUnsigned); + writePrimitive(b, address, locationIdentity, cValue, accessorInfo, method); return true; } - default: - throw shouldNotReachHereUnexpectedInput(accessorInfo.getAccessorKind()); // ExcludeFromJacocoGeneratedReport + default -> throw shouldNotReachHereUnexpectedInput(accessorInfo.getAccessorKind()); // ExcludeFromJacocoGeneratedReport } } - private static ValueNode readPrimitive(GraphBuilderContext b, OffsetAddressNode address, LocationIdentity locationIdentity, Stamp stamp, AccessorInfo accessorInfo, ResolvedJavaMethod method) { + private static ValueNode readPrimitive(GraphBuilderContext b, OffsetAddressNode address, LocationIdentity locationIdentity, Stamp stamp, AccessorInfo accessorInfo, AnalysisMethod method) { if (ImageSingletons.contains(CInterfaceWrapper.class)) { ValueNode replaced = ImageSingletons.lookup(CInterfaceWrapper.class).replacePrimitiveRead(b, address, stamp, method); if (replaced != null) { @@ -354,7 +378,7 @@ private static ValueNode readPrimitive(GraphBuilderContext b, OffsetAddressNode return read; } - private static void writePrimitive(GraphBuilderContext b, OffsetAddressNode address, LocationIdentity locationIdentity, ValueNode value, AccessorInfo accessorInfo, ResolvedJavaMethod method) { + private static void writePrimitive(GraphBuilderContext b, OffsetAddressNode address, LocationIdentity locationIdentity, ValueNode value, AccessorInfo accessorInfo, AnalysisMethod method) { if (ImageSingletons.contains(CInterfaceWrapper.class)) { boolean replaced = ImageSingletons.lookup(CInterfaceWrapper.class).replacePrimitiveWrite(b, address, value, method); if (replaced) { @@ -379,7 +403,7 @@ private static ValueNode makeOffset(StructuredGraph graph, ValueNode[] args, Acc if (accessorInfo.isIndexed()) { ValueNode index = args[accessorInfo.indexParameterNumber(true)]; assert index.getStackKind().isPrimitive(); - ValueNode wordIndex = adaptPrimitiveType(graph, index, index.getStackKind(), ConfigurationValues.getWordKind(), false); + ValueNode wordIndex = extend(graph, index, ConfigurationValues.getWordKind().getBitCount(), false); ValueNode scaledIndex = graph.unique(new MulNode(wordIndex, ConstantNode.forIntegerKind(ConfigurationValues.getWordKind(), indexScaling, graph))); offset = graph.unique(new AddNode(scaledIndex, offset)); @@ -412,92 +436,243 @@ private static LocationIdentity makeLocationIdentity(GraphBuilderContext b, Anal return locationIdentity; } - public static ValueNode adaptPrimitiveType(StructuredGraph graph, ValueNode value, JavaKind fromKind, JavaKind toKind, boolean isUnsigned) { - if (fromKind == toKind) { - return value; - } - assert fromKind.isNumericFloat() == toKind.isNumericFloat(); - - int fromBits = fromKind.getBitCount(); - int toBits = toKind.getBitCount(); + private ValueNode convertCIntegerToMethodReturnType(StructuredGraph graph, AnalysisMethod method, ValueNode cValue, boolean isCValueUnsigned) { + return convertCIntegerToMethodReturnType(graph, nativeLibs, method.getSignature().getReturnType(), cValue, isCValueUnsigned); + } - if (fromBits == toBits) { - return value; - } else if (fromKind.isNumericFloat()) { - FloatConvert op; - if (fromKind == JavaKind.Float && toKind == JavaKind.Double) { - op = FloatConvert.F2D; - } else if (fromKind == JavaKind.Double && toKind == JavaKind.Float) { - op = FloatConvert.D2F; - } else { - throw shouldNotReachHereUnexpectedInput(fromKind); // ExcludeFromJacocoGeneratedReport + /** + * Converts a C value to a value that can be returned by a Java method (i.e., a 32- or 64-bit + * value). Does zero- or sign-extend the value if necessary. + * + *
    +     * -----------------------------------------------------------------------------------------
    +     * | C type          | declared return type | result                                       |
    +     * -----------------------------------------------------------------------------------------
    +     * | signed 8-bit    | boolean              | 1 byte read, convert to 0 or 1 (32-bit)      |
    +     * | signed 8-bit    | byte                 | 1 byte read, sign extend to 32-bit           |
    +     * | signed 8-bit    | short                | 1 byte read, sign extend to 32-bit           |
    +     * | signed 8-bit    | char                 | 1 byte read, zero extend to 32-bit           |
    +     * | signed 8-bit    | int                  | 1 byte read, sign extend to 32-bit           |
    +     * | signed 8-bit    | long                 | 1 byte read, sign extend to 64-bit           |
    +     * | signed 8-bit    | UnsignedWord         | 1 byte read, zero extend to 64-bit           |
    +     * |                                                                                       |
    +     * | unsigned 8-bit  | boolean              | 1 byte read, convert to 0 or 1 (32-bit)      |
    +     * | unsigned 8-bit  | byte                 | 1 byte read, sign extend to 32-bit           |
    +     * | unsigned 8-bit  | short                | 1 byte read, zero extend to 32-bit           |
    +     * | unsigned 8-bit  | char                 | 1 byte read, zero extend to 32-bit           |
    +     * | unsigned 8-bit  | int                  | 1 byte read, zero extend to 32-bit           |
    +     * | unsigned 8-bit  | long                 | 1 byte read, zero extend to 64-bit           |
    +     * | unsigned 8-bit  | UnsignedWord         | 1 byte read, zero extend to 64-bit           |
    +     * |                                                                                       |
    +     * | signed 32-bit   | boolean              | 4 byte read, convert to 0 or 1 (32-bit)      |
    +     * | signed 32-bit   | byte                 | 1 byte read, sign extend to 32-bit           |
    +     * | signed 32-bit   | short                | 2 byte read, sign extend to 32-bit           |
    +     * | signed 32-bit   | char                 | 2 byte read, zero extend to 32-bit           |
    +     * | signed 32-bit   | int                  | 4 byte read, use value directly              |
    +     * | signed 32-bit   | long                 | 4 byte read, sign extend to 64-bit           |
    +     * | signed 32-bit   | UnsignedWord         | 4 byte read, sign extend to 64-bit           |
    +     * |                                                                                       |
    +     * | unsigned 32-bit | boolean              | 4 byte read, convert to 0 or 1 (32-bit)      |
    +     * | unsigned 32-bit | byte                 | 1 byte read, sign extend to 32-bit           |
    +     * | unsigned 32-bit | short                | 2 byte read, sign extend to 32-bit           |
    +     * | unsigned 32-bit | char                 | 2 byte read, zero extend to 32-bit           |
    +     * | unsigned 32-bit | int                  | 4 byte read, use value directly              |
    +     * | unsigned 32-bit | long                 | 4 byte read, zero extend to 64-bit           |
    +     * | unsigned 32-bit | UnsignedWord         | 4 byte read, zero extend to 64-bit           |
    +     * |                                                                                       |
    +     * | signed 64-bit   | boolean              | 8 byte read, convert to 0 or 1 (32-bit)      |
    +     * | signed 64-bit   | byte                 | 1 byte read, sign extend to 32-bit           |
    +     * | signed 64-bit   | short                | 2 byte read, sign extend to 32-bit           |
    +     * | signed 64-bit   | char                 | 2 byte read, zero extend to 32-bit           |
    +     * | signed 64-bit   | int                  | 4 byte read, use value directly              |
    +     * | signed 64-bit   | long                 | 8 byte read, use value directly              |
    +     * | signed 64-bit   | UnsignedWord         | 8 byte read, use value directly              |
    +     * |                                                                                       |
    +     * | unsigned 64-bit | boolean              | 8 byte read, convert to 0 or 1 (32-bit)      |
    +     * | unsigned 64-bit | byte                 | 1 byte read, sign extend to 32-bit           |
    +     * | unsigned 64-bit | short                | 2 byte read, sign extend to 32-bit           |
    +     * | unsigned 64-bit | char                 | 2 byte read, zero extend to 32-bit           |
    +     * | unsigned 64-bit | int                  | 4 byte read, use value directly              |
    +     * | unsigned 64-bit | long                 | 8 byte read, use value directly              |
    +     * | unsigned 64-bit | UnsignedWord         | 8 byte read, use value directly              |
    +     * -----------------------------------------------------------------------------------------
    +     * 
    + */ + public static ValueNode convertCIntegerToMethodReturnType(StructuredGraph graph, NativeLibraries nativeLibs, ResolvedJavaType declaredReturnType, + ValueNode cValue, boolean isCValueUnsigned) { + IntegerStamp cValueStamp = (IntegerStamp) cValue.stamp(NodeView.DEFAULT); + int bitsInC = cValueStamp.getBits(); + + JavaKind declaredReturnKind = nativeLibs.getWordTypes().asKind(declaredReturnType); + if (declaredReturnKind == JavaKind.Boolean) { + /* Convert the C value to a Java boolean (i.e., 32-bit zero or one). */ + ValueNode comparisonValue = cValue; + int comparisonBits = bitsInC; + if (bitsInC < 32) { + /* For the comparison below, we need at least 32 bits. */ + comparisonValue = graph.unique(new ZeroExtendNode(cValue, 32)); + comparisonBits = 32; } - return graph.unique(new FloatConvertNode(op, value)); - } else if (toKind == JavaKind.Boolean) { - JavaKind computeKind = fromKind == JavaKind.Long ? JavaKind.Long : JavaKind.Int; - LogicNode comparison = graph.unique(new IntegerEqualsNode(adaptPrimitiveType(graph, value, fromKind, computeKind, true), ConstantNode.forIntegerKind(computeKind, 0, graph))); + + LogicNode comparison = graph.unique(new IntegerEqualsNode(comparisonValue, ConstantNode.forIntegerBits(comparisonBits, 0, graph))); return graph.unique(new ConditionalNode(comparison, ConstantNode.forBoolean(false, graph), ConstantNode.forBoolean(true, graph))); - } else if (fromBits > toBits) { - return graph.unique(new NarrowNode(value, toBits)); - } else if (isUnsigned) { - return graph.unique(new ZeroExtendNode(value, toBits)); - } else { - return graph.unique(new SignExtendNode(value, toBits)); } + + /* + * Narrow the C value to the bits that are actually needed for the method's declared return + * type. + */ + ValueNode result = cValue; + int resultBits = bitsInC; + if (bitsInC > declaredReturnKind.getBitCount()) { + result = graph.unique(new NarrowNode(result, declaredReturnKind.getBitCount())); + resultBits = declaredReturnKind.getBitCount(); + } + + /* + * Finally, sign- or zero-extend the value. Here, we need to be careful that the result is + * within the value range of the method's declared return type (e.g., a Java method with + * return type "byte" may only return values between -128 and +127). If we violated that + * invariant, we could end up with miscompiled code (e.g., branches could fold away + * incorrectly). + * + * So, we primarily use the signedness of the method's declared return type to decide if we + * should zero- or sign-extend. There is only one exception: if the C value is unsigned and + * uses fewer bits than the method's declared return type, then we can safely zero-extend + * the value (i.e., the unsigned C value will fit into the positive value range of the + * larger Java type). + * + * If the narrowed C value already has the correct number of bits, then the sign- or + * zero-extension is still necessary. However, it will only adjust the stamp of the value so + * that it is within the value range of the method's declared return type. + */ + int actualReturnTypeBitCount = declaredReturnKind.getStackKind().getBitCount(); + assert actualReturnTypeBitCount >= resultBits; + + boolean zeroExtend = shouldZeroExtend(nativeLibs, declaredReturnType, isCValueUnsigned, bitsInC); + return extend(graph, result, actualReturnTypeBitCount, zeroExtend); + } + + private static boolean shouldZeroExtend(NativeLibraries nativeLibs, ResolvedJavaType declaredReturnType, boolean isCValueUnsigned, int bitsInC) { + JavaKind declaredReturnKind = nativeLibs.getWordTypes().asKind(declaredReturnType); + boolean isDeclaredReturnTypeSigned = nativeLibs.isSigned(declaredReturnType); + return !isDeclaredReturnTypeSigned || isCValueUnsigned && bitsInC < declaredReturnKind.getBitCount(); } - private static JavaKind kindFromSize(int sizeInBytes, JavaKind matchingKind) { - if (matchingKind == JavaKind.Object || sizeInBytes * 8 == matchingKind.getBitCount()) { - /* Out preferred matching kind fits, so we can use it. */ - return matchingKind; + private ValueNode convertCIntegerToMethodReturnType(StructuredGraph graph, AnalysisMethod method, long cValue, int bitsInC, boolean isCValueUnsigned) { + return convertCIntegerToMethodReturnType(graph, nativeLibs, method.getSignature().getReturnType(), cValue, bitsInC, isCValueUnsigned); + } + + /** + * Creates a {@link ConstantNode} and ensures that the stamp matches the declared return type of + * the method. Similar to the other {@link #convertCIntegerToMethodReturnType} implementation. + */ + public static ValueNode convertCIntegerToMethodReturnType(StructuredGraph graph, NativeLibraries nativeLibs, ResolvedJavaType declaredReturnType, + long cValue, int bitsInC, boolean isCValueUnsigned) { + JavaKind declaredReturnKind = nativeLibs.getWordTypes().asKind(declaredReturnType); + if (declaredReturnKind == JavaKind.Boolean) { + /* Convert the C value to a Java boolean (i.e., 32-bit zero or one). */ + return ConstantNode.forBoolean(cValue != 0, graph); } - if (matchingKind == JavaKind.Float || matchingKind == JavaKind.Double) { - switch (sizeInBytes) { - case 4: - return JavaKind.Float; - case 8: - return JavaKind.Double; - } + long result = convertCIntegerToMethodReturnType(nativeLibs, declaredReturnType, cValue, bitsInC, isCValueUnsigned); + int resultBits = declaredReturnKind.getStackKind().getBitCount(); + + /* + * Finally, sign- or zero-extend the value. Here, we need to be careful that the stamp of + * the result is within the value range of the method's declared return type. + */ + boolean zeroExtend = shouldZeroExtend(nativeLibs, declaredReturnType, isCValueUnsigned, bitsInC); + if (zeroExtend) { + IntegerStamp stamp = StampFactory.forUnsignedInteger(resultBits, result, result); + return ConstantNode.forIntegerStamp(stamp, result, graph); + } + return ConstantNode.forIntegerBits(resultBits, result, graph); + } + + public static Object convertCIntegerToMethodReturnType(NativeLibraries nativeLibs, Class objectReturnType, long cValue, int bitsInC, boolean isCValueUnsigned) { + ResolvedJavaType declaredReturnType = nativeLibs.getMetaAccess().lookupJavaType(getPrimitiveOrWordClass(nativeLibs, objectReturnType)); + return createReturnObject(objectReturnType, convertCIntegerToMethodReturnType(nativeLibs, declaredReturnType, cValue, bitsInC, isCValueUnsigned)); + } + + private static long convertCIntegerToMethodReturnType(NativeLibraries nativeLibs, ResolvedJavaType declaredReturnType, long cValue, int bitsInC, boolean isCValueUnsigned) { + JavaKind declaredReturnKind = nativeLibs.getWordTypes().asKind(declaredReturnType); + if (declaredReturnKind == JavaKind.Boolean) { + /* Convert the C value to a Java boolean (i.e., zero or one). */ + return cValue != 0L ? 1L : 0L; + } + + /* Sign- or zero-extend the value. */ + boolean zeroExtend = shouldZeroExtend(nativeLibs, declaredReturnType, isCValueUnsigned, bitsInC); + int inputBits = Math.min(bitsInC, declaredReturnKind.getBitCount()); + if (zeroExtend) { + return CodeUtil.zeroExtend(cValue, inputBits); + } + return CodeUtil.signExtend(cValue, inputBits); + } + + private static Class getPrimitiveOrWordClass(NativeLibraries nativeLibs, Class type) { + if (type == Boolean.class) { + return boolean.class; + } else if (type == Byte.class) { + return byte.class; + } else if (type == Short.class) { + return short.class; + } else if (type == Character.class) { + return char.class; + } else if (type == Integer.class) { + return int.class; + } else if (type == Long.class) { + return long.class; + } else if (type == Float.class) { + return float.class; + } else if (type == Double.class) { + return double.class; + } else if (nativeLibs.getWordTypes().isWord(type)) { + return type; } else { - switch (sizeInBytes) { - case 1: - return JavaKind.Byte; - case 2: - return JavaKind.Short; - case 4: - return JavaKind.Int; - case 8: - return JavaKind.Long; - } + throw VMError.shouldNotReachHere("Unexpected type: " + type); } - throw shouldNotReachHere("Unsupported size: " + sizeInBytes); } - private static boolean replaceConstant(GraphBuilderContext b, AnalysisMethod method, ConstantInfo constantInfo) { - Object value = constantInfo.getValueInfo().getProperty(); - JavaKind kind = b.getWordTypes().asKind(b.getInvokeReturnType()); + private static Object createReturnObject(Class returnType, long value) { + if (returnType == Boolean.class) { + return value != 0; + } else if (returnType == Byte.class) { + return (byte) value; + } else if (returnType == Short.class) { + return (short) value; + } else if (returnType == Character.class) { + return (char) value; + } else if (returnType == Integer.class) { + return (int) value; + } else if (returnType == Long.class) { + return value; + } else if (WordBase.class.isAssignableFrom(returnType)) { + return WordBoxFactory.box(value); + } else { + throw VMError.shouldNotReachHere("Unexpected returnType: " + returnType.getName()); + } + } - ConstantNode valueNode; - switch (constantInfo.getKind()) { - case INTEGER: - case POINTER: - if (method.getSignature().getReturnKind() == JavaKind.Boolean) { - valueNode = ConstantNode.forBoolean((long) value != 0, b.getGraph()); - } else { - valueNode = ConstantNode.forIntegerKind(kind, (long) value, b.getGraph()); - } - break; - case FLOAT: - valueNode = ConstantNode.forFloatingKind(kind, (double) value, b.getGraph()); - break; - case STRING: - case BYTEARRAY: - valueNode = ConstantNode.forConstant(b.getSnippetReflection().forObject(value), b.getMetaAccess(), b.getGraph()); - break; - default: - throw shouldNotReachHere("Unexpected constant kind " + constantInfo); + private static ValueNode extend(StructuredGraph graph, ValueNode value, int resultBits, boolean zeroExtend) { + if (zeroExtend) { + return graph.unique(new ZeroExtendNode(value, resultBits)); } + return graph.unique(new SignExtendNode(value, resultBits)); + } + + private boolean replaceConstant(GraphBuilderContext b, AnalysisMethod method, ConstantInfo constantInfo) { + Object value = constantInfo.getValue(); + JavaKind declaredReturnKind = b.getWordTypes().asKind(b.getInvokeReturnType()); + StructuredGraph graph = b.getGraph(); + + ValueNode valueNode = switch (constantInfo.getKind()) { + case INTEGER, POINTER -> convertCIntegerToMethodReturnType(graph, method, (long) value, constantInfo.getSizeInBytes() * Byte.SIZE, constantInfo.isUnsigned()); + case FLOAT -> ConstantNode.forFloatingKind(declaredReturnKind, (double) value, graph); + case STRING, BYTEARRAY -> ConstantNode.forConstant(b.getSnippetReflection().forObject(value), b.getMetaAccess(), graph); + default -> throw shouldNotReachHere("Unexpected constant kind " + constantInfo); + }; b.push(pushKind(method), valueNode); return true; }