Bring a custom global actor into C?

So was able to access a global C variable from the MainActor by updating my C code to either of the following

__attribute__((swift_attr("@UIActor"))) extern int mainActorGlobal;

or

__attribute__((swift_attr("@MainActor"))) extern int mainActorGlobal;

tested by accessing them from a static func main() async throws { in a Swift Package Manager based executable and also by doing

@MainActor
struct GlobalGetter {
    var testFetch: CInt {
        mainActorGlobal
    }
}

I'd like to do something similar with a custom global actor, but I reached a sticking point.

When I use the below, the attribute doesn't work from main(), as expected (they don't without @UIActor), but the getter works just fine... which shouldn't? because it should be isolated now?

And then when I make an object and isolate it to that custom Global Actor, it still can't grab the C Global that I isolated to that same actor?

In the C Header


__attribute__((swift_attr("@CInterfaceActor"))) extern int customActorGlobal;

int custom_actor_getter()
__attribute__((swift_attr("@CInterfaceActor")));

In the Swift file

import BackingC

@globalActor actor CInterfaceActor : GlobalActor {
    static let shared: CInterfaceActor = CInterfaceActor()

    public init() {}
    
}

@CInterfaceActor struct IsolatedCounter {
     // //Reference to var 'customActorGlobal' is not concurrency-safe because it involves shared mutable state
    //var counterAlias:CInt {
    //    customActorGlobal
    //}

    func getValue() -> CInt {
        custom_actor_getter()
    }

}

It doesn't surprise me that the custom global actor doesn't work out of the gate but I'm not entirely sure where to go from here.

I'm playing around with these ideas in this repo on this branch:

(Thank you to the folks in the WWDC help session folks who pointed me down this path this morning! apinotes are going to take a bit longer get working so I thought I'd go for the easier win temporarily)

1 Like

Rewriting this reply as I need better glasses, apparently.

I suspect the compiler is not correctly importing the swift_attr attribute and applying actor isolation as intended. This may be in part because C modules are imported as if with @preconcurrency, which suppresses most actor isolation diagnostics for symbols when they are accessed from a synchronous Swift caller.

You might need to give up on applying actor isolation directly to the C symbols if that's the case, but you can use __attribute__((swift_private)) to mark a symbol as hidden-but-callable-from-Swift:

__attribute__((swift_private)) extern int customActorGlobal;

__attribute__((swift_private)) static inline int custom_actor_getter(void) {
  return customActorGlobal;
}

swift, unavailable tells the Swift compiler that this symbol cannot be used from Swift in any context. swift_private tells Swift to import the symbol with two leading underscores so that it is not readily visible to Swift callers, but it can still be called. Then, anybody reading your code and seeing underscores where they don't belong can go get the Grand Whiffle Bat of Pull Request Justification™.

So then, you'd write a wrapper in Swift around the renamed C function, and all access to the shared resource in Swift would be via that wrapper:

@CInterfaceActor var customActorGlobal: CInt {
  __custom_actor_getter()
}

(Adding a setter is left as an exercise for the reader.)

1 Like

ETA: read the update!
If I switch to CMake / apply cSettings in the Package.swift can I turn off @preconcurrency?

I think maybe the compiler is just ignoring all my swift_attr("@CInterfaceActor") decorations? They don't seem to behave differently than undecorated ones, where the @UIActor ones do seem to be genuinely isolated.


ooo... i like the private. added.

C code now reads (added new func)

//left off the private just to compare
int custom_actor_getter()
__attribute__((swift_attr("@CInterfaceActor")));

__attribute__((swift_attr("@CInterfaceActor"), swift_private)) 
extern int customActorGlobal;

//new
int main_actor_getter() 
__attribute__((swift_attr("@UIActor")));

//new
int plain_getter(); 

It is!

    //must comment out to compile
    @CInterfaceActor var customActorGlobal: CInt {
        //complains that is not concurrency safe. 
       //"Reference to var '__customActorGlobal' is not concurrency-safe because it involves shared mutable state"
        __customActorGlobal
     }

     //must comment out to compile
    @CInterfaceActor var fromMainActorGetter: CInt {
        //rightfully complains that this is main actor isolated so can't be here. 
        //"Call to main actor-isolated global function 'main_actor_getter()' in a synchronous global actor 'CInterfaceActor'-isolated context"
        main_actor_getter()
     }

    @CInterfaceActor var fromPlainGetter: CInt {
        //does not care. 
        plain_getter()
     }

     @MainActor var fromCustomActorGetter: CInt {
        //does not care.
         custom_actor_getter()
     }

@main struct ExploreGlobals {
    //see OP for code for IsolatedCounter()
    static let counter = IsolatedCounter()

    static func main() async throws { 
         // does not complain because is main actor isolated in C
         print(mainActorGlobal)

        //complains because not main actor, must comment out to compile
        print(__customActorGlobal)

        //does not complain. 
        let directCall = custom_actor_getter()
        print(directCall)
       
       //requires await as expected. 
       print(await fromPlainGetter)

         let counterValue = await counter.getValue()
        print(counterValue)
    }
}

So I stalked the swiftlang github repo with

org:swiftlang path:*.h swift_attr("@

and the only thing I found that used the word actor in its name that wasn't @UIActor or @Mainactor was a C++ struct being cast as an actor:

struct __attribute__((swift_attr("@actor")))
__attribute__((swift_attr("import_reference")))
__attribute__((swift_attr("retain:immortal")))
__attribute__((swift_attr("release:immortal"))) MultipleAttrs {
  int test() const { return 42; }
  int testMutable() { return 42; }

  static MultipleAttrs *create() {
    return new (malloc(sizeof(MultipleAttrs))) MultipleAttrs();
  }
};

So bringing custom actors into C may not really be explored territory. Looks like APInotes are actually more common in the code base, but I haven't seen any examples of those addressing concurrency attributes.

I'll keep tweaking things, this will already yield a bunch of improvements.

1 Like