Skip to content

Add getCarry and getBorrow operations - #27

Merged
moigagoo merged 28 commits into
developfrom
feature/get_carry_get_borrow
Mar 26, 2026
Merged

moigagoo merged 28 commits into
developfrom
feature/get_carry_get_borrow

Conversation

@moigagoo

@moigagoo moigagoo commented Mar 10, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #22

I've researched the ways of calculating the carry flag without calculating the sum and it turns out calculating the sum and discarding the sum is the optimal solution.

I didn't want to put getCarry or getBorrow into composite.nim as those are not composite. But repeating the same carryingAdd(a, b, carryIn).carryOut for all implementations would be silly.

So my solution is to simply add the necessary logic directly into the dispatcher. This diverges from the original architecture where dispatchers are purely when-branches but I'd rather fix the docs and allow that than force a clumsy code pattern for the sake of purity.

The PR does not include benchmarks. This is because there are no implementations for getCarry or getBorrow, it's just sugar around carryingAdd and borrowingSub.

@moigagoo
moigagoo marked this pull request as ready for review March 10, 2026 13:47
@moigagoo
moigagoo requested review from arnetheduck and nitely March 10, 2026 13:47
@arnetheduck

Copy link
Copy Markdown
Contributor

we tend to avoid get in names when possible - also, in and out are kind of obvious since they're parameters/return values so it's not necessary to repeat the same information

@arnetheduck

Copy link
Copy Markdown
Contributor

calculating the sum and discarding the sum is the optimal solution

In assembly, what you're looking for is to verify that we use the carry flag which is usually the case with add/sub.

Also, no need to call out these operations as special - ie the aim overall is to have fine-grained operations that allow the caller to select the version that offers the smallest API possible in every (reasonably hardware-supported, on some platform) integer situation - while on typical x86 this might result in an operation whose implementation simply calls some other function (just like div on x86 always gives remainder too), it might be the case that on some platforms this might change in the future and we might have a "better" alternative.

The carryingAdd benchmark was too optimistic as it ignored the carry
flag entirely allowing the compiler to remove its computation entirely.
@moigagoo

Copy link
Copy Markdown
Collaborator Author

@arnetheduck Thanks for the comments!

we tend to avoid get in names when possible - also, in and out are kind of obvious since they're parameters/return values so it's not necessary to repeat the same information

I've renamed getCarry and getBorrow with carry and borrow. On one hand, it's shorter, which is good. But I'm a bit worried that "carry" and "borrow" are both verbs on top of being nouns so a function named carry could be misinterpreted as "carrying" something, which it doesn't.

Also, no need to call out these operations as special

This is a very valuable comment, thanks. I spent a lot of time figuring out the best way to implement carry without copying and pasting code. Those "proxy ops" felt like a good idea under the assumption that implementing carry fundamentally means reusing carryingAdd.

Well, this assumption is simply wrong, and I've implemented carry and borrow that are faster than carryingAdd and borrowingSub in pure Nim and using C intrinsics.

Comment thread src/intops/impl/inlineasm/x86.nim Outdated

@arnetheduck arnetheduck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

There will always be some tension between reusing code and maintaining a semblance of structure and duplication - it's easy to go too far in either direction, ie sometimes it's more clear to write the same code twice than to introduce elaborate code sharing schemes.

glibc vs musl is a telling example of this point where glibc has a massively complex include directory structure while musl doesn't bother and copy-pastes freely .. when you want to find out what code is actually being used on your platform, the latter is certainly easier to understand for the reader -> sometimes duplication is a feature and the trick is to learn when ;) Hardware-near code in particular has a different maintenance cost ratio since the hardware itself changes slowly - it means that duplication in hardware-near code is cheaper too since it's unlikely to change often.

@moigagoo
moigagoo merged commit f1b4b24 into develop Mar 26, 2026
48 checks passed
@moigagoo
moigagoo deleted the feature/get_carry_get_borrow branch March 26, 2026 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add borrowingSub and carryingAdd flavors that return only the borrow/carry

2 participants