Live data from Hacker News

The Problem with Friendly C

blog.regehr.org

151–160 of 174 posts

Re: The Problem with Friendly C

#151

Earlier quoted context omitted.

The whole point of undefined behaviour is to allow for optimizations. Adding tons of runtime checks is the opposite of optimization. A lot of this is about being able to use a single instruction of the respective target architecture vs. adding another two, three instructions for the check, thus slowing things down to half the speed or less.

It's not really about optimisations, but more about being able to translate directly to fast machine instructions (which may behave differently on different machines). What I'm talking about is not adding checks, but what happens when the compiler knows that something has undefined behavior through e.g. Constant propagation. This happens in the optimizer on IR level. The undefined behaviour is there to make translati…

> What I'm talking about is not adding checks, but what happens when the compiler knows that something has undefined behavior through e.g. Constant propagation.

But ... either the compiler can prove at compile time that the program has undefined behaviour, in which case it should simply refuse to compile, or it can not, in which case it has to emit a runtime check to determine when undefined behaviour "is about to happen", which is potentially expensive?

> The undefined behaviour is there to make translations to machine instructions efficient - so that you don't need to do checks on pointers and that addition, shift, etc. on several instructions are supported although they behave slightly differently

... or in other words: for optimisation?

I mean, optimisation ultimately is nothing but the selection of the cheapest possible sequence of instructions to perform a given computation. And undefined behaviour helps with that, not just in the last step of mapping your IR to instructions, but in every step before that, as every optimisation step has to preserve the semantics of the code, and the fewer restrictions there are on the interpretation of a given piece of code, the more options every optimisation step has to transform the code without changing the semantics, and thus the more chances for one of them being cheaper than the others.

Re: The Problem with Friendly C

#152
post #148

Earlier quoted context omitted.

> But if it did that, there might be target architectures where the code that would need to be generated to check for that case would have a 300% performance overhead See my sibling reply down-thread. You don't want the compiler to automatically insert the check; but neither do you want the compiler to assume you want to not insert the check. You want the compiler to error out until you tell it whether or not you wan…

> You want the compiler to error out until you tell it whether or not you want a check. Or rather, whether you want checked-division semantics—which will be free on some architectures and costly on others—or unchecked-division semantics [...] I think I disagree. I don't want that to be a feature of the compiler, but of the language, in which case there is no way to "error out". That is to say: there should be operato…

