• Re: Thought experiment

    From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Tue Jun 23 10:34:44 2026
    From Newsgroup: comp.arch

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    BGB <cr88192@gmail.com> posted:

    On 6/20/2026 5:01 PM, MitchAlsup wrote:
    ---------------
    Tagging to make it harder to stomp the link register;

    Put it somewhere it can't be stomped on !! like in memory on a page the
    application has no access permissions.


    Multiple stacks is a big ask, and non-accessible memory is not so good
    when dealing with an ISA where user code needs to handle the Link-Register.

    Code does not need to access or look at the return address in My 66000 ISA--except for the case where one wants to walk the stack back on a
    THROW() and its unstructured equivalent longjump().

    What about a debugging stack trace?
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Niklas Holsti@niklas.holsti@tidorum.invalid to comp.arch on Tue Jun 23 16:37:58 2026
    From Newsgroup: comp.arch

    On 2026-06-22 19:50, Stephen Fuld wrote:
    On 6/22/2026 9:26 AM, Niklas Holsti wrote:
    On 2026-06-22 17:59, Stephen Fuld wrote:
    On 6/22/2026 3:44 AM, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:

    [snip]

    There is a discussion going on at the moment about "pointer
    providence"

    Perhaps you meant pointer "provenance"? I hope we will not rely on the >>>>> "careful governance and guidance of God", or on an "instance of divine >>>>> intervention" to ensure pointer safety...

    Has pointer safety been shown to be equivalent to the halting
    problem?-a If so, "careful governance and guidance from God" may
    indeed be required.

    I don't know the answer to your question, but presumably we can do
    better than C does.-a Isn't that one of the, at least claimed,
    advantages of Rust, and perhaps even Ada?

    Both Rust and Ada have to be restricted in certain ways in order to
    ensure absence of pointer errors: Rust has to avoid "unsafe" code,

    I like Rust's solution.-a You can do unsafe things - sometimes they are
    just necessary - but they are not the default was of doing things, and
    you have to notate them in the source code which serves to discourage
    them and points people debugging errors to certain areas of the code
    that are more likley to be problematic.

    Same in Ada, mostly: some unsafe things are named "Unchecked_Xxx",
    others are available only if some specific predefined packages are used,
    which are not needed for most safe things.

    and Ada has to avoid pointer-related "unchecked" constructs and
    certain undefined behavior (which does exist in Ada, but less so than
    in C). The Ada subset called SPARK, together with its proof tools, is
    meant for such programming, and has a feature similar to Rust
    "ownership" though standard Ada does not.

    Is programming under SPARK rules significantly harder than under
    nonSPARK Ada?

    I don't have personal experience, but my impression is that it does not
    make it markedly harder than the usual restrictions on embedded,
    more-or-less critical software do. SPARK is defined and supported by the AdaCore company, not a standards group, and is evolving. The
    documentation is at https://www.adacore.com/documentation?tab=spark; the
    main restrictions are (quoted from https://docs.adacore.com/live/wave/spark2014/html/spark2014_rm/introduction.html#principal-language-restrictions,
    with my comments in []):

    --- quote:

    To facilitate formal analyses and verification, SPARK enforces a number
    of global restrictions to Ada. While these are covered in more detail in
    the remaining chapters of this document, the most notable restrictions are:

    - Restrictions on the use of access types and values [pointers], similar
    in some ways to the ownership model of the programming language Rust.

    - All expressions (including function calls) are free of side effects.

    - Aliasing of names is not permitted in general but the renaming of
    entities is permitted as there is a static relationship between the two
    names. In analysis all names introduced by a renaming declaration are
    replaced by the name of the renamed entity. This replacement is applied recursively when there are multiple renames of an entity.

    - Backward goto statements are not permitted.

    - The use of controlled types is not currently permitted. [These are
    types with automatic invocation of user-defined initialization and finalization operations on object creation, copying, and deletion.]

    - Tasks and protected objects are permitted only if the Ravenscar
    profile (or the Jorvik profile) is specified. [The main limitation in
    these profiles is that the set of tasks (threads) is static, no task
    ever terminates, and inter-task communication is by protected objects (monitors, synchronized objects) and not by rendez-vous.]

    - Raising and handling of exceptions is not currently permitted
    (exceptions can be included in a program but proof must be used to show
    that they cannot be raised).

    --- end quote.

    -aAlso, I believe that had the originators of C not allowed arithmetic
    on pointers (comparisons for equality would still be allowed, and
    array addressing would have to use subscripts) many of the problems
    with C pointers wouldn't have occurred.-a Of course, that horse has
    left the barn a long time ago.

    I recently helped to debug an Ada program that now and then, but not
    often, was overwriting some buffers. At one point in that program I
    had *cough* used pointer arithmetic *blush* instead of array indexing,
    for what I felt were good reasons at the time. But it bit me. An
    amusing clue to the error was that the bug happened more often when
    the satellite running the program was above Russia's borders. Perhaps
    you can guess reasons for that :-)

    Interesting.-a Perhaps it is because Russia has less "careful governance
    and guidance from God" :-)
    One could indeed say so, because the reason is Putin's attack on
    Ukraine, as you may have guessed.

    The Ada program runs a satellite-based GNSS receiver that acquires
    (finds) and then tracks GNSS signals from GNSS satellites (GPS, Galileo,
    and others) as those satellites rise or set. The purpose is to measure atmospheric properties from the way the atmosphere refracts the signal.

    The design and/or coding error was in the transition between two stages
    of the multi-stage procedure for finding and starting to track a GNSS
    signal from a GNSS satellite.

    So then: Russia attacks Ukraine => Ukraine defends itself with
    long-distance drones => Russia jams and perturbs GNSS signals along its borders => the satellite software often loses track of a signal it is
    tracking => the satellite software often has to re-acquire signals =>
    the bug manifests more often over Russia's borders.

    If one favours the Ukrainian Orthodox church, which objects to this war, Russia is going against God's guidance. If one favours the Russian
    Orthodox church, which blesses this war, Russia is following God's guidance.

    (The bug was not found in testing because it did not manifest on every transition between the two acquisition stages -- it manifested only when
    two other dynamic program states occurred together, at the same time as
    the transition, and one of these states is rather rare, at least in test conditions.)

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.arch on Tue Jun 23 14:32:22 2026
    From Newsgroup: comp.arch

    BGB <cr88192@gmail.com> writes:
    On 6/21/2026 2:56 PM, Robert Swindells wrote:
    On Sun, 21 Jun 2026 13:55:59 -0500, BGB wrote:

    Though, I guess one merit of a Lisp like language is that it is a lot
    easier to parse, and it could be possible to implement a fairly cheap
    compiler for it (in the basic case).

    Usual downside it that the excessive parenthesis tend to turn into a
    usability issue.

    You use an editor that keeps track of them.


    Probably.
    The main editor I use on Windows, Notepad2, has syntax highlighting and >parenthesis matching.

    Normal Notepad does not.

    Though, would seem that these features have become fairly common in >text-editors in Linux land.

    THat feature has been common in Unix and linux land for close to three decades.

    One might even note that color syntax highlighting predates
    Windows completely in one form or another (e.g 1969 Emily editor).



    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Stephen Fuld@sfuld@alumni.cmu.edu.invalid to comp.arch on Tue Jun 23 07:37:12 2026
    From Newsgroup: comp.arch

    On 6/23/2026 6:37 AM, Niklas Holsti wrote:
    On 2026-06-22 19:50, Stephen Fuld wrote:
    On 6/22/2026 9:26 AM, Niklas Holsti wrote:
    On 2026-06-22 17:59, Stephen Fuld wrote:
    On 6/22/2026 3:44 AM, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:

    -a-a [snip]

    There is a discussion going on at the moment about "pointer
    providence"

    Perhaps you meant pointer "provenance"? I hope we will not rely on >>>>>> the
    "careful governance and guidance of God", or on an "instance of
    divine
    intervention" to ensure pointer safety...

    Has pointer safety been shown to be equivalent to the halting
    problem?-a If so, "careful governance and guidance from God" may
    indeed be required.

    I don't know the answer to your question, but presumably we can do
    better than C does.-a Isn't that one of the, at least claimed,
    advantages of Rust, and perhaps even Ada?

    Both Rust and Ada have to be restricted in certain ways in order to
    ensure absence of pointer errors: Rust has to avoid "unsafe" code,

    I like Rust's solution.-a You can do unsafe things - sometimes they are
    just necessary - but they are not the default was of doing things, and
    you have to notate them in the source code which serves to discourage
    them and points people debugging errors to certain areas of the code
    that are more likley to be problematic.

    Same in Ada, mostly: some unsafe things are named "Unchecked_Xxx",
    others are available only if some specific predefined packages are used, which are not needed for most safe things.

    and Ada has to avoid pointer-related "unchecked" constructs and
    certain undefined behavior (which does exist in Ada, but less so than
    in C). The Ada subset called SPARK, together with its proof tools, is
    meant for such programming, and has a feature similar to Rust
    "ownership" though standard Ada does not.

    Is programming under SPARK rules significantly harder than under
    nonSPARK Ada?

    I don't have personal experience, but my impression is that it does not
    make it markedly harder than the usual restrictions on embedded, more- or-less critical software do. SPARK is defined and supported by the
    AdaCore company, not a standards group, and is evolving. The
    documentation is at https://www.adacore.com/documentation?tab=spark; the main restrictions are (quoted from https://docs.adacore.com/live/wave/ spark2014/html/spark2014_rm/introduction.html#principal-language- restrictions, with my comments in []):

    --- quote:

    To facilitate formal analyses and verification, SPARK enforces a number
    of global restrictions to Ada. While these are covered in more detail in
    the remaining chapters of this document, the most notable restrictions are:

    - Restrictions on the use of access types and values [pointers], similar
    in some ways to the ownership model of the programming language Rust.

    - All expressions (including function calls) are free of side effects.

    - Aliasing of names is not permitted in general but the renaming of
    entities is permitted as there is a static relationship between the two names. In analysis all names introduced by a renaming declaration are replaced by the name of the renamed entity. This replacement is applied recursively when there are multiple renames of an entity.

    - Backward goto statements are not permitted.

    - The use of controlled types is not currently permitted. [These are
    types with automatic invocation of user-defined initialization and finalization operations on object creation, copying, and deletion.]

    - Tasks and protected objects are permitted only if the Ravenscar
    profile (or the Jorvik profile) is specified. [The main limitation in
    these profiles is that the set of tasks (threads) is static, no task
    ever terminates, and inter-task communication is by protected objects (monitors, synchronized objects) and not by rendez-vous.]

    - Raising and handling of exceptions is not currently permitted
    (exceptions can be included in a program but proof must be used to show
    that they cannot be raised).

    --- end quote.

    -aAlso, I believe that had the originators of C not allowed
    arithmetic on pointers (comparisons for equality would still be
    allowed, and array addressing would have to use subscripts) many of
    the problems with C pointers wouldn't have occurred.-a Of course,
    that horse has left the barn a long time ago.

    I recently helped to debug an Ada program that now and then, but not
    often, was overwriting some buffers. At one point in that program I
    had *cough* used pointer arithmetic *blush* instead of array
    indexing, for what I felt were good reasons at the time. But it bit
    me. An amusing clue to the error was that the bug happened more often
    when the satellite running the program was above Russia's borders.
    Perhaps you can guess reasons for that :-)

    Interesting.-a Perhaps it is because Russia has less "careful
    governance and guidance from God" :-)
    One could indeed say so, because the reason is Putin's attack on
    Ukraine, as you may have guessed.

    The Ada program runs a satellite-based GNSS receiver that acquires
    (finds) and then tracks GNSS signals from GNSS satellites (GPS, Galileo,
    and others) as those satellites rise or set. The purpose is to measure atmospheric properties from the way the atmosphere refracts the signal.

    The design and/or coding error was in the transition between two stages
    of the multi-stage procedure for finding and starting to track a GNSS
    signal from a GNSS satellite.

    So then: Russia attacks Ukraine => Ukraine defends itself with long- distance drones => Russia jams and perturbs GNSS signals along its
    borders => the satellite software often loses track of a signal it is tracking => the satellite software often has to re-acquire signals =>
    the bug manifests more often over Russia's borders.

    If one favours the Ukrainian Orthodox church, which objects to this war, Russia is going against God's guidance. If one favours the Russian
    Orthodox church, which blesses this war, Russia is following God's
    guidance.

    (The bug was not found in testing because it did not manifest on every transition between the two acquisition stages -- it manifested only when
    two other dynamic program states occurred together, at the same time as
    the transition, and one of these states is rather rare, at least in test conditions.)

    For both the above and the discussion about SPARK, Thanks Niklas, quite interesting.
    --
    - Stephen Fuld
    (e-mail address disguised to prevent spam)
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Tue Jun 23 17:33:49 2026
    From Newsgroup: comp.arch


    Thomas Koenig <tkoenig@netcologne.de> posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    BGB <cr88192@gmail.com> posted:

    On 6/20/2026 5:01 PM, MitchAlsup wrote:
    ---------------
    Tagging to make it harder to stomp the link register;

    Put it somewhere it can't be stomped on !! like in memory on a page the >> > application has no access permissions.


    Multiple stacks is a big ask, and non-accessible memory is not so good
    when dealing with an ISA where user code needs to handle the Link-Register.

    Code does not need to access or look at the return address in My 66000 ISA--except for the case where one wants to walk the stack back on a THROW() and its unstructured equivalent longjump().

    What about a debugging stack trace?

    The debugger runs in a separate process with access to application
    Root pointer and ASID. In that process, Call-stack is RW-.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.arch on Tue Jun 23 17:43:38 2026
    From Newsgroup: comp.arch

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:

    Thomas Koenig <tkoenig@netcologne.de> posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    BGB <cr88192@gmail.com> posted:

    On 6/20/2026 5:01 PM, MitchAlsup wrote:
    ---------------
    Tagging to make it harder to stomp the link register;

    Put it somewhere it can't be stomped on !! like in memory on a page the >> >> > application has no access permissions.


    Multiple stacks is a big ask, and non-accessible memory is not so good >> >> when dealing with an ISA where user code needs to handle the Link-Register.

    Code does not need to access or look at the return address in My 66000
    ISA--except for the case where one wants to walk the stack back on a
    THROW() and its unstructured equivalent longjump().

    What about a debugging stack trace?

    The debugger runs in a separate process with access to application
    Root pointer and ASID. In that process, Call-stack is RW-.

    GLIBC has a function to obtain a backtrace at a current point
    in time. This is called in the context of the thread that invokes
    the call. It requires access to the call records on the stack
    in the context of the thread (the glicb functions are backtrace(3)
    and backtrace_symbols(3)).

    /**
    * Log a simulator stack traceback.
    */
    void
    c_osdep::backtrace(c_logger *lp)
    {
    int num_frames;
    void *framelist[100];
    char **strings;

    num_frames = ::backtrace(framelist, sizeof(framelist)/sizeof(framelist[0]));
    strings = ::backtrace_symbols(framelist, num_frames);
    if (strings == NULL) {
    lp->log("Unable to obtain simulator stack traceback: %s\n",
    strerror(errno));
    return;
    }
    for(int frame=0; frame < num_frames; frame++) {
    lp->log("[%2.2d] %s\n", frame, strings[frame]);
    }
    ::free(strings);
    }

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Tue Jun 23 15:15:49 2026
    From Newsgroup: comp.arch

    On 6/23/2026 12:43 PM, Scott Lurndal wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:

    Thomas Koenig <tkoenig@netcologne.de> posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    BGB <cr88192@gmail.com> posted:

    On 6/20/2026 5:01 PM, MitchAlsup wrote:
    ---------------
    Tagging to make it harder to stomp the link register;

    Put it somewhere it can't be stomped on !! like in memory on a page the >>>>>> application has no access permissions.


    Multiple stacks is a big ask, and non-accessible memory is not so good >>>>> when dealing with an ISA where user code needs to handle the Link-Register.

    Code does not need to access or look at the return address in My 66000 >>>> ISA--except for the case where one wants to walk the stack back on a
    THROW() and its unstructured equivalent longjump().

    What about a debugging stack trace?

    The debugger runs in a separate process with access to application
    Root pointer and ASID. In that process, Call-stack is RW-.

    GLIBC has a function to obtain a backtrace at a current point
    in time. This is called in the context of the thread that invokes
    the call. It requires access to the call records on the stack
    in the context of the thread (the glicb functions are backtrace(3)
    and backtrace_symbols(3)).

    /**
    * Log a simulator stack traceback.
    */
    void
    c_osdep::backtrace(c_logger *lp)
    {
    int num_frames;
    void *framelist[100];
    char **strings;

    num_frames = ::backtrace(framelist, sizeof(framelist)/sizeof(framelist[0]));
    strings = ::backtrace_symbols(framelist, num_frames);
    if (strings == NULL) {
    lp->log("Unable to obtain simulator stack traceback: %s\n",
    strerror(errno));
    return;
    }
    for(int frame=0; frame < num_frames; frame++) {
    lp->log("[%2.2d] %s\n", frame, strings[frame]);
    }
    ::free(strings);
    }


    Yeah, for what arguable benefits separate call / data stacks could
    bring, or making the call stack inaccessible to the program, this
    doesn't fit with the vibe of either RISC philosophy, or for sake of
    practical things like implementing C++ style throw/catch, or mechanisms
    like C's longjmp, ...

    One would likely need to defy minimalism by having additional hardware mechanisms to support these kinda things.


    Or, at least more than the damage already done in my case by putting
    mode tag bits and similar in the the link register, which could
    potentially effect code which messes with the link register value
    directly and assumes the link register represents a bare address value.

    ...

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Tue Jun 23 17:48:22 2026
    From Newsgroup: comp.arch

    On 6/22/2026 7:38 AM, Niklas Holsti wrote:
    On 2026-06-22 13:44, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:
    On 21/06/2026 20:57, MitchAlsup wrote:

    anton@mips.complang.tuwien.ac.at (Anton Ertl) posted:

    John Levine <johnl@taugh.com> writes:
    C killed off every memory model other than flat byte addressed
    memory.

    At least in the C standard the memory is segmented into objects.

    Pointers are sort of typed, but any real C program does stuff like >>>>>>> this:

    -a-a p = (struct foo *) malloc(42 * sizeof(struct foo));

    That produces an object of a certain size, and you must only
    access it
    through pointers derived from p.-a And programs usually satisfy that >>>>>> requirement.

    -a-a-a-a-a {
    -a-a-a-a-a-a-a-a-a p = (struct foo *) malloc(42 * sizeof(struct foo)); >>>>> -a-a-a-a-a-a-a-a-a fprintf( stream, "0x16,", p );
    -a-a-a-a-a-a-a-a-a ...
    -a-a-a-a-a-a-a-a-a if( fscanf( stream, "x16", q ) ) {
    -a-a-a-a-a-a-a-a-a-a-a-a-a use q
    -a-a-a-a-a-a-a-a-a }
    -a-a-a-a-a }

    is q "derived" though p ??

    There is a discussion going on at the moment about "pointer providence" >>>
    Perhaps you meant pointer "provenance"? I hope we will not rely on the
    "careful governance and guidance of God", or on an "instance of divine
    intervention" to ensure pointer safety...

    Has pointer safety been shown to be equivalent to the halting
    problem?-a If so, "careful governance and guidance from God" may
    indeed be required.

    I would assume it is undecidable, for unrestricted programs. The aim of pointer provenance is no doubt to restrict programs to make it decidable
    to some extent.


    I didn't really understand it myself.

    In my case, I tended to use more conservative approaches and then only optimize based on what can be verified by the compiler within certain fundamental assumptions.

    Say:
    Pointer 1 points at a stack array in the local function;
    Pointer 2 was derived from taking the address of a global array;
    Compiler can safely assume no-alias.

    Also, if two pointers were passed into a function, can also assume they
    don't alias with a pointer to a local array;
    ...


    Another option is a sort of "selective TBAA":
    Enable TBAA, but only if the current function doesn't contain any
    obvious pointer casts or similar.

    ...


    Then had noted that in my compiler (while working on it to try to reduce memory use), that there was a feature to walk the call-flow graph and
    mark off whichever global variables may be modified and similar as a
    result of calling some function.

    Had sort of forgot this existed, but is sometimes useful to know (can
    keep a global cached in a register if one knows the called function will
    not modify it, otherwise spill/reload is necessary).

    ...


    I am reminded of the person, apparently very religious, who some decades
    ago posted to solicit help for reimplementing all of computing (gcc,
    GNU, et cetera) on Biblical principles, because he thought Richard
    Stallman was too atheistic and had tainted his products. I have not
    heard how that went.

    There were a few people like that...

    There is seemingly a fine line though between being overly religious and
    being insane. A few of the people who I had seen who were like that, had
    been a bit of the latter.



    There is a lot of complexity with things like doctrine and theology,
    etc, but there is a characteristic difference IME.

    Well, and a leaning towards "reality defying" views; more emphasis on supernatural events and experiences, defiance of things like basic
    physics or rules of mathematics; and often pairing the outward
    religiosity with rather unstable or inconsistent adherence to moral or
    ethical behavior (or, applying it only to other people, while giving themselves free reign to indulge in whatever they feel like doing); ...

    Well, and seemingly, the "more genuine" thing being to express restraint
    in ones' own behavior in these areas, not to worry about or try to
    control what anyone else is doing.

    Well, and then there is sorta the cultural expectation that one
    evangelize to others, etc, but this doesn't make as much sense in
    contexts where everyone likely already knows and/or has already made up
    their mind.


    Or, one ends up getting on others' bad sides, say, if one admits that
    they don't personally buy into the "Young Earth Creationist" mindset,
    and feel that (as a society) people have mostly been interpreting
    Genesis incorrectly (and making themselves look stupid in the process,
    by insisting that everyone adopt an overly particular and somewhat
    nonsensical interpretation).

    But, alas, ...




    Though, this doesn't mean that I can claim to always have a 100% stable
    hold on what constitutes "reality" (and my own experiences do include
    things that seem to deviate from normal expectations).

    Though, most in my experience seem to be things like seeming time-flow
    and causality breaks: experiences where normal linear time-flow seems to
    break down; where events happen in ways that seem to break forwards
    causal order; or where sometimes stuff just "changes around" for no
    particular reason.


    Though, I guess I differ by not claiming to have any higher explanation
    for stuff like this...

    Also often more like "bad Sci-Fi tropes" than particularly religious
    though (like one seemingly encounters weirdness that more seems like
    something out of Star Trek or something...).


    Well, like high-level examples:
    Seeming delayed-choice key-ring color instability;
    Choose one color of keyring, it flip flops and changes later.
    Several instances variations of:
    Go to the bathroom, seemingly experience time displacement.
    Like, go into bathroom, may emerge with an unexpected time delta.
    Times where events seemingly tie into "time knots";
    Event sequence becomes paradoxical, causes/effects are reversed;
    Or occasional time-loops (reliving the same events multiple times);
    Unexpected changes appearing in ones' code;
    Like, the code was one way, then it was different.
    Or, in CNC, an event where some M01's turned into M00's somehow.
    Or, one remembers documenting something,
    but then what they wrote is nowhere to be found.
    ...


    Could try to come up with some sort of explanation, but purely
    observational, one might just claim "if one goes to the bathroom or
    similar, they may sometimes somehow initiate temporal anomalies". Well,
    and/or attribute it to neurological factors.

    Though, many of these sorts of events do seem oddly correlated with
    "went to the bathroom, then some weirdness happens...".



    Well, and realizing that some bigger mysteries from earlier in my life
    had more mundane explanations:
    Weird Mac style computer with external magneto-optical drive, etc:
    Apparently was actually a thing at the time...
    I just don't know why anyone would have showed it to me.
    Like, not actually "alien", just absurdly expensive.
    But, then, I question if I really saw it.
    But, why would I remember such a setup if I didn't see it?...
    But, why subject a random 3rd grader to Pascal and MPW?...
    Like, a story with plausible explanations, technically.
    But, the "why" aspect doesn't make sense...
    LaserDisc disappearance in the early 2000s:
    Apparently people mostly just got rid of them...
    The rare purple LaserDisc's:
    Apparently recordable LaserDisc was just a market flop,
    not some weird alien tech.
    A one-off incident in a school A/V setup.
    Like, sometimes they used LD, and not just VCRs.
    ...


    Though, it is still odd sometimes to have maybe encountered weird tech,
    to then have it disappear and never seeing it again (like, where one can question whether their current self is still living in the same timeline
    they existed in during their childhood).


    Not like there is anything particular religious about tech though, and
    if I saw this stuff as an adult would probably have not thought as much
    about it.


    Does sometimes seem like life could have gone differently in some areas,
    I was just sort of an epic fail at everything.

    ...


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Wed Jun 24 00:54:32 2026
    From Newsgroup: comp.arch


    BGB <cr88192@gmail.com> posted:

    On 6/22/2026 7:38 AM, Niklas Holsti wrote:
    On 2026-06-22 13:44, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:
    On 21/06/2026 20:57, MitchAlsup wrote:
    -------------
    In my case, I tended to use more conservative approaches and then only optimize based on what can be verified by the compiler within certain fundamental assumptions.

    Say:
    Pointer 1 points at a stack array in the local function;
    Pointer 2 was derived from taking the address of a global array;
    Compiler can safely assume no-alias.

    Also, if two pointers were passed into a function, can also assume they don't alias with a pointer to a local array;

    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming error.

    -----------------
    I am reminded of the person, apparently very religious, who some decades ago posted to solicit help for reimplementing all of computing (gcc,
    GNU, et cetera) on Biblical principles, because he thought Richard Stallman was too atheistic and had tainted his products. I have not
    heard how that went.

    Rick...

    --------
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Wed Jun 24 00:59:12 2026
    From Newsgroup: comp.arch


    scott@slp53.sl.home (Scott Lurndal) posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:

    Thomas Koenig <tkoenig@netcologne.de> posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    BGB <cr88192@gmail.com> posted:

    On 6/20/2026 5:01 PM, MitchAlsup wrote:
    ---------------
    Tagging to make it harder to stomp the link register;

    Put it somewhere it can't be stomped on !! like in memory on a page the
    application has no access permissions.


    Multiple stacks is a big ask, and non-accessible memory is not so good >> >> when dealing with an ISA where user code needs to handle the Link-Register.

    Code does not need to access or look at the return address in My 66000 >> > ISA--except for the case where one wants to walk the stack back on a
    THROW() and its unstructured equivalent longjump().

    What about a debugging stack trace?

    The debugger runs in a separate process with access to application
    Root pointer and ASID. In that process, Call-stack is RW-.

    GLIBC has a function to obtain a backtrace at a current point
    in time. This is called in the context of the thread that invokes
    the call. It requires access to the call records on the stack
    in the context of the thread (the glicb functions are backtrace(3)
    and backtrace_symbols(3)).

    When Thread is unExceptional it cannot access Call Stack,
    when Thread is Exceptional it can.

    ENTER, EXIT, and RET are exempt from the protection check.
    Call Stack Pointer is not accessible to unprivileged code.

    Don't see how one gets from a running application into debugger without
    taking an exception !?! or from running in the debugger to running in application without returning from an exception !!!
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Tue Jun 23 21:01:35 2026
    From Newsgroup: comp.arch

    On 6/23/2026 7:54 PM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/22/2026 7:38 AM, Niklas Holsti wrote:
    On 2026-06-22 13:44, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:
    On 21/06/2026 20:57, MitchAlsup wrote:
    -------------
    In my case, I tended to use more conservative approaches and then only
    optimize based on what can be verified by the compiler within certain
    fundamental assumptions.

    Say:
    Pointer 1 points at a stack array in the local function;
    Pointer 2 was derived from taking the address of a global array;
    Compiler can safely assume no-alias.

    Also, if two pointers were passed into a function, can also assume they
    don't alias with a pointer to a local array;

    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming error.


    Hard proof that alias is impossible is harder to achieve in practice...

    A softer "there is no reasonable possibility of alias" is easier to achieve.

    Like, one can assume that each independent memory object exists in its
    own local void, and that there is no reasonable way to reach from one to another.


    Like, even if you can potentially go out of bounds to reach from one independent memory object to another, for a compiler it may be
    sufficient merely to prove that the origins reflect two independent
    memory objects (and not two pointers within the same object, or a
    parent/child relationship).

    Likewise, global variables can be seen as separate objects, along with independent local variables.

    Say:
    int arra[16];
    int arrb[16];
    With arra and arrb being assumed independent, even if in-memory they are
    right next to each other, but excluding arrays within a common struct
    (where the containing struct can be seen as a common origin point).


    Everything passed in can go into an "unknown" category; where unknown
    pointers may be assumed to alias with each other.

    Otherwise, one would need to make assumptions about "every possible
    caller", which is unreasonable (caller behavior can be assumed to fall
    into an open-ended set, even in cases where callee behavior can be
    reasoned about via graph walks).

    Though, could still be done when one assumes that the callers form a
    closed set.

    ...


    -----------------
    I am reminded of the person, apparently very religious, who some decades >>> ago posted to solicit help for reimplementing all of computing (gcc,
    GNU, et cetera) on Biblical principles, because he thought Richard
    Stallman was too atheistic and had tainted his products. I have not
    heard how that went.

    Rick...


    That was one of them...


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From John Levine@johnl@taugh.com to comp.arch on Wed Jun 24 02:25:30 2026
    From Newsgroup: comp.arch

    According to BGB <cr88192@gmail.com>:
    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming error. >>
    Hard proof that alias is impossible is harder to achieve in practice...

    A softer "there is no reasonable possibility of alias" is easier to achieve.

    Sort of. The standard says that the compiler can assume no type punning, so that
    if pointers are of different types, they can't point at the same thing (with an exception for pointers to unions.)

    Even so, C has "restrict" to tell the compiler to assume that pointers never alias, and "volatile" to assume they always do.
    --
    Regards,
    John Levine, johnl@taugh.com, Primary Perpetrator of "The Internet for Dummies",
    Please consider the environment before reading this e-mail. https://jl.ly
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Tue Jun 23 22:41:19 2026
    From Newsgroup: comp.arch

    On 6/23/2026 9:25 PM, John Levine wrote:
    According to BGB <cr88192@gmail.com>:
    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming error. >>>
    Hard proof that alias is impossible is harder to achieve in practice...

    A softer "there is no reasonable possibility of alias" is easier to achieve.

    Sort of. The standard says that the compiler can assume no type punning, so that
    if pointers are of different types, they can't point at the same thing (with an
    exception for pointers to unions.)

    Even so, C has "restrict" to tell the compiler to assume that pointers never alias, and "volatile" to assume they always do.


    Possibly, though traditional type-based aliasing rules run into a
    problem in that pointer casting can break its assumptions, and a lot of
    code doesn't respect these rules (which taken purely at face value, are
    overly limiting).


    One option though is "if enabled, assume the rules are followed unless
    the compiler sees them being broken", in which case it disables TBAA
    when faced with TBAA violations. This approach seems to be moderately effective, and allows benefiting from some of the performance advantages
    of TBAA while also being more friendly to code that goes "wild west"
    with things like pointer casts and "cast and dereference" patterns.

    So, say, a nicer compromise (even if still breakable).
    int foo1(char *s, int *t)
    {
    *s=*t+1;
    return *t;
    }
    //assume not directly visible within same context:
    int foo2()
    {
    int i, j;
    i=4;
    j=foo1((char *)(&i), &i);
    return j;
    }
    What is the result of calling foo2?...
    Here, foo2 breaks TBAA but in a way invisible to foo1.

    Though, in theory, one workaround is that the compiler can see that foo2 breaks TBAA and can then flag foo1 that its operands may not safely
    assume TBAA.

    Though, this poses a problem for my current compiler design, as some of
    the alias handling stuff happens before the compiler will have a
    complete view of the call-graph.

    Would in effect need to add an additional internal compiler pass to
    detect and mark all the TBAA violations within the call-graph.

    But, for now, seems "mostly good enough".




    For volatile, one typically needs to go a little further:
    Every load and store needs to be performed explicitly;
    There is a need to disallow load/store reordering;
    ...
    Mostly because volatile may be used to access MMIO, and MMIO is more
    strict than normal RAM in this area.

    Though, could maybe be better if "volatile" could be broken into several subtypes depending on which particular behaviors are needed:
    Weaker case: Assume aliasing happens.
    May still prune non-aliasing load/store or reorder;
    Normal case:
    Every load/store needs to happen;
    No reordering allowed.
    Stronger case:
    Like the above, but also needs to be synchronous between cores;
    Though, this role overlaps with _Atomic.

    There is also ambiguity as to how far the volatile-ness extends, but
    this can be avoided by doing it at the point of cast-and-deref:
    (*(volatile uint64_t *)ptr)
    In this case, it applying explicitly to the deref operation rather than
    the handling of the pointer before this point.

    ...


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Wed Jun 24 05:34:30 2026
    From Newsgroup: comp.arch

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    Consider the Push/Pop mechanics in HW compared to FMAC in HW--which
    do you think is easier ???

    Now consider 16 pushed in a row versus a single instruction that performs
    the same amount of work. Which one needs to translate an address more
    often, which one needs to AGEN more often, and which one can access the
    cache once for up to 8 registers ???

    Modern x86 processors have a "stack engine" to address this
    problems. Multiple push or pop instructions, respectively,
    are split into two microops (one memory access, one decrement or
    increment), and the decrement/increment microops are then merged.

    This proably costs an extra cycle pipeline depth or so, but
    I haven't been able (after cursory looking) to find a number for
    newer architectures.
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Wed Jun 24 05:48:44 2026
    From Newsgroup: comp.arch

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Wed Jun 24 06:06:14 2026
    From Newsgroup: comp.arch

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    Thomas Koenig <tkoenig@netcologne.de> posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    BGB <cr88192@gmail.com> posted:

    On 6/20/2026 5:01 PM, MitchAlsup wrote:
    ---------------
    Tagging to make it harder to stomp the link register;

    Put it somewhere it can't be stomped on !! like in memory on a page the >> >> > application has no access permissions.


    Multiple stacks is a big ask, and non-accessible memory is not so good >> >> when dealing with an ISA where user code needs to handle the Link-Register.

    Code does not need to access or look at the return address in My 66000
    ISA--except for the case where one wants to walk the stack back on a
    THROW() and its unstructured equivalent longjump().

    What about a debugging stack trace?

    The debugger runs in a separate process with access to application
    Root pointer and ASID. In that process, Call-stack is RW-.

    But no error backtrace from an error occuring in a normal program?

    Example (Fortran reading from a non-opened file, compiled with
    -g -static-libgfortran):

    program memain
    call foo(a)
    print *,a
    end program memain

    subroutine foo(a)
    read (10) a
    end subroutine foo

    $ ./a.out
    At line 7 of file foo.f90 (unit = 10, file = 'fort.10')
    Fortran runtime error: End of file

    Error termination. Backtrace:
    #0 0x407d27 in us_read
    at ../../../dump/libgfortran/io/transfer.c:2983
    #1 0x407e24 in pre_position
    at ../../../dump/libgfortran/io/transfer.c:3109
    #2 0x40ae34 in data_transfer_init
    at ../../../dump/libgfortran/io/transfer.c:3562
    #3 0x40392f in foo_
    at /tmp/foo.f90:7
    #4 0x403976 in memain
    at /tmp/foo.f90:2
    #5 0x403a0f in main
    at /tmp/foo.f90:4
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Wed Jun 24 06:08:15 2026
    From Newsgroup: comp.arch

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    scott@slp53.sl.home (Scott Lurndal) posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:

    Thomas Koenig <tkoenig@netcologne.de> posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    BGB <cr88192@gmail.com> posted:

    On 6/20/2026 5:01 PM, MitchAlsup wrote:
    ---------------
    Tagging to make it harder to stomp the link register;

    Put it somewhere it can't be stomped on !! like in memory on a page the
    application has no access permissions.


    Multiple stacks is a big ask, and non-accessible memory is not so good
    when dealing with an ISA where user code needs to handle the Link-Register.

    Code does not need to access or look at the return address in My 66000 >> >> > ISA--except for the case where one wants to walk the stack back on a
    THROW() and its unstructured equivalent longjump().

    What about a debugging stack trace?

    The debugger runs in a separate process with access to application
    Root pointer and ASID. In that process, Call-stack is RW-.

    GLIBC has a function to obtain a backtrace at a current point
    in time. This is called in the context of the thread that invokes
    the call. It requires access to the call records on the stack
    in the context of the thread (the glicb functions are backtrace(3)
    and backtrace_symbols(3)).

    When Thread is unExceptional it cannot access Call Stack,
    when Thread is Exceptional it can.

    ENTER, EXIT, and RET are exempt from the protection check.
    Call Stack Pointer is not accessible to unprivileged code.

    Don't see how one gets from a running application into debugger without taking an exception !?! or from running in the debugger to running in application without returning from an exception !!!

    Issuing in application error for which one might want to look at
    a backtrace (see previous Fortran example).
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Wed Jun 24 06:20:45 2026
    From Newsgroup: comp.arch

    BGB <cr88192@gmail.com> schrieb:

    [LISP]

    Usual downside it that the excessive parenthesis tend to turn into a usability issue.

    Ample fun has been made of this over time.

    Example: https://xkcd.com/297/

    Or, from the priceless "A Brief, Incomplete, and Mostly Wrong History of Programming Languages":

    # 1958 - John McCarthy and Paul Graham invent LISP. Due to high
    # costs caused by a post-war depletion of the strategic parentheses
    # reserve LISP never becomes popular... Fortunately for computer
    # science the supply of curly braces and angle brackets remains high.
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Wed Jun 24 08:50:18 2026
    From Newsgroup: comp.arch

    On 24/06/2026 02:54, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/22/2026 7:38 AM, Niklas Holsti wrote:
    On 2026-06-22 13:44, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:
    On 21/06/2026 20:57, MitchAlsup wrote:
    -------------
    In my case, I tended to use more conservative approaches and then only
    optimize based on what can be verified by the compiler within certain
    fundamental assumptions.

    Say:
    Pointer 1 points at a stack array in the local function;
    Pointer 2 was derived from taking the address of a global array;
    Compiler can safely assume no-alias.

    Also, if two pointers were passed into a function, can also assume they
    don't alias with a pointer to a local array;

    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming error.


    C lets the compiler assume that things do not alias, under certain circumstances. If you have a local array (pointer 1) and its address
    does not "escape", and a global array (pointer 2), the compiler can
    assume they do not alias, as any aliasing could only be the result of UB.

    For pointers passed into functions, the compiler won't have any such
    knowledge (unless it happens to be able to see the calling and called
    code at the same time - if they are in the same file, or you are using
    some kind of link-time or whole-program optimisation). But you can tell
    the compiler that pointers don't alias, with the "restrict" qualifier.
    This can make a significant difference in some code, and means that the "Fortran is faster than C because pointer parameters can't alias"
    argument has not been true since 1999. (Fortran code may be faster for
    other reasons - such as "C programmers don't know how to use restrict".)


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Wed Jun 24 02:01:40 2026
    From Newsgroup: comp.arch

    On 6/24/2026 12:48 AM, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.


    Yes, this is one place where I disagree with GCC on.
    I decided to go with "more sane" default behavior (no TBAA by default,
    it is opt-in).

    Goal is to find rules that are "mostly sane" while still being effective.


    Localized approaches can work OK, but necessarily need to be conservative.

    Something like full provenance poses a harder problem though, as to know
    a solid answer requires tracing the flow of a variable across multiple control-flow frames (or maybe going further, into reasoning about things
    like objects and linked lists).

    Decided not to go too much into it, but this is not the first time I
    have encountered a variation of this problem. It is doable in theory,
    but actually doing it in a compiler is a bit more of a pain...

    ...


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Wed Jun 24 09:58:35 2026
    From Newsgroup: comp.arch

    On 24/06/2026 05:41, BGB wrote:
    On 6/23/2026 9:25 PM, John Levine wrote:
    According to BGB-a <cr88192@gmail.com>:
    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming
    error.

    Hard proof that alias is impossible is harder to achieve in practice...

    A softer "there is no reasonable possibility of alias" is easier to
    achieve.

    Sort of.-a The standard says that the compiler can assume no type
    punning, so that
    if pointers are of different types, they can't point at the same thing
    (with an
    exception for pointers to unions.)

    Even so, C has "restrict" to tell the compiler to assume that pointers
    never
    alias, and "volatile" to assume they always do.


    Possibly, though traditional type-based aliasing rules run into a
    problem in that pointer casting can break its assumptions, and a lot of
    code doesn't respect these rules (which taken purely at face value, are overly limiting).


    It's true that some programmers seem to think you can do whatever you
    like with pointers converted between different types. A lot of use of converted pointer types will be UB in C, but C does not make it at all difficult to write code with these conversions. There's a fair argument
    to be made that type-based alias analysis rarely gives good optimisation opportunities, restricts programmers, and lets people write code that
    they think is correct, but is not. Quite a number of C compilers
    specifically do not do any type-based aliasing analysis, or let you turn
    it off (gcc -fno-strict-aliasing).

    One key point is that in C++, type-based alias analysis is much more
    useful as you generally use far more different types (typedef in C does
    not make different types), and code is generally much more careful about accessing them with correct pointer types (or better, references,
    containers, smart pointers, etc.).

    Maybe things could be helped by attributes that give you better control
    over aliasing - gcc has a "may_alias" type attribute that can be used to
    give a type the "aliasing superpowers" of character types.


    One option though is "if enabled, assume the rules are followed unless
    the compiler sees them being broken", in which case it disables TBAA
    when faced with TBAA violations.

    That sounds /really/ bad. You can't have the behaviour - the semantics
    - dependent on whether or not a compiler is able to find an error in
    your code!

    An option to say TBAA is enabled or not makes sense. Even better, is
    having it as a pragma. (I always use gcc optimise pragmas if I need to disable a particular optimisation, to keep it safe regardless of command
    line options.) Standardising this in some way could be useful. And it
    is probably a good idea to have TBAA off by default - let those who
    understand it and want it, enable it. (But it should probably be on by default for C++.)

    And when a compiler has TBAA enabled, and it spots a violation, that's
    time for an error message - not silently disabling it!


    This approach seems to be moderately
    effective, and allows benefiting from some of the performance advantages
    of TBAA while also being more friendly to code that goes "wild west"
    with things like pointer casts and "cast and dereference" patterns.

    So, say, a nicer compromise (even if still breakable).

    It's better to use something other than "char" pointers, since character pointers can be used to access any data. There is no UB in your example
    here, that I can see - "*s" is allowed to access data pointed to by
    "*t". Let's pretend you use "short * s" or "float * s" instead.

    -a int foo1(char *s, int *t)
    -a {
    -a-a-a *s=*t+1;
    -a-a-a return *t;
    -a }
    -a //assume not directly visible within same context:
    -a int foo2()
    -a {
    -a-a-a int i, j;
    -a-a-a i=4;
    -a-a-a j=foo1((char *)(&i), &i);
    -a-a-a return j;
    -a }
    What is the result of calling foo2?...
    -a Here, foo2 breaks TBAA but in a way invisible to foo1.

    With the proviso mentioned above, there are countless ways in which you calling a function with unexpected or inappropriate parameters leads to
    UB. You always have to know the requirements for the parameters before calling a function. This situation is a drop in the ocean, and not
    really worth worrying about specifically IMHO.


    For volatile, one typically needs to go a little further:
    -a Every load and store needs to be performed explicitly;
    -a There is a need to disallow load/store reordering;

    "volatile" does not affect hardware ordering - it only affects the
    ordering within the program. It cannot see things that are at a level
    below the generated code.

    If you want to influence hardware ordering, use atomics and fences (from
    C11, or implementation extensions).

    -a ...
    Mostly because volatile may be used to access MMIO, and MMIO is more
    strict than normal RAM in this area.

    In the microcontroller world at least, that is done by the memory
    management unit or memory protection unit, specifying which address
    areas are accessible in different ways, which are cacheable, which can
    be buffered or re-ordered. That is all well below the level visible in
    a programming language - and once the MPU is set up correctly, it all
    "just works".


    Though, could maybe be better if "volatile" could be broken into several subtypes depending on which particular behaviors are needed:
    -a Weaker case: Assume aliasing happens.
    -a-a-a May still prune non-aliasing load/store or reorder;

    C does not need volatile for that - you've got aliasing superpower
    character types. In practice, you have memcpy() / memmove() to read or
    write data that might be aliased, or different types. All you need is
    for compilers to handle small memcpy's with fixed sizes efficiently (as
    gcc and clang do). Often this is more efficient that using volatiles -
    using "float f = 12.3; uint32_t x; memcpy(&x, &f, 4); return x;" will typically result in a float register to integer register move instruction.

    -a Normal case:
    -a-a-a Every load/store needs to happen;
    -a-a-a No reordering allowed.

    No re-ordering with respect to other volatiles, you mean. That is what volatile does today.

    -a Stronger case:
    -a-a-a Like the above, but also needs to be synchronous between cores;
    -a-a-a Though, this role overlaps with _Atomic.

    As you say, that is the job for atomics - or volatile atomics.

    So we already have all the features you want. It is fair to say,
    however, that some programmers misunderstand "volatile" and think it
    means one of the other cases you list. (Or the case that you didn't
    list - the assumption that volatile forces an order on non-volatile
    accesses or operations.)


    There is also ambiguity as to how far the volatile-ness extends, but
    this can be avoided by doing it at the point of cast-and-deref:
    -a (*(volatile uint64_t *)ptr)
    In this case, it applying explicitly to the deref operation rather than
    the handling of the pointer before this point.


    I don't know what you mean by an "ambiguity" here. There is no
    ambiguity in C about where "volatile" applies. There might be confusion
    or misunderstanding amongst some programmers, but not an ambiguity in
    the semantics.

    It is, IME, helpful to remember that "volatile" is primarily about
    /accesses/, rather than objects. This was somewhat unclear in the C
    standards until C17.



    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Wed Jun 24 10:30:12 2026
    From Newsgroup: comp.arch

    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.


    That's the way C is defined. It is debatable as to whether the rules in
    the C standard are ideal (I don't think they are, but the changes I'd
    make might be different from the ones you would like). But it is
    entirely appropriate for a compiler to follow the C rules unless you
    specify otherwise.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.arch on Wed Jun 24 14:30:17 2026
    From Newsgroup: comp.arch

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:

    scott@slp53.sl.home (Scott Lurndal) posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:

    Thomas Koenig <tkoenig@netcologne.de> posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    BGB <cr88192@gmail.com> posted:

    On 6/20/2026 5:01 PM, MitchAlsup wrote:
    ---------------
    Tagging to make it harder to stomp the link register;

    Put it somewhere it can't be stomped on !! like in memory on a page the
    application has no access permissions.


    Multiple stacks is a big ask, and non-accessible memory is not so good
    when dealing with an ISA where user code needs to handle the Link-Register.

    Code does not need to access or look at the return address in My 66000 >> >> > ISA--except for the case where one wants to walk the stack back on a
    THROW() and its unstructured equivalent longjump().

    What about a debugging stack trace?

    The debugger runs in a separate process with access to application
    Root pointer and ASID. In that process, Call-stack is RW-.

    GLIBC has a function to obtain a backtrace at a current point
    in time. This is called in the context of the thread that invokes
    the call. It requires access to the call records on the stack
    in the context of the thread (the glicb functions are backtrace(3)
    and backtrace_symbols(3)).

    When Thread is unExceptional it cannot access Call Stack,
    when Thread is Exceptional it can.

    ENTER, EXIT, and RET are exempt from the protection check.
    Call Stack Pointer is not accessible to unprivileged code.

    Don't see how one gets from a running application into debugger without >taking an exception !?! or from running in the debugger to running in >application without returning from an exception !!!

    The glibc function ::backtrace can be called at any time, in any context.

    Then there are the unix context functions that also allow access to
    resources not normally visible to an application - getcontext(2), makecontext(3) and the setjmp/sigsetjmp functions which also
    gather the thread context, including the current stack pointer.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Robert Swindells@rjs@fdy2.co.uk to comp.arch on Wed Jun 24 14:38:02 2026
    From Newsgroup: comp.arch

    On Mon, 22 Jun 2026 18:49:40 -0400, George Neuner wrote:

    On Sat, 20 Jun 2026 10:15:41 -0400, Stefan Monnier
    <monnier@iro.umontreal.ca> wrote:

    Robert Swindells [2026-06-19 11:20:10] wrote:
    On Fri, 19 Jun 2026 06:02:16 GMT, Anton Ertl wrote:
    Another architectural feature: One might think that tagging support
    would help dynamically typed programming languages (e.g., Lisp), and
    SPARC contains some support for that, but as one of the IIRC Franz
    Lisp developers has explained in this newsgroup, they actually did
    not use this feature, because the performance benefit was not big
    enough to
    [...]
    Franz Lisp doesn't use tags at all and only ran on VAX and 68k.

    I guess you two aren't talking bout the same "Franz Lisp". AFAIK Anton
    is referring to the commercial Common Lisp compiler associated with the >>Franz Inc company, marketed under the name "Allegro".

    === Stefan

    ISTM there were at least a couple of Lisps available for the Vax. I
    can't speak to Franz, but I do know at least one Vax Lisp was a BIBOP[1] system that (generally) did not use tags.

    Franz Lisp used BiBOP.

    I posted a link earlier in the thread to the PDF of "Performance and Evaluation of Lisp Systems", Chapter 2 contains descriptions of the
    various implementations available at that time.

    <https://dreamsongs.com/Files/Timrep.pdf>

    The following chapter lists the benchmarks used and results for each implementation.

    For some reason, later versions of SPECint li ran these Lisp benchmarks
    in the XLisp interpreter that had been compiled for the CPU under test.

    The benchmark results reported in the book are for fully compiled code.

    In BIBOP, memory "pages"[2] are dedicated to a single data type. The
    base address of the page is mapped to the type of the objects the page contains, and so the objects (and pointers to them) need no type
    information themselves. This allowed for full width pointers, fixnums
    and floats, and for conses, boxes, and other fixed sized data types (including user types) to avoid tagging.

    I ran Franz Lisp on the Atari ST, not having tags made it easy to
    interface to the GEM GUI.

    I also made a start on a hardware accelerator for BiBOP type checking for
    the ST. The expansion connector on it provided access to the full 68k bus including the function pins.

    The idea was to look for data reads within a defined range then use the
    page number as an address for a small SRAM holding the BiBOP table and
    latch the value stored at that address. Would tweak the compiler slightly
    to read a value into a CPU register before needing to read the latched
    type of it.

    A variant of this idea could be to store the type value in spare bits in a PTE, then define an instruction that treats the contents of a register as
    an address and returns the matching "type" bits for it.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From John Levine@johnl@taugh.com to comp.arch on Wed Jun 24 20:17:45 2026
    From Newsgroup: comp.arch

    According to David Brown <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined. It is debatable as to whether the rules in
    the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write code that is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't. This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.
    --
    Regards,
    John Levine, johnl@taugh.com, Primary Perpetrator of "The Internet for Dummies",
    Please consider the environment before reading this e-mail. https://jl.ly
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Wed Jun 24 16:34:55 2026
    From Newsgroup: comp.arch

    On 6/24/2026 3:17 PM, John Levine wrote:
    According to David Brown <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined. It is debatable as to whether the rules in
    the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't. This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.


    This one is why I added a "_memlzcpy()" function to my C library, whose
    main purpose is to give this sort of self-overlapping copy behavior (and
    to consolidate nearly every LZ77 style decompressor otherwise needing to supply their own version).

    In the case of a short backwards copy, it will call "memmove()", but as
    noted the behavior in the case of a short forwards copy are different.

    For non-overlap cases it can just invoke "memcpy()".





    Otherwise, was sitting around trying to fiddle with memory usage in my compiler, and there still seems to be around 16MB unaccounted for (after tracking basically all of the memory allocation and freeing in the
    compiler, VS debugger reports around 16MB more memory being used than
    the internal memory-use tracking does).


    This also seems larger than easily explained by the binary itself...

    Would estimate ~ 7MB for the EXE's sections + OS stack (1MB in Windows).


    But, in the past few days of fiddling I have gotten it from ~ 250 MB to compile Doom down to around 64MB (in VS), or 48MB (according to the
    internal allocation tracking).

    Though seemingly in the process, the build times have gotten ~ 2 seconds longer.

    Though, there were some changes effecting significant compiler
    structures (changing some raw strings to string handles, and breaking
    one larger structure into multiple parts), so this isn't entirely unreasonable. Changing the structure was a bit annoying as it was one of
    the most heavily used in the compiler, so involved touching a lot of code.


    One annoyance is that the 3AC opcode structure has a few fields that are minority use, but it likely isn't worth the pain of messing with it.

    Unlike the other struct, the Op struct is small enough that splitting it
    via a pointer would likely end up being net-negative for memory use.
    Would likely need to get a bit tricky and use two different-sized
    structs depending on sub-type and putting the lesser-used fields on the
    end, but this would be awkward and ugly. Not likely worth it.

    Well, because to address the full range of operations, it effectively has:
    operation tags
    2 types;
    2 destinations;
    4 sources;
    a 24-byte tagged-union immediate-value field
    needed for calls, member load/store, ...

    The 2-size strategy would likely be to have a full version with all the fields, and a subset version with:
    operation tags
    1 type
    1 destination
    3 sources
    And, then questioning whether it would be worth it to shave 48 bytes off
    an 88 byte struct (and maybe save a few MB at most).


    Well, and then I can see that the compiler is burning around 2MB on the
    data for figuring out whether called functions may have touched
    particular global variables (though this effects what optimizations the compiler can do, so isn't purely waste), ...

    ...

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Thu Jun 25 00:46:17 2026
    From Newsgroup: comp.arch


    BGB <cr88192@gmail.com> posted:

    On 6/24/2026 3:17 PM, John Levine wrote:
    -------------------
    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't. This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.


    This one is why I added a "_memlzcpy()" function to my C library, whose
    main purpose is to give this sort of self-overlapping copy behavior (and
    to consolidate nearly every LZ77 style decompressor otherwise needing to supply their own version).

    Instead, I added MM instruction to ISA. MM is memmove() ! LLVM is happy to
    use MM as a struct copy (sa = sb;) independent of where sa or sb are.

    In the case of a short backwards copy, it will call "memmove()", but as noted the behavior in the case of a short forwards copy are different.

    HW is really good at pointer compares and loop inversions.

    For non-overlap cases it can just invoke "memcpy()".

    Unnecessary with MM.

    Plus, while MM is doing its thing, non-memory ref instructions can make
    forward progress, and non-aliasing memory refs can use the 'other' Memory Units..
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Andy Valencia@vandys@vsta.org to comp.arch on Wed Jun 24 19:17:39 2026
    From Newsgroup: comp.arch

    Thomas Koenig <tkoenig@netcologne.de> writes:
    BGB <cr88192@gmail.com> schrieb:
    Usual downside it that the excessive parenthesis tend to turn into a usability issue.
    Ample fun has been made of this over time.

    From rec.humor.funny:

    From: jasmerb@mist.cs.orst.edu (Bryce Jasmer)
    Newsgroups: rec.humor.funny
    Subject: The Strategic Defense Initiative (SDI/Star Wars)
    Keywords: computer, funny
    Message-ID: <137457@looking.on.ca>
    Date: 23 Apr 90 10:30:08 GMT
    Sender: funnyr@looking.on.ca
    Posted: Mon Apr 23 11:30:08 1990
    Reply-Path: mist.cs.orst.edu!jasmerb

    Through some clever security hole manipulation if I have been able to
    break into all of the government's computers and acquire the Lisp code
    to SDI. Here is the last page (tail -10) of it to prove that I actually
    have the code:

    )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))


    Andy Valencia
    Home page: https://www.vsta.org/andy/
    To contact me: https://www.vsta.org/contact/andy.html
    No AI was used in the composition of this message
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Thu Jun 25 09:18:38 2026
    From Newsgroup: comp.arch

    On 24/06/2026 22:17, John Levine wrote:
    According to David Brown <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined. It is debatable as to whether the rules in
    the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't. This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.


    I think it is perhaps better to say that one of the less fortunate
    things about C is that people make assumptions without learning the
    language properly or looking up the details. And then code with these incorrect assumptions is then propagated.

    "memcpy" is a fine example of this. It says on the tin that using it
    for objects that overlap is undefined behaviour - C standard code for
    "don't do that".

    But lots of people hammer away at their keyboards without reading the
    manuals or instructions, or paying much attention to their tutorial
    books or courses. Some languages are much more forgiving there - Python
    aims to accept as wide a range of inputs as possible for any operation
    or library function, and aims to give you as much feedback about your
    mistakes at runtime. C aims for maximal efficiency on the assumption
    that you have read the specifications for the language, and follow the
    rules. You can do a lot of Python programming by combining trial and
    error with a bit of "how do I do this in Python" googling. In C, that's
    going to lead to tears sooner rather than later.

    In the case of "memcpy", a lot of people think it is defined - specified
    - by the na|>ve implementation (I believe it is shown as an example in
    K&R). And so they use "memcpy" everywhere, even in cases where
    "memmove" is the appropriate choice. Not long ago, a glibc developer discovered that on some Intel processors, in some circumstances, running
    the memory copy backwards lead to a noticeable speedup for memcpy(), and
    thus implemented that. The backlash of people who said the change
    "broke" their code was overwhelming, and the change was reverted.

    I really do think that things like this should be /easy/ to get right. Parameters to "memcpy" are not allowed to overlap, so that the copying
    can be as efficient as possible. "memmove" allows the parameters to
    overlap, but is likely to be less efficient. Use the one that suits
    your requirements.

    But people get it wrong. There's a lot of people who sit alone,
    programming in C, who should not be programming in C - they should be
    using different languages, or learning C better before using it. Or
    they should have better guidance and help, code reviews from people more experienced. Many people programming in C don't even enable warnings on
    their compiler. C is not a language for people who program "by
    intuition", it requires more discipline in developers than many other languages.

    You are right that there are more subtle possibilities for errors in C,
    and I know of no one who thinks the rules of C and the standard library
    are all ideal. But a huge percentage of the code bugs in C (as distinct
    from logical errors, specification errors, etc., that plague all
    programming in all languages) could be avoided by better development practices.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Thu Jun 25 09:22:49 2026
    From Newsgroup: comp.arch

    On 24/06/2026 23:34, BGB wrote:
    On 6/24/2026 3:17 PM, John Levine wrote:
    According to David Brown-a <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish.-a Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address.-a One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined.-a It is debatable as to whether the rules in >>> the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write
    code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    -a-a-a-achar a[100];

    -a-a-a-aa[0] = 42;
    -a-a-a-amemcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't.-a This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.


    This one is why I added a "_memlzcpy()" function to my C library, whose
    main purpose is to give this sort of self-overlapping copy behavior (and
    to consolidate nearly every LZ77 style decompressor otherwise needing to supply their own version).

    In the case of a short backwards copy, it will call "memmove()", but as noted the behavior in the case of a short forwards copy are different.

    For non-overlap cases it can just invoke "memcpy()".

    "memmove" will not fill the array above with 42. "memmove" acts as
    though it copies the source to a temporary buffer, then copies that
    temporary buffer to the destination. (If you want to fill the buffer
    with the value 42, "memset" is the function to use.)

    How is your "_memlzcpy" defined that is different from that?

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Thu Jun 25 03:23:27 2026
    From Newsgroup: comp.arch

    On 6/25/2026 2:22 AM, David Brown wrote:
    On 24/06/2026 23:34, BGB wrote:
    On 6/24/2026 3:17 PM, John Levine wrote:
    According to David Brown-a <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish.-a Actually, by default gcc assumes (i.e., it does not prove) >>>>> that pointers to different types (except char) do not point to the
    same address.-a One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined.-a It is debatable as to whether the
    rules in
    the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write
    code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    -a-a-a-achar a[100];

    -a-a-a-aa[0] = 42;
    -a-a-a-amemcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't.-a This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.


    This one is why I added a "_memlzcpy()" function to my C library,
    whose main purpose is to give this sort of self-overlapping copy
    behavior (and to consolidate nearly every LZ77 style decompressor
    otherwise needing to supply their own version).

    In the case of a short backwards copy, it will call "memmove()", but
    as noted the behavior in the case of a short forwards copy are different.

    For non-overlap cases it can just invoke "memcpy()".

    "memmove" will not fill the array above with 42.-a "memmove" acts as
    though it copies the source to a temporary buffer, then copies that temporary buffer to the destination.-a (If you want to fill the buffer
    with the value 42, "memset" is the function to use.)


    Yeah, this is why I created "_memlzcpy()", because the defined behavior
    for "memmove()" is not what one wants for self-overlapping forward copy.


    How is your "_memlzcpy" defined that is different from that?
    Here:
    _memlzcpy(dst+1, dst, len);
    Is functionally equivalent to:
    memset(dst+1, *dst, len);

    But, it can do more:
    _memlzcpy(dst+2, dst, len); //repeating 2-byte pattern
    _memlzcpy(dst+3, dst, len); //repeating 3-byte pattern
    ...

    So, required to work for every self-overlap distance.


    Or, in the case as commonly used in an LZ77 style decompressor:
    _memlzcpy(dest, dest-distance, length);


    Though, there are also:
    _memcpyf()
    _memmovef()
    _memlzcpyf()

    Where the 'f' in this case means:
    Allowed to be a little faster by potentially going up to 32 bytes extra.

    Where, in some cases it is faster to overshoot the end than to give an
    exact length, but it would not be valid to overshoot the copy for the
    normal versions (exact length even if it is a little slower).


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Stefan Monnier@monnier@iro.umontreal.ca to comp.arch on Wed Jun 24 19:50:09 2026
    From Newsgroup: comp.arch

    Robert Swindells [2026-06-24 14:38:02] wrote:
    On Mon, 22 Jun 2026 18:49:40 -0400, George Neuner wrote:
    On Sat, 20 Jun 2026 10:15:41 -0400, Stefan Monnier
    <monnier@iro.umontreal.ca> wrote:
    Robert Swindells [2026-06-19 11:20:10] wrote:
    On Fri, 19 Jun 2026 06:02:16 GMT, Anton Ertl wrote:
    Another architectural feature: One might think that tagging support
    would help dynamically typed programming languages (e.g., Lisp), and >>>>> SPARC contains some support for that, but as one of the IIRC Franz
    Lisp developers has explained in this newsgroup, they actually did
    not use this feature, because the performance benefit was not big
    enough to
    [...]
    Franz Lisp doesn't use tags at all and only ran on VAX and 68k.

    I guess you two aren't talking bout the same "Franz Lisp". AFAIK Anton
    is referring to the commercial Common Lisp compiler associated with the >>>Franz Inc company, marketed under the name "Allegro".

    === Stefan

    ISTM there were at least a couple of Lisps available for the Vax. I
    can't speak to Franz, but I do know at least one Vax Lisp was a BIBOP[1]
    system that (generally) did not use tags.

    Franz Lisp used BiBOP.

    Side note: the BiBoP technique is largely orthogonal to the
    architectural support for pointer tagging, because usually BiBoP is used
    to "eliminate" the tags present inside the heap representation of
    objects rather than the few tagbits stolen from pointers: the purpose of
    those tagbits is usually to be able to determine the type of the object *without* any memory access whereas BiBoP stores the corresponding info
    in memory.

    E.g. tagbits are most commonly used to distinguish between an immediate
    small integer value and a pointer. BiBoP wouldn't help with that,
    forcing the small integer to be stored in some "page of small integers"
    which could have a very serious performance impact.


    === Stefan
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Thu Jun 25 14:39:54 2026
    From Newsgroup: comp.arch

    BGB wrote:
    On 6/25/2026 2:22 AM, David Brown wrote:
    On 24/06/2026 23:34, BGB wrote:
    On 6/24/2026 3:17 PM, John Levine wrote:
    According to David Brown|e-a <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish.|e-a Actually, by default gcc assumes (i.e., it does not prove) >>>>>> that pointers to different types (except char) do not point to the>>>>>> same address.|e-a One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined.|e-a It is debatable as to whether the
    rules in
    the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write
    code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.: >>>>
    |e-a|e-a|e-a|e-achar a[100];

    |e-a|e-a|e-a|e-aa[0] = 42;
    |e-a|e-a|e-a|e-amemcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that>>>> moves larger blocks won't.|e-a This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.


    This one is why I added a "_memlzcpy()" function to my C library,
    whose main purpose is to give this sort of self-overlapping copy
    behavior (and to consolidate nearly every LZ77 style decompressor
    otherwise needing to supply their own version).

    In the case of a short backwards copy, it will call "memmove()", but >>> as noted the behavior in the case of a short forwards copy are
    different.

    For non-overlap cases it can just invoke "memcpy()".

    "memmove" will not fill the array above with 42.|e-a "memmove" acts as
    though it copies the source to a temporary buffer, then copies that
    temporary buffer to the destination.|e-a (If you want to fill the buffer
    with the value 42, "memset" is the function to use.)


    Yeah, this is why I created "_memlzcpy()", because the defined behavior
    for "memmove()" is not what one wants for self-overlapping forward copy.


    How is your "_memlzcpy" defined that is different from that?
    Here:
    -a _memlzcpy(dst+1, dst, len);
    Is functionally equivalent to:
    -a memset(dst+1, *dst, len);

    But, it can do more:
    -a _memlzcpy(dst+2, dst, len);-a //repeating 2-byte pattern
    -a _memlzcpy(dst+3, dst, len);-a //repeating 3-byte pattern
    -a ...

    So, required to work for every self-overlap distance.


    Or, in the case as commonly used in an LZ77 style decompressor:
    -a _memlzcpy(dest, dest-distance, length);


    Though, there are also:
    -a _memcpyf()
    -a _memmovef()
    -a _memlzcpyf()

    Where the 'f' in this case means:
    Allowed to be a little faster by potentially going up to 32 bytes extra.
    I'm guessing you really meant up to 31 bytes extra?
    This is what my own (faster than Google's version) LZ4 decompressor uses internally.
    I am using either a pair of SSE or a single AVX register (so 32 bytes in both cases) as the copy granule. For the specific,very common, case of
    an overlapping copy that unrolls RLL-encoded data, I start by loading
    the starting pattern into the bottom of a register, then use the pattern length to index into a table of swizzle patterns that will generate the
    required results, for any pattern up to 32 bytes long.
    swizzle_table:
    [0,0,0,0,0,0,0,...
    [0,1,0,1,0,1,0,1,...
    [0,1,2,0,1,2,0,1,2,...
    [0,1,2,3,0,1,2,3,...
    [0,1,2,3,4,0,1,2,3,..
    etc.
    Note that having 31 entries of 32 bytes each means that I'm allocating
    almost a KB of $L1 cache space just for this table, but when you're decompressing lots of data it pays off.
    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Robert Swindells@rjs@fdy2.co.uk to comp.arch on Thu Jun 25 13:24:45 2026
    From Newsgroup: comp.arch

    On Wed, 24 Jun 2026 19:50:09 -0400, Stefan Monnier wrote:

    Robert Swindells [2026-06-24 14:38:02] wrote:
    On Mon, 22 Jun 2026 18:49:40 -0400, George Neuner wrote:
    On Sat, 20 Jun 2026 10:15:41 -0400, Stefan Monnier
    <monnier@iro.umontreal.ca> wrote:
    Robert Swindells [2026-06-19 11:20:10] wrote:
    On Fri, 19 Jun 2026 06:02:16 GMT, Anton Ertl wrote:
    Another architectural feature: One might think that tagging support >>>>>> would help dynamically typed programming languages (e.g., Lisp),
    and SPARC contains some support for that, but as one of the IIRC
    Franz Lisp developers has explained in this newsgroup, they
    actually did not use this feature, because the performance benefit >>>>>> was not big enough to
    [...]
    Franz Lisp doesn't use tags at all and only ran on VAX and 68k.

    I guess you two aren't talking bout the same "Franz Lisp". AFAIK Anton >>>>is referring to the commercial Common Lisp compiler associated with
    the Franz Inc company, marketed under the name "Allegro".

    === Stefan

    ISTM there were at least a couple of Lisps available for the Vax. I
    can't speak to Franz, but I do know at least one Vax Lisp was a
    BIBOP[1]
    system that (generally) did not use tags.

    Franz Lisp used BiBOP.

    Side note: the BiBoP technique is largely orthogonal to the
    architectural support for pointer tagging, because usually BiBoP is used
    to "eliminate" the tags present inside the heap representation of
    objects rather than the few tagbits stolen from pointers: the purpose of those tagbits is usually to be able to determine the type of the object *without* any memory access whereas BiBoP stores the corresponding info
    in memory.

    E.g. tagbits are most commonly used to distinguish between an immediate
    small integer value and a pointer. BiBoP wouldn't help with that,
    forcing the small integer to be stored in some "page of small integers"
    which could have a very serious performance impact.

    But if you know that you have a "page of small integers" then you can just
    do address comparisons between them, the Franz Lisp compiler did this.



    === Stefan

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From EricP@ThatWouldBeTelling@thevillage.com to comp.arch on Thu Jun 25 09:38:22 2026
    From Newsgroup: comp.arch

    On 2026-Jun-25 08:39, Terje Mathisen wrote:
    BGB wrote:
    On 6/25/2026 2:22 AM, David Brown wrote:
    On 24/06/2026 23:34, BGB wrote:
    On 6/24/2026 3:17 PM, John Levine wrote:
    According to David Brown|e-a <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias. >>>>>>>
    I wish.|e-a Actually, by default gcc assumes (i.e., it does not prove) >>>>>>> that pointers to different types (except char) do not point to the >>>>>>> same address.|e-a One has to turn that off with -fno-strict-aliasing. >>>>>>> Other C compilers use the same assumption.

    That's the way C is defined.|e-a It is debatable as to whether the rules in
    the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.: >>>>>
    |e-a|e-a|e-a|e-achar a[100];

    |e-a|e-a|e-a|e-aa[0] = 42;
    |e-a|e-a|e-a|e-amemcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that >>>>> moves larger blocks won't.|e-a This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.


    This one is why I added a "_memlzcpy()" function to my C library, whose main purpose is to give this sort of self-overlapping copy behavior (and to consolidate nearly every LZ77 style decompressor otherwise needing to supply their own version).

    In the case of a short backwards copy, it will call "memmove()", but as noted the behavior in the case of a short forwards copy are different.

    For non-overlap cases it can just invoke "memcpy()".

    "memmove" will not fill the array above with 42.|e-a "memmove" acts as though it copies the source to a temporary buffer, then copies that temporary buffer to the destination.|e-a (If you want to fill the buffer with the value 42, "memset" is the function to use.)


    Yeah, this is why I created "_memlzcpy()", because the defined behavior for "memmove()" is not what one wants for self-overlapping forward copy.


    How is your "_memlzcpy" defined that is different from that?
    Here:
    -a-a _memlzcpy(dst+1, dst, len);
    Is functionally equivalent to:
    -a-a memset(dst+1, *dst, len);

    But, it can do more:
    -a-a _memlzcpy(dst+2, dst, len);-a //repeating 2-byte pattern
    -a-a _memlzcpy(dst+3, dst, len);-a //repeating 3-byte pattern
    -a-a ...

    So, required to work for every self-overlap distance.


    Or, in the case as commonly used in an LZ77 style decompressor:
    -a-a _memlzcpy(dest, dest-distance, length);


    Though, there are also:
    -a-a _memcpyf()
    -a-a _memmovef()
    -a-a _memlzcpyf()

    Where the 'f' in this case means:
    Allowed to be a little faster by potentially going up to 32 bytes extra.

    I'm guessing you really meant up to 31 bytes extra?

    This is what my own (faster than Google's version) LZ4 decompressor uses internally.

    I am using either a pair of SSE or a single AVX register (so 32 bytes in both cases) as the copy granule. For the specific,very common, case of an overlapping copy that unrolls RLL-encoded data, I start by loading the starting pattern into the bottom of a register, then use the pattern length to index into a table of swizzle patterns that will generate the required results, for any pattern up to 32 bytes long.

    swizzle_table:

    [0,0,0,0,0,0,0,...
    [0,1,0,1,0,1,0,1,...
    [0,1,2,0,1,2,0,1,2,...
    [0,1,2,3,0,1,2,3,...
    [0,1,2,3,4,0,1,2,3,..

    etc.

    Note that having 31 entries of 32 bytes each means that I'm allocating almost a KB of $L1 cache space just for this table, but when you're decompressing lots of data it pays off.

    Terje


    If I had 256b,32B registers I would like to have LDV Load Variable and STV Store Variable
    instructions, which take an address, a src/dst simd register, and either a scalar register
    or immediate byte count in the range 0..32. LDV loads the specified number of bytes into
    the simd starting at the least significant byte and zero-fills any unread ones. These should be relatively easy to implement if one already has unaligned SIMD LD/ST.

    One might also consider LDBV/STBV variable length bit vectors 0 to 256b,




    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.arch on Thu Jun 25 15:17:52 2026
    From Newsgroup: comp.arch

    John Levine <johnl@taugh.com> writes:
    According to David Brown <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined. It is debatable as to whether the rules in >>the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write code that >is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't. This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.

    The burroughs B3500 and medium systems successors, which is a
    memory-to-memory architecture had a number of move instructions,
    several of which had architecturally defined semantics for
    overlapping source and destination fields, which included
    functionality similar to that you describe above.

    MVR (Move Repeat) was the one most commonly used to fill
    a single value into multiple memory locations (where
    the value could be from one to 100 digits and the repeat count
    between 1 and 100).

    The remaining move instructions MVA (Move Alpha - i.e. bytes)
    MVD (Move Data), MVW (Move Words) and
    MVN (Move Numeric) had defined semantics
    for some cases of overlapping operands that could result in
    "smearing" a store over a large region of memory or
    repeating a digit throughout the receiving operand.

    For example, the overlap behavior for MVW was

    "When the final B address is less than the final A
    address and the fields partially overlap, the source
    data field will be shifted by that number of digits
    to the left. When the B data field partially overlaps
    the A data field and B is greater than A, repeat the data from
    the A address to the B address throughout the destination
    data field. The B data field may totally overlap the A
    data field"

    The overlap behavior for MVC (Move and Clear) could be
    used to right justify the A data in the B field with
    the destination filled with leading zeros or shift
    the data to the left depending on the relationship
    between A and B.

    All other overlap results were dependent upon the
    generation of processor and could not be relied upon
    between generations.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Thu Jun 25 16:26:43 2026
    From Newsgroup: comp.arch

    David Brown <david.brown@hesbynett.no> schrieb:

    "memmove" will not fill the array above with 42. "memmove" acts as
    though it copies the source to a temporary buffer, then copies that temporary buffer to the destination. (If you want to fill the buffer
    with the value 42, "memset" is the function to use.)

    This is what Fortran does for array assignment. From the language
    definition, the right-hand side of an assignment is evaluated
    completely, then the value is assigned to the lefth-and side.
    So, from the language definition,

    a = a + 1.0

    is something like, assuming a suitable declaration for tmp,

    allocate (tmp(size(a)))
    tmp = a + 1.0
    a = tmp
    deallocate (tmp)

    and a compiler is free to do that. However, for efficiency
    reason, a compiler write is well-advised to detect this
    case and make it into a simple loop.

    A lot of tricks can be played with dependency checking, loop
    reversal etc.
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Thu Jun 25 18:50:27 2026
    From Newsgroup: comp.arch

    EricP wrote:
    On 2026-Jun-25 08:39, Terje Mathisen wrote:
    BGB wrote:
    On 6/25/2026 2:22 AM, David Brown wrote:
    On 24/06/2026 23:34, BGB wrote:
    On 6/24/2026 3:17 PM, John Levine wrote:
    According to David Brown|arCU|e-a <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias. >>>>>>>>
    I wish.|arCU|e-a Actually, by default gcc assumes (i.e., it does not >>>>>>>> prove)
    that pointers to different types (except char) do not point to the >>>>>>>> same address.|arCU|e-a One has to turn that off with
    -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined.|arCU|e-a It is debatable as to whether >>>>>>> the rules in
    the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to
    write code that
    is intuitively reasonable and sometimes works but isn't portable, >>>>>> e.g.:

    |arCU|e-a|arCU|e-a|arCU|e-a|arCU|e-achar a[100];

    |arCU|e-a|arCU|e-a|arCU|e-a|arCU|e-aa[0] = 42;
    |arCU|e-a|arCU|e-a|arCU|e-a|arCU|e-amemcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that >>>>>> moves larger blocks won't.|arCU|e-a This example is really obvious (it's >>>>>> why there's also memmove()) but there's plenty of more subtle ones. >>>>>>

    This one is why I added a "_memlzcpy()" function to my C library,
    whose main purpose is to give this sort of self-overlapping copy
    behavior (and to consolidate nearly every LZ77 style decompressor
    otherwise needing to supply their own version).

    In the case of a short backwards copy, it will call "memmove()",
    but as noted the behavior in the case of a short forwards copy are >>>>> different.

    For non-overlap cases it can just invoke "memcpy()".

    "memmove" will not fill the array above with 42.|arCU|e-a "memmove" acts >>>> as though it copies the source to a temporary buffer, then copies
    that temporary buffer to the destination.|arCU|e-a (If you want to fill >>>> the buffer with the value 42, "memset" is the function to use.)


    Yeah, this is why I created "_memlzcpy()", because the defined
    behavior for "memmove()" is not what one wants for self-overlapping
    forward copy.


    How is your "_memlzcpy" defined that is different from that?
    Here:
    |e-a|e-a _memlzcpy(dst+1, dst, len);
    Is functionally equivalent to:
    |e-a|e-a memset(dst+1, *dst, len);

    But, it can do more:
    |e-a|e-a _memlzcpy(dst+2, dst, len);|e-a //repeating 2-byte pattern
    |e-a|e-a _memlzcpy(dst+3, dst, len);|e-a //repeating 3-byte pattern
    |e-a|e-a ...

    So, required to work for every self-overlap distance.


    Or, in the case as commonly used in an LZ77 style decompressor:
    |e-a|e-a _memlzcpy(dest, dest-distance, length);


    Though, there are also:
    |e-a|e-a _memcpyf()
    |e-a|e-a _memmovef()
    |e-a|e-a _memlzcpyf()

    Where the 'f' in this case means:
    Allowed to be a little faster by potentially going up to 32 bytes extra.

    I'm guessing you really meant up to 31 bytes extra?

    This is what my own (faster than Google's version) LZ4 decompressor
    uses internally.

    I am using either a pair of SSE or a single AVX register (so 32 bytes >> in both cases) as the copy granule. For the specific,very common, case
    of an overlapping copy that unrolls RLL-encoded data, I start by
    loading the starting pattern into the bottom of a register, then use
    the pattern length to index into a table of swizzle patterns that will
    generate the required results, for any pattern up to 32 bytes long.

    swizzle_table:

    [0,0,0,0,0,0,0,...
    [0,1,0,1,0,1,0,1,...
    [0,1,2,0,1,2,0,1,2,...
    [0,1,2,3,0,1,2,3,...
    [0,1,2,3,4,0,1,2,3,..

    etc.

    Note that having 31 entries of 32 bytes each means that I'm allocating
    almost a KB of $L1 cache space just for this table, but when you're
    decompressing lots of data it pays off.


    If I had 256b,32B registers I would like to have LDV Load Variable and > STV Store Variable
    instructions, which take an address, a src/dst simd register, and either
    a scalar register
    or immediate byte count in the range 0..32. LDV loads the specified
    number of bytes into
    the simd starting at the least significant byte and zero-fills any
    unread ones.
    These should be relatively easy to implement if one already has
    unaligned SIMD LD/ST.

    One might also consider LDBV/STBV variable length bit vectors 0 to 256b,
    We do have that, in the form of a masked move, but it is more efficient
    to simply use the regular unaligned store op.
    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Thu Jun 25 17:13:04 2026
    From Newsgroup: comp.arch


    Andy Valencia <vandys@vsta.org> posted:

    Thomas Koenig <tkoenig@netcologne.de> writes:
    BGB <cr88192@gmail.com> schrieb:
    Usual downside it that the excessive parenthesis tend to turn into a usability issue.
    Ample fun has been made of this over time.

    From rec.humor.funny:

    From: jasmerb@mist.cs.orst.edu (Bryce Jasmer)
    Newsgroups: rec.humor.funny
    Subject: The Strategic Defense Initiative (SDI/Star Wars)
    Keywords: computer, funny
    Message-ID: <137457@looking.on.ca>
    Date: 23 Apr 90 10:30:08 GMT
    Sender: funnyr@looking.on.ca
    Posted: Mon Apr 23 11:30:08 1990
    Reply-Path: mist.cs.orst.edu!jasmerb

    Through some clever security hole manipulation if I have been able to
    break into all of the government's computers and acquire the Lisp code
    to SDI. Here is the last page (tail -10) of it to prove that I actually have the code:

    )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))


    I remember the LISP on PDP-8. One could use the character ] to mean as many
    )s as needed to close the lambda.

    Andy Valencia
    Home page: https://www.vsta.org/andy/
    To contact me: https://www.vsta.org/contact/andy.html
    No AI was used in the composition of this message
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Thu Jun 25 17:14:28 2026
    From Newsgroup: comp.arch


    John Levine <johnl@taugh.com> posted:

    According to David Brown <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined. It is debatable as to whether the rules in >the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    Why not::

    memset( a, 42, 100 );

    ?????

    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't. This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Thu Jun 25 17:20:01 2026
    From Newsgroup: comp.arch


    David Brown <david.brown@hesbynett.no> posted:

    On 24/06/2026 23:34, BGB wrote:
    ---------------------
    "memmove" will not fill the array above with 42. "memmove" acts as
    though it copies the source to a temporary buffer, then copies that temporary buffer to the destination. (If you want to fill the buffer
    with the value 42, "memset" is the function to use.)

    Act as though it copies twice is utterly unnecessary as overlapping
    memory can simply be performed back-to-front instead of front-to-back.

    How is your "_memlzcpy" defined that is different from that?

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Thu Jun 25 20:45:26 2026
    From Newsgroup: comp.arch

    On 25/06/2026 19:20, MitchAlsup wrote:

    David Brown <david.brown@hesbynett.no> posted:

    On 24/06/2026 23:34, BGB wrote:
    ---------------------
    "memmove" will not fill the array above with 42. "memmove" acts as
    though it copies the source to a temporary buffer, then copies that
    temporary buffer to the destination. (If you want to fill the buffer
    with the value 42, "memset" is the function to use.)

    Act as though it copies twice is utterly unnecessary as overlapping
    memory can simply be performed back-to-front instead of front-to-back.


    You are mixing up "act as though" it does something, and implementing it
    that way. memmove implementations will typically figure out if they can
    work as a forwards loop or a backwards loop, and do that. For moves
    that are big enough to be worth the effort, they'll do it using big
    lumps (64 bit, or bigger if that is more efficient) and then handle any
    last few bytes individually. If the overlap is closer than a "lump",
    more effort is needed. As Thomas said in reference to Fortran array assignment, there are lots of tricks possible that give faster results
    than the simple forward or backwards byte copying.

    But however it is implemented, the result is the same as you would get
    by copying to a temporary area.


    How is your "_memlzcpy" defined that is different from that?


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From John Levine@johnl@taugh.com to comp.arch on Thu Jun 25 19:19:47 2026
    From Newsgroup: comp.arch

    According to MitchAlsup <user5857@newsgrouper.org.invalid>:

    John Levine <johnl@taugh.com> posted:

    According to David Brown <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined. It is debatable as to whether the rules in >> >the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    Why not::

    memset( a, 42, 100 );

    Jeez, it's an example.
    --
    Regards,
    John Levine, johnl@taugh.com, Primary Perpetrator of "The Internet for Dummies",
    Please consider the environment before reading this e-mail. https://jl.ly
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Thu Jun 25 14:55:28 2026
    From Newsgroup: comp.arch

    On 6/25/2026 7:39 AM, Terje Mathisen wrote:
    BGB wrote:
    On 6/25/2026 2:22 AM, David Brown wrote:
    On 24/06/2026 23:34, BGB wrote:
    On 6/24/2026 3:17 PM, John Levine wrote:
    According to David Brown|e-a <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias. >>>>>>>
    I wish.|e-a Actually, by default gcc assumes (i.e., it does not prove) >>>>>>> that pointers to different types (except char) do not point to the >>>>>>> same address.|e-a One has to turn that off with -fno-strict-aliasing. >>>>>>> Other C compilers use the same assumption.

    That's the way C is defined.|e-a It is debatable as to whether the >>>>>> rules in
    the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to
    write code that
    is intuitively reasonable and sometimes works but isn't portable,
    e.g.:

    |e-a|e-a|e-a|e-achar a[100];

    |e-a|e-a|e-a|e-aa[0] = 42;
    |e-a|e-a|e-a|e-amemcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that >>>>> moves larger blocks won't.|e-a This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.


    This one is why I added a "_memlzcpy()" function to my C library,
    whose main purpose is to give this sort of self-overlapping copy
    behavior (and to consolidate nearly every LZ77 style decompressor
    otherwise needing to supply their own version).

    In the case of a short backwards copy, it will call "memmove()", but
    as noted the behavior in the case of a short forwards copy are
    different.

    For non-overlap cases it can just invoke "memcpy()".

    "memmove" will not fill the array above with 42.|e-a "memmove" acts as
    though it copies the source to a temporary buffer, then copies that
    temporary buffer to the destination.|e-a (If you want to fill the
    buffer with the value 42, "memset" is the function to use.)


    Yeah, this is why I created "_memlzcpy()", because the defined
    behavior for "memmove()" is not what one wants for self-overlapping
    forward copy.


    How is your "_memlzcpy" defined that is different from that?
    Here:
    -a-a _memlzcpy(dst+1, dst, len);
    Is functionally equivalent to:
    -a-a memset(dst+1, *dst, len);

    But, it can do more:
    -a-a _memlzcpy(dst+2, dst, len);-a //repeating 2-byte pattern
    -a-a _memlzcpy(dst+3, dst, len);-a //repeating 3-byte pattern
    -a-a ...

    So, required to work for every self-overlap distance.


    Or, in the case as commonly used in an LZ77 style decompressor:
    -a-a _memlzcpy(dest, dest-distance, length);


    Though, there are also:
    -a-a _memcpyf()
    -a-a _memmovef()
    -a-a _memlzcpyf()

    Where the 'f' in this case means:
    Allowed to be a little faster by potentially going up to 32 bytes extra.

    I'm guessing you really meant up to 31 bytes extra?


    Yeah, off by 1 error.


    This is what my own (faster than Google's version) LZ4 decompressor uses internally.

    I am using either a pair of SSE or a single AVX register (so 32 bytes in both cases) as the copy granule. For the specific,very common, case of
    an overlapping copy that unrolls RLL-encoded data, I start by loading
    the starting pattern into the bottom of a register, then use the pattern length to index into a table of swizzle patterns that will generate the required results, for any pattern up to 32 bytes long.

    swizzle_table:

    [0,0,0,0,0,0,0,...
    [0,1,0,1,0,1,0,1,...
    [0,1,2,0,1,2,0,1,2,...
    [0,1,2,3,0,1,2,3,...
    [0,1,2,3,4,0,1,2,3,..

    etc.

    Note that having 31 entries of 32 bytes each means that I'm allocating almost a KB of $L1 cache space just for this table, but when you're decompressing lots of data it pays off.


    Not using my own C library on x86-64, ... usually (depending on context) MSVCRT or glibc or similar.



    In my case it is typically using 64-bit copies, but for better pipeline utilization it is better to copy in groups of 4x 64-bit loads/stores, or
    32 bytes.

    Well, and a 0,2,1,3 order on my core; but this is mostly a benefit if
    the pointer is aligned on a 16-byte boundary (can potentially avoid some internal penalties within the L1 cache).


    But, yeah, I can use this for both LZ4 and RP2 compression, which are typically the main two that I use.

    Some common properties:
    Both byte-oriented designs that allow for fast decompressors.
    Some different properties:
    LZ4 usually does slightly better for program binaries;
    RP2 usually does better for general data;
    LZ4 is usually faster on OoO machines;
    RP2 is usually faster on in-order.
    LZ4 limits:
    Distance: 64K
    Literal Length: Unbounded
    Match Length: Unbounded
    RP2 limits (typical):
    Distance: 128K
    Literal Length: Unbounded
    Match Length: 516 bytes

    There exist variants of RP2 which allow larger limits, but for many
    use-cases, the version with these limits makes the most sense. A newer
    variant added a different mechanism for long-distance matching (the
    original mechanism wasn't ideal in some ways), and some special cases to
    help with long RLE runs (a long-match / very-short-distance case), but
    these aren't really used much.

    As noted, the design of RP2 was a mutation of the EA RefPack design, but
    with bits moved around to work better for a little endian (the original
    design seemingly assumed big endian). Also typically traded 1 bit of
    match length for 1 bit of literal length (literal runs typically 0..7 vs 0..3).

    Did investigate at one point whether it might have been better to trade
    1 bit on distance instead, but testing seemed to confirm taking it from
    match length as the correct choice.

    In both cases, there is a presumed positive correlation between match
    length and distance, unlike LZ4 where they are uncorrelated (distance
    always 16 bits).

    ...



    Had also experimented with bolt-on post-compressors for RP2:
    STF + AdRice:
    Can push compression similar to that of Deflate
    It tends to have an advantage for small payloads.
    But, relative gains diminish with payload size.
    Speed drops into Deflate-like areas.
    Range Coder:
    Compression increases to LZMA like areas;
    Speed decreases to LZMA like areas.

    No Huffman or ANS, but:
    Huffman would be a more complex mechanism to apply as a bolt-on post compressor, and isn't likely to see a significant compression delta.

    ANS seems very weird / convoluted;
    Extant implementations I have looked at don't seem to live up to claims
    of extreme compression at high speeds, most like both speed and
    compression that seem to fall between Huffman and Range-Coding;
    Has quirks that would pose problems for use as a simple post-compressor.


    The idea being that one first encodes as RP2 normally, and then checks
    if a post compressor could give an acceptable level of additional
    compression. This avoids wasting the speed-cost of more expensive post-encoders on data which does not significantly benefit (and entropy
    coding isn't always the win one might think it is).

    The post-compressor effectively sorts out bytes into various categories
    and runs them through the corresponding entropy context.

    Decompression could be done 2-pass, but usually faster to do a combined decoder.

    Might seem like an overly jank approach, but worked well in testing...



    STF + AdRice is used in some of my compressors:

    STF: Each time a symbol is encode it, swap it towards the front of the
    list. The symbols are encoded as their positions in the list.
    Typical swapping is with the value 7/8 or 15/16 the current index (7/8
    is better for short payloads, 15/16 or 31/32 for long payloads).
    Initial state is typically the bytes from 00 to FF in order, but
    sometimes differs for some uses. For a byte context, needs 256 bytes of storage per context for decoding (encoding typically needs 512, for a reverse-lookup table). Main premise is to turn raw bytes into something
    that Rice coding can use.

    AdRice:
    Adaptive variant of Rice coding.
    Encodes Q as a unary coded prefix, followed by K bit suffix;
    Q=Val>>K, Suf=Val&((1<<K)-1)
    Except for an escape-case, where if Q>7:
    Encode an 8-bit prefix, and raw 8-bit index.
    State update:
    Typical variant:
    Q=0, if K>0, decrement K
    Q=1: Leave K as-is
    Q>1: If K<7, increment K
    Alt variant:
    K is understood as having a 3 bit fraction.
    Update instead increments the fraction,
    so major K update happens more slowly.

    The alternate variant's update rules can help for compression in some use-cases at the expense of others. It comes at a minor speed cost in
    some cases, as the typical variant can move all of the short cases into
    a lookup table, operating more like a table-driven Huffman decoder, but
    the fractional K doesn't map well to expressing the updated K via a
    combined lookup table.

    The merit is that slowing down the K adaptation causes K to more often
    be at the optimal value, whereas with the simple case it is typically
    off by 1.


    As for Huffman, it poses a frequent problem:
    At a 15 or 16 bit symbol length, the needed lookup table to do a whole
    symbol at once does not fit in L1 cache and often has a poor L1 hit rate;
    At 12 or 13 bits, a single lookup strategy is faster, but compression
    suffers (and mostly loses the compression advantage over STF+AdRice,
    while still having higher cache pressure).

    A partial table, say the first 8 bits, with fallback for the rest, can
    work, but is also a speed penalty.
    It also poses a problem for small payloads in that filling the lookup
    table is often a significant time penalty.

    ...


    There are some claims of formats with "extreme compression at amazing
    speeds" from some companies (like, Deflate-like compression at LZ4 like speeds), but given I don't have them, I can't test anything myself.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Thu Jun 25 14:58:36 2026
    From Newsgroup: comp.arch

    On 6/25/2026 12:20 PM, MitchAlsup wrote:

    David Brown <david.brown@hesbynett.no> posted:

    On 24/06/2026 23:34, BGB wrote:
    ---------------------
    "memmove" will not fill the array above with 42. "memmove" acts as
    though it copies the source to a temporary buffer, then copies that
    temporary buffer to the destination. (If you want to fill the buffer
    with the value 42, "memset" is the function to use.)

    Act as though it copies twice is utterly unnecessary as overlapping
    memory can simply be performed back-to-front instead of front-to-back.


    Yes, this is how memmove is done in practice IME.
    Forwards copy:
    Do it end-to-front (backwards)
    Backwards copy:
    Do it front-to-end (forwards)

    For what memmove is intended to do, it works well, but is not always
    what someone needs.

    And, for whatever reason, the people developing the C standards
    seemingly didn't feel "a copy function that explicitly produces
    repeating N byte patterns in the case of self-overlap" to be a priority...


    How is your "_memlzcpy" defined that is different from that?


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Thu Jun 25 18:07:15 2026
    From Newsgroup: comp.arch

    On 6/25/2026 12:13 PM, MitchAlsup wrote:

    Andy Valencia <vandys@vsta.org> posted:

    Thomas Koenig <tkoenig@netcologne.de> writes:
    BGB <cr88192@gmail.com> schrieb:
    Usual downside it that the excessive parenthesis tend to turn into a
    usability issue.
    Ample fun has been made of this over time.

    From rec.humor.funny:

    From: jasmerb@mist.cs.orst.edu (Bryce Jasmer)
    Newsgroups: rec.humor.funny
    Subject: The Strategic Defense Initiative (SDI/Star Wars)
    Keywords: computer, funny
    Message-ID: <137457@looking.on.ca>
    Date: 23 Apr 90 10:30:08 GMT
    Sender: funnyr@looking.on.ca
    Posted: Mon Apr 23 11:30:08 1990
    Reply-Path: mist.cs.orst.edu!jasmerb

    Through some clever security hole manipulation if I have been able to
    break into all of the government's computers and acquire the Lisp code
    to SDI. Here is the last page (tail -10) of it to prove that I actually
    have the code:

    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
    ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))


    I remember the LISP on PDP-8. One could use the character ] to mean as many )s as needed to close the lambda.


    I had ended up in a custom dialect using [] and {} for other things, say:
    (+ x y) //ye olde
    [1 2 3] //sorta like #<1 2 3>
    {:x 1 :y 2} //associative dictionary/object
    {x: 1 y: 2} //basically the same

    ":x" and "x:" were both understood as keywords (like in CL), sometimes
    but not always interchangeable (both were treated the same for most
    runtime tasks, but "(eq? :x x:)" would be false, as they were considered
    as distinct sub-types of keyword, mostly differentiated by remembering
    which side the colon went on).

    Though, these would evaluate its operands, so more like:
    [1 2 3] => (vector 1 2 3)
    {x: 1 y: 2} => (object x: 1 y: 2)
    Where, say:
    (define obj {x: 1 y: 2})
    (obj :z (+ (obj :x) (obj :y)))
    Would add a "z" member holding 3.

    But, say:
    (obj foo: 1 2 3)
    Would call a 'foo' method on the object with 1, 2, 3.

    (define obj2 {x: 1 y: 2 bar: (lambda () (+ x y))})

    Or, something to this effect...

    Also, IIRC, members starting with '$' would delegate, so like:
    (define obj1 {x: 1 y: 2})
    (define obj2 {z: 3 $up: obj1})

    (obj2 :y) => 2 (via the $up) member (there could be multiple).


    Though, this language wasn't pure by any means, sort of a Scheme /
    Common Lisp / Self hybrid.


    The original BGBScript language continued on with a similar model (*1),
    just replacing "$up" with "_up_" or similar. Also the lookup process
    would track where it had been, so cycles would not explode the lookup.

    Also there was a big hash table to track object/member lookups, so often lookups could remain (moderately) fast (by the standards of that era for
    a script VM).


    *1: Well, after the disastrously slow first BGBScript VM the second
    reused the first language (and its VM) as the core (throwing the JS like syntax on top).


    Seemed like a cool/nifty idea at the time, as did using this as the
    logical basis of the entire scoping model. But, when later wanting to
    move to a static-typed core (with type-inference, etc), this stuff came
    back to bite.


    Also went between different tagref formats.

    Say: Early VM:
    (31:3): Address
    ( 2:0): Tag
    Tag:
    000: Object Reference
    100: Cons Cell
    110: Various literal value types.
    x01: Fixnum (30 bits)
    x11: Flonum (30 bits, Binary32 with 2b cut off)

    First BS VM:
    Went over to bare pointers (more C friendly);
    Crammed fixnum and flonum into 24 bit address ranges (sucked).
    Second BSVM:
    Went back to the tagrefs.
    Also a precise GC, but this was a pain.
    Third VM:
    Mostly Went back to bare pointers for objects and cons cells;
    Went back to a conservative GC.

    Later, eg:
    Went 64-bit, then ended up moving the VM over to a 64-bit format.
    (63:48): Tag Bits
    (47: 0): Bare Address
    With a tag in the HOBs:
    0000: Object/Etc
    0001: Literal values/etc
    001x: Misc (bounded pointers ATM)
    01xx: Fixnum
    10xx: Flonum
    11xx: ...



    Some may or not recognize this as the format my ISA project is using...

    But, the basic scheme itself originated when the BGBScript VM went 64-bit.

    Well, also the type-tagging notation that BGBCC uses was also partly
    shared with the BGBScript VM.

    Ironically, cons cells and cons lists still exist, sorta, but are not
    really widely used in TestKern.

    Ironically, I had also partly used this typesystem internally in the
    makeshift BASIC dialect, which in another offshoot, started to gain some Lisp-like appendages.


    But, not entirely sure that "Weird mix of Lisp and 1980s style
    unstructured BASIC" is really a direction I want to go in.

    Though, could revive a Lisp style dialect, but with C style loops:
    (while (cond...) (begin
    (if (something) (break))
    ...
    ))
    (let-for (i 0) (< i 10) (++ i) (println "Yeah " i))
    With (break) and (continue).




    Well, and maybe in certain contexts bring back 32-bit tagrefs as a way
    to save memory (though, operating within the limits of a constrained
    heap, rather than public memory, and possibly referencing any external
    objects as handles).


    Could go further (eg, 16 bits), though most non-toy examples are likely
    to either run out of addressable cons-cells, or not actually have enough
    going on to benefit from the 16-bit handles.

    Say (if 16b):
    00: Object Handle
    01: CONS Handle
    10: Fixnum
    11: Misc
    00: Symbols (4K unique symbols)
    01: Keywords
    10: Magic values
    11: ?

    ...


    Though, could make sense for a GLSL compiler, as one is not as likely to
    run out of cons cells when compiling a shader. Fully populated CONS heap
    would be 64K, vs 256K for the same number of cons cells vs the normal
    memory management.

    Then again, the number of AST nodes that BGBCC uses when compiling Doom
    isn't wildly larger than this, so it is very possible that 16K cons
    cells could be enough to compile even a fairly complex GLSL shader...

    Well, or get wacky and use 48-bit cons cells with 24-bit tagrefs (4M
    cons cells, could compile full on C programs with this), hrrm...

    Well, also C would need more than 4K unique symbols, but one does
    generally stay under a 64K symbol limit (for something in a Doom'ish
    size range).

    ...

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Stefan Monnier@monnier@iro.umontreal.ca to comp.arch on Thu Jun 25 10:33:38 2026
    From Newsgroup: comp.arch

    The glibc function ::backtrace can be called at any time, in any context.

    I guess, internally, that backtrace function can signal an exception
    after setting up an appropriate "debugger" that will collect the
    backtrace and return it to the application.

    Then there are the unix context functions that also allow access to
    resources not normally visible to an application - getcontext(2), makecontext(3) and the setjmp/sigsetjmp functions which also
    gather the thread context, including the current stack pointer.

    IIRC these don't need to *look* at the stack, they can limit their work
    to manipulating the stack pointer.


    === Stefan
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Thu Jun 25 23:46:20 2026
    From Newsgroup: comp.arch


    scott@slp53.sl.home (Scott Lurndal) posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    /**
    * Log a simulator stack traceback.
    */
    void
    c_osdep::backtrace(c_logger *lp)
    {
    int num_frames;
    void *framelist[100];
    char **strings;

    num_frames = ::backtrace(framelist, sizeof(framelist)/sizeof(framelist[0]));

    Where does ::backtrace get access to the number of preserved registers
    on the stack and where the return address is on a per subroutine basis ??

    That is: each stack frame is of a different size with return address at a different spot per subroutine.

    strings = ::backtrace_symbols(framelist, num_frames);
    if (strings == NULL) {
    lp->log("Unable to obtain simulator stack traceback: %s\n",
    strerror(errno));
    return;
    }
    for(int frame=0; frame < num_frames; frame++) {
    lp->log("[%2.2d] %s\n", frame, strings[frame]);
    }
    ::free(strings);
    }

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Thu Jun 25 20:29:42 2026
    From Newsgroup: comp.arch

    On 6/19/2026 5:09 PM, Scott Lurndal wrote:
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> writes:
    On 6/19/2026 11:59 AM, John Levine wrote:
    According to David Brown <david.brown@hesbynett.no>:
    Possibly the biggest millstone around the neck of computing
    architectures is the C language. ...

    De-facto standards are /always/ albatrosses to some extent. Things are >>>> done that way because things are done that way - processors are designed >>>> to run C (or C-model languages, if you like) because that's what
    existing code is written in, and code is written in C (or similar
    languages, or languages with a VM written in C) because that's how
    existing processors work.

    C killed off every memory model other than flat byte addressed memory.
    Pointers are sort of typed, but any real C program does stuff like this: >>>
    p = (struct foo *) malloc(42 * sizeof(struct foo));

    Fwiw, why all of the casts?

    C and C++ handle void* conversions differently. You must cast
    the malloc result to a pointer of the declared type when using C++.

    Oh well, yeah. I had C on the brain. :^o



    It doesn't hurt to add the cast in C, and may help with documenting
    the intention of the programmer who wrote the code.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Fri Jun 26 06:08:15 2026
    From Newsgroup: comp.arch

    Scott Lurndal <scott@slp53.sl.home> schrieb:

    GLIBC has a function to obtain a backtrace at a current point
    in time. This is called in the context of the thread that invokes
    the call. It requires access to the call records on the stack
    in the context of the thread (the glicb functions are backtrace(3)
    and backtrace_symbols(3)).

    /**
    * Log a simulator stack traceback.
    */
    void
    c_osdep::backtrace(c_logger *lp)

    Nit: That is not glibc code, glibc code is C (it would be strange to
    have a C++ runtime library for C...)

    The glibc code can be seen, for example, at

    https://github.com/bminor/glibc/blob/master/debug/backtrace.c
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Fri Jun 26 06:38:48 2026
    From Newsgroup: comp.arch

    MitchAlsup <user5857@newsgrouper.org.invalid> schrieb:

    scott@slp53.sl.home (Scott Lurndal) posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    /**
    * Log a simulator stack traceback.
    */
    void
    c_osdep::backtrace(c_logger *lp)
    {
    int num_frames;
    void *framelist[100];
    char **strings;

    num_frames = ::backtrace(framelist, sizeof(framelist)/sizeof(framelist[0]));

    Where does ::backtrace get access to the number of preserved registers
    on the stack and where the return address is on a per subroutine basis ??

    That is: each stack frame is of a different size with return address at a different spot per subroutine.

    There are several methods.

    Rolling back via the frame pointer is one method, which of course
    incurs overhead.

    Then ther's EH frame based stack tracing, which uses DWARF debug
    info that is also used for exception handling. To use this,
    you need to interpret DWARF opcodes. (You also need to interpret
    DWARF opcodes for exception handling. An exception will usually
    cost you thousands of cycles, which is HUGE).

    The latest and greatest bor backtrace is probably SFrame (used in
    the Linux kernel, for example). This uses a lookup table to locate
    the current function. See https://sourceware.org/binutils/wiki/sframe .

    Hmm... one other question. Would EH frame-based stack unwinding
    (which is now the standard) work with My 66000's safe stack?
    I think not, because it needs the return address, but I may
    be wrong.
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Fri Jun 26 03:24:25 2026
    From Newsgroup: comp.arch

    On 6/23/2026 5:54 PM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/22/2026 7:38 AM, Niklas Holsti wrote:
    On 2026-06-22 13:44, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:
    On 21/06/2026 20:57, MitchAlsup wrote:
    -------------
    In my case, I tended to use more conservative approaches and then only
    optimize based on what can be verified by the compiler within certain
    fundamental assumptions.

    Say:
    Pointer 1 points at a stack array in the local function;
    Pointer 2 was derived from taking the address of a global array;
    Compiler can safely assume no-alias.

    Also, if two pointers were passed into a function, can also assume they
    don't alias with a pointer to a local array;

    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming error.

    -----------------
    I am reminded of the person, apparently very religious, who some decades >>> ago posted to solicit help for reimplementing all of computing (gcc,
    GNU, et cetera) on Biblical principles, because he thought Richard
    Stallman was too atheistic and had tainted his products. I have not
    heard how that went.

    Rick...

    --------

    sorry of if this is way off base, but well...

    What about container_of, or CONTAINING_RECORD?


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Stefan Monnier@monnier@iro.umontreal.ca to comp.arch on Thu Jun 25 22:18:18 2026
    From Newsgroup: comp.arch

    But if you know that you have a "page of small integers" then you can just
    do address comparisons between them, the Franz Lisp compiler did this.

    Ah, so you're using the leading bits that correspond to that "page of
    integers" as tagbits. You don't really need BiBoP allocation to do
    that, tho.


    === Stefan
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Stefan Monnier@monnier@iro.umontreal.ca to comp.arch on Thu Jun 25 22:23:35 2026
    From Newsgroup: comp.arch

    John Levine [2026-06-25 19:19:47] wrote:
    According to MitchAlsup <user5857@newsgrouper.org.invalid>:
    John Levine <johnl@taugh.com> posted:
    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    Why not::

    memset( a, 42, 100 );

    Jeez, it's an example.

    It's an example, indeed, but it's a pretty bad one since using `memset`
    is more clear, more concise, and actually works, whereas your example
    seems very contrived.


    === Stefan
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Fri Jun 26 14:10:55 2026
    From Newsgroup: comp.arch

    On 26/06/2026 04:23, Stefan Monnier wrote:
    John Levine [2026-06-25 19:19:47] wrote:
    According to MitchAlsup <user5857@newsgrouper.org.invalid>:
    John Levine <johnl@taugh.com> posted:
    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.: >>>>
    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    Why not::

    memset( a, 42, 100 );

    Jeez, it's an example.

    It's an example, indeed, but it's a pretty bad one since using `memset`
    is more clear, more concise, and actually works, whereas your example
    seems very contrived.


    While a memset would be much better in this case (assuming that is the
    effect the author was aiming for), it is certainly the case that people
    have used memcpy() with overlapping regions and an assumption that it
    copies forward in some way. John's point that "it is easy to write code
    that is intuitively reasonable and sometimes works but isn't portable"
    is to a fair extent independent of the quick example he wrote to
    demonstrate it.

    However, the example does show how John is somewhat inaccurate - and it demonstrates how difficult things are when people write code with
    undefined behaviour.

    It is /not/ intuitively reasonable to write code like that example. But
    it /looks/ like it is reasonable. The critical issue is that it is not
    clear from the code whether the author wants the memset-like behaviour
    that some memcpy implementations would give, where a[] is filled with
    42, or if the author wants the memmove-like behaviour that many other
    memcpy implementations would give (where a[0] remains 42, a[1] gets 42, a[2..99] gets whatever was previously in a[1..98]). What those values
    were depends on any initialisation there was of the rest of a[] - if
    they were not initialised, then memmove() here would also have been UB.

    John is also a bit inaccurate in writing "sometimes works but isn't
    portable" - when you have UB in the code, it's just luck if the end
    results meet your intentions. "Non-portable" code, as I see it, is code
    that does what you want on one target or compiler, but might not do so
    for other targets or compilers. Code with UB is worse - even if your
    code "works" at the moment, apparently unconnected changes to other
    parts of your code, changes to compiler flags, small updates to your
    tools, can all give you an end result that no longer fits your
    intentions. There's nothing wrong with writing non-portable code,
    though it is best to be aware that you are doing so - people do that all
    the time. There is always something wrong with writing code with UB - unfortunately, people do that a lot too.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Fri Jun 26 15:08:22 2026
    From Newsgroup: comp.arch

    MitchAlsup wrote:

    John Levine <johnl@taugh.com> posted:

    According to David Brown <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined. It is debatable as to whether the rules in >>> the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    Why not::

    memset( a, 42, 100 );


    In the case of a single repeating byte, memset is of course optimal, but
    the same LZ4 encoding is used to encode any repeating pattern, of
    lengths from 1 and up. There is no indexed memset where the pattern is
    of arbitrary length.

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Fri Jun 26 15:50:49 2026
    From Newsgroup: comp.arch

    On 26/06/2026 15:08, Terje Mathisen wrote:
    MitchAlsup wrote:

    John Levine <johnl@taugh.com> posted:

    According to David Brown-a <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish.-a Actually, by default gcc assumes (i.e., it does not prove) >>>>> that pointers to different types (except char) do not point to the
    same address.-a One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined.-a It is debatable as to whether the
    rules in
    the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write
    code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    -a-a-a-achar a[100];

    -a-a-a-aa[0] = 42;
    -a-a-a-amemcpy(a+1, a, 99);

    Why not::

    -a-a-a-a-a-a-a memset( a, 42, 100 );


    In the case of a single repeating byte, memset is of course optimal, but
    the same LZ4 encoding is used to encode any repeating pattern, of
    lengths from 1 and up. There is no indexed memset where the pattern is
    of arbitrary length.


    I don't think memset is necessarily "optimal", because the optimal
    solution will depend on the number of bytes to fill, and possibly
    alignments, and details of the exact processor. A particular memset implementation could be close to optimal for large blocks, where it is
    worth picking the best algorithm at runtime. And a compiler could pick
    the algorithm details at compile time if it knows the size of the target block. "Optimal" is a strong word.

    I would think that the kind of copying you need for LZ4 is quite
    specialised for that task - a function to do that belongs in LZ4 implementation code rather than as a standard function. Trying to use memcpy() for the task is, however, a recipe for having your name cursed
    by future maintainers!

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From EricP@ThatWouldBeTelling@thevillage.com to comp.arch on Fri Jun 26 10:11:05 2026
    From Newsgroup: comp.arch

    On 2026-Jun-26 06:24, Chris M. Thomasson wrote:
    On 6/23/2026 5:54 PM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/22/2026 7:38 AM, Niklas Holsti wrote:
    On 2026-06-22 13:44, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:
    On 21/06/2026 20:57, MitchAlsup wrote:
    -------------
    In my case, I tended to use more conservative approaches and then only
    optimize based on what can be verified by the compiler within certain
    fundamental assumptions.

    Say:
    -a-a-a Pointer 1 points at a stack array in the local function;
    -a-a-a Pointer 2 was derived from taking the address of a global array;
    -a-a-a Compiler can safely assume no-alias.

    Also, if two pointers were passed into a function, can also assume they
    don't alias with a pointer to a local array;

    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming error.

    sorry of if this is way off base, but well...

    What about container_of, or CONTAINING_RECORD?

    If that is what I think it is, where it cast from
    a pointer to a field inside a struct back to the containing struct
    by subtracting the field byte offset and changing the pointer type, irrespective of programming language that mechanism has been used
    by operating systems at least since RSX days.
    It is a compact way of having structs linked to many other structures.

    That macro is just a variant of the mechanism for C.
    The method is used by WinNT and Linux, and I believe also by the BSD's.

    GCC has a compile option, no_strict_alias or something, that anyone
    using it and doing "illegal" pointer casting must use.
    In Windows land, pointer casting at least used to be Microsoft's
    recommended method and is supported by their compiler because
    they use it too, extensively.

    I have used it when I had complex multiple linkages between data structures. Say an object is in multiple double linked lists and an index tree and I
    need to cast from a pointer to a list link field back to the object
    containing that link field. I also often put a validity check marker for
    each object type at the start of the container and Assert its correctness.
    The marker is zeroed when the container is destroyed to catch
    any dangling references.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.arch on Fri Jun 26 14:21:10 2026
    From Newsgroup: comp.arch

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:

    scott@slp53.sl.home (Scott Lurndal) posted:

    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    /**
    * Log a simulator stack traceback.
    */
    void
    c_osdep::backtrace(c_logger *lp)
    {
    int num_frames;
    void *framelist[100];
    char **strings;

    num_frames = ::backtrace(framelist, sizeof(framelist)/sizeof(framelist[0]));

    Where does ::backtrace get access to the number of preserved registers
    on the stack and where the return address is on a per subroutine basis ??

    https://elixir.bootlin.com/glibc/glibc-2.43.9000/A/ident/backtrace

    It is processor dependent, of course.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.arch on Fri Jun 26 14:22:29 2026
    From Newsgroup: comp.arch

    Thomas Koenig <tkoenig@netcologne.de> writes:
    Scott Lurndal <scott@slp53.sl.home> schrieb:

    GLIBC has a function to obtain a backtrace at a current point
    in time. This is called in the context of the thread that invokes
    the call. It requires access to the call records on the stack
    in the context of the thread (the glicb functions are backtrace(3)
    and backtrace_symbols(3)).

    /**
    * Log a simulator stack traceback.
    */
    void
    c_osdep::backtrace(c_logger *lp)

    Nit: That is not glibc code, glibc code is C (it would be strange to
    have a C++ runtime library for C...)

    Indeed it is C++ code calling a GLIBC function.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Fri Jun 26 16:15:06 2026
    From Newsgroup: comp.arch

    David Brown <david.brown@hesbynett.no> writes:
    it is certainly the case that people
    have used memcpy() with overlapping regions and an assumption that it
    copies forward in some way.

    More precisely, in 2010 there was a big flamewar because a newer glibc
    used backwards stride on some processors for some combinations of
    source and destination addresses, and this broke a pre-existing binary
    (of a Flash player IIRC). The "solution" was to use memmove for
    memcpy for existing binaries, and use the processor-dependent memcpy
    for new binaries. I heard no complaints about the solution for
    existing binaries.

    This shows that no binaries that link to glibc assumed that dest can
    overlap source with dest>src, and get some kind of replicating
    behaviour (probably because glibc stopped using byte-by-byte copying
    much earlier, if it ever had it at all).

    What the Flash player apparently used is operlapping memcpy with
    dest<src. It worked like memmove before the glibc release that caused
    the flame war, and actually used memmove once the solution was
    implemented.

    A better solution might have been to implement memcpy on the funny
    processors as follows:


    if (prefer_forward_stride(dest,src) || (((uintptr_t)src)-((uintptr_t)dest))<n )
    return memcpy_forward_stride(dest, src, n);
    else
    return memcpy_backward_stride(dest, src, n);

    This would have covered the Flash player usage (and any like it). Of
    course, memmove is only slightly more expensive to implement (you also
    have to cover the case where src<dest<src+n).

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From EricP@ThatWouldBeTelling@thevillage.com to comp.arch on Fri Jun 26 13:51:01 2026
    From Newsgroup: comp.arch

    On 2026-Jun-26 12:15, Anton Ertl wrote:
    David Brown <david.brown@hesbynett.no> writes:
    it is certainly the case that people
    have used memcpy() with overlapping regions and an assumption that it
    copies forward in some way.

    More precisely, in 2010 there was a big flamewar because a newer glibc
    used backwards stride on some processors for some combinations of
    source and destination addresses, and this broke a pre-existing binary
    (of a Flash player IIRC).

    Unbroken parts of Flash player existed?


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Fri Jun 26 11:33:21 2026
    From Newsgroup: comp.arch

    On 6/25/2026 10:14 AM, MitchAlsup wrote:

    John Levine <johnl@taugh.com> posted:

    According to David Brown <david.brown@hesbynett.no>:
    On 24/06/2026 07:48, Anton Ertl wrote:
    MitchAlsup <user5857@newsgrouper.org.invalid> writes:
    C requires the compiler to prove that the pointers cannot alias.

    I wish. Actually, by default gcc assumes (i.e., it does not prove)
    that pointers to different types (except char) do not point to the
    same address. One has to turn that off with -fno-strict-aliasing.
    Other C compilers use the same assumption.

    That's the way C is defined. It is debatable as to whether the rules in >>> the C standard are ideal ...

    One of the less fortunate things about C is that it is easy to write code that
    is intuitively reasonable and sometimes works but isn't portable, e.g.:

    char a[100];

    a[0] = 42;
    memcpy(a+1, a, 99);

    Why not::

    memset( a, 42, 100 );

    ?????

    Or something akin to, pesudo code:

    struct buffer
    {
    char a[100];
    };

    struct buffer b0 = { '\0' };

    or

    struct buffer b0 = { };

    Try to hold the flames for a little while. Typed it in as is from
    memory. ;^)



    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't. This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Fri Jun 26 11:36:30 2026
    From Newsgroup: comp.arch

    On 6/26/2026 7:11 AM, EricP wrote:
    On 2026-Jun-26 06:24, Chris M. Thomasson wrote:
    On 6/23/2026 5:54 PM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/22/2026 7:38 AM, Niklas Holsti wrote:
    On 2026-06-22 13:44, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:
    On 21/06/2026 20:57, MitchAlsup wrote:
    -------------
    In my case, I tended to use more conservative approaches and then only >>>> optimize based on what can be verified by the compiler within certain
    fundamental assumptions.

    Say:
    -a-a-a Pointer 1 points at a stack array in the local function;
    -a-a-a Pointer 2 was derived from taking the address of a global array; >>>> -a-a-a Compiler can safely assume no-alias.

    Also, if two pointers were passed into a function, can also assume they >>>> don't alias with a pointer to a local array;

    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming
    error.

    sorry of if this is way off base, but well...

    What about container_of, or CONTAINING_RECORD?

    If that is what I think it is, where it cast from
    a pointer to a field inside a struct back to the containing struct
    by subtracting the field byte offset and changing the pointer type, irrespective of programming language that mechanism has been used
    by operating systems at least since RSX days.
    It is a compact way of having structs linked to many other structures.

    That macro is just a variant of the mechanism for C.
    The method is used by WinNT and Linux, and I believe also by the BSD's.

    GCC has a compile option, no_strict_alias or something, that anyone
    using it and doing "illegal" pointer casting must use.
    In Windows land, pointer casting at least used to be Microsoft's
    recommended method and is supported by their compiler because
    they use it too, extensively.

    I have used it when I had complex multiple linkages between data
    structures.
    Say an object is in multiple double linked lists and an index tree and I
    need to cast from a pointer to a list link field back to the object containing that link field. I also often put a validity check marker for
    each object type at the start of the container and Assert its correctness. The marker is zeroed when the container is destroyed to catch
    any dangling references.



    Yup. You got it and basically had to use it the same way I have in the
    past. Its really cool. Also, check this shit out:

    #define RALLOC_ALIGN_OF(mp_type) \
    offsetof( \
    struct { \
    char pad_RALLOC_ALIGN_OF; \
    mp_type type_RALLOC_ALIGN_OF; \
    }, \
    type_RALLOC_ALIGN_OF \
    )

    ;^D
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Fri Jun 26 11:40:07 2026
    From Newsgroup: comp.arch

    On 6/26/2026 11:36 AM, Chris M. Thomasson wrote:
    On 6/26/2026 7:11 AM, EricP wrote:
    On 2026-Jun-26 06:24, Chris M. Thomasson wrote:
    On 6/23/2026 5:54 PM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/22/2026 7:38 AM, Niklas Holsti wrote:
    On 2026-06-22 13:44, Thomas Koenig wrote:
    Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
    On 2026-06-21 22:15, David Brown wrote:
    On 21/06/2026 20:57, MitchAlsup wrote:
    -------------
    In my case, I tended to use more conservative approaches and then only >>>>> optimize based on what can be verified by the compiler within certain >>>>> fundamental assumptions.

    Say:
    -a-a-a Pointer 1 points at a stack array in the local function;
    -a-a-a Pointer 2 was derived from taking the address of a global array; >>>>> -a-a-a Compiler can safely assume no-alias.

    Also, if two pointers were passed into a function, can also assume
    they
    don't alias with a pointer to a local array;

    C requires the compiler to prove that the pointers cannot alias.
    Fortran specifies that if the 2 argument alias, it is a programming
    error.

    sorry of if this is way off base, but well...

    What about container_of, or CONTAINING_RECORD?

    If that is what I think it is, where it cast from
    a pointer to a field inside a struct back to the containing struct
    by subtracting the field byte offset and changing the pointer type,
    irrespective of programming language that mechanism has been used
    by operating systems at least since RSX days.
    It is a compact way of having structs linked to many other structures.

    That macro is just a variant of the mechanism for C.
    The method is used by WinNT and Linux, and I believe also by the BSD's.

    GCC has a compile option, no_strict_alias or something, that anyone
    using it and doing "illegal" pointer casting must use.
    In Windows land, pointer casting at least used to be Microsoft's
    recommended method and is supported by their compiler because
    they use it too, extensively.

    I have used it when I had complex multiple linkages between data
    structures.
    Say an object is in multiple double linked lists and an index tree and I
    need to cast from a pointer to a list link field back to the object
    containing that link field. I also often put a validity check marker for
    each object type at the start of the container and Assert its
    correctness.
    The marker is zeroed when the container is destroyed to catch
    any dangling references.



    Yup. You got it and basically had to use it the same way I have in the
    past. Its really cool. Also, check this shit out:

    #define RALLOC_ALIGN_OF(mp_type) \
    -a offsetof( \
    -a-a-a struct { \
    -a-a-a-a-a char pad_RALLOC_ALIGN_OF; \
    -a-a-a-a-a mp_type type_RALLOC_ALIGN_OF; \
    -a-a-a }, \
    -a-a-a type_RALLOC_ALIGN_OF \
    -a )

    ;^D

    fwiw, https://groups.google.com/g/comp.lang.c/c/7oaJFWKVCTw/m/sSWYU9BUS_QJ

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From John Levine@johnl@taugh.com to comp.arch on Fri Jun 26 18:47:05 2026
    From Newsgroup: comp.arch

    According to Scott Lurndal <slp53@pacbell.net>:
    a[0] = 42;
    memcpy(a+1, a, 99);

    A naive byte copy will fill a[] with 42, a more typical version that
    moves larger blocks won't. This example is really obvious (it's
    why there's also memmove()) but there's plenty of more subtle ones.

    The burroughs B3500 and medium systems successors, which is a >memory-to-memory architecture had a number of move instructions,
    several of which had architecturally defined semantics for
    overlapping source and destination fields, which included
    functionality similar to that you describe above. ...

    So does S/360 and its sucessors. The original 360 had MVC which takes two addresses and a length, and the spec says it acts as if it copies a byte at a time, so the a -> a+1 hack is the usual way to set a block to a specific value. S/370 added MOVE LONG with separate lengths for the two operands and an explicit
    padding byte, so you fill memory with the padding byte by setting the source length to zero. If the operands have "destructive overlap", it sets a condition
    code and moves nothing. They also added MOVE INVERSE which reverses a byte string
    and has explicitly undefined results if the operands overlap by more than one byte.

    S/390 added the confusing;y named MOVE LONG UNICODE which moves and pads pairs of bytes. (That works OK for UTF-16, not any other Unicode encoding) It also has
    MOVE PAGE which blats a 4K page at a time, MOVE STRING which does a C-style copy
    up to a delimiter byte, and MOVE LONG EXTENDED which puts the padding byte in an operand (typically immediate in the instruction) rather than a register and doesn't check for destructive overlap, you get what you get.

    z/Series adds MOVE RIGHT TO LEFT which is similar to MVC except it's specified to move bytes right to left rather than left to right, with the example being to
    add a hole in the middle of an array.

    Copying strings is surprisingly complicated.
    --
    Regards,
    John Levine, johnl@taugh.com, Primary Perpetrator of "The Internet for Dummies",
    Please consider the environment before reading this e-mail. https://jl.ly
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Fri Jun 26 12:13:30 2026
    From Newsgroup: comp.arch

    On 6/20/2026 8:27 AM, Terje Mathisen wrote:
    Chris M. Thomasson wrote:
    On 6/19/2026 7:16 AM, Terje Mathisen wrote:
    [...]
    The Mill is probably the closest to Cheri that is still in active
    development.

    How close are you guys to making a Mill processor?

    I don't know, I'm just the FP emulation guy in the project. :-)

    Too cool for school. :^) I am wishing you all luck!
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Fri Jun 26 22:46:51 2026
    From Newsgroup: comp.arch

    David Brown wrote:
    On 26/06/2026 15:08, Terje Mathisen wrote:
    In the case of a single repeating byte, memset is of course optimal,
    but the same LZ4 encoding is used to encode any repeating pattern, of >> lengths from 1 and up. There is no indexed memset where the pattern is
    of arbitrary length.


    I don't think memset is necessarily "optimal", because the optimal
    solution will depend on the number of bytes to fill, and possibly alignments, and details of the exact processor.-a A particular memset implementation could be close to optimal for large blocks, where it is > worth picking the best algorithm at runtime.-a And a compiler could pick
    the algorithm details at compile time if it knows the size of the target block.-a "Optimal" is a strong word.

    I would think that the kind of copying you need for LZ4 is quite
    specialised for that task - a function to do that belongs in LZ4 implementation code rather than as a standard function.-a Trying to use memcpy() for the task is, however, a recipe for having your name cursed
    by future maintainers!

    Which is of course the main reason I wrote the SSE/AVX version to beat
    both the compiler autovectorizer and Google's reference code. :-)
    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Sat Jun 27 08:32:21 2026
    From Newsgroup: comp.arch

    EricP <ThatWouldBeTelling@thevillage.com> schrieb:
    On 2026-Jun-26 12:15, Anton Ertl wrote:
    David Brown <david.brown@hesbynett.no> writes:
    it is certainly the case that people
    have used memcpy() with overlapping regions and an assumption that it
    copies forward in some way.

    More precisely, in 2010 there was a big flamewar because a newer glibc
    used backwards stride on some processors for some combinations of
    source and destination addresses, and this broke a pre-existing binary
    (of a Flash player IIRC).

    Unbroken parts of Flash player existed?

    Granted (maybe the first drafts which didn't do anyting yet, the sort of

    int main()
    {
    return 0;
    }

    program, it didn't have any serious bugs yet, at least not in the
    functionality that was implemented up to that time. But remember
    IEFBR14...)

    But this shows the recently discussed problem with shared libraries.
    When they change things, programs may break, even if the change
    is allowed according to the applicable standards and documentation.
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Sat Jun 27 11:02:04 2026
    From Newsgroup: comp.arch

    On 26/06/2026 18:15, Anton Ertl wrote:
    David Brown <david.brown@hesbynett.no> writes:
    it is certainly the case that people
    have used memcpy() with overlapping regions and an assumption that it
    copies forward in some way.

    More precisely, in 2010 there was a big flamewar because a newer glibc
    used backwards stride on some processors for some combinations of
    source and destination addresses, and this broke a pre-existing binary
    (of a Flash player IIRC). The "solution" was to use memmove for
    memcpy for existing binaries, and use the processor-dependent memcpy
    for new binaries. I heard no complaints about the solution for
    existing binaries.

    I mentioned that incident briefly, but you've given more detail. I
    expect the most common misuse of memcpy() would be in assuming it worked
    like memmove() rather than memset() (in the example given) - and since memmove() also implements the specification of memcpy(), I'd expect it
    to work fine (albeit possibly marginally slower).


    This shows that no binaries that link to glibc assumed that dest can
    overlap source with dest>src, and get some kind of replicating
    behaviour (probably because glibc stopped using byte-by-byte copying
    much earlier, if it ever had it at all).

    Or at least, no one has noticed any such problem, or complained about it loudly enough for it to be heard. Maybe the people wanted memcpy() to duplicate 42 realised it was their mistake when glibc memcpy() changed,
    and quietly fixed their code. If it had been a common problem, or
    difficult to fix in existing code, I'm sure we'd have heard about it -
    but that does not mean that /no/ code made this assumption.


    What the Flash player apparently used is operlapping memcpy with
    dest<src. It worked like memmove before the glibc release that caused
    the flame war, and actually used memmove once the solution was
    implemented.

    A better solution might have been to implement memcpy on the funny
    processors as follows:


    if (prefer_forward_stride(dest,src) || (((uintptr_t)src)-((uintptr_t)dest))<n )
    return memcpy_forward_stride(dest, src, n);
    else
    return memcpy_backward_stride(dest, src, n);

    This would have covered the Flash player usage (and any like it). Of
    course, memmove is only slightly more expensive to implement (you also
    have to cover the case where src<dest<src+n).


    Yes.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Sat Jun 27 09:00:47 2026
    From Newsgroup: comp.arch

    Thomas Koenig <tkoenig@netcologne.de> writes:
    But this shows the recently discussed problem with shared libraries.
    When they change things, programs may break, even if the change
    is allowed according to the applicable standards and documentation.

    Anyone who treats an incomplete specification as a license to change
    behaviour is probably in violation of Hyrum's law, and, to use the perpetrator's (in the memcpy case) own words, "should never have been
    allowed to touch a keyboard"[1]. The memcpy episode is just one
    example.

    [1] <https://sourceware.org/bugzilla/show_bug.cgi?id=12518#c4> Actually,
    I think that touching a keyboard and even performing such programming
    mistakes is acceptable for someone during their education. For the
    maintainer of a widely used library, it's not.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Sat Jun 27 20:04:25 2026
    From Newsgroup: comp.arch

    On 27/06/2026 10:32, Thomas Koenig wrote:
    EricP <ThatWouldBeTelling@thevillage.com> schrieb:
    On 2026-Jun-26 12:15, Anton Ertl wrote:
    David Brown <david.brown@hesbynett.no> writes:
    it is certainly the case that people
    have used memcpy() with overlapping regions and an assumption that it
    copies forward in some way.

    More precisely, in 2010 there was a big flamewar because a newer glibc
    used backwards stride on some processors for some combinations of
    source and destination addresses, and this broke a pre-existing binary
    (of a Flash player IIRC).

    Unbroken parts of Flash player existed?

    Granted (maybe the first drafts which didn't do anyting yet, the sort of

    int main()
    {
    return 0;
    }

    program, it didn't have any serious bugs yet, at least not in the functionality that was implemented up to that time. But remember
    IEFBR14...)

    But this shows the recently discussed problem with shared libraries.
    When they change things, programs may break, even if the change
    is allowed according to the applicable standards and documentation.


    That is what - in an ideal world, at least - functionality should be
    specified clearly, people should read those specifications, and should
    not make assumptions outside those specifications. The whole point of a specification, such as the C standards or documentation for a shared
    library, is to provide a contract point between the user and the
    implementer. Implementations change, and they have to be able to do so
    - fixing bugs, improving performance, adding new features (weakening preconditions or strengthening postconditions), supporting different platforms, etc.

    Unfortunately, we don't live in an ideal world, and programmers
    regularly make unwarranted assumptions about things. That can be for at
    the language level - like assuming "int" is 32-bit, or that "x++;" is an atomic operation. Or it can be assuming functions are implemented in a particular way, just because they think it is "intuitive" or that old
    versions worked in a particular way.

    It's never easy for implementers to determine when they should pay most attention to giving good results to those that use their functions
    correctly, and when they should help those that have used their
    functions incorrectly avoid the consequences of their mistakes. After
    all, the people who made those mistakes are fallible humans like the
    rest of us, and the cost of the mistakes might be paid by innocent
    users. One thing is sure, however - no matter how much a function
    implementer can try to guess how people have used their function
    incorrectly and relied on some aspect of their previous implementation,
    some programmer using that function incorrectly will have done so with
    greater creativity and imagination.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Sat Jun 27 19:52:03 2026
    From Newsgroup: comp.arch

    Anton Ertl <anton@mips.complang.tuwien.ac.at> schrieb:
    Thomas Koenig <tkoenig@netcologne.de> writes:
    But this shows the recently discussed problem with shared libraries.
    When they change things, programs may break, even if the change
    is allowed according to the applicable standards and documentation.

    Anyone who treats an incomplete specification as a license to change behaviour is probably in violation of Hyrum's law,

    Not sure where you see the problem there. "Undefined behavior" in
    the C standard is something that programmers must avoid, on penalty
    of their programs doing weird thigns that they never expected.

    UNLESS they stick to a certain version of library, compiler and
    the rest of the tool chain and have tested this exhaustively.
    Unfortunately, shared libraries make this impossible.

    But I agree that Fortran's "shall" and "shall not" is clearer
    and leads to less confusion.
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Sat Jun 27 13:07:08 2026
    From Newsgroup: comp.arch

    On 6/27/2026 12:52 PM, Thomas Koenig wrote:
    Anton Ertl <anton@mips.complang.tuwien.ac.at> schrieb:
    Thomas Koenig <tkoenig@netcologne.de> writes:
    But this shows the recently discussed problem with shared libraries.
    When they change things, programs may break, even if the change
    is allowed according to the applicable standards and documentation.

    Anyone who treats an incomplete specification as a license to change
    behaviour is probably in violation of Hyrum's law,

    Not sure where you see the problem there. "Undefined behavior" in
    the C standard is something that programmers must avoid, on penalty
    of their programs doing weird thigns that they never expected.

    Undefined behavior can be defined by a system. But then your into non
    portable land.



    UNLESS they stick to a certain version of library, compiler and
    the rest of the tool chain and have tested this exhaustively.
    Unfortunately, shared libraries make this impossible.

    But I agree that Fortran's "shall" and "shall not" is clearer
    and leads to less confusion.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Sun Jun 28 07:45:51 2026
    From Newsgroup: comp.arch

    David Brown <david.brown@hesbynett.no> schrieb:

    It's never easy for implementers to determine when they should pay most attention to giving good results to those that use their functions correctly, and when they should help those that have used their
    functions incorrectly avoid the consequences of their mistakes.

    I have a clear preference there, and I believe it is shared in
    general by the gcc community.

    First, correct code should not be penalized.

    Second, the user should be given as much help as possible to find
    incorrect code. Code that violates the respective standard, be
    it undefined behavior in C or violation of a "shall" directive
    that is not a constraint in Fortran, should be noted, in order
    of preference, by

    1. Compile-time checking

    2. Low (factor 2-3) overhead checks for debugging runs (address
    sanitizer etc)

    3. High overhead checks (valgrind)

    Third, dubious code that has a correct interpretation but is very
    likely not what the user intended should be warned about.

    A classic of the last case is Fortran's

    real :: a
    read (*,*) a
    print *,a**(5/7)
    end

    which will print 1.0 as long as the input is finite.
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.arch on Sun Jun 28 11:12:20 2026
    From Newsgroup: comp.arch

    On 28/06/2026 09:45, Thomas Koenig wrote:
    David Brown <david.brown@hesbynett.no> schrieb:

    It's never easy for implementers to determine when they should pay most
    attention to giving good results to those that use their functions
    correctly, and when they should help those that have used their
    functions incorrectly avoid the consequences of their mistakes.

    I have a clear preference there, and I believe it is shared in
    general by the gcc community.

    First, correct code should not be penalized.

    Agreed.


    Second, the user should be given as much help as possible to find
    incorrect code. Code that violates the respective standard, be
    it undefined behavior in C or violation of a "shall" directive
    that is not a constraint in Fortran, should be noted, in order
    of preference, by

    1. Compile-time checking

    2. Low (factor 2-3) overhead checks for debugging runs (address
    sanitizer etc)

    3. High overhead checks (valgrind)


    Agreed.

    Third, dubious code that has a correct interpretation but is very
    likely not what the user intended should be warned about.

    A classic of the last case is Fortran's

    real :: a
    read (*,*) a
    print *,a**(5/7)
    end

    which will print 1.0 as long as the input is finite.


    Agreed. (Well, I don't know about the Fortran example - that's far
    beyond the very little I know of the language. But I agree on your principle.)

    And from my experience as a long-time gcc user, and following (and occasionally posting to) various mailing lists, I think you are correct
    that this is the general attitude in the gcc development community (and
    wider community, covering things like binutils, glibc, newlib, etc.).

    Of course gcc is never going to get everything right. There are going
    to be trade-offs, false positives, false negatives, bugs in the gcc
    code, misunderstandings of the standards, controversial interpretations
    of the standards, and so on. And there are limits to what is possible
    without unreasonable development time, unreasonable compile times, or
    trying to solve the halting problem. You are never going to please
    everyone all of the time. But my impression from the outside is that
    the gcc folk do an excellent job, and are very much aiming in what I too consider the right general direction for a development tool.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Sun Jun 28 11:14:58 2026
    From Newsgroup: comp.arch

    Thomas Koenig wrote:
    program, it didn't have any serious bugs yet, at least not in the functionality that was implemented up to that time. But remember
    IEFBR14...)

    Funnily enough, the IEFBR14 bug was repeated a couple of decades later
    in MSDOS when DUMMY.COM which simply returned to the OS without doing
    anything had the exact same issue:

    The original DUMMY.COM was one byte long, containing just a RET opcode:

    The 256-byte PSP which was loaded in front of all .COM programs
    intentionally contained CD 20h (INT 20h -> Terminate the running
    program) as the first two bytes, with a zero word pushed onto the stack,
    so that any program which used the CP/M convention that you could RET to
    the OS would work.

    Just like the original IEFBR14, this setup did not return a fixed
    ERRORLEVEL which batch scripts could use to determine if an error had happened, so for the MSDOS version the single-byte program needed to be changed to something like

    MOV AX,4700h
    INT 21h

    which would call the exit program OS function (AH=47h) with a return
    code (AL) of zero.

    Terje
    PS. Why do I still remember the x86 hex codes for all of this almost 40
    years after I last wrote 16-bit ASM code which needed it?
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Sun Jun 28 11:17:36 2026
    From Newsgroup: comp.arch

    Thomas Koenig wrote:
    David Brown <david.brown@hesbynett.no> schrieb:

    It's never easy for implementers to determine when they should pay most
    attention to giving good results to those that use their functions
    correctly, and when they should help those that have used their
    functions incorrectly avoid the consequences of their mistakes.

    I have a clear preference there, and I believe it is shared in
    general by the gcc community.

    First, correct code should not be penalized.

    Second, the user should be given as much help as possible to find
    incorrect code. Code that violates the respective standard, be
    it undefined behavior in C or violation of a "shall" directive
    that is not a constraint in Fortran, should be noted, in order
    of preference, by

    1. Compile-time checking

    2. Low (factor 2-3) overhead checks for debugging runs (address
    sanitizer etc)

    3. High overhead checks (valgrind)

    Third, dubious code that has a correct interpretation but is very
    likely not what the user intended should be warned about.

    A classic of the last case is Fortran's

    real :: a
    read (*,*) a
    print *,a**(5/7)
    end

    which will print 1.0 as long as the input is finite.

    Integer division (5/7) will always be zero, so the power operation
    returns one?

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Sun Jun 28 13:05:19 2026
    From Newsgroup: comp.arch

    John Levine <johnl@taugh.com> writes:
    S/390 added the confusing;y named MOVE LONG UNICODE which moves and pads pairs >of bytes. (That works OK for UTF-16, not any other Unicode encoding)

    As an excuse, S/390 was announced in 1990 and the first machines
    shipped in 1990. At that time, Unicode was only encoded in UCS2, if
    at all (the official Unicode 1.0 standard was only released in 1991,
    although the 16-bit-ness had been well-known for several years before
    that). UTF-8 was developed in 1992 and presented officially in
    January 1993. Unicode 2.0 came out in 1996 and specified that there
    were expected to be more code points than fit in 16 bits, and
    therefore introduced the UCS4/UTF-32, as well as the variable-width
    UTF-16 and UTF-8 encodings.

    S/390 was by far not the last system to take the "Unicode=16bit"
    route. Windows NT (released 1993), Java (released 1995), and
    JavaScript (released 1995) also went there. Systems that came out
    after Unicode 2.0 don't have that excuse, but are there any that made
    this mistake without having to be compatible with Windows NT or Java?

    Copying strings is surprisingly complicated.

    Even if you don't have the multiplicity of instructions that the S/360 descendents have. E.g., the 8086 has REP MOVSB with a direction bit
    to specify the stride direction (the 8086 and its descendants also
    support wider granularities, but REP MOVSB is probably the most
    relevant of them, because it can copy any length in bytes).

    Specificationwise REP MOVSB looks simple. And more importantly,
    unlike memcpy in C, it is fully specified.

    Concerning actual implementations and the non-overlapping case, in
    recent decades there have been CPUs where software implementations
    performed better than REP MOVSB, and CPUs where the hardware
    implementation caught up, at least in certain cases. Intel introduced "Enhanced REP MOVSB" (ERMSB) in Ivy Bridge (2012), but it is slow for
    short blocks. So in Ice Lake (2019) they introduced "Fast Short REP
    MOV" (FSRM), which is supposed to fix that. One can read about that
    in <https://sqlpey.com/assembly/rep-movsb-performance/> and how AMD's
    Zen 3 is doing at
    <https://lunnova.dev/articles/ryzen-slow-short-rep-mov/>.

    I have also played around with the Zen3 implementation for the
    overlapping case with forward stride and src<dst<src+n <2021Sep1.233440@mips.complang.tuwien.ac.at>. The following table is
    from that posting:

    | VFX64 lxf VFX32
    |orig new orig new orig new ulength
    |7474 1125 7624 1095 7807 423 1
    |3360 965 3140 933 4273 420 2
    |2367 957 2380 922 3018 411 3
    |2068 790 2147 761 2418 400 4
    |1645 779 1766 754 2083 426 5

    The numbers in the orig and new columns are cycles for copying 1000
    bytes, and ulength=dest-src, i.e., this is the pattern-replicating
    case.

    For the orig columns VFX64 and lxf use REP MOVSB with forward stride,
    while VFX32 uses (for the overlapping case) the following loop:

    ( 0804B94E 8A01 ) MOV AL, 0 [ECX]
    ( 0804B950 8D4901 ) LEA ECX, [ECX+01]
    ( 0804B953 8802 ) MOV 0 [EDX], AL
    ( 0804B955 83EB01 ) SUB EBX, 01
    ( 0804B958 8D5201 ) LEA EDX, [EDX+01]
    ( 0804B95B 75F1 ) JNZ/NE 0804B94E

    Both REP MOVSB and the loop exhibit similar performance
    characteristics, taking at least 6/ulength cycles per copied byte.
    This indicates that the microcode for the overlapping case (at least
    for cases where dest-src is small enough) performs a byte-copying loop
    like the loop implementation above, and the cycles for these small
    ulengths are determined by the store-to-load latency (Zen3 implements
    0-cycle store-to-load-forwarding in some cases <https://www.complang.tuwien.ac.at/anton/memdep/>, but that fast path
    is obviously not taken here).

    If one wants to implement the overlapping case efficiently, REP MOVSB
    is actually more complicated than MEMMOVE: You have to differentiate
    between (for forward stride) between src<dst<src+n, and the other
    cases; for the other cases, any fast implementation with forward
    stride will work (or backward stride except for dst<src<dst+n).

    For the src<dst<src+n case, one approach is to copy m=dst-src bytes,
    then copy 2m bytes from src to src+2m, then 4m bytes, ..., until the
    last copy would exceed n (and instead copy correspondingly fewer
    bytes). For this scheme you see the results in the "new" columns in
    the table above. For the primitive (non-overlapping) block copying
    CMOVE was used in the experiment above, i.e., again REP MOVSB for
    VFX64 and lxf, and a loop implementation for VFX32; however, in the non-overlapping case VFX32 copies 32 bits at a time.

    The REP MOVSB variants here are significantly slower than the
    word-copying loop, because in the non-overlapping case they REP MOVSB
    uses non-temporal stores, so the result is not in the near caches, so
    the next copying operation has to load the result from the L3 cache
    (judging by the ~160 cycles per additional CMOVE used) again.

    Another, probably better approach is to fill a number of SIMD
    registers with the pattern to be replicated, and then write that. I
    have not measured this approach, but I expect that it's the fastest
    one in many cases.

    The bottom line is that a fast memmove is less complex to implement
    than a REP MOVSB that is fast for all cases, even if the specification
    of REP MOVSB looks simpler.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Thomas Koenig@tkoenig@netcologne.de to comp.arch on Sun Jun 28 17:19:15 2026
    From Newsgroup: comp.arch

    Terje Mathisen <terje.mathisen@tmsw.no> schrieb:
    Thomas Koenig wrote:

    Third, dubious code that has a correct interpretation but is very
    likely not what the user intended should be warned about.

    A classic of the last case is Fortran's

    real :: a
    read (*,*) a
    print *,a**(5/7)
    end

    which will print 1.0 as long as the input is finite.

    Integer division (5/7) will always be zero, so the power operation
    returns one?

    Exactly.

    It's subtle enough so gfortran (well, me) put in a warning:

    $ gfortran -Wall exp.f90
    exp.f90:3:16:

    3 | print *,a**(5/7)
    | 1
    Warning: Integer division truncated to constant '0' at (1) [-Winteger-division]

    Another trap I fell into in my student days was 10**n for values
    of n which were in a loop with a range which had both positive
    and negative values...
    --
    This USENET posting was made without artificial intelligence,
    artificial impertinence, artificial arrogance, artificial stupidity,
    artificial flavorings or artificial colorants.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Sun Jun 28 20:11:19 2026
    From Newsgroup: comp.arch


    Thomas Koenig <tkoenig@netcologne.de> posted:

    David Brown <david.brown@hesbynett.no> schrieb:

    It's never easy for implementers to determine when they should pay most attention to giving good results to those that use their functions correctly, and when they should help those that have used their
    functions incorrectly avoid the consequences of their mistakes.

    I have a clear preference there, and I believe it is shared in
    general by the gcc community.

    First, correct code should not be penalized.

    Agreed.

    Second, the user should be given as much help as possible to find
    incorrect code. Code that violates the respective standard, be
    it undefined behavior in C or violation of a "shall" directive
    that is not a constraint in Fortran, should be noted, in order
    of preference, by

    1. Compile-time checking

    2. Low (factor 2-3) overhead checks for debugging runs (address
    sanitizer etc)

    3. High overhead checks (valgrind)

    Third, dubious code that has a correct interpretation but is very
    likely not what the user intended should be warned about.

    A classic of the last case is Fortran's

    real :: a
    read (*,*) a
    print *,a**(5/7)
    end

    which will print 1.0 as long as the input is finite.

    WATFIV would give a compile time warning for (5/7) being 0 instead of 5.0/7.0--and that was 1971.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Sun Jun 28 20:15:03 2026
    From Newsgroup: comp.arch


    Terje Mathisen <terje.mathisen@tmsw.no> posted:

    Thomas Koenig wrote:
    program, it didn't have any serious bugs yet, at least not in the functionality that was implemented up to that time. But remember IEFBR14...)

    Funnily enough, the IEFBR14 bug was repeated a couple of decades later
    in MSDOS when DUMMY.COM which simply returned to the OS without doing anything had the exact same issue:

    The original DUMMY.COM was one byte long, containing just a RET opcode:

    The 256-byte PSP which was loaded in front of all .COM programs intentionally contained CD 20h (INT 20h -> Terminate the running
    program) as the first two bytes, with a zero word pushed onto the stack,
    so that any program which used the CP/M convention that you could RET to
    the OS would work.

    Just like the original IEFBR14, this setup did not return a fixed
    ERRORLEVEL which batch scripts could use to determine if an error had happened, so for the MSDOS version the single-byte program needed to be changed to something like

    MOV AX,4700h
    INT 21h

    which would call the exit program OS function (AH=47h) with a return
    code (AL) of zero.

    Terje
    PS. Why do I still remember the x86 hex codes for all of this almost 40 years after I last wrote 16-bit ASM code which needed it?

    It got burned into your retina--like the 9 step PDP-11 startup sequence
    you "punched" into the front panel got burned into mine.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Mon Jun 29 00:24:10 2026
    From Newsgroup: comp.arch


    Thomas Koenig <tkoenig@netcologne.de> posted:

    Terje Mathisen <terje.mathisen@tmsw.no> schrieb:
    Thomas Koenig wrote:

    Third, dubious code that has a correct interpretation but is very
    likely not what the user intended should be warned about.

    A classic of the last case is Fortran's

    real :: a
    read (*,*) a
    print *,a**(5/7)
    end

    which will print 1.0 as long as the input is finite.

    Integer division (5/7) will always be zero, so the power operation
    returns one?

    Exactly.

    It's subtle enough so gfortran (well, me) put in a warning:

    $ gfortran -Wall exp.f90
    exp.f90:3:16:

    3 | print *,a**(5/7)
    | 1
    Warning: Integer division truncated to constant '0' at (1) [-Winteger-division]

    Another trap I fell into in my student days was 10**n for values
    of n which were in a loop with a range which had both positive
    and negative values...

    Showing that floating-point is not real!
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Mon Jun 29 22:54:12 2026
    From Newsgroup: comp.arch

    Anton Ertl wrote:
    The REP MOVSB variants here are significantly slower than the
    word-copying loop, because in the non-overlapping case they REP MOVSB
    uses non-temporal stores, so the result is not in the near caches, so
    the next copying operation has to load the result from the L3 cache
    (judging by the ~160 cycles per additional CMOVE used) again.

    Another, probably better approach is to fill a number of SIMD
    registers with the pattern to be replicated, and then write that. I
    have not measured this approach, but I expect that it's the fastest
    one in many cases.

    This is exactly what I found when I set out to beat Google's LZ4 implementation, the most costly part was the almost 1KB of patterns
    which I used to do that initial filling of 32 bytes of repeated byte
    strings.

    The bottom line is that a fast memmove is less complex to implement
    than a REP MOVSB that is fast for all cases, even if the specification
    of REP MOVSB looks simpler.

    In order to handle the LZ4 style repeated copies of 1-n bytes, up to
    length m, you pretty much have to have a hardware circuit which
    recognizes the overlapping target with an overlap less or equal to 16 or
    32 bytes, then fills an internal cache line sized buffer with the corresponding pattern, before doing a number of cache lines stores with
    a final partial line.

    Obviously doable at 1 or 2 cycles of startup overhead, hopefully
    including any partial cache line in order to fix target alignment, then
    0-to-N stores of the buffer cache line and a final partial store?

    Get Mitch to design it and it would run at one cycle per cache line,
    plus 2-3 cycles for startup and tail, but it might be quite expensive in
    gates for a very specialized circuit!

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Tue Jun 30 16:00:53 2026
    From Newsgroup: comp.arch

    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    In order to handle the LZ4 style repeated copies of 1-n bytes, up to
    length m, you pretty much have to have a hardware circuit which
    recognizes the overlapping target with an overlap less or equal to 16 or
    32 bytes, then fills an internal cache line sized buffer with the >corresponding pattern, before doing a number of cache lines stores with
    a final partial line.

    You can use VPERMB to replicate the pattern in a ZMM register. With
    just one ZMM register, you can put in patterns up to 64 bytes, and
    store floor(64/n)*n bytes at a time. You would need a table of
    indices (each 64 bytes) and a table of strides (each 1 byte) for
    lengths up to 32 (for pattern length 33-64 there is no replication in
    the register and the store stride is the pattern length).

    With more ZMM registers, longer patterns can be used with that
    approach, and even for shorter patterns, one may be able to waste
    less. E.g., with a pattern length of 33 and one register, only 52% of
    the capacity is used; with two registers, 77%; with 3 registers, 86%,
    with 4, 90%, etc. But you also need more storage space for the
    patterns.

    For the final partial store, you use the store with AVX-512's mask
    feature.

    Obviously doable at 1 or 2 cycles of startup overhead,

    My impression (based on REP MOVSB performance for non-overlapping
    copies 10 years ago) is that the minimal cost of such microcode assist
    things was 12 cycles or so. I'll have to make measurements with newer
    CPUs to see if there has been any improvement there.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Tue Jun 30 20:57:06 2026
    From Newsgroup: comp.arch

    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    In order to handle the LZ4 style repeated copies of 1-n bytes, up to
    length m, you pretty much have to have a hardware circuit which
    recognizes the overlapping target with an overlap less or equal to 16 or
    32 bytes, then fills an internal cache line sized buffer with the
    corresponding pattern, before doing a number of cache lines stores with
    a final partial line.

    You can use VPERMB to replicate the pattern in a ZMM register. With
    just one ZMM register, you can put in patterns up to 64 bytes, and

    Obviously correct, but that requires 31 entries of 64 bytes each just
    for the permutation table: Blowing 2 kB on this single function is
    unlikely to be a big win, much better to have a hardware assist to do it
    for you.

    store floor(64/n)*n bytes at a time. You would need a table of
    indices (each 64 bytes) and a table of strides (each 1 byte) for
    lengths up to 32 (for pattern length 33-64 there is no replication in
    the register and the store stride is the pattern length).

    It might be possible to generate the store pattern on the fly, at least
    for patterns of length greater than 4 or 8: Start by loading the next 64
    bytes and save it to a 128-byte buffer, then overwrite from where the
    pattern should repeat, extending a 10-byte pattern to 20 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...
    repeat once more to get 40 bytes 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...

    which is the maximum repeat length for a 64-byte store, at a cost of

    The initial load (always in $L1 cache), unaligned load

    Store to temp buffer, aligned store
    Store again at pattern length offset, unaligned store

    load from temp buffer
    store again at double pattern length offset

    load final pattern from the temp buffer

    This looks like 3 loads and 3 stores, most of them dependent upon the
    previous so at least 6 clock cycles?

    With more ZMM registers, longer patterns can be used with that
    approach, and even for shorter patterns, one may be able to waste
    less. E.g., with a pattern length of 33 and one register, only 52% of
    the capacity is used; with two registers, 77%; with 3 registers, 86%,
    with 4, 90%, etc. But you also need more storage space for the
    patterns.

    For the final partial store, you use the store with AVX-512's mask
    feature.

    Obviously doable at 1 or 2 cycles of startup overhead,

    My impression (based on REP MOVSB performance for non-overlapping
    copies 10 years ago) is that the minimal cost of such microcode assist
    things was 12 cycles or so. I'll have to make measurements with newer
    CPUs to see if there has been any improvement there.

    No, no! No microcode, this would need to be a proper hardwired function
    unit. More or less like the difference between classic 40-80 cycle FDIV
    and the latest AMD which does it in 7-10.

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Tue Jun 30 16:27:57 2026
    From Newsgroup: comp.arch

    On 6/26/2026 3:46 PM, Terje Mathisen wrote:
    David Brown wrote:
    On 26/06/2026 15:08, Terje Mathisen wrote:
    In the case of a single repeating byte, memset is of course optimal,
    but the same LZ4 encoding is used to encode any repeating pattern, of
    lengths from 1 and up. There is no indexed memset where the pattern
    is of arbitrary length.


    I don't think memset is necessarily "optimal", because the optimal
    solution will depend on the number of bytes to fill, and possibly
    alignments, and details of the exact processor.-a A particular memset
    implementation could be close to optimal for large blocks, where it is
    worth picking the best algorithm at runtime.-a And a compiler could
    pick the algorithm details at compile time if it knows the size of the
    target block.-a "Optimal" is a strong word.

    I would think that the kind of copying you need for LZ4 is quite
    specialised for that task - a function to do that belongs in LZ4
    implementation code rather than as a standard function.-a Trying to use
    memcpy() for the task is, however, a recipe for having your name
    cursed by future maintainers!

    Which is of course the main reason I wrote the SSE/AVX version to beat
    both the compiler autovectorizer and Google's reference code. :-)


    In my case, it was a library extension, hence the "_" prefix (say, sorta
    like "_msize()" and friends in MSVCRT).


    But, yeah, personally I found it commonly needed enough to where I felt justified in adding a library function for it.

    But, yeah:
    LZ4, RP2, Deflate, etc, can all use it...

    So, not really something that is only relevant to LZ4.

    Sometimes it has other uses though, such as using it as a way to
    flood-fill a raster image with a single color (can be faster than using
    a generic "for()" loop for this task).


    Though, one could maybe make a case for adding, say:
    _memset_2b(void *dest, uint16_t size_t n);
    _memset_4b(void *dest, uint32_t size_t n);
    _memset_8b(void *dest, uint64_t size_t n);

    As another way to express the same idea of flood-filling memory (and
    slightly different for the compiler to generate something inline without needing to pattern-match the arguments).


    Terje


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.arch on Tue Jun 30 22:02:06 2026
    From Newsgroup: comp.arch

    On Sat, 20 Jun 2026 01:01:29 GMT, MitchAlsup wrote:

    ... but C++ has new and new is not compatible with area.

    C++ lets the rCLnewrCY operator delegate to a type-specific allocator.
    Besides which, you can replace the global allocator with a custom one.
    So I donrCOt see why you canrCOt have mark/release-style allocation
    (which is what it was called in the first Pascal compiler I used),
    and make it selective, if you want.

    <https://cppreference.com/cpp/memory/new/operator_new> <https://cppreference.com/cpp/memory/new/set_new_handler>

    Algol was ruined with its parameter passing in 'thunks' and strict
    1-file compilation.

    Think of rCLthunksrCY as just anonymous callback functions which are not allowed to have arguments. Algol-68 even made this equivalence
    explicit through its rCLproceduringrCY coercion.

    Nowadays we call them rCLlambdasrCY, and we allow them to have arguments
    if you want. So the idea isnrCOt dead, itrCOs just been rehabilitated.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Tue Jun 30 22:45:35 2026
    From Newsgroup: comp.arch


    BGB <cr88192@gmail.com> posted:

    On 6/26/2026 3:46 PM, Terje Mathisen wrote:
    David Brown wrote:
    On 26/06/2026 15:08, Terje Mathisen wrote:
    In the case of a single repeating byte, memset is of course optimal,
    but the same LZ4 encoding is used to encode any repeating pattern, of >>> lengths from 1 and up. There is no indexed memset where the pattern
    is of arbitrary length.


    I don't think memset is necessarily "optimal", because the optimal
    solution will depend on the number of bytes to fill, and possibly
    alignments, and details of the exact processor.-a A particular memset
    implementation could be close to optimal for large blocks, where it is
    worth picking the best algorithm at runtime.-a And a compiler could
    pick the algorithm details at compile time if it knows the size of the
    target block.-a "Optimal" is a strong word.

    I would think that the kind of copying you need for LZ4 is quite
    specialised for that task - a function to do that belongs in LZ4
    implementation code rather than as a standard function.-a Trying to use >> memcpy() for the task is, however, a recipe for having your name
    cursed by future maintainers!

    Which is of course the main reason I wrote the SSE/AVX version to beat both the compiler autovectorizer and Google's reference code. :-)


    In my case, it was a library extension, hence the "_" prefix (say, sorta like "_msize()" and friends in MSVCRT).


    But, yeah, personally I found it commonly needed enough to where I felt justified in adding a library function for it.

    I found it common enough that they became instructions.

    But, yeah:
    LZ4, RP2, Deflate, etc, can all use it...

    So, not really something that is only relevant to LZ4.

    Sometimes it has other uses though, such as using it as a way to
    flood-fill a raster image with a single color (can be faster than using
    a generic "for()" loop for this task).


    Though, one could maybe make a case for adding, say:
    _memset_2b(void *dest, uint16_t size_t n);
    _memset_4b(void *dest, uint32_t size_t n);
    _memset_8b(void *dest, uint64_t size_t n);

    As another way to express the same idea of flood-filling memory (and slightly different for the compiler to generate something inline without needing to pattern-match the arguments).


    Terje


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.arch on Wed Jul 1 02:28:05 2026
    From Newsgroup: comp.arch

    On Tue, 30 Jun 2026 20:57:06 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    In order to handle the LZ4 style repeated copies of 1-n bytes, up
    to length m, you pretty much have to have a hardware circuit which
    recognizes the overlapping target with an overlap less or equal to
    16 or 32 bytes, then fills an internal cache line sized buffer
    with the corresponding pattern, before doing a number of cache
    lines stores with a final partial line.

    You can use VPERMB to replicate the pattern in a ZMM register. With
    just one ZMM register, you can put in patterns up to 64 bytes, and

    Obviously correct, but that requires 31 entries of 64 bytes each just
    for the permutation table: Blowing 2 kB on this single function is
    unlikely to be a big win, much better to have a hardware assist to do
    it for you.

    store floor(64/n)*n bytes at a time. You would need a table of
    indices (each 64 bytes) and a table of strides (each 1 byte) for
    lengths up to 32 (for pattern length 33-64 there is no replication
    in the register and the store stride is the pattern length).

    It might be possible to generate the store pattern on the fly, at
    least for patterns of length greater than 4 or 8: Start by loading
    the next 64 bytes and save it to a 128-byte buffer, then overwrite
    from where the pattern should repeat, extending a 10-byte pattern to
    20 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...
    repeat once more to get 40 bytes 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...

    which is the maximum repeat length for a 64-byte store, at a cost of

    The initial load (always in $L1 cache), unaligned load

    Store to temp buffer, aligned store
    Store again at pattern length offset, unaligned store

    load from temp buffer
    store again at double pattern length offset

    load final pattern from the temp buffer

    This looks like 3 loads and 3 stores, most of them dependent upon the previous so at least 6 clock cycles?

    With more ZMM registers, longer patterns can be used with that
    approach, and even for shorter patterns, one may be able to waste
    less. E.g., with a pattern length of 33 and one register, only 52%
    of the capacity is used; with two registers, 77%; with 3 registers,
    86%, with 4, 90%, etc. But you also need more storage space for the patterns.

    For the final partial store, you use the store with AVX-512's mask
    feature.

    Obviously doable at 1 or 2 cycles of startup overhead,

    My impression (based on REP MOVSB performance for non-overlapping
    copies 10 years ago) is that the minimal cost of such microcode
    assist things was 12 cycles or so. I'll have to make measurements
    with newer CPUs to see if there has been any improvement there.

    No, no! No microcode, this would need to be a proper hardwired
    function unit. More or less like the difference between classic 40-80
    cycle FDIV and the latest AMD which does it in 7-10.

    Terje


    Latest AMD does not do double precision FDIV in 7-10 (latency).
    The fastest AMD ever did was Zen1 (8-13). For later chips it's 13.

    BTW, for Intel the latest are also not the fastest, as measured by
    latency in cycles. Broadwell was 10-14. Skylake and later - 13-14.

    Some very old cores despite microcode based FDIV engine were also quite respectable. E.g. original Pentium did DP FDIV (x87) in 33 cycles.

    The slowest in recent (20 years) memories is Intel Bonell-based Atom -
    71 clock for x87 FDIV, 60 clocks for scalar SSE2 variant.
    Even Pentium4 Prescott, architecture that was famous for slow execution
    of less common instructions, is faster than that - 45 clocks for x87
    variant, 40 clocks for SSE2 variant.








    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Tue Jun 30 22:46:41 2026
    From Newsgroup: comp.arch

    On 6/30/2026 6:28 PM, Michael S wrote:
    On Tue, 30 Jun 2026 20:57:06 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    In order to handle the LZ4 style repeated copies of 1-n bytes, up
    to length m, you pretty much have to have a hardware circuit which
    recognizes the overlapping target with an overlap less or equal to
    16 or 32 bytes, then fills an internal cache line sized buffer
    with the corresponding pattern, before doing a number of cache
    lines stores with a final partial line.

    You can use VPERMB to replicate the pattern in a ZMM register. With
    just one ZMM register, you can put in patterns up to 64 bytes, and

    Obviously correct, but that requires 31 entries of 64 bytes each just
    for the permutation table: Blowing 2 kB on this single function is
    unlikely to be a big win, much better to have a hardware assist to do
    it for you.

    store floor(64/n)*n bytes at a time. You would need a table of
    indices (each 64 bytes) and a table of strides (each 1 byte) for
    lengths up to 32 (for pattern length 33-64 there is no replication
    in the register and the store stride is the pattern length).

    It might be possible to generate the store pattern on the fly, at
    least for patterns of length greater than 4 or 8: Start by loading
    the next 64 bytes and save it to a 128-byte buffer, then overwrite
    from where the pattern should repeat, extending a 10-byte pattern to
    20 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...
    repeat once more to get 40 bytes
    0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...

    which is the maximum repeat length for a 64-byte store, at a cost of

    The initial load (always in $L1 cache), unaligned load

    Store to temp buffer, aligned store
    Store again at pattern length offset, unaligned store

    load from temp buffer
    store again at double pattern length offset

    load final pattern from the temp buffer

    This looks like 3 loads and 3 stores, most of them dependent upon the
    previous so at least 6 clock cycles?

    With more ZMM registers, longer patterns can be used with that
    approach, and even for shorter patterns, one may be able to waste
    less. E.g., with a pattern length of 33 and one register, only 52%
    of the capacity is used; with two registers, 77%; with 3 registers,
    86%, with 4, 90%, etc. But you also need more storage space for the
    patterns.

    For the final partial store, you use the store with AVX-512's mask
    feature.

    Obviously doable at 1 or 2 cycles of startup overhead,

    My impression (based on REP MOVSB performance for non-overlapping
    copies 10 years ago) is that the minimal cost of such microcode
    assist things was 12 cycles or so. I'll have to make measurements
    with newer CPUs to see if there has been any improvement there.

    No, no! No microcode, this would need to be a proper hardwired
    function unit. More or less like the difference between classic 40-80
    cycle FDIV and the latest AMD which does it in 7-10.

    Terje


    Latest AMD does not do double precision FDIV in 7-10 (latency).
    The fastest AMD ever did was Zen1 (8-13). For later chips it's 13.

    BTW, for Intel the latest are also not the fastest, as measured by
    latency in cycles. Broadwell was 10-14. Skylake and later - 13-14.

    Some very old cores despite microcode based FDIV engine were also quite respectable. E.g. original Pentium did DP FDIV (x87) in 33 cycles.

    The slowest in recent (20 years) memories is Intel Bonell-based Atom -
    71 clock for x87 FDIV, 60 clocks for scalar SSE2 variant.
    Even Pentium4 Prescott, architecture that was famous for slow execution
    of less common instructions, is faster than that - 45 clocks for x87
    variant, 40 clocks for SSE2 variant.



    Does seem like (intuitively, untested) it should be possible to turn the reciprocal part of an FDIV into a bunch of table lookups and subtracts
    (each covering a part of the mantissa). Then the rest becomes a normal
    FMUL. Exponent is easier to determine.

    Seems like it could be viable for fast'ish Binary16 or Binary32, scaling
    it up to Binary64 would likely be expensive and have a lot of adder latency.


    Well, vs the existing options:
    Do N-R in software (cheapest);
    User trap-and-emulate if SW uses an FDIV instruction;
    Use a Shift-ADD MUL/DIV unit to deal with it.
    Slower than bare N-R but faster than trap-and-emulate.


    Usual issue though is that making it fast isn't a priority as it usually
    isn't used all that often. Except with perspective correct texturing in
    3D rendering, which does a lot of FDIV, but, it is also possible in this
    case to use fast approximations rather than a slow-but-accurate FDIV.

    ...


    But, thinking some, maybe I should at some point consider doing an
    OpenGL rasterizer that actually implements perspective correct rendering rather than dynamic world-space subdivision (or, maybe switch to
    screen-space subdivision?...).

    So, in the latter approach, would initially project geometry as-if it
    were perspective-correct (likely adding a part to clip polygons by the
    frustum planes to avoid anomalies; as perspective-correct math tends to
    wonk out when geometry extends outside the frustum).

    But, this avoids a bunch of extra trips through the projection
    transform, which is currently a bottleneck in TKRA-GL.


    Then primitives would be classified later:
    Small on-screen area: Use Affine
    Large on-screen area: Subdivide or use perspective-correct.
    Subdivision would use the perspective-correct interpolation math.

    Could maybe also try to redesign it in a way that makes sense for OpenGL
    2.x functionality as well (probably sticking to floating-point for more
    of the pipeline, and only dropping to fixed-point for the final stages
    when using affine).

    ...


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Wed Jul 1 07:56:11 2026
    From Newsgroup: comp.arch

    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    In order to handle the LZ4 style repeated copies of 1-n bytes, up to
    length m, you pretty much have to have a hardware circuit which
    recognizes the overlapping target with an overlap less or equal to 16 or >>> 32 bytes, then fills an internal cache line sized buffer with the
    corresponding pattern, before doing a number of cache lines stores with
    a final partial line.

    You can use VPERMB to replicate the pattern in a ZMM register. With
    just one ZMM register, you can put in patterns up to 64 bytes, and

    Obviously correct, but that requires 31 entries of 64 bytes each just
    for the permutation table: Blowing 2 kB on this single function is
    unlikely to be a big win

    2KB cost EUR 0.00003 even at todays RAM prices.

    Concerning the cache, for a given n you access exactly one cache line
    for a given pattern length. The cost of that depends on where the
    line is now, as usual with caches: If the n has been used recently, it
    may be in L1 and cost very little, if it has not been used in a long
    time, it may reside in RAM and tyhe access is very expensive, but that
    case is rare.

    much better to have a hardware assist to do it
    for you.

    Sure, one can dream.

    store floor(64/n)*n bytes at a time. You would need a table of
    indices (each 64 bytes) and a table of strides (each 1 byte) for
    lengths up to 32 (for pattern length 33-64 there is no replication in
    the register and the store stride is the pattern length).

    It might be possible to generate the store pattern on the fly, at least
    for patterns of length greater than 4 or 8: Start by loading the next 64 >bytes and save it to a 128-byte buffer, then overwrite from where the >pattern should repeat, extending a 10-byte pattern to 20 >0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...
    repeat once more to get 40 bytes >0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...

    No need for a buffer, just use the destination. That's the approach I
    have used earlier and that I would like to improve on with the SIMD register(s).

    One disadvantage of this approach is that you load stuff that has just
    been stored, which may result in a penalty of ~7 cycles for regular store-to-load forwarding, for every doubling of length, until the
    length is big enough. There is zero-cycle store-to-load forwarding,
    but with these kinds of accesses, it will probably rarely come into
    action.

    An additional disadvantage is that you also load stuff that is not in
    the store buffer; if there is a memory access that straddles the
    boundary between these, you do not see just the regular store-to-load forwarding latency, but the full roundtrip through the cache (~20
    cycles).

    The SIMD approach was intended to avoid that. Maybe there is a better
    way to do it than to have an index table in memory. Ideally you load
    the pattern with a masked load, then double it with a shift-and-merge
    approach until the SIMD register is maximally utilized, then use that
    for storing. But I don't know a good way to do that on AVX-512
    without memory accesses (but my knowledge of AVX-512 is not
    particularly high).

    which is the maximum repeat length for a 64-byte store, at a cost of

    The initial load (always in $L1 cache), unaligned load

    Store to temp buffer, aligned store
    Store again at pattern length offset, unaligned store

    load from temp buffer
    store again at double pattern length offset

    load final pattern from the temp buffer

    This looks like 3 loads and 3 stores, most of them dependent upon the >previous so at least 6 clock cycles?

    If the pattern is 1 or 2 bytes, you will probably special-case them to
    use a broadcast instruction, but for 3 bytes, you would probably use
    the doubling approach, and you need 5 loads and 4 stores until you
    have 48 bytes in your zmm register, for about 80 cycles or so total
    cost (no store-to-load forwarding, so ~20 cycles per store-load
    dependency).

    My impression (based on REP MOVSB performance for non-overlapping
    copies 10 years ago) is that the minimal cost of such microcode assist
    things was 12 cycles or so. I'll have to make measurements with newer
    CPUs to see if there has been any improvement there.

    No, no! No microcode, this would need to be a proper hardwired function >unit.

    Intel and AMD have not even done proper hardwired function units for
    the non-overlapping block-copying functionality of REP MOVSB, and
    instead go for microcode. So it is unlikely that they will implement
    a proper hardwired function unit for pattern replication anytime soon.

    More or less like the difference between classic 40-80 cycle FDIV
    and the latest AMD which does it in 7-10.

    One difference is that, in the end, block copying and pattern
    replication are bottlenecked by the load/store unit, the cache
    bandwidth and the DRAM bandwidth, so the benefit of a proper hardwired
    function unit is limited.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Wed Jul 1 12:33:54 2026
    From Newsgroup: comp.arch

    Michael S wrote:
    On Tue, 30 Jun 2026 20:57:06 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    In order to handle the LZ4 style repeated copies of 1-n bytes, up
    to length m, you pretty much have to have a hardware circuit which
    recognizes the overlapping target with an overlap less or equal to
    16 or 32 bytes, then fills an internal cache line sized buffer
    with the corresponding pattern, before doing a number of cache
    lines stores with a final partial line.

    You can use VPERMB to replicate the pattern in a ZMM register. With
    just one ZMM register, you can put in patterns up to 64 bytes, and

    Obviously correct, but that requires 31 entries of 64 bytes each just
    for the permutation table: Blowing 2 kB on this single function is
    unlikely to be a big win, much better to have a hardware assist to do
    it for you.

    store floor(64/n)*n bytes at a time. You would need a table of
    indices (each 64 bytes) and a table of strides (each 1 byte) for
    lengths up to 32 (for pattern length 33-64 there is no replication
    in the register and the store stride is the pattern length).

    It might be possible to generate the store pattern on the fly, at
    least for patterns of length greater than 4 or 8: Start by loading
    the next 64 bytes and save it to a 128-byte buffer, then overwrite
    from where the pattern should repeat, extending a 10-byte pattern to
    20 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...
    repeat once more to get 40 bytes
    0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...

    which is the maximum repeat length for a 64-byte store, at a cost of

    The initial load (always in $L1 cache), unaligned load

    Store to temp buffer, aligned store
    Store again at pattern length offset, unaligned store

    load from temp buffer
    store again at double pattern length offset

    load final pattern from the temp buffer

    This looks like 3 loads and 3 stores, most of them dependent upon the
    previous so at least 6 clock cycles?

    With more ZMM registers, longer patterns can be used with that
    approach, and even for shorter patterns, one may be able to waste
    less. E.g., with a pattern length of 33 and one register, only 52%
    of the capacity is used; with two registers, 77%; with 3 registers,
    86%, with 4, 90%, etc. But you also need more storage space for the
    patterns.

    For the final partial store, you use the store with AVX-512's mask
    feature.

    Obviously doable at 1 or 2 cycles of startup overhead,

    My impression (based on REP MOVSB performance for non-overlapping
    copies 10 years ago) is that the minimal cost of such microcode
    assist things was 12 cycles or so. I'll have to make measurements
    with newer CPUs to see if there has been any improvement there.

    No, no! No microcode, this would need to be a proper hardwired
    function unit. More or less like the difference between classic 40-80
    cycle FDIV and the latest AMD which does it in 7-10.

    Terje


    Latest AMD does not do double precision FDIV in 7-10 (latency).
    The fastest AMD ever did was Zen1 (8-13). For later chips it's 13.

    OK, thanks for the correction. I was mixing it up with integer DIV which
    at least Apple (ARM M4?) has made so fast that clang has stopped using
    the classic reciprocal mul trick when doing a constant MOD operation:
    DIV is faster than two MULs and a SUB.

    BTW, for Intel the latest are also not the fastest, as measured by
    latency in cycles. Broadwell was 10-14. Skylake and later - 13-14.

    Some very old cores despite microcode based FDIV engine were also quite respectable. E.g. original Pentium did DP FDIV (x87) in 33 cycles.

    Pentium FDIV is where I spent a couple of months on the initial
    verification of the bug and then the software workaround:

    It did two bits/clock, which when added to the startup and final bit
    overhead resulted in 40 cycles for extended (80-bit, with 64-bit
    mantissa). The same operation took about twice as long on the Pentium.

    You are right that double only needed 27 cycles to generate the mantissa
    and 33 total.


    The slowest in recent (20 years) memories is Intel Bonell-based Atom -
    71 clock for x87 FDIV, 60 clocks for scalar SSE2 variant.
    Even Pentium4 Prescott, architecture that was famous for slow execution
    of less common instructions, is faster than that - 45 clocks for x87
    variant, 40 clocks for SSE2 variant.

    Good to know!

    I tend to lookup my old friend Agner Fog's tables when I need this type
    of info. We worked together on a writeup of the reciprocal MUL idea back
    well before compilers started to implement it, but maybe a decade after
    it was first discovered/invented by some DEC guys.

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Wed Jul 1 12:54:27 2026
    From Newsgroup: comp.arch

    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    In order to handle the LZ4 style repeated copies of 1-n bytes, up to
    length m, you pretty much have to have a hardware circuit which
    recognizes the overlapping target with an overlap less or equal to 16 or >>>> 32 bytes, then fills an internal cache line sized buffer with the
    corresponding pattern, before doing a number of cache lines stores with >>>> a final partial line.

    You can use VPERMB to replicate the pattern in a ZMM register. With
    just one ZMM register, you can put in patterns up to 64 bytes, and

    Obviously correct, but that requires 31 entries of 64 bytes each just
    for the permutation table: Blowing 2 kB on this single function is
    unlikely to be a big win

    2KB cost EUR 0.00003 even at todays RAM prices.

    No, not the bit cost, but the alternative cost of not having the cache available for more useful stuff.

    Concerning the cache, for a given n you access exactly one cache line
    for a given pattern length. The cost of that depends on where the
    line is now, as usual with caches: If the n has been used recently, it
    may be in L1 and cost very little, if it has not been used in a long
    time, it may reside in RAM and tyhe access is very expensive, but that
    case is rare.

    much better to have a hardware assist to do it
    for you.

    Sure, one can dream.

    store floor(64/n)*n bytes at a time. You would need a table of
    indices (each 64 bytes) and a table of strides (each 1 byte) for
    lengths up to 32 (for pattern length 33-64 there is no replication in
    the register and the store stride is the pattern length).

    It might be possible to generate the store pattern on the fly, at least
    for patterns of length greater than 4 or 8: Start by loading the next 64
    bytes and save it to a 128-byte buffer, then overwrite from where the
    pattern should repeat, extending a 10-byte pattern to 20
    0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...
    repeat once more to get 40 bytes
    0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14...

    No need for a buffer, just use the destination. That's the approach I
    have used earlier and that I would like to improve on with the SIMD register(s).

    This means that you need about 100 extra bytes in the target buffer, in
    which case you also don't need to specialcase the final store operation.

    Anyway, from all of this I've come to the conclusion that I cannot do
    much better than my current 15-entry AVX permutation lookup table:

    It is fast enough to beat Google, while "only" using about 500 bytes of
    table space, so I'll leave the code alone, at least for now. :-)

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From jgd@jgd@cix.co.uk (John Dallman) to comp.arch on Wed Jul 1 12:01:40 2026
    From Newsgroup: comp.arch

    In article <2026Jun28.150519@mips.complang.tuwien.ac.at>, anton@mips.complang.tuwien.ac.at (Anton Ertl) wrote:

    John Levine <johnl@taugh.com> writes:
    S/390 added the confusing;y named MOVE LONG UNICODE which moves
    and pads pairs of bytes. (That works OK for UTF-16, not any
    other Unicode encoding)

    S/390 was by far not the last system to take the "Unicode=16bit"
    route. Windows NT (released 1993), Java (released 1995), and
    JavaScript (released 1995) also went there. Systems that came out
    after Unicode 2.0 don't have that excuse, but are there any that
    made this mistake without having to be compatible with Windows NT
    or Java?

    Yes. The idea of "Unicode=16bit" seems to have got emplaced among people
    who thought they had learned about Unicode when it first appeared and
    didn't notice it had changed. A senior chap at my employer made that
    mistake in about 1997. Fortunately, we were only storing strings and
    passing them on, so the limited amount of code he wrote is compatible
    with UTF-16, and the handling for 8-bit characters works with UTF-8.

    John
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Wed Jul 1 11:22:17 2026
    From Newsgroup: comp.arch

    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Obviously correct, but that requires 31 entries of 64 bytes each just
    for the permutation table: Blowing 2 kB on this single function is
    unlikely to be a big win

    2KB cost EUR 0.00003 even at todays RAM prices.

    No, not the bit cost, but the alternative cost of not having the cache >available for more useful stuff.

    Of course you have the caches available for the other stuff. The
    caches are automatically used for anything that is accessed (except
    when you ask the system not to).

    In the present context, the LZ4 input and output streams will trample
    all over the caches, and evict any cache lines that have been there
    before.

    One may consider using non-temporal loads and stores to reduce this,
    but given that LZ4 decompresses by rereading parts of the output
    stream, non-temporal stores to the output are probably a bad idea.

    Concerning non-temporal loads (for the input stream), what I find
    about them says that they have no effect on regular (write-back)
    memory; but apparently you can use prefetchnta to reduce the amount of trampling (in different ways on different CPUs; see <https://stackoverflow.com/questions/53270421/difference-between-prefetch-and-prefetchnta-instructions>).

    Anyway, once you need to write out a pattern of a given length, my
    VPERMB solution will load the indices from memory, evicting one cache
    line unless the indices for that length are already in the cache. The
    evicted cache line is likely either a not very recent one from the
    input or a not very recent one from the output. In the former case,
    that's fine, because the input is not reused (according to my very
    limited understanding of LZ4); in the latter case, there is a small
    possibility that the cache line might have been reused before being
    evicted by some load from the input stream or store to the output
    stream.

    Overall, I think the major downside of the VPERMB approach is not the
    eviction of other cache lines, but the latency of loading the index
    line, which might have been evicted by the input stream loads or
    output stream stores.

    No need for a buffer, just use the destination. That's the approach I
    have used earlier and that I would like to improve on with the SIMD
    register(s).

    This means that you need about 100 extra bytes in the target buffer

    What for? Could it be that you think that one prepares indices in
    that way? There is no point in doing that, one just copies the actual
    pattern to the destination and duplicates that.

    Actually, given the slowness due to a full round trip through the L1
    cache, it would probably be better to just store the pattern with
    length >=3 repeatedly until 64 bytes are full (or almost full) and
    then take the round-trip hit and load the result into the SIMD
    register, now to be used for storing.

    BTW, how often is the pattern more than 2 bytes long? The 1-byte case
    could be special-cased with vpbraodcastb, the 2-byte case with
    vpbroadcastw, and likewise for 4 and 8 bytes, without needing VPERMB
    and its index vector.

    Anyway, from all of this I've come to the conclusion that I cannot do
    much better than my current 15-entry AVX permutation lookup table:

    It is fast enough to beat Google, while "only" using about 500 bytes of >table space, so I'll leave the code alone, at least for now. :-)

    Loading aligned 32 bytes from a table is going to evict a cache line
    just as loading aligned 64 bytes does. The smaller table only
    provides a benefit if the second entry in that cache line is used
    before the cache line is evicted; e.g., if you loaded the entry for
    pattern length 14, the entry for pattern length 13.

    Given that you have that, you can easily measure the effect of the
    table load cache misses on performance (and how that would change by
    making the entries twice as large) by benchmarking it as it currently
    is and with an additional 32-byte padding between the entries. You can
    measure the cycles, and, with better measurement tools, you can
    measure the cache misses and how often the index table loads miss.

    Concerning competition, where do I find your version, Google's
    version, and the benchmarks? I probably won't find the time to try it
    out, but you never know.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Wed Jul 1 16:11:41 2026
    From Newsgroup: comp.arch

    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Obviously correct, but that requires 31 entries of 64 bytes each just
    for the permutation table: Blowing 2 kB on this single function is
    unlikely to be a big win

    2KB cost EUR 0.00003 even at todays RAM prices.

    No, not the bit cost, but the alternative cost of not having the cache
    available for more useful stuff.

    Of course you have the caches available for the other stuff. The
    caches are automatically used for anything that is accessed (except
    when you ask the system not to).

    In the present context, the LZ4 input and output streams will trample
    all over the caches, and evict any cache lines that have been there
    before.

    One may consider using non-temporal loads and stores to reduce this,
    but given that LZ4 decompresses by rereading parts of the output
    stream, non-temporal stores to the output are probably a bad idea.

    Concerning non-temporal loads (for the input stream), what I find
    about them says that they have no effect on regular (write-back)
    memory; but apparently you can use prefetchnta to reduce the amount of trampling (in different ways on different CPUs; see <https://stackoverflow.com/questions/53270421/difference-between-prefetch-and-prefetchnta-instructions>).

    Anyway, once you need to write out a pattern of a given length, my
    VPERMB solution will load the indices from memory, evicting one cache
    line unless the indices for that length are already in the cache. The evicted cache line is likely either a not very recent one from the
    input or a not very recent one from the output. In the former case,
    that's fine, because the input is not reused (according to my very
    limited understanding of LZ4); in the latter case, there is a small possibility that the cache line might have been reused before being
    evicted by some load from the input stream or store to the output
    stream.

    Overall, I think the major downside of the VPERMB approach is not the eviction of other cache lines, but the latency of loading the index
    line, which might have been evicted by the input stream loads or
    output stream stores.

    The only real downside from my viewpoint is that I have exactly zero
    machines available supporting AVX-512. :-(

    Until that changes, 256-bit/32-byte operations will be the rule.


    No need for a buffer, just use the destination. That's the approach I
    have used earlier and that I would like to improve on with the SIMD
    register(s).

    This means that you need about 100 extra bytes in the target buffer

    What for? Could it be that you think that one prepares indices in
    that way? There is no point in doing that, one just copies the actual pattern to the destination and duplicates that.

    I was thinking of having a 31-byte pattern: Load 64 bytes, then store
    them back at offset 31 for a total of 95 bytes, out of which at least 62
    will be used and up to 33 "wasted" overrun.

    If the pattern length was 15 then you'd either store it back three times
    (to get to 60) or store it once, then reload and store again (probably slower).


    Actually, given the slowness due to a full round trip through the L1
    cache, it would probably be better to just store the pattern with
    length >=3 repeatedly until 64 bytes are full (or almost full) and
    then take the round-trip hit and load the result into the SIMD
    register, now to be used for storing.

    BTW, how often is the pattern more than 2 bytes long? The 1-byte case
    could be special-cased with vpbraodcastb, the 2-byte case with
    vpbroadcastw, and likewise for 4 and 8 bytes, without needing VPERMB
    and its index vector.

    Avoiding all branches carries its own reward, possibly making special
    cases for 1,2,4(,8) byte lengths less of a win. It depends on which
    lengths actually occur often enough in compressed LZ4 files.

    Anyway, LZ4 uses special (branchy) handling for lengths greater than (or
    equal to?) 15 bytes long, and the same for the number of bytes to copy,
    so large-scale RLL streams will suffer more of a startup overhead.

    I guessed that it made more sense to make the common case of sub-15
    pattern lengths and low byte counts as fast as possible, they will be
    covered by a single AVX store.


    Anyway, from all of this I've come to the conclusion that I cannot do
    much better than my current 15-entry AVX permutation lookup table:

    It is fast enough to beat Google, while "only" using about 500 bytes of
    table space, so I'll leave the code alone, at least for now. :-)

    Loading aligned 32 bytes from a table is going to evict a cache line
    just as loading aligned 64 bytes does. The smaller table only
    provides a benefit if the second entry in that cache line is used
    before the cache line is evicted; e.g., if you loaded the entry for
    pattern length 14, the entry for pattern length 13.

    Given that you have that, you can easily measure the effect of the
    table load cache misses on performance (and how that would change by
    making the entries twice as large) by benchmarking it as it currently
    is and with an additional 32-byte padding between the entries. You can measure the cycles, and, with better measurement tools, you can
    measure the cache misses and how often the index table loads miss.

    Concerning competition, where do I find your version, Google's
    version, and the benchmarks? I probably won't find the time to try it
    out, but you never know.

    I did my testing 5-10 years (and two different company PCs) ago, but I
    might be able to find it again. The google code is/was available on
    github afair.

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Wed Jul 1 13:54:54 2026
    From Newsgroup: comp.arch

    jgd@cix.co.uk (John Dallman) writes:
    Fortunately, we were only storing strings and
    passing them on, so the limited amount of code he wrote is compatible
    with UTF-16, and the handling for 8-bit characters works with UTF-8.

    Yes, that's the usual case and that's why UTF-8 is a good approach for
    code that was designed for ASCII and Latin-1, but also for double-byte character set encodings with single-byte code units, such as Big-5 or
    Shift JIS. And likewise, UTF-16 is a good approach for code that was
    designed for Unicode 1.0.

    But of course, if the Unicode people had realized that from the start,
    we would never had UCS2 and thus no UTF-16. And we would have been
    spared a lot of pain.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Wed Jul 1 15:48:54 2026
    From Newsgroup: comp.arch


    BGB <cr88192@gmail.com> posted:

    On 6/30/2026 6:28 PM, Michael S wrote:
    On Tue, 30 Jun 2026 20:57:06 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    Anton Ertl wrote:
    --------------------
    Does seem like (intuitively, untested) it should be possible to turn the reciprocal part of an FDIV into a bunch of table lookups and subtracts
    (each covering a part of the mantissa). Then the rest becomes a normal
    FMUL. Exponent is easier to determine.

    One can do a reciprocal in about 2/3rds of a DIV.
    One cannot get IEEE 754 quality rounded results using a reciprocal,
    without a Newton-Rafson iteration.

    Seems like it could be viable for fast'ish Binary16 or Binary32, scaling
    it up to Binary64 would likely be expensive and have a lot of adder latency.

    We are talking about correctly rounded IEEE 754 Double Precision, here.


    Well, vs the existing options:
    Do N-R in software (cheapest);
    Goldschmidt is cheaper than N-R.
    User trap-and-emulate if SW uses an FDIV instruction;
    Use a Shift-ADD MUL/DIV unit to deal with it.
    LoL
    Slower than bare N-R but faster than trap-and-emulate.


    Usual issue though is that making it fast isn't a priority as it usually isn't used all that often.

    An instruction that occurs 1% of the time that takes 20-cycles, acts as if
    it occurs 20% of the time.

    Except with perspective correct texturing in
    3D rendering,

    Mostly done by carrying around w (1/distance) and multiplying.
    Still not IEEE 754 correctly rounded.

    which does a lot of FDIV, but, it is also possible in this case to use fast approximations rather than a slow-but-accurate FDIV.

    ...


    But, thinking some, maybe I should at some point consider doing an
    OpenGL rasterizer that actually implements perspective correct rendering

    Perspective correct, but not correctly rounded.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Wed Jul 1 16:14:10 2026
    From Newsgroup: comp.arch

    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    [...]
    The only real downside from my viewpoint is that I have exactly zero >machines available supporting AVX-512. :-(

    Laptops with AVX-512 have been available since 2019 (Ice Lake, later
    Tiger Lake, Zen4 and Zen5). "Desktop" PCs with AVX-512 have been
    available since 2021 (Rocket Lake, Zen4, Zen5). Currently I am typing
    on a deskside computer with Zen4 and there is a laptop with a Tiger
    Lake lying beside my screen.

    No need for a buffer, just use the destination. That's the approach I >>>> have used earlier and that I would like to improve on with the SIMD
    register(s).

    This means that you need about 100 extra bytes in the target buffer

    What for? Could it be that you think that one prepares indices in
    that way? There is no point in doing that, one just copies the actual
    pattern to the destination and duplicates that.

    I was thinking of having a 31-byte pattern: Load 64 bytes, then store
    them back at offset 31 for a total of 95 bytes, out of which at least 62 >will be used and up to 33 "wasted" overrun.

    With AVX-512, you can mask stores, and the masked-away bytes do not
    need to be accessible (I don't know what happens to the performance of
    the store if some masked-away bytes are not accessible).

    Avoiding all branches carries its own reward, possibly making special
    cases for 1,2,4(,8) byte lengths less of a win. It depends on which
    lengths actually occur often enough in compressed LZ4 files.

    The reward for avoiding branches is small if the branch is very
    predictable, so yes, it depends on the distribution and sequence of
    pattern lengths. The easiest thing here typically is to implement the branching program and measure the number of mispredictions.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From John Levine@johnl@taugh.com to comp.arch on Wed Jul 1 17:26:55 2026
    From Newsgroup: comp.arch

    According to Anton Ertl <anton@mips.complang.tuwien.ac.at>:
    jgd@cix.co.uk (John Dallman) writes:
    Fortunately, we were only storing strings and
    passing them on, so the limited amount of code he wrote is compatible
    with UTF-16, and the handling for 8-bit characters works with UTF-8.

    Yes, that's the usual case and that's why UTF-8 is a good approach for
    code that was designed for ASCII and Latin-1, but also for double-byte >character set encodings with single-byte code units, such as Big-5 or
    Shift JIS. And likewise, UTF-16 is a good approach for code that was >designed for Unicode 1.0.

    UTF-16 isn't really a good approach for anything due to the ugly way it
    uses surrogates to shoehorn in code points above 64K. You can't tell
    whether a code point is one or two byte pairs without looking at each
    one, and sorting or comparing them is painful.

    One of the underappreciated aspects of UTF-8 is that a naive unsigned
    byte sorts or comparisons of UTF-8 strings gives you the same order as
    sorting or comparing the equivalent UTF-32.

    But of course, if the Unicode people had realized that from the start,
    we would never had UCS2 and thus no UTF-16. And we would have been
    spared a lot of pain.

    It was literally another case of "64K is enough for anyone."
    --
    Regards,
    John Levine, johnl@taugh.com, Primary Perpetrator of "The Internet for Dummies",
    Please consider the environment before reading this e-mail. https://jl.ly
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.arch on Wed Jul 1 22:17:29 2026
    From Newsgroup: comp.arch

    On Wed, 1 Jul 2026 16:11:41 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    The only real downside from my viewpoint is that I have exactly zero machines available supporting AVX-512. :-(


    I think, you said here few years ago that you own Tiger Lake (Intel
    core gen 11) based laptop. Do I misremember or it died since then?


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Wed Jul 1 15:04:54 2026
    From Newsgroup: comp.arch

    On 7/1/2026 10:48 AM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/30/2026 6:28 PM, Michael S wrote:
    On Tue, 30 Jun 2026 20:57:06 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    Anton Ertl wrote:
    --------------------
    Does seem like (intuitively, untested) it should be possible to turn the
    reciprocal part of an FDIV into a bunch of table lookups and subtracts
    (each covering a part of the mantissa). Then the rest becomes a normal
    FMUL. Exponent is easier to determine.

    One can do a reciprocal in about 2/3rds of a DIV.
    One cannot get IEEE 754 quality rounded results using a reciprocal,
    without a Newton-Rafson iteration.


    Possibly, but generally both tend to land on the same value.

    I guess if needed could try to run some stats on x/y vs x*(1.0/y) to see
    if there is a significant difference.


    Seems like it could be viable for fast'ish Binary16 or Binary32, scaling
    it up to Binary64 would likely be expensive and have a lot of adder latency.

    We are talking about correctly rounded IEEE 754 Double Precision, here.


    Possibly, but could be more accurate than the (more or less):
    0x7FE0000000000000-GetBits(value)
    Method...

    Which sorta works as a first-order approximation, but usually requires
    the jank of slower-adapting first step to get it close enough that it
    reliably converges and doesn't fly off into space.


    It is possible to use a lookup table on the high-order bits to generate
    an adjustment value that gets it a little closer.

    This makes sense for HW, but wasn't usually done for software as it
    usually ended up slower than a modified N-R step.



    Expanded version could look like, say:
    RcpPartMantisa=LookupA[51:46]+LookupB[45:40]+LookupC[39:34]+...

    Then possibly use this to form a value to subtract from the bit pattern
    of the other value to perform a divide (and/or build a reciprocal and multiply).

    Could also be done in software.


    In a SW version, could use an N-R step as well to try to fix it up if
    needed.



    I am not sure how actual hardware usually does division quickly and accurately.



    Well, vs the existing options:
    Do N-R in software (cheapest);
    Goldschmidt is cheaper than N-R.


    AFAICT cheap Goldschmidt needs at least a fully pipelined FPU to have
    much advantage over N-R (or to do it via SIMD).


    This is in a way the sort of lame irony of the slow FPU path with
    non-fast cases for 4x Binary32 or 2x Binary64 SIMD on my CPU:
    The only real merit they have performance-wise is due to the ability to pipeline the FPU.

    Whereas, for the scalar FPU ops, they are bound by FPU latency.

    Granted, it does have 2x Binary64 SIMD, so could maybe be investigated
    for whether it could give something faster than N-R.



    User trap-and-emulate if SW uses an FDIV instruction;
    Use a Shift-ADD MUL/DIV unit to deal with it.
    LoL

    Both exist on the cheaper side, but:
    Trap-and-emulate: around 1000 cycles or so (hand-wave estimate)


    Shift-ADD:
    ~ 120 or so.

    But, costs more on the HW side than trap-and-emulate.
    Was able to get exact results this way though.

    In this case, using the same unit as used for 64-bit integer multiply
    and divide, but making the observation that one can run it out longer to
    get fractional bits rather than integer results. So, divide the
    mantissas as two 64-bit values, but then run it out so that rather than stopping at the integer result, it generates the whole result mantissa
    as a fraction.

    The Shift-ADD unit itself was based on noting that the
    shift-and-subtract divide algorithm could also be turned into mechanism
    that does both multiply and divide.

    Granted, probably a lot of this is already well-known.


    Slower than bare N-R but faster than trap-and-emulate.


    Usual issue though is that making it fast isn't a priority as it usually
    isn't used all that often.

    An instruction that occurs 1% of the time that takes 20-cycles, acts as if
    it occurs 20% of the time.


    FDIV is usually very rare, except when it is not...

    Most code could accept it as trap-and-emulate...

    But, if using naive divide and trap-and-emulate for Quake's SW renderer
    or similar, it tanks.

    Well, worse than the already single-digit framerates I am getting on a
    50 MHz CPU.


    Mostly, I was using explicit calls to the SW N-R method.

    There is a FDIVA instruction, but it is not sufficient on its own (it
    uses the "bitwise subtraction method"), so still needs N-R (mostly can
    save a few cycles that would otherwise be spent twiddling the sign bit).

    Though interestingly, this is a use-case for the RISC-V "FSGNJ"
    instructions (XG1/XG2 don't have these, but XG3 can borrow it from
    RISC-V; but with the limitation that it only works in the F registers).

    Though, otherwise, situation in plain RISC-V would be worse off, as
    there is no direct way to do integer subtract on F registers, so the
    SW-side logic requires moving the value back and forth between X and F registers.

    ...


    Except with perspective correct texturing in
    3D rendering,

    Mostly done by carrying around w (1/distance) and multiplying.
    Still not IEEE 754 correctly rounded.


    You need to interpolate (1/W) and then take the reciprocal to get W back occasionally (every some-odd pixels).

    One can (almost) get a similar effect by interpolating W, but it is less
    good.

    Or make-shift option is likely to mix them:
    Coarse, interpolate 1/W and then take the reciprocal for regular
    intervals (say, a 32x32 or 64x64 pixel square), then interpolate W and
    use this for the U/V math (at 8 or 16 pixels).


    I don't remember the specifics off-hand, but something to this effect.


    which does a lot of FDIV, but, it is also possible in this
    case to use fast approximations rather than a slow-but-accurate FDIV.

    ...


    But, thinking some, maybe I should at some point consider doing an
    OpenGL rasterizer that actually implements perspective correct rendering

    Perspective correct, but not correctly rounded.


    Precision is pretty loose, but still needs a few N-R stages to look "not broken".

    Like, sadly, it isn't sufficient to just use a bitwise subtract and call
    it done.


    Could use Binary16 and a lookup table, but then one would be mostly
    paying the cost in cache misses...




    Well, having some initial thoughts for the approach to a possible "TKRA-GL-2"...

    So, likely steps:


    Probably more properly split up contexts into frontend and backend contexts.

    Frontend context deals with local OpenGL stuff, like state-manipulation functions (glEnable/glDisable/etc). Always local to the client's address space.

    Backend context deals with the actual rasterization process, and would
    be made structurally independent of the frontend context.

    The two halves interact over a COM style interface (same as before).
    A partial split already exists, but as-is it is messy.

    TKRA-GL was originally assumed to run purely in the client space (as a
    pure software rasterizer), but the creation of a hardware rasterizer
    module (and some semblance of a proper user-mode) required moving the rasterizer parts into kernel space (creating a mess here).


    Will likely keep the backend edge walking and span-drawing stuff from
    the existing TKRA-GL.

    Will likely switch from world-space to screen-space subdivision.
    It will either subdivide the primitives in screen-space post transform,
    or in the process of generating the edge walks.

    I am more likely to break it up into edge-walk fragments, rather than
    change the scanline rasterization process, partly for both inertia and performance (will avoid needing to do multiple FP divides for every
    scanline).

    I am likely to replace the "big stacks of projected vertices" mechanism
    for linked-lists of primitives (quads or triangles).


    Will need to clip geometry early, as I had noted in the past that
    perspective correct rendering doesn't deal well with geometry that
    crosses the near plane; rather than with world-space subdivision where
    one can just sorta subdivide finer, and then discard any remaining
    pieces that cross the near plane (this is TKRA-GL's current strategy).

    Like, something about traveling through the focal point and W's sign
    flipping causes the U/V coords to seriously freak out.




    Maybe try to figure out more how I am going to do the attribute plumbing
    for GLSL shaders.


    This needs to cross between both ends, but front-end and backend ideally shouldn't need to talk to each other to negotiate parameter layout.

    One possibility:
    There is a provision for up to N uniform parameters slots, and N varying parameter slots;
    Likely they could be keyed up by hashing the symbol names (and assuming
    that no hash collisions happen).

    The DrawElements call or similar maps the locations from the front-end
    to the locations generated by the shader compiler/linker (well, since it
    seems like I am now on-track to having a potentially usable GLSL compiler).


    ...


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Kerr-Mudd, John@admin@127.0.0.1 to comp.arch on Wed Jul 1 21:55:20 2026
    From Newsgroup: comp.arch

    On Sun, 28 Jun 2026 20:15:03 GMT
    MitchAlsup <user5857@newsgrouper.org.invalid> wrote:


    Terje Mathisen <terje.mathisen@tmsw.no> posted:

    Thomas Koenig wrote:
    program, it didn't have any serious bugs yet, at least not in the functionality that was implemented up to that time. But remember IEFBR14...)

    Funnily enough, the IEFBR14 bug was repeated a couple of decades later
    in MSDOS when DUMMY.COM which simply returned to the OS without doing anything had the exact same issue:

    The original DUMMY.COM was one byte long, containing just a RET opcode:

    The 256-byte PSP which was loaded in front of all .COM programs intentionally contained CD 20h (INT 20h -> Terminate the running
    program) as the first two bytes, with a zero word pushed onto the stack, so that any program which used the CP/M convention that you could RET to the OS would work.

    Just like the original IEFBR14, this setup did not return a fixed ERRORLEVEL which batch scripts could use to determine if an error had happened, so for the MSDOS version the single-byte program needed to be changed to something like

    MOV AX,4700h
    INT 21h

    which would call the exit program OS function (AH=47h) with a return
    code (AL) of zero.

    Terje
    PS. Why do I still remember the x86 hex codes for all of this almost 40 years after I last wrote 16-bit ASM code which needed it?


    My excuse is that I'm still writing DOS x86 code! - and maybe there's
    some slippage - ah=4Ch, al=xx (0-FFh) sets the ERRORLEVEL (0-255d)
    (47 is Get Current Directory)

    It got burned into your retina--like the 9 step PDP-11 startup sequence
    you "punched" into the front panel got burned into mine.
    --
    Bah, and indeed Humbug.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Wed Jul 1 16:00:01 2026
    From Newsgroup: comp.arch

    On 7/1/2026 8:54 AM, Anton Ertl wrote:
    jgd@cix.co.uk (John Dallman) writes:
    Fortunately, we were only storing strings and
    passing them on, so the limited amount of code he wrote is compatible
    with UTF-16, and the handling for 8-bit characters works with UTF-8.

    Yes, that's the usual case and that's why UTF-8 is a good approach for
    code that was designed for ASCII and Latin-1, but also for double-byte character set encodings with single-byte code units, such as Big-5 or
    Shift JIS. And likewise, UTF-16 is a good approach for code that was designed for Unicode 1.0.

    But of course, if the Unicode people had realized that from the start,
    we would never had UCS2 and thus no UTF-16. And we would have been
    spared a lot of pain.


    Well, it is a situation of:
    UTF-8, Usually best-case option here for string storage;
    UTF-16, Kinda meh, works well if one assumes UCS-2 is enough;
    UTF-32, hard pressed to justify burning this much on strings.


    If not used carefully (compact storage, interning, ...), then strings
    can very often end up as a significant memory consumer in many programs.

    My usual storage policy for strings had been to treat them as read-only
    and then intern everything under a maximum length limit, as the cost of interning a one-off short string was usually less than the cost of heap-allocating it. But, interning a string means it can never go away,
    and for longer strings it was usually better to assume they are one-off
    and heap-allocate them (as maybe counter-intuitively, the memory cost of duplicating a large string that happens to be not one-off, is usually
    less than the cost of interning said string and then never seeing it again).

    For string tables, there is also the possibility of tokenizing the
    strings and then storing longer strings as a mixture of characters and references to other shorter strings; though this is not commonly used
    IME (generally involves ugly hacks of the UTF-8 encoding scheme and
    assumes that the strong will be unpacked prior to use). Idea though is
    to store such strings at the end of a string table, and then use funky overlong encodings to encode offsets to the preceding string literals.

    But, yeah, if storing a big blob of text or code or similar, this can potentially pay off.

    Where, say:
    00..7F: As usual
    80..BF: Not valid as first byte in a longer char (possible escapes)
    C0..DF: Two Byte (0000..07FF)
    E0..EF: Three Byte (0800..FFFF)
    F0..F7: Four Byte (010000..1FFFFF)
    If a 3-byte encodes a value under 0x800, or 4-byte under 0x10000, can interpret it as a string offset rather than a codepoint. Preceding
    string stops when it hits a NUL terminator; typically does not work recursively.

    Only works well when token length is significantly longer than the
    length needed to encode the offset though (so, generally better for
    chunks of code or similar, not so much for things like English text
    where the token length is typically too short).

    Could store index numbers into an interned string table, but this would require the decode logic to be aware of the string tables' index,
    whereas it is less troublesome to use byte offsets.



    For my BS2 language, I sorta went the route of assuming char = 16-bit
    (like Java and C#), but then ended up adding a 'cchar' type for the
    8-bit strings (that were actually used). So, it was likely a mistake in retrospect (more so as the actual implementation typically used a mix of
    UTF-8 and 1252).

    Though, implicitly one could interpret "char" as "holds a character", in
    which case one would need a 32-bit char, vs "actually just a byte, but
    we use a different name to signify that it points to a string".

    ...


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Wed Jul 1 16:16:14 2026
    From Newsgroup: comp.arch

    On 7/1/2026 2:17 PM, Michael S wrote:
    On Wed, 1 Jul 2026 16:11:41 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    The only real downside from my viewpoint is that I have exactly zero
    machines available supporting AVX-512. :-(


    I think, you said here few years ago that you own Tiger Lake (Intel
    core gen 11) based laptop. Do I misremember or it died since then?



    Ironically, I have a laptop I had gotten recentish which was the first
    machine I got to have native AVX-256 (had a "Core i7 8th Gen").

    Enabling it still doesn't see much performance gain for some basic test programs (assuming auto vectorization is doing its thing), but at least
    (as a plus point) enabling it doesn't actively make performance worse
    (main PC having a CPU that supports AVX on paper, but still uses a
    128-bit SIMD unit internally).


    I still remain personally skeptical that 256-bit SIMD is actually that
    much of a value add though, less so 512-bit SIMD.

    Like, more features that exist to burn transistors than to actually make programs meaningfully faster...


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Wed Jul 1 23:33:28 2026
    From Newsgroup: comp.arch

    Michael S wrote:
    On Wed, 1 Jul 2026 16:11:41 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    The only real downside from my viewpoint is that I have exactly zero
    machines available supporting AVX-512. :-(


    I think, you said here few years ago that you own Tiger Lake (Intel
    core gen 11) based laptop. Do I misremember or it died since then?

    My last work laptop might have been Core 11, but now that I'm retired my fastest machine is an Acer with a "13th Gen Intel(R) Core(TM) i7-1365U"
    cpu which only supports AVX2.

    My other PC is a 7 year old Microsoft Surface Pro 7 which does handle
    AVX afair, but certainly nothing beyond that.

    All my older machines (I've saved a few of them) are from the pre-2015 days.

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Wed Jul 1 23:35:19 2026
    From Newsgroup: comp.arch

    Kerr-Mudd, John wrote:
    On Sun, 28 Jun 2026 20:15:03 GMT
    MitchAlsup <user5857@newsgrouper.org.invalid> wrote:


    Terje Mathisen <terje.mathisen@tmsw.no> posted:

    Thomas Koenig wrote:
    program, it didn't have any serious bugs yet, at least not in the
    functionality that was implemented up to that time. But remember
    IEFBR14...)

    Funnily enough, the IEFBR14 bug was repeated a couple of decades later
    in MSDOS when DUMMY.COM which simply returned to the OS without doing
    anything had the exact same issue:

    The original DUMMY.COM was one byte long, containing just a RET opcode:

    The 256-byte PSP which was loaded in front of all .COM programs
    intentionally contained CD 20h (INT 20h -> Terminate the running
    program) as the first two bytes, with a zero word pushed onto the stack, >>> so that any program which used the CP/M convention that you could RET to >>> the OS would work.

    Just like the original IEFBR14, this setup did not return a fixed
    ERRORLEVEL which batch scripts could use to determine if an error had
    happened, so for the MSDOS version the single-byte program needed to be
    changed to something like

    MOV AX,4700h
    INT 21h

    which would call the exit program OS function (AH=47h) with a return
    code (AL) of zero.

    Terje
    PS. Why do I still remember the x86 hex codes for all of this almost 40
    years after I last wrote 16-bit ASM code which needed it?


    My excuse is that I'm still writing DOS x86 code! - and maybe there's
    some slippage - ah=4Ch, al=xx (0-FFh) sets the ERRORLEVEL (0-255d)
    (47 is Get Current Directory)

    Ouch! (Or should I say "Yes, I'm finally forgetting some of the details!"?)

    Anyway, thanks!

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.arch on Wed Jul 1 21:44:47 2026
    From Newsgroup: comp.arch

    anton@mips.complang.tuwien.ac.at (Anton Ertl) writes:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    [...]
    The only real downside from my viewpoint is that I have exactly zero >>machines available supporting AVX-512. :-(

    Laptops with AVX-512 have been available since 2019 (Ice Lake, later
    Tiger Lake, Zen4 and Zen5). "Desktop" PCs with AVX-512 have been
    available since 2021 (Rocket Lake, Zen4, Zen5). Currently I am typing
    on a deskside computer with Zen4 and there is a laptop with a Tiger
    Lake lying beside my screen.

    My 2013 home desktop identifies as Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz (HP)
    My 2025 home desktop identifies as Intel(R) Core(TM) i7-14700 (DELL)

    Neither of them support AVX-512 (not surprising for the 2013 cpu, but
    somewhat surprising for the 2025 cpu - likely it's e-fused out for
    a binning purposes).

    My CPOE work server identifies as Intel(R) Gold 6246R CPU @3.40Ghz
    and does support AVX-512. It's much more expensive than a typical non-corporate user would be able to afford.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.arch on Wed Jul 1 21:46:29 2026
    From Newsgroup: comp.arch

    BGB <cr88192@gmail.com> writes:
    On 7/1/2026 8:54 AM, Anton Ertl wrote:
    jgd@cix.co.uk (John Dallman) writes:
    Fortunately, we were only storing strings and
    passing them on, so the limited amount of code he wrote is compatible
    with UTF-16, and the handling for 8-bit characters works with UTF-8.

    Yes, that's the usual case and that's why UTF-8 is a good approach for
    code that was designed for ASCII and Latin-1, but also for double-byte
    character set encodings with single-byte code units, such as Big-5 or
    Shift JIS. And likewise, UTF-16 is a good approach for code that was
    designed for Unicode 1.0.

    But of course, if the Unicode people had realized that from the start,
    we would never had UCS2 and thus no UTF-16. And we would have been
    spared a lot of pain.


    Well, it is a situation of:
    UTF-8, Usually best-case option here for string storage;
    UTF-16, Kinda meh, works well if one assumes UCS-2 is enough;

    Actually UTF-16 is the worst of the bunch, particuarly for
    interoperabilty (due to byte-ordering, even if the BOM is present).

    It should be avoided in all new and non windows projects, IMO.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Thu Jul 2 05:20:30 2026
    From Newsgroup: comp.arch

    scott@slp53.sl.home (Scott Lurndal) writes:
    My 2025 home desktop identifies as Intel(R) Core(TM) i7-14700 (DELL)

    In 2025 you could have bought a machine with a Ryzen 7xxx (Zen4), 8xxx
    (Zen 4), or 9xxx (Zen 5), all with AVX-512, although maybe not from
    DELL.

    Neither of them support AVX-512 (not surprising for the 2013 cpu, but >somewhat surprising for the 2025 cpu - likely it's e-fused out for
    a binning purposes).

    The problem is that the E-Cores do not support AVX-512, apparently
    because Intel thought up the concept of hybrid CPUs only late in the
    game, and did not tell the design teams of the E-Cores to design
    AVX-512 in. So when they brought out the hybrid CPUs, they disabled
    AVX-512 for the P-Cores in order to provide the same architecture on
    both kinds of cores. Originally you could enable AVX-512 in the BIOS
    if you disabled the E-Cores, but later they removed this option, and
    even when they brought out the Socket-1700 Xeons where the E-cores are disabled, they still disabled AVX-512.

    Supposedly the Nova Lake which will probably come out early next year
    will have AVX10 (i.e., AVX-512 with a saner feature reporting
    interface), but, more importantly, APX (32 GPRs and 3-address
    instructions).

    My CPOE work server identifies as Intel(R) Gold 6246R CPU @3.40Ghz
    and does support AVX-512. It's much more expensive than a typical >non-corporate user would be able to afford.

    At the moment the cheapest CPU I can buy with AVX-512 is a Ryzen 5
    8400F (EUR 111 boxed, EUR 94 tray), or, if you don't want to use a
    discrete graphics card, a Ryzen 5 7400 for EUR 119 (boxed).

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Thu Jul 2 05:41:24 2026
    From Newsgroup: comp.arch

    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    My last work laptop might have been Core 11, but now that I'm retired my >fastest machine is an Acer with a "13th Gen Intel(R) Core(TM) i7-1365U"
    cpu which only supports AVX2.

    After providing AVX-512 in the 10th and 11th generation for laptops,
    Intel eliminated in in the 12th and following generations, for reasons discussed in <2026Jul2.072030@mips.complang.tuwien.ac.at>. You should
    have bought a laptop with a Ryzen 7x4x for AVX-512.

    My other PC is a 7 year old Microsoft Surface Pro 7 which does handle
    AVX afair, but certainly nothing beyond that.

    Actually, according to <https://support.microsoft.com/en-us/surface/models/surface-pro-7-specs-and-features>
    the Surface Pro 7 has one of

    Dual-core 10th Gen Intel-< Corerao i3-1005G1 Processor
    Quad-core 10th Gen Intel-< Corerao i5-1035G4 Processor
    Quad-core 10th Gen Intel-< Corerao i7-1065G7 Processor

    all of which support AVX-512 (Intel 10th laptop generation).

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Thu Jul 2 12:28:17 2026
    From Newsgroup: comp.arch

    Scott Lurndal wrote:
    BGB <cr88192@gmail.com> writes:
    On 7/1/2026 8:54 AM, Anton Ertl wrote:
    jgd@cix.co.uk (John Dallman) writes:
    Fortunately, we were only storing strings and
    passing them on, so the limited amount of code he wrote is compatible
    with UTF-16, and the handling for 8-bit characters works with UTF-8.

    Yes, that's the usual case and that's why UTF-8 is a good approach for
    code that was designed for ASCII and Latin-1, but also for double-byte
    character set encodings with single-byte code units, such as Big-5 or
    Shift JIS. And likewise, UTF-16 is a good approach for code that was
    designed for Unicode 1.0.

    But of course, if the Unicode people had realized that from the start,
    we would never had UCS2 and thus no UTF-16. And we would have been
    spared a lot of pain.


    Well, it is a situation of:
    UTF-8, Usually best-case option here for string storage;
    UTF-16, Kinda meh, works well if one assumes UCS-2 is enough;

    Actually UTF-16 is the worst of the bunch, particuarly for
    interoperabilty (due to byte-ordering, even if the BOM is present).

    It should be avoided in all new and non windows projects, IMO.


    It should in fact be avoided even for Windows: Keep everything in UTF8,
    then translate only when you need to call a Windows API.

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Thu Jul 2 12:36:49 2026
    From Newsgroup: comp.arch

    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    My last work laptop might have been Core 11, but now that I'm retired my
    fastest machine is an Acer with a "13th Gen Intel(R) Core(TM) i7-1365U"
    cpu which only supports AVX2.

    After providing AVX-512 in the 10th and 11th generation for laptops,
    Intel eliminated in in the 12th and following generations, for reasons discussed in <2026Jul2.072030@mips.complang.tuwien.ac.at>. You should
    have bought a laptop with a Ryzen 7x4x for AVX-512.

    My other PC is a 7 year old Microsoft Surface Pro 7 which does handle
    AVX afair, but certainly nothing beyond that.

    Actually, according to <https://support.microsoft.com/en-us/surface/models/surface-pro-7-specs-and-features>
    the Surface Pro 7 has one of

    Dual-core 10th Gen Intel|e-< Core|orCR-o i3-1005G1 Processor
    Quad-core 10th Gen Intel|e-< Core|orCR-o i5-1035G4 Processor
    Quad-core 10th Gen Intel|e-< Core|orCR-o i7-1065G7 Processor

    all of which support AVX-512 (Intel 10th laptop generation).
    Thank You!
    I had no idea that it had the cache-line size SIMD engine, I simply took it for granted that it had less features than the ~3 year old Acer.
    My model has the middle (i5) variant.
    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Thu Jul 2 11:21:28 2026
    From Newsgroup: comp.arch

    On 7/2/2026 3:28 AM, Terje Mathisen wrote:
    Scott Lurndal wrote:
    BGB <cr88192@gmail.com> writes:
    On 7/1/2026 8:54 AM, Anton Ertl wrote:
    jgd@cix.co.uk (John Dallman) writes:
    Fortunately, we were only storing strings and
    passing them on, so the limited amount of code he wrote is compatible >>>>> with UTF-16, and the handling for 8-bit characters works with UTF-8.

    Yes, that's the usual case and that's why UTF-8 is a good approach for >>>> code that was designed for ASCII and Latin-1, but also for double-byte >>>> character set encodings with single-byte code units, such as Big-5 or
    Shift JIS.-a And likewise, UTF-16 is a good approach for code that was >>>> designed for Unicode 1.0.

    But of course, if the Unicode people had realized that from the start, >>>> we would never had UCS2 and thus no UTF-16.-a And we would have been
    spared a lot of pain.


    Well, it is a situation of:
    -a-a UTF-8, Usually best-case option here for string storage;
    -a-a UTF-16, Kinda meh, works well if one assumes UCS-2 is enough;

    Actually UTF-16 is the worst of the bunch, particuarly for
    interoperabilty (due to byte-ordering, even if the BOM is present).

    It should be avoided in all new and non windows projects, IMO.


    It should in fact be avoided even for Windows: Keep everything in UTF8,
    then translate only when you need to call a Windows API.

    Windows API? The *A or *W versions? ;^D
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Thu Jul 2 19:35:31 2026
    From Newsgroup: comp.arch


    BGB <cr88192@gmail.com> posted:

    On 7/1/2026 10:48 AM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/30/2026 6:28 PM, Michael S wrote:
    On Tue, 30 Jun 2026 20:57:06 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    Anton Ertl wrote:
    --------------------
    Does seem like (intuitively, untested) it should be possible to turn the >> reciprocal part of an FDIV into a bunch of table lookups and subtracts
    (each covering a part of the mantissa). Then the rest becomes a normal
    FMUL. Exponent is easier to determine.

    One can do a reciprocal in about 2/3rds of a DIV.
    One cannot get IEEE 754 quality rounded results using a reciprocal,
    without a Newton-Rafson iteration.


    Possibly, but generally both tend to land on the same value.

    The thing is, and FPU is either IEEE correct or it is not.
    An architecture is either IEEE correct or it is not.
    There is no way around this--no matter what you may believe.

    If 100% of all 2^(2|u53) possible FIDVs are not 100% identical with
    IEEE correct FIDV values, the FPU is not IEEE correct. There is no
    other way to think about it.

    I guess if needed could try to run some stats on x/y vs x*(1.0/y) to see
    if there is a significant difference.

    You will see the error is greater than 0.5 ULP--which means it is not
    IEEE correct.

    Even if you make 1 rounding error every 1,000,000 FDIVs, it is still not
    IEEE correct.
    -------------------
    Well, vs the existing options:
    Do N-R in software (cheapest);
    Goldschmidt is cheaper than N-R.


    AFAICT cheap Goldschmidt needs at least a fully pipelined FPU to have
    much advantage over N-R (or to do it via SIMD).

    Goldschmidt has 2 independent multiplies per iteration.
    Newton-Raphson has 2 dependent multiplies per iteration.
    This means there are a large number of ways to make GS
    faster than N-R.
    ------------------
    ----------------------
    An instruction that occurs 1% of the time that takes 20-cycles, acts as if it occurs 20% of the time.


    FDIV is usually very rare, except when it is not...

    Most code could accept it as trap-and-emulate...

    Most supercomputer codes require an FDIV that is on the order of 4|u
    the latency of FMUL or FMAC.

    But, if using naive divide and trap-and-emulate for Quake's SW renderer
    or similar, it tanks.

    Proving, once again, that either:
    a) you are an idiot,
    b) you cannot learn,
    c) you do not listen !!!

    So, which is it ???

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Thu Jul 2 19:39:14 2026
    From Newsgroup: comp.arch


    anton@mips.complang.tuwien.ac.at (Anton Ertl) posted:

    scott@slp53.sl.home (Scott Lurndal) writes:
    My 2025 home desktop identifies as Intel(R) Core(TM) i7-14700 (DELL)

    In 2025 you could have bought a machine with a Ryzen 7xxx (Zen4), 8xxx
    (Zen 4), or 9xxx (Zen 5), all with AVX-512, although maybe not from
    DELL.

    Neither of them support AVX-512 (not surprising for the 2013 cpu, but >somewhat surprising for the 2025 cpu - likely it's e-fused out for
    a binning purposes).

    The problem is that the E-Cores do not support AVX-512, apparently
    because Intel thought up the concept of hybrid CPUs

    Only when one defines "thought up" as "I see several others have already
    done it an brought it to market".

    only late in the
    game, and did not tell the design teams of the E-Cores to design
    AVX-512 in. So when they brought out the hybrid CPUs, they disabled
    AVX-512 for the P-Cores in order to provide the same architecture on
    both kinds of cores. Originally you could enable AVX-512 in the BIOS
    if you disabled the E-Cores, but later they removed this option, and
    even when they brought out the Socket-1700 Xeons where the E-cores are disabled, they still disabled AVX-512.

    Supposedly the Nova Lake which will probably come out early next year
    will have AVX10 (i.e., AVX-512 with a saner feature reporting
    interface), but, more importantly, APX (32 GPRs and 3-address
    instructions).

    My CPOE work server identifies as Intel(R) Gold 6246R CPU @3.40Ghz
    and does support AVX-512. It's much more expensive than a typical >non-corporate user would be able to afford.

    At the moment the cheapest CPU I can buy with AVX-512 is a Ryzen 5
    8400F (EUR 111 boxed, EUR 94 tray), or, if you don't want to use a
    discrete graphics card, a Ryzen 5 7400 for EUR 119 (boxed).

    - anton
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Thu Jul 2 23:03:00 2026
    From Newsgroup: comp.arch

    Chris M. Thomasson wrote:
    On 7/2/2026 3:28 AM, Terje Mathisen wrote:
    Scott Lurndal wrote:
    BGB <cr88192@gmail.com> writes:
    On 7/1/2026 8:54 AM, Anton Ertl wrote:
    jgd@cix.co.uk (John Dallman) writes:
    Fortunately, we were only storing strings and
    passing them on, so the limited amount of code he wrote is compatible >>>>>> with UTF-16, and the handling for 8-bit characters works with UTF-8. >>>>>
    Yes, that's the usual case and that's why UTF-8 is a good approach for >>>>> code that was designed for ASCII and Latin-1, but also for double-byte >>>>> character set encodings with single-byte code units, such as Big-5 or >>>>> Shift JIS.|e-a And likewise, UTF-16 is a good approach for code that was >>>>> designed for Unicode 1.0.

    But of course, if the Unicode people had realized that from the start, >>>>> we would never had UCS2 and thus no UTF-16.|e-a And we would have been >>>>> spared a lot of pain.


    Well, it is a situation of:
    |e-a|e-a UTF-8, Usually best-case option here for string storage;
    |e-a|e-a UTF-16, Kinda meh, works well if one assumes UCS-2 is enough;

    Actually UTF-16 is the worst of the bunch, particuarly for
    interoperabilty (due to byte-ordering, even if the BOM is present).

    It should be avoided in all new and non windows projects, IMO.


    It should in fact be avoided even for Windows: Keep everything in
    UTF8, then translate only when you need to call a Windows API.

    Windows API? The *A or *W versions? ;^D
    Probably default to the W version unless you know that your UTF8 in
    reality is just 7-bit US ASCII. :-)
    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Thu Jul 2 16:10:24 2026
    From Newsgroup: comp.arch

    On 7/2/2026 2:03 PM, Terje Mathisen wrote:
    Chris M. Thomasson wrote:
    On 7/2/2026 3:28 AM, Terje Mathisen wrote:
    Scott Lurndal wrote:
    BGB <cr88192@gmail.com> writes:
    On 7/1/2026 8:54 AM, Anton Ertl wrote:
    jgd@cix.co.uk (John Dallman) writes:
    Fortunately, we were only storing strings and
    passing them on, so the limited amount of code he wrote is
    compatible
    with UTF-16, and the handling for 8-bit characters works with UTF-8. >>>>>>
    Yes, that's the usual case and that's why UTF-8 is a good approach >>>>>> for
    code that was designed for ASCII and Latin-1, but also for double- >>>>>> byte
    character set encodings with single-byte code units, such as Big-5 or >>>>>> Shift JIS.|e-a And likewise, UTF-16 is a good approach for code that >>>>>> was
    designed for Unicode 1.0.

    But of course, if the Unicode people had realized that from the
    start,
    we would never had UCS2 and thus no UTF-16.|e-a And we would have been >>>>>> spared a lot of pain.


    Well, it is a situation of:
    |e-a|e-a UTF-8, Usually best-case option here for string storage;
    |e-a|e-a UTF-16, Kinda meh, works well if one assumes UCS-2 is enough; >>>>
    Actually UTF-16 is the worst of the bunch, particuarly for
    interoperabilty (due to byte-ordering, even if the BOM is present).

    It should be avoided in all new and non windows projects, IMO.


    It should in fact be avoided even for Windows: Keep everything in
    UTF8, then translate only when you need to call a Windows API.

    Windows API? The *A or *W versions? ;^D

    Probably default to the W version unless you know that your UTF8 in
    reality is just 7-bit US ASCII. :-)


    ROFL! Yup. Use what ya need. ;^)

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From George Neuner@gneuner2@comcast.net to comp.arch on Fri Jul 3 01:54:21 2026
    From Newsgroup: comp.arch

    On Thu, 02 Jul 2026 05:41:24 GMT, anton@mips.complang.tuwien.ac.at
    (Anton Ertl) wrote:

    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    My last work laptop might have been Core 11, but now that I'm retired my >>fastest machine is an Acer with a "13th Gen Intel(R) Core(TM) i7-1365U" >>cpu which only supports AVX2.

    After providing AVX-512 in the 10th and 11th generation for laptops,
    Intel eliminated in in the 12th and following generations, for reasons >discussed in <2026Jul2.072030@mips.complang.tuwien.ac.at>. You should
    have bought a laptop with a Ryzen 7x4x for AVX-512.

    My other PC is a 7 year old Microsoft Surface Pro 7 which does handle
    AVX afair, but certainly nothing beyond that.

    Actually, according to ><https://support.microsoft.com/en-us/surface/models/surface-pro-7-specs-and-features>
    the Surface Pro 7 has one of

    Dual-core 10th Gen Intel-< Corerao i3-1005G1 Processor
    Quad-core 10th Gen Intel-< Corerao i5-1035G4 Processor
    Quad-core 10th Gen Intel-< Corerao i7-1065G7 Processor

    all of which support AVX-512 (Intel 10th laptop generation).

    - anton

    But not all Intel 10th generation have AVX-512. I have an i7-10700
    (Comet Lake) desktop that has AVX and AVX2, but not AVX-512.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Stephen Fuld@sfuld@alumni.cmu.edu.invalid to comp.arch on Thu Jul 2 23:07:19 2026
    From Newsgroup: comp.arch

    On 7/2/2026 12:35 PM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 7/1/2026 10:48 AM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/30/2026 6:28 PM, Michael S wrote:
    On Tue, 30 Jun 2026 20:57:06 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    Anton Ertl wrote:
    --------------------
    Does seem like (intuitively, untested) it should be possible to turn the >>>> reciprocal part of an FDIV into a bunch of table lookups and subtracts >>>> (each covering a part of the mantissa). Then the rest becomes a normal >>>> FMUL. Exponent is easier to determine.

    One can do a reciprocal in about 2/3rds of a DIV.
    One cannot get IEEE 754 quality rounded results using a reciprocal,
    without a Newton-Rafson iteration.


    Possibly, but generally both tend to land on the same value.

    The thing is, and FPU is either IEEE correct or it is not.
    An architecture is either IEEE correct or it is not.
    There is no way around this--no matter what you may believe.

    If 100% of all 2^(2|u53) possible FIDVs are not 100% identical with
    IEEE correct FIDV values, the FPU is not IEEE correct. There is no
    other way to think about it.

    I guess if needed could try to run some stats on x/y vs x*(1.0/y) to see
    if there is a significant difference.

    You will see the error is greater than 0.5 ULP--which means it is not
    IEEE correct.

    Even if you make 1 rounding error every 1,000,000 FDIVs, it is still not
    IEEE correct.
    -------------------
    Well, vs the existing options:
    Do N-R in software (cheapest);
    Goldschmidt is cheaper than N-R.


    AFAICT cheap Goldschmidt needs at least a fully pipelined FPU to have
    much advantage over N-R (or to do it via SIMD).

    Goldschmidt has 2 independent multiplies per iteration.
    Newton-Raphson has 2 dependent multiplies per iteration.
    This means there are a large number of ways to make GS
    faster than N-R.
    ------------------
    ----------------------
    An instruction that occurs 1% of the time that takes 20-cycles, acts as if >>> it occurs 20% of the time.


    FDIV is usually very rare, except when it is not...

    Most code could accept it as trap-and-emulate...

    Most supercomputer codes require an FDIV that is on the order of 4|u
    the latency of FMUL or FMAC.

    But, if using naive divide and trap-and-emulate for Quake's SW renderer
    or similar, it tanks.

    Proving, once again, that either:
    a) you are an idiot,
    b) you cannot learn,
    c) you do not listen !!!

    So, which is it ???

    I suspect it may be another manifestation of BGB's "bias" toward gaming
    and graphics, and not much experience with scientific applications. In graphics, if the FP calculations are off by a bit occasionally, it
    probably doesn't matter, and that is the world BGB is in. But as we
    have discussed in a different context, the requirements for FP in
    scientific calculations is different, and more exacting/less forgiving.
    While you, Mitch, certainly have experience with graphics requirements,
    you also have the experience in scientific applications that BGB
    apparently lacks.

    So if BGB is willing to accept that his design will only be acceptable
    for gaming/graphics applications and not for scientific applications,
    his FP implementation is perhaps OK. But if he wants it to be a general purpose CPU, then it probably isn't.
    --
    - Stephen Fuld
    (e-mail address disguised to prevent spam)
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Fri Jul 3 06:45:57 2026
    From Newsgroup: comp.arch

    George Neuner <gneuner2@comcast.net> writes:
    On Thu, 02 Jul 2026 05:41:24 GMT, anton@mips.complang.tuwien.ac.at
    (Anton Ertl) wrote:
    After providing AVX-512 in the 10th and 11th generation for laptops,
    ...
    But not all Intel 10th generation have AVX-512. I have an i7-10700
    (Comet Lake) desktop that has AVX and AVX2, but not AVX-512.

    Yes, that's why I wrote "for laptops". However, that is actually not
    precise enough. Intel sold Comet Lakes for power-hungry laptops (Core ix-10xxxH, and Ice Lake for the power-stingy laptops (Core ix-10xxGx).

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Fri Jul 3 07:22:25 2026
    From Newsgroup: comp.arch

    BGB <cr88192@gmail.com> writes:
    Ironically, I have a laptop I had gotten recentish which was the first >machine I got to have native AVX-256 (had a "Core i7 8th Gen").

    Enabling it still doesn't see much performance gain for some basic test >programs (assuming auto vectorization is doing its thing),

    Assuming that auto-vectorization will actually vectorize programs that
    can be vectorized by humans is, to the first approximation, wrong, and
    you see that in the lack of performance gain.

    but at least
    (as a plus point) enabling it doesn't actively make performance worse

    Auto-vectorization also degrades the performance in some cases; see,
    e.g., <2026Jan24.122830@mips.complang.tuwien.ac.at>:

    |Here's the output of ./gforth-fast onebench.fs compiled from the
    |current development version with gcc-12.2 and running on a Ryzen 5800X |(numbers are times, lower is better):
    |
    | sieve bubble matrix fib fft gcc options
    | 0.025 0.023 0.013 0.033 0.016 -O2
    | 0.025 0.023 0.013 0.037 0.016 -O3 -fno-tree-vectorize (gforth default)
    | 0.404 0.418 0.377 0.472 0.244 -O3 (with auto vectorization)
    | 0.145 0.122 0.124 0.122 0.073 gforth default, using --no-dynamic

    Or, for the bubble-sort of John Hennessy's small integer benchmarks, a
    factor 5.7 slowdown.

    I still remain personally skeptical that 256-bit SIMD is actually that
    much of a value add though, less so 512-bit SIMD.

    The higher width is just one benefit; every doubling doubles the
    throughput (up to memory bandwidth limits) for vectorizable code when
    working with long vectors.

    Other benefits are:

    AVX(2) adds unaligned memory accesses (SSE requires 16-byte alignment,
    which is idiotic) and three-address instructions.

    AVX-512 adds masked execution (whether a lane result is used is
    determined by a mask bit), 32 registers, and additional instructions
    like VPERMB.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Fri Jul 3 04:35:37 2026
    From Newsgroup: comp.arch

    On 7/3/2026 1:07 AM, Stephen Fuld wrote:
    On 7/2/2026 12:35 PM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 7/1/2026 10:48 AM, MitchAlsup wrote:

    BGB <cr88192@gmail.com> posted:

    On 6/30/2026 6:28 PM, Michael S wrote:
    On Tue, 30 Jun 2026 20:57:06 +0200
    Terje Mathisen <terje.mathisen@tmsw.no> wrote:

    Anton Ertl wrote:
    --------------------
    Does seem like (intuitively, untested) it should be possible to
    turn the
    reciprocal part of an FDIV into a bunch of table lookups and subtracts >>>>> (each covering a part of the mantissa). Then the rest becomes a normal >>>>> FMUL. Exponent is easier to determine.

    One can do a reciprocal in about 2/3rds of a DIV.
    One cannot get IEEE 754 quality rounded results using a reciprocal,
    without a Newton-Rafson iteration.

    Possibly, but generally both tend to land on the same value.

    The thing is, and FPU is either IEEE correct or it is not.
    An architecture is either IEEE correct or it is not.
    There is no way around this--no matter what you may believe.

    If 100% of all 2^(2|u53) possible FIDVs are not 100% identical with
    IEEE correct FIDV values, the FPU is not IEEE correct. There is no
    other way to think about it.
    I guess if needed could try to run some stats on x/y vs x*(1.0/y) to see >>> if there is a significant difference.

    You will see the error is greater than 0.5 ULP--which means it is not
    IEEE correct.

    Even if you make 1 rounding error every 1,000,000 FDIVs, it is still not
    IEEE correct.
    -------------------
    Well, vs the existing options:
    -a-a-a-a Do N-R in software (cheapest);
    Goldschmidt is cheaper than N-R.


    AFAICT cheap Goldschmidt needs at least a fully pipelined FPU to have
    much advantage over N-R (or to do it via SIMD).

    Goldschmidt has 2 independent multiplies per iteration.
    Newton-Raphson has 2 dependent multiplies per iteration.
    This means there are a large number of ways to make GS
    faster than N-R.
    ------------------
    ----------------------
    An instruction that occurs 1% of the time that takes 20-cycles, acts
    as if
    it occurs 20% of the time.


    FDIV is usually very rare, except when it is not...

    Most code could accept it as trap-and-emulate...

    Most supercomputer codes require an FDIV that is on the order of 4|u
    the latency of FMUL or FMAC.

    But, if using naive divide and trap-and-emulate for Quake's SW renderer
    or similar, it tanks.

    Proving, once again, that either:
    a) you are an idiot,
    b) you cannot learn,
    c) you do not listen !!!

    So, which is it ???

    I suspect it may be another manifestation of BGB's "bias" toward gaming
    and graphics, and not much experience with scientific applications.-a In graphics, if the FP calculations are off by a bit occasionally, it
    probably doesn't matter, and that is the world BGB is in.-a But as we
    have discussed in a different context, the requirements for FP in
    scientific calculations is different, and more exacting/less forgiving. While you, Mitch, certainly have experience with graphics requirements,
    you also have the experience in scientific applications that BGB
    apparently lacks.


    Possibly, or at least I haven't done much where exact FP results
    actually matter all that much.

    Whole lot of stuff where you ideally want it within some epsilon of the correct answer, or some specific cases where an exact result matters,
    but this is not usually the default, and usually in specific
    circumstances (for example, something like "SpiderMonkey" will require
    that integer operations of floating points produce exact results).


    Typically 3D graphics are looser, usually a context-dependent accuracy requirement.

    Say: 3D/OpenGL stuff: Needs ~ 16 .. 20 bits of accuracy in the mantissa
    for stuff like coord math. Things like pixel colors and audio need a
    fair bit less (usually 4 to 6 bits is sufficient).


    So if BGB is willing to accept that his design will only be acceptable
    for gaming/graphics applications and not for scientific applications,
    his FP implementation is perhaps OK.-a But if he wants it to be a general purpose CPU, then it probably isn't.


    Yeah, if an implementation was intended to be used in a context where
    exact IEEE results were needed, someone could pay for the cost of doing
    an exact FPU. I am not in this context though, and can't really afford
    an exact FPU.

    They probably wouldn't use my existing core for serious work here, as it
    would be a bad idea.


    Otherwise, my existing FPU core does not and can't claim IEEE compliance
    in hardware. It can be faked in software, but in some scenarios may come
    with a (potentially severe) performance penalty (mostly via emulation
    traps). Other scenarios, the impact is minor (mostly because the traps
    happen rarely).

    Some other instructions get slower.
    The FPU is entirely non-pipelined in the IEEE mode.
    In the normal mode, Binary16 and Binary32 SIMD ops can be pipelined.

    There are a range of FPU related instructions that are also just
    straight up disallowed with strict IEEE handling.

    Default is to leave it off though.


    But, as-is, getting a fully compliant double-precision FPU in hardware
    that is semi-fast is likely to exceed what I could reasonably pull off
    (in terms of resource costs) on the classes of FPGA I am targeting.


    Also my goal isn't really to do a "one true CPU core", rather my
    existing CPU could be more regarded as a reference implementation. If
    other people did implementations they would probably tweak stuff however
    best fits the use-case (or pick/choose features to add in or leave out).


    Sorta like RISC-V: The merit of RV is not that you can go out and buy something that competes with ARM or Intel/AMD chips, but rather that
    people can be free to tweak the ISA how they want (without needing to
    care as much). Even if the central committees want to avoid too much fragmentation, which also seems like a sensible goal.

    Though, ATM this means assuming that RV64G continues to work.




    For now, I am mostly focusing on games and other similar sorts of things.

    But, as noted here:
    Doom, Heretic, Hexen, ...:
    Works fairly well at this point.
    ROTT has also moved into the "works well" category.
    Quake:
    SW Quake: Works, but slow...
    GL Quake: Ironically faster.
    But, was also modified to use vertex lighting and similar.
    Quake 3:
    Very slow;
    Game itself eats a lot more CPU;
    It is comparably slightly less demanding on OpenGL itself,
    despite a lot more graphical effects.
    Like, it has less overdraw, but more expensive render logic.


    Then Misc:
    BGBTech3: Works sorta OK, but at short draw distances;
    Was able to make it generally faster than Quake / GLQuake.
    Mostly sorta like Minecraft but prioritizing low RAM use.
    Though, only at small draw-distances.
    Can also run pretty well on a 23 year old laptop.
    Also works well on a newer laptop (*1).

    Laptop CPU seems pretty fast, but its iGPU is seemingly meh. Laptop
    starts to chug a little with render distance larger than around 8 chunks
    (128 blocks), vs 16 (256 blocks) being usable on my PC. With my BT3
    engine I can seemingly push it up to around 384 blocks on my main PC
    without too much issue. Though, BT3 does tend to look a bit more glitchy
    due to its reliance on ray-casting for visibility determination.

    The laptop can seemingly still manage a 192 block draw distance though
    with BT3.

    Though, in normal Minecraft, had noted that there is a subtle
    performance killing in the form of excessive vegetation, and
    kelp/seagrass/etc blocks under water. Better perf mostly means stripping
    the land an ocean biomes of all of this. Less of an issue for BT3 though
    which mostly doesn't do this.

    Also terrain generation sticks to a 128-block limit, though the engine
    itself now supports higher and lower, but it works in a different way vs
    MC (an overhead region appears if one builds into it, and an underground region appears of one digs through the bottom of the world). Though, ATM
    this means an effective world-height of 384 blocks.


    To run on my CPU core though generally need to use a draw distance of
    around 24 or 32 blocks. In this case the engine builds a visible shell
    of every block hit by a raycast (for larger draw distances, it marks
    chunks that a raycast passes into/through).

    But, is a bit lacking in terms of being an actually interesting game
    though. As it was admittedly more of a test-case or proof-of-concept.



    Did start at least putting a look into trying to port Quake2, but
    haven't done so yet. In many ways it falls between Quake 1 and Quake 3.


    Did experiment with moving partly from world-space to screen-space subdivision, but performance effect is minor. Does still use world-space
    for dealing with near clip plane or frustum edges, but uses screen-space
    for geometry that is inside the frustum.


    ...


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Fri Jul 3 17:03:14 2026
    From Newsgroup: comp.arch

    John Levine <johnl@taugh.com> writes:
    According to Anton Ertl <anton@mips.complang.tuwien.ac.at>:
    Yes, that's the usual case and that's why UTF-8 is a good approach for
    code that was designed for ASCII and Latin-1, but also for double-byte >>character set encodings with single-byte code units, such as Big-5 or
    Shift JIS. And likewise, UTF-16 is a good approach for code that was >>designed for Unicode 1.0.

    UTF-16 isn't really a good approach for anything due to the ugly way it
    uses surrogates to shoehorn in code points above 64K. You can't tell
    whether a code point is one or two byte pairs without looking at each
    one,

    True, but that's rarely needed.

    and sorting or comparing them is painful.

    Sorting or comparing according to locale is painful indeed.

    One of the underappreciated aspects of UTF-8 is that a naive unsigned
    byte sorts or comparisons of UTF-8 strings gives you the same order as >sorting or comparing the equivalent UTF-32.

    Interesting, but does it really matter? If you compare or sort the
    strings that way, probably any total order is good enough.

    But of course, if the Unicode people had realized that from the start,
    we would never had UCS2 and thus no UTF-16. And we would have been
    spared a lot of pain.

    It was literally another case of "64K is enough for anyone."

    I think that the major mistake was the assumption that we need to deal
    with individual code points efficiently. There are not that many
    places where that is actually needed. If it was needed a lot, UTF-32
    would have won, not UTF-8.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Fri Jul 3 13:56:02 2026
    From Newsgroup: comp.arch

    On 7/3/2026 12:03 PM, Anton Ertl wrote:
    John Levine <johnl@taugh.com> writes:
    According to Anton Ertl <anton@mips.complang.tuwien.ac.at>:
    Yes, that's the usual case and that's why UTF-8 is a good approach for
    code that was designed for ASCII and Latin-1, but also for double-byte
    character set encodings with single-byte code units, such as Big-5 or
    Shift JIS. And likewise, UTF-16 is a good approach for code that was
    designed for Unicode 1.0.

    UTF-16 isn't really a good approach for anything due to the ugly way it
    uses surrogates to shoehorn in code points above 64K. You can't tell
    whether a code point is one or two byte pairs without looking at each
    one,

    True, but that's rarely needed.


    From a space POV, if the text consists of a lot of CJK characters or
    emojis, UTF-16 is a win.

    If any significant amount of ASCII characters, it is a loss.
    For alphabets like Greek or Cyrillic, it is break-even.

    For English or Western European languages, Codepage-1252 is typically
    the densest form.


    and sorting or comparing them is painful.

    Sorting or comparing according to locale is painful indeed.


    Usual thing IME is to ignore locale in both cases and compare according
    to raw values. For things that are not directly user facing, can ignore locale.

    Well, and treat things like case-insensitive compare as an obscure
    special case.


    One of the underappreciated aspects of UTF-8 is that a naive unsigned
    byte sorts or comparisons of UTF-8 strings gives you the same order as
    sorting or comparing the equivalent UTF-32.

    Interesting, but does it really matter? If you compare or sort the
    strings that way, probably any total order is good enough.


    Same strategy works for UTF-16, just using 16-bit values.


    But of course, if the Unicode people had realized that from the start,
    we would never had UCS2 and thus no UTF-16. And we would have been
    spared a lot of pain.

    It was literally another case of "64K is enough for anyone."

    I think that the major mistake was the assumption that we need to deal
    with individual code points efficiently. There are not that many
    places where that is actually needed. If it was needed a lot, UTF-32
    would have won, not UTF-8.


    Agreed.

    Most string operations either don't actually care much what the string contains, or can be done in a linear forward walk.

    Except for something like implementing a text-editor or similar, random
    access to individual characters isn't usually a thing.

    - anton

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Fri Jul 3 21:39:18 2026
    From Newsgroup: comp.arch

    Anton Ertl wrote:
    John Levine <johnl@taugh.com> writes:
    According to Anton Ertl <anton@mips.complang.tuwien.ac.at>:
    Yes, that's the usual case and that's why UTF-8 is a good approach for
    code that was designed for ASCII and Latin-1, but also for double-byte
    character set encodings with single-byte code units, such as Big-5 or
    Shift JIS. And likewise, UTF-16 is a good approach for code that was
    designed for Unicode 1.0.

    UTF-16 isn't really a good approach for anything due to the ugly way it
    uses surrogates to shoehorn in code points above 64K. You can't tell
    whether a code point is one or two byte pairs without looking at each
    one,

    True, but that's rarely needed.

    and sorting or comparing them is painful.

    Sorting or comparing according to locale is painful indeed.

    One of the underappreciated aspects of UTF-8 is that a naive unsigned
    byte sorts or comparisons of UTF-8 strings gives you the same order as
    sorting or comparing the equivalent UTF-32.

    Interesting, but does it really matter? If you compare or sort the
    strings that way, probably any total order is good enough.

    But of course, if the Unicode people had realized that from the start,
    we would never had UCS2 and thus no UTF-16. And we would have been
    spared a lot of pain.

    It was literally another case of "64K is enough for anyone."

    I think that the major mistake was the assumption that we need to deal
    with individual code points efficiently. There are not that many
    places where that is actually needed. If it was needed a lot, UTF-32
    would have won, not UTF-8.

    The real deal-breaker is the fact that if you use chunked storage, like
    most editors will do, then it becomes trivial to skip forward/back N
    character positions by simply jumping past any intermediate chunks, then iterate to locate the exact character.

    If you really care (locally) about direct linear UTF-32 access, then you simply unpack the current chunk, do whatever you need to do, then pack
    it back up.

    The very first time I needed to write a text editor, in Borland's Turbo Pascal, I simply used a list of lines, each limited by the TP 255-byte
    length limit. It was plenty fast enough, you could never see any delay
    between hitting a key and the screen update.

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From MitchAlsup@user5857@newsgrouper.org.invalid to comp.arch on Fri Jul 3 19:50:18 2026
    From Newsgroup: comp.arch


    Terje Mathisen <terje.mathisen@tmsw.no> posted:

    Anton Ertl wrote:
    John Levine <johnl@taugh.com> writes:
    According to Anton Ertl <anton@mips.complang.tuwien.ac.at>:
    -----------------------
    I think that the major mistake was the assumption that we need to deal
    with individual code points efficiently. There are not that many
    places where that is actually needed. If it was needed a lot, UTF-32
    would have won, not UTF-8.

    The real deal-breaker is the fact that if you use chunked storage, like
    most editors will do, then it becomes trivial to skip forward/back N character positions by simply jumping past any intermediate chunks, then iterate to locate the exact character.

    If you really care (locally) about direct linear UTF-32 access, then you simply unpack the current chunk, do whatever you need to do, then pack
    it back up.

    The very first time I needed to write a text editor, in Borland's Turbo Pascal, I simply used a list of lines, each limited by the TP 255-byte length limit. It was plenty fast enough, you could never see any delay between hitting a key and the screen update.

    My NCR BASIC interpreter allocated strings in 18-byte quanta, 2-byte
    next pointer and up to 16 characters*. Thing was, the BASIC string
    handling functions were faster on my interpreter than they were on
    another NCR BASIC interpreter. In addition, there was never a time
    one had to re-pack string-space since it either had another quanta
    or it did not. This saved a bunch of time in the cash register things
    the interpreter had to perform, and made it much more real time.
    {{Circa 1978-1979}}

    (*) the string variable contained the current size of the string and
    a pointer to the first quanta.

    Terje


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Fri Jul 3 22:02:09 2026
    From Newsgroup: comp.arch

    BGB wrote:
    On 7/3/2026 12:03 PM, Anton Ertl wrote:
    John Levine <johnl@taugh.com> writes:
    According to Anton Ertl <anton@mips.complang.tuwien.ac.at>:
    Yes, that's the usual case and that's why UTF-8 is a good approach for >>>> code that was designed for ASCII and Latin-1, but also for double-byte >>>> character set encodings with single-byte code units, such as Big-5 or
    Shift JIS.-a And likewise, UTF-16 is a good approach for code that was >>>> designed for Unicode 1.0.

    UTF-16 isn't really a good approach for anything due to the ugly way it
    uses surrogates to shoehorn in code points above 64K.-a You can't tell
    whether a code point is one or two byte pairs without looking at each>>> one,

    True, but that's rarely needed.


    From a space POV, if the text consists of a lot of CJK characters or emojis, UTF-16 is a win.

    If any significant amount of ASCII characters, it is a loss.
    -a For alphabets like Greek or Cyrillic, it is break-even.

    For English or Western European languages, Codepage-1252 is typically
    the densest form.


    and sorting or comparing them is painful.

    Sorting or comparing according to locale is painful indeed.


    Usual thing IME is to ignore locale in both cases and compare according
    to raw values. For things that are not directly user facing, can ignore locale.

    Well, and treat things like case-insensitive compare as an obscure
    special case.


    One of the underappreciated aspects of UTF-8 is that a naive unsigned>>> byte sorts or comparisons of UTF-8 strings gives you the same order as
    sorting or comparing the equivalent UTF-32.

    Interesting, but does it really matter?-a If you compare or sort the
    strings that way, probably any total order is good enough.


    Same strategy works for UTF-16, just using 16-bit values.
    Sort and search in a case-insensitive manner is actually quite hard to
    make really fast.
    For sort of more complicated character sets like Norwegian, the best
    I've been able to come up with is to start by making monocased keys
    consisting of the first N characters plus the index of the original
    string. The same stage can also convert special character pairs into
    single values, like AA->|a, AE->|a, OE->|y etc.
    After the first stage sort of these keys, do a fixup stage using the
    full strings for any duplicate keys.
    Boyer-Moore string search needs something similar where the original
    search key is copied twice, into a lower-only and an upper-only version.
    This is needed because inline monocasing of arbitrary Unicode letters
    can be quite expensive.
    Looking on the bright side, you can use a small (byte-sized) skip table
    even for 16 or 32-bit character encodings or directly on the UTF-8
    sequence, you just have to accept that some of those skips will be
    non-optimal since each entry needs to contains the shortest skip length indicated by any colliding character keys.
    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Fri Jul 3 20:15:51 2026
    From Newsgroup: comp.arch

    On 7/3/2026 3:02 PM, Terje Mathisen wrote:
    BGB wrote:
    On 7/3/2026 12:03 PM, Anton Ertl wrote:
    John Levine <johnl@taugh.com> writes:
    According to Anton Ertl <anton@mips.complang.tuwien.ac.at>:
    Yes, that's the usual case and that's why UTF-8 is a good approach for >>>>> code that was designed for ASCII and Latin-1, but also for double-byte >>>>> character set encodings with single-byte code units, such as Big-5 or >>>>> Shift JIS.-a And likewise, UTF-16 is a good approach for code that was >>>>> designed for Unicode 1.0.

    UTF-16 isn't really a good approach for anything due to the ugly way it >>>> uses surrogates to shoehorn in code points above 64K.-a You can't tell >>>> whether a code point is one or two byte pairs without looking at each
    one,

    True, but that's rarely needed.


    -aFrom a space POV, if the text consists of a lot of CJK characters or
    emojis, UTF-16 is a win.

    If any significant amount of ASCII characters, it is a loss.
    -a-a For alphabets like Greek or Cyrillic, it is break-even.

    For English or Western European languages, Codepage-1252 is typically
    the densest form.


    and sorting or comparing them is painful.

    Sorting or comparing according to locale is painful indeed.


    Usual thing IME is to ignore locale in both cases and compare
    according to raw values. For things that are not directly user facing,
    can ignore locale.

    Well, and treat things like case-insensitive compare as an obscure
    special case.


    One of the underappreciated aspects of UTF-8 is that a naive unsigned
    byte sorts or comparisons of UTF-8 strings gives you the same order as >>>> sorting or comparing the equivalent UTF-32.

    Interesting, but does it really matter?-a If you compare or sort the
    strings that way, probably any total order is good enough.


    Same strategy works for UTF-16, just using 16-bit values.

    Sort and search in a case-insensitive manner is actually quite hard to
    make really fast.

    For sort of more complicated character sets like Norwegian, the best
    I've been able to come up with is to start by making monocased keys consisting of the first N characters plus the index of the original
    string. The same stage can also convert special character pairs into
    single values, like AA->|a, AE->|a, OE->|y etc.

    After the first stage sort of these keys, do a fixup stage using the
    full strings for any duplicate keys.

    Boyer-Moore string search needs something similar where the original
    search key is copied twice, into a lower-only and an upper-only version.

    This is needed because inline monocasing of arbitrary Unicode letters
    can be quite expensive.

    Looking on the bright side, you can use a small (byte-sized) skip table
    even for 16 or 32-bit character encodings or directly on the UTF-8
    sequence, you just have to accept that some of those skips will be non- optimal since each entry needs to contains the shortest skip length indicated by any colliding character keys.


    Case normalization and then compare is possible, but in this case
    doesn't need to depend all that much on the encoding (mostly the same
    stuff regardless of character encoding scheme).


    General strategy though I had used is to decompose any combined
    characters, normalize for case (in the relevant alphabets), and then
    compare. Could be skipped if the input string is purely ASCII or with
    limited subsets (which allow for simpler case-folding).

    To what limited extent I had dealt with this, had mostly used "locale agnostic" rules:
    After any basic normalization and case-folding, simply compare based on codepoint ordering.


    Though another option could be to run general recombining rules, convert
    back to UTF-8 or similar, and use the byte-compare of the UTF-8 strings
    as the canonical order. In some variants, such as M-UTF-8, relative
    ordering between UTF-8 and UTF-16 should be the same, but may differ in
    plain UTF-8 if non-BMP characters are not represented as surrogate pairs within the encoded stream (though the more common use of M-UTF-8 is to
    allow an embedded NUL character).


    Though can note that in my project, had mostly ended up going for case-sensitive filesystem handling even for traditionally
    case-insensitive filesystems (like FAT).

    There is also a certain level of VFS level name normalization, but this
    needs to be kept fairly generic and stable as changes in the
    normalization rules could potentially interfere with the ability to
    access files (and any locale-dependent behaviors would need to be avoided).


    Say for example:
    Program saves a file with non-ASCII characters in the name;
    OS's VFS changes its normalization rules;
    Program tries to open the same file;
    Its request gets re-normalized to something different;
    Path lookup fails because the names were stored under the old rules and
    so no longer equivalent.

    Doing re-normalization when searching directories being undesirable as
    it would add significantly to the lookup cost (or would break entirely
    in the case of hashed or search-tree based lookups).

    No good solution beyond, say:
    Do nothing at all, any name normalization is left as an
    application-level concern (AFAIK, Linux partly goes this way, depending
    on FS driver, but mostly leans in a "raw UTF-8" direction);
    Use super generic normalization rules and assume that they wont be
    changed later (errm, route I went for now);
    Explicit versioning of the rules (which are then frozen at "mkfs" or
    similar, and VFS is assumed to keep old rules around in case rules change); Storing the case-folding and collation related tables in the filesystem
    itself (eg, NTFS does this);
    ...



    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Sat Jul 4 09:32:28 2026
    From Newsgroup: comp.arch

    BGB <cr88192@gmail.com> writes:
    From a space POV, if the text consists of a lot of CJK characters or
    emojis, UTF-16 is a win.

    If any significant amount of ASCII characters, it is a loss.

    Exactly. And, as argued on one of the pages, the result of the latter
    is that even for text with relatively many CJK characters such as
    Chinese, Japanese, or Korean web pages, UTF-16 loses, because they
    contains enough ASCII in the form of spaces, punctuation and HTML
    characters.

    Concerning Emojis, I don't want to imagine a text that consists of a
    lot of emojis and nothing else, but anyway, looking at <https://www.unicode.org/emoji/charts/full-emoji-list.html>, all
    emojis contain at least one code point that's not included in the BMP,
    and therefore needs 4 bytes in UTF-16. These code points also need 4
    bytes in UTF-8, so that's not a win for UTF-16.

    Some emojis are composed of several code points, the first of which is
    not in the BMP, but often the others are, and these following code
    points take 2 bytes in UTF-16 and 3 bytes in UTF-8, so one might
    consider them a win for UTF-16. The most extreme on the page seems to
    be, e.g., 425 with 7 code points, one of which is not in the BMP,
    i.e., 16 bytes in UTF-16, and 22 byes in UTF-8, for a whopping winning
    margin of 1.375. So if you have a lot of text consisting of emojis of
    women kneeling, facing right, then UTF-16 is for you.

    I finally found an emoji in the BMP: U+26F9 (person bouncing ball), so
    here the UTF-16 advantage is even a factor 1.5. There are a few more
    emojis in the BMP, but the wast majority has at least one code point
    outside the BMP, and consist of just a single code point, and for
    those emojis UTF-16 does not give any advantage.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Sun Jul 5 05:25:38 2026
    From Newsgroup: comp.arch

    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    The real deal-breaker is the fact that if you use chunked storage, like
    most editors will do, then it becomes trivial to skip forward/back N >character positions by simply jumping past any intermediate chunks, then >iterate to locate the exact character.

    Even if you don't, and use, e.g., a gap buffer (which I found much
    easier to work with than some line-based representation when I wrote
    an editor a long time ago),

    * Cursor forward and back is cheap (but requires a change compared to
    a character set with fixed width characters; but note that thanks to
    composed characters you don't have fixed width even with UTF-32).

    * Cursor up and down needs to look for the newline anyway; it does
    need a way to determine the horizontal position of the cursor in the
    old line and to get there in the new line (but thanks to composed
    characters and characters with different widths, even in
    "monospaced" fonts, you have to do that even with UTF-32.

    * Page up and down is like cursor up and down, but with multiple
    newlines.

    * Search searches for the string, and in that case the code for an
    editor that understands UTF-8 will usually be the same as for an
    editor for 8-bit encodings like ASCII or ISO Latin-1.

    * Displaying the buffer on the screen needs knowledge about the size
    of each character in bytes and its width on screen. You could
    display code point by code point, and treat composing code points as
    having width 0, and then UTF-32 avoids the UTF-8 need to deal with
    variable-width code points, but that's vary small change in the
    overall scheme of things.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Sun Jul 5 12:07:53 2026
    From Newsgroup: comp.arch

    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    The real deal-breaker is the fact that if you use chunked storage, like
    most editors will do, then it becomes trivial to skip forward/back N
    character positions by simply jumping past any intermediate chunks, then
    iterate to locate the exact character.

    Even if you don't, and use, e.g., a gap buffer (which I found much
    easier to work with than some line-based representation when I wrote
    an editor a long time ago),

    I have used a gap buffer in later years, after getting out of the 16bit
    world.

    Back in the MsDos era, I would have been limited to just 64KB of text
    unless I accepted the need for some kind of chunking, switching to one
    "far ptr" per line meant that I now could have 16K lines, and it made
    all those vertical cursor moves trivial.

    The next stage would be to use 16-bit line indices into multiple 64KB
    chunks, or bite the bullet and start using disk-based storage for
    everything outside the active area.

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Sun Jul 5 10:40:37 2026
    From Newsgroup: comp.arch

    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    Even if you don't, and use, e.g., a gap buffer (which I found much
    easier to work with than some line-based representation when I wrote
    an editor a long time ago),

    I have used a gap buffer in later years, after getting out of the 16bit >world.

    Back in the MsDos era, I would have been limited to just 64KB of text

    My editor was on the C64, so I was limited to 64KB of text anyway.
    But with 170KB disks, that was not a limit I found restrictive.

    unless I accepted the need for some kind of chunking, switching to one
    "far ptr" per line meant that I now could have 16K lines, and it made
    all those vertical cursor moves trivial.

    You still have to check the line length. And often move the line when
    it grows.

    I found that special-casing line ends added lots of complications to
    my line-based editor, which all went away in the gap-buffer based
    editor. E.g., when the user presses Backspace at the start of a line,
    you have to combine the current line with the previous line, whereas
    the gap buffer version just deletes the newline character before the
    cursor, just like what Backspace does to any other character.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Sun Jul 5 13:05:50 2026
    From Newsgroup: comp.arch

    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    Even if you don't, and use, e.g., a gap buffer (which I found much
    easier to work with than some line-based representation when I wrote
    an editor a long time ago),

    I have used a gap buffer in later years, after getting out of the 16bit
    world.

    Back in the MsDos era, I would have been limited to just 64KB of text

    My editor was on the C64, so I was limited to 64KB of text anyway.
    But with 170KB disks, that was not a limit I found restrictive.

    unless I accepted the need for some kind of chunking, switching to one
    "far ptr" per line meant that I now could have 16K lines, and it made
    all those vertical cursor moves trivial.

    You still have to check the line length. And often move the line when
    it grows.

    My choice was strongly influenced by the fact that Turbo Pascal had length-preceeded strings, so the actual/current length is always just a
    single byte load away.

    I found that special-casing line ends added lots of complications to
    my line-based editor, which all went away in the gap-buffer based
    editor. E.g., when the user presses Backspace at the start of a line,
    you have to combine the current line with the previous line, whereas
    the gap buffer version just deletes the newline character before the
    cursor, just like what Backspace does to any other character.

    The only hard thing for me was the need to reduce the line count, the
    actual merging was just a line[i-1] := line[i-1]+line[i];

    Anyway, we are (mostly) in violent agreement: Having a flat address
    space and a gap buffer makes almost everything much simpler. If you need
    to do massive block operations all the time, then it might make sense to
    move to a more complicated data structure.

    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.arch on Sun Jul 5 14:18:47 2026
    From Newsgroup: comp.arch

    On 7/5/2026 4:05 AM, Terje Mathisen wrote:
    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    Even if you don't, and use, e.g., a gap buffer (which I found much
    easier to work with than some line-based representation when I wrote
    an editor a long time ago),

    I have used a gap buffer in later years, after getting out of the 16bit
    world.

    Back in the MsDos era, I would have been limited to just 64KB of text

    My editor was on the C64, so I was limited to 64KB of text anyway.
    But with 170KB disks, that was not a limit I found restrictive.

    unless I accepted the need for some kind of chunking, switching to one
    "far ptr" per line meant that I now could have 16K lines, and it made
    all those vertical cursor moves trivial.

    You still have to check the line length.-a And often move the line when
    it grows.

    My choice was strongly influenced by the fact that Turbo Pascal had length-preceeded strings, so the actual/current length is always just a single byte load away.

    I found that special-casing line ends added lots of complications to
    my line-based editor, which all went away in the gap-buffer based
    editor.-a E.g., when the user presses Backspace at the start of a line,
    you have to combine the current line with the previous line, whereas
    the gap buffer version just deletes the newline character before the
    cursor, just like what Backspace does to any other character.

    The only hard thing for me was the need to reduce the line count, the
    actual merging was just a line[i-1] := line[i-1]+line[i];

    Anyway, we are (mostly) in violent agreement: Having a flat address
    space and a gap buffer makes almost everything much simpler. If you need
    to do massive block operations all the time, then it might make sense to move to a more complicated data structure.

    Fwiw, for some reason this made me think of Ropes:

    https://en.wikipedia.org/wiki/Rope_(data_structure)

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From BGB@cr88192@gmail.com to comp.arch on Sun Jul 5 17:51:46 2026
    From Newsgroup: comp.arch

    On 7/5/2026 6:05 AM, Terje Mathisen wrote:
    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    Even if you don't, and use, e.g., a gap buffer (which I found much
    easier to work with than some line-based representation when I wrote
    an editor a long time ago),

    I have used a gap buffer in later years, after getting out of the 16bit
    world.

    Back in the MsDos era, I would have been limited to just 64KB of text

    My editor was on the C64, so I was limited to 64KB of text anyway.
    But with 170KB disks, that was not a limit I found restrictive.

    unless I accepted the need for some kind of chunking, switching to one
    "far ptr" per line meant that I now could have 16K lines, and it made
    all those vertical cursor moves trivial.

    You still have to check the line length.-a And often move the line when
    it grows.

    My choice was strongly influenced by the fact that Turbo Pascal had length-preceeded strings, so the actual/current length is always just a single byte load away.

    I found that special-casing line ends added lots of complications to
    my line-based editor, which all went away in the gap-buffer based
    editor.-a E.g., when the user presses Backspace at the start of a line,
    you have to combine the current line with the previous line, whereas
    the gap buffer version just deletes the newline character before the
    cursor, just like what Backspace does to any other character.

    The only hard thing for me was the need to reduce the line count, the
    actual merging was just a line[i-1] := line[i-1]+line[i];

    Anyway, we are (mostly) in violent agreement: Having a flat address
    space and a gap buffer makes almost everything much simpler. If you need
    to do massive block operations all the time, then it might make sense to move to a more complicated data structure.


    When I had done text editors (sometimes) it had usually been using a
    line buffer.

    Though, often (maybe or maybe not controversially) there was often an
    implicit imposed line-length limit (if exceeded, the editor would
    basically force a wrap, though the internal limit was typically larger
    than the usual 76..80 column word wrap). For wrapping, would usually
    find an earlier point to add an implicit line break (would be flagged as
    not a true line break).

    In some cases, had used a 32-bit character format, which allowed both a codepoint and some additional display metadata to be stored (such as for syntax highlighting or similar). Though, this could make sense to be
    limited to the "on-screen" part of the text view (everything that is not currently visible does not need this).



    Not sure what approach "Windows Notepad" uses, but had noted that
    whatever approach it uses tends to become glacially slow if the text
    file is too big.

    Some other editors, like Notepad2 or Notepad++, while happily load a
    multi-MB text file without bogging down. Though, it is kinda funny in
    this modern era to have text-editors that can't really handle files over
    a few hundred kB without visibly bogging down.

    Looking it up: Apparently the original Notepad just uses a single large
    linear buffer for everything.

    ...

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Terje Mathisen@terje.mathisen@tmsw.no to comp.arch on Mon Jul 6 20:40:40 2026
    From Newsgroup: comp.arch

    Chris M. Thomasson wrote:
    On 7/5/2026 4:05 AM, Terje Mathisen wrote:
    Anton Ertl wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Anton Ertl wrote:
    Even if you don't, and use, e.g., a gap buffer (which I found much
    easier to work with than some line-based representation when I wrote >>>>> an editor a long time ago),

    I have used a gap buffer in later years, after getting out of the 16bit >>>> world.

    Back in the MsDos era, I would have been limited to just 64KB of text

    My editor was on the C64, so I was limited to 64KB of text anyway.
    But with 170KB disks, that was not a limit I found restrictive.

    unless I accepted the need for some kind of chunking, switching to one >>>> "far ptr" per line meant that I now could have 16K lines, and it made
    all those vertical cursor moves trivial.

    You still have to check the line length.|e-a And often move the line when >>> it grows.

    My choice was strongly influenced by the fact that Turbo Pascal had
    length-preceeded strings, so the actual/current length is always just >> a single byte load away.

    I found that special-casing line ends added lots of complications to
    my line-based editor, which all went away in the gap-buffer based
    editor.|e-a E.g., when the user presses Backspace at the start of a line, >>> you have to combine the current line with the previous line, whereas
    the gap buffer version just deletes the newline character before the
    cursor, just like what Backspace does to any other character.

    The only hard thing for me was the need to reduce the line count, the >> actual merging was just a line[i-1] := line[i-1]+line[i];

    Anyway, we are (mostly) in violent agreement: Having a flat address
    space and a gap buffer makes almost everything much simpler. If you
    need to do massive block operations all the time, then it might make
    sense to move to a more complicated data structure.

    Fwiw, for some reason this made me think of Ropes:

    https://en.wikipedia.org/wiki/Rope_(data_structure)

    Ropes are of course one of the most efficient ways to implement chunked storage.
    Terje
    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Tue Jul 7 04:37:03 2026
    From Newsgroup: comp.arch

    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Chris M. Thomasson wrote:
    https://en.wikipedia.org/wiki/Rope_(data_structure)
    =20
    Ropes are of course one of the most efficient ways to implement chunked=20 >storage.

    Ropes are good for one thing: concatenating two strings. Everything
    else is somewhat complicated, and, on modern hardware, relatively slow
    even if the algorithmic complexity is the same; e.g., even if you do a
    lot of concatenation, as long as you also do quite a bit of
    "reporting" (as the page linked above calls it), e.g., searching the
    string, it's a good idea to consolidate short strings into flat
    strings rather than ropes to keep the rope overhead small. The
    crossover point depends on the balance between the various operations,
    but I expect that a crossover point that's smaller than 64 bytes will
    rarely be most efficient on modern hardware.

    As for editors, the gap buffer is simple and efficient for the usual operations, but if moving the data is expensive for some reason, the
    right alternative is the piece table, which has been designed for the
    purpose, and has been used successfully for the purpose <https://en.wikipedia.org/wiki/Piece_table#Usage>; however, as the
    various problems with Microsoft Word .doc documents show, better
    consolidate the representation for permanent storage.

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Stefan Monnier@monnier@iro.umontreal.ca to comp.arch on Tue Jul 7 13:37:58 2026
    From Newsgroup: comp.arch

    Anton Ertl [2026-07-07 04:37:03] wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Chris M. Thomasson wrote:
    https://en.wikipedia.org/wiki/Rope_(data_structure)
    =20
    Ropes are of course one of the most efficient ways to implement chunked=20 >>storage.
    Ropes are good for one thing: concatenating two strings.

    They're also pretty good to keep different versions of an editor's buffer.

    As for editors, the gap buffer is simple and efficient for the usual operations,

    Indeed. Especially as memory bandwidth has nicely increased over the
    years, so the extra cost incurred when moving the gap has been going
    down faster than the extra cost of indirecting through the various nodes
    of a rope.

    but if moving the data is expensive for some reason, the right
    alternative is the piece table, which has been designed for the
    purpose, and has been used successfully for the purpose <https://en.wikipedia.org/wiki/Piece_table#Usage>; however, as the
    various problems with Microsoft Word .doc documents show, better
    consolidate the representation for permanent storage.

    The "same" problem appears for the piece table itself: it can be kept as
    a tree (like ropes would) or as an array when you may want to use a gap
    to handle insertion/deletion of elements.


    === Stefan
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Wed Jul 8 07:00:01 2026
    From Newsgroup: comp.arch

    Stefan Monnier <monnier@iro.umontreal.ca> writes:
    Anton Ertl [2026-07-07 04:37:03] wrote:
    Terje Mathisen <terje.mathisen@tmsw.no> writes:
    Chris M. Thomasson wrote:
    https://en.wikipedia.org/wiki/Rope_(data_structure)
    =20
    Ropes are of course one of the most efficient ways to implement chunked=20 >>>storage.
    Ropes are good for one thing: concatenating two strings.

    They're also pretty good to keep different versions of an editor's buffer.

    They can be implemented in that way, but that increases the
    complications.

    As for editors, the gap buffer is simple and efficient for the usual
    operations,

    Indeed. Especially as memory bandwidth has nicely increased over the
    years, so the extra cost incurred when moving the gap has been going
    down faster than the extra cost of indirecting through the various nodes
    of a rope.

    Not just that. My C64 has 64KB and it's CPU can move about 64KB/s
    (5/6 cycles load, 5/6 cycles store, 2 cycles increment, 3 cycles
    branch, and a little bit of overhead all 256 bytes, all at 1MHz). My
    Ryzen 8700G has 64GB and a memory bandwidth of 64GB/s (the memory
    controller and RAM can probably can do more, but the interface between
    the CCX and the RAM is limited to 64GB); I have not measured block
    copy yet, and it will not exceed 32GB/s, but basically block copy
    speed has roughly grown with RAM. Edited files typically have not
    grown as much, so the gap buffer works better than in the times of the
    C64.

    but if moving the data is expensive for some reason, the right
    alternative is the piece table, which has been designed for the
    purpose, and has been used successfully for the purpose
    <https://en.wikipedia.org/wiki/Piece_table#Usage>; however, as the
    various problems with Microsoft Word .doc documents show, better
    consolidate the representation for permanent storage.

    The "same" problem appears for the piece table itself: it can be kept as
    a tree (like ropes would) or as an array when you may want to use a gap
    to handle insertion/deletion of elements.

    I don't know which problem you mean. What I meant:

    1) .doc files were reportedly prone to corruption that only became
    apparent too late (so any easy recovery, and in many cases (no
    long-time backups), all recovery was impossible). The late discovery
    was probably due to the lazy way of access, and the corruption was
    probably due to bugs that were hard to detect due to the late
    discovery and were easy to make due to the complicated data structure.

    2) .doc files contain data that the user considers deleted. There
    were several high-profile cases of leakage of secret information due
    to this.

    What problem did you have in mind?

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.arch on Wed Jul 8 17:59:36 2026
    From Newsgroup: comp.arch

    anton@mips.complang.tuwien.ac.at (Anton Ertl) writes:
    My C64 has 64KB and it's CPU can move about 64KB/s
    (5/6 cycles load, 5/6 cycles store, 2 cycles increment, 3 cycles
    branch, and a little bit of overhead all 256 bytes, all at 1MHz). My
    Ryzen 8700G has 64GB and a memory bandwidth of 64GB/s (the memory
    controller and RAM can probably can do more, but the interface between
    the CCX and the RAM is limited to 64GB); I have not measured block
    copy yet, and it will not exceed 32GB/s, but basically block copy
    speed has roughly grown with RAM.

    I have now measured the speed of glibc 2.36 memmove on the Ryzen 8700G
    by measuring 1000 1GB copies:

    perf stat gforth-fast -e "1_000_000_000 constant s s allocate throw constant a a s 1 fill s allocate throw constant b a : foo 0 do a b s move loop ; 1000 foo bye"

    The result is that it copies about 24GB/s, i.e., the complete RAM of
    64GB in 2.9s. With recent RAM prices, tomorrow's PC will get faster
    by having less RAM to copy:-).

    - anton
    --
    'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
    Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Stefan Monnier@monnier@iro.umontreal.ca to comp.arch on Wed Jul 8 10:44:29 2026
    From Newsgroup: comp.arch

    Anton Ertl [2026-07-08 07:00:01] wrote:
    Stefan Monnier <monnier@iro.umontreal.ca> writes:
    Anton Ertl [2026-07-07 04:37:03] wrote:
    but if moving the data is expensive for some reason, the right
    alternative is the piece table, which has been designed for the
    purpose, and has been used successfully for the purpose
    <https://en.wikipedia.org/wiki/Piece_table#Usage>; however, as the
    various problems with Microsoft Word .doc documents show, better
    consolidate the representation for permanent storage.
    The "same" problem appears for the piece table itself: it can be kept as
    a tree (like ropes would) or as an array when you may want to use a gap
    to handle insertion/deletion of elements.
    I don't know which problem you mean.

    I was referring to the problem of "if moving the data is expensive for
    some reason". I should have been more careful to snip your last
    sentence about Word, sorry.


    === Stefan
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.arch on Thu Jul 9 02:35:39 2026
    From Newsgroup: comp.arch

    On Sat, 20 Jun 2026 15:39:06 -0500, BGB wrote:

    Lisp is a funny language: Big promises in the design; But, only
    deliver them poorly (and can't improve on the delivery of any given
    thing without eroding the original promises).

    I got into an argument over in comp.lang.lisp over why Python
    (specifically, CPython) tries to use reference-counting as a first
    resort in its memory-allocation management, only falling back to garbage-collection when it hits cycles which reference counts canrCOt
    handle. There were some there who kept insisting that the pure garbage collection approach (as used in current Lisps, also Java) is somehow
    superior from a performance viewpoint. But it seems to me it is always
    going to be cache-hostile.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.arch on Thu Jul 9 02:43:52 2026
    From Newsgroup: comp.arch

    On Sun, 21 Jun 2026 13:55:59 -0500, BGB wrote:

    Usual downside it that the excessive parenthesis tend to turn into a usability issue.

    Not helped, I think, by the traditional rCLparenthesis pileuprCY layout.

    I like to space out my code a bit more, e.g.

    (let
    (
    ...
    (delete-between
    (lambda (find-opener find-closer)
    ; deletes regions between successive pairs of points
    ; identified by find-opener and find-closer callbacks
    (goto-char (point-min))
    (while (funcall find-opener)
    (let*
    (
    (end-tag (point))
    (start-tag
    (progn
    (search-backward "<" nil nil)
    (point)
    ) ; progn
    )
    )
    (goto-char end-tag)
    (funcall find-closer)
    (delete-region start-tag (point))
    ) ; let*
    ) ; while
    ) ; lambda
    ) ; delete-between
    ...
    )

    ...
    (funcall delete-between ; Get rid of commented-out sections.
    (lambda () (search-forward "<!--" nil t))
    (lambda () (search-forward "-->" nil nil))
    )
    ...

    ) ; let

    One other major hassle was typically a lack of C style loops (with
    break or continue), but this could be addressed in theory.

    ThatrCOs easy to define in a macro. Plus some Lisps also have
    continuations!
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Lawrence =?iso-8859-13?q?D=FFOliveiro?=@ldo@nz.invalid to comp.arch on Thu Jul 9 02:45:56 2026
    From Newsgroup: comp.arch

    On Sun, 21 Jun 2026 19:52:10 -0000 (UTC), Robert Swindells wrote:

    I'm not trying to defend SPARC and am happy to take your word for it
    that M88K was fast for the time.

    I do know that, when Apple was looking for a RISC architecture to move
    the Macintosh to as the Motorola 68000 family was running out of
    steam, its engineers were not impressed with the performance of M88K.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.arch on Sun Aug 16 10:43:35 2026
    From Newsgroup: comp.arch

    Thomas Koenig <tkoenig@netcologne.de> writes:

    Has pointer safety been shown to be equivalent to the halting
    problem?

    Pointer safety is certainly equivalent to the halting problem.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.arch on Sun Aug 16 10:57:33 2026
    From Newsgroup: comp.arch

    Niklas Holsti <niklas.holsti@tidorum.invalid> writes:

    On 2026-06-22 13:44, Thomas Koenig wrote:

    Has pointer safety been shown to be equivalent to the halting
    problem? If so, "careful governance and guidance from God" may
    indeed be required.

    I would assume it is undecidable, for unrestricted programs.

    Yes, certainly.

    The aim of pointer provenance is no doubt to restrict programs
    to make it decidable to some extent.

    I don't think that's right. My understanding is that pointer
    provenance is meant to further the goal of defining the semantics
    of C, but doesn't limit what programs are allowed. Also the
    question of what is the provenance of a pointer may itself be
    undecidable in some cases -- I believe that to be the case but
    have not read enough about it to be certain.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.arch on Sun Aug 16 11:45:10 2026
    From Newsgroup: comp.arch

    EricP <ThatWouldBeTelling@thevillage.com> writes:

    [..discussion of pointer manipulation..]

    If that is what I think it is, where it cast from a pointer to a
    field inside a struct back to the containing struct by subtracting
    the field byte offset and changing the pointer type, irrespective
    of programming language that mechanism has been used by operating
    systems at least since RSX days.
    It is a compact way of having structs linked to many other structures.

    That macro is just a variant of the mechanism for C.
    The method is used by WinNT and Linux, and I believe also by the
    BSD's.

    What's the phrase? "I'd bet dollars to donuts"? :) (Of course
    these days a donut costs more than a dollar but never mind that...)

    GCC has a compile option, no_strict_alias or something, that
    anyone using it and doing "illegal" pointer casting must use.
    In Windows land, pointer casting at least used to be Microsoft's
    recommended method and is supported by their compiler because they
    use it too, extensively.

    What many people call "strict aliasing" is a way of referring to
    what is termed in the C standard "effective type" rules. Two
    comments about that.

    One, effective type rules bear only on memory accesses, and have
    nothing to do with pointer type conversions. Any type conversion of
    a pointer value that works under -fno-strict-aliasing works equally
    well under -fstrict-aliasing. How a converted pointer is used to
    access memory might run afoul of the effective type rules, but just
    converting a pointer does not.

    Two, the particular case of pointer manipulation being discussed,
    where a pointer to a member is adjusted to a pointer to the
    containing struct, and then used to access the struct, is fine as
    long as the containing struct is in fact present. In a case like
    the following (please excuse any typos)

    typedef struct {
    ... some earlier members;
    int x;
    } Blah;

    Blah b = { ... };
    int *p = &b.x;
    Blah *pb = (Blah*)((char*)p - offsetof(Blah,x));

    the pointer pb may be used as a pointer to the struct, including
    accessing any members, without violating effective type rules.
    Code like this may be compiled using -fstrict-aliasing, without
    falling into the dreaded realm of undefined behavior.

    I guess I should add a comment about another case, something like
    this

    int x;
    short s = *(short *)&x; // use only the low bits of x

    where a converted pointer is used to access memory using a different
    type than the variable being accessed. This code DOES violate
    effective type rules, and so needs -fno-strict-aliasing if someone
    wants to use it. (My advice here is never write code like this, but
    I know that some people may elect not to follow my advice in all
    cases. :)
    --- Synchronet 3.22a-Linux NewsLink 1.2