{"article":{"slug":"symmetries-in-boolean-scans","title":"Symmetries in boolean scans","subtitle":null,"summary":"Andrea Piseri looks at how array languages like APL implement scans over packed boolean arrays, where a naive loop manages about 0.6 elements per cycle. Using truth-table symmetries, the essay shows that fast versions of just and, less-than and xor are enough to derive good implementations of every other boolean scan, with bit-twiddling implementations in an appendix.","content_type":"essay","language":"en","canonical_url":"https://ap29600.github.io/boolean_scans.html","author":{"name":"Andrea Piseri","url":"https://ap29600.github.io","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Andrea Piseri","url":"https://ap29600.github.io","listing_slug":null,"listing":null},"topics":[{"name":"Programming Languages","slug":"programming-languages","url":"https://listedarticles.com/topics/programming-languages"},{"name":"Performance","slug":"performance","url":"https://listedarticles.com/topics/performance"},{"name":"Algorithms","slug":"algorithms","url":"https://listedarticles.com/topics/algorithms"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":3251,"reading_minutes":14,"published_at":"2026-10-17T02:19:17.258Z","added_at":"2026-10-17T02:19:17.258Z","updated_at":"2026-10-17T02:19:17.258Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/symmetries-in-boolean-scans","markdown_url":"https://listedarticles.com/articles/symmetries-in-boolean-scans.md","example":false,"citation":"Andrea Piseri, Andrea Piseri. \"Symmetries in boolean scans.\" 17 Oct 2026. https://ap29600.github.io/boolean_scans.html (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://ap29600.github.io/boolean_scans.html"},"body_markdown":"# Symmetries in boolean scans\n\nArray languages like APL are famous for using many pretty squiggles for\nrepresenting built-in functions. These are called \"primitives\" and are\ndifferent from user-defined functions, because the interpreters know about them\nand can do fancy optimizations with them, like recognizing special combinations\n[[1](#_footnotedef_1 \"View footnote.\")].\n\nAPL programmers have come to rely on these optimizations, for example the\nvectorization of some combinations like `+/` (sum). if you were to feed your\ntypical APL interpreter the function `{⍺+⍵}/` - which is functionally\nequivalent to `+/` [[2](#_footnotedef_2 \"View footnote.\")] since `{⍺+⍵}` is\njust a dfn [[3](#_footnotedef_3 \"View footnote.\")] over `+` - the\ninterpreter would execute it much slower, not just because executing the\nabstraction does two function calls per element (one for the dfn, one for the\nprimitive `+`) but because it does any at all.\n\nAny respectable APL has a tight loop implemented in native code dedicated to\nthis exact combination, which is why APL can sometimes compete with languages\nthat compile to native code.\n\nTable of Contents\n\n- [Scans and folds](#_scans_and_folds)\n- [Algorithmic esoterica](#_algorithmic_esoterica)\n- [Case study: Resolving quotes](#_case_study_resolving_quotes)\n- [Boolean scans: how many do we really need?](#_boolean_scans_how_many_do_we_really_need)\n- [Scan composition rules](#_scan_composition_rules)\n- [Truth table shuffles](#_truth_table_shuffles)\n- [Conclusion](#_conclusion)\n- [Appendix A. Brainstorming and observations](#_appendix_a_brainstorming_and_observations)\n  - [Truth tables as finite state machines](#_truth_tables_as_finite_state_machines)\n  - [Invariants under the given transformations](#_invariants_under_the_given_transformations)\n- [Appendix B. Efficient implementation of boolean scans](#_appendix_b_efficient_implementation_of_boolean_scans)\n  - [Xor](#_xor)\n  - [And](#_and)\n  - [Less-than](#_less_than)\n\n## Scans and folds\n\n- `fold` is a higher order function generalizing the concept of iterating over\n  an ordered collection repeatedly calling a binary function that takes as\n  inputs an accumulator and an element of the collection, and returns a new\n  accumulator.\n- `scan` is the same, but instead of returning the final value of the\n  accumulator it returns a collection of all intermediate values the\n  accumulator took over the course of the iteration.\n\nIn both cases an initial value may or may not be provided, and in the second\ncase the first element is treated as an initial value, and is returned\nunchanged, concatenated with the scan of the rest of the collection.\n\nAPL represents these functions with the operators `/` and `\\`, so that\n\n```\n       10 = +/ 1 2 3 4\n 1 3 6 10 = +\\ 1 2 3 4\n```\n\nIn the rest of this article, I’ll be using a fantasy dialect of APL modified to\nhave better behaved fold and scan semantics [[4](#_footnotedef_4 \"View footnote.\")] to write some functional equations. If you don’t know APL,\ndon’t worry about it, just pretend this is the scan from any traditional\nfunctional language like Lisp, Haskell, Ocaml, etc. The main equations will\nalso be translated to Haskell.\n\n## Algorithmic esoterica\n\nWhen I say APLers rely on these, I really mean it! scans, folds, reverse scans,\nwith the operators min, max, plus, times are all familiar, if not even routine\noperations for the average python programmer who is comfortable with numpy,\nbecause they really are useful.\n\nFolding (from the right) with subtraction gives an alternating sum, that’s cute.\n\nAPL programs go beyond that. What does scanning with the less-than function do?\nscanning with not-equal? folding with…​ modulo? [[5](#_footnotedef_5 \"View footnote.\")]\n\nThe issue here is that APL needs escape hatches to handle data dependencies in\nalgorithms, both because it’s syntactically awkward to write loops and because\nexplicitly iterating over an array in APL is mind-numbingly slow when compared\nto the speed at which the primitive functions run.\nScans and folds are a very general way to accumulate information and \"move it\naround\" in the array, and they’re APL’s most idiomatic way to do so.\n\n## Case study: Resolving quotes\n\nSay you have a string of text containing quote characters, and you want to tell\nwhich positions in the string are inside a pair of quotes or not: if you were to\nwrite that in C you’d do something like this:\n\n```\nbool in_quotes = false;\nfor (size_t i = 0; i < n; ++i) {\n    if (string[i] == '\"') {\n        in_quotes = !in_quotes;\n    }\n}\n```\n\nWhile in APL you would write the following, where the result is an array containing the values of\n`in_quotes` from the previous example. The idea is to scan over the boolean results from comparing\neach character in the string with the `\"` character.\n\n```\nin_quotes ← ≠\\string='\"'\n```\n\nTo see why this works, let’s make the C version branchless by encoding the `if` statement\ninto arithmetic; to make the similarity more apparent we can write `!=` where ordinarily\n`^` would be more natural, on the values `0` and `1` they do the same thing.\n\n```\nbool in_quotes = false;\nfor (size_t i = 0; i < n; ++i) {\n    in_quotes = in_quotes != (string[i] == '\"');\n}\n```\n\n## Boolean scans: how many do we really need?\n\nAPL has a lot of primitives. it’s the only language I know of which has all non-trivial binary\nboolean functions built in: here they are, next to their truth tables.\n\n|  | Common name | 00 | 01 | 10 | 11 |\n| --- | --- | --- | --- | --- | --- |\n| `∧` | and | 0 | 0 | 0 | 1 |\n| `>` | greater than | 0 | 0 | 1 | 0 |\n| `<` | less than | 0 | 1 | 0 | 0 |\n| `≠` | not equal (xor) | 0 | 1 | 1 | 0 |\n| `∨` | or | 0 | 1 | 1 | 1 |\n| `⍱` | nor | 1 | 0 | 0 | 0 |\n| `=` | equal | 1 | 0 | 0 | 1 |\n| `≥` | greater than or equal | 1 | 0 | 1 | 1 |\n| `≤` | less than or equal (implication) | 1 | 1 | 0 | 1 |\n| `⍲` | nand | 1 | 1 | 1 | 0 |\n\nThis is an issue on an implementer’s side, because many primitives mean many\ncombinations, and writing decent code for each of them costs in labour as well\nas executable size (therefore instruction cache). Moreover, what qualifies as\ndecent code for a boolean scan is a much higher bar than the snippet I showed\nabove: APL uses packed bit vectors to represent boolean arrays, meaning that\nthese operations have the potential to consume tens of elements per CPU cycle,\nwhile by a quick benchmark a naive implementation would handle about 0.6\nelements per cycle [[6](#_footnotedef_6 \"View footnote.\")]\n\nTelling which of these are really useful only looking at the truth tables is\nnot trivial, and it really sucks as a user to learn that the operation\nthat precisely solves your problem is shoddily implemented.\n\nIs there some way that we can reduce the implementation work without sacrificing\nthe performance?\n\n## Scan composition rules\n\nThe structure of the scan `⍺ f\\ ⍵` is as follows:\n\nSuppose we want to do some pre-processing with a function `g` to alter the behaviour of the scan:\nwhat we’d end up with is a data flow of this kind:\n\nThe motivation for looking at this is that preprocessing a boolean array with a\nunary scalar function is just about the cheapest thing we could do, in fact there\nis only one non-trivial boolean unary scalar function: boolean negation.\nLet’s keep this observation aside for now, and ask ourselves: is this operation still\na scan? If so, what function operand should scan take to produce this result?\n\nIt’s apparent that the right argument to `f` is being fed through `g` at each step in the\niteration, so the answer is `{⍺ f (g ⍵)}`, which brings us to the simple equation\n\n```\n⍺ f\\ g¨ ⍵ ←→ ⍺ (f∘g)\\ ⍵\n```\n\nEquivalent Haskell\n\n```\nscanl f x (map g ys) = scanl (\\x y -> f x (g y)) x ys\n```\n\nIf `g` is any function, where `∘` is APL’s composition operator \"jot\" that satisfies\n\n```\n⍺ f∘g ⍵ ←→ ⍺ f (g ⍵)\n```\n\nIt’s a little trickier to see what effect post-processing with `g` would have:\n\nIt may look exactly like before, but notice that the input to each subsequent `f` call\nis not the same as the value that is sent to the output array, so this is not a scan!\nTo fix this, let’s assume that `g` has a left inverse `g-1` and insert it in between each pair of calls:\n\nThis is a scan diagram using the function `{g ((g⍣¯1) ⍺) f ⍵}`, except the initial value `x` has been preprocessed with `g`.\nthis gives the equation:\n\n```\ng¨ ⍺ f\\ ⍵ ←→ (g ⍺) (g⍤(g⍣¯1⍛f))\\ ⍵\n```\n\nEquivalent Haskell\n\n```\nmap g (scanl f x ys) = scanl (\\x y -> g (f (ig x) y)) (g x) ys\n```\n\nAgain some composition operators have been used:\n\n```\n(g⍣¯1) g ⍵ ←→ ⍵ ⍝ APL can sometimes find inverses by itself!\n⍺ (g⍤f) ⍵ ←→ g (⍺ f ⍵)\n⍺ (g⍛f) ⍵ ←→ (g ⍺) f ⍵\n```\n\nAnd finally we can put these two relations together to find a third.\n\n```\ng ⍺ f\\ g ⍵ ←→ (g ⍺) g⍤(g⍣¯1⍛f∘g)\\ ⍵\n```\n\nEquivalent Haskell\n\n```\nmap g (scanl f x (map g ys)) = scanl (\\x y -> g (f (ig x) (g y))) (g x) ys\n```\n\n## Truth table shuffles\n\nIn particular, we are interested in seeing which pairs of primitives are\nrelated by one of these three relations, where `g` is the negation function `~`.\nWe start with the truth tables of our primitives and analyze how they change under the\ntransformations described above.\n\nWe represent all of the truth tables at once as the array `a` with shape `10 2 2`\n\n```\n    a\n┌┌→──┐\n↓↓0 0│\n││0 1│\n││   │\n││0 0│\n││1 0│\n││   │\n││0 1│\n││0 0│\n││   │\n││0 1│\n││1 0│\n││   │\n││0 1│\n││1 1│\n││   │\n││1 0│\n││0 0│\n││   │\n││1 0│\n││0 1│\n││   │\n││1 0│\n││1 1│\n││   │\n││1 1│\n││0 1│\n││   │\n││1 1│\n││1 0│\n└└~──┘\n```\n\nComposing on the right argument with `~` is the same as reversing the last axis with\nthe `⌽` function:\n\n```\n    pre ← ⌽[2] a\n```\n\nNegating the output is just a matter of negating each entry of the table; if we do this, we must also\nnegate the left argument, which we do by reversing the first axis of each truth table (the middle axis here).\n\n```\n    post ← ~ ⌽[1] a\n```\n\nAnd of course we can do both transformations at once\n\n```\n    both ← ~ ⌽[1] ⌽[2] a\n```\n\nNow we can find out who’s related to whom by searching for the truth tables we got in the original array:\n\n```\n    index ← ↑(⊂a)⍳¨a pre post both\n```\n\n```\n┌→──────────────────┐\n↓0 1 2 3 4 5 6 7 8 9│\n│1 0 5 6 7 2 3 4 9 8│\n│7 4 9 3 1 8 6 0 5 2│\n│4 7 8 6 0 9 3 1 2 5│\n└~──────────────────┘\n```\n\nNow that we know this, we can index into the list of the symbols the truth tables referred to:\n\n```\n    '∧><≠∨⍱=≥≤⍲'[index]\n```\n\n```\n┌→─────────┐\n↓∧><≠∨⍱=≥≤⍲│\n│>∧⍱=≥<≠∨⍲≤│\n│≥∨⍲≠>≤=∧⍱<│\n│∨≥≤=∧⍲≠><⍱│\n└──────────┘\n```\n\nNow, how do we interpret this? along the top row are the\noriginal symbols; the second row are the symbols that\nare related to the top row by the first scan equation we derived\nabove, i.e. `⍺ p\\ ~⍵ ←→ ⍺ q\\ ⍵`.\nThe third and fourth row, similarly, are related to the first\nvia `~ (~⍺) p\\ ⍵ ←→ ⍺ q\\ ⍵` and `~ (~⍺) p\\ ~⍵ ←→ ⍺ q\\ ⍵`\n\n## Conclusion\n\nWe can see that the set `∧`, `<`, `≠` covers all columns at\nleast once, therefore if we provide fast implementations of each of\nthese, we can derive decent implementations of the others by just\napplying these symmetries. This cuts down on implementation time\nand executable size, without completely sacrificing performance.\n\nNone of this is completely new, by the way. The trick `⍺ ≠\\ ⍵ ←→ ⍺ =\\ ~⍵`\nis well known among K programmers, since K doesn’t have a builtin\nnot-equal operator. In the same vein, many of these shortcuts\nare employed in the CBQN interpreter for BQN for the\n[less common\ncombinations](https://github.com/dzaima/CBQN/blob/af583e19566a032b89e0077b866b0ba0dcc2a365/src/builtins/scan.c#L328).\n\n## Appendix A. Brainstorming and observations\n\nI originally discussed this on the APL farm discord server, where some of the users\nmade interesting comments:\n\n### Truth tables as finite state machines\n\nIn the K language, the form `x f\\ y`, is equivalent to the `x a\\ y` form, where\n`a` is a matrix containing the values of `f` on pairs of non-negative integers.\nThis form advances the state machine\n[[7](#_footnotedef_7 \"View footnote.\")]\nwith transitions defined in `a` over the sequence `y`. In this interpretation, the concrete\ntransformations we applied to the truth tables take the place of the\ncomposition operators in the original equations we defined.\n\n```\n(  x a\\ ~y) =   x  (  |'a)\\ y\n(~ x a\\  y) = (~x) (~|  a)\\ y\n(~ x a\\ ~y) = (~x) (~||'a)\\ y\n```\n\nEquivalent Haskell\n\n```\nrunFsm a x ys = scan (\\x y -> (a !! x) !! y) x ys\n\n            runFsm a x $ map (1 -) ys = runFsm      x  (                  map reverse           a) ys\nmap (1 -) $ runFsm a x             ys = runFsm (1 - x) (map (map (1 -))               $ reverse a) ys\nmap (1 -) $ runFsm a x $ map (1 -) ys = runFsm (1 - x) (map (map (1 -)) $ map reverse $ reverse a) ys\n```\n\n### Invariants under the given transformations\n\nThe transformations we gave permute the rows or columns of the tables\nand/or negate the entries. This implies that the number of ones in the tables either remains\nequal to 2 or flip-flops between 1 and 3 [[8](#_footnotedef_8 \"View footnote.\")]. Moreover, since the transformations commute and are self-inverses,\nour relation is an equivalence relation; we deduce that `=` and `≠` are in a class of their\nown, with one transformation fixing both operations, and the others fill at least two more.\n\nOn these, the transformation `~⌽[0]a` can be applied so that there are 3 0s,\nthen `⌽[1]a` can be applied to ensure that the single `1` entry occurs on the second column.\nThe only two possibilities are distinguished by whether the `1` value is at\n`a[1;1]` (which gives `∧`) or at `a[0;1]` (which gives `<`)\n\n## Appendix B. Efficient implementation of boolean scans\n\n### Xor\n\nA xor scan on a machine word can be implemented efficiently in a few instructions by a typical prefix doubling\ntechnique, similar to the one described by [Bit Twiddling Hacks](https://graphics.stanford.edu/~seander/bithacks.html#ParityParallel)\n\n```\nuint64_t xorscan_word (uint64_t word) {\n    word ^= word << 1;\n    word ^= word << 2;\n    word ^= word << 4;\n    word ^= word << 8;\n    word ^= word << 16;\n    word ^= word << 32;\n    return word;\n}\n```\n\nWe only need to handle the carry from the previous word. The typical way to do this would be\nto xor it with the first bit in `word`, but this is actually sub-optimal: notice that `carry`\nis needed at the very start of each iteration and only updated at the very end, which ties all iterations\nin a very long dependency chain.\n\n```\nvoid xorscan_bad (bool initial, uint64_t *words, size_t n) {\n    uint64_t carry = (uint64_t)initial;\n    for (size_t i = 0; i < n; ++i) {\n        uint64_t word = xorscan_word(words[i] ^ carry);\n        carry = word >> 63;\n        words[i] = word;\n    }\n}\n```\n\nThis compiles to the following core loop, which runs at around 3-4 bits per cycle.\n(all the timings I’ll give are for memory that is hot in cache, otherwise memory latency\nwould dominate runtime)\n\nAssembly output\n\n```\n.loop:\n    xor    (%rdx),%rdi\n    add    $0x8,%rdx\n    lea    (%rdi,%rdi,1),%rax\n    xor    %rdi,%rax\n    lea    0x0(,%rax,4),%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rcx\n    shl    $0x4,%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rcx\n    shl    $0x8,%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rcx\n    shl    $0x10,%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rcx\n    shl    $0x20,%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rdi\n    mov    %rax,-0x8(%rdx)\n    shr    $0x3f,%rdi\n    cmp    %rdx,%rsi\n    jne    .loop\n```\n\nOn a CPU with super-scalar execution, it would be nice if we could delay the usage of the previous result until\nthe latest possible time.\nFortunately xor is associative, so we can just broadcast the carry bit to every position in the final result: now\nthe CPU is free to start executing `xorscan_word` for the next iteration before the result of the previous one is ready.\n\n```\nvoid xorscan_good (bool initial, uint64_t *words, size_t n) {\n    uint64_t carry = - (uint64_t)initial;\n    for (size_t i = 0; i < n; ++i) {\n        uint64_t word = carry ^ xorscan_word(words[i]);\n        carry = - (word >> 63);\n        words[i] = word;\n    }\n}\n```\n\nThis is much faster, processing up to 10.5 bits per cycle.\n\nAssembly output\n\n```\n.loop:\n    mov    (%rcx),%rdx\n    add    $0x8,%rcx\n    lea    (%rdx,%rdx,1),%rax\n    xor    %rdx,%rax\n    lea    0x0(,%rax,4),%rdx\n    xor    %rdx,%rax\n    mov    %rax,%rdx\n    shl    $0x4,%rdx\n    xor    %rdx,%rax\n    mov    %rax,%rdx\n    shl    $0x8,%rdx\n    xor    %rdx,%rax\n    mov    %rax,%rdx\n    shl    $0x10,%rdx\n    xor    %rdx,%rax\n    mov    %rax,%rdx\n    shl    $0x20,%rdx\n    xor    %rdx,%rax\n    xor    %rsi,%rax\n    mov    %rax,%rsi\n    mov    %rax,-0x8(%rcx)\n    sar    $0x3f,%rsi\n    cmp    %rcx,%rdi\n    jne    .loop\n```\n\nIf the CPU supports a carry-less multiply instruction (many modern CPUs do!) then `xorscan_word` can be implemented as carry-less multiplication with the word `0xffffffffffffffff` (all ones).\n\n### And\n\nAll of the above (except for the carry-less multiply) can be applied verbatim, by replacing `^` with `&`; however there is a faster\nmethod available.\n\nFirstly, a single-word and scan can be implemented much more simply than xor\n[[9](#_footnotedef_9 \"View footnote.\")].\n\n```\nuint64_t andscan_word (uint64_t word) {\n    return word & ~(word + 1);\n}\n```\n\nThis works because adding one turns the first run of ones into a run of\nzeros, then the negation turns the initial run of zeros back into a run of ones,\nas well as negating everything else. The final bitwise and\nsees two matching runs of ones, a matching pair of zero bits, and mismatched\nbits everywhere else.\n\nHere is an example, with the bits ordered from least to most significant.\n\n|  |  |\n| --- | --- |\n| `word` | `11100011010101011` |\n| `word+1` | `00010011010101011` |\n| `~(word + 1)` | `11101100101010100` |\n| `word & ~(word + 1)` | `11100000000000000` |\n\nTo extend this to work on an array of words, we observe that at most one word\nwill need an actual scan, because everything before it is known to be all ones\nalready, and everything after it can be unconditionally set to zero.\n\nThe full implementation might be something like the following.\n\n```\nvoid andscan (bool initial, uint64_t *words, size_t n) {\n    size_t i = 0;\n    if (initial) {\n        for (; i < n; ++i) {\n            uint64_t word = words[i];\n            if (~word) {\n                words[i] = andscan_word(word);\n                ++i;\n                break;\n            }\n        }\n    }\n    for (; i < n; ++i) {\n        words[i] = 0;\n    }\n}\n```\n\nThis produces two separate loops, the first and most intensive of them can do 62 bits per cycle, while the\nother gets compiled into a call to memset.\n\nAssembly output\n\n```\n.loop:\n    add    $0x8,%rcx\n    cmp    %rax,%rdx\n    je     .found\n    mov    (%rcx),%rsi\n    inc    %rax\n    cmp    $0xffffffffffffffff,%rsi\n    je     .loop\n```\n\n### Less-than\n\nThis is the most complicated case, and the solution I’ll present comes from the\n[simdjson](https://github.com/simdjson/simdjson/blob/f807de973580fb22cc0db6b3b896716684b0266c/src/generic/stage1/json_escape_scanner.h#L50-L71)\nlibrary, which uses it to find escaped characters. In fact, the intuition behind\nwhat `<\\` does is that it turns off every `1` bit that is at an odd offset in\na contiguous sequence of `1`s, which is exactly what you need to figure out\nwhich backslashes in a string work as escapes and which are escaped.\n\n```\nuint64_t ltscan_word (uint64_t word) {\n    uint64_t guard = word << 1 | 0xaaaaaaaaaaaaaaaa;\n    uint64_t parity = guard - word;\n    return (parity ^ 0xaaaaaaaaaaaaaaaa) & word;\n}\n```\n\nHere is an example, again in little endian bit order.\n\n|  |  |\n| --- | --- |\n| `word` | `1110000111100111` |\n| `word << 1` | `0111000011110011` |\n| `guard` | `0111010111110111` |\n| `parity` | `1110010000010000` |\n| `parity ^ odd` | `1011000101000101` |\n| `result` | `1010000101000101` |\n\nShifting and setting the odd bits controls whether or not a borrow chain is\ninitiated in the subtraction: the start of a run of `1`s is set to `0` if it\nis at an even position, otherwise it stays `1`. The last element of a run is\nalso moved past the end to stop the borrow chain. Then subtracting the original\nbit mask creates a chain of borrows which leaves every bit set in the first\ncase, and just leaves every bit unset in the second case. If we now flip every\nodd bit, we will have the even bits set in the runs that start at even\npositions, the odd bits set in the runs that start at odd positions, and\nsome garbage outside of these runs which we clean up with bitwise and.\n\nLike the xor case, the trivial way to chain these together is to clear the\nfirst bit whenever the last bit of the previous result is set, though\nMarshall’s\n[notes](https://mlochbaum.github.io/BQN/implementation/primitive/fold.html#booleans)\non this also mention a smarter way to reduce the length of the critical path.\n\n```\nvoid ltscan (bool init, uint64_t *words, size_t n) {\n    uint64_t carry = (uint64_t)init;\n    for (size_t i = 0; i < n; ++i) {\n        uint64_t word = ltscan_word(words[i] &~ carry);\n        carry = word >> 63;\n        words[i] = word;\n    }\n}\n```\n\nThis particular version can do 8.8 bits per cycle.\n\nAssembly output\n\n```\n.loop:\n    andn   (%rsi),%rdi,%rdi\n    add    $0x8,%rsi\n    lea    (%rdi,%rdi,1),%rax\n    or     %rdx,%rax\n    sub    %rdi,%rax\n    xor    %rdx,%rax\n    and    %rdi,%rax\n    mov    %rax,%rdi\n    mov    %rax,-0x8(%rsi)\n    shr    $0x3f,%rdi\n    cmp    %rsi,%rcx\n    jne    .loop\n```\n","body_html":"<h1 id=\"symmetries-in-boolean-scans\">Symmetries in boolean scans</h1>\n<p>Array languages like APL are famous for using many pretty squiggles for\nrepresenting built-in functions. These are called &quot;primitives&quot; and are\ndifferent from user-defined functions, because the interpreters know about them\nand can do fancy optimizations with them, like recognizing special combinations\n[<a href=\"#_footnotedef_1\" title=\"View footnote.\">1</a>].</p>\n<p>APL programmers have come to rely on these optimizations, for example the\nvectorization of some combinations like <code>+/</code> (sum). if you were to feed your\ntypical APL interpreter the function <code>{⍺+⍵}/</code> - which is functionally\nequivalent to <code>+/</code> [<a href=\"#_footnotedef_2\" title=\"View footnote.\">2</a>] since <code>{⍺+⍵}</code> is\njust a dfn [<a href=\"#_footnotedef_3\" title=\"View footnote.\">3</a>] over <code>+</code> - the\ninterpreter would execute it much slower, not just because executing the\nabstraction does two function calls per element (one for the dfn, one for the\nprimitive <code>+</code>) but because it does any at all.</p>\n<p>Any respectable APL has a tight loop implemented in native code dedicated to\nthis exact combination, which is why APL can sometimes compete with languages\nthat compile to native code.</p>\n<p>Table of Contents</p>\n<ul><li><a href=\"#_scans_and_folds\">Scans and folds</a></li><li><a href=\"#_algorithmic_esoterica\">Algorithmic esoterica</a></li><li><a href=\"#_case_study_resolving_quotes\">Case study: Resolving quotes</a></li><li><a href=\"#_boolean_scans_how_many_do_we_really_need\">Boolean scans: how many do we really need?</a></li><li><a href=\"#_scan_composition_rules\">Scan composition rules</a></li><li><a href=\"#_truth_table_shuffles\">Truth table shuffles</a></li><li><a href=\"#_conclusion\">Conclusion</a></li><li><a href=\"#_appendix_a_brainstorming_and_observations\">Appendix A. Brainstorming and observations</a><ul><li><a href=\"#_truth_tables_as_finite_state_machines\">Truth tables as finite state machines</a></li><li><a href=\"#_invariants_under_the_given_transformations\">Invariants under the given transformations</a></li></ul></li><li><a href=\"#_appendix_b_efficient_implementation_of_boolean_scans\">Appendix B. Efficient implementation of boolean scans</a><ul><li><a href=\"#_xor\">Xor</a></li><li><a href=\"#_and\">And</a></li><li><a href=\"#_less_than\">Less-than</a></li></ul></li></ul>\n<h2 id=\"scans-and-folds\">Scans and folds</h2>\n<ul><li><p><code>fold</code> is a higher order function generalizing the concept of iterating over</p><p>an ordered collection repeatedly calling a binary function that takes as\ninputs an accumulator and an element of the collection, and returns a new\naccumulator.</p></li><li><p><code>scan</code> is the same, but instead of returning the final value of the</p><p>accumulator it returns a collection of all intermediate values the\naccumulator took over the course of the iteration.</p></li></ul>\n<p>In both cases an initial value may or may not be provided, and in the second\ncase the first element is treated as an initial value, and is returned\nunchanged, concatenated with the scan of the rest of the collection.</p>\n<p>APL represents these functions with the operators <code>/</code> and <code>\\</code>, so that</p>\n<pre><code>       10 = +/ 1 2 3 4\n 1 3 6 10 = +\\ 1 2 3 4</code></pre>\n<p>In the rest of this article, I’ll be using a fantasy dialect of APL modified to\nhave better behaved fold and scan semantics [<a href=\"#_footnotedef_4\" title=\"View footnote.\">4</a>] to write some functional equations. If you don’t know APL,\ndon’t worry about it, just pretend this is the scan from any traditional\nfunctional language like Lisp, Haskell, Ocaml, etc. The main equations will\nalso be translated to Haskell.</p>\n<h2 id=\"algorithmic-esoterica\">Algorithmic esoterica</h2>\n<p>When I say APLers rely on these, I really mean it! scans, folds, reverse scans,\nwith the operators min, max, plus, times are all familiar, if not even routine\noperations for the average python programmer who is comfortable with numpy,\nbecause they really are useful.</p>\n<p>Folding (from the right) with subtraction gives an alternating sum, that’s cute.</p>\n<p>APL programs go beyond that. What does scanning with the less-than function do?\nscanning with not-equal? folding with…​ modulo? [<a href=\"#_footnotedef_5\" title=\"View footnote.\">5</a>]</p>\n<p>The issue here is that APL needs escape hatches to handle data dependencies in\nalgorithms, both because it’s syntactically awkward to write loops and because\nexplicitly iterating over an array in APL is mind-numbingly slow when compared\nto the speed at which the primitive functions run.\nScans and folds are a very general way to accumulate information and &quot;move it\naround&quot; in the array, and they’re APL’s most idiomatic way to do so.</p>\n<h2 id=\"case-study-resolving-quotes\">Case study: Resolving quotes</h2>\n<p>Say you have a string of text containing quote characters, and you want to tell\nwhich positions in the string are inside a pair of quotes or not: if you were to\nwrite that in C you’d do something like this:</p>\n<pre><code>bool in_quotes = false;\nfor (size_t i = 0; i &lt; n; ++i) {\n    if (string[i] == &#39;&quot;&#39;) {\n        in_quotes = !in_quotes;\n    }\n}</code></pre>\n<p>While in APL you would write the following, where the result is an array containing the values of\n<code>in_quotes</code> from the previous example. The idea is to scan over the boolean results from comparing\neach character in the string with the <code>&quot;</code> character.</p>\n<pre><code>in_quotes ← ≠\\string=&#39;&quot;&#39;</code></pre>\n<p>To see why this works, let’s make the C version branchless by encoding the <code>if</code> statement\ninto arithmetic; to make the similarity more apparent we can write <code>!=</code> where ordinarily\n<code>^</code> would be more natural, on the values <code>0</code> and <code>1</code> they do the same thing.</p>\n<pre><code>bool in_quotes = false;\nfor (size_t i = 0; i &lt; n; ++i) {\n    in_quotes = in_quotes != (string[i] == &#39;&quot;&#39;);\n}</code></pre>\n<h2 id=\"boolean-scans-how-many-do-we-really-need\">Boolean scans: how many do we really need?</h2>\n<p>APL has a lot of primitives. it’s the only language I know of which has all non-trivial binary\nboolean functions built in: here they are, next to their truth tables.</p>\n<div class=\"table-wrap\"><table><thead><tr><th></th><th>Common name</th><th>00</th><th>01</th><th>10</th><th>11</th></tr></thead><tbody><tr><td><code>∧</code></td><td>and</td><td>0</td><td>0</td><td>0</td><td>1</td></tr><tr><td><code>&gt;</code></td><td>greater than</td><td>0</td><td>0</td><td>1</td><td>0</td></tr><tr><td><code>&lt;</code></td><td>less than</td><td>0</td><td>1</td><td>0</td><td>0</td></tr><tr><td><code>≠</code></td><td>not equal (xor)</td><td>0</td><td>1</td><td>1</td><td>0</td></tr><tr><td><code>∨</code></td><td>or</td><td>0</td><td>1</td><td>1</td><td>1</td></tr><tr><td><code>⍱</code></td><td>nor</td><td>1</td><td>0</td><td>0</td><td>0</td></tr><tr><td><code>=</code></td><td>equal</td><td>1</td><td>0</td><td>0</td><td>1</td></tr><tr><td><code>≥</code></td><td>greater than or equal</td><td>1</td><td>0</td><td>1</td><td>1</td></tr><tr><td><code>≤</code></td><td>less than or equal (implication)</td><td>1</td><td>1</td><td>0</td><td>1</td></tr><tr><td><code>⍲</code></td><td>nand</td><td>1</td><td>1</td><td>1</td><td>0</td></tr></tbody></table></div>\n<p>This is an issue on an implementer’s side, because many primitives mean many\ncombinations, and writing decent code for each of them costs in labour as well\nas executable size (therefore instruction cache). Moreover, what qualifies as\ndecent code for a boolean scan is a much higher bar than the snippet I showed\nabove: APL uses packed bit vectors to represent boolean arrays, meaning that\nthese operations have the potential to consume tens of elements per CPU cycle,\nwhile by a quick benchmark a naive implementation would handle about 0.6\nelements per cycle [<a href=\"#_footnotedef_6\" title=\"View footnote.\">6</a>]</p>\n<p>Telling which of these are really useful only looking at the truth tables is\nnot trivial, and it really sucks as a user to learn that the operation\nthat precisely solves your problem is shoddily implemented.</p>\n<p>Is there some way that we can reduce the implementation work without sacrificing\nthe performance?</p>\n<h2 id=\"scan-composition-rules\">Scan composition rules</h2>\n<p>The structure of the scan <code>⍺ f\\ ⍵</code> is as follows:</p>\n<p>Suppose we want to do some pre-processing with a function <code>g</code> to alter the behaviour of the scan:\nwhat we’d end up with is a data flow of this kind:</p>\n<p>The motivation for looking at this is that preprocessing a boolean array with a\nunary scalar function is just about the cheapest thing we could do, in fact there\nis only one non-trivial boolean unary scalar function: boolean negation.\nLet’s keep this observation aside for now, and ask ourselves: is this operation still\na scan? If so, what function operand should scan take to produce this result?</p>\n<p>It’s apparent that the right argument to <code>f</code> is being fed through <code>g</code> at each step in the\niteration, so the answer is <code>{⍺ f (g ⍵)}</code>, which brings us to the simple equation</p>\n<pre><code>⍺ f\\ g¨ ⍵ ←→ ⍺ (f∘g)\\ ⍵</code></pre>\n<p>Equivalent Haskell</p>\n<pre><code>scanl f x (map g ys) = scanl (\\x y -&gt; f x (g y)) x ys</code></pre>\n<p>If <code>g</code> is any function, where <code>∘</code> is APL’s composition operator &quot;jot&quot; that satisfies</p>\n<pre><code>⍺ f∘g ⍵ ←→ ⍺ f (g ⍵)</code></pre>\n<p>It’s a little trickier to see what effect post-processing with <code>g</code> would have:</p>\n<p>It may look exactly like before, but notice that the input to each subsequent <code>f</code> call\nis not the same as the value that is sent to the output array, so this is not a scan!\nTo fix this, let’s assume that <code>g</code> has a left inverse <code>g-1</code> and insert it in between each pair of calls:</p>\n<p>This is a scan diagram using the function <code>{g ((g⍣¯1) ⍺) f ⍵}</code>, except the initial value <code>x</code> has been preprocessed with <code>g</code>.\nthis gives the equation:</p>\n<pre><code>g¨ ⍺ f\\ ⍵ ←→ (g ⍺) (g⍤(g⍣¯1⍛f))\\ ⍵</code></pre>\n<p>Equivalent Haskell</p>\n<pre><code>map g (scanl f x ys) = scanl (\\x y -&gt; g (f (ig x) y)) (g x) ys</code></pre>\n<p>Again some composition operators have been used:</p>\n<pre><code>(g⍣¯1) g ⍵ ←→ ⍵ ⍝ APL can sometimes find inverses by itself!\n⍺ (g⍤f) ⍵ ←→ g (⍺ f ⍵)\n⍺ (g⍛f) ⍵ ←→ (g ⍺) f ⍵</code></pre>\n<p>And finally we can put these two relations together to find a third.</p>\n<pre><code>g ⍺ f\\ g ⍵ ←→ (g ⍺) g⍤(g⍣¯1⍛f∘g)\\ ⍵</code></pre>\n<p>Equivalent Haskell</p>\n<pre><code>map g (scanl f x (map g ys)) = scanl (\\x y -&gt; g (f (ig x) (g y))) (g x) ys</code></pre>\n<h2 id=\"truth-table-shuffles\">Truth table shuffles</h2>\n<p>In particular, we are interested in seeing which pairs of primitives are\nrelated by one of these three relations, where <code>g</code> is the negation function <code>~</code>.\nWe start with the truth tables of our primitives and analyze how they change under the\ntransformations described above.</p>\n<p>We represent all of the truth tables at once as the array <code>a</code> with shape <code>10 2 2</code></p>\n<pre><code>    a\n┌┌→──┐\n↓↓0 0│\n││0 1│\n││   │\n││0 0│\n││1 0│\n││   │\n││0 1│\n││0 0│\n││   │\n││0 1│\n││1 0│\n││   │\n││0 1│\n││1 1│\n││   │\n││1 0│\n││0 0│\n││   │\n││1 0│\n││0 1│\n││   │\n││1 0│\n││1 1│\n││   │\n││1 1│\n││0 1│\n││   │\n││1 1│\n││1 0│\n└└~──┘</code></pre>\n<p>Composing on the right argument with <code>~</code> is the same as reversing the last axis with\nthe <code>⌽</code> function:</p>\n<pre><code>    pre ← ⌽[2] a</code></pre>\n<p>Negating the output is just a matter of negating each entry of the table; if we do this, we must also\nnegate the left argument, which we do by reversing the first axis of each truth table (the middle axis here).</p>\n<pre><code>    post ← ~ ⌽[1] a</code></pre>\n<p>And of course we can do both transformations at once</p>\n<pre><code>    both ← ~ ⌽[1] ⌽[2] a</code></pre>\n<p>Now we can find out who’s related to whom by searching for the truth tables we got in the original array:</p>\n<pre><code>    index ← ↑(⊂a)⍳¨a pre post both</code></pre>\n<pre><code>┌→──────────────────┐\n↓0 1 2 3 4 5 6 7 8 9│\n│1 0 5 6 7 2 3 4 9 8│\n│7 4 9 3 1 8 6 0 5 2│\n│4 7 8 6 0 9 3 1 2 5│\n└~──────────────────┘</code></pre>\n<p>Now that we know this, we can index into the list of the symbols the truth tables referred to:</p>\n<pre><code>    &#39;∧&gt;&lt;≠∨⍱=≥≤⍲&#39;[index]</code></pre>\n<pre><code>┌→─────────┐\n↓∧&gt;&lt;≠∨⍱=≥≤⍲│\n│&gt;∧⍱=≥&lt;≠∨⍲≤│\n│≥∨⍲≠&gt;≤=∧⍱&lt;│\n│∨≥≤=∧⍲≠&gt;&lt;⍱│\n└──────────┘</code></pre>\n<p>Now, how do we interpret this? along the top row are the\noriginal symbols; the second row are the symbols that\nare related to the top row by the first scan equation we derived\nabove, i.e. <code>⍺ p\\ ~⍵ ←→ ⍺ q\\ ⍵</code>.\nThe third and fourth row, similarly, are related to the first\nvia <code>~ (~⍺) p\\ ⍵ ←→ ⍺ q\\ ⍵</code> and <code>~ (~⍺) p\\ ~⍵ ←→ ⍺ q\\ ⍵</code></p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>We can see that the set <code>∧</code>, <code>&lt;</code>, <code>≠</code> covers all columns at\nleast once, therefore if we provide fast implementations of each of\nthese, we can derive decent implementations of the others by just\napplying these symmetries. This cuts down on implementation time\nand executable size, without completely sacrificing performance.</p>\n<p>None of this is completely new, by the way. The trick <code>⍺ ≠\\ ⍵ ←→ ⍺ =\\ ~⍵</code>\nis well known among K programmers, since K doesn’t have a builtin\nnot-equal operator. In the same vein, many of these shortcuts\nare employed in the CBQN interpreter for BQN for the\n<a href=\"https://github.com/dzaima/CBQN/blob/af583e19566a032b89e0077b866b0ba0dcc2a365/src/builtins/scan.c#L328\" rel=\"nofollow ugc noopener\">less common\ncombinations</a>.</p>\n<h2 id=\"appendix-a-brainstorming-and-observations\">Appendix A. Brainstorming and observations</h2>\n<p>I originally discussed this on the APL farm discord server, where some of the users\nmade interesting comments:</p>\n<h3 id=\"truth-tables-as-finite-state-machines\">Truth tables as finite state machines</h3>\n<p>In the K language, the form <code>x f\\ y</code>, is equivalent to the <code>x a\\ y</code> form, where\n<code>a</code> is a matrix containing the values of <code>f</code> on pairs of non-negative integers.\nThis form advances the state machine\n[<a href=\"#_footnotedef_7\" title=\"View footnote.\">7</a>]\nwith transitions defined in <code>a</code> over the sequence <code>y</code>. In this interpretation, the concrete\ntransformations we applied to the truth tables take the place of the\ncomposition operators in the original equations we defined.</p>\n<pre><code>(  x a\\ ~y) =   x  (  |&#39;a)\\ y\n(~ x a\\  y) = (~x) (~|  a)\\ y\n(~ x a\\ ~y) = (~x) (~||&#39;a)\\ y</code></pre>\n<p>Equivalent Haskell</p>\n<pre><code>runFsm a x ys = scan (\\x y -&gt; (a !! x) !! y) x ys\n\n            runFsm a x $ map (1 -) ys = runFsm      x  (                  map reverse           a) ys\nmap (1 -) $ runFsm a x             ys = runFsm (1 - x) (map (map (1 -))               $ reverse a) ys\nmap (1 -) $ runFsm a x $ map (1 -) ys = runFsm (1 - x) (map (map (1 -)) $ map reverse $ reverse a) ys</code></pre>\n<h3 id=\"invariants-under-the-given-transformations\">Invariants under the given transformations</h3>\n<p>The transformations we gave permute the rows or columns of the tables\nand/or negate the entries. This implies that the number of ones in the tables either remains\nequal to 2 or flip-flops between 1 and 3 [<a href=\"#_footnotedef_8\" title=\"View footnote.\">8</a>]. Moreover, since the transformations commute and are self-inverses,\nour relation is an equivalence relation; we deduce that <code>=</code> and <code>≠</code> are in a class of their\nown, with one transformation fixing both operations, and the others fill at least two more.</p>\n<p>On these, the transformation <code>~⌽[0]a</code> can be applied so that there are 3 0s,\nthen <code>⌽[1]a</code> can be applied to ensure that the single <code>1</code> entry occurs on the second column.\nThe only two possibilities are distinguished by whether the <code>1</code> value is at\n<code>a[1;1]</code> (which gives <code>∧</code>) or at <code>a[0;1]</code> (which gives <code>&lt;</code>)</p>\n<h2 id=\"appendix-b-efficient-implementation-of-boolean-scans\">Appendix B. Efficient implementation of boolean scans</h2>\n<h3 id=\"xor\">Xor</h3>\n<p>A xor scan on a machine word can be implemented efficiently in a few instructions by a typical prefix doubling\ntechnique, similar to the one described by <a href=\"https://graphics.stanford.edu/~seander/bithacks.html#ParityParallel\" rel=\"nofollow ugc noopener\">Bit Twiddling Hacks</a></p>\n<pre><code>uint64_t xorscan_word (uint64_t word) {\n    word ^= word &lt;&lt; 1;\n    word ^= word &lt;&lt; 2;\n    word ^= word &lt;&lt; 4;\n    word ^= word &lt;&lt; 8;\n    word ^= word &lt;&lt; 16;\n    word ^= word &lt;&lt; 32;\n    return word;\n}</code></pre>\n<p>We only need to handle the carry from the previous word. The typical way to do this would be\nto xor it with the first bit in <code>word</code>, but this is actually sub-optimal: notice that <code>carry</code>\nis needed at the very start of each iteration and only updated at the very end, which ties all iterations\nin a very long dependency chain.</p>\n<pre><code>void xorscan_bad (bool initial, uint64_t *words, size_t n) {\n    uint64_t carry = (uint64_t)initial;\n    for (size_t i = 0; i &lt; n; ++i) {\n        uint64_t word = xorscan_word(words[i] ^ carry);\n        carry = word &gt;&gt; 63;\n        words[i] = word;\n    }\n}</code></pre>\n<p>This compiles to the following core loop, which runs at around 3-4 bits per cycle.\n(all the timings I’ll give are for memory that is hot in cache, otherwise memory latency\nwould dominate runtime)</p>\n<p>Assembly output</p>\n<pre><code>.loop:\n    xor    (%rdx),%rdi\n    add    $0x8,%rdx\n    lea    (%rdi,%rdi,1),%rax\n    xor    %rdi,%rax\n    lea    0x0(,%rax,4),%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rcx\n    shl    $0x4,%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rcx\n    shl    $0x8,%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rcx\n    shl    $0x10,%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rcx\n    shl    $0x20,%rcx\n    xor    %rcx,%rax\n    mov    %rax,%rdi\n    mov    %rax,-0x8(%rdx)\n    shr    $0x3f,%rdi\n    cmp    %rdx,%rsi\n    jne    .loop</code></pre>\n<p>On a CPU with super-scalar execution, it would be nice if we could delay the usage of the previous result until\nthe latest possible time.\nFortunately xor is associative, so we can just broadcast the carry bit to every position in the final result: now\nthe CPU is free to start executing <code>xorscan_word</code> for the next iteration before the result of the previous one is ready.</p>\n<pre><code>void xorscan_good (bool initial, uint64_t *words, size_t n) {\n    uint64_t carry = - (uint64_t)initial;\n    for (size_t i = 0; i &lt; n; ++i) {\n        uint64_t word = carry ^ xorscan_word(words[i]);\n        carry = - (word &gt;&gt; 63);\n        words[i] = word;\n    }\n}</code></pre>\n<p>This is much faster, processing up to 10.5 bits per cycle.</p>\n<p>Assembly output</p>\n<pre><code>.loop:\n    mov    (%rcx),%rdx\n    add    $0x8,%rcx\n    lea    (%rdx,%rdx,1),%rax\n    xor    %rdx,%rax\n    lea    0x0(,%rax,4),%rdx\n    xor    %rdx,%rax\n    mov    %rax,%rdx\n    shl    $0x4,%rdx\n    xor    %rdx,%rax\n    mov    %rax,%rdx\n    shl    $0x8,%rdx\n    xor    %rdx,%rax\n    mov    %rax,%rdx\n    shl    $0x10,%rdx\n    xor    %rdx,%rax\n    mov    %rax,%rdx\n    shl    $0x20,%rdx\n    xor    %rdx,%rax\n    xor    %rsi,%rax\n    mov    %rax,%rsi\n    mov    %rax,-0x8(%rcx)\n    sar    $0x3f,%rsi\n    cmp    %rcx,%rdi\n    jne    .loop</code></pre>\n<p>If the CPU supports a carry-less multiply instruction (many modern CPUs do!) then <code>xorscan_word</code> can be implemented as carry-less multiplication with the word <code>0xffffffffffffffff</code> (all ones).</p>\n<h3 id=\"and\">And</h3>\n<p>All of the above (except for the carry-less multiply) can be applied verbatim, by replacing <code>^</code> with <code>&amp;</code>; however there is a faster\nmethod available.</p>\n<p>Firstly, a single-word and scan can be implemented much more simply than xor\n[<a href=\"#_footnotedef_9\" title=\"View footnote.\">9</a>].</p>\n<pre><code>uint64_t andscan_word (uint64_t word) {\n    return word &amp; ~(word + 1);\n}</code></pre>\n<p>This works because adding one turns the first run of ones into a run of\nzeros, then the negation turns the initial run of zeros back into a run of ones,\nas well as negating everything else. The final bitwise and\nsees two matching runs of ones, a matching pair of zero bits, and mismatched\nbits everywhere else.</p>\n<p>Here is an example, with the bits ordered from least to most significant.</p>\n<div class=\"table-wrap\"><table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><code>word</code></td><td><code>11100011010101011</code></td></tr><tr><td><code>word+1</code></td><td><code>00010011010101011</code></td></tr><tr><td><code>~(word + 1)</code></td><td><code>11101100101010100</code></td></tr><tr><td><code>word &amp; ~(word + 1)</code></td><td><code>11100000000000000</code></td></tr></tbody></table></div>\n<p>To extend this to work on an array of words, we observe that at most one word\nwill need an actual scan, because everything before it is known to be all ones\nalready, and everything after it can be unconditionally set to zero.</p>\n<p>The full implementation might be something like the following.</p>\n<pre><code>void andscan (bool initial, uint64_t *words, size_t n) {\n    size_t i = 0;\n    if (initial) {\n        for (; i &lt; n; ++i) {\n            uint64_t word = words[i];\n            if (~word) {\n                words[i] = andscan_word(word);\n                ++i;\n                break;\n            }\n        }\n    }\n    for (; i &lt; n; ++i) {\n        words[i] = 0;\n    }\n}</code></pre>\n<p>This produces two separate loops, the first and most intensive of them can do 62 bits per cycle, while the\nother gets compiled into a call to memset.</p>\n<p>Assembly output</p>\n<pre><code>.loop:\n    add    $0x8,%rcx\n    cmp    %rax,%rdx\n    je     .found\n    mov    (%rcx),%rsi\n    inc    %rax\n    cmp    $0xffffffffffffffff,%rsi\n    je     .loop</code></pre>\n<h3 id=\"less-than\">Less-than</h3>\n<p>This is the most complicated case, and the solution I’ll present comes from the\n<a href=\"https://github.com/simdjson/simdjson/blob/f807de973580fb22cc0db6b3b896716684b0266c/src/generic/stage1/json_escape_scanner.h#L50-L71\" rel=\"nofollow ugc noopener\">simdjson</a>\nlibrary, which uses it to find escaped characters. In fact, the intuition behind\nwhat <code>&lt;\\</code> does is that it turns off every <code>1</code> bit that is at an odd offset in\na contiguous sequence of <code>1</code>s, which is exactly what you need to figure out\nwhich backslashes in a string work as escapes and which are escaped.</p>\n<pre><code>uint64_t ltscan_word (uint64_t word) {\n    uint64_t guard = word &lt;&lt; 1 | 0xaaaaaaaaaaaaaaaa;\n    uint64_t parity = guard - word;\n    return (parity ^ 0xaaaaaaaaaaaaaaaa) &amp; word;\n}</code></pre>\n<p>Here is an example, again in little endian bit order.</p>\n<div class=\"table-wrap\"><table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><code>word</code></td><td><code>1110000111100111</code></td></tr><tr><td><code>word &lt;&lt; 1</code></td><td><code>0111000011110011</code></td></tr><tr><td><code>guard</code></td><td><code>0111010111110111</code></td></tr><tr><td><code>parity</code></td><td><code>1110010000010000</code></td></tr><tr><td><code>parity ^ odd</code></td><td><code>1011000101000101</code></td></tr><tr><td><code>result</code></td><td><code>1010000101000101</code></td></tr></tbody></table></div>\n<p>Shifting and setting the odd bits controls whether or not a borrow chain is\ninitiated in the subtraction: the start of a run of <code>1</code>s is set to <code>0</code> if it\nis at an even position, otherwise it stays <code>1</code>. The last element of a run is\nalso moved past the end to stop the borrow chain. Then subtracting the original\nbit mask creates a chain of borrows which leaves every bit set in the first\ncase, and just leaves every bit unset in the second case. If we now flip every\nodd bit, we will have the even bits set in the runs that start at even\npositions, the odd bits set in the runs that start at odd positions, and\nsome garbage outside of these runs which we clean up with bitwise and.</p>\n<p>Like the xor case, the trivial way to chain these together is to clear the\nfirst bit whenever the last bit of the previous result is set, though\nMarshall’s\n<a href=\"https://mlochbaum.github.io/BQN/implementation/primitive/fold.html#booleans\" rel=\"nofollow ugc noopener\">notes</a>\non this also mention a smarter way to reduce the length of the critical path.</p>\n<pre><code>void ltscan (bool init, uint64_t *words, size_t n) {\n    uint64_t carry = (uint64_t)init;\n    for (size_t i = 0; i &lt; n; ++i) {\n        uint64_t word = ltscan_word(words[i] &amp;~ carry);\n        carry = word &gt;&gt; 63;\n        words[i] = word;\n    }\n}</code></pre>\n<p>This particular version can do 8.8 bits per cycle.</p>\n<p>Assembly output</p>\n<pre><code>.loop:\n    andn   (%rsi),%rdi,%rdi\n    add    $0x8,%rsi\n    lea    (%rdi,%rdi,1),%rax\n    or     %rdx,%rax\n    sub    %rdi,%rax\n    xor    %rdx,%rax\n    and    %rdi,%rax\n    mov    %rax,%rdi\n    mov    %rax,-0x8(%rsi)\n    shr    $0x3f,%rdi\n    cmp    %rsi,%rcx\n    jne    .loop</code></pre>","headings":[{"level":1,"text":"Symmetries in boolean scans","id":"symmetries-in-boolean-scans"},{"level":2,"text":"Scans and folds","id":"scans-and-folds"},{"level":2,"text":"Algorithmic esoterica","id":"algorithmic-esoterica"},{"level":2,"text":"Case study: Resolving quotes","id":"case-study-resolving-quotes"},{"level":2,"text":"Boolean scans: how many do we really need?","id":"boolean-scans-how-many-do-we-really-need"},{"level":2,"text":"Scan composition rules","id":"scan-composition-rules"},{"level":2,"text":"Truth table shuffles","id":"truth-table-shuffles"},{"level":2,"text":"Conclusion","id":"conclusion"},{"level":2,"text":"Appendix A. Brainstorming and observations","id":"appendix-a-brainstorming-and-observations"},{"level":3,"text":"Truth tables as finite state machines","id":"truth-tables-as-finite-state-machines"},{"level":3,"text":"Invariants under the given transformations","id":"invariants-under-the-given-transformations"},{"level":2,"text":"Appendix B. Efficient implementation of boolean scans","id":"appendix-b-efficient-implementation-of-boolean-scans"},{"level":3,"text":"Xor","id":"xor"},{"level":3,"text":"And","id":"and"},{"level":3,"text":"Less-than","id":"less-than"}]}}