Swift passing type info to array

Hi,

I'm calling Swift code from my custom compiler and am trying to find out when type info is passed.

public struct GenC<T> {
  public var val: T!;
  public subscript(i: Int) ->  T {
    get { return val; } set(value) { val = value; }
  }
}

This is passed as:

  call swiftcc void @"$s5carlo4GenCVyxSicis"(%swift.opaque* noalias nocapture %9, i64 123, %swift.type* %11, %T5carlo4GenCV* nocapture swiftself %12)
  call swiftcc void @"$s5carlo4GenCVyxSicig"(%swift.opaque* noalias nocapture sret %24, i64 321, %swift.type* %11, %T5carlo4GenCV* noalias nocapture swiftself %25)

ie a pointer to the struct is passed last, as swiftself.
and the typeof for the generic instance (GenC<Int> in my case) is passed before that.

However, what I notice for array of int is this:

call swiftcc void @"$sSayxSicig"(%swift.opaque* noalias nocapture sret %9, i64 1, %swift.bridge* %0, %swift.type* @"$sSiN")

So it passes a pointer for the return value as sret, i64 is the index, %0 is the subvalue (why isn't this swiftself?) and the subtype is passed as last arg. Can anyone explain why the subtype is used and why it's passed last instead of 1 before last, and why SwiftSelf is missing?

Related follow up:

Why does:

import Foundation;

public struct StructTest<T> {
  public var x: T

  public func Fred(_ a: Int, _ b: StructTest<T>) {}
}


public func Wiliam() {
  var q = StructTest<Double>(x: 15);
  q.Fred(15, q);
}

The ctor call for StructTest get typeof Double passed, while Fred gets typeof StructTest<Double> passed?

Not sure what the confusion is. In your specification, StructTest, when instantiated, requires a Double value to initialize x. StructTest.Fred takes an integer and a StructTest as parameters, exactly what the compiler is doing.

My confusion is when to use a full instantisted type, and when to pass generic parameters separately. Both for array, the original case and the next case the compiler does different things in different cases. I'm trying to find out what the rule is so I can do the same.

You can look at the PolymorphicConvention class in the Swift compiler:

The compiler tries to avoid passing metadata for things it can derive from the standard parameters to the function, as you can see by the various consider* calls in the PolymorphicConvention constructor. For indirect generic arguments, it also introduces a metadata argument for the type of the value being passed, since it is likely the callee will need that metadata in order to manipulate values of the type:

1 Like

Thanks that helps a lot. The only thing I can't infer from this is the array logic though:

call swiftcc void @"$sSayxSicig"(%swift.opaque* noalias nocapture sret %50, i64 0, %swift.bridge* %41, %swift.type* @"$sSiN")
that's "myarray[0]" for type [Int], but whatever I try to do:


public struct StructTest<T> {
  var q: T
}

public extension StructTest {
   public subscript(index: Int) -> T { return q; }

For my own subscripts it always like (even in an extension):
call swiftcc void @"$s5carlo10StructTestVyxSicig"(%swift.opaque* noalias nocapture sret %26, i64 0, %swift.type* %28, %T5carlo10StructTestV* noalias nocapture swiftself %29)

So whatever I do, I'm getting the self as "swiftself", but array doesn't.

Your struct's layout is dependent on its generic argument, because the q: T property is stored in-line. This means that, in unspecialized code, it gets passed indirectly, and so we add the GenericLValue type argument to the parameter list of the function. For Array, its layout is always fixed, since regardless of the element type the representation is a pointer to the array buffer, so we don't add the argument for the whole type metadata. Since there are no other arguments to get the Element type metadata from, we fall back to adding a new type argument for it.

If you changed StructTest so that its layout was not dependent on the type argument, say by changing q's type to UnsafePointer<T> or something like that, then you should see it have similar behavior to Array's methods.

Am I the only one who finds it insane/scary that none of this is cleanly spec'ed somewhere, for a compiler that's ostensibly to be used for production-work development on one of the worlds dominant computing platforms?

2 Likes

You’re not the only one who would like more formal specifications — but to be fair, Swift hasn’t undergone any kind of formal standardisation process like ANSI C/ISO C(++)/ECMAScript. So there isn’t any standard for 3rd-party compiler implementors to follow, beyond what the official compiler does.

FWIW, I couldn’t find a 3rd party Rust compiler, either. You might find similarly underspecified holes if you tried to write a compiler for that language, too.

See this post on Stack Overflow: Python not a standardized language? - Stack Overflow

The big difference with Rust is that there aren't new platform apis written in Rust only for iOS / MacOS so any other compiler for iOS / MacOS would be required to interact with it.

Also I'm not looking for the standard for the language itself. But how to interact with it.

1 Like

Right. it's not so much about the language being standardized, this about the ABI, which — by definition — is a specification of how the binary interface between separate pieces of software compiled for it works. You'd expect that to be formally or informally documented beyond "it's whatever the current t compiler code does". Because sooner or later someone's gonna change that code.

There may not be a written spec yet, but the calling convention code was designed and implemented the way it is intentionally, and we’re not going to change it on existing platforms.

I doubt marc meant to change it, from what he wrote. But to have it written down so that nobody accidentally changes it.

Something like:


public struct StructTest<T> {
  var q: UnsafePointer<T>
}

public extension StructTest {
   public subscript(index: Int) -> T { return q.pointee;
 }

}

Still uses:
define swiftcc void @"$s5carlo10StructTestVyxSicig"(%swift.opaque* noalias nocapture sret, i64, %swift.type* %"StructTest<T>", %T5carlo10StructTestV* noalias nocapture swiftself dereferenceable(8)) #0 {

It's (complex, at that) code, so by definition it's going to have bugs. When you hit a bug — what defines "correct behavior", if the code is the spec?

1 Like

Ah, if you have library evolution enabled, then because StructTest is public it's going to be assumed to be ABI-resilient by default and still passed indirectly. Marking it as @frozen, making it internal, or disabling library evolution should make it use the direct calling convention.

Practically speaking, even if there were a spec, the code would define the behavior, because we can't break existing binaries compiled with older compilers. For instance, Clang has x86_64 C ABI bugs that are intentionally not fixed on Darwin to maintain ABI compatibility.

I'm going to be chewing on the implications of that statement for quite a while.........

2 Likes