On Monday, 11 February 2013 at 14:54:48 UTC, kinke wrote:
I'd propose a small change so that suited structs are passed transparently byref only if the parameter is not mutable, e.g., for a function foo(const BigStruct s). The compiler would therefore not need to analyze the code flow in the callee to determine if the parameter is modified and hence a copy is needed.

The compiler would nevertheless need to be quite smart though:

---
struct MyBigStruct { double a, b, c; }
double foo(const MyBigStruct s, double* x)
{
    *x = s.a;
    return s.b;
}
// naive optimization by byref passing
double foo_ref(const ref MyBigStruct s, double* x)
{
    *x = s.a;
    return s.b;
}

MyBigStruct s = { 1, 2, 3 };
double* x = &s.b;
auto bla = foo(s, x); // returns 2; now s.b = 1
s.b = 2;              // reset s.b to 2
bla = foo_ref(s, x);  // returns 1! (the new s.b = 1)
---

So the compiler would need to prove there is no way the argument can be modified and handle tricky aliasing issues accordingly.

Thinking about it some more, I'm fairly convinced the proposal in this thread is just too dangerous. Proving the argument passed transparently byref cannot be modified is just too complex imo; e.g., it could be modified by the callee via aliasing issues shown in the example above, or it could be modified by another thread while the callee is running, hence modifying the callee's parameter at the same time!

I think the intended move semantics (byval passing for small structs w/o postblit constructor/destructor, otherwise byref) are only really safe if the argument is an rvalue - the rvalue is guaranteed not to be used after the call, so potential modifications are not visible for the caller, and the rvalue is guaranteed not to be used simultaneously in another thread. Certain lvalue cases could be optimized as well, e.g., if the lvalue is a private variable of the caller (local variable or parameter) AND is not used after the call.

---
struct MyBigStruct { double a, b, c; }

double bar(MyBigStruct s) { return s.a; }

double foo(MyBigStruct s) // s is an lvalue (parameter)
{
  return bar(s); // s NOT used afterwards => byref passing
}

void main()
{
  MyBigStruct s; // s is an lvalue (local variable)
  invoke foo(s) in another thread; // s used afterwards => byval
  if (...)
  {
    foo(s);      // s used afterwards (next line) => byval
    s.a += 1;
  }
  else
    foo(s);      // s NOT used afterwards => byref
}
---

So imo parameters need to be denoted by something special like 'auto ref' if a copy is to be elided for performance reasons - not in its current form though (only for templates and leading to code-bloating); instead, rvalues should simply be transformed to lvalues before the call and then passed byref just like ordinary lvalues. And as I've already pointed out a few times in recent discussions, I'm not a fan of 'const auto ref' for not-mutable parameters, so I'd very much like to see 'const ref' for these (allowing rvalues too, just like C++). The function signature therefore clearly indicates that these params are references to the caller's arguments, with all potentially dangerous implications.

Reply via email to