I agree with pretty much everything you wrote here. I think my main disagreement is that you can fix this at the language level. Languages are static entities; some are "living" in the sense that they get new major revisions (C++0x, Python 3, etc.) but on a day-to-day basis, you have to deal with the language you've got, and most languages don't have any way to "annotate in" these sort of semantics on a per-module basis. (C is one of the only places this has been given even bare thought, actually, with compiler #pragmas.)

IMHO the goal of a compiler is this: to take as input 1. code represented in some standard grammar, and 2. configuration represented in a compiler-specific format; and to combine these two things to generate a build artifact.

I assume that you want the build artifact to have the same deterministic behavioral semantics on all target platforms, regardless of whether your language allows you to encode those semantics. Insofar as compilers have the two inputs stated above, then each semantic property that can't be made explicit by the language grammar, must then fall to the compiler's configuration.

In other words, it's the compiler's job (through you) to fix the language's mistakes, such that vague code becomes non-vague binaries.

Imagine for a moment that, instead of global command-line switches passed directly to the compiler, the compiler's configuration existed in the form of a "hints file" alongside each source file—effectively, a proprietary system of markup/annotations for the language, but separated into its own file instead of being intermingled in the source like Java's @annotations.

In such a model, you'd have a 1:1 correspondence between input #1 and input #2: each file of C code would have an equivalent file of hints, telling the compiler everything it needs to know that isn't in the C code (and which, moreover, can't actually be expressed at all in C.)

With our current backwards batch-programming methodologies, you'd probably have to write the "hint" files yourself, or have your IDE generate them by twiddling knobs. My real expectation, though, is that your compiler would actually run through your source files interactively, asking you questions where it thinks things are vague and recording the answers in its hint files, only actually doing a compilation pass once all its questions have been answered. Your compiler would actually work with you like a copy-editor, taking your muddled prose and putting in little query-marks to show its confusion.

Which is all to say, we're never going to get a revision of the C language standard that will actually give "x + y" platform-independent overflow semantics. But we could definitely create compilers that would annotate each "x + y" in a C codebase with a particular overflow semantics, without doing anything to the code itself that breaks the C language grammar. This would let us remove the vast majority of what is currently "undefined behavior" from our code, without fighting a losing battle for more semantically-explicit languages.

Re: The Problem with Friendly C

#153
post #152

Earlier quoted context omitted.

> You want the compiler to error out until you tell it whether or not you want a check. Or rather, whether you want checked-division semantics—which will be free on some architectures and costly on others—or unchecked-division semantics [...] I think I disagree. I don't want that to be a feature of the compiler, but of the language, in which case there is no way to "error out". That is to say: there should be operato…

I agree with pretty much everything you wrote here. I think my main disagreement is that you can fix this at the language level. Languages are static entities; some are "living" in the sense that they get new major revisions (C++0x, Python 3, etc.) but on a day-to-day basis, you have to deal with the language you've got, and most languages don't have any way to "annotate in" these sort of semantics on a per-module ba…

> Anyway, that's all to say, IMHO the goal of a compiler is to take as input 1. code represented in some standard grammar, and 2. configuration represented in a compiler-specific format, and combine these to generate a build artifact.

Then, what you really are doing is defining a new language. And one with a horrible syntax at that. If the "code" alone does not actually describe what the program is supposed to do, that "configuration" becomes part of the program (if the binary's behaviour changes depending on whether you compile it with or without the "configuration", it is obviously part of the code), and having parts of your program kindof "out of band" is about the worst idea ever for auditing and maintenance, and for general readability. Also, given that that part of your new language is not part of the C standard, say, you cannot use any C compiler to compile it. If you are lucky, you find another compiler that implements another new language based on C with a different syntax for the "configuration" that provides the same semantics, then you can start porting from your own new language to that compiler's new language, and then maintain two implementations of that part of your code (erm, "configuration").

If you want to create a new language with better semantics, better invent one with sane, in-band, syntax. If you want to write C ... well, then write C, C is turing complete, so you can express anything you need in it, and every (non-buggy) C compiler will understand it.

> My real expectation, though, is that your compiler would actually run through your source files interactively, asking you questions where it thinks things are vague and recording the answers in its hint files, only actually doing a compilation pass once all its questions have been answered.

I think that that can't work. There are just so many places where it's somewhere between extremely hard and impossible for a C compiler to prove that undefined behaviour cannot happen that you probably would need a dozen annotations for every line of your code. Plus, keeping track of how the recorded annotations map to the code after it has been modified is extremely fragile, so if you wanted to be sure, you'd have to answer all the questions for a source file again after any semantic change you make.

Re: The Problem with Friendly C

#154

Earlier quoted context omitted.

There are integer additions of different size, but is there anything you could honestly call plain integer addition that has different behavior from wrapping?

What's your point? If you define a "plain integer addition" to be "wrapping binary two-complement addition", then there isn't, otherwise, there is. But the C standard doesn't say "+ maps to what you could honestly call plain integer addition" anyway, so it's kindof pointless? And if you were to define your own language, you obviously could define "+" to have architecture-specific semantics, whithout appealing to any…

>What's your point? If you define a "plain integer addition" to be "wrapping binary two-complement addition", then there isn't, otherwise, there is.

I'm not defining it that way. I used the word 'plain' because some instructions are specifically designed to be variants of normal instructions. It's in their name that they do thing abnormally, so they should be discarded when talking about normal behavior.

>But the C standard doesn't say "+ maps to what you could honestly call plain integer addition" anyway, so it's kindof pointless?

You originally said: There is no such thing as "+ on x86". "+" is an operator of the C language.

So I was explaining what "+" means in the absence of C rules.

+ means addition in general conversation, that's basic math and English skills.

That it's integer addition is obvious from context, because we're adding 1 to INT_MAX.

And I already explained why I used the word 'plain'.

-

There are architectures with normal integer add instructions that do not have twos-complement behavior. I'm just not aware of any such instructions on x86. Can you name one?

If there are none, then I am comfortable asserting that "+" on x86 exists and is twos-complement.

Re: The Problem with Friendly C

#155
post #142
post #129

Earlier quoted context omitted.

I haven't used it yet, but apparently Rust's FFI with C (in both directions) is quite good: https://doc.rust-lang.org/book/ffi.html

That doesn't do IPC though right? When I say IPC, I mean where you have a Rust process and and a C/C++ process running with different privileges. When people say FFI, they mean Rust code and C code running in the same process. I thought Chromium had a library for this (for multiple C++ processes), but I guess it is sort of ad hoc now? https://www.chromium.org/developers/design-documents/inter-p... https://www.chromiu…

https://github.com/pcwalton/gaol

Re: The Problem with Friendly C

#156

Earlier quoted context omitted.

What's your point? If you define a "plain integer addition" to be "wrapping binary two-complement addition", then there isn't, otherwise, there is. But the C standard doesn't say "+ maps to what you could honestly call plain integer addition" anyway, so it's kindof pointless? And if you were to define your own language, you obviously could define "+" to have architecture-specific semantics, whithout appealing to any…

>What's your point? If you define a "plain integer addition" to be "wrapping binary two-complement addition", then there isn't, otherwise, there is. I'm not defining it that way. I used the word 'plain' because some instructions are specifically designed to be variants of normal instructions. It's in their name that they do thing abnormally, so they should be discarded when talking about normal behavior. >But the C s…

> + means addition in general conversation, that's basic math and English skills. > > That it's integer addition is obvious from context, because we're adding 1 to INT_MAX.

Well ... yeah, "+" usually means addition, sure. But there are so many types of addition that that's not really specific enough for a language definition. And adding 1 to INT_MAX? Well, you could interpret "INT_MAX" to indicate C code, and thus "+" to be C's addition operator. In which case, it's not integer addition, at least not the kind that basic math and English skills would suggest: Adding two positive integers is always defined (and also never gives a value lower than either of the two operands), very much unlike "+" for "int"s in C.

> There are architectures with normal integer add instructions that do not have twos-complement behavior. I'm just not aware of any such instructions on x86. Can you name one?

Sure, PADDSW, for example. At least if I understand your "twos complement" requirement correctly to mean a specific wraparound behaviour. Or, if you want to count that, DAA and AAA.

Re: The Problem with Friendly C

#157

Earlier quoted context omitted.

>What's your point? If you define a "plain integer addition" to be "wrapping binary two-complement addition", then there isn't, otherwise, there is. I'm not defining it that way. I used the word 'plain' because some instructions are specifically designed to be variants of normal instructions. It's in their name that they do thing abnormally, so they should be discarded when talking about normal behavior. >But the C s…

> + means addition in general conversation, that's basic math and English skills. > > That it's integer addition is obvious from context, because we're adding 1 to INT_MAX. Well ... yeah, "+" usually means addition, sure. But there are so many types of addition that that's not really specific enough for a language definition. And adding 1 to INT_MAX? Well, you could interpret "INT_MAX" to indicate C code, and thus "+…

>PADDSW

As I see it that's a variant of PADDW, so whatever PADDW does is what matters, and PADDW does twos complement.

>DAA and AAA

I thought about calling those out, I think it's stretching too much to refer to an archaic instruction that works on 'ascii' or such when we're figuring the behavior of 'integers'. Even if it is one byte.

Re: The Problem with Friendly C

#158

Earlier quoted context omitted.

> + means addition in general conversation, that's basic math and English skills. > > That it's integer addition is obvious from context, because we're adding 1 to INT_MAX. Well ... yeah, "+" usually means addition, sure. But there are so many types of addition that that's not really specific enough for a language definition. And adding 1 to INT_MAX? Well, you could interpret "INT_MAX" to indicate C code, and thus "+…

>PADDSW As I see it that's a variant of PADDW, so whatever PADDW does is what matters, and PADDW does twos complement. >DAA and AAA I thought about calling those out, I think it's stretching too much to refer to an archaic instruction that works on 'ascii' or such when we're figuring the behavior of 'integers'. Even if it is one byte.

> As I see it that's a variant of PADDW, so whatever PADDW does is what matters, and PADDW does twos complement.

Nope, PADDW is a variant of PADDSW, so whatever PADDSW does is what matters, and PADDSW does saturating addition.

Or in other words: You are defining "normal" to mean "twos-complement with wrap-around" after all, which makes it rather unsurprising that every "normal" instruction according to your definition turns out to be doing twos-complement addition with wrap-around.

> I thought about calling those out, I think it's stretching too much to refer to an archaic instruction that works on 'ascii' or such when we're figuring the behavior of 'integers'. Even if it is one byte.

Well, packed BCD definitely is not ASCII. Nor twos-complement, nor ones-complement, nor IEEE754. And BCD quite definitely represents a range of the integers.

Re: The Problem with Friendly C

#159

Earlier quoted context omitted.

>PADDSW As I see it that's a variant of PADDW, so whatever PADDW does is what matters, and PADDW does twos complement. >DAA and AAA I thought about calling those out, I think it's stretching too much to refer to an archaic instruction that works on 'ascii' or such when we're figuring the behavior of 'integers'. Even if it is one byte.

> As I see it that's a variant of PADDW, so whatever PADDW does is what matters, and PADDW does twos complement. Nope, PADDW is a variant of PADDSW, so whatever PADDSW does is what matters, and PADDSW does saturating addition. Or in other words: You are defining "normal" to mean "twos-complement with wrap-around" after all, which makes it rather unsurprising that every "normal" instruction according to your definitio…

"ascii adjust after addition"

> Nope, PADDW is a variant of PADDSW, so whatever PADDSW does is what matters, and PADDSW does saturating addition.

That's like arguing that Red Sports Car is a variant of Red Sports Car with Stripes. The one that adds adjectives is the variant, the one without those adjectives is the base.

If PADDW did saturating arithmetic and PADDWW did wrapping, I would agree with you, but that's not how x86 works.

Re: The Problem with Friendly C

#160
post #128

Earlier quoted context omitted.

Rust or Go are possible alternatives, or Java if it doesn't need to be system level.

Java for performance critical or realtime things sounds like a feverish dreamed nightmare.

The question was about safety, and java is indeed safe.
Post reply on HN