:: journal :: 2023

short-form posts, dated.

< 2022 · 2024 > · 2026 2025 2024 2023 2022 2021 · tags

People who think that

int max(int * array, int length)
{
  int currentMaximum = 0;
  for (int index = 0; index < length; index++)
  {
    if(array[index] > currentMaximum)
    {
      currentMaximum = array[index];
    }
  }
  return currentMaximum;
}

is better than

I max(I*a,I n)_(I t=0;Fi(n,IF(a[i]>t,t=a[i]))t)

Can not be trusted.

Getting slowly bored of AoC. Too many uninteresting, cookie cutter imperative problems.

I wish mailing lists were more common. E-mail feels a bit like a forgotten medium to me, but it helps me stay organised and safe with OpenPGP signing & encryption.

(1)  (2×((¯3+25×)×!(+)×!)+.÷!(3×)×2*)

Interesting benchmark results for general-purpose data compressor performance on Brainfuck code (compiled tiny_sk from the asm2bf distribution):

% wc -c *
29446 tiny_sk.b
 1188 tiny_sk.b.br
 1405 tiny_sk.b.bz2
 1368 tiny_sk.b.bz3
 1345 tiny_sk.b.gz
 1392 tiny_sk.b.lzma
 1097 tiny_sk.b.paq8l
 1269 tiny_sk.b.zst

Remarkably: While PAQ8L wins, its closest contender is actually Brotli. Then Zstandard, to be followed by gzip, bzip3, lzma and bzip2.

Figuratively, trying to bite off twice as much as I can chew sounds like a good way to wrap up the last few years for me. Seemingly, I can chew more and more as time passes. This can't be sustainable.

The illusion of simplicity backed by megabytes of cruft: "Chickenshit Minimalism"

Some musings about register allocation.

Before you read this, remember that this is a highly hypothetical scenario completely disconnected from how CPUs work. Registers do not have to be 8 bytes, there are caches for registers, etc...

Consider a special variant of register allocation where outside of wanting to minimise the amount of spills interprocedurally, we also want to put another constraints on how the registers are allocated. For example, instead of using the first available temporary register as many code generators for MIPS do (unfortunately further away from optimal on x86 due to register requirements for div, etc...); we want to pick a register such that the last referenced register is the closest to the currently considered register. In particular, consider some function f(x, n, m) which models the runtime of the two operand instruction being currently executed. Long division p <- p/q has the computational complexity of O(q^2), hence our function f(x,n,m)=(xm)^2, where x signifies the cost of loads from p and q. Loading Q after having loaded Q again is cheap (caching), but loading P after having loaded Q or vice versa is more expensive. The cost x is defined as |R_p - R_q| - i.e. the distance between two registers in the register file. This may come useful in scenarios where registers are large and the computer has multi-level cache that quickly evicts unused data and eagerly caches new data.

For example: div r1, r4 has the cost factor |1-4|=3 further applied to the worst-case (r4)^2 amount of operations - the instruction would take 3*(r4)^2 cycles. The cost factor of div r2, r1 would be only |2-1|=1, hence the instruction takes only (r1)^2 cycles.

Hence the question posed is: What is the most efficient algorithm to model this particular register preference? The answer to this question would probably provide answers to other similar questions regarding register preference that are ubiquitous on platforms where the exact register you choose for a particular variable does matter (e.g. x86; due to how certain instructions like to store output/take input from a hardcoded register).

A crucial thing to notice is that the problem of register allocation with a preference on the closest register available is essentially equivalent to a modified variation of the 1-D Travelling Salesman Problem where every node can be (and is encouraged to be) visited multiple times if possible!

It just so happens that compilers appear to emit low numbered registers, but that's due to preferential treatment for volatile registers, as used by calling conventions and then coalesced etc. since using a higher numbered (typically where non-volatile/callee-save live) amounts to spill + reload insertion. In graph colouring, one could use a heuristic to select free colours as closest to an already selected colour. Hence the compiler backend developer's solution to the problem would be prioritising colours closest to the direct neighbours already assigned colours assuming an ordering to the colours, obviously where colours are numbers to produce a non-optimal but relatively good result.

Notice how similar this approach is to the nearest neighbour search approximate solution to the Travelling Salesman Problem. Hence, to connect the dots: I think that this particular solution is the best one considering speed and how close the output is to being optimal. An optimal analogue would be the exhaustive search TSP solution, while a considerably less optimal but way faster in terms of computational complexity option could be applying the Christofides algorithm.

If you are still wondering what is the use of it, I have to disappoint you and refer you to reading and comprehending this article: https://esolangs.org/wiki/Brainfuck

Regarding the recent WHO decision to classify aspartame as a potential carcinogen, remember that being a hairdresser or eating pickled food according to the medical literature is also potentially carcinogenic.

