Author Topic: C word  (Read 30713 times)

0 Members and 1 Guest are viewing this topic.

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14490
  • Country: fr
Re: C word
« Reply #75 on: September 22, 2019, 08:16:22 pm »
Yes of course, VHDL was directly derived from Ada, and actually borrows a lot from the Ada language. Much more so than Verilog "borrows" from C, which was often what could be heard (VHDL comes from Ada, Verilog from C: that's false! VHDL is a lot closer to Ada than Verilog ever was to C.)

As to SV, I don't know it much, so, hoping someone knowledgeable can answer your question.

And now just a bonus for bit fields with Ada:
Code: [Select]
-- The "high-level" definition of a record:
type Count7_t is mod 128;

type BitField_t is
record
Enable : Boolean;
Count : Count7_t;
Status : Natural range 0..255;
end record;

-- Now describe how it's layed out at the bit level:
for BitField_t use
record
Enable at 0 range 0..0;
Count at 0 range 1..7;
Status at 0 range 8..15;
end record;

-- Finally, ensure it's exactly 16-bit wide:
for BitField_t'Size use 16;

-- Define endian-ness:
for BitField_t'Bit_Order use Low_Order_First;
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: C word
« Reply #76 on: September 23, 2019, 09:37:15 am »
Quote
subtype Byte_t is Natural range 0..255;  -- There you have your [0..255] unsigned integer. But that's not all...
-- As is, a Byte_t can't be greater than 255. It won't roll-over. It will raise an error if you attempt to increment one that is 255.

You're saying that by default, Ada won't let you do math on 8bit variables without checking each result for overflow?
(unless your declaration gets more complicated...)

Ouch?  (I mean, I guess you define types the way you want them to behave, and then just use those all the time.  But still - ouch!)

(Isn't that exactly what crashed Ariane, too?  An overflow exception in code where no one thought they needed to care?)
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6848
  • Country: va
Re: C word
« Reply #77 on: September 23, 2019, 11:28:32 am »
Quote
(Isn't that exactly what crashed Ariane, too?  An overflow exception in code where no one thought they needed to care?)

I don't know the details, but looking at this as failure to trap an exception...

It's a rock and a hard place kind of thing. If you don't trap the exception then you're in big trouble, but what if there was no exception? The options are to roll over or do nothing, and either could be as fatal as an unhandled exception. On the one hand they might coincidentally result in a non-fatal (at that point) value, but it's a wrong value nevertheless. On the other hand with the exception you at leas get to know something is wrong and have a chance to do something about it before it goes further.
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #78 on: September 23, 2019, 11:45:18 am »
Isn't that exactly what crashed Ariane, too?  An overflow exception in code where no one thought they needed to care?

Hard to believe since exceptions have low-level constraints, hence to pass the DO178B/level A-C they MUST be checked in both normal and abnormal conditions. However, the worst-case cannot consider what might happen when the hardware gets too seriously damaged. Modules have two ways of redundancy, but not for everything.

 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6266
  • Country: fi
    • My home page and email address
Re: C word
« Reply #79 on: September 23, 2019, 01:46:22 pm »
Higher-level languages with an actual runtime will have a hard time replacing really low-level languages like C, because of the engineering differences.

It is easy to miss how truly simple C really is.  Almost everything is part of the standard library, which is only provided in hosted environments; a freestanding environment is truly simple.

I have played with the notion of replacing C with something more appropriate for the tasks I have used it (from microcontrollers to kernels to low-level libraries to applications), and I've come to realize that the features C is missing or should replace with something, are rather simple.

For example, we really need a notion of compile-time only data structures, as a completely new concept.  As a practical example, consider the I/O pin configuration on a microcontroller.  We really do not care how they are initialized, just that they are initialized to a specific state.  Fortran foreach loops are a small step in this direction, decoupling the order in which iterations are done, making it possible for a compiler to easily parallelize such loops.  The fundamental idea is a way to represent final states, when intermediate states or order of operations or other side effects are unimportant.

Currently, C has two contexts: hosted and freestanding.  Splitting these into facilities would make a lot of sense.  For example, when using GCC on microcontrollers you actually use an interesting subset of C++.  The Linux kernel uses a subset of C that excludes floating-point math unless special precautions are taken.  And so on.

As of 2019, there are two approaches to synchronization primitives in hardware: load-link/store-conditional and compare-and-swap.  If we consider synchronization primitives a facility, then LL/SC and CAS would be alternate implementations of that facility.

A lot of hardware can do atomic loads, stores, and even additions and subtractions.  These should be exposed as a facility.

As you can see, the above points are really about the "standard library", and modularizing it in a way that is different to now, but better corresponds to the actual use cases we can see in various software project types.  Some of the facilities are provided by the user, some by the environment, some by the compiler (consider e.g. udivdi3 helper when using GCC).

The language itself needs some new concepts.  Arrays with compile-time boundary checking, for example; arrays with run-time boundary checking; vector types.  One important concept we need is splitting casting into conversion and reinterpretation.  Conversion occurs when a value of one type is converted to the same or as similar value as possible in the other type; reinterpretation occurs when the storage bit pattern is used with a different type of the same size.

I do not believe atomic types is the right direction, because atomicity is a property of the access and not of the variable itself.  I think a variable attribute indicating atomic access being required would be much better.  (Similarly, variable attributes specifying byte order would be useful.  As would be a way to specify data structures exactly.)

The sequence point semantics in C are too strong.  In many cases, the order of operations and their side effects really does not matter, and it would be better if the compiler could choose/optimize them as it sees fit.  A lot of initialization loops fall into this category.  Also, instead of global atomicity, it would be useful to give operations compile-time "tags", and provide primitives for compiler/hardware synchronization within each "tag".  An example of such use is dual counters for opportunistic read-only access for rarely-modified data structures: modifiers increment one before, and the other after, modifying the data.  An opportunistic reader gets the latter counter first, then the data, and then the first counter; the data is reliable only if the counter values matches.  The order in which the counter values and their increments are visible is paramount, and is easily b0rked by caching strategies.
In practice, foreach-type order-ignoring loops, and some way to mark entire scopes as "side effect order and sequence points irrelevant", might suffice.
I suspect that turning the entire thing on its head, dropping sequence point order unless explicitly marked, would be better.

The biggest flaw in C is that it does not have a native facility exposing the status flags.  For example, an internal ABI would be much more efficient, if it used a status flag, say carry flag, to indicate failure.  This way, a function could have one return type for success, and completely another for failure cases.  In essence, the status flag would allow a kind of "exception" handling, except in a form native to basically all current hardware that can support C.

Even more interesting would be if the language allowed multiple ABIs in the same project -- it would have to do that on a per-callable basis.  It would help a lot in implementing low-level libraries for various higher-level programming languages.  Static introspection would be useful here too, for example with a new ELF section describing exported callables, so that higher-level languages could call these directly, without any special shims in between; much like ctypes for Python.

As a whole, these changes would neither bring it closer to assembly, nor add higher-level abstractions; it's a step sideways.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14490
  • Country: fr
Re: C word
« Reply #80 on: September 23, 2019, 03:28:51 pm »
Quote
subtype Byte_t is Natural range 0..255;  -- There you have your [0..255] unsigned integer. But that's not all...
-- As is, a Byte_t can't be greater than 255. It won't roll-over. It will raise an error if you attempt to increment one that is 255.

You're saying that by default, Ada won't let you do math on 8bit variables without checking each result for overflow?
(unless your declaration gets more complicated...)

You think the type definition I gave just below this is complicated? :o

It's not a matter of being complicated? It's just what a "range" is about. Basic language stuff. If said variable gets out of its range and that can be caught at compile-time, you'll get a compiler error or  warning. If it can't, and that happens at run-time, you'll get an exception. Ranges add constraints, why would you want them to basically get ignored?

Again if you want an 8-bit type that behaves as you probably expect as a low-level programmer, just declare it as "mod 256". Make sure this is what you really want, because in real programs, assigning a value greater than 255 to an 8-bit variable, unless you want it truncated on PURPOSE, is really bad, and C in that respect has only very limited abilities to detect it (assigning from a larger width integer to a smaller one without a cast will get you a warning, provided you enabled the corresponding warning... which is often not the case by default. Now that is ouch, and very commonly seen in C code.)

And as I stated after, ranges and size are two different things in Ada. Just because you declared a range of 0..255 doesn't mean you'll directly get an 8-bit variable either. You'll just get an integer the acceptable values of which are in this range. The size, I think, would depend on target and compiler decisions by default, unless you give it a clear spec with the "Size" attribute.

Ouch?  (I mean, I guess you define types the way you want them to behave, and then just use those all the time.  But still - ouch!)

Not sure what the ouch is about actually. If you define constrained types, you'll get the constraints that go with them instead of just getting random/"implementation-defined" stuff as often happens in C.
See above once again. If you want an 8-bit type that rolls over by itself, declare it "mod 256".

If OTOH, you'd like specific features, such as having the value "capped" to the minimum or maximum of its range instead of rolling over, you'll have to define a specific type, overloading the base operators (yes Ada is also an OO language.) Really not that complicated either.

(Isn't that exactly what crashed Ariane, too?  An overflow exception in code where no one thought they needed to care?)

I don't quite remember, but any unexpected overflow can lead to catastrophic results. It's kind of weird to think that not taking any action when one occurs would be better than raising an exception.
Of course you have to handle exceptions properly.
Had it not raised an exception, it could have led to bogus results, possibly controlling a key element of the system unexpectedly and make it crash as well.

The problem here was "no one thought they needed to care". Not that an exception was raised, or the language!

Some people may prefer programming in a "I don't know for sure what will happen in some cases, but hey, it compiles".
I remember we had a team joke in a job years ago. One embedded developer, when asked if his code was ready to be integrated, would sometimes say: "it compiles, so it must work". We ended up using this as some kind of meme when we needed a laugh.
 ;D

Of course ranges, and parameters integrity in general, can be enforced in other languages including C. You'll just have to do this manually, which can get tedious (but has also distinct advantages, I talked about parameters/variable checking in another thread...)

As to the merits of exceptions themselves, which are nothing specific to Ada, but are commonly implemented in most high-level languages these days (except C, and if I got it right, Rust?), this is yet another debate, and would actually be an interesting one.
« Last Edit: September 23, 2019, 03:32:38 pm by SiliconWizard »
 

Offline FreddieChopin

  • Regular Contributor
  • *
  • !
  • Posts: 102
  • Country: ua
Re: C word
« Reply #81 on: September 23, 2019, 05:05:29 pm »
Quote
subtype Byte_t is Natural range 0..255;  -- There you have your [0..255] unsigned integer. But that's not all...
-- As is, a Byte_t can't be greater than 255. It won't roll-over. It will raise an error if you attempt to increment one that is 255.

You're saying that by default, Ada won't let you do math on 8bit variables without checking each result for overflow?
(unless your declaration gets more complicated...)

You think the type definition I gave just below this is complicated? :o

It's not a matter of being complicated? It's just what a "range" is about. Basic language stuff. If said variable gets out of its range and that can be caught at compile-time, you'll get a compiler error or  warning. If it can't, and that happens at run-time, you'll get an exception. Ranges add constraints, why would you want them to basically get ignored?

Again if you want an 8-bit type that behaves as you probably expect as a low-level programmer, just declare it as "mod 256". Make sure this is what you really want, because in real programs, assigning a value greater than 255 to an 8-bit variable, unless you want it truncated on PURPOSE, is really bad, and C in that respect has only very limited abilities to detect it (assigning from a larger width integer to a smaller one without a cast will get you a warning, provided you enabled the corresponding warning... which is often not the case by default. Now that is ouch, and very commonly seen in C code.)

And as I stated after, ranges and size are two different things in Ada. Just because you declared a range of 0..255 doesn't mean you'll directly get an 8-bit variable either. You'll just get an integer the acceptable values of which are in this range. The size, I think, would depend on target and compiler decisions by default, unless you give it a clear spec with the "Size" attribute.

Ouch?  (I mean, I guess you define types the way you want them to behave, and then just use those all the time.  But still - ouch!)

Not sure what the ouch is about actually. If you define constrained types, you'll get the constraints that go with them instead of just getting random/"implementation-defined" stuff as often happens in C.
See above once again. If you want an 8-bit type that rolls over by itself, declare it "mod 256".

If OTOH, you'd like specific features, such as having the value "capped" to the minimum or maximum of its range instead of rolling over, you'll have to define a specific type, overloading the base operators (yes Ada is also an OO language.) Really not that complicated either.

(Isn't that exactly what crashed Ariane, too?  An overflow exception in code where no one thought they needed to care?)

I don't quite remember, but any unexpected overflow can lead to catastrophic results. It's kind of weird to think that not taking any action when one occurs would be better than raising an exception.
Of course you have to handle exceptions properly.
Had it not raised an exception, it could have led to bogus results, possibly controlling a key element of the system unexpectedly and make it crash as well.

The problem here was "no one thought they needed to care". Not that an exception was raised, or the language!

Some people may prefer programming in a "I don't know for sure what will happen in some cases, but hey, it compiles".
I remember we had a team joke in a job years ago. One embedded developer, when asked if his code was ready to be integrated, would sometimes say: "it compiles, so it must work". We ended up using this as some kind of meme when we needed a laugh.
 ;D

Of course ranges, and parameters integrity in general, can be enforced in other languages including C. You'll just have to do this manually, which can get tedious (but has also distinct advantages, I talked about parameters/variable checking in another thread...)

As to the merits of exceptions themselves, which are nothing specific to Ada, but are commonly implemented in most high-level languages these days (except C, and if I got it right, Rust?), this is yet another debate, and would actually be an interesting one.

It's ofen useful to ignore variable range (allow overflow) in order to do fun bit hacks. For example blink LED every three iterations of main loop (software PWM):
Code: [Select]

static uint8_t blinkCounter = 0x00;

/* This is called on every loop in main() */
void MyApplication::LoopFunc(void)
{
        blinkCounter++;

        if(blinkCounter % 3)
              *port_io1 = 0U;
        else
              *port_io1 = 1U;
}

void main(void)
{
      TRISA = 0x00;
      ANSEL = 0x0000;
      LATA = 0x00;

      MyApplication app = MyApplication(&LATA);
 
      app.SetupFunc();

      while(1U)
      {
             app.LoopFunc();
             
             if(app.ShouldExitLoop())
                     break;
      }
}

 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14490
  • Country: fr
Re: C word
« Reply #82 on: September 23, 2019, 07:39:01 pm »
It's ofen useful to ignore variable range (allow overflow) in order to do fun bit hacks. For example blink LED every three iterations of main loop (software PWM):

In your example, you're actually neither ignoring range, nor exactly "allowing overflow" (which is a vague term, as it could well overflow in any way, and not just roll-over, if you just state that you're "allowing overflow".) And it's not really a "hack" actually...

You've in fact selected uint8_t so that the counter variable could hold values that are at least in the 0 to 2 range (which is guaranteed indeed as uint8_t is defined to be exactly 8-bit). You selected that type for a reason. You didn't ignore its range!

And then, you assumed that it will roll-over on overflow. I'd have to re-read the C standard (or C++ here, as that's what you used) to check that, but I'm still not 100% sure it guarantees that integers actually roll over. It's a perfectly reasonable assumption when one know that those languages compile pretty close to the metal, and that myriads of programmers have assumed roll-over (including myself), I'm just saying I don't actually KNOW for sure it's even guaranteed! (Now if someone has enough courage to look that up in the standard right now, please have at it. ;D )
(And for the record, yes, some CPUs actually can be configured to do various things on overflow, and not just roll over! So the possibility is physically there.)

I gave enough info above, I think, so that you could write the exact same thing in Ada with as much ease.
Something like:
Code: [Select]
-- Assuming your blink counter is only used in the way you showed:
type BlinkCounter_t is mod 3 with Default_Value => 0;   -- The good thing with default values is that you don't need to actually initialize any variable of this type
...
blinkCounter : BlinkCounter_t ;
...
blinkCounter := blinkCounter  + 1;
if blinkCounter /= 0 then
...
else
...
end if;
...

If you are going to use the counter for something else and still want it to roll over exactly 8 bits, you can replace the above with:

Code: [Select]
type BlinkCounter_t is mod 256 with Default_Value => 0;
...
-- The 'if' is now:
if (blinkCounter mod 3) /= 0 then
....

I don't think it's really neither hard, not much longer to write than the C counterpart, and the good thing is you have absolutely no assumption to make. This would be guaranteed to work on absolutely ANY target.

« Last Edit: September 23, 2019, 07:41:42 pm by SiliconWizard »
 

Offline boz

  • Regular Contributor
  • *
  • Posts: 75
  • Country: nz
    • Roving Dynamics Ltd
Re: C word
« Reply #83 on: September 23, 2019, 09:34:22 pm »

Code: [Select]
if(DefCon=5) LaunchNukes();

Whats not to love about C  :)
Fearless diver and computer genius
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #84 on: September 23, 2019, 09:58:06 pm »
Why not hate C? Because I seriously *hate* people not using braces after an if-statement

if (expr) { foo(); }

Today I spent something like 3 hours at debugging u-boot for a *stupid* bug caused by someone who hadn't had included within braces the statement following "if" by a single statement and ... when his colleague applied the patch, it added something in the wrong way  :palm:
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 6785
  • Country: pl
Re: C word
« Reply #85 on: September 23, 2019, 10:11:52 pm »
Unsigned overflow works normally in C/C++.

Signed overflow is undefined behavior, meaning the compiler is free to replace your whole code with one drawing an ASCII d*ck on the screen and that's standard-compliant.

They want to mandate two's complement signed numbers in the next version of C++. Maybe then signed overflow will be defined, maybe not.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14490
  • Country: fr
Re: C word
« Reply #86 on: September 23, 2019, 10:58:00 pm »
Why not hate C? Because I seriously *hate* people not using braces after an if-statement

if (expr) { foo(); }

Today I spent something like 3 hours at debugging u-boot for a *stupid* bug caused by someone who hadn't had included within braces the statement following "if" by a single statement and ... when his colleague applied the patch, it added something in the wrong way  :palm:

That is a common pitfall. Some teams have rules about that.
I personally have a less strict rule in most cases: don't mix braced and non-braced expressions in a single if statement. That looks confusing and can be risky for maintenance. But I don't require braces in all cases.

For instance, I'll be ok with:
if (....)
    ...;
else
    ...;

but not with:
if (...)
{
    ....;
    ........
}
else
    ...;

Of course your rule of always using braces can be defended.

As to applying patches... tell me about it.
I'm severly against applying patches (diff's) blindly to an existing code base. I usually always do it by hand-merging. In team work, that's an opportunity for code reviews and explaining to others why you modified or added this or that. Automatic merging looks nice, but it's an infinite plague.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: C word
« Reply #87 on: September 24, 2019, 02:00:26 am »
Quote
It's just what a "range" is about.
I guess I'm more boggled by defining your single-byte variables as "ranges" in the first place.  Is "for Byte_t'Size use 8;" a modifier, or a replacement for the "range"?
What happens with "subtype Byte_t is integer; for Byte_t'Szie use 8'" ?


Quote
Unsigned overflow works normally in C/C++.
Signed overflow is undefined behavior, meaning the compiler is free to replace your whole code with one drawing an ASCII d*ck on the screen and that's standard-compliant.
nonsense.  Signed overflow is a runtime condition, so the compiler can't detect it.  I suppose that it could specifically detect a signed overflow and choose to do something weird, but...



 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1211
  • Country: pl
Re: C word
« Reply #88 on: September 24, 2019, 06:32:02 am »
nonsense.  Signed overflow is a runtime condition, so the compiler can't detect it.  I suppose that it could specifically detect a signed overflow and choose to do something weird, but...
I believe it’s pretty obvious that magic made a justified simplification here. But, if you want to be very precise, let me rephrase magic’s statement: signed overflow is undefined behavior, meaning the compiler is free to generate code that, if signed overflow occurs during runtime, will draw an ASCII d*ck on the screen and that's standard-compliant. ;)

What currently is observed, is that compilers generate code that happily calculates an invalid value if a signed overflow occurs. Since very often that value is later converted to unsigned range AND because two’s complement is widely used(1) AND most processors survive signed overflows(2), the final result “magically” matches the expected outcome. But that is a pure coincidence — it is not determined by what is written in the code. :)

