The Purpose, Revealed

Interestingly enough, no one correctly guessed why I needed code in the compiler to transform a method argument list into a form that batched up the side effecting expressions, stuck their values in variables, and then called the method with the side-effect-free variable references. There certainly were some fascinating ideas in the comments though!

The reason is quite mundane. We had a bug in the code that performs semantic analysis of calls with named arguments. One of our rules in C# that I've talked about a lot on this blog is that sub-expression evaluation occurs in left-to-right order. If you have a method void M(int x, int y) {} and then a call site M(y : F(), x : G()); we cannot simply generate the code as though you'd written M(G(), F()). The side effects of F have to happen before the side effects of G, so we generate code as though you'd said temp1 = F(); temp2 = G(); M(y : temp1, x : temp2); and now we can generate the call as M(temp2, temp1); without worrying about re-ordering a side effect.

The bug was that the rewriter was not getting the reordering correct if the parameters were ref/out or the arguments were locals without side effects. As I often do when fixing a bug, I reasoned that the initial bug had been caused because the rewriter was under-specified.

The fix will get into the initial release of C# 4, but the problems that Pavel identified with it -- that certain obscure side effects such as the order in which bad array accesses throw exceptions, or the order in which static class constructors execute, are not correctly preserved by my algorithm, will not be fixed for the initial release. Doing these correctly requires us to be able to generate temporaries of "reference to argument" types, which we've never done before and which our IL generator was not built to handle. Had we discovered the bug much earlier in the cycle, we probably could have applied a more solid fix, but it is too late now.

Thanks again to Pavel for the great analysis, and thanks to our old friend nikov for reporting the bug in the first place.

And happy thanksgiving to my readers in the United States! As I do every year, I'm roasting a turkey to feed fourteen people. I highly recommend the "brine the bird then roast it upside-down" method described in The Joy Of Cooking. It's worked perfectly for me for the last ten years straight.

Comments

  • Anonymous
    November 25, 2009
    [Off topic] Thanks for the book recommendations in the comments to a prior posting. I'm going through C# In Depth now and it's quite informative. It is taking things I understand and have used on a more superficial level and is giving me a deeper understanding, which I guess is basically what it promised to do. While I didn't come in as the C# 1 guy trying to learn 2 and 3, it's certainly making my knowledge of 2 and 3 better.

  • Anonymous
    November 25, 2009
    Hmm. I'm surprised that C# specifies an ordering of parameter evaluation; I'm used to C/C++ where the ordering is intentionally left unspecified, for precisely the reason described here: it's difficult and complicated to ensure a specific ordering. (And after coding C/C++ for so many years, I view side effects in general with a jaundiced eye.) But I suppose it does make coding easier if you can count on a specific ordering.

  • Anonymous
    November 26, 2009
    @Larry It's more than just function arguments. You can also write stuff like this in C#:    a = a++ * a++; or even    a[0] = a[a[0]++]; which is U.B. in C++, but well-defined in C#. Eric (and others) have written about it before, covering both behavior and rationale; in order: http://blogs.msdn.com/oldnewthing/archive/2007/08/14/4374222.aspx http://blogs.msdn.com/ericlippert/archive/2007/08/14/c-and-the-pit-of-despair.aspx http://blogs.msdn.com/ericlippert/archive/2008/05/23/precedence-vs-associativity-vs-order.aspx http://blogs.msdn.com/ericlippert/archive/2009/08/10/precedence-vs-order-redux.aspx

  • Anonymous
    November 26, 2009
    The comment has been removed

  • Anonymous
    November 26, 2009
    I'm firmly on the other side of the fence. :) I think everything that can reasonably be defined should be defined, even if it should never actually be used. Having holes in your language is very ugly, and I think having consistent rules also makes your language easier to reason about.  It's also more composable that way. The C# approach, including things like strict left-to-right evaluation, means that I, if I know the language decently, can always determine what some code will do, without needing to compile or run it. Undefined behaviour is okay if you're the one writing the code, so you can avoid it ... if someone else wrote it, it's probably best if there is a definite way the code is working, without gotchas with implementation or platform differences.

  • Anonymous
    November 26, 2009
    Indeed. I'm fairly new to C#. I'm also fairly paranoid: I'll use parentheses to write x = a + (b * c);

  • Anonymous
    November 26, 2009
    @Larry, in that case, to be consistent, you should really write:   x = (a + (b *c)) since = is just another binary operator. ~

  • Anonymous
    November 26, 2009
    @Pavel: There are cases where I have written that. :-D