Skip to content

Instantly share code, notes, and snippets.

@omochi
Created July 30, 2021 10:16
Show Gist options
  • Save omochi/7796a776f71b5dd704dc480ac93106e2 to your computer and use it in GitHub Desktop.
Save omochi/7796a776f71b5dd704dc480ac93106e2 to your computer and use it in GitHub Desktop.

slidenumber: true autoscale: true

今から使える

SwiftとC++の

新しいinterop手法

わいわいswiftc #29

@omochimetaru


前回登壇は#20でした

  • League of Legends 楽しい!

今日の話

C++コードをSwiftから使う


本命

  • Interoperability between Swift and C++1

  • 待てない!


libswift2

Libswift is the part of the Swift compiler, which is implemented in Swift. ... Bridging SIL between C++ and Swift is toll-free, i.e. does not involve any "conversion" between C++ and Swift SIL.


Toll-free bridge

  • あるオブジェクトをメモリ表現の変換無しでそのまま複数の言語から使う

  • CFDataRef (C言語) と NSData (Objective-C)

  • NSData (Objective-C) と Data (Swift)

  • [NEW] C++ と Swift


C++でのSILの型


ValueBase

class ValueBase : 
  public SILNode, 
  public SILAllocated<ValueBase> 
{
  SILType Type;
  inline use_range getUses() const;
}
  • ベースクラス
  • SILNode: グラフ機能 + Swift Interop
  • SILAllocated: Bumpアロケータ

SILNode

class SILNode :
  public SwiftObjectHeader {}
  • SwiftObjectHeader: 後述

SILType

class SILType {
  using ValueType = llvm::PointerIntPair<TypeBase *, 2, unsigned>;
  ValueType value;
}
  • TypeBaseはASTステージの型

SILValue

class SILValue {
  ValueBase *Value;
  SILValue(const ValueBase *V = nullptr)
}
  • Existential container

SILArgument

class SILArgument : public ValueBase {}
  • BasicBlockの引数

SILInstruction

class SILInstruction : public llvm::ilist_node<SILInstruction> {
  template <typename ContextTy>
  void *operator new(size_t Bytes, const ContextTy &C,
                   size_t Alignment = alignof(ValueBase)) {}
  SILNode *asSILNode();
}

class NonSingleValueInstruction : public SILInstruction, public SILNode {}

ABSTRACT_VALUE_AND_INST(SingleValueInstruction, ValueBase, SILInstruction)
  • 実質的にSILNodeを親に持つ。

SILBasicBlock

class SILBasicBlock :
public llvm::ilist_node<SILBasicBlock>, 
public SILAllocated<SILBasicBlock>,
public SwiftObjectHeader {
  using InstListType = llvm::iplist<SILInstruction>;
  TinyPtrVector<SILArgument *> ArgumentList;
  InstListType InstList;
}

SwiftでのSILの型


Value

public protocol Value : AnyObject {
  var uses: UseList { get }
  var type: Type { get }
}

Type

public struct Type {
  var bridged: BridgedType
}

Argument

public class Argument : Value {}

Instruction

public class Instruction {}

public class SingleValueInstruction : Instruction, Value {}

public final class MultipleValueInstructionResult : Value {}

BasicBlock

final public class BasicBlock {}

Bridgingの方法


クラスのレイアウト互換を取る

  • Swiftのclassのインスタンスは先頭にisaポインタと参照カウンタを持つ

  • C++側で互換性のある構造を持たせる

struct BridgedSwiftObject {
  SwiftMetatype metatype;
  int64_t refCounts;
}

struct SwiftObjectHeader : BridgedSwiftObject {}
  • さらにこのフィールドをなんとかする

SwiftObjectHeaderの注入

class SILNode: SwiftObjectHeader {}
class ValueBase: SILNode {}
class SILArgument: ValueBase {}

class SingleValueInstruction: ValueBase, SILInstruction {}
class NonSingleValueInstruction: SILInstruction, SILNode {}

class SILBasicBlock: SwiftObjectHeader {}
  • 何かしらSwiftObjectHeaderにたどり着く

参照カウンタを潰す

参照カウントの特定のビットを立てておくとSwiftは無視する3

/*
  HeapObject {
    isa
    InlineRefCounts {
      atomic<InlineRefCountBits> {
        strong RC + unowned RC + flags
        OR
        HeapObjectSideTableEntry*
      }
    }
  }
*/

struct RefCountBitOffsets<8> {
  /*
   ---Immortal case---
   All bits set, the object does not deallocate or have a refcount
   */
}

