Live data from Hacker News

How much does Rust's bounds checking cost?

blog.readyset.io

191–195 of 195 posts

Re: How much does Rust's bounds checking cost?

#191

Earlier quoted context omitted.

What's special about compilers, then? Compilers are code, and therefore, as you say, buggy. Library authors know things about what their code is meant to be doing that compilers cannot deduce, so cannot act on. But the library author can. A library, according to how heavily it is used, benefits from more thorough testing than generic application code gets.

You're right that compilers tend to have bugs, but in practice, compiler bugs are rarely the cause of software issues. The same cannot be said of libraries. Major SSL/TLS libraries for instance tend to be written in C, and all of them have had memory-safety bugs. > Library authors know things about what their code is meant to be doing that compilers cannot deduce, so cannot act on. But the library author can. I don't…

That is how the trope goes. But looking at the actual faults, we see them in bad old C-like code, so not interesting in the present context. Modern C++ code, for example, will always have exactly zero use-after-free bugs. OpenSSL is old C code, so of even less relevance here.

But the topic was not CVEs. It was optimization. An optimization opportunity that would be missed by a compiler can be explicitly spelled out for it by a library author. Whether the library author is obliged by the compiler to write "unsafe" next to it has exactly zero effect on the correctness of the code written in that place: you can easily write incorrect code there. If you don't, it was not because the compiler provided any help.

Re: How much does Rust's bounds checking cost?

#192

Earlier quoted context omitted.

You're right that compilers tend to have bugs, but in practice, compiler bugs are rarely the cause of software issues. The same cannot be said of libraries. Major SSL/TLS libraries for instance tend to be written in C, and all of them have had memory-safety bugs. > Library authors know things about what their code is meant to be doing that compilers cannot deduce, so cannot act on. But the library author can. I don't…

That is how the trope goes. But looking at the actual faults, we see them in bad old C-like code, so not interesting in the present context. Modern C++ code, for example, will always have exactly zero use-after-free bugs. OpenSSL is old C code, so of even less relevance here. But the topic was not CVEs. It was optimization. An optimization opportunity that would be missed by a compiler can be explicitly spelled out f…

> Modern C++ code, for example, will always have exactly zero use-after-free bugs.

Not so. C++ is not a safe language, and never will be. Even if you avoid raw pointers you aren't safe from use-after-free bugs as the standard library makes them possible even then, with std::string_view (and perhaps other functionality). [0][1][2]

There is no safe subset of C++. People have tried, e.g. the MISRA folks, but they're unable to find a subset of C++ which is both safe and usable. The only way to guarantee the absence of undefined behaviour in a C++ codebase (or a C codebase) is to use formal analysis tools, which are a tremendous burden.

If it were possible to get decent safety guarantees out of C++, Mozilla wouldn't have bothered inventing a whole new language in the hope of improving Firefox.

I do agree though that modern C++ code is likely to have fewer memory-safety issues than 'C-style' C++ code.

> OpenSSL is old C code, so of even less relevance here.

It isn't irrelevant, our conversation wasn't specifically about the C++ language. I was responding to your suggestion that using well-known libraries written in unsafe languages, is a reliable way to avoid memory-safety issues. We know this isn't the case.

> An optimization opportunity that would be missed by a compiler can be explicitly spelled out for it by a library author.

Sure, but this whole thread is discussing that bounds checks are in practice generally inexpensive on modern hardware, except in cases like SIMD optimisations being precluded by the need for checks. I suspect this extends to other runtime safety checks too, but I don't have hard numbers to hand.

> An optimization opportunity that would be missed by a compiler can be explicitly spelled out for it by a library author.

Sure, that's an advantage of low-level languages. It doesn't negate the importance of memory-safety though.

Runtime checks are unlikely ever to have zero performance cost, sure, but the cost can be close to zero, and the fallout of removing checks from buggy code can be considerable.

> Whether the library author is obliged by the compiler to write "unsafe" next to it has exactly zero effect on the correctness of the code written in that place: you can easily write incorrect code there.

If it were a simple boolean matter of correct vs incorrect, then sure, but it often isn't. In practice, it can mean the difference between an exception being thrown, and undefined behaviour running riot, possibly leading to serious security issues.

> If you don't, it was not because the compiler provided any help.

Runtime checks are very helpful during development.

[0] https://alexgaynor.net/2019/apr/21/modern-c++-wont-save-us/

[1] https://news.ycombinator.com/item?id=26932505

[2] https://news.ycombinator.com/item?id=19723066

Re: How much does Rust's bounds checking cost?

#193

Earlier quoted context omitted.

