HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
dzaima
CBQN
C2023–2026
dzaima / CBQN

CBQN

a BQN implementation in C

2Distinct people
13Mentions
0Explicit recommenders
412GitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

a BQN implementation in C

GitHub

Where it is used

No documented product use has been verified yet.

What people are saying

No recommendation phrase was found in the indexed comments.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

Mentioned in the same Hacker News threads. That is not proof they are substitutes.

This week on Hacker News

Nov 7–Nov 14 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/14/2026, 3:56:07 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year13 dated mentions

Observed mentions: 2023-05-07 19:04:57 UTC to 2026-05-26 12:25:19 UTC.

0123452023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 5 mentions; 0 submissions, 5 comments20232024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 3 mentions; 0 submissions, 3 comments20242025-01-01 00:00:00 UTC to 2026-01-01 00:00:00 UTC (exclusive): 4 mentions; 0 submissions, 4 comments2026-01-01 00:00:00 UTC to 2027-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments. Incomplete calendar period.2026*

* An incomplete calendar period is marked with an outlined bar. Its count is not directly comparable with a full period.

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC505
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC303
20252025-01-01 00:00:00 UTC2026-01-01 00:00:00 UTC404
2026 (partial)2026-01-01 00:00:00 UTC2027-01-01 00:00:00 UTC101

What Hacker News says

