Support bit fields in interop

Fix #728
This commit is contained in:
Svyatoslav Scherbina
2017-08-15 11:10:13 +03:00
committed by SvyatoslavScherbina
parent ce27695290
commit 5c3e8d63d4
11 changed files with 276 additions and 2 deletions
@@ -34,6 +34,7 @@ private class StructDefImpl(
) {
override val fields = mutableListOf<Field>()
override val bitFields = mutableListOf<BitField>()
}
private class EnumDefImpl(spelling: String, type: Type) : EnumDef(spelling, type) {
@@ -158,7 +159,8 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
val typeAlign = clang_Type_getAlignOf(clang_getCursorType(fieldCursor))
structDef.fields.add(Field(name, fieldType, offset, typeAlign))
} else {
// Ignore bit fields for now.
val size = clang_getFieldDeclBitWidth(fieldCursor)
structDef.bitFields.add(BitField(name, fieldType, offset, size))
}
} else {
// Unnamed field.
@@ -55,6 +55,8 @@ class Field(val name: String, val type: Type, val offset: Long, val typeAlign: L
val Field.isAligned: Boolean
get() = offset % (typeAlign * 8) == 0L
class BitField(val name: String, val type: Type, val offset: Long, val size: Int)
/**
* C struct declaration.
*/
@@ -74,6 +76,8 @@ abstract class StructDef(val size: Long, val align: Int,
val hasNaturalLayout: Boolean) {
abstract val fields: List<Field>
// TODO: merge two lists to preserve declaration order.
abstract val bitFields: List<BitField>
}
/**
@@ -99,4 +99,7 @@ object nativeMemUtils {
private external fun malloc(size: Long, align: Int): NativePtr
@SymbolName("Kotlin_interop_free")
private external fun cfree(ptr: NativePtr)
private external fun cfree(ptr: NativePtr)
@Intrinsic external fun readBits(ptr: NativePtr, offset: Long, size: Int, signed: Boolean): Long
@Intrinsic external fun writeBits(ptr: NativePtr, offset: Long, size: Int, value: Long)
@@ -303,9 +303,39 @@ class StubGenerator(
log("Warning: cannot generate definition for field ${decl.kotlinName}.${field.name}")
}
}
if (platform == KotlinPlatform.NATIVE) {
for (field in def.bitFields) {
val typeMirror = mirror(field.type)
val typeInfo = typeMirror.info
val kotlinType = typeMirror.argType
val rawType = typeInfo.bridgedType
out("var ${field.name.asSimpleName()}: $kotlinType")
val signed = field.type.getUnderlyingIntegerType().isSigned
val readBitsExpr =
"readBits(this.rawPtr, ${field.offset}, ${field.size}, $signed).to${rawType.kotlinType}()"
out(" get() = ${typeInfo.argFromBridged(readBitsExpr)}")
val rawValue = typeInfo.argToBridged("value")
val setExpr = "writeBits(this.rawPtr, ${field.offset}, ${field.size}, $rawValue.toLong())"
out(" set(value) = $setExpr")
out("")
}
}
}
}
private tailrec fun Type.getUnderlyingIntegerType(): IntegerType = when (this) {
is IntegerType -> this
is EnumType -> this.def.baseType.getUnderlyingIntegerType()
is Typedef -> this.def.aliased.getUnderlyingIntegerType()
else -> error(this)
}
/**
* Produces to [out] the definition of Kotlin class representing the reference to given forward (incomplete) struct.
*/
@@ -102,6 +102,9 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val narrow = packageScope.getContributedFunctions("narrow").single()
val readBits = packageScope.getContributedFunctions("readBits").single()
val writeBits = packageScope.getContributedFunctions("writeBits").single()
val objCObject = packageScope.getContributedClassifier("ObjCObject") as ClassDescriptor
val objCPointerHolder = packageScope.getContributedClassifier("ObjCPointerHolder") as ClassDescriptor
@@ -345,8 +345,38 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
return LLVMBlockAddress(function, bbLabel)!!
}
fun and(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildAnd(builder, arg0, arg1, name)!!
fun or(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildOr(builder, arg0, arg1, name)!!
fun zext(arg: LLVMValueRef, type: LLVMTypeRef): LLVMValueRef =
LLVMBuildZExt(builder, arg, type, "")!!
fun sext(arg: LLVMValueRef, type: LLVMTypeRef): LLVMValueRef =
LLVMBuildSExt(builder, arg, type, "")!!
fun ext(arg: LLVMValueRef, type: LLVMTypeRef, signed: Boolean): LLVMValueRef =
if (signed) {
sext(arg, type)
} else {
zext(arg, type)
}
fun trunc(arg: LLVMValueRef, type: LLVMTypeRef): LLVMValueRef =
LLVMBuildTrunc(builder, arg, type, "")!!
private fun shift(op: LLVMOpcode, arg: LLVMValueRef, amount: Int) =
if (amount == 0) {
arg
} else {
LLVMBuildBinOp(builder, op, arg, LLVMConstInt(arg.type, amount.toLong(), 0), "")!!
}
fun shl(arg: LLVMValueRef, amount: Int) = shift(LLVMOpcode.LLVMShl, arg, amount)
fun shr(arg: LLVMValueRef, amount: Int, signed: Boolean) =
shift(if (signed) LLVMOpcode.LLVMAShr else LLVMOpcode.LLVMLShr,
arg, amount)
/* integers comparisons */
fun icmpEq(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildICmp(builder, LLVMIntPredicate.LLVMIntEQ, arg0, arg1, name)!!
@@ -1991,10 +1991,103 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
genGetObjCMessenger(args, isLU = true)
}
interop.readBits -> genReadBits(args)
interop.writeBits -> genWriteBits(args)
else -> TODO(callee.descriptor.original.toString())
}
}
private fun genReadBits(args: List<LLVMValueRef>): LLVMValueRef {
val ptr = args[0]
assert(ptr.type == int8TypePtr)
val offset = extractConstUnsignedInt(args[1])
val size = extractConstUnsignedInt(args[2]).toInt()
val signed = extractConstUnsignedInt(args[3]) != 0L
val prefixBitsNum = (offset % 8).toInt()
val suffixBitsNum = (8 - ((size + offset) % 8).toInt()) % 8
// Note: LLVM allows to read without padding tail up to byte boundary, but the result seems to be incorrect.
val bitsWithPaddingNum = prefixBitsNum + size + suffixBitsNum
val bitsWithPaddingType = LLVMIntType(bitsWithPaddingNum)!!
with (functionGenerationContext) {
val bitsWithPaddingPtr = bitcast(pointerType(bitsWithPaddingType), gep(ptr, Int64(offset / 8).llvm))
val bitsWithPadding = load(bitsWithPaddingPtr).setUnaligned()
val bits = shr(
shl(bitsWithPadding, suffixBitsNum),
prefixBitsNum + suffixBitsNum, signed
)
return if (bitsWithPaddingNum == 64) {
bits
} else if (bitsWithPaddingNum > 64) {
trunc(bits, kInt64)
} else {
ext(bits, kInt64, signed)
}
}
}
private fun genWriteBits(args: List<LLVMValueRef>): LLVMValueRef {
val ptr = args[0]
assert(ptr.type == int8TypePtr)
val offset = extractConstUnsignedInt(args[1])
val size = extractConstUnsignedInt(args[2]).toInt()
val value = args[3]
assert(value.type == kInt64)
val bitsType = LLVMIntType(size)!!
val prefixBitsNum = (offset % 8).toInt()
val suffixBitsNum = (8 - ((size + offset) % 8).toInt()) % 8
val bitsWithPaddingNum = prefixBitsNum + size + suffixBitsNum
val bitsWithPaddingType = LLVMIntType(bitsWithPaddingNum)!!
// 0011111000:
val discardBitsMask = LLVMConstShl(
LLVMConstZExt(
LLVMConstAllOnes(bitsType), // 11111
bitsWithPaddingType
), // 1111100000
LLVMConstInt(bitsWithPaddingType, prefixBitsNum.toLong(), 0)
)
val preservedBitsMask = LLVMConstNot(discardBitsMask)!!
with (functionGenerationContext) {
val bitsWithPaddingPtr = bitcast(pointerType(bitsWithPaddingType), gep(ptr, Int64(offset / 8).llvm))
val bits = trunc(value, bitsType)
val bitsToStore = if (prefixBitsNum == 0 && suffixBitsNum == 0) {
bits
} else {
val previousValue = load(bitsWithPaddingPtr).setUnaligned()
val preservedBits = and(previousValue, preservedBitsMask)
val bitsWithPadding = shl(zext(bits, bitsWithPaddingType), prefixBitsNum)
or(bitsWithPadding, preservedBits)
}
LLVMBuildStore(builder, bitsToStore, bitsWithPaddingPtr)!!.setUnaligned()
}
return codegen.theUnitInstanceRef.llvm
}
private fun extractConstUnsignedInt(value: LLVMValueRef): Long {
assert(LLVMIsConstant(value) != 0)
return LLVMConstIntGetZExtValue(value)
}
private fun genGetObjCClass(classDescriptor: ClassDescriptor): LLVMValueRef {
assert(!classDescriptor.isInterface)
@@ -126,6 +126,7 @@ internal val RuntimeAware.kTypeInfoPtr: LLVMTypeRef
get() = pointerType(kTypeInfo)
internal val kInt1 = LLVMInt1Type()!!
internal val kBoolean = kInt1
internal val kInt64 = LLVMInt64Type()!!
internal val kInt8Ptr = pointerType(int8Type)
internal val kInt8PtrPtr = pointerType(kInt8Ptr)
internal val kNullInt8Ptr = LLVMConstNull(kInt8Ptr)!!
@@ -258,3 +259,5 @@ fun parseBitcodeFile(path: String): LLVMModuleRef = memScoped {
internal fun String.mdString() = LLVMMDString(this, this.length)!!
internal fun node(vararg it:LLVMValueRef) = LLVMMDNode(it.toList().toCValues(), it.size)
internal fun LLVMValueRef.setUnaligned() = apply { LLVMSetAlignment(this, 1) }
+12
View File
@@ -1989,6 +1989,11 @@ kotlinNativeInterop {
flavor 'native'
}
bitfields {
defFile 'interop/basics/bitfields.def'
flavor 'native'
}
if (isMac()) {
opengl {
defFile '../../samples/opengl/src/main/c_interop/opengl.def'
@@ -2046,6 +2051,12 @@ task interop4(type: RunInteropKonanTest) {
interop = 'cstdio'
}
task interop_bitfields(type: RunInteropKonanTest) {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/basics/bf.kt"
interop = 'bitfields'
}
task interop_echo_server(type: RunInteropKonanTest) {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
if (!isMac()) {
@@ -2066,6 +2077,7 @@ if (isMac()) {
}
task interop_objc_smoke(type: RunInteropKonanTest) {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
goldValue = "Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\nDeallocated\nDeallocated\n"
source = "interop/objc/smoke.kt"
+74
View File
@@ -0,0 +1,74 @@
import bitfields.*
import kotlinx.cinterop.*
fun assertEquals(value1: Any?, value2: Any?) {
if (value1 != value2)
throw AssertionError("Expected $value1, got $value2")
}
fun check(s: S, x1: Long, x2: B2, x3: Short, x4: Int, x5: Int, x6: Long) {
assertEquals(x1, s.x1)
assertEquals(x1, getX1(s.ptr))
assertEquals(x2, s.x2)
assertEquals(x2, getX2(s.ptr))
assertEquals(x3, s.x3)
assertEquals(x3, getX3(s.ptr))
assertEquals(x4, s.x4)
assertEquals(x4, getX4(s.ptr))
assertEquals(x5, s.x5)
assertEquals(x5, getX5(s.ptr))
assertEquals(x6, s.x6)
assertEquals(x6, getX6(s.ptr))
}
fun assign(s: S, x1: Long, x2: B2, x3: Short, x4: Int, x5: Int, x6: Long) {
s.x1 = x1
s.x2 = x2
s.x3 = x3
s.x4 = x4
s.x5 = x5
s.x6 = x6
}
fun assignReversed(s: S, x1: Long, x2: B2, x3: Short, x4: Int, x5: Int, x6: Long) {
s.x6 = x6
s.x5 = x5
s.x4 = x4
s.x3 = x3
s.x2 = x2
s.x1 = x1
}
fun test(s: S, x1: Long, x2: B2, x3: Short, x4: Int, x5: Int, x6: Long) {
assign(s, x1, x2, x3, x4, x5, x6)
check(s, x1, x2, x3, x4, x5, x6)
assignReversed(s, x1, x2, x3, x4, x5, x6)
check(s, x1, x2, x3, x4, x5, x6)
// Also check with some insignificant bits modified:
assign(s, x1 + 2, x2, (x3 + 8).toShort(), x4 - 16, x5 + 32, x6 + Long.MIN_VALUE)
check(s, x1, x2, x3, x4, x5, x6)
assignReversed(s, x1 + 2, x2, (x3 + 8).toShort(), x4 - 16, x5 + 32, x6 + Long.MIN_VALUE)
check(s, x1, x2, x3, x4, x5, x6)
}
fun main(args: Array<String>) {
memScoped {
val s = alloc<S>()
for (x1 in -1L..0L)
for (x2 in B2.values())
for (x3 in 0..7)
for (x4 in intArrayOf(0, 6, 15))
for (x5 in intArrayOf(-16, -2, -1, 0, 5, 15))
for (x6 in longArrayOf(Long.MIN_VALUE/2, -1L shl 36, -325L, 0, 1L shl 48, Long.MAX_VALUE/2))
test(s, x1, x2, x3.toShort(), x4, x5, x6)
}
}
@@ -0,0 +1,20 @@
---
enum B2 {
ZERO, ONE, TWO, THREE
};
struct __attribute__((packed)) S {
long long x1 : 1;
enum B2 x2 : 2;
unsigned short x3 : 3;
unsigned int x4 : 4;
int x5 : 5;
long long x6 : 63;
};
static long long getX1(struct S* s) { return s->x1; }
static enum B2 getX2(struct S* s) { return s->x2; }
static unsigned short getX3(struct S* s) { return s->x3; }
static unsigned int getX4(struct S* s) { return s->x4; }
static int getX5(struct S* s) { return s->x5; }
static long long getX6(struct S* s) { return s->x6; }