From 032a70de5c5f507cf19ac65130e366a9b7d83e11 Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Sat, 5 Oct 2024 12:16:29 +0200 Subject: [PATCH] feature: support MemoryLayout#byteOffsetHandle methods (#131) --- .../sirywell/handlehints/dfa/SsaAnalyzer.kt | 7 +- .../handlehints/foreign/MemoryLayoutHelper.kt | 113 +++++++++++++++--- .../handlehints/foreign/PathTraverser.kt | 2 - .../lookup/HandleHintsReferenceContributor.kt | 2 - .../messages/MethodHandleMessages.properties | 1 + .../mhtype/MemoryLayoutTypeTest.kt | 2 + .../MemoryLayoutByteOffsetHandle.java | 24 ++++ 7 files changed, 128 insertions(+), 23 deletions(-) create mode 100644 src/test/testData/MemoryLayoutByteOffsetHandle.java diff --git a/src/main/kotlin/de/sirywell/handlehints/dfa/SsaAnalyzer.kt b/src/main/kotlin/de/sirywell/handlehints/dfa/SsaAnalyzer.kt index 9c6beab..19ceed1 100644 --- a/src/main/kotlin/de/sirywell/handlehints/dfa/SsaAnalyzer.kt +++ b/src/main/kotlin/de/sirywell/handlehints/dfa/SsaAnalyzer.kt @@ -383,13 +383,13 @@ class SsaAnalyzer(private val controlFlow: ControlFlow, val typeData: TypeData) val methodExpression = expression.methodExpression val qualifier = methodExpression.qualifierExpression return when (expression.methodName) { - // for now, let's ignore names and order "withoutName" -> qualifier?.memoryLayoutType(block)?.withName(WITHOUT_NAME) "withName" -> { if (arguments.size != 1 || qualifier == null) return noMatch() memoryLayoutHelper.withName(qualifier, arguments[0], block) } + // for now, let's ignore order "withOrder" -> qualifier?.type(block) "withByteAlignment" -> { if (arguments.size != 1 || qualifier == null) return noMatch() @@ -418,6 +418,11 @@ class SsaAnalyzer(private val controlFlow: ControlFlow, val typeData: TypeData) memoryLayoutHelper.arrayElementVarHandle(qualifier, arguments, block) } + "byteOffsetHandle" -> { + if (qualifier == null) return noMatch() + memoryLayoutHelper.byteOffsetHandle(qualifier, arguments, methodExpression, block) + } + "varHandle" -> { if (qualifier == null) return noMatch() memoryLayoutHelper.varHandle(qualifier, arguments, methodExpression, block) diff --git a/src/main/kotlin/de/sirywell/handlehints/foreign/MemoryLayoutHelper.kt b/src/main/kotlin/de/sirywell/handlehints/foreign/MemoryLayoutHelper.kt index ed84ad7..bd3a332 100644 --- a/src/main/kotlin/de/sirywell/handlehints/foreign/MemoryLayoutHelper.kt +++ b/src/main/kotlin/de/sirywell/handlehints/foreign/MemoryLayoutHelper.kt @@ -150,7 +150,11 @@ class MemoryLayoutHelper(private val ssaAnalyzer: SsaAnalyzer) : ProblemEmitter( return layoutType.withTargetLayout(null) } - fun withTargetLayout(qualifier: PsiExpression, layoutExpr: PsiExpression, block: SsaConstruction.Block): MemoryLayoutType { + fun withTargetLayout( + qualifier: PsiExpression, + layoutExpr: PsiExpression, + block: SsaConstruction.Block + ): MemoryLayoutType { val layoutType = ssaAnalyzer.memoryLayoutType(qualifier, block) as? AddressLayoutType ?: return TopMemoryLayoutType val layout = ssaAnalyzer.memoryLayoutType(layoutExpr, block) ?: TopMemoryLayoutType @@ -175,6 +179,20 @@ class MemoryLayoutHelper(private val ssaAnalyzer: SsaAnalyzer) : ProblemEmitter( .maxOrNull() ?: 1 // if empty, the alignment is 1 } + fun byteOffsetHandle( + qualifier: PsiExpression, + arguments: List, + methodExpr: PsiExpression, + block: SsaConstruction.Block + ): MethodHandleType { + val layoutType = ssaAnalyzer.memoryLayoutType(qualifier, block) ?: TopMemoryLayoutType + val path = toPath(arguments, block) + return MethodHandlePathTraverser(typeData) { + if (it == -1) methodExpr + else arguments[it] + }.traverse(path, layoutType, mutableListOf(ExactType.longType)) + } + fun varHandle( qualifier: PsiExpression, arguments: List, @@ -202,35 +220,32 @@ class MemoryLayoutHelper(private val ssaAnalyzer: SsaAnalyzer) : ProblemEmitter( /** * Emits a warning if the last path element does not result in a ValueLayout */ - class VarHandlePathTraverser(typeData: TypeData, private val contextElement: (Int) -> PsiExpression) : - ProblemEmitter(typeData), PathTraverser { - override fun onPathEmpty(layoutType: MemoryLayoutType, coords: MutableList): VarHandleType { - return when (layoutType) { - is NormalValueLayoutType -> onComplete(layoutType, coords) - else -> emitProblem(contextElement(-1), message("problem.foreign.memory.pathTargetNotValueLayout")) - } - } + abstract class HandlePathTraverser>( + typeData: TypeData, + internal val contextElement: (Int) -> PsiExpression + ) : + ProblemEmitter(typeData), PathTraverser { override fun onTopPathElement( path: List>, coords: MutableList, layoutType: MemoryLayoutType - ) = TopVarHandleType + ) = top() + + abstract fun top(): T override fun onBottomPathElement( path: List>, coords: MutableList, layoutType: MemoryLayoutType - ) = BotVarHandleType // TODO does that make sense? + ) = bot() // TODO does that make sense? - override fun onTopLayout(path: List>, coords: MutableList): VarHandleType { - return CompleteVarHandleType(TopType, IncompleteTypeList(coords.toIndexedMap())) - } + abstract fun bot(): T override fun onBottomLayout( path: List>, coords: MutableList - ) = BotVarHandleType // TODO does that make sense? + ) = bot() // TODO does that make sense? override fun invalidAddressDereference(head: IndexedValue): MemoryLayoutType { return emitProblem(contextElement(head.index), message("problem.foreign.memory.dereferenceElementInvalid")) @@ -280,11 +295,35 @@ class MemoryLayoutHelper(private val ssaAnalyzer: SsaAnalyzer) : ProblemEmitter( ): MemoryLayoutType { return emitOutOfBounds(layoutType.elementCount, contextElement(head.index), index, true) } + } - override fun onComplete( + class VarHandlePathTraverser( + typeData: TypeData, + contextElement: (Int) -> PsiExpression + ) : HandlePathTraverser(typeData, contextElement) { + + override fun onPathEmpty(layoutType: MemoryLayoutType, coords: MutableList): VarHandleType { + return when (layoutType) { + is NormalValueLayoutType -> onComplete(layoutType, coords) + else -> emitProblem(contextElement(-1), message("problem.foreign.memory.pathTargetNotValueLayout")) + } + } + + override fun top() = TopVarHandleType + + override fun bot() = BotVarHandleType + + override fun onTopLayout( + path: List>, + coords: MutableList + ): VarHandleType { + return CompleteVarHandleType(TopType, IncompleteTypeList(coords.toIndexedMap())) + } + + private fun onComplete( layoutType: ValueLayoutType, coords: MutableList - ): CompleteVarHandleType { + ): VarHandleType { if (layoutType is NormalValueLayoutType) { return CompleteVarHandleType(layoutType.type, CompleteTypeList(coords)) } else { @@ -295,7 +334,44 @@ class MemoryLayoutHelper(private val ssaAnalyzer: SsaAnalyzer) : ProblemEmitter( } } - fun arrayElementVarHandle(qualifier: PsiExpression, arguments: List, block: SsaConstruction.Block): VarHandleType { + class MethodHandlePathTraverser( + typeData: TypeData, + contextElement: (Int) -> PsiExpression + ) : HandlePathTraverser(typeData, contextElement) { + override fun top() = TopMethodHandleType + + override fun bot() = BotMethodHandleType + + override fun onPathEmpty( + layoutType: MemoryLayoutType, + coords: MutableList + ): MethodHandleType { + return CompleteMethodHandleType(ExactType.longType, CompleteTypeList(coords), TriState.NO) + } + + override fun onTopLayout( + path: List>, + coords: MutableList + ): MethodHandleType { + return CompleteMethodHandleType(TopType, IncompleteTypeList(coords.toIndexedMap()), TriState.NO) + } + + override fun dereferenceElement( + layoutType: MemoryLayoutType, + head: IndexedValue + ): MemoryLayoutType { + return emitProblem( + contextElement(head.index), + message("problem.foreign.memory.dereferenceElementNotAllowed") + ) + } + } + + fun arrayElementVarHandle( + qualifier: PsiExpression, + arguments: List, + block: SsaConstruction.Block + ): VarHandleType { val layoutType = ssaAnalyzer.memoryLayoutType(qualifier, block) ?: TopMemoryLayoutType val memorySegmentType = PsiType.getTypeByName("java.lang.foreign.MemorySegment", qualifier.project, qualifier.resolveScope) @@ -312,5 +388,6 @@ class MemoryLayoutHelper(private val ssaAnalyzer: SsaAnalyzer) : ProblemEmitter( return SCALE_HANDLE } } + private val SCALE_HANDLE_PARAMETERS = CompleteTypeList(listOf(ExactType.longType, ExactType.longType)) private val SCALE_HANDLE = CompleteMethodHandleType(ExactType.longType, SCALE_HANDLE_PARAMETERS, TriState.NO) \ No newline at end of file diff --git a/src/main/kotlin/de/sirywell/handlehints/foreign/PathTraverser.kt b/src/main/kotlin/de/sirywell/handlehints/foreign/PathTraverser.kt index 9ebd7a0..22e7dd9 100644 --- a/src/main/kotlin/de/sirywell/handlehints/foreign/PathTraverser.kt +++ b/src/main/kotlin/de/sirywell/handlehints/foreign/PathTraverser.kt @@ -165,8 +165,6 @@ interface PathTraverser { head: IndexedValue ): MemoryLayoutType - fun onComplete(layoutType: ValueLayoutType, coords: MutableList): T - fun onPathEmpty(layoutType: MemoryLayoutType, coords: MutableList): T fun onTopPathElement( diff --git a/src/main/kotlin/de/sirywell/handlehints/lookup/HandleHintsReferenceContributor.kt b/src/main/kotlin/de/sirywell/handlehints/lookup/HandleHintsReferenceContributor.kt index e68cb67..c577689 100644 --- a/src/main/kotlin/de/sirywell/handlehints/lookup/HandleHintsReferenceContributor.kt +++ b/src/main/kotlin/de/sirywell/handlehints/lookup/HandleHintsReferenceContributor.kt @@ -177,8 +177,6 @@ private class ReturningPathTraverser : PathTraverser { head: IndexedValue ) = TopMemoryLayoutType - override fun onComplete(layoutType: ValueLayoutType, coords: MutableList) = null - override fun onPathEmpty(layoutType: MemoryLayoutType, coords: MutableList) = layoutType override fun onTopPathElement( diff --git a/src/main/resources/messages/MethodHandleMessages.properties b/src/main/resources/messages/MethodHandleMessages.properties index 4050d4b..7d7ad9d 100644 --- a/src/main/resources/messages/MethodHandleMessages.properties +++ b/src/main/resources/messages/MethodHandleMessages.properties @@ -55,5 +55,6 @@ problem.foreign.memory.pathElementMismatch=A {0} path element cannot be applied problem.foreign.memory.pathGroupElementOutOfBounds=Group element at index {0} exceeds the number of member layouts {1}. problem.foreign.memory.pathGroupElementUnknownName=Group layout does not have a member layout with name {0}. problem.foreign.memory.dereferenceElementInvalid=Address layout has no target layout to dereference. +problem.foreign.memory.dereferenceElementNotAllowed=Dereference path element is not allowed here. problem.transforming.asType.redundant=Call to 'asType' is redundant as the MethodHandle already has that type problem.transforming.asType.incompatibleToPrimitiveCast=Cannot cast reference type {0} to primitive type {1} diff --git a/src/test/kotlin/de/sirywell/handlehints/mhtype/MemoryLayoutTypeTest.kt b/src/test/kotlin/de/sirywell/handlehints/mhtype/MemoryLayoutTypeTest.kt index bd7f1cf..b955888 100644 --- a/src/test/kotlin/de/sirywell/handlehints/mhtype/MemoryLayoutTypeTest.kt +++ b/src/test/kotlin/de/sirywell/handlehints/mhtype/MemoryLayoutTypeTest.kt @@ -18,6 +18,8 @@ class MemoryLayoutTypeTest : TypeAnalysisTestBase() { // fun testMemoryLayoutScaleHandle() = doTypeCheckingTest() // fun testMemoryLayoutArrayElementVarHandle() = doTypeCheckingTest() + fun testMemoryLayoutByteOffsetHandle() = doInspectionAndTypeCheckingTest() + fun testMemoryLayoutWithName() = doTypeCheckingTest() diff --git a/src/test/testData/MemoryLayoutByteOffsetHandle.java b/src/test/testData/MemoryLayoutByteOffsetHandle.java new file mode 100644 index 0000000..81cb093 --- /dev/null +++ b/src/test/testData/MemoryLayoutByteOffsetHandle.java @@ -0,0 +1,24 @@ +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.StructLayout; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; + +import static java.lang.foreign.MemoryLayout.PathElement.*; + +class MemoryLayoutByteOffsetHandle { + void byteOffsetHandlePaths() { + // simple byteOffset + MemoryLayout ml00 = MemoryLayout.structLayout(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT); + MethodHandle mh00 = ml00.byteOffsetHandle(MemoryLayout.PathElement.groupElement(1)); + SequenceLayout ml10 = MemoryLayout.sequenceLayout(10, ml00); + // byteOffset contributing a coordinate + MethodHandle mh10 = ml10.byteOffsetHandle(sequenceElement()); + // byteOffset not contributing a coordinate + MethodHandle mh11 = ml10.byteOffsetHandle(sequenceElement(5)); + + // dereference elements are not supported + MemoryLayout ml20 = ValueLayout.ADDRESS.withTargetLayout(ml00); + MethodHandle mh20 = ml20.byteOffsetHandle(dereferenceElement()); + } +} \ No newline at end of file