[cinterop] Fix type annotation for struct containing anonymous union … (#4289)

This commit is contained in:
Vladimir Ivanov
2021-05-18 14:34:56 +03:00
committed by GitHub
parent b01478746c
commit 5a0f113e6d
10 changed files with 591 additions and 165 deletions
@@ -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()
@@ -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
}