Importing C structs with non-trivially copyable (i.e., ARC pointer) fields

I've been tinkering with some basic C++ interop recently in the compiler, and it was suggested that I try to pick off non-trivially copyable C structs as an easier-to-chew task that still makes some progress in that overall direction.

I've changed ClangImporter (draft PR) to generate the following Swift interface for a C struct:

// C struct
typedef struct {
  int intValue;
  double doubleValue;
  MyClass* unqualifiedObject;
  __strong MyClass* strongObject;
  __weak MyClass* weakObject;
  __unsafe_unretained MyClass* unsafeUnretainedObject;
} MyStruct;
// Swift interface, according to swift-ide-test
struct MyStruct {
  var intValue: Int32
  var doubleValue: Double
  var unqualifiedObject: MyClass!
  var strongObject: MyClass!
  weak var weakObject: @sil_weak MyClass!
  var unsafeUnretainedObject: Unmanaged<MyClass>!

  init()
  init(
    intValue: Int32, doubleValue: Double, unqualifiedObject: MyClass!,
    strongObject: MyClass!, weakObject: MyClass!,
    unsafeUnretainedObject: Unmanaged<MyClass>!)
}

(I kept the existing Unmanaged-wrapping behavior for __unsafe_unretained fields, though I wonder if using the unowned(unsafe) storage type would also be appropriate here. That would be source-breaking, though.)

With that done, I need some advice on how to handle the SIL bits that need to be changed. Clang emits struct-layout-derived functions to act as the default-constructor, assignment, and destructor functions for these types (among others) which handle the necessary retains/releases for those fields.

My thinking so far is this:

  • Instead of generating an init() that calls the zeroInitializer built-in, I need to generate and call __default_constructor_<layout>.
  • The memberwise init should call __default_constructor_<layout> before setting the fields.
  • Value assignment should call __copy_assignment_<layout> instead of the SIL assign instruction.
  • __destructor_<layout> needs to be called before the SIL destroy_value instruction.

At the IRGen level, it seems like I could use Clang's CodeGen to emit these special functions, and the nice thing about Swift is that I might be able to emit them in the context of the struct itself (as opposed to C, where they are emitted as static functions in the context of the call sites due to the lack of ODR).

But I'm unsure what to do at the SILGen level to glue things together between the imported Swift interface and the code I want Clang to generate (if that's what I should be doing here).

Am I on the right track here?

It seems like SIL shouldn't need to know about this, if we can use those implicit operators as the value witness operations for the type. Then only IRGen needs to know about them in order to lower copy_value, destroy_value, and fill out the value witness table for the type.

5 Likes

Thanks, after using that as a starting point to dig around in the code base anymore, that makes more sense. (This is my first time wading this far into the deep-end of codegen, so ELI5 is appreciated :slight_smile: )

If I throw together my own example struct and compile that down to IR, I can see the swift.vwtable that gets generated for it, along with the functions it points to. It looks like IRGen/{GenOpaque,GenValueWitness}.cpp is where I need to start poking around, by having it generate different witness functions for values that are non-trivially-copyable C structs?

1 Like

What you'd want to do is implement a new TypeInfo kind for nontrivial C types. By overriding TypeInfo's methods, you should be able to get the correct codegen for value witness tables and SIL-level operations to just fall out.

3 Likes

One thing that stands out: we don't actually have a model for ObjC __weak in Swift or SIL. It should be easy to add if you want to get some experience doing SIL & IRGen work.

Also unqualified objects shouldn't happen; I don't think we ever want to parse ObjC in non-ARC mode.

1 Like

Technically, two new TypeInfo classes will be needed, depending on whether the C struct is loadable (RecordDecl::canPassInRegisters()). Non-loadable C structs will also need a tweak to SIL-level type classification.

2 Likes

Good point. You might be able to follow the same CRTP pattern we use for StructTypeInfoBase etc., to allow common code to be shared among the loadable and non-loadable variants.

1 Like

Right. It should probably also share as much code as possible with the existing implementation for trivial C structs.

1 Like

Thanks! I'll look into the uses of TypeInfo and work on adding my own.

Definitely part of my educational goal. What's currently missing here?

We need to teach IRGen how to use the objc_*Weak entrypoints described in the ARC spec. IRGen normally takes its cues from the SILType of a value, and since the ARC functions aren't intercompatible with any of the Swift weak-reference schemes, that means we also need a new kind of ReferenceStorageType (probably spelled ObjCWeakStorageType) and a corresponding ReferenceOwnership. Fortunately, most of the boilerplate for that should just fall out from the reference-storage metaprogramming that @DaveZ added.

1 Like
Terms of Service

Privacy Policy

Cookie Policy