[cinterop] Fix type annotation for struct containing anonymous union … (#4289)
This commit is contained in:
+33
-16
@@ -95,22 +95,39 @@ class DarwinArm64AbiInfo : ObjCAbiInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Consider edge cases with anonymous inner:
|
||||
hasIntegerLikeLayout
|
||||
1 N struct X { struct {}; int v1; }; // despite the offset(v1) == 0 and sizeof(X) == 4
|
||||
2 N struct X { struct {}; char v1; }; // same with char
|
||||
3 N struct X { struct {} v1; short v2; }; // same with named empty field; sizeof == 2, offset(v2) == 0
|
||||
4 N struct X { int v1; struct {}; }; // despite there is only one field but empty struct has offset == 4
|
||||
5 N struct X { char v1; struct {}; }; // same, sizeof is 1
|
||||
6 N struct X { char v1; struct {char v2:4;}; }; // despite v2 is bitfield
|
||||
7 Y struct X { char v1; char v2:4; }; // but this is OK (bitfield)
|
||||
8 Y struct X { struct {char v1;}; char v2:4; }; // same, bitfield is OK
|
||||
9 Y struct X { struct {char v1;} v1; char v2:4; }; // same, OK v2 is bitfield
|
||||
10 Y struct X { struct {} v1; char v2:4; }; // OK, v2 is bitfield
|
||||
11 Y struct X { struct {char v1;}; short v2:16; }; // OK, v2 is bitfield even if it has full size
|
||||
|
||||
#1..3: the field offset == 0 but still not eligible for `hasIntegerLikeLayout`
|
||||
Looks like we have to use the field' sequential number instead of offset
|
||||
*/
|
||||
private fun StructDef.hasIntegerLikeLayout(): Boolean {
|
||||
return size <= 4 &&
|
||||
members.mapIndexed { index, it ->
|
||||
// Assuming the member order has not been changed
|
||||
when (it) {
|
||||
is BitField -> it.type.isIntegerLikeType()
|
||||
is Field -> index == 0 && it.type.isIntegerLikeType() // assert(offset == 0)
|
||||
is AnonymousInnerRecord -> index == 0 && it.def.hasIntegerLikeLayout()
|
||||
is IncompleteField -> false
|
||||
}
|
||||
}.all {it}
|
||||
}
|
||||
|
||||
private fun Type.isIntegerLikeType(): Boolean = when (this) {
|
||||
is RecordType -> {
|
||||
val def = this.decl.def
|
||||
if (def == null) {
|
||||
false
|
||||
} else {
|
||||
def.size <= 4 &&
|
||||
def.members.all {
|
||||
when (it) {
|
||||
is BitField -> it.type.isIntegerLikeType()
|
||||
is Field -> it.offset == 0L && it.type.isIntegerLikeType()
|
||||
is IncompleteField -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is RecordType -> decl.def?.hasIntegerLikeLayout() ?: false
|
||||
is ObjCPointer, is PointerType, CharType, is BoolType -> true
|
||||
is IntegerType -> this.size <= 4
|
||||
is Typedef -> this.def.aliased.isIntegerLikeType()
|
||||
@@ -122,7 +139,7 @@ private fun Type.isIntegerLikeType(): Boolean = when (this) {
|
||||
private fun Type.hasUnalignedMembers(): Boolean = when (this) {
|
||||
is Typedef -> this.def.aliased.hasUnalignedMembers()
|
||||
is RecordType -> this.decl.def!!.let { def ->
|
||||
def.fields.any {
|
||||
def.fields.any { // TODO: what about bitfields?
|
||||
!it.isAligned ||
|
||||
// Check members of fields too:
|
||||
it.type.hasUnalignedMembers()
|
||||
|
||||
+81
-30
@@ -8,36 +8,75 @@ fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) {
|
||||
StructDef.Kind.CLASS -> null
|
||||
}
|
||||
|
||||
private fun tryRenderStruct(def: StructDef): String? {
|
||||
val isPackedStruct = def.fields.any { !it.isAligned }
|
||||
/**
|
||||
* Members of anonymous struct/union are the fields of enclosing named aggregate and has the corresponding offset.
|
||||
* However for the purpose of alignment heuristic we use "immediate" offset, i.e. relative to the immediate parent.
|
||||
* Consider for ex. a packed struct containing not packed anonymous `Inner`: inner fields are not aligned relative to the root.
|
||||
*
|
||||
* For the purpose of `isPacked` heuristic we should analyze immediate children only, i.e. ignore the members of nested
|
||||
* anonymous struct / union (included by `fields` getter). For ex. inner anon struct may be packed and its members unaligned,
|
||||
* however this does not imply `packed` attribute at outer struct.
|
||||
*
|
||||
* Empty inner records (ie offsetBytes == null) does not affect `packed` heuristic and shall be ignored here.
|
||||
* Unsupported members (ie BitField and IncompleteField) to be ignored too but won't be compiled anyway.
|
||||
*/
|
||||
private val StructDef.isPacked: Boolean
|
||||
get() {
|
||||
val baseOffset = fields.firstOrNull()?.offsetBytes ?: return false
|
||||
return members.any { member ->
|
||||
when (member) {
|
||||
is Field -> (member.offsetBytes - baseOffset) % member.typeAlign != 0L
|
||||
is AnonymousInnerRecord ->
|
||||
member.offsetBytes?.let { (it - baseOffset) % member.def.align != 0L } ?: false
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryRenderStruct(def: StructDef): String? {
|
||||
// The only case when offset starts from non-zero is a inner anonymous struct or union
|
||||
val baseOffset = def.fields.firstOrNull()?.offsetBytes ?: 0L
|
||||
var offset = 0L
|
||||
|
||||
return buildString {
|
||||
append("struct")
|
||||
if (isPackedStruct) append(" __attribute__((packed))")
|
||||
append(" { ")
|
||||
val isPackedStruct = def.isPacked
|
||||
|
||||
def.members.forEachIndexed { index, it ->
|
||||
val name = "p$index"
|
||||
// The following is to deal with the case when a field has big alignment but occasionally its offset is naturally aligned,
|
||||
// so we can't guess it by heuristic. However the enclosing struct must be explicitly aligned.
|
||||
val maxAlign = def.members.filterIsInstance<Field>().maxOfOrNull { it.typeAlign }
|
||||
val forceAlign = maxAlign?.let { def.align > maxAlign }
|
||||
?: (def.align > 1) // Anonymous inner may be empty AND explicitly aligned
|
||||
|
||||
return buildString {
|
||||
append("struct { ")
|
||||
|
||||
def.members.forEach { it ->
|
||||
val decl = when (it) {
|
||||
is Field -> {
|
||||
val immediateOffset = it.offsetBytes - baseOffset
|
||||
val defaultAlignment = if (isPackedStruct) 1L else it.typeAlign
|
||||
val alignment = guessAlignment(offset, it.offsetBytes, defaultAlignment) ?: return null
|
||||
val alignment = guessAlignment(offset, immediateOffset, defaultAlignment) ?: return null
|
||||
offset = immediateOffset + it.typeSize
|
||||
|
||||
offset = it.offsetBytes + it.typeSize
|
||||
|
||||
tryRenderVar(it.type, name)
|
||||
?.plus(if (alignment == defaultAlignment) "" else "__attribute__((aligned($alignment)))")
|
||||
tryRenderVar(it.type, it.name)
|
||||
?.plus(if (alignment == defaultAlignment) "" else " __attribute__((aligned($alignment)))")
|
||||
}
|
||||
|
||||
is BitField, // TODO: tryRenderVar(it.type, name)?.plus(" : ${it.size}")
|
||||
is IncompleteField -> null // e.g. flexible array member.
|
||||
is AnonymousInnerRecord -> {
|
||||
// No need to advance offset if offsetBytes is null 'cause it means that record is empty. Assert that.
|
||||
assert(it.offsetBytes != null || it.typeSize == 0L)
|
||||
it.offsetBytes?.let { offsetBytes ->
|
||||
offset = offsetBytes - baseOffset + it.typeSize
|
||||
}
|
||||
tryRenderStructOrUnion(it.def)
|
||||
}
|
||||
} ?: return null
|
||||
append("$decl; ")
|
||||
}
|
||||
|
||||
append("}")
|
||||
if (isPackedStruct) append(" __attribute__((packed))")
|
||||
if (forceAlign) append(" __attribute__((aligned(${def.align})))")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,20 +87,26 @@ private fun guessAlignment(offset: Long, paddedOffset: Long, defaultAlignment: L
|
||||
|
||||
private fun alignUp(x: Long, alignment: Long): Long = (x + alignment - 1) and ((alignment - 1).inv())
|
||||
|
||||
private fun tryRenderUnion(def: StructDef): String? =
|
||||
if (def.members.any { it.offset != 0L }) null else buildString {
|
||||
append("union { ")
|
||||
def.members.forEachIndexed { index, it ->
|
||||
val decl = when (it) {
|
||||
is Field -> tryRenderVar(it.type, "p$index")
|
||||
is BitField, is IncompleteField -> null
|
||||
} ?: return null
|
||||
|
||||
append("$decl; ")
|
||||
}
|
||||
append("}")
|
||||
private fun tryRenderUnion(def: StructDef): String? {
|
||||
val maxAlign = def.members.filterIsInstance<Field>().maxOfOrNull { it.typeAlign }
|
||||
val forceAlign = maxAlign?.let { def.align > maxAlign }
|
||||
?: (def.align > 1) // Anonymous inner may be empty AND explicitly aligned
|
||||
|
||||
return buildString {
|
||||
append("union { ")
|
||||
def.members.forEach { it ->
|
||||
val name = it.name
|
||||
val decl = when (it) {
|
||||
is Field -> tryRenderVar(it.type, name)
|
||||
is BitField, is IncompleteField -> null
|
||||
is AnonymousInnerRecord -> tryRenderStructOrUnion(it.def)
|
||||
} ?: return null
|
||||
append("$decl; ")
|
||||
}
|
||||
append("}")
|
||||
if (forceAlign) append(" __attribute__((aligned(${def.align})))")
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryRenderVar(type: Type, name: String): String? = when (type) {
|
||||
CharType, is BoolType -> "char $name"
|
||||
@@ -78,7 +123,13 @@ private fun tryRenderVar(type: Type, name: String): String? = when (type) {
|
||||
else -> null
|
||||
}
|
||||
|
||||
private val Field.offsetBytes: Long get() {
|
||||
require(this.offset % 8 == 0L)
|
||||
return this.offset / 8
|
||||
}
|
||||
private val Field.offsetBytes: Long
|
||||
get() {
|
||||
require(this.offset % 8 == 0L)
|
||||
return this.offset / 8
|
||||
}
|
||||
|
||||
private val AnonymousInnerRecord.offsetBytes: Long?
|
||||
get() {
|
||||
return def.fields.firstOrNull()?.offsetBytes
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user