Skip to content

fuzz: keep coins_view fuzzers within caller contracts #284

Description

@kevkevinpal

mentioned in: bitcoin#34655

"> no reason why subtracting would ever fail on an unsigned type

TrySub is meant as "try to subtract without wraparound": if i < j, it leaves i unchanged (hence the Try prefix) and returns false, so callers can act.

It's unfortunate that this has a runtime impact even outside fuzzing

I think bool ret = i >= T{j}; i -= T{j}; return ret; is nicer, because in contexts where the return value is ignored, it is exactly equivalent to just a subtraction

Do we want to be exactly equivalent though?
The current cheap branching prevents the accounting counters from wrapping, so even if accounting is wrong elsewhere, the values stay closer to what we expect.


I have tried a few variants to see the effect of the change (plain subtraction vs return + internal Assume vs mutating TrySub + caller Assume with [[unlikely]]), see https://godbolt.org/z/nf9nT9Mjj

Details

Both compilers emit the same single subtract (plus the bool zero-extend in the bool case).
For example, GCC:

sub_return_size:
    sub     QWORD PTR [rdi], rsi
    ret

and for bool:

sub_return_bool:
    movzx   esi, sil
    sub     QWORD PTR [rdi], rsi
    ret

So return + internal Assume has zero codegen cost in non-abort builds, and adding [[unlikely]] doesn't seem to change anything there either.


Result 2: Only the mutating TrySub variant changes semantics (avoids wrap), and it necessarily adds a branch. [[unlikely]] didn't change codegen here either.
GCC (size_t case):

sub_trysub_size:
    mov     rax, QWORD PTR [rdi]
    cmp     rax, rsi
    jb      .Lskip
    sub     rax, rsi
    mov     QWORD PTR [rdi], rax
.Lskip:
    ret

clang emits the same idea, just as sub then jb then store:

sub_trysub_size:
    mov     rax, qword ptr [rdi]
    sub     rax, rsi
    jb      .Lskip
    mov     qword ptr [rdi], rax
.Lskip:
    ret

…and the sub_trysub_unlikely_* versions were identical to sub_trysub_* for both compilers (so [[unlikely]] doesn't seem to buy anything here either; the cold path is already the taken jump).

Note: I have experimented a bit with __builtin_sub_overflow(), it does change GCC codegen slighly to the same sub; jb shape clang already emits (removing the explicit cmp), but I’m not sure it’s an actual win, so I’ll leave it for a follow-up.

" - l0rinc

Metadata

Metadata

Assignees

No one assigned

    Labels

    followupThis is a possible follow-up someone can work onneed to confirmNeed to confirm if this is still needed or already done

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions