Skip to content

Commit

Permalink
feature: support MemoryLayout#byteOffsetHandle methods (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
SirYwell authored Oct 5, 2024
1 parent 4589365 commit 032a70d
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 23 deletions.
7 changes: 6 additions & 1 deletion src/main/kotlin/de/sirywell/handlehints/dfa/SsaAnalyzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
113 changes: 95 additions & 18 deletions src/main/kotlin/de/sirywell/handlehints/foreign/MemoryLayoutHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<PsiExpression>,
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<PsiExpression>,
Expand Down Expand Up @@ -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<VarHandleType> {
override fun onPathEmpty(layoutType: MemoryLayoutType, coords: MutableList<Type>): VarHandleType {
return when (layoutType) {
is NormalValueLayoutType -> onComplete(layoutType, coords)
else -> emitProblem(contextElement(-1), message("problem.foreign.memory.pathTargetNotValueLayout"))
}
}
abstract class HandlePathTraverser<T : TypeLatticeElement<T>>(
typeData: TypeData,
internal val contextElement: (Int) -> PsiExpression
) :
ProblemEmitter(typeData), PathTraverser<T> {

override fun onTopPathElement(
path: List<IndexedValue<PathElementType>>,
coords: MutableList<Type>,
layoutType: MemoryLayoutType
) = TopVarHandleType
) = top()

abstract fun top(): T

override fun onBottomPathElement(
path: List<IndexedValue<PathElementType>>,
coords: MutableList<Type>,
layoutType: MemoryLayoutType
) = BotVarHandleType // TODO does that make sense?
) = bot() // TODO does that make sense?

override fun onTopLayout(path: List<IndexedValue<PathElementType>>, coords: MutableList<Type>): VarHandleType {
return CompleteVarHandleType(TopType, IncompleteTypeList(coords.toIndexedMap()))
}
abstract fun bot(): T

override fun onBottomLayout(
path: List<IndexedValue<PathElementType>>,
coords: MutableList<Type>
) = BotVarHandleType // TODO does that make sense?
) = bot() // TODO does that make sense?

override fun invalidAddressDereference(head: IndexedValue<DereferenceElementType>): MemoryLayoutType {
return emitProblem(contextElement(head.index), message("problem.foreign.memory.dereferenceElementInvalid"))
Expand Down Expand Up @@ -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<VarHandleType>(typeData, contextElement) {

override fun onPathEmpty(layoutType: MemoryLayoutType, coords: MutableList<Type>): 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<IndexedValue<PathElementType>>,
coords: MutableList<Type>
): VarHandleType {
return CompleteVarHandleType(TopType, IncompleteTypeList(coords.toIndexedMap()))
}

private fun onComplete(
layoutType: ValueLayoutType,
coords: MutableList<Type>
): CompleteVarHandleType {
): VarHandleType {
if (layoutType is NormalValueLayoutType) {
return CompleteVarHandleType(layoutType.type, CompleteTypeList(coords))
} else {
Expand All @@ -295,7 +334,44 @@ class MemoryLayoutHelper(private val ssaAnalyzer: SsaAnalyzer) : ProblemEmitter(
}
}

fun arrayElementVarHandle(qualifier: PsiExpression, arguments: List<PsiExpression>, block: SsaConstruction.Block): VarHandleType {
class MethodHandlePathTraverser(
typeData: TypeData,
contextElement: (Int) -> PsiExpression
) : HandlePathTraverser<MethodHandleType>(typeData, contextElement) {
override fun top() = TopMethodHandleType

override fun bot() = BotMethodHandleType

override fun onPathEmpty(
layoutType: MemoryLayoutType,
coords: MutableList<Type>
): MethodHandleType {
return CompleteMethodHandleType(ExactType.longType, CompleteTypeList(coords), TriState.NO)
}

override fun onTopLayout(
path: List<IndexedValue<PathElementType>>,
coords: MutableList<Type>
): MethodHandleType {
return CompleteMethodHandleType(TopType, IncompleteTypeList(coords.toIndexedMap()), TriState.NO)
}

override fun dereferenceElement(
layoutType: MemoryLayoutType,
head: IndexedValue<DereferenceElementType>
): MemoryLayoutType {
return emitProblem(
contextElement(head.index),
message("problem.foreign.memory.dereferenceElementNotAllowed")
)
}
}

fun arrayElementVarHandle(
qualifier: PsiExpression,
arguments: List<PsiExpression>,
block: SsaConstruction.Block
): VarHandleType {
val layoutType = ssaAnalyzer.memoryLayoutType(qualifier, block) ?: TopMemoryLayoutType
val memorySegmentType =
PsiType.getTypeByName("java.lang.foreign.MemorySegment", qualifier.project, qualifier.resolveScope)
Expand All @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ interface PathTraverser<T> {
head: IndexedValue<SequenceElementType>
): MemoryLayoutType

fun onComplete(layoutType: ValueLayoutType, coords: MutableList<Type>): T

fun onPathEmpty(layoutType: MemoryLayoutType, coords: MutableList<Type>): T

fun onTopPathElement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,6 @@ private class ReturningPathTraverser : PathTraverser<MemoryLayoutType?> {
head: IndexedValue<SequenceElementType>
) = TopMemoryLayoutType

override fun onComplete(layoutType: ValueLayoutType, coords: MutableList<Type>) = null

override fun onPathEmpty(layoutType: MemoryLayoutType, coords: MutableList<Type>) = layoutType

override fun onTopPathElement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class MemoryLayoutTypeTest : TypeAnalysisTestBase() {
// fun testMemoryLayoutScaleHandle() = doTypeCheckingTest()
// fun testMemoryLayoutArrayElementVarHandle() = doTypeCheckingTest()

fun testMemoryLayoutByteOffsetHandle() = doInspectionAndTypeCheckingTest()

fun testMemoryLayoutWithName() = doTypeCheckingTest()


Expand Down
24 changes: 24 additions & 0 deletions src/test/testData/MemoryLayoutByteOffsetHandle.java
Original file line number Diff line number Diff line change
@@ -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
<info descr="4%[int4int4]">MemoryLayout ml00 = <info descr="4%[int4int4]">MemoryLayout.structLayout(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)</info>;</info>
<info descr="(long)long">MethodHandle mh00 = <info descr="(long)long">ml00.byteOffsetHandle(<info descr="groupElement(1)">MemoryLayout.PathElement.groupElement(1)</info>)</info>;</info>
<info descr="[10:4%[int4int4]]">SequenceLayout ml10 = <info descr="[10:4%[int4int4]]">MemoryLayout.sequenceLayout(10, ml00)</info>;</info>
// byteOffset contributing a coordinate
<info descr="(long,long)long">MethodHandle mh10 = <info descr="(long,long)long">ml10.byteOffsetHandle(<info descr="sequenceElement()">sequenceElement()</info>)</info>;</info>
// byteOffset not contributing a coordinate
<info descr="(long)long">MethodHandle mh11 = <info descr="(long)long">ml10.byteOffsetHandle(<info descr="sequenceElement(5)">sequenceElement(5)</info>)</info>;</info>

// dereference elements are not supported
<info descr="a?:4%[int4int4]">MemoryLayout ml20 = <info descr="a?:4%[int4int4]">ValueLayout.ADDRESS.withTargetLayout(ml00)</info>;</info>
<info descr="(0=long)⊤">MethodHandle mh20 = <info descr="(0=long)⊤">ml20.byteOffsetHandle(<warning descr="Dereference path element is not allowed here."><info descr="dereferenceElement()">dereferenceElement()</info></warning>)</info>;</info>
}
}

0 comments on commit 032a70d

Please sign in to comment.