In this case, "atomic 128-bit store" is the special instruction, with the twist that half of those 128 bits contain a pointer.
HN user
corsix
You can start with this idea, and then make it _very_ performant by using bit counting instructions. See https://www.corsix.org/content/higher-quality-random-floats for an exposition.
Indeed, LuaJIT support for Windows/Arm64 was added in https://github.com/LuaJIT/LuaJIT/issues/593, and there’s experimental out-of-tree support for Windows/Arm64EC in https://github.com/LuaJIT/LuaJIT/issues/1096
For an implementation of logical immediate encoding without the loop, see https://github.com/LuaJIT/LuaJIT/blob/04dca7911ea255f37be799...
AArch64 NEON has the URSQRTE instruction, which gets closer to the OP's question than you might think; view a 32-bit value as a fixed-precision integer with 32 fractional bits (so the representable range is evenly spaced 0 through 1-ε, where ε=2^-32), then URSQRTE computes the approximate inverse square root, halves it, then clamps it to the range 0 through 1-ε. Fixed-precision integers aren't quite integers, and approximate inverse square root isn't quite square root, but it might get you somewhere close.
The related FRSQRTE instruction is much more conventional, operating on 32-bit floats, again giving approximate inverse square root.
Unfortunately things aren't so simple, as when doing JIT compilation, LuaJIT _will_ try to shorten the lifetimes of local variables. Using the latest available version of LuaJIT (https://github.com/LuaJIT/LuaJIT/commit/0d313b243194a0b8d239...), the following reliably fails for me:
local ffi = require"ffi"
local function collect_lots()
for i = 1, 20 do collectgarbage() end
end
local function f(s)
local blob = ffi.new"int[2]"
local interior = blob + 1
interior[0] = 13 -- should become the return value
s:gsub(".", collect_lots)
return interior[0] -- kept alive by blob?
end
for i = 1, 60 do
local str = ("x"):rep(i - 59)
assert(f(str) == 13) -- can fail!!
endSome compute might be on the AMX units (dedicated matrix multiplication coprocessor, closely attached to the CPU, distinct from both ANE and GPU). They gained bf16 support in M2.
close() is documented as a cancellation point, and is the kind of syscall that might crop up in a destructor.
Using your cdf framing, while there is a point about [0, 1) versus (0, 1] intervals, the bigger point of the article is about whether said cdf holds for any IEEE-754 double-precision p, or whether it only holds for p of the form i*2^-53 (for integer i).
Oh, cute. It looks like they ever-so-slightly overweight the probability of values whose mantissa is entirely zeroes though. For example, the probability of hitting exactly 0.5 should be 2^-54 + 2^-55, whereas zig looks to give it 2^-54 + 2^-54.
https://www.corsix.org/content/whirlwind-tour-aarch64-vector... is my take on NEON, albeit not quite the same form factor as the OP.
I think https://github.com/LuaJIT/LuaJIT/commit/6a2163a6b45d6d251599... improved things a bit, notably making automatic tarballs work again.
I’m pleased to see FUTEX2_SIZE_U64, but saddened that it isn’t actually implemented. It has always seemed like a very useful primitive to have.
Also interesting is that https://luajit.org/status.html now states “LuaJIT is actively developed and maintained” (whereas for the last ~5 years, “actively” isn’t a word I’d have used), and makes reference to a TBA development branch.
Per https://www.stateof.ai/compute, one of the players in the market has ten thousand GPUs in a private cloud. Out-computing just that one player is hard enough, let alone out-computing the whole market.
The trie structure described in the article can be (ab)used to export an infinite number of symbols from a library: https://www.corsix.org/content/exporting-an-infinite-number-...
From a hardware perspective, vector instructions operate on small 1D vectors, whereas tensor instructions operate on small 2D matrices. I say “instructions”, but it’s really only matrix multiply or matrix multiply and accumulate - most other instructions are fine staying as 1D.
Assuming that you're after "round to nearest with ties toward even", then the quoted numpy code gets very close to `vcvtps2ph`, and one minor tweak gets it to bitwise identical: replace `ret += (ret == 0x7c00u)` with `ret |= 0x200`. Alternatively, the quoted Maratyszcza code gets to the same place if you replace `& 0x7c00u` with `& 0x7dffu`.
The first niche that came to mind was x86 code running under Rosetta 2; despite ARM having an equivalent to F16C, Rosetta 2 doesn’t translate AVX, and F16C doesn’t have a non-AVX encoding.
Using the notation from the article, N+K is sufficient for RS(N,K). One point of confusion is that different authors use different notation; some use RS(num data shards, num parity shards), some use RS(total num shards, num data shards), and some use RS(total num shards, num parity shards). Per the article, I'll use RS(num data shards, num parity shards).
As for where the +1 comes from, the clue is in the "noting that you shouldn't use the value 0 in the encoding matrix" remark. The TLDR is that the +1 isn't required, and arises from an (incorrect) attempt to fix an incorrect construction. The non-TLDR is rather long. First, we need to move from polynomials (per the article) to matrices (per the quoted remark). For this movement, let F denote the polynomial from the article, then it so happens that F(k+1) can be expressed as a linear combination of F(1), F(2), ..., F(k). Similarly, F(k+2) can be expressed as a linear combination of F(1), F(2), ..., F(k). This continues to be true up to F(k+t) (and beyond). These various linear combinations can be written as a k-by-t matrix, which is what the quoted remark means by "encoding matrix". Second, once thinking with matrices rather than with polynomials, people want to construct the encoding matrix directly, rather than deriving it from a polynomial. In this direct construction, the requirement is that every square submatrix (of any size) of the k-by-t matrix is invertible. Accordingly, no element of the k-by-t matrix can be zero, as the 1-by-1 submatrix containing just that zero element isn't invertible. Third, one common (but incorrect) direct construction is to create a k-by-t Vandermonde matrix. Such a matrix is usually constructed from some number of distinct elements, but if zero is used as such an element, then the resultant matrix will contain zeroes, which is problematic. Excluding zero causes the Vandermonde construction to _sometimes_ work, but it still doesn't _always_ work. Per https://www.corsix.org/content/reed-solomon-for-software-rai..., there's a slightly different Vandermonde construction that _does_ always work, and also a Cauchy construction that always works, both of which work for zero. Both of these correct constructions have a strong parallel to the polynomial construction: they involve choosing N+K distinct field elements, which is akin to choosing the N+K distinct x co-ordinates for the polynomial.
The next article in blog order is one application: https://www.corsix.org/content/reed-solomon-for-software-rai...
Another application is crypto: the SubBytes step of AES maps very neatly onto gf2p8affineinvqb, so algorithms that are similar to AES but not exactly AES could make use of gf2p8affineinvqb
Alternatively, the following pair of articles, the first of which is already referenced as a footnote in the OP: http://www.corsix.org/content/galois-field-instructions-2021... http://www.corsix.org/content/reed-solomon-for-software-raid
Lua gets this right - the lowering of loops (e.g. https://www.lua.org/manual/5.1/manual.html#2.4.5) says “var is invisible” and has “local v = var”, the latter akin to Go’s “item := item”
Performance comparison is hard given that the Intel one hasn’t shipped yet.
The elephant in the room contrast: Apple has been shipping this for years, whereas Intel _might_ ship theirs in something later this year.
Slightly cursed is how I roll. I would like to know where the trick first originated from though (I found it at https://github.com/yvt/amx-rs/blob/main/src/nativeops.rs#L22 rather than inventing it de novo)
https://www.corsix.org/content/contrasting-intel-amx-and-app... compares this with Intel’s AMX
Given latency 3 / throughput 1, the only reasonable implementations are: A) Three ports, each non-pipelined, taking 3 cycles B) One port, with three-cycle pipeline (each cycle, one instruction can enter the start of the pipeline, and anything in-progress moves forward one stage) Given that CRC isn't too hard to pipeline, and (B) requires less physical hardware, it is almost certainly (B).
https://uops.info/html-instr/CRC32_R64_R64.html answers that for you. Zen2 and Zen3 same as Intel: latency 3, throughput 1. Older AMD chips less good.
The math is fiddly to get right, but (as the author) I'd suggest that the disadvantage is very tight coupling to the CPU implementation: the interleaving is based on the relative speeds of the two methodologies, so if the relative speeds of the two methodologies drastically changes on a future CPU implementation, this _particular_ interleaving could end up _slower_ than either methodology on its own.