It fixes "0, 0, 0" to be desired "1, 2, 3" but it is still returning heap memory address to me on macOS + debug. (It returns the desired static data address on macOS + release, FTM).
Yeah because the array storage is allocated on the heap. The global just stores the pointer to the heap allocation.
In this case it seems like the object should be promotable to global storage, and subsequently that the one-time initializer and indirection in the global variable can also be optimized away. I'm not sure why that happens when the literal appears in a local context but not when assigned to a global variable.
Can you elaborate?
Are we talking simply about making the array-as-static optimization a guarantee, or some new constructs that can be usable for this?
And does it include something for more complex cases, like static pointers inside the data (Could currently be represented with a tuple, but not with an array)?
This optimization does occur for globals in -O
, but in -Onone
this does not happen and the array is instead lazily initialized (meaning we do a heap alloc and the global just stores this pointer).
More specifically, the array object is still lazily initialized in -O
, but we don't do a heap alloc, we have a dedicated static initialized object that we initialize the array with on program entry.
let array = [1, 2, 3]
gives me:
main:
push rax
lea rdi, [rip + (demangling cache variable for type metadata for Swift._ContiguousArrayStorage<Swift.Int>)]
call __swift_instantiateConcreteTypeFromMangledName
lea rsi, [rip + mainTv_+8]
mov rdi, rax
call swift_initStaticObject@PLT
mov qword ptr [rip + (output.array : [Swift.Int])], rax
xor eax, eax
pop rcx
ret
output.array : [Swift.Int]:
.zero 8
mainTv_:
.zero 8
.zero 16
.quad 3
.quad 6
.quad 1
.quad 2
.quad 3
Wow, good to know... So it is still O(n)
instead of O(1)
, even though it's not using heap... Do you know a way to distinguish static data memory mapped from executable and this static data initialised upon the app launch? Will check if could get that info from vmmap
.
If you're referring to the initialization, that has been optimized into a global constant. That's the
in the assembly.
Do you know why?
Looking at swift_initStaticObject
, it seems to set a metadata pointer and initialise an immortal refcount. I'm assuming the refcount layout is ABI already so that could be done at compile-time. Can the metadata pointer not also be derived?
Yeah, I was looking for an alternative way of figuring that out, e.g. when you only have the pointer itself but not the code. It looks like region type (in vmmap
) is a good indicator:
// C:
const char data[] = { ... } // __TEXT
char data[] = {...} // __DATA
// Swift:
var data = (42, 24, ... ) // __DATA
Changing the above Swift's "var data" to "let" doesn't place the bytes in a readonly memory unlike what happens in C.
let bytes: (B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B) = (
0x86, 0x73, 0xF1, 0xDE, 0x63, 0x19, 0x50, 0x27, 0x66, 0x39, 0x72, 0x04, 0x58, 0x29, 0x32, 0x46
)
let f = findBytes() // this searches app memory for the above byte pattern
let ptr = UnsafeMutablePointer<UInt8>(bitPattern: f)!
print(String(bytes.2, radix: 0x10)) // f1
ptr[2] = 0x42 // no crash here compared to C
print(String(bytes.2, radix: 0x10)) // 42
Yeah, in this snippet the metadata pointer is being allocated at runtime. I thought we could specialize metadata, but it just seems to not be kicking in or maybe it only kicks in for the defining module to create.
So, erm, what's the conclusion on this? Should I write a pitch? If so, what would be the direction? New syntax? Attribute? Change of semantics?