https://pubmed.ncbi.nlm.nih.gov/19755396/ & https://aacrjournals.org/cebp/article/21/6/905/69347/Pickled-Food-and-Risk-of-Gastric-Cancer-a

I have just removed 99% of JavaScript code that used to be on my website. The remaining 0.5KB is used for the mobile navbar to work. So, technically speaking, my website is now completely the same as if you disabled JS entirely. And it still has syntax highlighting in blog posts and math rendering. The only exception being the inherently dynamic subpages of my website (the SDL game ports, etc... - these obviously won't work well without JS)

Most people would never tolerate common scams in a physical setting but if you make one small change as to the technology being used, the mentality in some people changes.

This phenomenon of distancing layers via technology is actually really common; think of how many friends you have that would never fall for traditional multi-level marketing scheme, "get rich quick" scam, penny stock pump and dump, but then if you change the technology to, say, cryptocurrency, then some of those red flags just subconsciously go away. I've seen real-life examples of people who on one hand are aware enough to say all these influencers trying to shill this penny stock just want to pump and dump me but then later on they say "Yeah, I really do think that this doggy themed token with no utility whatsoever is going to become 100x more valuable so I better get in quick!" and you might even know somebody who went "I'm not going to give this RuneScape scammer my go- oh my goodness Obama's doubling my Bitcoin on Twitter!!".

So many new scams are just old scams with new technology because of this very same psychological distancing barriers that we subconsciously create.

It would be nice if IRC didn't die and someone came up with ways to extend the protocol to support E2EE and other stuff.

I remember being a pretty happy and frequent IRC dweller back in maybe 2019, but it has only gone downhill since (when it came to activity, quality of discussion, the entire freenode drama, etc...) and because I haven't made that many particularly good friends, I didn't end up being invited into mid-sized private networks which to my knowledge still thrive and do surprisingly well considering that they are IRC. I can only imagine a similar fate has met XMPP.

OTOH, most quality internet places are slowly moving away from centralised services and slowly dig themselves underground. It's getting harder and harder to tell AIs apart from humans, some of my friends are particularly paranoid about their messages being used to train LLMs. Internet is slowly becoming an useless sea of spam again.

My main issue with python is the GIL, mess with venvs and other nonsense, bad performance. Python itself is not very embeddable, lacks a proper “type” system. It would be nice if we had some sort of an unintrusive typing system that would help to catch a lot of embarrassing mistakes we make while writing js/python/lua/other untyped languages; I feel like gradual typing from TS actually solves this problem pretty nicely!

A reasonably fast lisp/scheme built on top of a lua-like VM with a gengc & jit compiler + gradual typing of TS and a ground-up implementation of a rich standard library that doesn’t make the programmer reinvent hashtables, graphs or linked hashsets would also be nice. To me what makes a scripting lang good is reasonable (not C-level but still okayish) performance, a substantial amount of software you can graft code from to speed up development (see: python’s ecosystem), some sort of largely inference-based static verification with minimal amount of decorators and other crust to prevent certain classes of errors in the runtime, etc.

A gentle reminder of the still unsolved issue that I had with the Linux Kernel ever since I started using a M.2 drive:

If you use LUKS to encrypt a M.2 SSD drive and then perform intensive I/O from within the system, it is going to lock up your entire machine. Impressive, isn't it?

Debian has dm-crypt worker I/O queues enabled by default and they're written very poorly, so the kernel waits until they are full or near-full before trying to sync them to the disk, and with multiple queues all fighting for disk access, the disk dies under the load and the system locks up. Now, a linux nerd is going to cry me a bucket of tears that the queues are written perfectly with no flaws whatsoever. The problem is that i don't care, whatever iotop shows is the truth revealed.

I also can't run any of my VirtualBox VMs because of this bug in VBox reported 10 years ago: https://www.virtualbox.org/ticket/10031?cversion=0&cnum_hist=14. Obviously, VBox hates I/O latency and eventually gives up if access to the host's storage takes too long so the hypervisor turns back around to the guest VM and says that the read/write is impossible and the Windows instance in the machine randomly bluescreens.

Situations like these make me miss Windows pretty badly. Shame that W8, W10 and W11 are essentially spyware unless you go through heights to debloat them.

Calling all C language lawyers for help: I am wondering whether empty structures are allowed or not. assuming either c99 or c11, whichever more favourable. To quote the standard,

6.2.6.1 Except for bit-fields, objects are composed of contiguous sequences of one or more bytes, the number, order, and encoding of which are either explicitly specified or implementation-defined.

This could imply that an empty struct has a non-zero size (so a C++ like behaviour), however, 6.2.5-20 says: "A structure type describes a sequentially allocated nonempty set of member objects". So I thought that I can circumvent it the following way: struct { int dummy: 0; } variable;

One would have to remove the declarator, yielding struct { int : 0; } variable;, per 6.7.2.1.4: "The expression that specifies the width of a bit-field shall be an integer constant expression with a nonnegative value that does not exceed the width of an object of the type that would be specified were the colon and expression omitted.) If the value is zero, the declaration shall have no declarator."

So finally, whether we can have empty structs or not depends on whether int : 0 counts as a member object, but i can't find anything that would be conclusive on this matter. I have already observed that the C standard treats zero size bit-fields specially, but the only relevant bit of information I could find was 6.7.2.1.12: "As a special case, a bit-field structure member with a width of 0 indicates that no further bit-field is to be packed into the unit in which the previous bit-field, if any, was placed."

Any ideas?

N.B. the wording in 6.7.2.1.4 says "non-negative", not "positive", meaning that the width of 0 is technically allowed as a "normal" width.

This has to be the most curiosity-inducing error messagge that I have seen in a long while.

In static member function ‘static constexpr std::char_traits<char>::char_type* std::char_traits<char>::copy(char_type*, const char_type*, std::size_t)’,
   inlined from ‘static constexpr void std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::_S_copy(_CharT*, const _CharT*, size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ at /usr/include/c++/12/bits/basic_string.h:423:21,
   [...]
/usr/include/c++/12/bits/char_traits.h:431:56: warning: ‘void* __builtin_memcpy(void*, const void*, long unsigned int)’ accessing 9223372036854775810 or more bytes at offsets -4611686018427387902 and [-4611686018427387903, 4611686018427387904] may overlap up to 9223372036854775813 bytes at offset -3 [-Wrestrict]
 431 |         return static_cast<char_type*>(__builtin_memcpy(__s1, __s2, __n));
     |                                        ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~

Turns out that this is actually a compiler bug - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329 and the comments on this thread are awesome. Creating a new string instance using the (int n, char c) constructor basically causes warnings to break due to some issue with Ranger, it’s been a known bug for 2 minor versions of GCC, and there is no good fix for it. And you need a PhD in compiler design to understand why.

Realisation: When I was working on my ELF infector post, I once didn't properly sandbox it from the rest of my system. This way I accidentally got cargo and a bunch of other binaries infected and when they randomly started behaving weirdly, I finally figured out what was going on and reinstalled Rust.

One way to allow RC without a cycle collector would be to enforce an unidirectional heap. And the actor model will surely help in avoiding atomics too.

Seems like bzip3 wipes the floor with lzma when it comes to java class files. What if i told you that the size of the average jar you see online could be 2x-4x smaller?

 94136320 lisp.tar
  6827813 lisp.tar.bz3
 13984737 lisp.tar.gz
  7693715 lisp.tar.lzma
  8292311 lisp.tar.zst
130934896 total

Discussing CS with people on Discord feels like giving a nuclear physics lecture in a supermarket.

It’s alive…

--> cas:taylor x 0 (cas:fn x \sin x)
ƒ(x)=(- x (+ (+ (* (/ 1 6) (** x 3)) (* (/ 1 120) (** x 5))) (o (** x 6))))
--> cas:integral (cas:taylor x 0 (cas:fn x \sin x)) dx
ƒ(x)=(- (- (* (/ 1 720) (** x 6)) (+ (* (/ 1 24) (** x 4)) (* (* (/ 1 2) x) x))))

I have decided to decommission Lovelace (the i5-7400 16G server) and sell it. Primarily because it's not very power efficient and I can move my services to VPSes anyway.

An interesting method of computing permutation parity using the 3-cycle composition property:

bool parity(int * p, int n) {
    if (n == 1) return 0;
    int C[n], I[n];
    for(int i = 0; i < n; i++)
        C[i] = i, I[i] = i;
    for (int i = 0; i < n - 2; i++) {
        if (C[i] != p[i]) {
            int j = I[p[i]];
            int tmp = C[i];
            C[i] = C[j];
            int k = j == n - 1;
            C[j] = C[n - 1 - k];
            C[n - 1 - k] = tmp;
            I[C[n - 1 - k]] = n - 1 - k;
            I[C[j]] = j;
            I[C[i]] = i;
        }
    }
    return C[n - 1] != p[n - 1];
}

Unsurprisingly, translates horribly into array logic languages. I wonder how would I implement it in my Lisp…

I have installed an unlicensed copy of Oracle Database somewhere on your network. Give me five Bitcoins by the end of the week or I will inform Oracle’s legal department.

A (hopefully) interesting idea: A virtual machine with a rich standard library and instruction set, procedural, functional, based on the Actor model. I plan to use only reference counting and cycle collection, have it be variable-based (no manual register allocation and no stack to make matters worse). Fully immutable, but it's possible to implement functional data structures using a cute way built into the interpreter. Likely JIT-compiled using either cranelift or llvm. Can send code over the LAN or even the Internet for transparently parallel execution. Provides some cute utilities for number pushing; completely statically typed and ideally the code is monomorphised before being passed to the VM.

< 2022 · 2024 >