____
(1) Therefore, for additive operations, there is no difference between unsigned and signed ranges.
(2) TBH I do not know hardware that doesn’t have well-defined signed overflow condition.
People imagine AI as T1000. What we got so far is glorified T9.
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #89 on: September 24, 2019, 11:58:14 am »
What currently is observed, is that compilers generate code that happily calculates an invalid value if a signed overflow occurs. Since very often that value is later converted to unsigned range AND because two’s complement is widely used(1) AND most processors survive signed overflows(2), the final result “magically” matches the expected outcome. But that is a pure coincidence — it is not determined by what is written in the code. :)

... in avionics you usually have redundancy and CBITs.

Redundancy is performed by a Voter as a trusted piece of hardware that is assumed to be "intrinsically safe" (yes, it's an hypothesis, but it's verified by severe grades), but this only has a crucial role regarding interrupts response and CPU cycles on the physical bus.

Hence, it monitors how each CPU physically operates on the BUS, measuring the integrity of the CPU from its behavior on the BUS.

But there is another grade to measure integrity, and this is where CBITs play their role.

Continouls bit internal tests are scheduled as an interrupt driven task. When the Voter requires a CBIT by firing an interrupt, each CPU must respond with the correct answer.

The Voter might ask "hey, CPU(i)? are you still alive? yes, because you have just answered my interrupt(i), but are you drunken or ready to rock? let's check out by submitting this simple question: let's calculate the sum of n over i x to the i y to the n minus i for i from nought [= zero] to n from tab1 (you have a copy in rom1), what is the result?"

