Live data from Hacker News

Carbon’s most exciting feature is its calling convention

foonathan.net

51–60 of 223 posts

Re: Carbon’s most exciting feature is its calling convention

#51

Earlier quoted context omitted.

Microsoft. - don’t listen to me, it’s Google.

It's Google but yes, another instance of Embrace, Extend and Extinguish . I hope this doesn't take off, with my sincere apologies to the ones who have been working hard on this. The last thing the C/C++ ecosystem needs is becoming de facto owned by a private company.

It hasn't been an issue at all for Go. As long as all the source is public, I don't see an issue using a large, overvalued tech monopoly for advancing technology.

Re: Carbon’s most exciting feature is its calling convention

#52
post #32

C++ also pays a price for insisting not only that objects have addresses, but those addresses are distinct. If you've got a 1.6 billion empty tuples in variable A, 1.4 billion in variable B and 1.8 billion in variable C, C++ can't see a way to do that on a 32-bit operating system. It needs to give each empty tuple an address, so it must think of 4.8 billion integers between 0 and 2^32 and it can't do that, so your pr…

Is that really a problem now that the overwhelming majority of C++ programs run on 64bits platforms?

A better example of address uniqueness being a problem is with code like:

    struct Marker {};
    struct Foo {
        Marker marker;
        int64_t number;
    };
If you write the equivalent in C with GCC extensions, or Rust, sizeof(Foo) would be 8, the same as sizeof(int64_t); `marker` doesn't take up any extra space. In C++, however, sizeof(Foo) is 16, because `marker` must take up at least 1 byte to have a unique address, which gets expanded to 8 bytes due to alignment.

Now, as of C++20, you can reduce sizeof(Foo) to 8 by tagging `marker` as [[no_unique_address]]. However, this has drawbacks. First of all, it's easy to get situations like this in highly generic code, so it's hard to predict where [[no_unique_address]] needs to be applied (and applying it everywhere would be verbose).

Second of all, [[no_unique_address]] is dangerous, because it doesn't just allow empty fields to be omitted, it also allows nonempty fields to have trailing padding bytes reused for other fields. Normally that's okay, but if you have any code that performs memcpy or memset or similar based on the size of a type, such as:

    struct Foo {
        Foo(const Foo &other) {
            memcpy(this, &other, sizeof(Foo));
        }
        // …some fields here…
    };
…then if that code writes to a [[no_unique_address]] field, it can overwrite adjacent fields, since sizeof(Foo) includes any trailing padding bytes!

Re: Carbon’s most exciting feature is its calling convention

#54
post #48

Maybe I'm missing something, but I don't see what's special about Carbon here. In C++ the compiler can also optimize pass-by-const-reference to pass-by-value, and they do. It just can't do it across an ABI boundary, but that should only be an issue with dynamic libraries, and Carbon has to follow the standard ABI there as well. Just make sure the compiler knows it doesn't have to follow the standard ABI for every sym…

I have been burned many times in the past by "the compiler is allowed to optimise something away". You write your code assuming such an optimisation will happen, and for some reason, the compiler decides not to apply the optimisation. Perhaps the wind was blowing in the wrong direction, or it was in a bad mood, or you forgot to specify -fvisiblitly=hidden. The exciting part here is that it happens by default, and the…

I don't think that fvisibility=hidden on its own is sufficient, it does not allow the compiler to break the call abi as the function could still be called from another .o (which will only know the mangled name of the original function). You need fvisibility=internal (or maybe fno-semantic-interposition but I'm not sure if it's enough).

Re: Carbon’s most exciting feature is its calling convention

#55
post #40

Earlier quoted context omitted.

> it’s UB to take the address of a parameter via any mechanism other than explicitly taking the address - which a compiler obviously sees, and because it’s UB the compiler optimizes is free to assume no one is taking the address. I don't get that: can you express in C++ a code that "take the address of a parameter via any mechanism other than explicitly taking the address"?

Consider this C code (also "works" if compiled as C++): int main(void) { int x = 0; int arr[1]; int *p = arr + 1; *p = 42; return x; } On a lot of systems (e.g., https://godbolt.org/z/jYqM8TT3Y ), it just so happens that `x` is right above `arr` on the stack, so that code will return 42. But that code is absolutely UB. The more general name for this concept is "pointer provenance". Basically, you can't pull pointer v…

That's a buffer overflow. The optimizer doesn't need to reason about changing the behavior of such things.

Re: Carbon’s most exciting feature is its calling convention

#56

C++ also pays a price for insisting not only that objects have addresses, but those addresses are distinct. If you've got a 1.6 billion empty tuples in variable A, 1.4 billion in variable B and 1.8 billion in variable C, C++ can't see a way to do that on a 32-bit operating system. It needs to give each empty tuple an address, so it must think of 4.8 billion integers between 0 and 2^32 and it can't do that, so your pr…

I may be overlooking something, but I don’t see a realistic use case for having multiple empty tuples without an address.

If you have those, I don’t see any way to discriminate between them. If so, why would you ever want to have more than one of a given type? Is there some template code that might accidentally try to create them?

Re: Carbon’s most exciting feature is its calling convention

#57
post #55

Earlier quoted context omitted.

Consider this C code (also "works" if compiled as C++): int main(void) { int x = 0; int arr[1]; int *p = arr + 1; *p = 42; return x; } On a lot of systems (e.g., https://godbolt.org/z/jYqM8TT3Y ), it just so happens that `x` is right above `arr` on the stack, so that code will return 42. But that code is absolutely UB. The more general name for this concept is "pointer provenance". Basically, you can't pull pointer v…

That's a buffer overflow. The optimizer doesn't need to reason about changing the behavior of such things.

The point is that on systems where that code returns 42, `p` has the exact same value it would if I did `int *p = &x;` instead, but not the same provenance.

Re: Carbon’s most exciting feature is its calling convention

#58
post #33
post #14

Earlier quoted context omitted.

Some of the biggest problems with C++ come from its backwards compatibility with C. Yes, it wins you users in the short term, but it's a pain to support as both languages evolve

The binary compatibility is the big deal. The other safe languages have explicitly taken the view the interop with C++ is bad, and so we should instead do interop with the significantly less safe C instead. The real killer is that the lack of any interaction with C++ means that you can’t do any real incremental adoption of one of those safe languages in any big security critical projects. Saying that the solution to…

> The other safe languages have explicitly taken the view the interop with C++ is bad

It's not that it's inherently bad, it's just insanely difficult to do and, probably, isn't worth the pain. Carbon's approach to solving this problem includes embedding a custom C++ compiler as part of its toolchain, and at this point it's just the idea, who knows if they will be able to actually do it.

> they are all hell bent on not providing even just basic ABI stability

Right, the famous stable C++ ABI

Re: Carbon’s most exciting feature is its calling convention

#59
post #9

That's been done before. The original Modula 1 compiler had that. In a language where the default parameter mode is a read-only reference, it's an obvious optimization. The reverse is true. Anything passed by value can be treated as const reference by the compiler if the compiler knows enough about access and lifetime. The compiler must be able to determine that the parameter is neither deallocated nor modified while…

The MIPS NUBI ABI from 2005 also proposed this for C:

  ftp://ftp.linux-mips.org//pub/linux/mips/doc/NUBI/MD00438-2C-NUBIDESC-SPC-00.20.pdf
Section 3.4, page 21:

"Arguments: [...]

Derived types (structures etc) and non-standard scalar types are passed in a register if and only if their memory- stored image is register-size aligned and fits into a register. [...]

All other arguments are passed by reference. The callee must copy the argument if it writes it or takes its address."

Post reply on HN