Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-12773] cross module construction of an ExpressibleByArrayLiteral thing seems to always heap reify the array #55218

Open
weissi opened this issue May 10, 2020 · 2 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself performance

Comments

@weissi
Copy link
Member

weissi commented May 10, 2020

Previous ID SR-12773
Radar rdar://problem/63068408
Original Reporter @weissi
Type Bug

Attachment: Download

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, Performance
Assignee None
Priority Medium

md5: 97df26e87ca67dd66496b98d58ca76d2

Issue Description:

Consider the following basic ExpressibleByArrayLiteral data structure:

public struct Foo: Equatable {
    @usableFromInline var optionA: Bool
    @usableFromInline var optionB: Optional<Int>

    public typealias ArrayLiteralElement = FooElement

    public struct FooElement: Equatable {
        @usableFromInline enum Backing: Equatable {
            case a
            case b(Int)
        }

        @usableFromInline var backing: Backing

        @inlinable internal init(backing: Backing) {
            self.backing = backing
        }

        public static let optionA = FooElement(backing: .a)

        @inlinable
        public static func optionB(_ x: Int) -> FooElement {
                return FooElement(backing: .b(x))
        }
    }
}

basically a slightly fancier OptionSet. For a nice API, we could make this ExpressibleByArrayLiteral with

extension Foo: ExpressibleByArrayLiteral {
    @inlinable
    public init(arrayLiteral things: FooElement...) {
        self.optionA = false
        self.optionB = nil
        for thing in things {
            switch thing.backing {
                case .a:
                    self.optionA = true
                case .b(let x):
                    self.optionB = x
            }
        }
    }
}

Problem

This all works totally fine but calling public init(arrayLiteral things: FooElement...) heap allocates (even when inlined) an array. The array doesn't escape so this should worst case stack allocate.

So when driving this from an example function

@inline(never)
func XXX_CHECK_XXX() -> Foo {
    return [.optionA, .optionB(0xbeef), .optionA]
}

from another module, we get the following code generated:

TestApp.XXX_CHECK_XXX() -> Foo.Foo:
0000000100001bf0        pushq   %rbp
0000000100001bf1        movq    %rsp, %rbp
0000000100001bf4        pushq   %r15
0000000100001bf6        pushq   %r14
0000000100001bf8        pushq   %rbx
0000000100001bf9        pushq   %rax
0000000100001bfa        leaq    demangling cache variable for type metadata for Swift._ContiguousArrayStorage<Foo.Foo.FooElement>(%rip), %rdi
0000000100001c01        callq   ___swift_instantiateConcreteTypeFromMangledName
0000000100001c06        movl    $0x50, %esi
0000000100001c0b        movl    $0x7, %edx
0000000100001c10        movq    %rax, %rdi
vvvvvvvvvvvvv vvvvvvvvvvvvv vvvvvvvvvvvvv vvvvvvvvvvvvv
0000000100001c13        callq   0x1000041b8 ## symbol stub for: _swift_allocObject
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ 
0000000100001c18        movq    %rax, %rbx
0000000100001c1b        movaps  0x29de(%rip), %xmm0
0000000100001c22        movups  %xmm0, 0x10(%rax)
0000000100001c26        callq   Foo.Foo.FooElement.optionA.unsafeMutableAddressor : Foo.Foo.FooElement
0000000100001c2b        movq    (%rax), %rcx
0000000100001c2e        movb    0x8(%rax), %r14b
0000000100001c32        movq    %rcx, 0x20(%rbx)
0000000100001c36        movb    %r14b, 0x28(%rbx)
0000000100001c3a        movq    $0xbeef, 0x30(%rbx)
0000000100001c42        movb    $0x0, 0x38(%rbx)
0000000100001c46        movq    %rcx, 0x40(%rbx)
0000000100001c4a        movb    %r14b, 0x48(%rbx)
0000000100001c4e        testb   %r14b, %r14b
0000000100001c51        movl    $0xbeef, %r15d
0000000100001c57        cmoveq  %rcx, %r15
0000000100001c5b        movq    %rbx, %rdi
0000000100001c5e        callq   0x1000041dc ## symbol stub for: _swift_release