This way the Voter can check both the ROM integrity and the full integrity of the ALU, and if the CPU cannot handle overflows, it will be embarrassing, because the given answer will be wrong, hence the Voter will think the CPU is drunken (or damage), therefore it will exclude it from the bus.


The above is usually written in assembly (BPUM) and C(BSP), but in Ada .... well, we have some applications that must be written in a similar way, since they need to check their operative integrity.


Mission-critical means that If you have a system with m grades of redundancy, and n nodes report failure, then (m-n) must be > the minimal grade to assure the mission can have success.

Otherwise, the mission is aborted, which usually means you cannot even take off if you are on the ground and the system calculates you need strong Maintenance, or ... even worse, when you are not on the ground, your aircraft is too damaged to go on.

In both cases, you need the software to be able to check overflows in the correct way, and it would be very stupid if a bug made the Voter think the hardware is defective when it's just a software bug.

You might be fired for this, or even worse, you will be forced to have the worst fifteen min of your life in the Colonel Mustard's office.
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 6785
  • Country: pl
Re: C word
« Reply #90 on: September 24, 2019, 01:35:33 pm »
Signed overflow is a runtime condition, so the compiler can't detect it.  I suppose that it could specifically detect a signed overflow and choose to do something weird, but...
Sometimes it can be predicted in advance and the compiler may choose to do very weird things indeed.

