Struct is copied for comparison of equality

When I was studying the generated assembly from Swift compiler, I found that some structs are being copied on stack before the equality check. I am having a hard time reproducing it, sorry for not providing with a demo at this time.

The affected struct does not implement ==, so the compiler synthesizes one. I was wondering if playing with the ownership modifier on the parameters would help diagnose the problem (swift/lib/Sema/DerivedConformanceEquatableHashable.cpp):

  auto getParamDecl = [&](StringRef s) -> ParamDecl * {
    auto *param = new (C) ParamDecl(SourceLoc(),
                                    SourceLoc(), Identifier(), SourceLoc(),
                                    C.getIdentifier(s), parentDC);
-   param->setSpecifier(ParamSpecifier::Default);
+   param->setSpecifier(ParamSpecifier::Borrowing);
    param->setInterfaceType(selfIfaceTy);
    param->setImplicit();
    return param;
  };

... then it refuses to bootstrap the compiler.

[14/305][  4%][7.623s] Building swift module _CompilerRegexParser
FAILED: bootstrapping1/SwiftCompilerSources/_CompilerRegexParser.o <unknown>:0: warning: the '-enable-experimental-cxx-interop' flag is deprecated; please pass '-cxx-interoperability-mode=' instead
<unknown>:0: note: Swift will maintain source compatibility for imported APIs based on the selected compatibility mode, so updating the Swift compiler will not change how APIs are imported
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'a' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
<unknown>:0: error: 'b' is borrowed and cannot be consumed
<unknown>:0: note: consumed here
[15/305][  4%][11.203s] Building swift module Basic
<unknown>:0: warning: the '-enable-experimental-cxx-interop' flag is deprecated; please pass '-cxx-interoperability-mode=' instead
<unknown>:0: note: Swift will maintain source compatibility for imported APIs based on the selected compatibility mode, so updating the Swift compiler will not change how APIs are imported
ninja: build stopped: subcommand failed.
ERROR: command terminated with a non-zero exit status 1, aborting

It seems that the synthesized comparison function is performing consuming operations (which should be unexpected?) on the arguments which incurs the copy I found in the production code.

I got a reproducible demo: Compiler Explorer

borrowing prevents implicit copies of the value, so it's expected that making it the default would be hugely source-breaking; that's why we've never considered doing that.

Hi John,

I understand the decision to keep the default specifier, but I still think it is confusing that the compiler generates copies for equality checks - at least I would never expect such behavior. Is it possible to get rid of the implicit copies if they are unexpected? If not, why are they necessary?

1 Like

The type here is large and trivial, so borrowing wouldn't necessarily prevent the compiler from generating the extra moves here, I think that's just inefficiency in our managing of temporary copies. We have been working on improving the codegen in this area to avoid this.

2 Likes

Yeah, this is a known problem with the borrowing specifier: it does generate some "false positive" errors where some things are treated as consumptions that don't need to be. But of course there are some places in the standard library where parameters do get used multiple times in places that require independent copies.

Just to clarify this - are you referring to implicit borrowing for function arguments?

I don't remember the exact places that we incorrectly treat as consumes, but maybe @Joe_Groff, @Michael_Gottesman, or @kavon can weigh in here.