What is missing to replace C with Swift?

I was reading this blog post on cross-platform Swift and wondered why we still have to drop down to C in Swift packages, particularly with efforts like Embedded Swift and features like inline arrays that increasingly make Swift a competitor to C and Zig. :grinning_face:

The two issues that I've seen are that Swift's ClangImporter still cannot handle many common C preprocessor cases- if a preprocessor variable is assigned a constant, no problem, but if it has an expression that evaluates to a constant, that won't get imported automatically- and the lack of inline assembly.

What else is holding you back from writing pure Swift packages? Btw, I am not suggesting that the millions of lines of old C code will ever be rewritten in Swift or any other modern language- better to sandbox all that old code tighter- but we can still make progress on chucking out the bits of C that we've been forced to resort to in our Swift packages.

3 Likes

At the very least, being able to define bit-specific layouts is something that is not currently possible within Swift. IMO, that alone is a huge issue as it requires resorting to shim headers to define layout types.

The other thing that I find is often preventative is things like variadic functions or “unsafe” casts.

5 Likes

swift-mmio provides a nice ergonomic API to change bits in memory mapped I/O registers. FWIW, we do have a growing community using Swift on microcontrollers.

1 Like

I would be very surprised if that also supports all the behavioural differences of bitfields. Bit-fields are non-standard and the way that they work across different compilers is different. If this actually implements MSVC, GNU, Borland, Watcom, XLC, SunProCC style bit-field packing, that would certainly be interesting. The behavioural differences are part of the C ABI and thus I do think that this is not something that a package can take on - this needs to be addressed within the Clang Importer and the language level.

Edit:
DMC I believe now behaves more similarly to other compilers, but has in the past diverged. I think that HP-UX on PA-RISC might’ve had some differences.

Also, of interest would be Macintosh environment. Metrowerks CodeWarrior had flexible control over the bit-packing layouts based on pragmas.

I think the point of this discussion was replacing C with Swift. If you’re pure Swift then the ClangImporter isn’t an issue.

I’ll let @rauhul speak to the level of bit field support swift-mmio offers. Its target audience is device interface writers where the bit layout really matters.

I get that - but the point is that wen you have things like MMIO and are working with low level systems work, then you are often forced to use C libraries because the vendor SDK is going to give you that and often as a precompiled library even.

If you are trying to implement type layouts with Swift, bitfields are one of those complex pieces that I don’t know if MMIO’s implementation is sufficient and we should be relying on Clang to a certain extent to ensure that we get the ABI correct.

I think this discussion often gets a little bit confused.

If you need to match the bit layout of a type defined in C, using the C header that defines it is by far the best way to do it: one source of truth, one set of rules interpreting it, done. Happily this works in Swift today (at least, it does if the header supports clang. But if it doesn’t, we’re not really talking about “C,” we’re talking about some vendor-specific tool, and getting that to support Swift is a separate problem).

If you don’t need to match a bit layout defined in C, but rather some documented format, then the new binary parsing library (GitHub - apple/swift-binary-parsing) is much closer to the direction that we mostly want to take than the old-school C approach of defining a type’s layout and yolo-casting some bytes, since it gives us an opportunity to make the operation safe and ensure that parsing does not proceed with broken invariants. This _also_ works in Swift today (but please file feature requests for things that could be easier).

Vendor-specific “C” tooling is a real weak spot, but not one that Swift can do much about other than become popular enough in low-level usage that vendors start providing support.

20 Likes

Originally Swift wasn't applicable to realitime code, although recently we've got noAllocation and noLocs which in theory make Swift even safer than C in those regards:

compare:

void usedInRealtime() {
    malloc(0); // no complains (undesirable behaviour)
}

and

@_noLocks func usedInRealime() {
    malloc(0) // compilation error (desirable behaviour)
}

InlineArrays was one of the missing stones which is now mostly in place.

I can't think of anything else from C we are missing.

1 Like

Yeah, this does get confusing because it is a pretty messy area historically.

Maybe the fact that I’ve not spent much time looking at the new binary parser library, but that would be still decoding right? What if I’m encoding something which needs bit specific layout?

So far, the headers hatch is what has been deemed the best for those definitions (declarations as per C), and for the remaining arbitrary type casting.

2 Likes

I would expect that we (or someone else) will build analogous tooling for encoding as well; it’s less pressing because encoding generally has fewer issues with safely maintaining invariants, but also important.

1 Like

Although it's not part of standard C, the ability to specify the section into which a symbol is emitted is an important feature for embedded programming (and for Swift itself.) @kubamracek has a pitch up for an @section attribute to address that shortcoming.

3 Likes

I tried using Embedded Swift to rewrite parts of the xv6 kernel. It was a fun learning experience for me and I think is a good benchmark for your question or maybe the more specific question: “could you teach a college level class on OS design in Swift?” (instead of C). It is probably time for me to try again, but I remember one of the biggest blockers I hit was trying to implement the kernel spinlock with atomics and then needing malloc. I very easily was able to replace some basic parts of the boot sequence and all the user space binaries.

7 Likes

Oh this is so interesting, I would love to read a blog about this.

3 Likes

I’ve previously mentioned it, but something I was actually lacking is the ability to get a pointer to literal tuples and InlineArrays. i.e. store a literal value in the .data section, and get a pointer to it, either from within the code, or for use in another literal also stored in the .data section.

The tools Swift currently has require making a heap allocation, which is a problem when creating a dynamic library instead of an executable. Because you often don't have a place where you can put init code to make those heap allocations, and such init code could be subject to thread safety issues if you can't guarantee it's only called once.

4 Likes

Not exactly C, but relevant:

  • placement new of C++
  • sometimes I miss the flexibility of C preprocessor where I could put arbitrary stuff in #if #endif brackets.
3 Likes

Placement new is, effectively, supported in Swift. What isn't supported is direct storage of reference-counted objects. Compare:

auto buf = reinterpret_cast<std::string *>(std::calloc(...));
new (buf) std::string("Hello world!");
let buf = UnsafeMutablePointer<String>.allocate(capacity: 1)
buf.initialize(to: "Hello world!")
1 Like