Specializing certain generic Swift types and functions (for bare metal)

I'm currently trying to compile some trivial Swift code for bare metal. It utilizes uSwift instead of the standard library, in an attempt to avoid dynamic allocations and expensive calls to Swift runtime. I was hoping I could use UnsafePointer and other pointer types to bootstrap MMU and eventually get some allocator working on bare metal, unblocking more Swift runtime features for use farther down the line. Unfortunately, looks like use of UnsafePointer still requires metadata lookups.

Consider this code in StaticString:

  /// A pointer to a null-terminated sequence of UTF-8 code units.
  ///
  /// - Important: Accessing this property when `hasPointerRepresentation` is
  ///   `false` triggers a runtime error.
  @_transparent
  public var utf8Start: UnsafePointer<UInt8> {
    _precondition(
      hasPointerRepresentation,
      "StaticString should have pointer representation")
    return UnsafePointer(bitPattern: UInt(_startPtrOrData))!
  }

Apparently, this property requires metadata lookups to call generic UnsafePointer<UInt8>(bitPattern:). I was hoping this call would be specialized, but that doesn't happen for some reason. Generated code adds references to swift_getTypeByMangledNameInContextInMetadataState and swift_getWitnessTable symbols. Adding @inlinable @inline(__always) to the declaration of this initializer doesn't help. I also tried adding @_specialize(exported: true, kind: full, where Pointee == UInt8), but apparently it can only be applied to generic functions, and this initializer isn't generic by itself.

Is there any other way to monomorphize/specialize this call to UnsafePointer(bitPattern:) at compile-time, so that metadata lookups were eliminated, supposedly by the optimizer? The actual need for utf8Start is irrelevant, I'm interested in a more general question of specializing certain generic functions when using pointer types in Swift. I'm currently not interested in using other generic types, and was hoping that pointer types were low-level enough to be usable on bare metal straight away.

For anyone interested in the actual linker error messages, I shared those on the corresponding uSwift GitHub PR.

5 Likes

There's no reason we shouldn't be able to monomorphize that call site, as long as the initializer is indeed showing up as inlinable with its body in the standard library swiftinterface file. We should make it inlinable in the real standard library if it isn't. Is your build picking up the updated swiftinterface and swiftmodule for the library when you make those changes locally? And are you building with -O? I believe @inline(__always) is still not inlined in -Onone mode. A robust "bare metal" Swift environment more than likely needs help from the compiler to require transformations that eliminate possibly-allocating operations. You might try adding a compiler mode that enforces the @noLocks mode by default across all code.

5 Likes

I guess the reason is that both utf8Start property and UnsafePointer are public and they need to cross module boundaries?

Is it even possible for publicly available generics to provide two different implementations at the same time, one specialized, and the other generic one with witness tables, metadata and all? In my case, would the public interface of the uSwift library vend both generic UnsafePointer and UnsafePointer<UInt8> specialization, inlined down to Builtin.RawPointer?

If public access modifier is the reason, I probably wouldn't mind developing everything in a single module, avoiding any public declarations. Would be rather unfortunate and inconvenient, but still doable.

I was hoping there was a more explicit way to control this. I assumed @noLocks only warns about ARC usage. Would it also emit proper diagnostics for code that may trigger metadata lookups? If the amount of inlining depends on the optimization level, would it mean that @noLocks can produce different diagnostics for different -O flags?