(CMO also doesn't help).

how to show the allocations

Test project attached, repro is

swift build -c release
sudo dtrace -n 'pid$target::malloc:entry { @calls[ustack()] = count(); } :::END { printa(@calls); }' -c .build/release/TestApp

output is

[...]
              libsystem_malloc.dylib`malloc
              libswiftCore.dylib`swift_slowAlloc+0x19
              libswiftCore.dylib`swift_allocObject+0x27
              TestApp`XXX_CHECK_XXX()+0x28
              TestApp`main+0x25
              libdyld.dylib`start+0x1
              TestApp`0x1
             1000

which shows 1000 allocations from that stack (because I call XXX_CHECK_XXX 1000 times).

more info

This does not reproduce if everything's copied into one module. The code is then

test.XXX_CHECK_XXX() -> test.Foo:
0000000100001410        pushq   %rbp
0000000100001411        movq    %rsp, %rbp
0000000100001414        pushq   %r15
0000000100001416        pushq   %r14
0000000100001418        pushq   %r13
000000010000141a        pushq   %r12
000000010000141c        pushq   %rbx
000000010000141d        subq    $0x58, %rsp
0000000100001421        leaq    demangling cache variable for type metadata for Swift._ContiguousArrayStorage<test.Foo.FooElement>(%rip),
 %rdi
0000000100001428        callq   ___swift_instantiateConcreteTypeFromMangledName
000000010000142d        leaq    -0x78(%rbp), %rsi
0000000100001431        movq    %rax, %rdi
vvvvvvvvvvvvvvv vvvvvvvvvvvvvvv vvvvvvvvvvvvvvv vvvvvvvvvvvvvvv
0000000100001434        callq   0x100001c58 ## symbol stub for: _swift_initStackObject
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
0000000100001439        movq    %rax, %r12
000000010000143c        movq    %rax, %r14
000000010000143f        movaps  0x8aa(%rip), %xmm0
0000000100001446        movups  %xmm0, 0x10(%rax)
000000010000144a        movl    $0x3, %edi
000000010000144f        cmpq    $-0x1, 0x1c19(%rip)
0000000100001457        jne     0x100001552
000000010000145d        movq    static test.Foo.FooElement.optionA : test.Foo.FooElement(%rip), %r15
0000000100001464        movb    0x1e1e(%rip), %al
000000010000146a        movq    %r15, 0x20(%r14)
000000010000146e        movb    %al, 0x28(%r14)
0000000100001472        movq    $0xbeef, 0x30(%r14)
000000010000147a        movb    $0x0, 0x38(%r14)
000000010000147f        movq    %r15, 0x40(%r14)
0000000100001483        movb    %al, 0x48(%r14)
0000000100001487        testq   %rdi, %rdi
000000010000148a        je      0x1000014a7
000000010000148c        movb    0x28(%r14), %r13b
0000000100001490        xorl    %eax, %eax
0000000100001492        testb   %r13b, %r13b
0000000100001495        cmovneq %rax, %r15
0000000100001499        cmpq    $0x1, %rdi
000000010000149d        jne     0x1000014b4
000000010000149f        movl    %r13d, %ebx
00000001000014a2        jmp     0x10000152f
00000001000014a7        movb    $0x1, %r13b
00000001000014aa        xorl    %ebx, %ebx
00000001000014ac        xorl    %r15d, %r15d
00000001000014af        jmp     0x100001533
00000001000014b4        leal    -0x1(%rdi), %r8d
00000001000014b8        andl    $0x1, %r8d
00000001000014bc        cmpq    $0x2, %rdi
00000001000014c0        jne     0x1000014cc
00000001000014c2        movl    $0x1, %ecx
00000001000014c7        movl    %r13d, %ebx
00000001000014ca        jmp     0x100001510
00000001000014cc        leaq    0x48(%r12), %rsi
00000001000014d1        subq    %r8, %rdi
00000001000014d4        movl    $0x1, %ecx
00000001000014d9        movl    %r13d, %ebx
00000001000014dc        jmp     0x1000014ee
00000001000014de        nop
00000001000014e0        orb     %dl, %bl
[...]

which allocates the array on the stack. That's much better.

test environment

swift-DEVELOPMENT-SNAPSHOT-2020-05-08-a.xctoolchain
@weissi
Copy link
Member Author

weissi commented May 10, 2020

CC @eeckstein

@weissi
Copy link
Member Author

weissi commented May 10, 2020

@swift-ci create

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself performance
Projects
None yet
Development

No branches or pull requests

1 participant