[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
@@ -26,15 +26,13 @@ private class StructDeclImpl(spelling: String, override val location: Location)
} }
private class StructDefImpl( private class StructDefImpl(
size: Long, align: Int, decl: StructDecl, size: Long,
override val kind: Kind align: Int,
) : StructDef( override val kind: Kind,
size, align, decl override val members: List<StructMember>,
) { override val methods: List<FunctionDecl>,
override val members = mutableListOf<StructMember>() override val staticFields: List<GlobalDecl>
override val methods = mutableListOf<FunctionDecl>() ) : StructDef(size, align)
override val staticFields = mutableListOf<GlobalDecl>()
}
private class EnumDefImpl(spelling: String, type: Type, override val location: Location) : EnumDef(spelling, type) { private class EnumDefImpl(spelling: String, type: Type, override val location: Location) : EnumDef(spelling, type) {
override val constants = mutableListOf<EnumConstant>() override val constants = mutableListOf<EnumConstant>()
@@ -181,24 +179,21 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
): StructDecl = structRegistry.getOrPut(cursor, { createStructDecl(cursor) }) { decl -> ): StructDecl = structRegistry.getOrPut(cursor, { createStructDecl(cursor) }) { decl ->
val definitionCursor = clang_getCursorDefinition(cursor) val definitionCursor = clang_getCursorDefinition(cursor)
if (clang_Cursor_isNull(definitionCursor) == 0) { if (clang_Cursor_isNull(definitionCursor) == 0) {
assert(clang_isCursorDefinition(definitionCursor) != 0) decl.def = createStructDef(definitionCursor, definitionCursor.type, decl)
// TODO: is this a bug or this is a wrong thing to do?
// Otherwise c++ class definition is created from its forward declaration
// and hence is empty.
//createStructDef(decl, cursor)
createStructDef(decl, definitionCursor)
} }
} }
private fun createStructDecl(cursor: CValue<CXCursor>): StructDeclImpl {
val cursorType = clang_getCursorType(cursor)
val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose()
return StructDeclImpl(typeSpelling, getLocation(cursor)) private fun createStructDecl(cursor: CValue<CXCursor>): StructDeclImpl =
} StructDeclImpl(cursor.type.name, getLocation(cursor))
private fun addCxxMembers(classCursor: CValue<CXCursor>, clazz: StructDefImpl) { private data class CxxMembers(val methods: List<FunctionDecl> = emptyList(), val staticFields: List<GlobalDecl> = emptyList())
if (library.language != Language.CPP) return
private fun collectCxxMembers(classCursor: CValue<CXCursor>, receiverType: RecordType): CxxMembers {
assert(library.language == Language.CPP)
val methods = mutableListOf<FunctionDecl>()
val staticFields = mutableListOf<GlobalDecl>()
// TODO skip method (function) when encounter UnsupportedType in params or ret value. Otherwise all class methods will be lost due to exception (?) // TODO skip method (function) when encounter UnsupportedType in params or ret value. Otherwise all class methods will be lost due to exception (?)
visitChildren(classCursor) { cursor, _ -> visitChildren(classCursor) { cursor, _ ->
@@ -206,23 +201,23 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
// TODO If a kotlin class is _conceptually_ derived from its c++ counterpart, then it shall be able to override virtual private and access protected // TODO If a kotlin class is _conceptually_ derived from its c++ counterpart, then it shall be able to override virtual private and access protected
when (cursor.kind) { when (cursor.kind) {
CXCursorKind.CXCursor_CXXMethod -> { CXCursorKind.CXCursor_CXXMethod -> {
val isOperatorFunction = (clang_getCursorSpelling(cursor).convertAndDispose().take(8) == "operator") val isOperatorFunction = cursor.spelling.startsWith("operator")
// operators are Not Implemented Yet // operators are Not Implemented Yet
if (!isOperatorFunction) { if (!isOperatorFunction) {
if (clang_isFunctionTypeVariadic(clang_getCursorType(cursor)) == 0) // FIXME why it doesn't work??? if (clang_isFunctionTypeVariadic(clang_getCursorType(cursor)) == 0) // FIXME why it doesn't work???
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) } getFunction(cursor, receiverType)?.let { methods.add(it) }
} }
} }
CXCursorKind.CXCursor_Constructor, CXCursorKind.CXCursor_Constructor,
CXCursorKind.CXCursor_Destructor -> CXCursorKind.CXCursor_Destructor ->
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) } getFunction(cursor, receiverType)?.let { methods.add(it) }
CXCursorKind.CXCursor_VarDecl -> { CXCursorKind.CXCursor_VarDecl -> {
clazz.staticFields.add(GlobalDecl( staticFields.add(GlobalDecl(
name =getCursorSpelling(cursor), name = getCursorSpelling(cursor),
type = convertCursorType(cursor), type = convertCursorType(cursor),
isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0, isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0,
parentName = clazz.decl.spelling) parentName = receiverType.decl.spelling)
) )
} }
@@ -232,72 +227,90 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
} }
CXChildVisitResult.CXChildVisit_Continue CXChildVisitResult.CXChildVisit_Continue
} }
return CxxMembers(methods, staticFields)
} }
private fun createStructDef(structDecl: StructDeclImpl, cursor: CValue<CXCursor>) { private fun createStructDef(cursor: CValue<CXCursor>, structType: CValue<CXType>, structDecl: StructDecl? = null): StructDefImpl {
assert(clang_isCursorDefinition(cursor) != 0)
val type = clang_getCursorType(cursor) val type = clang_getCursorType(cursor)
val fields = mutableListOf<StructMember>()
addDeclaredFields(fields, type, type)
val size = clang_Type_getSizeOf(type) val size = clang_Type_getSizeOf(type)
val align = clang_Type_getAlignOf(type).toInt() val align = clang_Type_getAlignOf(type).toInt()
val members = getMembers(cursor, structType)
val cxxMembers =
if (library.language == Language.CPP && structDecl != null) collectCxxMembers(cursor, RecordType(structDecl))
else CxxMembers()
val structDef = StructDefImpl( with(cxxMembers) {
size, align, structDecl, return StructDefImpl(
when (cursor.kind) { size, align,
CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION when (cursor.kind) {
CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION
CXCursorKind.CXCursor_ClassDecl -> StructDef.Kind.CLASS CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT
else -> error(cursor.kind) CXCursorKind.CXCursor_ClassDecl -> StructDef.Kind.CLASS
} else -> error(cursor.kind)
) },
members,
structDef.members += fields methods,
addCxxMembers(cursor, structDef) staticFields
)
structDecl.def = structDef }
} }
private fun addDeclaredFields(result: MutableList<StructMember>, structType: CValue<CXType>, containerType: CValue<CXType>) { // cursor may be at the root struct or at a inner anonymous struct or union,
// TODO: We don't exactly preserve C++ layout here, but // while structType is always the nearest named enclosing struct/union (i.e. root struct)
// we don't allow general case C++ classes by value at the moment. // All offsets are calculated relative to this named parent
getFields(containerType).filter { library.language != Language.CPP || it.isCxxPublic }.forEach { fieldCursor -> private fun getMembers(cursor: CValue<CXCursor>, structType: CValue<CXType>): List<StructMember> =
val name = getCursorSpelling(fieldCursor) // TODO: We don't exactly preserve C++ layout here, but we don't allow general case C++ classes by value at the moment.
if (name.isNotEmpty()) { getFields(cursor.type).filter { library.language != Language.CPP || it.isCxxPublic }.map { fieldCursor ->
val fieldType = convertCursorType(fieldCursor)
val offset = clang_Type_getOffsetOf(structType, name) /*
val member = if (offset < 0) { * We want to identify anonymous struct/union member, according with definition (ISO/IEC 9899):
IncompleteField(name, fieldType) * "An unnamed member whose type specifier is a structure specifier with no tag is called an anonymous structure"
} else if (clang_Cursor_isBitField(fieldCursor) == 0) { * `clang_Cursor_isAnonymous` intended to identify such entity and distinguish with cases alike:
val canonicalFieldType = clang_getCanonicalType(clang_getCursorType(fieldCursor)) * struct {
Field( * struct {int x; } f; // named member of anonymous type "struct with no tag"
name, * int : 16; // anonymous bitfield
fieldType, * typedef struct { int z; } foo; // c++ only; anon struct but the struct tag is implicitly assigned by the compiler; clang_getTypeSpelling still empty
offset, * struct { int a; }; // this is the only one that we are looking for, i.e. anonymous struct member
clang_Type_getSizeOf(canonicalFieldType), * }
clang_Type_getAlignOf(canonicalFieldType) * `clang_Cursor_isAnonymous` implementation has been changed since LLVM 8 so we have to additionally check type.kind == CXType_Record
) * Starting from LLVM 9 a new function `clang_Cursor_isAnonymousRecordDecl` provided specifically for that.
} else { * Also, both `clang_Cursor_isAnonymous` and `clang_Cursor_isAnonymousRecordDecl` expect StructDecl cursor but we got
val size = clang_getFieldDeclBitWidth(fieldCursor) * FieldDecl cursor now, so we have to convert cursor to type declaration cursor first (e.g. StructDecl))
BitField(name, fieldType, offset, size) */
} val declCursor = clang_getTypeDeclaration(fieldCursor.type)
result.add(member)
} else { // Behavior of clang_Cursor_isAnonymous is changing starting from LLVM 8.
// Unnamed field. // Use lately introduced clang_Cursor_isAnonymousRecordDecl when available (LLVM 9)
val fieldType = clang_getCursorType(fieldCursor) val isAnonymousRecordType = (fieldCursor.type.kind == CXType_Record) && (clang_Cursor_isAnonymous(declCursor) == 1)
when (fieldType.kind) { when {
CXTypeKind.CXType_Record -> { isAnonymousRecordType -> {
// Unnamed struct fields also contribute their fields: // TODO: clang_Cursor_getOffsetOfField is OK for anonymous, but only for the 1st level of such nesting
addDeclaredFields(result, structType, fieldType) AnonymousInnerRecord(
createStructDef(clang_getCursorDefinition(declCursor), structType))
} }
else -> { else -> {
// Nothing. val name = getCursorSpelling(fieldCursor)
val fieldType = convertCursorType(fieldCursor)
val offset = clang_Type_getOffsetOf(structType, name)
if (offset < 0) {
IncompleteField(name)
} else if (clang_Cursor_isBitField(fieldCursor) == 0) {
val canonicalFieldType = clang_getCanonicalType(fieldCursor.type)
Field(
name,
fieldType,
offset,
clang_Type_getSizeOf(canonicalFieldType),
clang_Type_getAlignOf(canonicalFieldType)
)
} else {
val size = clang_getFieldDeclBitWidth(fieldCursor)
BitField(name, fieldType, offset, size)
}
} }
} }
} }
}
}
private fun getEnumDefAt(cursor: CValue<CXCursor>): EnumDefImpl { private fun getEnumDefAt(cursor: CValue<CXCursor>): EnumDefImpl {
if (clang_isCursorDefinition(cursor) == 0) { if (clang_isCursorDefinition(cursor) == 0) {
@@ -1019,17 +1032,15 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
return getParentName(parent, nextPkg) return getParentName(parent, nextPkg)
} }
private fun getFunction(cursor: CValue<CXCursor>, receiver: StructDecl? = null): FunctionDecl? { private fun getFunction(cursor: CValue<CXCursor>, receiver: RecordType? = null): FunctionDecl? {
if (!isFuncDeclEligible(cursor)) { if (!isFuncDeclEligible(cursor)) {
log("Skip function ${clang_getCursorSpelling(cursor).convertAndDispose()}") log("Skip function ${clang_getCursorSpelling(cursor).convertAndDispose()}")
return null return null
} }
var name = clang_getCursorSpelling(cursor).convertAndDispose() var name = cursor.spelling
val cursorReturnType = clang_getCursorResultType(cursor) val cursorReturnType = clang_getCursorResultType(cursor)
val cursorReturnTypeSpelling = clang_getTypeSpelling(cursorReturnType).convertAndDispose() if (cursorReturnType.name.isUnknownTemplate()) return null
if (cursorReturnTypeSpelling.isUnknownTemplate()) return null
var returnType = convertType(cursorReturnType, clang_getCursorResultTypeAttributes(cursor)) var returnType = convertType(cursorReturnType, clang_getCursorResultTypeAttributes(cursor))
@@ -1048,20 +1059,20 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
// TODO Do the following if clang_getCursorLanguage(cursor) == CXLanguageKind.CXLanguage_CPlusPlus ... // TODO Do the following if clang_getCursorLanguage(cursor) == CXLanguageKind.CXLanguage_CPlusPlus ...
val parentName = getParentName(cursor) val parentName = getParentName(cursor)
val cxxMethodInfo = receiver?.let { val cxxMethodInfo = receiver?.let {
val receiverPointerType = PointerType(receiver, clang_CXXMethod_isConst(cursor) != 0)
CxxMethodInfo( CxxMethodInfo(
PointerType(RecordType(receiver), receiverPointerType, // CXCursor_ConversionFunction has constness too
clang_CXXMethod_isConst(cursor) != 0), // CXCursor_ConversionFunction has constness too
when (cursor.kind) { when (cursor.kind) {
CXCursorKind.CXCursor_Constructor -> { CXCursorKind.CXCursor_Constructor -> {
returnType = PointerType(RecordType(receiver)) returnType = receiverPointerType
name = "__init__" // It is intended to init preallocated memory with placement new, so it is not "create" factory method. TODO One may want "create" method also. name = "__init__" // It is intended to init preallocated memory with placement new, so it is not "create" factory method. TODO One may want "create" method also.
// Parameter type for placement new is void*, but I want to emphasize that memory block ahall have proper size and alignment // Parameter type for placement new is void*, but I want to emphasize that memory block ahall have proper size and alignment
parameters.add(0, Parameter("self", PointerType(RecordType(receiver)), false)) parameters.add(0, Parameter("self", receiverPointerType, false))
CxxMethodKind.Constructor CxxMethodKind.Constructor
} }
CXCursorKind.CXCursor_Destructor -> { CXCursorKind.CXCursor_Destructor -> {
name = "__destroy__" name = "__destroy__"
parameters.add(0, Parameter("self", PointerType(RecordType(receiver)), false)) parameters.add(0, Parameter("self", receiverPointerType, false))
CxxMethodKind.Destructor CxxMethodKind.Destructor
} }
// CXCursorKind.CXCursor_ConversionFunction -> ... // CXCursorKind.CXCursor_ConversionFunction -> ...
@@ -1069,9 +1080,7 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
if (clang_CXXMethod_isStatic(cursor) != 0) { if (clang_CXXMethod_isStatic(cursor) != 0) {
CxxMethodKind.StaticMethod CxxMethodKind.StaticMethod
} else { } else {
parameters.add(0, Parameter("self", parameters.add(0, Parameter("self", receiverPointerType, false))
PointerType(RecordType(receiver), clang_CXXMethod_isConst(cursor) != 0),
false))
CxxMethodKind.InstanceMethod CxxMethodKind.InstanceMethod
} }
else -> CxxMethodKind.None // Not implemented. Not expected, OK to assert (?) else -> CxxMethodKind.None // Not implemented. Not expected, OK to assert (?)
@@ -1148,7 +1157,7 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
val argNum = clang_Cursor_getNumArguments(cursor) val argNum = clang_Cursor_getNumArguments(cursor)
val args = (0..argNum - 1).map { val args = (0..argNum - 1).map {
val argCursor = clang_Cursor_getArgument(cursor, it) val argCursor = clang_Cursor_getArgument(cursor, it)
if (clang_getTypeSpelling(clang_getCursorType(argCursor)).convertAndDispose().isUnknownTemplate()) { if (argCursor.type.name.isUnknownTemplate()) {
return null return null
} }
val argName = getCursorSpelling(argCursor) val argName = getCursorSpelling(argCursor)
@@ -120,25 +120,30 @@ interface TypeDeclaration {
val location: Location val location: Location
} }
sealed class StructMember(val name: String, val type: Type) { sealed class StructMember(val name: String) {
abstract val offset: Long? abstract val offset: Long?
} }
/** /**
* C struct field. * C struct field.
*/ */
class Field(name: String, type: Type, override val offset: Long, val typeSize: Long, val typeAlign: Long) class Field(name: String, val type: Type, override val offset: Long, val typeSize: Long, val typeAlign: Long)
: StructMember(name, type) : StructMember(name)
val Field.isAligned: Boolean val Field.isAligned: Boolean
get() = offset % (typeAlign * 8) == 0L get() = offset % (typeAlign * 8) == 0L
class BitField(name: String, type: Type, override val offset: Long, val size: Int) : StructMember(name, type) class BitField(name: String, val type: Type, override val offset: Long, val size: Int) : StructMember(name)
class IncompleteField(name: String, type: Type) : StructMember(name, type) { class IncompleteField(name: String) : StructMember(name) {
override val offset: Long? get() = null override val offset: Long? get() = null
} }
class AnonymousInnerRecord(val def: StructDef) : StructMember("") {
override val offset: Long? get() = null
val typeSize: Long = def.size
}
/** /**
* C struct declaration. * C struct declaration.
*/ */
@@ -153,19 +158,36 @@ abstract class StructDecl(val spelling: String) : TypeDeclaration {
* @param hasNaturalLayout must be `false` if the struct has unnatural layout, e.g. it is `packed`. * @param hasNaturalLayout must be `false` if the struct has unnatural layout, e.g. it is `packed`.
* May be `false` even if the struct has natural layout. * May be `false` even if the struct has natural layout.
*/ */
abstract class StructDef(val size: Long, val align: Int, val decl: StructDecl) { abstract class StructDef(val size: Long, val align: Int) {
enum class Kind { enum class Kind {
STRUCT, UNION, CLASS STRUCT, UNION, CLASS
} }
abstract val methods: List<FunctionDecl>
abstract val members: List<StructMember>
abstract val staticFields: List<GlobalDecl>
abstract val kind: Kind abstract val kind: Kind
abstract val members: List<StructMember>
abstract val methods: List<FunctionDecl>
abstract val staticFields: List<GlobalDecl>
val fields: List<Field> get() = members.filterIsInstance<Field>() val fields: List<Field>
val bitFields: List<BitField> get() = members.filterIsInstance<BitField>() get() = mutableListOf<Field>().apply {
members.forEach {
when (it) {
is Field -> add(it)
is AnonymousInnerRecord -> addAll(it.def.fields)
}
}
}
val bitFields: List<BitField>
get() = mutableListOf<BitField>().apply {
members.forEach {
when (it) {
is BitField -> add(it)
is AnonymousInnerRecord -> addAll(it.def.bitFields)
}
}
}
} }
/** /**
@@ -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) { private fun Type.isIntegerLikeType(): Boolean = when (this) {
is RecordType -> { is RecordType -> decl.def?.hasIntegerLikeLayout() ?: false
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 ObjCPointer, is PointerType, CharType, is BoolType -> true is ObjCPointer, is PointerType, CharType, is BoolType -> true
is IntegerType -> this.size <= 4 is IntegerType -> this.size <= 4
is Typedef -> this.def.aliased.isIntegerLikeType() is Typedef -> this.def.aliased.isIntegerLikeType()
@@ -122,7 +139,7 @@ private fun Type.isIntegerLikeType(): Boolean = when (this) {
private fun Type.hasUnalignedMembers(): Boolean = when (this) { private fun Type.hasUnalignedMembers(): Boolean = when (this) {
is Typedef -> this.def.aliased.hasUnalignedMembers() is Typedef -> this.def.aliased.hasUnalignedMembers()
is RecordType -> this.decl.def!!.let { def -> is RecordType -> this.decl.def!!.let { def ->
def.fields.any { def.fields.any { // TODO: what about bitfields?
!it.isAligned || !it.isAligned ||
// Check members of fields too: // Check members of fields too:
it.type.hasUnalignedMembers() it.type.hasUnalignedMembers()
@@ -8,36 +8,75 @@ fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) {
StructDef.Kind.CLASS -> null 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 var offset = 0L
return buildString { val isPackedStruct = def.isPacked
append("struct")
if (isPackedStruct) append(" __attribute__((packed))")
append(" { ")
def.members.forEachIndexed { index, it -> // The following is to deal with the case when a field has big alignment but occasionally its offset is naturally aligned,
val name = "p$index" // 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) { val decl = when (it) {
is Field -> { is Field -> {
val immediateOffset = it.offsetBytes - baseOffset
val defaultAlignment = if (isPackedStruct) 1L else it.typeAlign 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, it.name)
?.plus(if (alignment == defaultAlignment) "" else " __attribute__((aligned($alignment)))")
tryRenderVar(it.type, name)
?.plus(if (alignment == defaultAlignment) "" else "__attribute__((aligned($alignment)))")
} }
is BitField, // TODO: tryRenderVar(it.type, name)?.plus(" : ${it.size}") is BitField, // TODO: tryRenderVar(it.type, name)?.plus(" : ${it.size}")
is IncompleteField -> null // e.g. flexible array member. 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 } ?: return null
append("$decl; ") append("$decl; ")
} }
append("}") 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 alignUp(x: Long, alignment: Long): Long = (x + alignment - 1) and ((alignment - 1).inv())
private fun tryRenderUnion(def: StructDef): String? = private fun tryRenderUnion(def: StructDef): String? {
if (def.members.any { it.offset != 0L }) null else buildString { val maxAlign = def.members.filterIsInstance<Field>().maxOfOrNull { it.typeAlign }
append("union { ") val forceAlign = maxAlign?.let { def.align > maxAlign }
def.members.forEachIndexed { index, it -> ?: (def.align > 1) // Anonymous inner may be empty AND explicitly aligned
val decl = when (it) {
is Field -> tryRenderVar(it.type, "p$index")
is BitField, is IncompleteField -> null
} ?: return null
append("$decl; ")
}
append("}")
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) { private fun tryRenderVar(type: Type, name: String): String? = when (type) {
CharType, is BoolType -> "char $name" CharType, is BoolType -> "char $name"
@@ -78,7 +123,13 @@ private fun tryRenderVar(type: Type, name: String): String? = when (type) {
else -> null else -> null
} }
private val Field.offsetBytes: Long get() { private val Field.offsetBytes: Long
require(this.offset % 8 == 0L) get() {
return this.offset / 8 require(this.offset % 8 == 0L)
} return this.offset / 8
}
private val AnonymousInnerRecord.offsetBytes: Long?
get() {
return def.fields.firstOrNull()?.offsetBytes
}
@@ -3700,6 +3700,10 @@ createInterop("ctypes") {
it.defFile 'interop/basics/ctypes.def' it.defFile 'interop/basics/ctypes.def'
} }
createInterop("structAnonym") {
it.defFile 'interop/basics/structAnonym.def'
}
createInterop("cvalues") { createInterop("cvalues") {
it.defFile 'interop/basics/cvalues.def' it.defFile 'interop/basics/cvalues.def'
} }
@@ -4039,6 +4043,12 @@ interopTest("interop_types") {
interop = 'ctypes' interop = 'ctypes'
} }
interopTest("interop_structAnonym") {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/basics/structAnonym.kt"
interop = 'structAnonym'
}
interopTest("interop_vectors") { interopTest("interop_vectors") {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/basics/vectors.kt" source = "interop/basics/vectors.kt"
@@ -11,7 +11,7 @@ fun assertEquals(value1: Any?, value2: Any?) {
throw AssertionError("Expected $value1, got $value2") throw AssertionError("Expected $value1, got $value2")
} }
fun check(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E, x8: Boolean) { fun check(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E, x8: Boolean, x9: Int) {
assertEquals(x1, s.x1) assertEquals(x1, s.x1)
assertEquals(x1, getX1(s.ptr)) assertEquals(x1, getX1(s.ptr))
@@ -35,9 +35,12 @@ fun check(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E
assertEquals(x8, s.x8) assertEquals(x8, s.x8)
assertEquals(x8, getX8(s.ptr)) assertEquals(x8, getX8(s.ptr))
assertEquals(x9, s.x9)
assertEquals(x9, getX9(s.ptr))
} }
fun assign(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E, x8: Boolean) { fun assign(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E, x8: Boolean, x9: Int) {
s.x1 = x1 s.x1 = x1
s.x2 = x2 s.x2 = x2
s.x3 = x3 s.x3 = x3
@@ -46,9 +49,11 @@ fun assign(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7:
s.x6 = x6 s.x6 = x6
s.x7 = x7 s.x7 = x7
s.x8 = x8 s.x8 = x8
s.x9 = x9
} }
fun assignReversed(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E, x8: Boolean) { fun assignReversed(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E, x8: Boolean, x9: Int) {
s.x9 = x9
s.x8 = x8 s.x8 = x8
s.x7 = x7 s.x7 = x7
s.x6 = x6 s.x6 = x6
@@ -59,20 +64,20 @@ fun assignReversed(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Lo
s.x1 = x1 s.x1 = x1
} }
fun test(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E, x8: Boolean) { fun test(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E, x8: Boolean, x9: Int) {
assign(s, x1, x2, x3, x4, x5, x6, x7, x8) assign(s, x1, x2, x3, x4, x5, x6, x7, x8, x9)
check(s, x1, x2, x3, x4, x5, x6, x7, x8) check(s, x1, x2, x3, x4, x5, x6, x7, x8, x9)
assignReversed(s, x1, x2, x3, x4, x5, x6, x7, x8) assignReversed(s, x1, x2, x3, x4, x5, x6, x7, x8, x9)
check(s, x1, x2, x3, x4, x5, x6, x7, x8) check(s, x1, x2, x3, x4, x5, x6, x7, x8, x9)
// Also check with some insignificant bits modified: // Also check with some insignificant bits modified:
assign(s, x1 + 2, x2, (x3 + 8u).toUShort(), x4 - 16u, x5 + 32, x6 + Long.MIN_VALUE, x7, x8) assign(s, x1 + 2, x2, (x3 + 8u).toUShort(), x4 - 16u, x5 + 32, x6 + Long.MIN_VALUE, x7, x8, x9 + 16)
check(s, x1, x2, x3, x4, x5, x6, x7, x8) check(s, x1, x2, x3, x4, x5, x6, x7, x8, x9)
assignReversed(s, x1 + 2, x2, (x3 + 8u).toUShort(), x4 - 16u, x5 + 32, x6 + Long.MIN_VALUE, x7, x8) assignReversed(s, x1 + 2, x2, (x3 + 8u).toUShort(), x4 - 16u, x5 + 32, x6 + Long.MIN_VALUE, x7, x8, x9 + 16)
check(s, x1, x2, x3, x4, x5, x6, x7, x8) check(s, x1, x2, x3, x4, x5, x6, x7, x8, x9)
} }
fun main(args: Array<String>) { fun main(args: Array<String>) {
@@ -86,6 +91,7 @@ fun main(args: Array<String>) {
for (x6 in longArrayOf(Long.MIN_VALUE/2, -1L shl 36, -325L, 0, 1L shl 48, Long.MAX_VALUE/2)) for (x6 in longArrayOf(Long.MIN_VALUE/2, -1L shl 36, -325L, 0, 1L shl 48, Long.MAX_VALUE/2))
for (x7 in E.values()) for (x7 in E.values())
for (x8 in arrayOf(false, true)) for (x8 in arrayOf(false, true))
test(s, x1, x2, x3.toUShort(), x4, x5, x6, x7, x8) for (x9 in intArrayOf(-8, -2, -1, 0, 5, 7)) // 4 bits width
test(s, x1, x2, x3.toUShort(), x4, x5, x6, x7, x8, x9)
} }
} }
@@ -16,6 +16,7 @@ struct __attribute__((packed)) S {
long long x6 : 63; long long x6 : 63;
enum E x7: 2; enum E x7: 2;
_Bool x8 : 1; _Bool x8 : 1;
struct { int x9:4; };
}; };
static long long getX1(struct S* s) { return s->x1; } static long long getX1(struct S* s) { return s->x1; }
@@ -26,3 +27,4 @@ static int getX5(struct S* s) { return s->x5; }
static long long getX6(struct S* s) { return s->x6; } static long long getX6(struct S* s) { return s->x6; }
static enum E getX7(struct S* s) { return s->x7; } static enum E getX7(struct S* s) { return s->x7; }
static _Bool getX8(struct S* s) { return s->x8; } static _Bool getX8(struct S* s) { return s->x8; }
static int getX9(struct S* s) { return s->x9; }
@@ -1,4 +1,5 @@
--- ---
// KT-28065 // KT-28065
struct StructWithConstFields { struct StructWithConstFields {
int x; int x;
@@ -0,0 +1,192 @@
---
/*
* Test of return/send-by-value for aggregate type (struct or union) with anonymous inner struct or union member.
* Specific issues: alignment, packed, nested named and anon struct/union, other anon types (named field of anon struct type; anon bitfield)
*/
#include <inttypes.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winitializer-overrides"
union _GLKVector3
{
struct { float x, y, z; };
struct { float r, g, b; };
struct { float s, t, p; };
float v[3];
};
static union _GLKVector3 get_GLKVector3() {
union _GLKVector3 ret = {{1, 2, 3}};
return ret;
}
static float hash_GLKVector3(union _GLKVector3 x) {
union _GLKVector3 ret = {{1, 2, 3}};
return x.x + 2.0f * x.y + 4.0f * x.z;
}
// trivial alignment: member is already aligned, but this implies implicit larger alignment of the root struct
struct StructAnonRecordMember_ImplicitAlignment {
int32_t a[4];
struct {
int b __attribute__((aligned(16)));
};
};
static struct StructAnonRecordMember_ImplicitAlignment retByValue_StructAnonRecordMember_ImplicitAlignment() {
struct StructAnonRecordMember_ImplicitAlignment t = {
.a = {1,2,3,4},
.b = 42
};
return t;
}
struct StructAnonRecordMember_ExplicitAlignment {
char a;
struct {
__attribute__((aligned(4)))
char x;
};
};
static struct StructAnonRecordMember_ExplicitAlignment retByValue_StructAnonRecordMember_ExplicitAlignment() {
struct StructAnonRecordMember_ExplicitAlignment t = {
.a = 'a',
.x = 'x'
};
return t;
}
// Deep nesting
struct StructAnonRecordMember_Nested {
int x;
union { // implicitly aligned to 8 bytes due to int64, or 4 bytes at 32-bit arch
int a[2];
struct {
int64_t b;
};
};
char z;
double y;
};
static struct StructAnonRecordMember_Nested retByValue_StructAnonRecordMember_Nested() {
struct StructAnonRecordMember_Nested c = {
.x = 37,
.b = 42,
.z = 'z',
.y = 3.14
};
return c;
}
static int sendByValue_StructAnonRecordMember_Nested(struct StructAnonRecordMember_Nested c) {
return c.a[0] + 2 * c.a[1];
}
// Basic, 2 levels
struct StructAnonRecordMember_Complicate {
char first; // __attribute__((aligned(16)));
union {
int a[2];
union { char c1; int c2; };
struct { char b1; int64_t b2; }; // implicit 64-bits alignment
};
char second __attribute__((aligned(16)));
struct {
char x;
struct { int64_t b11, b12; } Y2;
float f __attribute__((aligned(16)));
}; // __attribute__((aligned(16)));
char last;
};
#define INIT(T, x) struct T x = \
{ \
.first = 'a', \
.b1 = 'b', \
.b2 = 42, \
.second = 's', \
.last = 'z', \
.f = 3.14, \
.Y2 = {11, 12} \
}
static struct StructAnonRecordMember_Complicate retByValue_StructAnonRecordMember_Complicate() {
INIT(StructAnonRecordMember_Complicate, c);
return c;
}
struct StructAnonRecordMember_Packed {
char first;
union {
int a[2];
union { char c1; int c2; };
struct { char b1; int64_t b2; };
};
char second;
struct {
char x;
struct { int64_t b11, b12; } Y2;
float f;
} __attribute__((aligned(16)));
char last;
} __attribute__ ((packed));
static struct StructAnonRecordMember_Packed retByValue_StructAnonRecordMember_Packed() {
INIT(StructAnonRecordMember_Packed, c);
return c;
}
// Nested struct may be packed too
#pragma pack(1)
struct StructAnonRecordMember_PragmaPacked {
char first;
union {
int a[2];
union { char c1; int c2; };
struct { char b1; int64_t b2; };
};
char second;
struct {
char x;
struct { int64_t b11, b12; } Y2;
float f __attribute__((aligned(16)));
}; // __attribute__((aligned(16)));
char last;
} __attribute__ ((packed));
#pragma pack()
static struct StructAnonRecordMember_PragmaPacked retByValue_StructAnonRecordMember_PragmaPacked() {
INIT(StructAnonRecordMember_PragmaPacked, c);
return c;
}
#pragma pack(2)
struct StructAnonRecordMember_Packed2 {
char first;
union {
int a[2];
union { char c1; int c2; };
struct { char b1; int64_t b2; };
};
char second;
struct {
char x;
struct { int64_t b11, b12; } Y2;
float f __attribute__((aligned(16)));
}; // __attribute__((aligned(16)));
char last;
} __attribute__ ((packed));
#pragma pack()
static struct StructAnonRecordMember_Packed2 retByValue_StructAnonRecordMember_Packed2() {
INIT(StructAnonRecordMember_Packed2, c);
return c;
}
#pragma clang diagnostic pop
@@ -0,0 +1,116 @@
import kotlinx.cinterop.*
import kotlin.test.*
import structAnonym.*
fun test_GLKVector3() {
get_GLKVector3().useContents {
assertEquals(1.0f, x)
assertEquals(2.0f, g)
assertEquals(3.0f, p)
r = 0.1f
g = 0.2f
b = 0.3f
assertEquals(v[0], r)
assertEquals(v[1], g)
assertEquals(v[2], b)
val ret = hash_GLKVector3(this.readValue())
assertEquals(s + 2f * t + 4f * p , ret)
}
}
fun test_StructAnonRecordMember_ImplicitAlignment() {
retByValue_StructAnonRecordMember_ImplicitAlignment()
.useContents {
assertEquals(1, a[0])
assertEquals(4, a[3])
assertEquals(42, b)
}
}
fun test_StructAnonRecordMember_ExplicitAlignment() {
retByValue_StructAnonRecordMember_ExplicitAlignment()
.useContents {
assertEquals('a', a.toInt().toChar())
assertEquals('x', x.toInt().toChar())
}
}
fun test_StructAnonRecordMember_Nested() {
retByValue_StructAnonRecordMember_Nested()
.useContents {
assertEquals(37, x)
assertEquals(42, b)
assertEquals('z', z.toInt().toChar())
assertEquals(3.14, y)
a[0] = 3
a[1] = 5
assertEquals(3 + 2*5, sendByValue_StructAnonRecordMember_Nested(this.readValue()))
}
}
fun test_StructAnonym_Complicate() {
retByValue_StructAnonRecordMember_Complicate()
.useContents{
assertEquals('a', first.toInt().toChar())
assertEquals('s', second.toInt().toChar())
assertEquals('z', last.toInt().toChar())
assertEquals('b', b1.toInt().toChar())
assertEquals(42L, b2)
assertEquals(3.14F, f)
assertEquals(11L, Y2.b11)
}
}
fun test_StructAnonym_Packed() {
retByValue_StructAnonRecordMember_Packed2()
.useContents{
assertEquals('a', first.toInt().toChar())
assertEquals('s', second.toInt().toChar())
assertEquals('z', last.toInt().toChar())
assertEquals('b', b1.toInt().toChar())
assertEquals(42L, b2)
assertEquals(3.14F, f)
assertEquals(11L, Y2.b11)
}
}
fun test_StructAnonym_PragmaPacked() {
retByValue_StructAnonRecordMember_PragmaPacked()
.useContents{
assertEquals('a', first.toInt().toChar())
assertEquals('s', second.toInt().toChar())
assertEquals('z', last.toInt().toChar())
assertEquals('b', b1.toInt().toChar())
assertEquals(42L, b2)
assertEquals(3.14F, f)
assertEquals(11L, Y2.b11)
}
}
fun test_StructAnonym_Packed2() {
retByValue_StructAnonRecordMember_Packed2()
.useContents{
assertEquals('a', first.toInt().toChar())
assertEquals('s', second.toInt().toChar())
assertEquals('z', last.toInt().toChar())
assertEquals('b', b1.toInt().toChar())
assertEquals(42L, b2)
assertEquals(3.14F, f)
assertEquals(11L, Y2.b11)
}
}
fun main() {
test_GLKVector3()
test_StructAnonRecordMember_ImplicitAlignment()
test_StructAnonRecordMember_ExplicitAlignment()
test_StructAnonRecordMember_Nested()
test_StructAnonym_Complicate()
test_StructAnonym_Packed()
test_StructAnonym_PragmaPacked()
test_StructAnonym_Packed2()
}