Real bugs have been found in the wild when compilers decided that certain variable having certain value leads to UB, therefore the variable will never have that value in any valid run of the program, therefore some code can be modified by assuming the variable must have different values, because it doesn't affect any valid run of the program. Then of course somebody calls the code with a wrong argument and a complete clusterfuck unfolds, in addition to the UB, which may or may not even be a real problem by itself.
 

Offline legacy

  • Super Contributor
  • ***
  • !
  • Posts: 4415
  • Country: ch
Re: C word
« Reply #91 on: September 24, 2019, 02:05:05 pm »
And today I am fixing this

Quote
In most cases we improve GCC to exploit well defined behaviors of the standard. In this case we created defined __builtin_constant_p with insufficient documentation to allow a user to reasonably predict the surprising behavior shown in this testcase.

GCC has created a path which will never be executed and used that to introduce a constant which does not exist in the source. Unless you know what jump-threading can do, this transformation isn't obvious.


gcc-7 has an "optimization" pass that completely screws up, and generates the code expansion for the (impossible) case of calling ilog2() with a zero constant, even when the code gcc compiles does not actually have a zero constant.

And we try to generate a compile-time error for anybody doing ilog2() on a constant where that doesn't make sense (be it zero or negative). So now gcc7 will fail the build due to our sanity checking, because it created that constant-zero case that didn't actually exist in the source code.