13 sources
This is one of the best uses I've found for Singeli[0]. Here's how I implemented an AVX2 transpose kernel similar to that in transpose_Vec256_kernel, for generic type and vector/kernel size (I use unpack instructions rather than shuffle and blend, which I think is probably faster since it's just one instruction for each interaction): https://github.com/dzaima/CBQN/blob/v0.11.0/src/singeli/src/... The language is oriented towards compile-time array programming instead of managing a bunch of individual vectors. So you have runtime vec_select{} (docs at [1]), mirrored by compile-time select{}, and the indices generated by pairs{} can be used in either. [0] https://github.com/mlochbaum/Singeli/ [1] https://github.com/mlochbaum/Singeli/tree/master/include#sim...
In What it takes to transpose a matrix
Actually, even finding a larger function that would by itself have a meaningful disassembly is posing problematic; basically every function deals with in-memory data structures non-trivially, and a bunch do indirect jumps (function pointers, but also lookup-table-based switches, which require table data from memory in addition to assembly to disassemble). Like, here's a ~2.7x larger function: https://dzaima.github.io/paste/#0jVdNjxs3DL3nVwzQo30gRY00ChY... (is https://github.com/dzaima/CBQN/blob/90c1dc09e88c5324373281f6... with a bunch of inlining) (I'm keeping the other symbol names there even though they'd likely not be there for real closed-source things, under the assumption that for a full thing you'd have something doing a quick naming pass beforehand) This is still very much on the trivial end, but it's already dealing with in-memor
Read the rest
y structures, three inlined memory allocation calls (two half-deduplicated into one by the compiler, and the compiler initializing a bunch of the objects' fields in one store), and a bunch of inlined tagged object manipulations; should definitely be possible to get some disassembly from that, but figuring out the useful abstractions that make it readable without pain would probably take aggregating over multiple functions. (unrelated notes of your previous results - claude indeed guessed correctly that it's BQN! though CBQN is presumably wholesale in its training data anyway; it did miss that the function has an unused 0th arg (a "this" pointer), which'd cause problems as the function is stored & used as a generic function pointer (this'd probably be easily resolved when attempting to integrate it in a wider disassembly though); neither claude nor cgpt unified the `x>>48==0xfff7` and `(x&0xffff000000000000)==0xfff7000000000000` which do the exact same thing but clang is stupid [https://github.com/llvm/llvm-project/issues/62145] and generates different things; and of course a big question is how many such intricacies could be automatically reduced down with a full codebases worth of context, cause understandably the single-function disassemblies are way way more verbose than the original)
In The current state of the theory that GPL propagates to AI models
Beyond what others have mentioned, I think another big differentiating factor between APL and the rest of those languages is that APL isn't focused on allowing the user to expand the language meaningfully, but rather on being a well-rounded language by itself (which is how it can be reasonably useful without objects with named fields, mutation, explicit loops (or only gotos in APLs infancy!), no first-class functions, no macros, and only one level of higher-order function (though of course most APL implementations have some of those anyway)). As such there's really no pretty "core" that pulls its weight to implement in 1000LoC and is useful for much. Here's a simple minimal APL parser in JS that I wrote once to display one way of parsing APL: https://gist.github.com/dzaima/5130955a1c2065aa1a94a4707b309... Couple that with an implementatio
Read the rest
n of whatever primitives you want, and a simple AST walker, and you've got a simple small APL interpreter. But those primitive implementations already take a good chunk of code, and adding variables/functions/nested functions/scoping/array formatting/etc adds more and more bits of independent code. Perhaps if you accept defining bits in the language in itself via a bootstrap step, BQN is a good candicate for existing small implementations - a BQN vm + minimal primitive set is ~500LoC of JS[0] (second half of the file is what you could call the native components of a stdlib), 2KLoC for first public commit of a C impl[1], both of those having the rest of the primitives being self-hosted[2], and the compiler (source text → bytecode) being self-hosted too[3]. (the C impl also used r0.bqn for even less required native primitives, but modern CBQN uses very little of even r1.bqn, having most important things native and heavily optimized) [0]: https://github.com/mlochbaum/BQN/blob/master/docs/bqn.js though earlier revisions might be more readable [1]: https://github.com/dzaima/CBQN/tree/bad822447f703a584fe7338d... [2]: https://github.com/mlochbaum/BQN/blob/master/src/r1.bqn (note that while this has syntax that looks like assigning to primitives, that's not actual BQN syntax and is transpiled away) [3]: https://github.com/mlochbaum/BQN/blob/master/src/c.bqn
In APL Interpreter – An implementation of APL, written in Haskell (2024)
Don't think any language has standardized SIMD that's particularly nice; Highway is probably a quite nice library on C++, though I haven't used it enough to get comfortable. The thing I use for my projects is Singeli[1], a DSL specifically made for SIMD stuff (though it's capable of generally sanely doing abstractions over types/operations/loops; it's just a fancy code generator). Obligatory disclaimer that I'm one of the two people working on its design. It's far from a nice experience starting from nothing, but it's pretty nice for what I do. CBQN's the main place it's used, can click around its source: https://github.com/dzaima/CBQN/tree/develop/src/singeli/src Its goal isn't necessarily to unify architectures, but rather make it as easy as possible to make abstractions that do; as such its built-in includes for x86 don't have arbitrar
Read the rest
y shuffling, but do provide a sane interface over the cases that are supported in a single instruction (not including constant creation/loading), and those can run on NEON unchanged (assuming they're ran on 128-bit vectors, of course, as NEON doesn't support larger ones); and, with Singeli just generating C/C++ currently, you can just map in __builtin_shufflevector if desired. e.g. here's your AVX2 `pre`: include 'skin/c' # defines infix a+b & a*b etc to run __add/__mul/... (yes, those aren't here by default, and you can define custom infix/prefix ops) include 'arch/c' # defines __add & __mul to do C ops include 'arch/iintrinsic/basic' # not necessary for a shuffle, but provides basic x86 arith ops include 'arch/iintrinsic/select' # x86 shuffles; there's similar 'arch/neon_intrin/basic' & 'arch/neon_intrin/select' for NEON fn pre(inp: [32]i8, out: *[32]i8) : void = { store{out, 0, vec_shuffle{16, inp, merge{ # 16 specifies to repeat per 16-elt lane range{8}*2+1, # lower half: 8 Y components; compile-time index calculations range{4}*4, range{4}*4+2 # upper half: (4 * U), (4 * V). }}} } As a more fancy thing, I've got this working (via bodging together the definitions in CBQN with some sugar to make this pretty; not including all that boilerplate here), compilable to SSE2/AVX2/NEON producing a 4x unrolled core loop, plus tail handling (via reading past the end and doing a load-blend-store if necessary because that's what CBQN's fine with; could easily define a fancy_loop such that it does a scalar tail though). (also can be compiled to RVV via currently-unpublished mappings; no need to unroll for RVV; can choose to do either a stripmined loop or one with a separate tail): fn sigmoid{E}(r:*E, x:*E, n:ux) : void = { def V = arch_preferred_vector{E} @fancy_loop{V,4}(r in tup{'dst',r}, x, M in 'mask' over n) { # this loop body is generated 3 times for x86 & ARM - with x being a 4-elt tuple (core unrolled loop); a 1-elt tuple and no masking; a 1-elt tuple and masking if (any_hom{M, ...(x!=x)}) { emit{void, 'abort'} } r{x / __sqrt{1 + x*x}} } # were it not for a bug in tuple loop var mutation in Singeli having undesired pervasion, this would be possible: # @fancy_loop{V,4}(r, x, M in 'mask' over n) { # if (...) ... # r = x / __sqrt{1 + x*x} # } } export{'sigmoid', sigmoid{f32}} (e.g. generated C for AVX2: https://godbolt.org/z/KTeGsazKP) (I'm not actually particularly expecting interest in Singeli; I just like writing stuff :) ) [1]: https://github.com/mlochbaum/Singeli
In Towards fearless SIMD, 7 years later
I got stuck on sorting too, was working on SingeliSort (https://github.com/mlochbaum/SingeliSort) for a while. The basic performance is there but I need to get serious about testing before using it. But the radix sort and counting sort should be very solid. The approach is about the same as the C code currently used in CBQN, linked below. The main complication is to reduce constant overhead for shorter arrays with a small count type and better prefix sums, interleaved SWAR for SingeliSort since it targets generic architecture and shared SIMD utilities in CBQN. Email in my Github profile, feel free to contact me any time if you'd like to talk about algorithms! 32-bit radix sort: https://github.com/dzaima/CBQN/blob/v0.8.0/src/builtins/grad... plus https://github.com/dzaima/CBQN/blob/v0.8.0/src/builtins/radi...
In Static search trees: faster than binary search
For most things (or, well, that depends on what one commits I guess), the filtered form will likely be the more preferable one. If you want to keep around some of the largely-useless stuff just in case, you can `git tag backup/sso-garbage` or similar before rebasing. For fun, here's a git log pre-rebase of some backup tag I have (final commits: https://github.com/dzaima/CBQN/commits/eccbac37ab15bd68320d9...): f6bc866f (tag: backup/pre-rebase-si-bitwiden) ..aarch64 0fd19c39 Singeli n→8 bitwiden ed2a0655 more Singeli utils 556aa17b use q_fbit more a28adcbb minor src/README.md cleanup d6d40fc7 .warning comment 9dcf2f75 .don't need customizeShape for explicitly-created bitarr 958a04a1 update Singeli submodule 159ee16e .. ea43cb2d . b067d7a8 !! c588a381 fix ⟨1‿2⟩⊸⊏˘ mat 0e251720 . 8f049ed
Read the rest
e fast inds⊸⊏˘bits for 4-bit & 2-bit input & output cells def8c196 fast inds⊸⊏˘bits for 8-bit input & output cells 1638f8d4 .valgrind false-positive hiding 1023aaa5 --replxx-read-only bee4169e .more valgrind improvement c3643fc6 use custom valgrind pdep/pext everywhere 45796542 fix out-of-bounds load on empty replxx line bc5894b9 make bitp_get & bitp_set load/store u8 instead of u64 3b4381b2 include last power of two in fast-path ⌽˘ Most of that didn't compile on aarch64 before that last commit, even changes that could affect aarch64 as I sprinkled in some other things while working on the "main" thing as they came up.
In Rewrite Git history via drag-and-drop
I was able to make a variant of the higher-base version that runs in a single pass, by stopping when one partition fills up and using a different method for the remaining (asymptotically few) elements. I described the idea, which is based on another effort called MergeShuffle, here: https://mlochbaum.github.io/BQN/implementation/primitive/ran... And it is better when N gets large. My implementation set the cutoff at 2^19 elements, although the effect isn't too big for a few more powers of two. Here's the main radix loop: https://github.com/dzaima/CBQN/blob/v0.7.0/src/builtins/sysf...
In Zero Tolerance for Bias
Worth pointing out that this can depend a lot more on fiddly details than you might expect. In particular, you're dealing with a small fixed width allowing the hash to be stored in the table instead of the key. The article emphasizes variable-length keys, and I don't see any specialization on key sizes (if 4- and 8-byte keys aren't common then this makes sense; if they are then I'd expect dedicated table code for those sizes to be valuable). And set lookups are also just a bit different from value lookups. I think these cases are different enough that I have no idea if the results would carry over, although I can see how the bidirectional approach would reduce probing more than RH which seems good. ...and since I've done a lot of work with Robin Hood on small-key lookups, I can point out some little tweaks that have made a big difference
Read the rest
for me. I have 8-byte lookups at just over 3ns/lookup[0], albeit at a very low load factor, typically <50%. A key step was to use the maximum possible hash as a sentinel value, handling it specially in case it shows up in the data. This way, instead of probing until finding an empty bucket or greater hash, probing just finds the first slot that's greater than or equal to the requested key's hash. So the lookup code[1] is very simple (the rest, not so much). The while loop is only needed on a hash collision, so at a low load factor a lookup is effectively branchless. However, these choices are specialized for a batched search where the number of insertions never has to be higher than the number of searches, and all the insertions can be done first. And focused on small-ish (under a million entries) tables. [0] https://mlochbaum.github.io/bencharray/pages/search.html [1] https://github.com/dzaima/CBQN/blob/5c7ab3f/src/singeli/src/...
In Building a faster hash table for high performance SQL joins
The kind of things you can use direct SIMD for is a reasonably-different space than array languages; while some things can translate reasonably, APL-likes get much less pretty on operations where elements don't have homogeneous meaning/mapping, e.g.: the base64 conversion of OP. (for reference, I work on https://github.com/dzaima/CBQN, an implementation of BQN, an APL derivative, and we have a DSL for implementing SIMD primitives for it (and sometimes scalar ones too): https://github.com/mlochbaum/Singeli)
In Designing a SIMD Algorithm from Scratch
In a project I'm working on[0], there's an array object type used throughout, which can sometimes point to arbitrary data elsewhere. In a funky edge-case[1], such an array can be built with an unaligned data pointer. Thus, if gcc/clang started seriously utilizing aligned pointer accesses everywhere, nearly every single load & store in the entire project would have to be replaced with something significantly more verbose. Maybe in a more fancy language you could have ptr<int> vs unaligned_ptr<int> or similar, but in C you kinda just have compiler flags, and maybe __attribute__-s if you can spare some verbosity. C UB is often genuinely useful, but imo having an opt-out option for certain things is, regardless, a very reasonable request. [0]: https://github.com/dzaima/CBQN [1]: Any regularly allocated array has appropriate alignment. But the
Read the rest
re are some functions that take a slice of the array "virtually" (i.e. pointing to it instead of copying), and another one that bitwise-reinterprets an array to one with a different element type (again, operating virtually). This leads to a problem when e.g. taking i8 elements [3;7) and reinterpreting as an i32 array. A workaround would be to make the reinterpret copy memory if necessary (and this would have to be done if targeting something without unaligned load/store), but that'd ruin it being a nice O(1).
In GCC always assumes aligned pointer accesses (2020)
> An algorithm which works well for hardware implementing 128-bit vectors might be quite bad for one implementing 2048-bit vectors Yeah, that's definitely a concern. I do wish that RVV had some built-in way to cap the width in a request (of course emulatable by doing a min() manually), but beyond that it's up to the hardware I guess. > but they are likely to also have many loops which cannot be vectorised performance-portably Such would also likely not be autovectorizable either, making whether it can be done performance-portably much less significant due to the much lower frequency of manually-vectorized things. In most situations, if you're at the point of manually vectorizing some piece of code, it's enough of a bottleneck that you can tune it for whatever (set of) hardware you're targeting. I, as someone who enjoys writing manually ve
Read the rest
ctorized stuff, don't particularly like that added burden, but it's a pretty reasonable decision. > I have some half-written code to use vector shuffles in place of loads for indexing small arrays in the j interpreter Marshall has written that for CBQN[0] for AVX2, primarily tuned for Intel[1], but yeah the boundary is pretty sketchy to have to hard-code and RVV could definitely make it worse. But base 128-bit RVV, with LMUL=8 provides 1024-bit lookup tables in a single instruction, which should at least mostly negate the desire of manually blending multiple, which is where you'd start desiring tuning. Though, I wouldn't be surprised if an LMUL=8 in-register shuffle on some hardware would be slower than a memory gather for some element widths, which'd suck. > but n/4 starts to cause problems The existence of LMUL=8 means that hardware will necessarily have to figure out some way to split single instructions across many cycles. Arguably that's by itself pretty bad, but at least I'd imagine that further splitting up should be largely free, though the register file size problem of course persists. > sve would effectively standardise on 256 bits Yeah, I feel like 256 is a pretty nice sweet spot too. In regards to higher-level hardware interfacing, I'd worry that what it'd do would be making everything uniformly slow, instead of making everything uniformly fast, thus potentially not actually being any better than status quo. And such would be at a very significantly worse spot in terms of performance-portable tuning than RVV. Not to mention that nicer array operation behavior wouldn't help the vast majority of code which is very scalar & dispatchy. And an equivalent of this would be to just share programs in IR and not machine code form, which we can already do, but don't really for whatever reason (if requiring near-native speed; otherwise there's JS/WASM/JVM. Then there's LLVM IR/MLIR but I haven't heard of it being used as a shared binary format. Maybe Julia would count with it compiling LLVM IR at runtime, and it having high-level array ops?) [0]: https://github.com/dzaima/CBQN/blob/a67a19dd0bf6be5eb765cac8... - wd is bit width of the table elements, xl - number of such elements in the table. Beyond that, it just does the expected thing of blending permutes & shuffles via some Singeli that not even I have bothered to particularly understand [1]: "Select" section at https://mlochbaum.github.io/bencharray/pages/summary.html on Tiger Lake i5-1135G7, with similarly scaling results on haswell & skylake
In Vector ALU Patterns
By "large" I mean compared to the fraction of needs-slow-emulation things present currently in C (which is currently approximately zero, other than using integer/float types unsupported on the target hardware (and maybe multiplication/division), but that's comparatively extremely easy to determine). Maybe my use-case - implementing an array language - is unusually unfit for being done in a generic way, but I just don't see how it (or much of anything outside of very trivial things) would be reasonable to figure out how to do performantly with anything other than either manually using intrinsics, or with custom-written abstractions around such with known worst-case emulation amount (we're doing[0] the latter with a DSL[1] that's basically a basic compile-time code generator; that just leaves unsupported things undefined on any given archit
Read the rest
ecture, and in most occurrences of hitting that I've found ways to write things avoiding expensive emulation). [0]: https://github.com/dzaima/CBQN/tree/master/src/singeli/src [1]: https://github.com/mlochbaum/Singeli
In Ask HN: Why are there no traditional language compilers that target the JVM?

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.