[cinterop] Fix type annotation for struct containing anonymous union … (#4289)
This commit is contained in:
+104
-95
@@ -26,15 +26,13 @@ private class StructDeclImpl(spelling: String, override val location: Location)
|
||||
}
|
||||
|
||||
private class StructDefImpl(
|
||||
size: Long, align: Int, decl: StructDecl,
|
||||
override val kind: Kind
|
||||
) : StructDef(
|
||||
size, align, decl
|
||||
) {
|
||||
override val members = mutableListOf<StructMember>()
|
||||
override val methods = mutableListOf<FunctionDecl>()
|
||||
override val staticFields = mutableListOf<GlobalDecl>()
|
||||
}
|
||||
size: Long,
|
||||
align: Int,
|
||||
override val kind: Kind,
|
||||
override val members: List<StructMember>,
|
||||
override val methods: List<FunctionDecl>,
|
||||
override val staticFields: List<GlobalDecl>
|
||||
) : StructDef(size, align)
|
||||
|
||||
private class EnumDefImpl(spelling: String, type: Type, override val location: Location) : EnumDef(spelling, type) {
|
||||
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 ->
|
||||
val definitionCursor = clang_getCursorDefinition(cursor)
|
||||
if (clang_Cursor_isNull(definitionCursor) == 0) {
|
||||
assert(clang_isCursorDefinition(definitionCursor) != 0)
|
||||
// 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)
|
||||
decl.def = createStructDef(definitionCursor, definitionCursor.type, decl)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (library.language != Language.CPP) return
|
||||
private data class CxxMembers(val methods: List<FunctionDecl> = emptyList(), val staticFields: List<GlobalDecl> = emptyList())
|
||||
|
||||
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 (?)
|
||||
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
|
||||
when (cursor.kind) {
|
||||
CXCursorKind.CXCursor_CXXMethod -> {
|
||||
val isOperatorFunction = (clang_getCursorSpelling(cursor).convertAndDispose().take(8) == "operator")
|
||||
val isOperatorFunction = cursor.spelling.startsWith("operator")
|
||||
// operators are Not Implemented Yet
|
||||
if (!isOperatorFunction) {
|
||||
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_Destructor ->
|
||||
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
|
||||
getFunction(cursor, receiverType)?.let { methods.add(it) }
|
||||
|
||||
CXCursorKind.CXCursor_VarDecl -> {
|
||||
clazz.staticFields.add(GlobalDecl(
|
||||
name =getCursorSpelling(cursor),
|
||||
staticFields.add(GlobalDecl(
|
||||
name = getCursorSpelling(cursor),
|
||||
type = convertCursorType(cursor),
|
||||
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
|
||||
}
|
||||
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 fields = mutableListOf<StructMember>()
|
||||
addDeclaredFields(fields, type, type)
|
||||
|
||||
val size = clang_Type_getSizeOf(type)
|
||||
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(
|
||||
size, align, structDecl,
|
||||
when (cursor.kind) {
|
||||
CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION
|
||||
CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT
|
||||
CXCursorKind.CXCursor_ClassDecl -> StructDef.Kind.CLASS
|
||||
else -> error(cursor.kind)
|
||||
}
|
||||
)
|
||||
|
||||
structDef.members += fields
|
||||
addCxxMembers(cursor, structDef)
|
||||
|
||||
structDecl.def = structDef
|
||||
with(cxxMembers) {
|
||||
return StructDefImpl(
|
||||
size, align,
|
||||
when (cursor.kind) {
|
||||
CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION
|
||||
CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT
|
||||
CXCursorKind.CXCursor_ClassDecl -> StructDef.Kind.CLASS
|
||||
else -> error(cursor.kind)
|
||||
},
|
||||
members,
|
||||
methods,
|
||||
staticFields
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addDeclaredFields(result: MutableList<StructMember>, structType: CValue<CXType>, containerType: CValue<CXType>) {
|
||||
// TODO: We don't exactly preserve C++ layout here, but
|
||||
// we don't allow general case C++ classes by value at the moment.
|
||||
getFields(containerType).filter { library.language != Language.CPP || it.isCxxPublic }.forEach { fieldCursor ->
|
||||
val name = getCursorSpelling(fieldCursor)
|
||||
if (name.isNotEmpty()) {
|
||||
val fieldType = convertCursorType(fieldCursor)
|
||||
val offset = clang_Type_getOffsetOf(structType, name)
|
||||
val member = if (offset < 0) {
|
||||
IncompleteField(name, fieldType)
|
||||
} else if (clang_Cursor_isBitField(fieldCursor) == 0) {
|
||||
val canonicalFieldType = clang_getCanonicalType(clang_getCursorType(fieldCursor))
|
||||
Field(
|
||||
name,
|
||||
fieldType,
|
||||
offset,
|
||||
clang_Type_getSizeOf(canonicalFieldType),
|
||||
clang_Type_getAlignOf(canonicalFieldType)
|
||||
)
|
||||
} else {
|
||||
val size = clang_getFieldDeclBitWidth(fieldCursor)
|
||||
BitField(name, fieldType, offset, size)
|
||||
}
|
||||
result.add(member)
|
||||
} else {
|
||||
// Unnamed field.
|
||||
val fieldType = clang_getCursorType(fieldCursor)
|
||||
when (fieldType.kind) {
|
||||
CXTypeKind.CXType_Record -> {
|
||||
// Unnamed struct fields also contribute their fields:
|
||||
addDeclaredFields(result, structType, fieldType)
|
||||
// cursor may be at the root struct or at a inner anonymous struct or union,
|
||||
// while structType is always the nearest named enclosing struct/union (i.e. root struct)
|
||||
// All offsets are calculated relative to this named parent
|
||||
private fun getMembers(cursor: CValue<CXCursor>, structType: CValue<CXType>): List<StructMember> =
|
||||
// TODO: We don't exactly preserve C++ layout here, but we don't allow general case C++ classes by value at the moment.
|
||||
getFields(cursor.type).filter { library.language != Language.CPP || it.isCxxPublic }.map { fieldCursor ->
|
||||
|
||||
/*
|
||||
* We want to identify anonymous struct/union member, according with definition (ISO/IEC 9899):
|
||||
* "An unnamed member whose type specifier is a structure specifier with no tag is called an anonymous structure"
|
||||
* `clang_Cursor_isAnonymous` intended to identify such entity and distinguish with cases alike:
|
||||
* struct {
|
||||
* struct {int x; } f; // named member of anonymous type "struct with no tag"
|
||||
* int : 16; // anonymous bitfield
|
||||
* typedef struct { int z; } foo; // c++ only; anon struct but the struct tag is implicitly assigned by the compiler; clang_getTypeSpelling still empty
|
||||
* struct { int a; }; // this is the only one that we are looking for, i.e. anonymous struct member
|
||||
* }
|
||||
* `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.
|
||||
* Also, both `clang_Cursor_isAnonymous` and `clang_Cursor_isAnonymousRecordDecl` expect StructDecl cursor but we got
|
||||
* FieldDecl cursor now, so we have to convert cursor to type declaration cursor first (e.g. StructDecl))
|
||||
*/
|
||||
val declCursor = clang_getTypeDeclaration(fieldCursor.type)
|
||||
|
||||
// Behavior of clang_Cursor_isAnonymous is changing starting from LLVM 8.
|
||||
// Use lately introduced clang_Cursor_isAnonymousRecordDecl when available (LLVM 9)
|
||||
val isAnonymousRecordType = (fieldCursor.type.kind == CXType_Record) && (clang_Cursor_isAnonymous(declCursor) == 1)
|
||||
when {
|
||||
isAnonymousRecordType -> {
|
||||
// TODO: clang_Cursor_getOffsetOfField is OK for anonymous, but only for the 1st level of such nesting
|
||||
AnonymousInnerRecord(
|
||||
createStructDef(clang_getCursorDefinition(declCursor), structType))
|
||||
}
|
||||
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 {
|
||||
if (clang_isCursorDefinition(cursor) == 0) {
|
||||
@@ -1019,17 +1032,15 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
|
||||
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)) {
|
||||
log("Skip function ${clang_getCursorSpelling(cursor).convertAndDispose()}")
|
||||
return null
|
||||
}
|
||||
var name = clang_getCursorSpelling(cursor).convertAndDispose()
|
||||
var name = cursor.spelling
|
||||
|
||||
val cursorReturnType = clang_getCursorResultType(cursor)
|
||||
val cursorReturnTypeSpelling = clang_getTypeSpelling(cursorReturnType).convertAndDispose()
|
||||
|
||||
if (cursorReturnTypeSpelling.isUnknownTemplate()) return null
|
||||
if (cursorReturnType.name.isUnknownTemplate()) return null
|
||||
|
||||
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 ...
|
||||
val parentName = getParentName(cursor)
|
||||
val cxxMethodInfo = receiver?.let {
|
||||
val receiverPointerType = PointerType(receiver, clang_CXXMethod_isConst(cursor) != 0)
|
||||
CxxMethodInfo(
|
||||
PointerType(RecordType(receiver),
|
||||
clang_CXXMethod_isConst(cursor) != 0), // CXCursor_ConversionFunction has constness too
|
||||
receiverPointerType, // CXCursor_ConversionFunction has constness too
|
||||
when (cursor.kind) {
|
||||
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.
|
||||
// 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
|
||||
}
|
||||
CXCursorKind.CXCursor_Destructor -> {
|
||||
name = "__destroy__"
|
||||
parameters.add(0, Parameter("self", PointerType(RecordType(receiver)), false))
|
||||
parameters.add(0, Parameter("self", receiverPointerType, false))
|
||||
CxxMethodKind.Destructor
|
||||
}
|
||||
// CXCursorKind.CXCursor_ConversionFunction -> ...
|
||||
@@ -1069,9 +1080,7 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
|
||||
if (clang_CXXMethod_isStatic(cursor) != 0) {
|
||||
CxxMethodKind.StaticMethod
|
||||
} else {
|
||||
parameters.add(0, Parameter("self",
|
||||
PointerType(RecordType(receiver), clang_CXXMethod_isConst(cursor) != 0),
|
||||
false))
|
||||
parameters.add(0, Parameter("self", receiverPointerType, false))
|
||||
CxxMethodKind.InstanceMethod
|
||||
}
|
||||
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 args = (0..argNum - 1).map {
|
||||
val argCursor = clang_Cursor_getArgument(cursor, it)
|
||||
if (clang_getTypeSpelling(clang_getCursorType(argCursor)).convertAndDispose().isUnknownTemplate()) {
|
||||
if (argCursor.type.name.isUnknownTemplate()) {
|
||||
return null
|
||||
}
|
||||
val argName = getCursorSpelling(argCursor)
|
||||
|
||||
+33
-11
@@ -120,25 +120,30 @@ interface TypeDeclaration {
|
||||
val location: Location
|
||||
}
|
||||
|
||||
sealed class StructMember(val name: String, val type: Type) {
|
||||
sealed class StructMember(val name: String) {
|
||||
abstract val offset: Long?
|
||||
}
|
||||
|
||||
/**
|
||||
* C struct field.
|
||||
*/
|
||||
class Field(name: String, type: Type, override val offset: Long, val typeSize: Long, val typeAlign: Long)
|
||||
: StructMember(name, type)
|
||||
class Field(name: String, val type: Type, override val offset: Long, val typeSize: Long, val typeAlign: Long)
|
||||
: StructMember(name)
|
||||
|
||||
val Field.isAligned: Boolean
|
||||
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
|
||||
}
|
||||
|
||||
class AnonymousInnerRecord(val def: StructDef) : StructMember("") {
|
||||
override val offset: Long? get() = null
|
||||
val typeSize: Long = def.size
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
* 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 {
|
||||
STRUCT, UNION, CLASS
|
||||
}
|
||||
|
||||
abstract val methods: List<FunctionDecl>
|
||||
abstract val members: List<StructMember>
|
||||
abstract val staticFields: List<GlobalDecl>
|
||||
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 bitFields: List<BitField> get() = members.filterIsInstance<BitField>()
|
||||
val fields: List<Field>
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+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
|
||||
}
|
||||
|
||||
@@ -3700,6 +3700,10 @@ createInterop("ctypes") {
|
||||
it.defFile 'interop/basics/ctypes.def'
|
||||
}
|
||||
|
||||
createInterop("structAnonym") {
|
||||
it.defFile 'interop/basics/structAnonym.def'
|
||||
}
|
||||
|
||||
createInterop("cvalues") {
|
||||
it.defFile 'interop/basics/cvalues.def'
|
||||
}
|
||||
@@ -4039,6 +4043,12 @@ interopTest("interop_types") {
|
||||
interop = 'ctypes'
|
||||
}
|
||||
|
||||
interopTest("interop_structAnonym") {
|
||||
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
|
||||
source = "interop/basics/structAnonym.kt"
|
||||
interop = 'structAnonym'
|
||||
}
|
||||
|
||||
interopTest("interop_vectors") {
|
||||
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
|
||||
source = "interop/basics/vectors.kt"
|
||||
|
||||
@@ -11,7 +11,7 @@ fun assertEquals(value1: Any?, value2: Any?) {
|
||||
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, 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, 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.x2 = x2
|
||||
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.x7 = x7
|
||||
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.x7 = x7
|
||||
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
|
||||
}
|
||||
|
||||
fun test(s: S, x1: Long, x2: B2, x3: UShort, x4: UInt, x5: Int, x6: Long, x7: E, x8: Boolean) {
|
||||
assign(s, x1, x2, x3, x4, x5, x6, x7, x8)
|
||||
check(s, x1, x2, x3, x4, x5, x6, x7, x8)
|
||||
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, x9)
|
||||
check(s, x1, x2, x3, x4, x5, x6, x7, x8, x9)
|
||||
|
||||
assignReversed(s, x1, x2, x3, x4, x5, x6, x7, x8)
|
||||
check(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, x9)
|
||||
|
||||
// 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)
|
||||
check(s, x1, x2, x3, x4, x5, x6, 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, x9)
|
||||
|
||||
assignReversed(s, x1 + 2, x2, (x3 + 8u).toUShort(), x4 - 16u, x5 + 32, x6 + Long.MIN_VALUE, x7, x8)
|
||||
check(s, x1, x2, x3, x4, x5, x6, 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, x9)
|
||||
}
|
||||
|
||||
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 (x7 in E.values())
|
||||
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;
|
||||
enum E x7: 2;
|
||||
_Bool x8 : 1;
|
||||
struct { int x9:4; };
|
||||
};
|
||||
|
||||
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 enum E getX7(struct S* s) { return s->x7; }
|
||||
static _Bool getX8(struct S* s) { return s->x8; }
|
||||
static int getX9(struct S* s) { return s->x9; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
|
||||
// KT-28065
|
||||
struct StructWithConstFields {
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user