There's a whole long discussion on the kernel mailing about how to work around this gcc bug. The gcc people themselevs have discussed their "feature" in

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=72785

but it's all water under the bridge, because while it looked at one point like it would be solved by the time gcc7 was released, that was not to be.

So now we have to deal with this compiler braindamage.

And the only simple approach seems to be to just delete the code that tries to warn about bad uses of ilog2().

So now "ilog2()" will just return 0 not just for the value 1, but for any non-positive value too.

It's not like I can recall anybody having ever actually tried to use this function on any invalid value, but maybe the sanity check just meant that such code never made it out in public.


Customers have just update their gcc compiler to v7 and a coupld of their 4.9 Kernels need to be ... patched  :scared:
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6266
  • Country: fi
    • My home page and email address
Re: C word
« Reply #92 on: September 24, 2019, 02:36:30 pm »
The reason C has Undefined Behaviour is itself an interesting tale; a relic from times when the compiler makers had no idea what kind new hardware would be, and they wanted to ensure they could support those as well.

Arithmetic rules are one of the "facilities" I'd like to control at compile time on an expression by expression basis.  I want to be able to choose between modular arithmetic with two's complement for negative values and bounded/signaling ranges.  The hardware can do this just fine (although in most cases the former is the "native" approach with zero overhead, and the latter involves generating explicit checks); it is just that C does not have a facility to express those.

