[HN Gopher] Static Integer Types (2021)
       ___________________________________________________________________
        
       Static Integer Types (2021)
        
       Author : isaacaggrey
       Score  : 33 points
       Date   : 2022-05-17 18:25 UTC (1 days ago)
        
 (HTM) web link (tratt.net)
 (TXT) w3m dump (tratt.net)
        
       | tialaramex wrote:
       | Note that, here in 2022, those ARM chips with CHERI do now exist,
       | Morello: https://www.arm.com/architecture/cpu/morello
       | 
       | Also, although this article says Rust has to choose usize ~= u128
       | on Morello which is unpalatable, Aria proposes that instead Rust
       | _tweaks the definition of usize_ to say it 's about addresses not
       | pointers, and thus usize ~= u64 on Morello.
       | 
       | https://gankra.github.io/blah/fix-rust-pointers/ leading into
       | https://gankra.github.io/blah/tower-of-weakenings/
       | 
       | If you have nightly Rust, you can play with Aria's new semantics
       | because she implemented them. I think they're a good idea, but I
       | don't have much "skin in the game", unlike, apparently the author
       | of this article.
        
       | klodolph wrote:
       | > [...] but what would a signed memory address mean and what
       | happens if/when we want to use all 64-bits to represent memory
       | addresses [...]
       | 
       | Half of this space is reserved for the kernel.
        
         | chrisseaton wrote:
         | > Half of this space is reserved for the kernel.
         | 
         | That's an implementation detail - and what if your kernel is
         | different, or you're the one writing the kernel?
        
           | tialaramex wrote:
           | Right, the languages which have these "static integer types"
           | are going to be more predisposed to implementing an operating
           | system kernel or writing bare metal code that doesn't sit on
           | an operating system.
           | 
           | Linux is written in C (one of these languages) and likely
           | parts of it will eventually be written in Rust (another of
           | them). Python, which does not bother with this (in 3.x) isn't
           | very well suited to writing such software, although Snek
           | ("Snek is friend!"
           | https://www.youtube.com/watch?v=w4sWZzYysvs&t=2401s)
           | https://sneklang.org/ is somewhat suitable for small devices.
        
         | Someone wrote:
         | That's an implementation choice (likely a highly popular one in
         | 64-bit systems), not a necessity. It could be a quarter (as
         | Windows could do in 32 bit. https://docs.microsoft.com/en-
         | us/windows/win32/memory/4-giga...) or even nothing (that's what
         | Mac OS X did int 32 bit. See
         | https://flylib.com/books/en/3.126.1.91/1/), in OSes that have a
         | 100% separate address space for the kernel.
        
           | tialaramex wrote:
           | Both these choices (and others) were available in 32-bit
           | Linux, even after x86-64 was popular because Enterprises can
           | be slow moving (e.g. first AMD64 CPUs come out in 2003, but
           | it took years for some outfits to stop buying 32-bit Intel
           | CPUs and then years _more_ to stop putting a 32-bit OS on
           | them)
           | 
           | Linux offered a conventional 2:2 split (as your parent
           | describes) but also a 3:1 split (like Windows /3GB) and 4:4
           | (like OS X, plenty of address space but now context switches
           | are very slow) but I believe it also had 1:3 (there can be
           | cases where what you need is a _lot_ of RAM mapped by the
           | kernel, but you can 't or won't go 64-bit) and something like
           | 3.5:0.5 for people who hadn't learned their lesson with 3:1
           | ratio and needed to be kicked in the head more often.
        
       | phdelightful wrote:
       | Is there a C++ library that implements static integer types with
       | these ideas? In principle the operations don't seem complicated,
       | but there are probably enough edge cases that it's tricky to get
       | it all right.
        
       | RcouF1uZ4gsC wrote:
       | If you are interested in a C++ library that makes using integers
       | a lot safer, take a look at Boost.SafeInt
       | 
       | https://www.boost.org/doc/libs/1_79_0/libs/safe_numerics/doc...
        
         | tialaramex wrote:
         | Ironically (?) this doesn't seem to supply the one thing
         | implied by the explanation it gives
         | 
         | It begins by telling us about how in C++ you're obliged to
         | detect _potential_ overflow, because if the overflow already
         | happened then in many cases that 's Undefined Behaviour and all
         | bets are off.
         | 
         | But you shouldn't use this to merely _detect_ such overflow,
         | out of the box it will give you an Exception - and C++
         | proponents will insist those are only for _Exceptional_
         | situations you mustn 't expect them to have decent performance
         | for merely _conditions_ such as this overflow check - which
         | might happen in some inner loop depending on the system design.
         | 
         | I don't see a way to ask the equivalent of Rust's checked_add()
         | which gives you back Some(answer) or None depending on whether
         | this addition overflows.
        
       | MauranKilom wrote:
       | > Undefined behaviour on integer types is a terrible idea (though
       | unspecified behaviour might have a place).
       | 
       | Would unspecified behavior be sufficient to attain those compiler
       | optimizations that are the reason for keeping signed integer
       | overflow undefined in newer C and C++ versions?
        
         | klodolph wrote:
         | No, it would not be sufficient.
         | 
         | "Undefined behavior" is nothing more than "the compiler is
         | allowed to assume that your program does not do this, and can
         | use that assumption to optimize your program." The compiler can
         | optimize your program to do something completely unexpected
         | when your integers overflow.
         | 
         | For example, there are architectures with a special "counter"
         | register that is more efficient to use for loops. This may come
         | with some kind of instruction like "decrement counter and
         | branch if not zero". The semantics of this counter register are
         | often not a clean match for the semantics of signed integers.
         | For example, the counter may be wider than the integer type
         | you're using, or it may only support certain types of
         | comparisons.
         | 
         | Edit: That's the example that came to mind. There are a bunch
         | of microoptimizations involving arithmetic or comparisons that
         | are only valid when overflow is not possible. But loop
         | optimizations are the monster that looms over everything in a
         | discussion about signed integer UB in C, because when signed
         | integer overflow is undefined, the compiler is allowed to make
         | tons of inferences about loops that are much harder to reason
         | about otherwise. The compiler can transform variables to use
         | induction, make inferences about how memory accesses work, do
         | certain types of vectorization, etc. If you are curious about
         | the performance impact, try compiling various benchmarks with
         | `-fwrapv` or `-ftrapv` and compare the performance of these
         | benchmarks to what happens when you don't use either of those
         | flags.
         | 
         | Unspecified behavior is actually a lot narrower than it sounds.
         | It sounds like "the compiler can do anything here", but that's
         | actually what undefined behavior is. For unspecified behavior,
         | you're either using an unspecified value, or there's something
         | with a set of possible behaviors to choose from. For example,
         | evaluation order is unspecified, the sign of certain operations
         | is unspecified, whether two identical string literals compare
         | equal is unspecified, etc. The compiler has to make some choice
         | --function arguments are evaluated in _some_ order, two
         | particular string literals either compare equal or don't.
         | 
         | Consider an optimization like (x+a)/a => x/a+1. This is
         | possible because the compiler assumes "x+a" does not overflow.
         | If overflow were unspecified behavior, _something_ would have
         | to happen when you add x+a, and the result would have to have
         | to trap or result in a value of the correct type. No possible
         | "unspecified" behavior would result in additional precision
         | that prevented overflow (the standard is clear about precision
         | for arithmetic operations).
        
       | wahern wrote:
       | > I hope it's uncontroversial that, like Rust, languages should
       | not allow implicit casts between integer types at all.
       | 
       | I find this controversial. The unstated option #4 for addressing
       | C's permissive implicit narrowing conversions is to simply
       | disallow implicit narrowing conversions, but continue providing
       | implicit integer conversions to types of greater-than-or-equal
       | rank.
       | 
       | I suspect the reason you left out option #4 is an entirely
       | different, self-imposed constraint in Rust:
       | 
       | > [Rust] defines From casts for integer types that will succeed
       | on every platform. Since casting a 32-bit integer to a usize
       | would fail on a 16-bit platform, I'm not ever allowed to compile
       | -- even on a 64-bit platform where such a cast would always
       | succeed.
       | 
       | But therein lies the original questionable turn that Rust made
       | wrt usize--there's no accounting for _future_ platforms.
       | 
       | What makes C so portable is precisely the notion of C's integer
       | ranking system and implicit conversions. By guaranteeing relative
       | rank and permitting implicit widening conversions, most issues on
       | most _future_ architectures have been accommodated. It 's not
       | perfect, but the the value for your money is immense. The vast
       | majority of issues like this go away.
       | 
       | And what do you lose by permitting implicit widening conversions?
       | There are some potential correctness issues. For example,
       | subexpressions computing bitmasks might not behave as expected
       | when converted to a wider type in an outer expression. But this
       | same problem exists in your proposed solutions and explicit
       | conversions, generally. I would even consider implicit
       | conversions safer because we can always add additional rules
       | (optional or mandatory) that capture these cases (e.g. -Wwidth-
       | dependent-shift-followed-by-implicit-widening), whereas explicit
       | conversions usually have the effect of short-circuiting stronger
       | type checking. (Unless you go the C++ route and add a panoply of
       | conversion operators. But better hope you chose the right one!)
       | 
       | You lose the ability for a build to fail on target X because the
       | conversion wouldn't work on target Y. But that requirement is
       | fundamentally in conflict with accommodating _future_
       | architectures, and incentives the type of explicit conversions
       | that could hide or complicate future porting issues.
       | 
       | Regarding this footnote:
       | 
       | > [8] For reasons that are unclear to me, uintptr_t is an
       | optional type in C99. However, I don't know of any platforms
       | which support C99 but don't define it.
       | 
       | AFAIU, the reason is exactly because the committee foresaw that
       | not all architectures could accommodate conversions between
       | object pointers and integers. Relatedly, the C standard DOES NOT
       | permit conversions between object pointers and function pointers,
       | which also means the C standard DOES NOT permit conversions
       | between function pointers and uintptr_t, even if uintptr_t is
       | defined. Both capability systems and memory architectures already
       | existed that couldn't accommodate the latter conversions. The
       | value of function pointer/integer conversions was much less than
       | object pointer/integer conversions, so they defined uintptr_t
       | only for object pointers, and made uintptr_t optional.
       | 
       | Function pointer/object pointer conversions are widely supported
       | by C compilers, but this is an extension that makes code non-
       | conforming. See C11 J.5.7. Note that non-conforming does not mean
       | undefined; it just means that such code is not C code as defined
       | by the standard and beyond its purview. It creates a headache for
       | POSIX, which defines a singular interface, dlsym, for acquiring
       | _both_ object and function references.
        
       ___________________________________________________________________
       (page generated 2022-05-18 23:02 UTC)