That is how the trope goes. But looking at the actual faults, we see them in bad old C-like code, so not interesting in the present context. Modern C++ code, for example, will always have exactly zero use-after-free bugs. OpenSSL is old C code, so of even less relevance here. But the topic was not CVEs. It was optimization. An optimization opportunity that would be missed by a compiler can be explicitly spelled out f…

> Modern C++ code, for example, will always have exactly zero use-after-free bugs. Not so. C++ is not a safe language, and never will be. Even if you avoid raw pointers you aren't safe from use-after-free bugs as the standard library makes them possible even then, with std::string_view (and perhaps other functionality). [0][1][2] There is no safe subset of C++. People have tried, e.g. the MISRA folks, but they're una…

You keep trying to change the subject. But I have not promoted "subsetting" as a means to safety, and safety is anyway not interesting to real people. People want their programs to be useful. To be useful, a program must be correct, and every correct program is implicitly safe.

But the actual topic was not not that. The actual topic you have tried to steer away from is optimization. The point I made was that the author of a library can take up responsibilities that some people insist only the the language, via the compiler, can perform. The library author can perform optimizations the compiler fails to, and the library author can define interfaces that can only be used correctly, and safely. To the programmer using a library, it makes no difference, except that they may be unable to use some new, immature language, but can easily pick up and use a good library.

Re: How much does Rust's bounds checking cost?

#194
post #189

Earlier quoted context omitted.

I'm far from an expert here, but don't most modern branch predictors actually keep a shift register of the last few conditional branches, hash that branch history with the instruction's address, and use that as the index into the predictor's state table, and assume hash collisions won't happen? (Hash collisions only have a material impact if two conditional branches in the same hot code path collide, which is much mo…

Details of actual branch predictors are proprietary, so we must speculate. You don't stall on branches you have no history of, but apply heuristics that don't depend on history. E.g., backward branches are usually taken, forward branches less so. I think newer chips can try both alternatives, to some degree, although this cannot be carried far as it blows up exponentially.

I was asking about indirect branches, and the Branch Target Buffer... there are two main cases where you're making indirect jumps or indirect function calls in ELF C/C++ executables: shared library function calls (an IP-relative call to a Program Linkage Table (PLT) entry, which contains an indirect jump through a Global Offset Table (GOT) entry). The other common case is C++ virtual function calls, where you're loading the vtable entry into a register and immediately making an indirect function call through that register. You'd like to be able to start speculatively executing that jump (library call)/virtual function call while fetching the GOT/vtable entry is still in progress.

Using only information resident in the architectural registers and the decoded instruction stream, there's no heuristic that can accurately tell you where printf (or this->do_my_thing()) lives in the address space. If the address for printf isn't in the BTB, do you just stall the pipeline while loading the proper GOT entry? I thought that in general, they just hashed the instruction address to get the BTB entry and blindly assumed that BTB entry would be the target address.. (and of course, issue the GOT read and check the assumption as soon as the real GOT entry is read). The alternative would be to stall the whole pipeline until the GOT could be read anyway, so might as well do something... unless you're really concerned about power consumption. Even in extremely power limited situations, assuming hash collisions in the BTB are rare might be less power-hungry than checking the source address in the BTB entry on each and every indirect branch.

Storing both the source and destination addresses in the BTB entry means twice as much space used, and extra latency and power usage in checking the source address. Does anyone here have good knowledge of when it's worth it to store both the source and destination address in the branch target buffer, instead of just storing the target address and presuming hash collisions are rare?

On another side note: has anyone heard of an architecture that supports IP-relative indirect function calls? Such an addressing mode would allow making a function call through the GOT entry without having to load the PLT entry into the instruction cache. I'm guessing that since there's a fair overhead for sharded library calls, the most performance-sensitive code already avoids dynamic library calls, so there's not much to be gained in exchange for the added complexity of the extra call addressing mode.

Re: How much does Rust's bounds checking cost?

#195
post #194

Earlier quoted context omitted.

Details of actual branch predictors are proprietary, so we must speculate. You don't stall on branches you have no history of, but apply heuristics that don't depend on history. E.g., backward branches are usually taken, forward branches less so. I think newer chips can try both alternatives, to some degree, although this cannot be carried far as it blows up exponentially.

I was asking about indirect branches, and the Branch Target Buffer... there are two main cases where you're making indirect jumps or indirect function calls in ELF C/C++ executables: shared library function calls (an IP-relative call to a Program Linkage Table (PLT) entry, which contains an indirect jump through a Global Offset Table (GOT) entry). The other common case is C++ virtual function calls, where you're load…

Interesting questions, arbitrarily hard to research.

Attention seems to be reserved for events that occur in the sorts of tight loops that affect benchmark results. Thus, the first encounter with any branch may stall indefinitely long without (market) penalty.

Game coders have learned to batch their indirect calls according to actual destination, to throw the branch predictor a bone.

Post reply on HN