If you look at e.g. C99 fenv(), the current approach is to treat everything "mathematical" as a thread/task property; this is an unnecessary abstraction that does not help in writing robust code.

(The reason I do not see types as a solution, is because that would force every expression combining variables from more than one type to use temporary variables or explicit conversions/casts.  IMNSHO, the arithmetic "rules" are not a type property, but a property of each expression.)

I've mentioned this before, but seeing what happened with C11, I have completely lost any faith in the direction the C standards committee and even GCC developers are pushing the language.  A large number of compiler developers are even pushing for C to be "merged" with C++ (perhaps not in a literal sense, but definitely in the sense of defining C concepts and approaches in terms of C++), which is kind of odd, but completely understandable when you realize that it is possible to consider C a subset of C++, even though it is factually incorrect.

The only true pushback against inaninites (as in, "but the standard says it is UB so we CAN do nasal daemons", when asked to make the compiler do the only sane thing in a particular case instead of shitting itself) in GCC I have seen have come from Linux kernel developers.  I got so fed up, I haven't submitted GCC bug fixes since 2015 or so.
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 6785
  • Country: pl
Re: C word
« Reply #93 on: September 24, 2019, 02:40:20 pm »
What happened in C11?

I thought it only added a bunch of libraries (based on new C++11 libs, indeed).
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6266
  • Country: fi
    • My home page and email address
Re: C word
« Reply #94 on: September 24, 2019, 03:40:29 pm »
What happened in C11?
For starters, C++ stuff included, Microsoft-only (optional) "safe" I/O variants added, but not even getline() from POSIX.1.  The committee is now stuffed with MS stooges, whose main focus is making sure MS can keep treating C as a subset of C++ and does not have to change their product.  The C language itself is not making any progress, unless you consider pushing for C to become a strict subset of C++ progress.

See, I've helped C programmers for years, and know how useful it would be for learners to use a subset of POSIX.1 in the learning process.  The key things are getline() (unlimited line lengths), nftw() (directory tree exploration), fnmatch() and glob() (file name matching and globbing), regcomp()/regexec() (regular expressions for text matching), fwide()/fgetws() and so on (for wide input; even POSIX has dropped the ball on getwline()).  All of these are trivially supported across all currently-used operating systems; it's just that there is one vendor who does not want to, with enough chairs in the C standard committee, stopping any such efforts.  Yet, I don't want to be the asshole who shows them that the desktop OS they so dearly love is actually a well-designed trap to keep them tied to a single OS vendor.

Unless something changes drastically about the C standard committee, the C language itself is fucked: it is destined to become a strict subset of C++ because a single vendor finds it suits their product better, and has spent enough money to stuff the committee.
« Last Edit: September 24, 2019, 03:42:52 pm by Nominal Animal »
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14490
  • Country: fr
Re: C word
« Reply #95 on: September 24, 2019, 03:58:03 pm »
I guess I'm more boggled by defining your single-byte variables as "ranges" in the first place.  Is "for Byte_t'Size use 8;" a modifier, or a replacement for the "range"?

You may be too used to very low-level stuff maybe?
Value ranges and storage size are two different things. This is a place where C can be completely confusing. (And again, of course you can implement/enforce ranges manually, but that's not part of the language itself.) By default, implementations assume that the storage size implies value range. We're used to that, but Ada gives you more.

The 'Size' attribute (Ada has attributes, as some other languages do) defines storage size in bits. It's called an attribute, but I guess you can see that as a "modifier". It's absolutely no replacement for the range. A range makes a "subtype", which inherits its parent's attributes (for instance, integer or natural here).

What happens with "subtype Byte_t is integer; for Byte_t'Szie use 8'" ?

That's an interesting question, which shows you have grasped the idea!
(Just a side note, your declaration wouldn't work, you can't declare a subtype as a base type (integer here), but that's just a technical Ada thing. I'll assume you declared a type that requires more bits than what you then defined in the Size attribute.)

The above won't work on most platforms (compilers will reject that). I'd have to brush up on the standard, but I think "integer" is implementation-defined, a bit like "int" in C. And as with C, in MOST cases, "integer" is wider that 8-bit, with an according range. (I don't think you'll ever run into a platform for which an Ada compiler will define "integer" as an 8-bit word!)

Thus, your declaration would be contradictory, and not allowed. GCC, for instance, would give that: "size for "Byte_t " too small, minimum allowed is xxx" (xxx depending on how many bits are required for the integer type).

You can't define types with sizes too small to hold their ranges, but the other way around is perfectly possible.

Quote
Unsigned overflow works normally in C/C++.
Signed overflow is undefined behavior, meaning the compiler is free to replace your whole code with one drawing an ASCII d*ck on the screen and that's standard-compliant.
nonsense.  Signed overflow is a runtime condition, so the compiler can't detect it.  I suppose that it could specifically detect a signed overflow and choose to do something weird, but...

As I said earlier, I actually was not sure what the standard had to say about integer overflow (signed or unsigned for that matter), and wasn't feeling like looking that up.
But, I just did. And this is what C99 says:
Quote
A computation  involving  unsigned  operands  can  never overflow, because  a  result  that  cannot  be  represented  by  the  resulting  unsigned  integer  type  is reduced  modulo  the  number  that is  one  greater  than  the  largest  value  that  can  be represented by the resulting type."

So, it defines the "roll-over" behavior we talked about for unsigned indeed. As it doesn't actually say anything about signed operands, we can conclude that's it's by definition an undefined behavior indeed.
The compiler can't detect that an operation will overflow (unless its static analysis features are good enough and it can be deducted from static analysis only), but it can trivially detect that you are using signed integers in an operation that *could* overflow (such as + or -), and generate appropriate code to ensure a specific overflow behavior if overflow happens.
« Last Edit: September 24, 2019, 04:01:26 pm by SiliconWizard »
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 1211
  • Country: pl
Re: C word
« Reply #96 on: September 24, 2019, 06:58:09 pm »
For starters, C++ stuff included, Microsoft-only (optional) "safe" I/O variants added, but not even getline() from POSIX.1.  The committee is now stuffed with MS stooges, whose main focus is making sure MS can keep treating C as a subset of C++ and does not have to change their product.  The C language itself is not making any progress, unless you consider pushing for C to become a strict subset of C++ progress.
Microsoft is not even supporting C anymore. They are about 20 years late, so they would have to put a lot of effort now to support even the current version of the language. So how can they want to keep anything, if there is nothing to keep? While Microsoft has introduced the _s versions of some functions, it is not even supporting them in full (see K.3.7.4.3 as an example). And the idea itself is not Microsoft’s: avoiding returning newly allocated resources was a normal part of best practices before C11 was released. I don’t like Microsoft myself, but you can’t blame them for everything.

See, I've helped C programmers for years, and know how useful it would be for learners to use a subset of POSIX.1 in the learning process.  The key things are getline() (unlimited line lengths),
This would go against the minimalism principle of C. It would essentialy be more convenient fgets. While the _s functions are also duplicates, they are not a part of C core specification and they bring significant gains by offering the proper syntax, which should’ve been used from the begining.

nftw() (directory tree exploration), fnmatch() and glob() (file name matching and globbing),
That would require including a completely new concept to C: that of a file system and directories hierarchy. And that would be a very specific definition of it, which may become outdated very fast. Also see the next paragraph…

regcomp()/regexec() (regular expressions for text matching),
Writing a proper regex engine requires a lot of effort. Much more than the rest of the C library. For what? A few rare use cases, for which you would use a proper, 3rd party library anyway? That also applies to the filesystem-related suggestions. What would be the use of those in a C program? Those belong to the business logic, not interfacing system or writing firmware for a microcontroller, and they have limited use even in general. Do not confuse your specific domain with the whole world.

Unless something changes drastically about the C standard committee, the C language itself is fucked: it is destined to become a strict subset of C++ because a single vendor finds it suits their product better, and has spent enough money to stuff the committee.
If it is, it’s because the language is low-level, but in fact it doesn’t correspond to the real hardware in 2019. It’s still PDP-11, relaxed a bit to allow different platforms. But there is no competition.
People imagine AI as T1000. What we got so far is glorified T9.
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2803
  • Country: nz
Re: C word
« Reply #97 on: September 24, 2019, 10:10:50 pm »
...it’s because the language is low-level, but in fact it doesn’t correspond to the real hardware in 2019. It’s still PDP-11, relaxed a bit to allow different platforms.

All programming languages and runtime environments (and you could alos argue even ISAs, especially if they are microcoded) don't correspond to real hardware, but are pretty much abstracted virtual machines, with different levels of details hidden away from the programmer. The original PDP-11/20 was a 16-bit machine without any MMU, without hardware floating point, without H/W multiply, and with 32Kbyte of memory - almost below today's lowest-end micros.

Are you indirectly saying that C isn't suited to the following use cases:

- CPUs that use twos complement binary?
- CPUs with hardware multiply or divide?
- CPUs with 32-bit register machines?
- CPUs with 64-bit register machines?
- CPUs with an address space > 18-bits?
- CPUs with floating point H/W?
- CPUs that don't have a dedicated stack pointer (like most RISC designs?
- CPUs with support for paging/virtual memory?
- Storage that can go > 1,000 MB/s (I got an NVMe disk in my Intel NUC!)
- CPUs with vector instructions? (well, you may have a point there)
- Multi-threaded/multi-core CPUs? (well, this is true too, I guess...)

Which bits of the PDP-11 is C boat-anchored onto?
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6266
  • Country: fi
    • My home page and email address
Re: C word
« Reply #98 on: September 24, 2019, 10:15:47 pm »
That also applies to the filesystem-related suggestions. What would be the use of those in a C program? Those belong to the business logic, not interfacing system or writing firmware for a microcontroller, and they have limited use even in general. Do not confuse your specific domain with the whole world.
You're the confused one: you don't use a hosted environment (with the standard library available) for microcontrollers or kernels and such anyway.  The standard C library is only really useful for portable applications.  Adding "safe" optional variants instead of making them actually useful is idiocy.

Note that I personally am quite happy with C99 and POSIX.1-2008.  Not all of C11 is bad, but it just isn't good enough in practice for one to use or rely on.  I'm just saying that seeing where the C standards committee went with C11, indicates there is no hope C will actually become any more useful to developers like myself.

In practice, the C implementations have already split the standard library, as the C compiler provides some of the functions instead of the C library.  (For GCC, see here.)  Some functions interfaces, especially printf() and scanf() families of functions formatting string formats, must be known to the compiler for it to check if the syntax makes any sense; yet, they are only defined in terms of a hosted environment.  None of these practical changes/developments have been acknowledged or considered by the C standards committee, which means they really aren't interested in how the C language is actually used, and what kind of changes or additions would make it even more useful.  They're just a circle jerk discussing what kind of changes make sense for their compiler products.
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6848
  • Country: va
Re: C word
« Reply #99 on: September 24, 2019, 10:41:06 pm »
Quote
only defined in terms of a hosted environment

What do you mean by a hosted environment?
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf