• Prioritize Performance over Correctness

    From Janis Papanagnou@3:633/10 to All on Wed Jul 22 01:38:59 2026
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title.[*] I don't recall whether
    that had already been referenced anywhere in that thread; if not
    it might be of interest to some. A couple arguments and examples
    look familiar already, but I think it may be valuable anyway.

    Janis

    [*] https://research.swtch.com/ub


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Tue Jul 21 17:26:41 2026
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title.[*] I don't recall whether
    that had already been referenced anywhere in that thread; if not
    it might be of interest to some. A couple arguments and examples
    look familiar already, but I think it may be valuable anyway.

    Janis

    [*] https://research.swtch.com/ub

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness". (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */

    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Wed Jul 22 01:50:00 2026
    In article <113p2o1$2es3m$1@kst.eternal-september.org>,
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title.[*] I don't recall whether
    that had already been referenced anywhere in that thread; if not
    it might be of interest to some. A couple arguments and examples
    look familiar already, but I think it may be valuable anyway.

    Janis

    [*] https://research.swtch.com/ub

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness". (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    From the post:

    ```
    term% cat eraseall.c
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    Do();
    return 0;
    }

    term% clang -O1 -Wall -Wextra -pedantic -std=c23 -o eraseall eraseall.c
    term% ./eraseall
    baz invoked.
    term%
    ```

    Something similar came up a month or so ago; recall my "what.c"
    program:

    ```
    term% cat what.c
    #include <stdio.h>
    int main(void) { for (unsigned int k = 0; k != 1; k += 2){} return 0; }
    void hello(void) { printf("Hello, World!\n"); }
    term% clang -O1 -Wall -Werror -pedantic -pedantic-errors -std=c23 -o what what.c
    term% ./what
    Hello, World!
    term%
    ```

    It's notable that the quoted text for the ANSI C standard also
    says that an implementation is free to abort translation if it
    encounters UB, which I recall was something that was a point of
    concention in the earlier discussion. Kuyper posted a link to
    a defect report that suggested that the compiler had to
    translate the program if it could prove that (in that case,
    manifest) UB was never triggered; however, that was from 1994,
    and not normative anyhow. The committee has had ample time to
    tune up the writing about this in the standard, and has chosen
    not to: Cox's post is on-point; the current standard is deeply
    flawed.

    - Dan C.


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Keith Thompson@3:633/10 to All on Tue Jul 21 20:25:38 2026
    cross@spitfire.i.gajendra.net (Dan Cross) writes:
    [...]
    ```
    term% cat eraseall.c
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    Do();
    return 0;
    }

    term% clang -O1 -Wall -Wextra -pedantic -std=c23 -o eraseall eraseall.c
    term% ./eraseall
    baz invoked.
    term%
    ```

    In that program, Do is a function pointer that's initialized to a null
    pointer value (because it's static), so the call `Do()` has undefined
    behavior.

    Something similar came up a month or so ago; recall my "what.c"
    program:

    ```
    term% cat what.c
    #include <stdio.h>
    int main(void) { for (unsigned int k = 0; k != 1; k += 2){} return 0; }
    void hello(void) { printf("Hello, World!\n"); }
    term% clang -O1 -Wall -Werror -pedantic -pedantic-errors -std=c23 -o what what.c
    term% ./what
    Hello, World!
    term%
    ```

    That's an example of something more specific. N3220 6.8.6.1p4
    gives explicit permission for an implementation to "assume" that
    certain iteration statements will terminate. (I've said before
    that I dislike this rule for several reasons.)

    It's notable that the quoted text for the ANSI C standard also
    says that an implementation is free to abort translation if it
    encounters UB, which I recall was something that was a point of
    concention in the earlier discussion. Kuyper posted a link to
    a defect report that suggested that the compiler had to
    translate the program if it could prove that (in that case,
    manifest) UB was never triggered; however, that was from 1994,
    and not normative anyhow.

    C89 and all later editions of the C standard say (in a non-normative
    note) that undefined behavior can include "terminating a
    translation or execution (with the issuance of a diagnostic
    message)". But almost all undefined behavior occurs at run time.
    That could have been worded more clearly, but my interpretation
    is that an implementation that detects that a source construct
    has undefined behavior can terminate translation only if it can
    prove that it's always executed. `if (0) 1/0;` does not permit
    terminating translation.

    The committee has had ample time to
    tune up the writing about this in the standard, and has chosen
    not to: Cox's post is on-point; the current standard is deeply
    flawed.

    How would you suggest fixing it?

    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */

    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Wed Jul 22 07:33:29 2026
    On 2026-07-22 02:26, Keith Thompson wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title. [...]

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness". (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    It was actually a (deliberate) "marketing trick" to provoke interest
    by reducing the title to an (IMO) yet more aggravating formulation.

    Yes, it was not my intention to suggest performance over correctness.
    (Would anyone?)

    Janis


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Wed Jul 22 10:41:15 2026
    On 22/07/2026 07:33, Janis Papanagnou wrote:
    On 2026-07-22 02:26, Keith Thompson wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title. [...]

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness".ÿ (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    It was actually a (deliberate) "marketing trick" to provoke interest
    by reducing the title to an (IMO) yet more aggravating formulation.

    Yes, it was not my intention to suggest performance over correctness.
    (Would anyone?)


    Unfortunately, the answer to your final question is yes - and that is
    one of the reasons UB can be a problem. If by "correctness" you mean
    "code does what it should", people do sometimes release code that they
    know has bugs, but they know that fixing them would slow down the code - perhaps it is still good enough for their purposes at the time.

    More relevant to this discussion, if by "correct" code you mean code
    that has fully defined or appropriate implementation-defined behaviour
    (most code does not need to be fully portable) according to the C
    standards and/or platform or compiler extended semantics, then it is absolutely the case that programmers regularly prioritise performance
    over correctness by relying on UB. The result is code that works
    efficiently and as intended at the time, using tools tested by the
    developer. Then the UB bites the next person down the road that uses
    the same code in good faith, but with a different compiler or different options.

    If the reliance on the UB was unintentional, it's a bug - programmers
    are humans, they don't always know everything, and people make mistakes.
    But some programmers do so /knowing/ that the code is UB. Sometimes
    it is even stated in comments. The most common cases, IME, are reliance
    on two's complement wrapping, and messing with pointer casts to access
    data as different types, possibly with incorrect alignment.


    As for the linked paper, I am not particularly impressed by its arguments.

    I think it is fair enough to have different opinions about what should
    or should not be UB in language standards - and it is inarguably the
    case that some things that are UB could have defined behaviour at quite
    minor performance costs. (Note, however, that studies looking at the performance cost of disabling certain optimisations - such as "-fno-strict-aliasing" or "-fwrapv" - almost invariably use only x86
    targets. x86 processors are designed to get the best from poorly
    optimised code, because that's what is often run on them, while most
    other processors except compilers to do more of the work. Compiler optimisations often have a much bigger impact on non-x86 targets and on processors with smaller relative memory latencies.)

    Fundamentally, the idea of "UB" is that it describes code that does not
    make sense, where there is no correct answer or meaningful specification
    of behaviour. What should it mean to dereference a pointer that does
    not point to a valid object of the type you are using? What should it
    mean to store a large result in a target type that is too small? Code
    that does this kind of thing is buggy - it cannot give correct answers. Nothing the compiler can do could make the code correct.

    The compiler's logic then goes like this:

    1. UB is incorrect code.
    2. C is a "trust the programmer" language.
    3. The compiler assumes the programmer has written correct code.
    4. Therefore, the programmer has not written code with UB.
    5. Therefore, UB cannot happen.
    6. Therefore the compiler can optimise on that assumption, such as by
    skipping some code generation.

    The only question is, is it a good idea for the compiler to be allowed
    to make the program "more wrong" ? I don't think it matters as much as
    people often think. It's easy to find or create examples where compiler optimisations on UB can amplify errors. Equally, however, you can find
    or create examples where assuming some kind of na‹ve "obvious"
    implementation of the UB code will also lead to bad results. Programs
    adding two positive integers are going to expect a positive result - a
    wrapped negative result is going to lead to tears. You need the same
    kind of care in development and testing no matter how the compiler
    treats its UB - and if you have done that job, these kinds of compiler optimisations do not significantly (IMHO) affect the risk of later problems.

    The killer point, however, is when you have received some code that you
    can justifiably assume is correct and well tested, but contains reliance
    on how certain types of UB happen to be implemented. It is not really
    any different than code that makes other undocumented assumptions, such
    as implementation-dependent details, reliance on certain files or OS
    features, timing requirements, etc. And compiling with "-fwrapv -fno-strict-aliasing" (if you are using a compiler that supports such features) will mitigate the most common cases of reliance on UB.


    As for the paper itself, I have a few points.

    It follows the time-worn path of complaining that "modern" compilers
    have distorted the "original" idea of UB and abuse it for optimisation
    in ways that are dangerous to the uninitiated. First, optimisation
    using UB is not new - I first used a compiler that assumed integer
    overflow does not wrap over thirty years ago. Second, compilers already default to "-O0" - if you know so little about your tools that you can't enable warnings, you won't have optimisation either. Third,
    improvements in compiler optimisation have gone hand-in-hand with
    improvements in compiler static error checking. Using "-Wall" along
    with optimisation will eliminate the solid majority of the risks and
    worries of the author. Fourth, the development of "sanitizers" not only allows far more UB to be found during testing, but it allows far more
    bugs to be found than would be possible if the mistakes were /not/ UB - "-fsanitize=signed-integer-overflow" catches overflow bugs precisely
    because it is UB in C, whereas if it were defines as wrapping then the
    mistake in the programmer's code would be correct as far as the language
    is concerned.

    Compilers are, of course, not perfect. But they are usually pretty
    good. Most of the paper could be scraped and replaced with the advice
    of always using "-Wall", possibly with "-Wextra" and/or fine-tuning of
    other warning flags, regardless of optimisation. And make use of
    sanitizers while testing. And always test with solid optimisations
    enabled (like "-O2"), though lower optimisations can be handy for fault-finding and debugging some aspects of code.

    It repeats the myths about signed integer overflow being UB because some computers have different representations of signed integers - C uses implementation-defined behaviour to handle implementation differences,
    and only resorts to UB when there is no sensible definition of "correct" behaviour, or where implementation-defined behaviour could have
    significant overhead costs.

    Both C++ (from C++20, IIRC) and C (from C23) allow only two's complement signed integer representations. Both committees made it clear that
    signed arithmetic overflow is still UB, because it does not lead to a
    sensible answer and allows optimisations and additional error checking.
    It is not hard to get wrapping behaviour if you want it (using
    conversions to and from unsigned types), and C23 introduced standardised checked arithmetic functions.


    As for infinite loops, loops with a constant controlling expression
    (like "for (;;)" ) are exempt from the "assumed to terminate" clause in
    C. I don't know if the example given was a bug in Clang or if it used
    C++ and C++ has different rules here.

    And in the next example, merging two loops into one, the merge won't
    introduce new bugs or race conditions - if another thread is using
    "count2", then there needs to be some kind of coordination (like
    atomics), and then the loops could not be merged anyway. I am not
    convinced that allowing the compiler to assume termination of certain
    types of loop is a particularly useful feature - it seems to me to be an
    added complication with very few potential benefits. But I can't see it
    being a problem for sensible working code either.

    Don't dereference null pointers. It's a bad idea.

    Read the specifications of the functions you want to use. std::sort
    requires a comparison function with a strict weak ordering - give it
    something else, and it's a user bug. Perhaps std::sort could have been specified in a nicer way, but that is hardly a compiler problem.










    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Dan Cross@3:633/10 to All on Wed Jul 22 17:37:01 2026
    In article <113pd7i$2hns9$1@kst.eternal-september.org>,
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >cross@spitfire.i.gajendra.net (Dan Cross) writes:
    [...]
    ```
    term% cat eraseall.c
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    Do();
    return 0;
    }

    term% clang -O1 -Wall -Wextra -pedantic -std=c23 -o eraseall eraseall.c
    term% ./eraseall
    baz invoked.
    term%
    ```

    In that program, Do is a function pointer that's initialized to a null >pointer value (because it's static), so the call `Do()` has undefined >behavior.

    Yup. UB gives the compiler permission to go wild.

    Something similar came up a month or so ago; recall my "what.c"
    program:

    ```
    term% cat what.c
    #include <stdio.h>
    int main(void) { for (unsigned int k = 0; k != 1; k += 2){} return 0; }
    void hello(void) { printf("Hello, World!\n"); }
    term% clang -O1 -Wall -Werror -pedantic -pedantic-errors -std=c23 -o what what.c
    term% ./what
    Hello, World!
    term%
    ```

    That's an example of something more specific. N3220 6.8.6.1p4
    gives explicit permission for an implementation to "assume" that
    certain iteration statements will terminate. (I've said before
    that I dislike this rule for several reasons.)

    Yup. This behavior indeed allowed by the standard, but that it
    is allowed by the standard is, itself, an indictment of the
    standard.

    It's notable that the quoted text for the ANSI C standard also
    says that an implementation is free to abort translation if it
    encounters UB, which I recall was something that was a point of
    concention in the earlier discussion. Kuyper posted a link to
    a defect report that suggested that the compiler had to
    translate the program if it could prove that (in that case,
    manifest) UB was never triggered; however, that was from 1994,
    and not normative anyhow.

    C89 and all later editions of the C standard say (in a non-normative
    note) that undefined behavior can include "terminating a
    translation or execution (with the issuance of a diagnostic
    message)". But almost all undefined behavior occurs at run time.
    That could have been worded more clearly, but my interpretation
    is that an implementation that detects that a source construct
    has undefined behavior can terminate translation only if it can
    prove that it's always executed. `if (0) 1/0;` does not permit
    terminating translation.

    Unfortunately, the standard does not say that in normative text.

    The committee has had ample time to
    tune up the writing about this in the standard, and has chosen
    not to: Cox's post is on-point; the current standard is deeply
    flawed.

    How would you suggest fixing it?

    Honestly? I don't think that it can be fixed. There are too
    many competing interests, and C is the language that it is. I
    do not think we're going to get away from that.

    That said, a thing that they _could_ do is be more explicit
    about when termination is allowed and so forth. The UB working
    group is trying to reign in the undue dominance of UB, and I
    give them credit for that; I don't know how far they will get,
    though.

    In the meanwhile, _most_ programmers should not be writing in C.

    - Dan C.


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Jul 22 13:26:45 2026
    On 7/21/2026 6:50 PM, Dan Cross wrote:
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    Do();
    return 0;
    }

    That should be a seg fault? Anyway:

    _______________
    #include <stdio.h>
    #include <stdlib.h>

    typedef void (*Function)(void);

    static Function Do = NULL;

    static void
    baz(void)
    {
    printf("baz invoked.\n");
    }

    static void
    bar(void)
    {
    baz();
    }

    void
    foo(void)
    {
    Do = bar;
    }

    int
    main(void)
    {
    foo(); // :^)
    Do();
    return 0;
    }
    _______________


    Better? ;^)

    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From BGB@3:633/10 to All on Wed Jul 22 18:08:07 2026
    On 7/22/2026 3:41 AM, David Brown wrote:
    On 22/07/2026 07:33, Janis Papanagnou wrote:
    On 2026-07-22 02:26, Keith Thompson wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
    There was some recent longish thread with discussions addressing
    Undefined Behavior. I just stumbled across a paper from Russ Cox
    on that topic with a provoking title. [...]

    For those who don't follow the link, the full title of the paper is
    "C and C++ Prioritize Performance over Correctness".ÿ (Your
    paraphrase could be interpreted as advice, which I'm sure is not
    how you intended it.)

    It was actually a (deliberate) "marketing trick" to provoke interest
    by reducing the title to an (IMO) yet more aggravating formulation.

    Yes, it was not my intention to suggest performance over correctness.
    (Would anyone?)


    Unfortunately, the answer to your final question is yes - and that is
    one of the reasons UB can be a problem.ÿ If by "correctness" you mean
    "code does what it should", people do sometimes release code that they
    know has bugs, but they know that fixing them would slow down the code - perhaps it is still good enough for their purposes at the time.


    Or, the cliche: "It's not a bug, it's a feature..."


    More relevant to this discussion, if by "correct" code you mean code
    that has fully defined or appropriate implementation-defined behaviour
    (most code does not need to be fully portable) according to the C
    standards and/or platform or compiler extended semantics, then it is absolutely the case that programmers regularly prioritise performance
    over correctness by relying on UB.ÿ The result is code that works efficiently and as intended at the time, using tools tested by the developer.ÿ Then the UB bites the next person down the road that uses
    the same code in good faith, but with a different compiler or different options.



    Well, or for example, this pile of wonk:

    #define gfxedit_getu16(ptr) (*(vol_u16p)(ptr))
    #define gfxedit_getu32(ptr) (*(vol_u32p)(ptr))
    #define gfxedit_getu64(ptr) (*(vol_u64p)(ptr))
    #define gfxedit_setu16(ptr,val) (*(vol_u16p)(ptr)=(val))
    #define gfxedit_setu32(ptr,val) (*(vol_u32p)(ptr)=(val))
    #define gfxedit_setu64(ptr,val) (*(vol_u64p)(ptr)=(val))

    Insert other versions for different platform constraints.

    But, say:
    u64 gfxedit_getu64(void *ptr)
    { u64 v; memcpy(&v, ptr, 8); return(v); }
    Being also possible, but a significant performance penalty on some targets.

    The situation is potentially much worse for big endian machines, but
    these are rare at this point.

    Say:
    u16 gfxedit_getu16(void *ptr)
    { u16 v; v=((byte *)ptr)[0]|
    (((u16)((byte *)ptr)[1]))<<8); return(v); }
    u32 gfxedit_getu32(void *ptr)
    { u32 v; v=gfxedit_getu16(ptr)|
    (((u32)gfxedit_getu16(((byte *)ptr)+2))<<16); return(v); }
    u64 gfxedit_getu64(void *ptr)
    { u64 v; v=gfxedit_getu32(ptr)|
    (((u64)gfxedit_getu32(((byte *)ptr)+4))<<32); return(v); }

    Though, for BE machines, one wouldn't want to overuse these.

    ...


    And, the function:

    byte *gfxedit_memlzcpyf(byte *dst, byte *src, int len)
    {
    byte *cs, *ct, *cte;
    u64 v0, v1;
    int d;
    int i;

    d=dst-src;

    if(d>len)
    {
    cs=src; ct=dst; cte=dst+len;
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    if(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    if(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    while(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    }
    }
    }
    return(cte);
    }
    if(d<=8)
    {
    if(d==1)
    {
    v0=*(byte *)src; v0|=(v0<<8); v0|=(v0<<16); v0|=(v0<<32);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v0); ct+=16; }
    return(cte);
    }
    if(d==2)
    {
    v0=gfxedit_getu16(src); v0|=(v0<<16); v0|=(v0<<32);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v0); ct+=16; }
    return(cte);
    }
    if(d==4)
    {
    v0=gfxedit_getu32(src); v0|=(v0<<32);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v0); ct+=16; }
    return(cte);
    }
    if(d==8)
    {
    v0=gfxedit_getu64(src);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v0); ct+=16; }
    return(cte);
    }

    if(d<0)
    {
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    }
    return(cte);
    }

    if(d==3)
    {
    v0=gfxedit_getu32(src); v0=v0<<40; v0=(v0>>16)|(v0>>40);
    ct=dst; cte=dst+len;
    while(ct<cte)
    {
    gfxedit_setu64(ct+ 0, v0);
    gfxedit_setu64(ct+ 6, v0);
    gfxedit_setu64(ct+12, v0);
    ct+=18;
    }
    return(cte);
    }
    if(d==5)
    {
    v0=gfxedit_getu64(src);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); ct+=5; }
    return(cte);
    }
    if(d==7)
    {
    v0=gfxedit_getu64(src);
    ct=dst; cte=dst+len;
    while(ct<cte)
    { gfxedit_setu64(ct, v0); ct+=7; }
    return(cte);
    }
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    { *ct++=*cs++; }
    return(cte);
    }
    if(d>=16)
    {
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    {
    v0=gfxedit_getu64(cs); v1=gfxedit_getu64(cs+8);
    gfxedit_setu64(ct, v0); gfxedit_setu64(ct+8, v1);
    cs+=16; ct+=16;
    }
    return(cte);
    }
    else
    {
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    { v0=gfxedit_getu64(cs); gfxedit_setu64(ct, v0); cs+=8; ct+=8; }
    return(cte);
    }
    }


    Where one might ask, why not just:
    byte *gfxedit_memlzcpyf(byte *dst, byte *src, int len)
    {
    int i, d;
    d=dst-src;
    if(d>len)
    { memcpy(dst, src, len); return(dst+len); }
    if(d<0)
    { memmove(dst, src, len); return(dst+len); }
    if(d==1)
    { memset(dst, *src, len); return(dst+len); }
    for(i=0; i<len; i++)
    dst[i]=src[i];
    return(dst+len);
    }

    But, on the relevant implementation, trying to do the latter being
    around an order of magnitude slower...

    Note: Not on my target... Where I had added _memlzcpyf() as an extension
    to the C library to avoid having to endlessly re-implement this stuff.
    But, for other targets, it is still necessary.

    Where, the 'f' variant is basically the variant that prioritizes speed
    on the allowance that it may copy a little extra.


    Well, except on SiFive chips where this in not the fastest option.

    The fastest case for a SiFive style CPU being or similar, say:
    byte *gfxedit_memlzcpyf(byte *dst, byte *src, int len)
    {
    byte *cs, *ct, *cte;
    int i, d;
    cs=src; ct=dst; cte=dst+len;
    while(ct<cte)
    { *ct++=*cs++; }
    return(cte);
    }

    But, for the original example, this works better on a CPU with higher instruction latency, but the ability to use misaligned loads and stores
    with zero added cost (whereas, the SiFive chips have lower
    per-instruction latency but a very steep cost for misaligned memory
    access; basically in the form of hidden trap-and-emulate).

    Where, say, both CPU's can run RISc-V code, but have significant
    differences in performance characteristics...

    One might also want to use the latter naive byte copy for big endian
    RISC machines (like on PowerPC or similar), ...

    So, it can all turn into a big mess of cruft and ifdef's.


    And, No:
    You can't just use memmove() here...

    In this case the typical repeating bytes on self-overlap behavior being required for correct operation.


    But, while small and elegant and portable, the above version would be undesirable due to being around 10x slower.

    One could try to detect alignment and then have separate aligned vs
    unaligned loops, but in the use-case it gains very little and may end up slower than always assuming misaligned (where it is a sort of chaos
    where most of the copies are both misaligned and fairly short).



    If the reliance on the UB was unintentional, it's a bug - programmers
    are humans, they don't always know everything, and people make mistakes.
    ÿBut some programmers do so /knowing/ that the code is UB.ÿ Sometimes
    it is even stated in comments.ÿ The most common cases, IME, are reliance
    on two's complement wrapping, and messing with pointer casts to access
    data as different types, possibly with incorrect alignment.


    As for the linked paper, I am not particularly impressed by its arguments.

    I think it is fair enough to have different opinions about what should
    or should not be UB in language standards - and it is inarguably the
    case that some things that are UB could have defined behaviour at quite minor performance costs.ÿ (Note, however, that studies looking at the performance cost of disabling certain optimisations - such as "-fno- strict-aliasing" or "-fwrapv" - almost invariably use only x86 targets.
    x86 processors are designed to get the best from poorly optimised code, because that's what is often run on them, while most other processors
    except compilers to do more of the work.ÿ Compiler optimisations often
    have a much bigger impact on non-x86 targets and on processors with
    smaller relative memory latencies.)


    The idea is that compilers should still try to produce efficient code
    even with these semantic limitations.


    Fundamentally, the idea of "UB" is that it describes code that does not
    make sense, where there is no correct answer or meaningful specification
    of behaviour.ÿ What should it mean to dereference a pointer that does
    not point to a valid object of the type you are using?ÿ What should it
    mean to store a large result in a target type that is too small?ÿ Code
    that does this kind of thing is buggy - it cannot give correct answers. Nothing the compiler can do could make the code correct.

    The compiler's logic then goes like this:

    1. UB is incorrect code.
    2. C is a "trust the programmer" language.
    3. The compiler assumes the programmer has written correct code.
    4. Therefore, the programmer has not written code with UB.
    5. Therefore, UB cannot happen.
    6. Therefore the compiler can optimise on that assumption, such as by skipping some code generation.

    The only question is, is it a good idea for the compiler to be allowed
    to make the program "more wrong" ?ÿ I don't think it matters as much as people often think.ÿ It's easy to find or create examples where compiler optimisations on UB can amplify errors.ÿ Equally, however, you can find
    or create examples where assuming some kind of na‹ve "obvious" implementation of the UB code will also lead to bad results.ÿ Programs adding two positive integers are going to expect a positive result - a wrapped negative result is going to lead to tears.ÿ You need the same
    kind of care in development and testing no matter how the compiler
    treats its UB - and if you have done that job, these kinds of compiler optimisations do not significantly (IMHO) affect the risk of later
    problems.

    The killer point, however, is when you have received some code that you
    can justifiably assume is correct and well tested, but contains reliance
    on how certain types of UB happen to be implemented.ÿ It is not really
    any different than code that makes other undocumented assumptions, such
    as implementation-dependent details, reliance on certain files or OS features, timing requirements, etc.ÿ And compiling with "-fwrapv -fno- strict-aliasing" (if you are using a compiler that supports such
    features) will mitigate the most common cases of reliance on UB.


    In some cases, it makes sense to assume that some things formally
    considered UB would be better considered as Implementation Defined.

    In practice, it likely wouldn't make that big of a difference; since
    many cases the compiler still treats these cases is implementation
    defined rather than true UB.


    In this case, this leaves "true UB" as mostly cases where no sensible or consistent behavior can be expected even within a given machine, say:
    reliance on the values of uninitialized local variables;
    going out of bounds on a conventional array (*1).

    *1: I consider out-of-bounds within an array inside a struct to be a
    partial exception, as one can reasonably assume that going OOB would
    access other members within the same struct.

    But, the relative ordering and layout of stack and global variables I considered to be outside the scope of where valid assumptions can be
    made in C.


    However, such assumptions could be made for assembler...

    Granted, in premise one could do, say:
    extern u64 arr1[4];
    extern u64 arr2[4];

    __asm {
    .section .data
    .global arr1
    .global arr2
    arr1:
    .quad 0
    .quad 0
    .quad 0
    .quad 0
    arr2:
    .quad 0
    .quad 0
    .quad 0
    .quad 0
    };

    And, arguably in this case one can argue that it may be valid to assume
    that arr1 overflows into arr2.

    But, then again, one also can't rely on the portability of __asm or
    __asm__ blocks between targets. So, like with the structs, this would
    exist more as a partial exception.

    This example being partly N/A for GCC or similar, which uses a different syntax for ASM blocks (well, and some compilers, like 64-bit MSVC,
    having dropped support for inline ASM).


    But, I had seen non-zero code which had relied on the assumption of
    overflows carrying over consistently between global arrays.

    In my compiler, such assumptions are not safe to be made, as the
    compiler likes to shuffle the declarations around. Partly balancing randomization and efficiency; as it can be more efficient to arrange
    variables by usage frequency, and functions by a combination of
    frequency and proximity. Say, ideally keeping commonly called functions
    closer together, and if possible also clustering callers and callees
    closer together. Even in the absence of profiler feedback, it tends to
    make it such that the "cold path" is less likely to be part of the core working set (and reducing the TLB miss frequency).


    Did run into a little bit of a hassle recently that my compilers' output
    tends to have a section alignment smaller than the page alignment, which turned into a bit of pain trying to set up page access permissions for
    the kernel binary (moving away from just setting the whole thing as RWX).

    Ended up adding a few special case flags to the implementation of the "mprotect" functionality:
    INNER: Only applies to completely covered pages,
    ignores partial pages.
    Default: covers all pages in range.
    ALLOW: Only allows new access, does not remove access.
    DENY: Only removes access.
    Default: New permissions fully replace old permissions.

    These allowed dealing better with the partial page scenarios.
    Can initially set the whole image READ|NOUSER;
    Then use ALLOW to add WRITE/EXEC/USER to the relevant sections.
    Maybe questionable, but there does exist a R|X|USER section.
    Mostly has stuff for Syscalls and COM.
    It is likely this part could be migrated into Userland.
    Mostly it is a vestige of an earlier developmental stage.



    As for the paper itself, I have a few points.

    It follows the time-worn path of complaining that "modern" compilers
    have distorted the "original" idea of UB and abuse it for optimisation
    in ways that are dangerous to the uninitiated.ÿ First, optimisation
    using UB is not new - I first used a compiler that assumed integer
    overflow does not wrap over thirty years ago.ÿ Second, compilers already default to "-O0" - if you know so little about your tools that you can't enable warnings, you won't have optimisation either.ÿ Third,
    improvements in compiler optimisation have gone hand-in-hand with improvements in compiler static error checking.ÿ Using "-Wall" along
    with optimisation will eliminate the solid majority of the risks and
    worries of the author.ÿ Fourth, the development of "sanitizers" not only allows far more UB to be found during testing, but it allows far more
    bugs to be found than would be possible if the mistakes were /not/ UB - "-fsanitize=signed-integer-overflow" catches overflow bugs precisely
    because it is UB in C, whereas if it were defines as wrapping then the mistake in the programmer's code would be correct as far as the language
    is concerned.


    For keeping old code working, it makes sense to assume wrapping.

    But, maybe it could make sense to have a semantic distinction for cases
    where wrapping is assumed vs where it is likely unintentional.

    My proposal, if any, would be, say:
    If "signed" is used explicitly, it means wrap-on-overflow is expected.

    So, say:
    int i;
    signed int v;
    If 'i' overflows, one can question whether or not it was intended; but
    if 'v' overflows, assume it is intentional (as with 'unsigned').

    This being in part because an explicit "signed" keyword is uncommon to
    be used with normal counting/index type variables.



    Compilers are, of course, not perfect.ÿ But they are usually pretty
    good.ÿ Most of the paper could be scraped and replaced with the advice
    of always using "-Wall", possibly with "-Wextra" and/or fine-tuning of
    other warning flags, regardless of optimisation.ÿ And make use of
    sanitizers while testing.ÿ And always test with solid optimisations
    enabled (like "-O2"), though lower optimisations can be handy for fault- finding and debugging some aspects of code.

    It repeats the myths about signed integer overflow being UB because some computers have different representations of signed integers - C uses implementation-defined behaviour to handle implementation differences,
    and only resorts to UB when there is no sensible definition of "correct" behaviour, or where implementation-defined behaviour could have
    significant overhead costs.

    Both C++ (from C++20, IIRC) and C (from C23) allow only two's complement signed integer representations.ÿ Both committees made it clear that
    signed arithmetic overflow is still UB, because it does not lead to a sensible answer and allows optimisations and additional error checking.
    It is not hard to get wrapping behaviour if you want it (using
    conversions to and from unsigned types), and C23 introduced standardised checked arithmetic functions.


    As for infinite loops, loops with a constant controlling expression
    (like "for (;;)" ) are exempt from the "assumed to terminate" clause in
    C.ÿ I don't know if the example given was a bug in Clang or if it used
    C++ and C++ has different rules here.

    And in the next example, merging two loops into one, the merge won't introduce new bugs or race conditions - if another thread is using
    "count2", then there needs to be some kind of coordination (like
    atomics), and then the loops could not be merged anyway.ÿ I am not
    convinced that allowing the compiler to assume termination of certain
    types of loop is a particularly useful feature - it seems to me to be an added complication with very few potential benefits.ÿ But I can't see it being a problem for sensible working code either.

    Don't dereference null pointers.ÿ It's a bad idea.


    Dereferencing NULL is one of those cases where the most sensible option
    is to treat it as a fault...

    Most sensible option is to assume that trying to dereference NULL is unintentional (given the main purpose of NULL being as a "this object
    doesn't exist" pointer).

    But:
    Assuming it doesn't happen and then removing the offending code path
    entirely is also bad IMO. However, turning the code-path into a break instruction or similar (following any other externally visible side
    effects) would still likely be acceptable IMO (or, at least, less bad in
    this case than just continuing down the other path as if nothing had
    ever happened).



    Read the specifications of the functions you want to use.ÿ std::sort requires a comparison function with a strict weak ordering - give it something else, and it's a user bug.ÿ Perhaps std::sort could have been specified in a nicer way, but that is hardly a compiler problem.


    --- PyGate Linux v1.5.18
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)