The Purpose, Revealed

The Purpose, Revealed

Rate This
  • Comments 8

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.

  • [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.*

  • 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.

  • @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:

  • You *can* write code like that, I'm not sure you *should*. I'm firmly in the C++ camp of U.B. here: whether they're well-defined or not, they look horrible and it's not immediately obvious what the effects are. To me, that means they should be U.B., so people get what they deserve for making my head hurt :-P

  • 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.

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

  • @Larry, in that case, to be consistent, you should really write:

      x = (a + (b *c))

    since = is just another binary operator. ~

  • @Pavel: There are cases where I have written that. :-D

Page 1 of 1 (8 items)