For whatever reason most newer languages (say rust) don't actually solve this problem either. They could diverge from the normal and do saturating (which arm does have) ints, or throw exceptions on overflow/underflow, but they don't because that would be to hard when they have to manually check overflow on each operation because its not a common feature of many processor arches.
edit: although LEA is one of the instructions which avoids flags updates, so even if you wanted to trap it probably wouldn't.
There are tons of checked_add()_sub/_mul macros or functions floating around. At least in C++ one could override the global operators if needed.
(And if that’s the case, by now INTO will be a slow microcode implementation, with JO _trigger_into probably being much faster)
No Intel/AMD CPU supports INTO in 64-bit mode.
It is wise to compile any C/C++ program with options like:
"-fsanitize=undefined -fsanitize-undefined-trap-on-error"
(as for gcc) or whatever they are called by the used compiler.
In that case, the compiler will add instructions with the same effect as INTO, but with more overhead than it was needed in 32-bit Intel/AMD CPUs.
The execution time should be about the same for INTO and JO, both for the normal case, when both INTO and JO are ignored and for the error case, when JO is guaranteed to be mispredicted, so its long execution time added to the time needed to fetch and execute the following instructions required to match the effect of an INTO (e.g. saving state and a second not-predicted jump or call that might be needed to reach the actual exception handler) will also be about the same as what would have been needed by an INTO exception, if not longer.
And if you're really worried about size, "JNO 1f; INT xy; 1f:" 'with xy having to be system wide and well known, but it would only take 3 extra bytes (or just 2, if you HLT and the OS knows what JO/HLT combo means). The branch predictor will likely predict this as a no-take in any tight loop.
Other architectures, like IBM POWER, have much more powerful conditional trap instructions.
The 64-bit ARM ISA is the only other important architecture where integer overflow must also be done by combining a conditional jump either with a function call or with a supervisor call (i.e. the equivalent of INT).
It is true that if INTO would have never been defined in 8086, it would not have been a great loss, because it can be emulated, as you say.
Nevertheless, its deprecation by AMD cannot be a reason for praise. At the same time, they have also deprecated a few other instructions, which were a good choice, because they were much less useful. On the other hand there were a very large number of other instructions that could have been deprecated instead of INTO, which were much less useful than INTO, but they have been kept nevertheless.
It was pretty clear that INTO and BOUND were not included on the deprecated list based on their degree of usefulness but mainly because it was obvious that the CPU designers will be able to get away with this, because all the important software companies were habituated to compile their release versions by choosing to disable all run-time checks, so they will not complain about this omission.
If the software vendors would have ever been punished for choosing to omit the error checks, then they would have requested CPUs that still have good performance when the checks are enabled and the CPU designers would have been prodded to improve such features.
Nowadays, due to the hype about security flaws, the CPU vendors are introducing a large number of run-time checks, but most of them seem rather misguided, because in the attempt to protect against programming errors some of the methods used e.g. for limiting the targets of jumps also prevent the use of certain good implementation techniques for some programming language features.
Instead of the many dubious new "security" features of Intel/AMD and ARM CPUs, I would much prefer to see improvements in the efficiency of overflow checking and bounds checking, because these are checks that are required in any program, no matter how well it is written, while most of the new "security" features are mostly useful to avoid problems created by programmer mistakes.
I.e., sometimes you might want the check, but other times you certainly don't, and turning it off and on at the right times is too hard to get right. And, the ability to turn it on, even when it was never used, slowed everything down.
You would anyway need to be able express the choice in the languages used. If you had such a way, a compiler could insert extra instructions to check or to suppress checking where it is not wanted, but making such a choice wherever arithmetic happens would be a big burden to programmers already just barely hanging on.
It was removed because it was seldom used by Microsoft and by the other large companies which mattered, so the AMD and Intel designers took advantage of this bad habit of many programmers, in order to simplify their CPU design work.
It was seldom used because the programmers have always preferred to obtain the highest speed even with the risk of erroneous computations in seldom cases.
The reason is that a lower speed is noticed immediately, possibly leading to a lost sale, while occasional errors may be discovered after a long time and even when they are discovered, the programmers who have made this choice are not punished proportionally with the losses that might have been caused to the users, which in most cases are difficult to quantify anyway.
A normal policy is to always check for integer overflow, out-of-bounds accesses and so on. Such checks are provided by all decent compilers.
Only when performance problems are identified and if it is certain that there is no danger in omitting the checks, the checks should be omitted in the functions where this matters.
Selecting whether run-time checks must be done or not is very easy. You just need to use the appropriate sets of compiler flags for the 2 kinds of functions, those with enabled checks and those with disabled checks.
It is sad that the C tradition has imposed that the default compiler flags are with disabled checks, but that is not an acceptable excuse. Any C/C++ programmer should always enable the checks, unless there are good reasons to disable them.
If they do not do this, it is completely their fault and not the fault of the programming languages.
However the C/C++ standard libraries have the problem that they lack a good standard way of handling such exceptions (i.e. a better way than the UNIX signals). That means that if you do not want to abort the program in case of overflows or out-of-bounds accesses, it can be difficult to ensure a recovery after errors.
In fact harmless overflow is extremely common, and relied upon. Trapping by default, while it would would call attention to some bugs, would also turn many working programs into crashing programs.
Saturating arithmetic would sometimes produce better results.
When computing with modular numbers, you rely on the exact modular operations. That is not overflow.
There exists no "harmless overflow". The overflow of "signed integers" is an undefined operation in the C/C++ standard.
The actual behavior of integer overflow is determined by the compiling options. With any decent compiler, you may choose between 3 behaviors: trap on overflow, which aborts the program unless it is caught, display an error message and continue the execution with an undefined result of the overflown operation (typically with the purpose of also seeing other error mesages), or ignore the overflow and continue the execution with an undefined result of the overflown operation (the default option).
Relying in any way upon what happens on overflows (of "signed integers"), when choosing a compiling option that does not trap on overflow, is an unacceptable mistake, because what the program does is unpredictable.
Relying on what modular arithmetic does with "unsigned numbers" is correct, except that in most cases they are not really used as modular numbers, but the programmers expect that modular reduction will never happen. When it happens and it was not expected, it may cause similar problems like overflows, even if the result could have been predicted.
You are right that saturating arithmetic is the main alternative to trapping on overflow. It can be used in the same way as infinities are used with floating-point numbers when the overflow exception is masked.
Unfortunately, saturating arithmetic does not exist in the standard C/C++ languages. Therefore in C/C++ you may choose only between trapping on overflow and dangerous undefined behavior.
Ada does this correctly, and lets you choose between exception and wraparound. UB is not an option in the language spec, though you can turn off the checks as an optimization option in GNAT, or maybe turn it off at specific lines of code with a pragma.
Of course using straighjacket systems programming languages (as they put it) doesn't match hacker culture, so here we are.
In the Ariane 5 case, an ill-tuned and anyway unnecessary out-of-bounds check was compiled into the inertial platform firmware, causing a debug traceback to be dumped to the rocket gimbal controls, which interpreted the traceback as instructions to steer sideways. To be clear: without the trap, the payload would have been delivered unharmed. There was nothing useful that could be done about the out-of-bounds value during the actual launch, and they knew that when they built the system. Extra points for dumping debug details down a channel where it corrupted correct data.
Europeans I have mentioned this to considered what did happen a sensible outcome.
The code reuse in the project was pretty poor engineering, they reused code from a different project which had different flight paths and characteristics, and didn't test it.
The modern equivalent would be copying and pasting code from a completely different project, and then just shipping to production without testing it at all, even locally.
Note that Ada is being used for the Ariane 6.
Spacecraft failures that can be blamed on failings of a particular language are very rare. There was a planetary space probe that failed arguably because it was coded in Fortran.
Leaving in debug code, especially for an entire part of the launch that doesn't exist anymore by bringing in reused and untested code, seems like the real problem. I'm pretty sure integer overflow in this case would be undefined behavior, so you don't know what the compiler would do, especially since things were a lot more wild on the hardware side in 1996. Your point sounds like arguing that preferring Objective-C would prevent all those pesky `NullPointerException`s in Java since it allows sending messages to nil (null), but that's success by coincidence.
If you read the ESA's report [1], there were a lot of things which failed before you even get to the language. This software failure is the only one in the entire Ariane series of rockets. Considering that this is 1996, the list of languages to pick from was pretty slim, e.g. this predates C++ initial standardization (1998), `enum class` and other modern C++ features.
Even using just Ada 83 (of which 95, 2005, 2012, and 2022 succeeded and improved), I would argue that its forward looking features of preventing mixed mode arithmetic having properly typed checked enums and other features (like being strong typed) probably prevented a significant number of failures should other available languages at the time been used.
> The value of BH was much higher than expected because the early part of the trajectory of Ariane 5 differs from that of Ariane 4 and results in considerably higher horizontal velocity values.
> o) In Ariane 4 flights using the same type of inertial reference system there has been no such failure because the trajectory during the first 40 seconds of flight is such that the particular variable related to horizontal velocity cannot reach, with an adequate operational margin, a value beyond the limit present in the software.
The Ariane 5 disaster was implicit in the design. Without that built-in flaw, the Ariane 4 inertial platform would have worked correctly as-was, and the first Ariane 5 would have delivered its $half-billion payload successfully, instead of obliterating it.
There is no circumstance where dumping debug tracebacks down a control channel not prepared for that is ever correct. Period. Anyone suggesting otherwise is displaying their blinders.
We agree here.
Did you read the report?
> The error occurred in a part of the software that only performs alignment of the strap-down inertial platform. This software module computes meaningful results only before lift-off. As soon as the launcher lifts off, this function serves no purpose.
> The alignment function is operative for 50 seconds after starting of the Flight Mode of the SRIs which occurs at H0 - 3 seconds for Ariane 5. Consequently, when lift-off occurs, the function continues for approx. 40 seconds of flight. This time sequence is based on a requirement of Ariane 4 and is not required for Ariane 5.
> m) The inertial reference system of Ariane 5 is essentially common to a system which is presently flying on Ariane 4. The part of the software which caused the interruption in the inertial system computers is used before launch to align the inertial reference system and, in Ariane 4, also to enable a rapid realignment of the system in case of a late hold in the countdown. This realignment function, which does not serve any purpose on Ariane 5, was nevertheless retained for commonality reasons and allowed, as in Ariane 4, to operate for approx. 40 seconds after lift-off.
> The reason for the three remaining variables, including the one denoting horizontal bias, being unprotected was that further reasoning indicated that they were either physically limited or that there was a large margin of safety, a reasoning which in the case of the variable BH turned out to be faulty.
> Ariane 4 should not produce such an acceleration
My point is that it's a velocity, not an acceleration and hence is physically impossible by the design and flight parameters on the Ariane 4. It's like saying an unsigned byte (8 bits) is okay for a speedometer for a minivan because >255 mph is outside the physical capabilities of the vehicle, but reusing that speedometer code for a fighter jet is not appropriate.
All code is written with these assumptions of value ranges, it's just a manner of how the language handles them, and whether that behavior is predictable (UB or not) or crashes when outside those bounds. A lot of C and C++ code is written with short/int/long without realizing that those are fuzzy boundaries (short <= int <= long) and without going to ensure sufficient sizes (e.g. uint32_t). Math code which doesn't account for this can produce wild and usually incorrect behavior.
When enabled, any value in Ada can throw a constraint check if violated, and it just makes it explicit. You're assuming that "Don't crash on overflow" is always a good thing based on a single case, there's a lot of systems, particularly mechanical ones, in which a broken sensor reporting a pegged sensor value would want an immediately halt to operation. Also in space applications, out of bounds values can arise from other conditions as well, such as cosmic rays flipping bits, in which a computer restart (relying on the redundant system) is the correct and expected behavior.
Well, yeah. The firmware had been designed for a quite different rocket, the Ariane 4: https://www.bugsnag.com/blog/bug-day-ariane-5-disaster
I don't know how this is really a mark against Ada, it's an issue that would only be discovered by human intervention or by running a simulation of the rocket.
It is not a mark against Ada, particularly, it is a mark against brain-dead insistence on overflow traps.
And, destroying a $half-billion payload because you insisted on leaving debugging apparatus in a production system would seem less obviously correct if the cost of the loss were deducted from your salary. So, people insisting it was a good result may be interpreted as expressing satisfaction with freedom from consequences of irresponsible behavior.
Have you seen how the C math library does error handling? It silently writes to errno. I can just imagine the runtime using the trap to do just that and continue as if nothing happened.
However all C/C++ compilers have options to detect and report or trap on all common errors, but few people use these compiling options, because of a usually baseless fear that they might reduce the performance more than acceptable.
In the cases when the performance is indeed too low for correct execution, the complaints should be directed to the CPU manufacturers who are guilty of this (by neglecting the architectural features needed for handling many kinds of exceptions, which were considered mandatory in older CPUs), instead of improving the performance of the programs by cheating, i.e. by omitting the error checks and by accepting that the programs may have horribly wrong behaviors in seldom cases.
In C++ you can throw an exception, and count on anybody who cares to act on it, resolving the conflict.
Another one would be bound, which eventually got dropped.
is a massive understatement…
That flag is not set by default in the new project templates for whatever reason. There's also an escape hatch, the unchecked keyword, for the rare cases where you need to overflow, e.g. when implementing hash tables.
Midori team kind of disagreed with that, and most of System C# features keep landing on regular C# a bit per version, but I digress.
Also Singularity https://en.wikipedia.org/wiki/Singularity_(operating_system) and a few random attempts like SharpOs https://sourceforge.net/projects/sharpos/ and Cosmos.
An approach similar to what Meadows has taken with NuttX.
I haven't mentioned, because I expected it as a possible reply that Singularity wasn't 100% pure Sing#.
"
Effectively this implies that the compiler is free to emit code that operates on `reg` as if it were a 32-bit value. If this is the case (and it is on the kernel I tested), a user can forge an expression register value that will overflow upon multiplication with `NFT_REG32_SIZE` (4) and upon addition with `len`, will be a value smaller than `sizeof_field(struct nft_regs, data)` (0x50). Once this check passes, the least significant byte of `reg` can still contain a value that will index outside of the bounds of the `struct nft_regs regs` that it will later be used with. "
Which is the classic way to exploit integer overflow to bypass buffer checks (i've personally written code like this, which thankfully was caught before it got this far).