struct SwiftObjectHeader : BridgedSwiftObject {
  SwiftObjectHeader(SwiftMetatype metatype) {
     this->metatype = metatype;
     this->refCounts = ~(uint64_t)0;
  }
}

メタクラスを注入する

事前にSwiftから取ってきてC++に渡す

// Swift
@_cdecl("initializeLibSwift")
public func initializeLibSwift() {
  registerSILClasses()
  registerSwiftPasses()
}

private func register<T: AnyObject>(_ cl: T.Type) {
  String(describing: cl).withBridgedStringRef { nameStr in
    let metatype = unsafeBitCast(cl, to: SwiftMetatype.self)
    registerBridgedClass(nameStr, metatype)
  }
}

public func registerSILClasses() {
  ...
  register(BasicBlock.self)
  ...
  register(StoreInst.self)
  register(CopyAddrInst.self)
  register(DeallocStackInst.self)
  ...
}

// C++
SwiftMetatype nodeMetatypes[(unsigned)SILNodeKind::Last_SILNode + 1];

void registerBridgedClass(BridgedStringRef className, SwiftMetatype metatype) {
  StringRef clName = getStringRef(className);
  if (clName == "BasicBlock")
    return SILBasicBlock::registerBridgedMetatype(metatype);
  ...
  nodeMetatypes[(unsigned)kind] = metatype;
}

直接渡すかテーブルにしまう。


class SILBasicBlock {
  static SwiftMetatype registeredMetatype;

  static void registerBridgedMetatype(SwiftMetatype metatype) {
    registeredMetatype = metatype;
  }
}

SILBasicBlock::SILBasicBlock() :
  SwiftObjectHeader(registeredMetatype), Parent(nullptr) {}

class SILNode {
  SILNode(SILNodeKind kind) : SwiftObjectHeader(getSILNodeMetatype(kind)) {}
}

SwiftMetatype SILNode::getSILNodeMetatype(SILNodeKind kind) {
  SwiftMetatype metatype = nodeMetatypes[(unsigned)kind];
  return metatype;
}

Bridgingの実装


ブリッジ用の型を定義する

C実装なのでC++とSwiftから使える

typedef struct {
  void * _Nullable typePtr;
} BridgedType;

typedef struct {
  SwiftObject obj;
} BridgedValue;

typedef struct {
  SwiftObject obj;
} BridgedArgument;

typedef struct {
  SwiftObject obj;
} BridgedInstruction;

typedef struct {
  SwiftObject obj;
} BridgedBasicBlock;

struct BridgedSwiftObject {
  SwiftMetatype metatype;
  int64_t refCounts;
};

typedef struct BridgedSwiftObject * _Nonnull SwiftObject;

Nullabilityのブリッジ

typedef struct BridgedSwiftObject * _Nullable OptionalSwiftObject;

typedef struct {
  OptionalSwiftObject obj;
} OptionalBridgedInstruction;

SwiftからC++の呼び出し

final public class BasicBlock {
  public var instructions: List<Instruction> {
    List(startAt: SILBasicBlock_firstInst(bridged).instruction)
  }

  var bridged: BridgedBasicBlock { BridgedBasicBlock(obj: SwiftObject(self)) }
}

SwiftObject

SwiftObjectをSwiftで再定義。BridgedSwiftObjectは共有。

AnyObject のポインタを直変換🤩

public typealias SwiftObject = UnsafeMutablePointer<BridgedSwiftObject>

extension UnsafeMutablePointer where Pointee == BridgedSwiftObject {
  init<T: AnyObject>(_ object: T) {
    let ptr = Unmanaged.passUnretained(object).toOpaque()
    self = ptr.bindMemory(to: BridgedSwiftObject.self, capacity: 1)
  }
  
  func getAs<T: AnyObject>(_ objectType: T.Type) -> T {
    return Unmanaged<T>.fromOpaque(self).takeUnretainedValue()
  }
}

Optional

extension Optional where Wrapped == UnsafeMutablePointer<BridgedSwiftObject> {
  func getAs<T: AnyObject>(_ objectType: T.Type) -> T? {
    if let pointer = self {
      return Unmanaged<T>.fromOpaque(pointer).takeUnretainedValue()
    }
    return nil
  }
}

再掲

// Swift
final public class BasicBlock {
  var bridged: BridgedBasicBlock { BridgedBasicBlock(obj: SwiftObject(self)) }
}
// C++
struct BridgedSwiftObject {
  SwiftMetatype metatype;
  int64_t refCounts;
};
typedef struct BridgedSwiftObject * _Nonnull SwiftObject;
typedef struct {
  SwiftObject obj;
} BridgedBasicBlock;

処理のブリッジ

OptionalBridgedInstruction SILBasicBlock_firstInst(BridgedBasicBlock block) {
  SILBasicBlock *b = castToBasicBlock(block);
  if (b->empty())
    return {nullptr};
  return {b->front().asSILNode()};
}

inline SILBasicBlock *castToBasicBlock(BridgedBasicBlock block) {
  return static_cast<SILBasicBlock *>(block.obj);
}

再掲

class SILNode: SwiftObjectHeader {}
struct SwiftObjectHeader : BridgedSwiftObject {}
typedef struct BridgedSwiftObject * _Nullable OptionalSwiftObject;
typedef struct {
  OptionalSwiftObject obj;
} OptionalBridgedInstruction;

再掲

final public class BasicBlock {
  public var instructions: List<Instruction> {
    List(startAt: SILBasicBlock_firstInst(bridged).instruction)
  }

  var bridged: BridgedBasicBlock { BridgedBasicBlock(obj: SwiftObject(self)) }
}

Swift側で型付きでアクセス

extension BridgedInstruction {
  public var instruction: Instruction { obj.getAs(Instruction.self) }
  public func getAs<T: Instruction>(_ instType: T.Type) -> T { obj.getAs(T.self) }
}

extension OptionalBridgedInstruction {
  var instruction: Instruction? { obj.getAs(Instruction.self) }
}

Type

// Swift
public struct Type {
  var bridged: BridgedType
  
  public var isAddress: Bool { SILType_isAddress(bridged) != 0 }
  public var isObject: Bool { !isAddress }
}
// C++
typedef struct {
  void * _Nullable typePtr;
} BridgedType;

文字列

// C++
typedef struct {
  const unsigned char * _Nullable data;
  size_t length;
} BridgedStringRef;
// Swift
extension BridgedStringRef {
  public var string: String {
    let buffer = UnsafeBufferPointer<UInt8>(start: data, count: Int(length))
    return String(decoding: buffer, as: UTF8.self)
  }
  
  func takeString() -> String {
    let str = string
    freeBridgedStringRef(self)
    return str
  }
}

callerに解放責任が渡る場合がある

inline BridgedStringRef getBridgedStringRef(llvm::StringRef str) {
  return { (const unsigned char *)str.data(), str.size() };
}

/// Copies the string in an malloc'ed memory and the caller is responsible for
/// freeing it.
inline BridgedStringRef getCopiedBridgedStringRef(std::string str,
                                           bool removeTrailingNewline = false) {
  // A couple of mallocs are needed for passing a std::string to libswift. But
  // it's currently only used or debug descriptions. So, its' maybe not so bad -
  // for now.
  // TODO: find a better way to pass std::strings to libswift.
  StringRef strRef(str);
  if (removeTrailingNewline)
    strRef.consume_back("\n");
  llvm::MallocAllocator allocator;
  StringRef copy = strRef.copy(allocator);
  return getBridgedStringRef(copy);
}

public protocol Value {}

extension Value {
  public var description: String {
    SILNode_debugDescription(bridgedNode).takeString()
  }

  var bridgedNode: BridgedNode {
    BridgedNode(obj: SwiftObject(self as AnyObject))
  }
}

インスタンス生成

専用のBuilderを通してC++側で生成する

final public class BuiltinInst : SingleValueInstruction {}

public class SingleValueInstruction : Instruction, Value {}

public class Instruction {}

public struct Builder {
  public func createBuiltinBinaryFunction(name: String,
      operandType: Type, resultType: Type, arguments: [Value]) -> BuiltinInst {
    notifyInstructionsChanged()
    return arguments.withBridgedValues { valuesRef in
      return name.withBridgedStringRef { nameStr in
        let bi = SILBuilder_createBuiltinBinaryFunction(
          bridgedInsPoint, location.bridgedLocation, nameStr,
          operandType.bridged, resultType.bridged, valuesRef)
        return bi.getAs(BuiltinInst.self)
      }
    }
  }
}

まとめ

  • C++側でSwiftのオブジェクトヘッダを継承させる
  • メタタイプは頑張ってSwiftから渡す
  • 参照カウンタをimmortalモードにしてC++でメモリ管理する
  • C構造体を使ってブリッジインターフェースに型をつける

Footnotes

  1. https://github.com/apple/swift/blob/main/docs/CppInteroperabilityManifesto.md

  2. https://github.com/apple/swift/tree/main/libswift

  3. https://github.com/apple/swift/blob/main/stdlib/public/SwiftShims/RefCount.h

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment