Contributor's Guide

Optimizing arithmetic operations for a variety of enviroments and consumers is a never-ending chase. There hardly will ever be a moment when intops will be 100% ready: there's always an older Nim version, and newer GCC version, or an obscure CPU vendor to target.

Because of that, contributors' help is essential for intops' development. You can help improve intops by improving the code, improving the docs, and reporting issues.

Library Structure

src
β”‚   intops.nim              <- entrypoint for the public API
β”‚
└───intops
    β”‚   consts.nim          <- global constants for environment detection
    β”‚
    β”œβ”€β”€β”€impl                <- implementations of primitives
    β”‚   β”‚   inlineasm.nim   <- entrypoint for the Inline ASM family of implementations
    β”‚   β”‚   inlinec.nim
    β”‚   β”‚   intrinsics.nim
    β”‚   β”‚   pure.nim
    β”‚   β”‚   ...
    β”‚   β”‚
    β”‚   β”œβ”€β”€β”€inlineasm
    β”‚   β”‚       arm64.nim   <- implementation in ARM64 Assembly 
    β”‚   β”‚       x86.nim
    β”‚   β”‚       ...
    β”‚   β”‚
    β”‚   └───...
    β”‚
    └───ops                 <- operation families
            add.nim         <- addition flavors: carrying, saturating, etc.
            mul.nim
            sub.nim
            ...

The entrypoint is the root intops module. It's the library's public API and exposes all the available primitives.

Each operation family has its own submodule in intops/ops. E.g. intops/ops/add is the submodule that contains various addition flavors.

These submodules contain the dispatchers that pick the best implementation of the given operation and for the given CPU, OS, and C compiler. I.e., each operation "knows" its best implementation and "decides" which one to run.

The actual implementations are stored in submodules in intops/impl. For example, intops/impl/intrinsics contains all primitives implemented with C intrinsics.

API Conventions

  1. intops follows the common Nim convention of calling things in camelCase.
  2. intops prefers pure functions that return values to the ones that modify mutable arguments.
  3. The docstrings are mandatory for the dispatchers in intops/ops modules because this is the library public API.
  4. Operations the return a wider type that the input type are called widening. For example wideningMulΒ is multiplication that takes two 64-bit integers and return a single 128-bit integer (althouth represented as a pair of 64-bit integers).
  5. Operations that return a carry or borrow flag are called carrying and borrowing, e.g. carryingAdd.
  6. Operations that return an overflow flag are called overflowing, e.g. overflowingSub.
  7. Operations that return maximal or mininal type value when a type border is hit are called saturating, e.g. saturatingAdd.
  8. Carry, borrow, and overflow flags are booleans.

Tests

The tests for intops are located in a single file tests/tintops.nim.

To run the tests locally, use nimble test command.

With this command, the tests are run:

  • without compilation flags in runtime mode
  • without compilation flags in compile-time mode
  • with each compilation flag separately
  • with all compilation flags

When executed on the CI, the tests are run against multiple OS, C compilers, and architectures:

  • amd64 + Linux + gcc 13
  • amd64 + Linux + gcc 14
  • amd64 + Windows + clang 19
  • i386 + Linux + gcc 13
  • amd64 + macOS + clang 17
  • arm46 + macOS + clang 17

This is hardly reproduceable locally, but you can cover at least some of the cases by passing flags to nimble test. For example, to emulate running the tests in a 32-bit CPU, run nimble --cpu:i386 test. On Windows, you also must pass the path to your GCC installation, e.g. nimble --gcc.path:D:\mingw32\bin\ --cpu:i386 test. To run your tests contunously during development, use monit:

  1. Install monit with nimble install monit.
  2. Create a file called .monit.yml in the working directory:
%YAML 1.2
%TAG !n! tag:nimyaml.org,2016:
---
!n!custom:MonitorConfig
sleep: 1
targets:
  - name: Run tests
    paths: [src, tests]
    commands:
      - nimble test
      - nimble --gcc.path:D:\mingw32\bin\ --cpu:i386 test
    extensions: [.nim]
    files: []
    exclude_extensions: []
    exclude_files: []
    once: true
  1. Run monit run

Docs

The docs consist of two parts:

  • the book (this is what you're reading right now)
  • the API docs

The book is created using nimibook. Each page is a Nim file that can hold Markdown content and Nim code. The Nim code is executed during the build and its output are included in the book.

The API docs are generated from the source code docstrings.

To build the docs locally, run:

  • nimble bookΒ to build the book
  • nimble apidocs to build the API docs
  • nimble docs to build both