Skip to content

Pseudo C omits carry and borrow inputs from multiword arithmetic #8590

Description

@plafosse

When multiword arithmetic uses ADC/SBC, Pseudo C drops the carry or borrow computed from the low halves. The high-half result consequently ignores an essential input.

repro(1, 2, UINT64_MAX, 1) should return 4, but the displayed addition returns 3. repro_borrow(5, 2, 0, 1) should return 2, but the displayed subtraction returns 3.

Original C

#include <stdint.h>

uint64_t repro(uint64_t a, uint64_t b, uint64_t low_a, uint64_t low_b)
{
    // Add two 128-bit integers and return the high half.
    // Adding UINT64_MAX + 1 in the low half must carry into a + b.
    __uint128_t lhs = ((__uint128_t)a << 64) | low_a;
    __uint128_t rhs = ((__uint128_t)b << 64) | low_b;
    return (lhs + rhs) >> 64;
}

uint64_t repro_borrow(uint64_t a, uint64_t b, uint64_t low_a, uint64_t low_b)
{
    // Subtract two 128-bit integers and return the high half.
    // Subtracting 1 from 0 in the low half must borrow from a - b.
    __uint128_t lhs = ((__uint128_t)a << 64) | low_a;
    __uint128_t rhs = ((__uint128_t)b << 64) | low_b;
    return (lhs - rhs) >> 64;
}

Observed Pseudo C

Comments below annotate the captured output; the expressions themselves are unchanged.

uint64_t repro(uint64_t a, uint64_t b, uint64_t low_a, uint64_t low_b) __pure
{
    // BUG: the low-half addition carries 1, but that input is absent.
    return a + b;
}

uint64_t repro_borrow(uint64_t a, uint64_t b, uint64_t low_a, uint64_t low_b) __pure
{
    // BUG: the low-half subtraction borrows 1, but that input is absent.
    return a - b - 0;
}

The corresponding AArch64 code is 28 bytes across the two functions:

repro:
    cmn x2, x3        // Compute the carry from low_a + low_b.
    adc x0, x0, x1    // Include that carry in the high-half addition.
    ret
repro_borrow:
    cmp x2, x3        // Determine whether the low-half subtraction borrows.
    sub x8, x0, x1
    sbc x0, x8, xzr   // Subtract the borrow from the high-half result.
    ret

HLIL retains both inputs: return adc.q(a, b, low_a + low_b u< low_a) and return sbb.q(a - b, 0, low_a u< low_b). Those carry/borrow inputs disappear in Pseudo C.

15-carry-borrow.zip

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions