Gary Moore's Website

Hello, I'm a programmer living in Belfast.

Identity: Keyoxide
Electronic Mail: gary@mooremoore.net
Personal Forgejo: gmoore
GitHub: @doggodoge
BlueSky: @mooremoore.net
__ __ ___ ___ .-----.---.-.----.--.--.| |.-----. .-----.| |_.--.--.' _|.' _| | _ | _ | _| | | |_||__ --| |__ --|| _| | | _|| _| |___ |___._|__| |___ | |_____| |_____||____|_____|__| |__| |_____| |_____| --- Cursed Kata Idea ---------------- So there's these programming languages with popular programming exercises that are modelled after fixing the broken program by changing a few lines, like Ruby Koans, Ziglings, Rustlings etc. What if there was a cursed version of this? The langauge would have to be something like C or Zig. The idea would be to "fix" programs like the one in the "Cursed Code Snippet" article I wrote earlier. Could remove the mprotect stuff and then be like, "why does this not work?", "That's right, you can't execute code in a page unless it's read only." There would be hints of course, maybe just a link to some syscall docs. What other "cursed" programs are there that kind of provide some insight into the OS/userland boundary? Worth a think about I think. Tugging on this rope a bit more... yeah maybe it doesn't have to be "cursed" necessarily. Just examples that inform the user about something they wouldn't expect with regards to programming. --- BSides Belfast 2026 ------------------- Managed to get a ticket at the last minute for BSides 2026, the refreshing thing about this conference, it has a much, much lower density of talks oriented around AI compared to conferences focused on programming specifically. It's a bit of a breath of fresh air. Here's the talks I'm hoping to have a look at: 1. Reverse Engineering an OAuth Supply Chain Attack 2. No CVV, No 3DS, No Problem 3. Bedtime Bug Hunting: A Friendly Android Auth Bypass Story 4. The Silent Risk in Your Browser: Detecting Malicious Extensions 5. Communicating with QR Codes The last keynote of the day is called "AI is Here to Guide Not Decide", and I'm very much on the fence wrt whether to attend that one. I am completely at a wits end with AI talks. On the oauth one at the start there, it's the big Vercel one that happened recently. It's a bad sign when you think "which one?" This particular talk is about the OAuth one. I think it would be cool to hear a breakdown of the React Server Component one though, it's like we didn't learn the lesson from Java Serialization. The CVV one, I don't know that much about it, so a good candidate for learning something new. It was competing with a talk on a Kubernetes honeypot, which honestly doesn't sound that interesting to me. Malicious browser extensions, yes I'm interested in this one. If you've ever written a browser extension, you'd realise what a clown show all that is. Especially the manifest v2 stuff, you're trusting an extension vendor with everything you view on the web, everything you type in, pretty much it all. Communicating with QR codes, there's some interesting things here. For example using the Game Controller API to bypass browser permissions dialogs. Seems neat, and covers more than just QR codes. I don't understand how you could use them to exfiltrate data across an airgap, I assume you need physical access to a machine with a screen or something? Yeah that interested me, so let's see how that works. The AI one. I don't think it'll tell me anything I don't already know. Think I might skip it. So those are my picks, let's see how it goes :) I'll try to write about what I found particularly interesting. Talk Thoughts ~~~~~~~~~~~~~ Reverse Engineering an OAuth Supply Chain Attack Art gave a great talk on the recent-ish breach caused by a third party app integration at Vercel. Specifically involved an employee giving access to their google account with full permissions to sort sort of AI company, and the attacker using that token to read the users email, and thus the magic link sent to their email to log into vercel. The really neat thing, Art produced a sort of parallel copy of the infrastructure to demonstrate the likely approach used. Claude would refuse to produce the app unless it was spelled out in very bold letters that the website is backdoored or something. Kind of funny, as if this couldn't be changed after the fact manually. No CVV, No 3DS, No Problem Really interesting talk about a catastrophic chain of bad engineering with some airline, probably a US airline this be real. The endpoint was not checking the validity of the signatures on the JWT token. You could just delete the signature, replace the payload, and it just works. 200. There's four steps after the chain with pretty catastrophic failures at each step resulting in the ability to book airline tickets with someone elses card, with practically zero information required beforehand. Kind of scares me off putting my credit card information into the ropier airlines, or websites in general. Glad I use disposable virtual cards. The CVV was not checked at all, and 3DS was bypassed because the account was considered trusted. I'm not a cybersecurity professional, I can't perfectly recall the details of this. --- Cursed Code Snippet ------------------- Wrote this shitty little nugget a day or two ago. It's kind of interesting in that it illustrates basically what a JIT compiler does with virtual memory. Here, we define a function using an array, and call the array: #+feature dynamic-literals package main import "core:fmt" import "core:mem" import vmem "core:mem/virtual" Collatz_Step :: proc "c" (x: i64) -> i64 main :: proc() { arena: vmem.Arena _ = vmem.arena_init_growing(&arena) context.allocator = vmem.arena_allocator(&arena) instructions := [dynamic]u32 { 0b1_00_100110_1_000001_111111_00000_01000, 0b1_0_0_01011_00_0_00000_000001_00000_01001, 0b1_11_100100_1_000000_000000_00000_11111, 0b1_0_0_11010100_01001_0000_01_01000_00000, 0b1101011_0010_11111_000000_11110_00000, } vmem.protect( rawptr(arena.curr_block), uint(mem.PAGE_SIZE), {.Read, .Execute} ) collatz_step := transmute(Collatz_Step)raw_data(instructions[:]) fmt.println(collatz_step(12)) // 6 } The underscores in the instructions are field boundaries. Makes it a bit easier to see the structure in the instructions. The magic trick here, all arm64 instructions are 4 bytes wide so we can just wing them into an array. We use a dynamic array here which will use whatever allocator is defined in context. We use an arena allocator backed by virtual memory. After implicitly writing to the first page of the arena by defining the array, we set that page read only and executable. On macOS where I'm running this, it has a W^X scheme for executable memory. Fun fact, MIT/GNU Scheme doesn't support apple silicon for this reason[0]. Then we just cast the array and call it lol. Really this is just what JIT compilers like V8 do all the time, but I thought it was neat to define the function using array syntax. vmem.protect() is basically just a wrapper around the equivalent mprotect[1] procedure in the C stdlib. Well syscall really. [0]: https://www.gnu.org/software/mit-scheme/ [1]: https://man7.org/linux/man-pages/man2/mprotect.2.html --- ASM of the Week: csel ---------------------- It's Saturday already?! Ok, I'll give this a go before I forget. I'll attempt to write about a single assembly instruction every week. I will probably fail at this, but hopefully at least get a few ones I find interesting out there. I think I'm probably using this one up too soon because it's so good :-) ~| csel Xd, Xm, Xn, cond |~ - Xd is the destination register - Xm and Xn are the left and right registers. cond is one of the following flags binary cond condition ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0000 EQ equals 0001 NE not equals 0010 CS carry set 0011 CC carry clear 0100 MI minus / negative 0101 PL plus / positive 0110 VS signed overflow 0111 VC no signed overflow 1000 HI unsigned higher 1001 LS unsigned lower or same 1010 GE signed greater than or equal 1011 LT signed less than 1100 GT signed greater than 1101 LE signed less than or equal 1110 AL always 1111 NV never (historical name) What does it do then? It's basically a branchless comparison, if you write a ternary in C, there's a good chance the compiler will turn it into this instruction, or a helpful related one like csinc (conditional select increment). On modern computers it's often faster to compute either side of a branch and just use an instruction like csel to save either the left or right side based on the condition. You avoid rolling the dice with the branch predictor that way, which can be handy in a tight loop as you don't risk flushing the instruction pipeline. For an example, let's look at an alternative to the naive implementation of a collatz step from a previous post here: collatz_step :: asm(n: int) -> (r: int) [ n -> r, even_result: int, odd_result: int, odd_mul_by: int, odd_add_by: int, #clobber flags, // tst still sets flags, even if branchless ]{ mov even_div_by, 2 mov odd_mul_by, 3 mov odd_add_by, 1 asr even_result, n, 1 // shift right, dividing by two madd odd_result, n, odd_mul_by, odd_add_by tst n, 1 csel r, even_result, odd_result, eq } > gary(todo): improve this explanation, it kind of sucks lol We set up our registers, then compute each side of the branch, the result you'd get if n was either even or odd. If it's even, we divide by two using a signed aware right shift. In a base 2 number system, reducing the power of the number is equivalent to a divide by two, in the same way going from 100 to 10 is a divide by ten. For the odd result, we need to get 3n + 1, so we use the madd instruction. This combines a multiply and an add into a single instruction. We then test if n is even or odd with tst, and use the csel instruction. This is basically your ternary, with the operand at the end being the comparison. That selects what result to save into the left most operand, and it doesn't involve the branch predictor. --- Web Dev Mini Rant ----------------- I don't think design is the greatest source of problems in a lot of web applications. Making everything CRUD via REST API where you have these serial dependency chains that cause a tree of loading spinners to kick off is the biggest problem. In Jira, why does my dropdown have a loading spinner? It's mad. I wish more companies were producing web apps that worked more like Linear, where you literally load everything relevant to the user in one go. Send down updates via server sent events or something. Use a sync engine, do what you need to do, but make all the data available to the user so they never have to wait on a server round trip. I wish there was a bit more creativity wrt where and how applications represent their data. If you think your app is unavoidably UI->API->DB and back, that might not be the case at all. There is often a more specialised shape for data in your domain that is more amenable to local processing with diff based syncing. --- Small Note on C# Discriminated Unions ------------------------------------- Had a look at the new discriminated union stuff C# and they're kind of disappointing. You don't get a key benefit you'd normally associate with tagged unions in a lang like C or Rust. Even if you create a union of structs, it's all by ref and an array of them is sprayed all over the heap. The only benefit I can see is explicit polymorphism, which is nice, but I'd have expected a union of structs in an array to have good cache locality. --- Odin's New Assembler -------------------- So I don't really know assembly, but Odin introduced their own take on inline assembly a few days ago and I've been farting about with it. They do some interesting things that I believe you don't really see in C or whatever. I have an ARM64 machine, so examples will use ARM64 assembly. Here's probably the most basic example of the syntax: double :: proc(x: int) -> int { return x * 2 } double :: asm(x: int) -> (r: int) [ x -> r, ]{ add r, x, x } I've also provided the equivalent odin procedure. So what's happening here? First thing, it kind of looks like a proc, but you declare some additional metadata between the square brackets. double :: asm(x: int) -> (r: int) [ Only thing notable about the signature vs a proc, if you're not familiar with Odin it would be the named return value. Naming it is important for the next line. x -> r, This is called a tie. here you're telling odin that it should use the same register for both x and r. In this case that would be x0 on arm64. ]{ add r, x, x } If you're used to x86_64 assembly, this might look odd. Arithmetic on ARM64 takes three registers rather than two. Another thing worth noting, arithmetic on ARM does not support immediates in the instruction, only registers. So it's: add Rd, Rn, Rm Where Rd is the destination register, Rn and Rm are the operands. Here's Odin's sort of magic trick for it's inline asm implementation. SIMD is made really trivial with it's SIMD specific types, here's an add example again, where instead we double a vector of four unsigned 32bit ints. double_4u32 :: asm(nums: #simd[4]u32) -> (r: #simd[4]u32) [ nums -> r, ]{ add r, nums, nums } Ok but how do you do register allocation and branching? Here's a pretty naive implementation of a collatz step: collatz_step :: asm(n: int) -> (r: int) [ n -> r, mul_by: int, add_by: int, div_by: int, #clobber flags, ]{ mov mul_by, 3 mov add_by, 1 mov div_by, 2 tst n, 1 b_eq .even // odd madd r, n, mul_by, add_by b .done .even: sdiv r, n, div_by .done: } This is a bit more involved, but still a pretty trivial example. Here's what's new: [ n -> r, mul_by: int, add_by: int, div_by: int, #clobber flags, ] Here's that prelude again, but there's more than just a tie now. We declare what variables we will be using, and Odin automatically does the register allocation for us now. You can also use this syntax in order to explicitly assign a register yourself: mul_by: int = %x1, We're going to be using a branch in this routine, so we use #clobber flags which tells odin we're modifying flags. That's opaque to the compiler, and this is information that's useful to it, I'm not exactly sure the mechanics to be quite honest. Will have to learn more about it. mov mul_by, 3 mov add_by, 1 mov div_by, 2 Here we just set up the registers for the arithmetic operations that we will be doing later, because like I said arm64 arithmetic doesn't support ops with immediates (that being sliterals like 1, 3 etc. tst n, 1 b_eq .even This is our branch, first we test to see if the number is even, and jump to the even label if that's the case. The "tst Rn, op2" instruction is actually an alias to "ands ZR, Rn, op2" instruction, which is a bitwise AND that also sets the Z flag (result is zero). We're basically checking the least significant bit to check if the number is even rather than whipping out a mod. Gotta love base 2 :-) The rest is just arithmetic, with a jump to done in the odd case. ~So my review~ I can't give a review really, but this getting introduced to the language I use daily for side projects, it's gotten rid of the mental roadblock for me to learn an assembly language, so for that alone I am a b i g fan ( ͡° ͜ʖ ͡°) Plenty of things I have not touched on at all here, this is just a progress report of a beginner. The ties, automatic register allocation, and experiencing assembly without the ugly [sp, displacement] stuff everywhere, it's been nice. I'll try to keep writing about my progress here, haven't really touched on memory access from inline asm and probably other topics I've missed. Maybe an instruction of the week mini post? Hmm. Thanks for reading! --- Migration from SourceHut ------------------------ I've migrated from SourceHut to my own personal Forgejo at git.mooremoore.net. There's a few reasons for this, the primary one is the SourceHut seem to want to tell you how you should do your work, even though you're paying for a service. I'm going to miss the mailing list stuff, so might try and whip up something with GNU mailman or something eventually. I had to update the links on two articles here, so the atom feed may bounce about a bit, not actually sure how that will go. Apologies! --- Atom Feed Support ----------------- I already consistently use the same title and article separator in this plain text file I'm writing, so just split along them, construct atom feed entries, slap in XML and throw onto bunny.net. This post is a test to see if it works. :) :( update ): Yes those are fowny faces. I have been rightly called out for bad timestamps. Fixing it required being a bit smarter about parsing, so I now parse out the articles from the format you see on the website, get a hash of the content, and store that in a wee state file so we can see if articles are clean or have been dirtied. Dirty articles get a new timestamp, non-modified, clean ones stay with the same one. In theory, anyway. Just realising today being sloppy about timestamps will mess up others feeds, I don't want that 😬 Let's see if this works, only this article should get a new timestamp. :(( update 2 )): Sorry, that caused every article timestamp to update again. That was because the state file didn't exist the first run. Should be fine now (I hope). Checked there, yes that did it. Sorry about disturbing the force, shouldn't happen again. --- toe update ---------- Just did a final bit of plank sanding, replace malloc/free usage with scratch space, and just general tidying up. Removed curl callbacks that really didn't need to be there, probably the most consequential change. --- Unusual Youtube Content ----------------------- This channel is so niche and out of the ordinary that I just felt I should comment on it lol. There's a guy in Czechia who waits until there's severe storms then goes to explore various storm drains around the country. This is the sort of niche content I can get behind! xD Example: https://www.youtube.com/watch?v=GQtzYgH8buc --- Website Next Steps ------------------ Just a bit of tidy up left to do on this site. For the contact card at the top right here, I want to just use a css grid to give the key/value pairs some nice alignment. The <pre> I'm using for this plan file, that's gotta go. It can cause some issues when using things like angle brackets. We should just slap this in some other tag and set it to display a monospace font with css. Should work grand. ~ update ~ That should be those changes in there now :) I also added HTML escaping so writing <pre> doesn't actually render that as a real <pre>. Should hopefully be nice and firm and solid. --- Site Performance Improvements ----------------------------- I threw together this site very quickly, and the templating for it is very inefficient. Here's basically what happens currently: - download plan from storage - stream to a string - template and string interpolate - return response This happens for every response, and the resulting latency is ~400ms from where I am. I shouldn't really be doing SSR for that templating step, I should just just render an index.html from the file watcher and serve that static asset directly from a bucket. Will have a look into this tomorrow, should hopefully bring down latency significantly from nearly half a second to hopefully ~30ms on my connection. ~ the next day ~ I've implemented the changes. We now just template the index.html file in the file watcher and push the fully formed thing to bunny.net. We can just expose that storage bucket directly, it's brought latency down from typically over 400ms to ~40ms, so that's a 10x improvement. Nice :) --- A Million Tracks with Instant Startup ------------------------------------- Recently I've been working on a music player app for myself. For now it only really plays .flac files using miniaudio, and the user interface is just a giant list of tracks. I've been using it as a test bed for different ideas around how the data for a music player should be structured though, and that's a focus on completely avoiding the use of pointers. Without pointers, that means any reference to a piece of data is actually a stable offset into a buffer. That makes the story around persistence really, really easy. Application state is just a file memory mapped into your applications virtual address space. This is essentially the core data structure of the app: tracks: #soa[]Track Any strings in a column of a track is a handle into a string interners []u8 buffer, so we can persist those too no problem. With all state represented using stable indices/handles into buffers, there is absolutely zero serialisation and deserialisation cost, and no impedance mismatch between persisted state and runtime state. That means your application is really just a window into application state on disk, and the OS only pages what's on screen into real memory. If your application is just a window that hangs on state that's already in it's final form on disk, opening the application is really just initing the UI and hooking into that state, and that means you can open a 1,000,000 track library in a few 10s of milliseconds. Very happy with the result, doing interesting things with search too. Will write about that at some point soon hopefully :) --- WASM SIMD in C Compiled With Zig -------------------------------- So I've been writing a bit of code for my JS string interner, and I've set a hard rule for myself to only use JS. Really bumping into some sort of limits there though. I'm talking about prefix search. The naive way to find strings that match a prefix is to do a byte by byte scan. A better way is to pack multiple bytes into one number and use bitmasks to match. That's effectively what I went with, this is called SIMD Within a Register (SWAR). You kinda have your hands tied behind your back from the start doing this in JS though, as JS engines only support floats at 64 bit width. You're stuck with 32 bit integers. We scan 4 characters in a single instruction, that still gets us 4x the performance over the simple byte scan in V8. Still, I was curious what you can do these days with WASM, which now supports 128 bit wide SIMD. That maps pretty much perfectly to my Mac's NEON instruction set for SIMD. ~|update|~ WASM SIMD does in fact not map pretty much perfectly to NEON SIMD instructions. Many operations require a few instructions and may involve things like swizzling things about. You pretty much need a spreadsheet open to see what does and doesn't work well for your ISA, and there's no fused multiply adds without what amounts to an unsafe flag. I'm glad this exists, but it's not fabulous. ~|------|~ Initially I tried just writing the weird Sexpr .wat stuff directly. It's relatively straight forward to do so. Still, wasn't the most pleasent thing in the world to write. The reason I avoided writing it in Zig is because it's not at all stable. I didn't write it in C because I didn't want the emscripten headache. Then it dawned on me, Zig is a really good C cross compiler. So I ended up just writing the code in C with WASM SIMD intrinsics, and cross compiled with Zig. It worked out well! And Clang wrote better WASM than I could. I'd definitely recommend trying this combo, WASM + C + Zig. Absolute power trio. --- Allocators in C --------------- Just finished my arena allocator, it works by using mmap to overcommit 4GB of virtual memory and when you reset the allocator, the pointer just gets set to 0 and we madvise MADV_DONTNEED. Seems to work well so far! :) It also can return an Allocator struct which defines an interface, so we can have functions just take that interface. We also set the last page to PROT_NONE so it acts as a guard page. If anything in the application tries to write into that page, the app just crashes. Gives us some basic protection for free, the CPU mem unit handles it. Here's how the memory is initialised, and the guard page set: Arena *arena_init() { void *mem = mmap(NULL, FOUR_GB, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mem == MAP_FAILED) { return NULL; } long page_size = sysconf(_SC_PAGESIZE); void *guard_page = (char *)mem + FOUR_GB - page_size; mprotect(guard_page, page_size, PROT_NONE); Arena *arena = malloc(sizeof(Arena)); arena->mem = mem; arena->cap = FOUR_GB; arena->offset = 0; return arena; } Just point to the start of the last page, pass in the page size of your platform, PROT_NONE, and it's off to the races! Important to programmatically get the page size rather than hard code it as some popular platforms these days (Apple Silicon) use a 16K page size rather than 4K. Also, here's how the memory is cleared, and it's probably the main thing that makes memory arenas so powerful: void arena_reset(Arena *arena) { if (arena == NULL) { return; } madvise(arena->mem, arena->offset, MADV_DONTNEED); arena->offset = 0; } The important thing here, we just reset the offset to 0, and we can reuse all that mem. A sort of interesting thing here is the use of madvise to tell the kernel that we don't need this memory anymore, so the kernel can go ahead and reclaim it. Kind of on the fence about the madvise in there. It sort of defeats the point of the arena, adding a syscall in there is kinda heavy, and where arenas are used, memory usage before a free tends to reach a similar high water mark. Hmm, now I'm thinking about removing it. Update: I would not recommend using madvise in the arena reset function. --- Gabe Deck --------- I've been playing games a good bit on the Gabe Deck[0], and it's got a really unique control scheme. I've been having a mess with Raylib recently in order to get a bit better at C programming[1], I might have a look at integating Steam Input to see what cool stuff you can do with the track pads and gyro. [0]: https://store.steampowered.com/steamdeck [1]: https://git.mooremoore.net/gmoore/game-playground-raylib --- Automatic .plan Upload ---------------------- This is my first proper automatic upload of my .plan file using my kqueue based watcher[0]. Should use zero CPU while not in use, and I can write these plan files by just editing the file in neovim or whatever editor, and on save it'll automatically get uploaded to my website. I'm not sure how long the upload takes, but it's surely < 500ms. By the time I save and alt+tab refresh my browser the content is there already. We're using bunny.net and completely moved away from cloudflare. This website is just a template on the bunny.net equivalent of workers, and this plan just gets uploaded to their object storage. On request, the template gets filled in from object storage, so the way we update this plan is actually rather simple. 1. Edit .plan 2. Save 3. Watcher picks up the change 4. Watcher uploads to bunny.net object storage 5. Future requests to the worker with my site automatically picks up my changes Lovely! Personal micro-blogging doesn't need to be so complicated :) Just had another thought there, this is a pretty neat thing for covering live events. Might want to somehow send updates via server sent-events or something, but on the editor side, seems great for that. ~ update ~ I've cut out the bunny.net worker step entirely and I just template in the worker and upload the fully formed HTML to bunny.net storage. The final result is a much faster experience when visiting the site. Down from over 400ms to ~40ms, so that's a good result :) [0]: https://git.mooremoore.net/gmoore/toe --- Blog Publishing Platform ------------------------ So I have an idea for this, something similar to what I've done already for publishing to my gemlog on the gemini protocol. That is, why not just send the blog post to myself as a signed email? Already got a decent bit of the code, just need to flesh out HTML templating vs the much simpler gem format. That's what I think I'll do eventually in order to have a platform for the below article series. There's one important deviation compared to how I did it before, though. Instead of the silly stuff with the git repo and SourceHut build step as before, I'll just push to a bucket, and do something with a bunny.net script. --- "Low-Level" JS article series idea ---------------------------------- So this is something that's been floating about in my head for a while now. There's some interesting performance topics that I haven't really seen covered much with regards to JavaScript, and I suppose that's manual memory management with typed arrays. There's some specific probably niche topics here: 1. Packing low cardinality information into bitfields 2. Discussion on deopt pitfalls wrt polymorphic func parameters 3. SIMD within a register, specifically in u32s 4. Buffer backed string interning for cache locality 5. Eliminating GC churn with fixed slots and freelists 6. Improving instruction level parallelism with loop unrolling And more topics as I think of them I suppose. Before doing a wee series on JS perf, I suppose I should build out some way to publish blogs on my personal site. I already publish to the gemini protocol because the LLM crawlers don't go looking there, but I've got this .plan thing here now, and I think I can borrow the kqueue stuff for a really low friction CMS-ish thing.