Should anyone be using @_silgen_name outside of the standard library developers?

Hi all,

I modified some Swift code today for use as a PAM module. Adding to my concern about code size for a Swift *.so, I also need to use @_silgen_name so that I can expose non-mangled names to libpam.

For example:

@_silgen_name("pam_sm_acct_mgmt")
public func pam_sm_acct_mgmt(pamh: pam_handler_t, flags: Int, argc: Int, argv: vchar) -> Int {
return PAM_IGNORE
}

I'm troubled though by that underscore naming and the fact that it was changed from @asmname to @_silgen_name between Swifts 1 and 2.

Is there any other interop tool I should be using to accomplish the same task? If not I'll just bang it over to a C based language and call it a day.

Thanks,
Josh

@_silgen_name only changes the symbol mangling. The function will still use Swift's calling convention, which is likely to be incompatible with what libpam expects. You'd be better off using @_cdecl("pam_sm_acct_mgmt"); although not officially supported either, it's more likely to be in the future, and not only exports the unmangled symbol but does so with the correct calling convention (and if you're mixing Swift and C in a module, also declares the function in the generated header for the C half of your project to use).

6 Likes

Thanks for the confirmation. Given that there isn't a supported way to export the symbols I'll probably just redo it in Obj-C. I'm not going to ship commercial software with unsupported code in it. :slight_smile:

FWIW the PAM module works flawlessly with @_silgen_name as it stands today on shipping macOS and Swift.

Thanks again.

If it's not a huge bother to rewrite in ObjC, that's certainly the most robust and forward-compatible thing. If a @_silgen_name-d symbol happens to be ABI compatible with a call from a C function, that's purely luck.

It's not any trouble as the entire PAM module part of the code is about 20 lines long, I'm just authorizing a custom right on the Mac, sorta like the built-in TouchID PAM module does. Our Authorization Plugin does all the heavy lifting, PAM is just a trigger.

As a side benefit, I won't end up with a 10MB *.so.2 file from those 20 lines either. :smile:

2 Likes

Our Authorization Plugin does all the heavy lifting …

Ah um, you’re writing that in Swift? If so, that’s a bad idea. Authorisation plug-ins are loaded directly into various system processes and, as such, can’t be written in Swift.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

Hey Quinn,

Although this is better suited to the Apple Dev forums, I'll reply in the thread here.

The actual mech interface is a C class (Based on some of your samples actually). Nearly everything else is done in Swift for us. We also do DNS and some Kerberos stuff in C, but that's about it.

Our stuff is deployed at scale by many customers as are a few other mostly-Swift authorization plugins. With the PAM module I was looking for ways to do the C interop with the system directly from Swift, but as this thread shows we aren't there yet. It's not a worry for me as I speak Obj-C as well.

Thanks,
Josh

I think we should eventually support both:

  • @_silgen_name but only for defining external symbols (maybe with the @extern syntax).
  • @_cdecl as @interop(c) (+ deprecating @objc in favor of @interop(objc))
@extern(c)
func external_symbol_from_linked_library(_ value: Int) -> CChar

@interop(c) // Exposed to C as `char * funcExposedToC(int value)`
func funcExposedToC(_ value: Int) -> String {
    ...
}

@interop(objc) // Exposed to Objective-C as `NSString * funcExposedToObjC(int value)`
func funcExposedToObjC(_ value: Int) -> String {
    ...
}
2 Likes

There's no reason for anyone outside of the standard library to use @_silgen_name. Even there, the reasons are legacy and not fundamental.

I think it is useful to be able to define symbols inline in Swift without having to rely on an external C header. And in the case of Swift symbols even that would not be enough.

1 Like

The actual mech interface is a C class (Based on some of your samples
actually). Nearly everything else is done in Swift for us.

This is quite worrying. In the absence of ABI compatibility, all Swift code running within your process must be built with the same Swift compiler. That’s not something your can guarantee with an authorisation plug-in, because the relevant host processes (SecurityAgent and authorizationhost) can load arbitrary plug-ins.

The one saving grace, I guess, is that authorisation plug-ins are generally deployed to managed environments, where the site admin has control over what plug-ins get installed.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

We are reliant on @_silgen_name to “forward-declare” certain C functions that are defined externally and linked dynamically. This is pretty terrifying, I know, but last I checked we had no better option because @_cdecl requires an inline function definition.

On that note, is there any plan to allow @_cdecl or any future spelling to apply to static members of Swift types? That was a useful way of namespacing some inevitable global symbols that Java forces us to use in the Swift world on Android. That only worked with @_silgen_name though, which is no longer an option for obvious calling convention reasons.

We are reliant on @_silgen_name to “forward-declare” certain C functions that are defined externally and linked dynamically. This is pretty terrifying, I know, but last I checked we had no better option because @_cdecl requires an inline function definition.

You can't do that. Is there a reason importing a C header doesn't work for this purpose? That's the intended way to work with external C definitions.

On that note, is there any plan to allow @_cdecl or any future spelling to apply to static members of Swift types? That was a useful way of namespacing some inevitable global symbols that Java forces us to use in the Swift world on Android. That only worked with @_silgen_name though, which is no longer an option for obvious calling convention reasons.

@_cdecl could conceivably work on static members at some point in the future, sure.

@Joe_Groff thanks, yes I realise it’s not a good or sustainable idea, but we’ve been relying on it for years and it’s been “fine”.

It’s possible we could import an extra header, I haven’t looked at the specifics of that code in a while. I think originally we were relying on an external module that didn’t expose this particular functionality in its public headers and didn’t want to add an entire module just for this.

Other than the calling convention (which is important, but only because @_cdecl doesn’t work here), is there a philosophical reason importing a single function from an external header is preferred over just forward-declaring the function in Swift? From what I can see, both are as theoretically unsafe as one another, but one involves significantly more overhead than the other (module map, header, import statement, compiler flags).

Like many things of this sort, it'll be "fine" until it's not, and there are a number of reasons @_silgen_name could stop being fine for this purpose. As you noted, Swift's calling convention could diverge from C's on some platform, or we could rename or remove the attribute, or change its symbol mangling behavior, since it's only intended to be used by the standard library.

1 Like

I think it is clear that using a private attribute is unsafe but I do think Swift should expose a public attribute for defining C (and other languages) external symbols inline, using a bodiless @_cdecl / @interop(c), @extern(c) or whatever.

1 Like

I understand why you want to discourage this (I think) but, without ABI stability, the prospect of Swift changing and us having to refactor and recompile certain things has been a given.

I think I understood the only alternative is the header/modulemap/import statement/compiler flag combination? I’m prepared to accept that this may be the case but, until “it’s not”, @_silgen_name is enticing by comparison (especially when we’re talking about a single function). For us we’re weighing a minor inconvenience against a less minor inconvenience.

Is there a reason importing a C header doesn't work for this purpose? That's the intended way to work with external C definitions.

Well, it doesn't play nice with Playgrounds, at the least. I would love to have the option available out of convenience.

It seems like Playgrounds could reasonably support bridging headers and/or embedded C modules.

That would be a welcome feature for sure!

Why is importing a C header the only "intended" way to work with external C definitions, though? Regarding your original comment about @_cdecl, is it not also likely we'll see a similar attribute that behaves like @_silgen_name? (in that it doesn't require a function definition)

The convenience goes beyond my Playgrounds example, but stems from the same line of thought: it alleviates you from needing to set up a bridging header for what might otherwise be a very simple project. It's a small convenience, but it goes a long way. I'd love to be able to avoid using bridging headers at all someday, if I had the option.