Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

So, the other day on IRC, uh, some some Zigg user, uh, they sent me this link to this like, um, this blog post and said, “Hey, uh, like would Zig catch this?” So, I was like, “All right, let’s take a look.” Uh, so I click it. It’s just like Open ZFS author, nice blog post kind of talking about how um, there like it’s a function and some C code and there’s like a bug in there. Like, can you spot it? And I was like, I don’t see it. Let me port it to Zigg. So, I port to Zigg

and um as part of doing that, I’m obviously I’m converting like all the variables to constants because that’s how you write Zig code. >> Yeah, obviously. Uh and then like as soon as I do that, it’s like, okay, well, that’s a dead store and like the final constants not used at the end of the function. Like there’s the bug. Found it. >> Yeah. >> I I didn’t even have to get to type checker. I I got it with like the formatter like zig format found the bug.

Yeah. >> So, so I I go ahead I did all this trouble. So I was like, “All right, let me just like fully make a blog post.” So I just made a quick blog post, pushed it out, forgot about it. Next day it’s on lobsters. And people are upset because welcome to Software Unscripted. I’m your host, Richard Feldman. Today I’m talking with Andrew Kelly, the creator of the Zigg programming language, which is the language I’ve been spending a lot of time with lately because the Rock

programming language, which I’ve been developing for the past few years, is getting a compiler rewrite in Zigg. We talk about all sorts of things from memory management to serialization and deserialization of compiler intermediate representations, bite code, what that means, shared memory across processes, and antivirus of all things. Software Encrypted is supported on Patreon. If you’d like to become a supporter, please check out patreon.com/softwareed. And now, Andrew Kelly. All right,

Andrew, thanks for joining me. >> Hey. Yeah, good to be here. >> Cool. So, uh, you made Zigg. I am enjoying using Zig. So, first off, thanks for making Zigg. Uh, I I’ve been using it a lot more now. I mean, as you know, because we’ve been, you know, talking about this for years like used to use it for Rock Standard Library and now we’re using it for the whole compiler uh in the rewrite. Um, and one of the things that a lot of people have been talking about when it comes to Zigg lately is the new IO

design. Um for people who haven’t heard about this, do you want to just give a real quick recap? >> Yeah, sure thing. Uh yeah, so I made the decision that instead of having uh crossplatform OS abstractions that were hardcoded to call directly into operating system uh APIs like file system, networking, timers, and all this stuff. Uh I made the decision to break everyone’s code, a lot of it. uh and you’re going to now have to take a IO parameter to any function that wants to do any of that

stuff. Uh and then that’s going to have a different implementation depending on your execution model, right? And so this is pretty similar to the allocator API where much like allocators, it’s like if you want to do allocations, you got to pass an allocator around. Um, so the the response to this on, you know, hacker news and and places like that has been um weirdly focused on like some aspect of the blog post that you happen to mention where you were kind of talking about the like what color is your

function thing and people are like well that’s not what I think about when I think of what color is your function. But I don’t know that that whole thing just seemed very strange to me because like before we kind of actually let’s not get into that distraction for a second. Um, one of the things that strikes me about this is like, okay, on on the one hand, yes, it does mean that you need to rewrite all of your IO related code to take an extra parameter. Fair enough. However, it’s also the case

that like at least in my experience, IO related code tends to always kind of be in like one section of the codebase. I guess it really kind of depends on what type of thing you’re doing. Like if I were doing a I don’t know like an e-commerce type web server in Zigg, maybe I’m doing a whole lot of like database stuff constantly. But I’m kind of assuming that’s not what people are usually using Zigg for. So I don’t know. Like I mean me working on a compiler, it’s like all the IO happens at one

place. So we’re not talking about like you know a ton of uh like the codebase needing to change for this. Um, do you know about people actually needing to like have a ton of uh of I/IO like scattered all over their codebase? It’s really going to change a lot of function signatures. >> Yeah, I mean that’s a good point. I think actually yeah, updating to the IO parameter, it’s going to be uh easy peasy lemon squeezy, no big deal. Uh, but I decided though as a prerequisite that I needed to re-evaluate the um like

reader writer streaming APIs. Uh, and I also broke those. Um, so that’s all and I already landed those um that breakage. We we called that scandal writergate. We have we we have a a practice of naming our big breakages after our presidential scandals. So, but only only like the good old scandals from the old days when they were like important, you know. Anyway, uh point being uh that actually is difficult to upgrade to because uh yeah, I mean turns out writing a streaming implementation uh it’s very very dependent on the

interface that you have available to you and in particular >> the kind of like key design concept with this with this new way of doing streams is that the buffer is in the interface not the implement. mentation. >> Interesting. Okay. So, you can kind of you can customize it yourself essentially. >> Yeah, you can do that. But more importantly, um like there’s all these helper methods in the interface that all can then just um like put the hot path as operating on that buffer and then the

slow path is going to call into your streaming implementation that calls you know like >> write or read or whatever. >> Okay, that makes sense. Yeah, we haven’t done anything with streaming yet. There’s only one place where we might do it, which is like when you’re downloading a package because it might be, especially if it’s like a platform and it’s got like a big host binary in there. Um, we might want to like download and decompress as we’re downloading. Um, stuff like that. But

that’s kind of it. Uh, so there there hasn’t Yeah, that hasn’t affected us at all at this point. Um, yeah. Yeah. Okay. So, so let’s let’s talk about this blog post thing because this is really interesting to me. So background for those who aren’t familiar with the like what color is your function blog post. This is uh Bob Nestrom wrote it. He’s the author of crafting interpreters most famously. Um and he wrote this post that basically talks about Node.js is like async uh and callback based APIs and

sort of the complaint is like in Node.js you have and and in JavaScript in general I guess now um you have like synchronous IO. So in the browser this will only be local storage but um you know in Node.js JS you can have like synchronous file reads and file writes and you can also have async using the async keyword and promises and you can also separately have callback based and the idea is that if you switch from one of these to the other um you have to change not only the function that actually wants to change it but also all

the callers have to change as well. Uh like if you’re async all of them have to become if you’re switching from sync to async all the callers have to be async um etc. Uh so you in the blog post were basically like hey this addresses the function coloring problem because now we’re sort of at least this is my understanding. You can tell me if this is you know you you would disagree with it but essentially the the point is that we’re now abstracting over that. So it’s like if you want to do any IO yes you do

need to like pass something around but if you want to switch how that IO is being done from synchronous to asynchronous or from one async style to another different like exeutor whatever um that’s a non-breaking change. basically you just do that wherever you want at any point in the chain and it’s like cool from now on we’re doing it guess we’re doing async now. Um >> exactly >> and then the objection that people had in hacker news was like well no but like the real like thing that happens in

function coloring is that if you have some something deep in your call stack now I have to go add a parameter I have to add the parameters to all the colors which to me feels like missing the point for two reasons. One reason is that well quite often I mean you’re not literally passing one parameter. You have like an environment strct or something that you’re passing around. You’re just pulling it off of that. So if you already had the environment strct being passed down, you actually don’t need to

add an extra parameter. You’re just like env.io or whatever um that that you know didn’t used to have that now it has it. But secondly, which is different from async. You there’s no async like in no.js equivalent of that. Um but secondly again to the earlier point at least in my experience for most type of applications that Zigg authors are going to be using I would imagine all the IO is going to be concentrated in one place anyway. So like like it’s not going to be a big surprise like oh man I you know

previously I I was like not doing any IO in the let’s do some IO on the file system part of the compiler and now I am. It’s like no I was always doing IO there. So, I don’t know. Those both neither of those objections feel like um they would impact me, at least the way I use Zig. But again, I don’t know how other people are using Zig, and you would know better than I would. But >> to me, it just felt like kind of a pedantic um point to make. >> That that objection is about going from

not doing IO to doing IO, in which case threading a like IO parameter down your call stack is like >> good, I think. Well, so it’s funny because so I work at Zed and by the way, it’s so confusing like using a threeletter thing that starts with Z and working on a three-letter thing that starts with Z like all the time. I literally yesterday I was talking with someone and I was like, we hadn’t talked in a while and we were catching up on stuff and I I was talking about like, you know, oh, I’m working at Zed. I’m

using Zigg on Rock and stuff like that. And then he was like, oh yeah, I’ve gotten really into Zed lately. I’ve been using it to build this thing. And I was like, think you mean Zigg. It’s like he just swapped it, you know. Yeah. >> But yeah, it’s uh it definitely feels to me like the the if you want to have stuff testable, you need to do something like this or something that’s like more complicated and in my opinion worse. So like at Zed in Rust, we do this. We pass around a thing called FS, not IO. It’s

like, you know, specific to the file system. We have separate ones for like HTTP and whatnot, but it’s like yeah, because in our tests, we want to simulate these like weird file system states that we don’t want to have to recreate on the real file system in tests. We want to be able to be like, what if this write fails for this reason? What if this read fails for this esoteric reason? And so, like, if you want a simulator, as far as I’m aware, there’s like two popular ways to do

that. One is you pass a parameter like we’re doing, and you delegate to that. And then two is you do some sort of object-oriented dependency injection Java thing which to me like having done that in my career as a early in the early days of my career as a Java programmer I just look back on that as I was like why it’s so complicated to like do all that stuff when you could just be like just pass a parameter around and sometimes the parameter is different that’s it the end so simple.

Yeah. >> It’s nice. Yeah. Yeah. Like the one of the first things we’re going to do after we have widespread usage of this parameter obviously is uh is this kind of injection testing like we we already have this function uh test all allocation failures where it will it will run your unit test first to figure out how many allocations you make and then like that’s that number is like n and then it will just rerun your unit test like n minus one times >> each time failing a different like

allocation to make sure that you like handle out of memory correctly. So we can then do the same thing for like uh a file not found or like file system errors or uh or even just like ordering uh like you could test your um like you maybe you have some concurrent thing and then like you actually run your unit tests single threaded with like multiple combinations of um of orderings to make sure that it’s correct. >> Yeah. Yeah. So, at Zed, we do that for the real-time collaboration stuff. Um,

because it’s all based on like CRDTs and stuff and so there’s all this like really elaborate like what if it comes in in this order and that order and the rights were, you know, like because there’s yeah, there’s the combinators of that can get pretty wild and you definitely want to have reliability across all the different scenarios that can happen because otherwise you get these bugs in production that are really frustrating to debug because the circumstances required to reproduce them

are extremely difficult and like rare to come by. So like having total control over that in the testing environment is awesome. Um this is also something we’re planning on embracing in rock uh with the new design bas basically um without going into too much detail uh because platform authors have total control over all IO APIs. Uh we have the option of either doing something like what ZIG is doing and what we’re doing at zed for the file system APIs or not. We don’t have to. I think for scripts maybe it’s

not um like really required but at the same time it’s also really simple because you just put at the top of your script like you know FS equals give me a real FS and then you’re kind of done. Um but yeah I I to me it seems like just a worthwhile thing to do to sort of standardize on because if you want to test your code in a way that doesn’t require using the actual file system, it’s like what you want anyway. And why wouldn’t we encourage that? that’s that’s what I would like to use anyway.

Um, and the other nice thing about that is uh something that we’re planning on doing with our tests is because we have full control over the IO systems and whatnot and we know when code is pure versus not, we can have there be okay. Well, we can already have there be a category of tests where you don’t do any effects where we just say, hey, if the none of the dependencies of this test changed, like if none of the code going into this test changed, we don’t need to rerun it. Like we know we know what the

answer is. It’s all pure functions. So like no no no need to rerun at all. Um but also it’s the case that if all of your code is either pure or the effects are simulated, meaning like we’re not actually going to the platform to get the real implementations of anything, but rather we’re just sort of stubbing them out with like kind of the idea is all IO can be simulated by just using a really simple bakedin primitive that’s like read to and write from some global memory store. Um, and if we just pass

that into the a certain type of test where you’re like, okay, I want to do one of these simulation tests where like I’m going to replace all the IO implementations with this one store, then we have the same property where it’s like, okay, well, we don’t have arbitrary IO happening. All the IO is going through this one store. And so again, we can just guarantee that for all those tests, if none of the implementation, if none of the dependencies changed, we don’t need to bother rerunning the tests. The hope is

that with those two types of tests combined, this total surface area of tests that always need to get rerun is just limited to basically integration tests where you really want to do the actual IO for real and like IO and all the other ones will just be essentially cached and just like don’t need to rerun at all unless of course the relevant code paths change in which case you want them to rerun. Um, we haven’t done any of that yet, but I don’t know, it seems like a really nice benefit and like why

wouldn’t we encourage people to like have a pit of success there when the only cost is like an extra parameter here and there in the you know cases where you’re doing IO and not even that if you’ve got an environment seems seems great to me. >> Yeah, that sounds really nice. Yeah, we we have a a core team member right now who um set up like a risk v Jupiter board or something and is trying to run the like zigi on it and it’s real slow. So uh that kind of um you know like reduction of of unnecessary testing

would be helpful. >> Yeah. Yeah. >> I mean in general like I remember when I was earlier on in my career hearing about all these patterns like dependency injection and like list substitution principle and this and that but as I’ve spent more time in my career like I mean those are helpful terms to know I guess to be able to talk about these things but at the end of the day it really kind of comes down to like this is hard. It’ll be less hard if you do it this way. Um and and and a great example of

this is like our ripple. So the original rockrepple was written in kind of a like okay get the IO like read what the user you know typed in then go like do the evaluation thing and it got pretty hard like the thing that was hard um to test this especially because the web assembly ripple like the the one that goes on the website is like totally different in a lot of ways. It doesn’t have like standard in it doesn’t have the whole like TTY you know like read line up arrow down arrow stuff. It’s just

completely different. And so the solution was like, okay, let’s just make the ripple a state machine where it’s like, okay, we get some input somehow from from whatever source and then once that’s done, what do we want to do next? And then we can test all of that. And then you just have to sort of staple the IO on top of that. And it’s different for web assembly versus the command line. But that part is just so thin and relatively trivial that it’s like, well, we know that all the logic’s going to

work the same way. So you could call that like, well, now we’re talking about like dependency injection or abstraction or this or that. But it’s like the general idea is just like yeah just make it so that the IO happens separately from the actual logic and then I can test the actual logic separately and then my life is a lot better. Um yeah there’s a lot of things like that where I think we can get wrapped up in pro as programmers in like classifying things as like well this is technically an

abstraction or like an abstraction boundary or this or that but the end of the day it’s really just about like how hard is it to do it and like what are the characteristics of what you could do instead. >> Yeah. And that and that goes back to um the thing we were just talking about with like function colors and stuff because to me it’s not a philosophical you know like armchair category theorist question like my it’s an engineering question like do you need a copy do you need an async copy of the standard

library or not that’s the question for me >> right >> and if you you either answer yes to that question or no to that question and that’s that’s the point >> yeah so something that we are planning on doing in the new compiler um this is not implemented yet but It’s it’s the design that we want to go with is is basically um do something simpler but kind of fancier and a little bit more go-l like in the sense of like essentially whenever you do an effect we’re leaving it up to the platform

whether that’s synchronous or asynchronous like co- routines versus whatever um essentially you’re just saying like look I am saying when I run this code that these things are going to be run in this sequence or if they’re going to be run concurrently I’ll say that with like a callback you know like a lambda or something saying like run these two lambdas concur currently. Um, but I’m not defining like what’s actually going on behind the scenes in terms of the CPU. I’m just saying like

run this and then run this and then run this and then run this. Um, the different platforms might decide to do those in different ways. Some of them might do async, some of them might do, you know, green threads, some of them might do whatever. The point is just that we’re defining rock as a language where like when you say, I want to do this and do that, well, we’re going to guarantee that it’s going to happen in that order, at least as long as the platform implementation’s correct. Um,

but we’re not going to guarantee like is is this synchronous versus not. And I’m not really sure. I mean, I guess if you’re doing really low-level stuff, it totally makes sense why you would care about that because you care about things like, okay, what if an interrupt happens? Like now, you know, that’s wildly different in in some scenarios versus other. Um, but we’re not, you know, this is a high level language. We’re not trying to like give you control over that. There’s no like

interrupt handling in rock. That’s not a thing. So yeah, I don’t know why I I have not yet thought of a reason why someone might care about that. >> Yeah, that um that makes me think of uh Loris who wrote the Async.io blog post that you were mentioning before. Uh he shared with me another draft today where he um he’s trying to introduce like another vocabulary word to our like computer science uh vernacular. Uh you know how people say like um par uh concurrency is not parallelism.

Yeah. idea there being like concurrency is uh potentially single threaded like you can do something for a little bit switch tasks do something else that’s concern concurrency but that’s not parallelism >> right >> well he he introduces a third word which is um asynchrony or asynchronicity depending on your >> preferred English >> and the idea is that uh if you express asynchrony you’re saying that uh these operations are allowed to happen out of order, but they’re also allowed to

happen uh in order. That’s that’s asynchrony. Interesting. How is that different from concurrency? >> Because uh if you I can give you an example of something where asynchrony is valid. Um >> okay, >> but where I can give you a different example where it’s invalid and you need concurrency. Okay, >> so the example a simple example is just like writing two files to disk like you just have this one and you have this one and you need to write them to different files

and it doesn’t matter you can do one before the other you can do it at the same time it’s fine >> so you can express asynchrony in this code >> and like with like Zigg’s proposed semantics it’s like async aait on on both of these things um and then now you’re free to run those in a single threaded blocking program that’s legal. >> Mhm. >> But you’re also free to run them uh in in like a concurrent program, an event loop or whatever. That’s also legal.

I see. Okay. So, they’re still sequential, which is different from concurrent. Like concurrent would say, wait, is that true? Or they are potentially not? >> No, they’re they’re they’re allowed to be executed out of order. >> Okay. >> But they’re also allowed to be executed in order. >> But isn’t that also true of concurrency? Like if I if I say these two things are concurrent, they’re running concurrently. I mean, I’m kind of saying

that either order is fine, right? Like I’m not I’m not saying it’s a problem if they haven’t execute in the same order, but it could be a problem if they don’t run concurrently. And I can give you an example. >> Okay, >> so the example is going to be um you have a unit test that wants to uh create a server and a client. >> Okay, >> both. So you’re server is going to need to accept and your client’s going to need to connect. Um okay and you can do this in a like a

single threaded uh non-blocking uh manner. You can write this unit test >> right >> but if you only express asynchrony for this example it will deadlock because uh because asynchrony means that you also can run them in order. So for example if I try to do um server uh if I if I try to do um yeah server.accept except h it’s like waiting for the client to connect, right? But then the next line is like client.connect. Well, it’s never going to get there. >> I need concurrency in order for this uh

unit test to not deadlock. >> Uh okay. So, this is about like blocking versus non-blocking to some extent. >> Yeah. Yeah. Exactly. >> Okay. Gotcha. Interesting. Yeah. Uh hard to see if that’ll stick. That’s a very subtle distinction. Yeah, it’s subtle, but it’s like a correctness issue, right? Because like because in the other example, uh, asynchrony was enough. Like they could run blocking, they could run non-blocking, it’s all fine. But in this example, you have a stronger

requirement, >> right? >> You have to express a different requirement in your code for it to be correct. >> Yeah. Right. Yeah. I wonder if I don’t know if if like the terminology blockingity might I don’t know. >> Well, we have a we we have an API that like solves a problem. So like you just have to ask for uh concurrency. So it’s like we have async await and then we also have like async concurrent await >> and you’re expressing to the uh to the system to to the execution engine like

this can’t be running this cannot uh run blocking. it needs to be run concurrently >> with the calling context and if it can’t then like you have to just panic and say like this execution engine is not capable of >> uh of like running this code >> right so I guess that’s actually an example of what I was saying earlier of I haven’t thought of any examples of where this distinction might matter in rock code we don’t have any rock code that does that sort of thing like that

low-level like you know wait for a connection type thing um but now I need to go think through like do could we have something like that in theuture future that might have a problem with this. Um I mean I guess if we do then we can just introduce a similar primitive or it wouldn’t be a language primitive in our case. It would just be like a you got to throw this in a lambda. It’s like kind of the solution to all these problems. Um but yeah >> put succinctly >> put succinctly it’s a unit test in which

you need to do both client and server in the same thread. >> Yeah. Okay. Yes. Uh, right, which only would really come up in testing. I think maybe like uh the only other case I can think of where your client and server you’re still not connecting to yourself, I guess. Yeah, is um what’s it called? Uh OOTH. uh if you if you have like a command line um that does OOTH, the way that that usually works is you spin up a little localhost server and then you have the user like you send tell the OS

to open a browser and the user can log in the browser using their cookie or whatever and then the browser tries to hit local host and because you happen to have that CLI server running then yeah so that’s the same machine but it’s not the same process so wouldn’t be a problem there. Um, >> yeah, there you just need the server. No client, right? >> Uh, in your process, right? Yeah, you don’t have your own client. I mean, there is a client, but it’s in the browser’s process, so that’s fine.

Um, >> not your problem, >> right? Yeah. Speaking of IO, um, so lately I’ve been doing like one of the motivations for the rewrite uh, in Rox compiler to Zigg uh, was that we were going to need to do a rewrite anyway if we wanted to get the really amazing um, caching design that Zigg has. And I think we’ve taken it a slight step further. Um, but a very small one. I think we’re still fundamentally doing the same kind of thing. So, let me describe what we’re I

have a work in progress PR that’s not quite merged yet. Um, by the time people are seeing this, hopefully it will be. Uh, but basically the idea here is that so we have this thing. Um, okay. So, high level motivation. Uh, we have individual modules. For us, that’s kind of the like most granular compilation unit you can get. Like in Rust it’s a crate but in in rock it’s like you have a rock file that’s a module we want to be able to cache as much work as we can on that module. So this is basically

like parsing uh canonicalization so like name resolution type checking all of those things we would like to cache it and then cache it under a key where that key gets invalidated if like its dependencies change but actually we can have two different caches. One of them is like all the independent stuff. So like parsing doesn’t depend on any other modules. Um type checking does because you could have imported stuff. Um, so those are cached under two different keys. But the really critical thing is

that so we’ve already rewritten everything so that in the compiler we really rarely use pointers. Um, almost always you use indices for everything. So all of our trees are not like trees of pointers to other things. Anytime a node in the tree references another node in the tree, it’s with an index. And all of that is relative to the initial address of the whole tree. Um, what this gets us is that if you look at an individual module, we have a fixed and pretty small number of actual allocations. Like it’s like 20 or

something. I forget exactly what it is, but like per module, right? Um, so it’s like all of your like parse nodes, all of your regions, all of your this or that, whatever. There there’s a pretty small total number of these things. So this enables us to do what uh I believe you’re doing something almost exactly like this in Zigg because this is where I heard about the idea from was talking to you about it. um is basically okay well we’ve just got a bunch of stuff in memory what we can do is at least on

Unix systems Windows doesn’t have a usable alternative to this unfortunately but you can just do a pv um which is basically where you say okay uh I would like to just go grab every um create a array of pointers to all these things and say like okay first here’s here’s this one and here’s how long it is and then here’s this one here’s how long it is and you just with one sis call you say here’s here’s all these like an array of pointers and and their lengths Um, and the operating system will just

go write all those to disk continuously in one giant blob of bytes, >> which is pretty cool, right? Uh, on the one hand, like that’s that’s already a pretty significant savings compared to going through and like serializing it all to I’ll use a pathological example. Like imagine if you wanted to write JSON to disk instead of this. Like that’s just hilariously more like expensive than what we’re describing here. It’s like one SIS call and preparing the the prep work for that one SIS call is also

trivial. you’re talking about like make an array of like 20 entries total like no matter how much stuff is in your module. >> Um >> why why stop there, you know? Let’s let’s wrap that in base 64 HTTP and >> Oh, you’re right. Yeah, I forgot. Right. We we we could make it worse. Yeah. Than JSON for sure. Uh XML with attributes on every node. Um >> yeah. So uh so then uh so that’s that’s got it on disk. But then the really cool part is that reading it from disk um uh

involves a technique. So this is where I think we’re being a little bit uh more I don’t know a little bit more more ridiculous than necessary uh than than Zigg is. So when we read it from disk the the way that we are going to attempt to do this is basically so we got this big blob of bytes. We just read all that into an allocation. We’re like okay now all those bites are in memory and it would be nice if we could just say okay great we’ll just cast the beginning of that to the the main strct that’s got

all these things and we’re done. But of course that won’t quite work because if you just cast the strct to like okay now we have the module environment it’s back. Um well there were still some pointers there. There were like 20 of them. And so those are all pointing to addresses of like whatever happened to be in memory when I did the right. So that’s not going to work. Um so what we’re doing instead is we’re doing a fixup on both sides. So basically when we do the right uh what we’re doing is

we are normalizing each of those addresses to the original address. So in other words, we’re we’re kind of converting the addresses to indices. So it’s like, oh, I’m at like offset 10. I’m at offset 30. I’m at offset 50. And those aren’t real pointer addresses, but they’re not supposed to be. They’re just supposed to be consistent and like helpful. So then after you deserialize them, you basically go through and fix them up in the other direction. So you say, okay, well, this this pointer was

written down as 30 when it was serialized, but I know that my actual base memory address for all of this is like, you know, 7,920 or whatever. So just like add 30 to that and that’s the address. And so now we’ve rehydrated all of those again just by going through these like 20 pointers and just doing some arithmetic and that’s it. Um and now boop, we have everything back in memory. There’s a little bit like of detail I’m omitting there where like when you do the right you have to like

account for alignment. Um although apparently maybe not. Danielle Lamir just wrote uh a blog post about how like apparently like on modern systems like being like misaligned reads and writes are like fine like don’t actually have a per penalty which is wild to me but um whatever we’re doing it just to be you know because the cost is minimal and it’s probably >> okay hold on I gota I got a bunch of questions about this >> okay sure go for it >> so so are you uh are you using do you

have like a specialized hashmap that operates on like indices instead of pointers for its backing memory. >> Uh, not yet. Um, so that’s like >> you want to though. >> What’s that? >> Okay. >> Well, sounds like you’re planning to. >> We’re only using hashmaps for things like string interns and stuff like that. We actually have very minimal use of hashmaps. And I actually want to do something way fancier for string interns. Um, so let’s just go on a micro

tangent about that. I have this hypothesis that we could use SIMD to make string interning just like astronomically faster by basically being like okay so that we’re interning identifiers right so you have variable names like X or AB or like fu or whatever um my hypothesis is that if you look at the distribution of those you have a lot of them okay maybe not that many that are like one letter but that one letter does come up you know decently often still especially in like type variable names maybe that won’t end

up being true in rock whatever um but then like two letter and then threeletter and four letter etc. Well, if you look at especially the short ones and we do actually have a culture in rock of like trying to name things using abbreviations. So for example, instead of string we say str. Um micro tangent off the micro tangent. Um I think this is good. Like uh some people look at that and they’re like uh h I don’t like that. I think you should just like write out the whole word. I think you should

not bother writing out the whole word for things that are really commonly used like string because a like it’s it’s just a it’s a solution in search of a problem. It’s like, have you ever seen people use a system like this? Nobody has a problem with this in practice. It’s just a total non-issue. You’re just your brain just adjusts to it very quickly when you’re using it all the time. It makes more sense to me to write out something longer just because if you’re not using it um if you’re not

using it frequently, then you just might not know what it is. Like you’re not you don’t know what the expansion is. But for really common stuff like stir int bool, you know, actually, okay, micro tangent off the micro tangent off the micro tangent. Bool. B O L is like, this is shout out to Brian Hicks who pointed this out to me. B O L is named after Boolean logic, but Boolean logic is named after George Bool. B O L E. So really like we’re abbreviating B O L E to B O L. Just like couldn’t fit that E in there, you know,

George Bool. >> Anyway, incredible. >> Um, okay. Popping that micro. >> He’s like, I was almost famous. >> So close. Um, yeah. You can you imagine like if you’re George Bool, like you know, zombie George Bool comes back to life and like autocorrect is always trying to drop the E off the end of your name. You’re like, come on. >> Yeah. Like really, you couldn’t take one more letter from my name? Like, all right. >> Right. At any rate, so because of that,

I think, you know, we’re going to have a lot of like three and fourletter variable names. So, the idea is basically like, okay, string and turning, we just need to go look up like what’s the, you know, the ID, like an integer ID associated with this thing. You could put it in a hashmap obviously, but my hypothesis is that it’ll actually be a lot faster if we just have different arrays of for the different bucket sizes like of of really common like lengths. So like 1 2 3 8 etc. And you just blast through those in SIMD and

just check really fast and find the index. >> That’s the hypothesis. Um so if you’ve got like >> wait so are are these buckets also like hashmaps each or >> no it’s just like well so okay let me give an example. So let’s say we’ve got a bucket for um identifiers of length four bytes, right? So like um uh no three three is a worse example just because it’s um uh you have to have like a zero padding because like symbio only works in you know powers of two. Um okay

so length four uh we we so we’ve got some identifier names of I don’t know now I’m trying to think of like what’s a common four-letter identifier um form. I don’t know. So you have an identifier named form and then next to that you have like another one and next to that you have another one. Let’s say um uh then you have another one called yeah bool. Why not? Sure, that’s a good one. That’s a good four-letter one. Um so we’re like, okay, what’s the what’s the

string intern ID of bool? So all we do is we just go into that array and we’re just like using SIMD, we’re like let me just find the index in this array of like which one has B O L in it. And using SIMD, we can do this 16 bytes at a time, right? So we can look at four of these at a time. And because we’re proceeding sequentially through it, the um what do you call it? Predictor. uh the cache predictor thingy. >> Yeah, like it’ll the the cache behavior should be pretty good. Plus also like

all these are going to be really hot because we’re going to be interning all the time. Um so they’re have a decent chance of being in cache anyway. Uh >> is this like per function? Like how big are these arrays? >> Uh this is per module. So like an individual file would have its own intern set of interns. Um, so this will be all the identifiers in that module and we want to turn them crush them down and from like you know however many bytes they are into like um four bytes. Actually you know what now that I say

this uh >> like another optimization that we can do is for really small ones if we’re using four byte identifiers we can actually just like inline those into the 32 bits that we’ve already got like the small string optimization and just like store them right in there. But that’s neither here there. Um but I guess for yeah so for like longer ones like I don’t know eight bytes or six bytes or whatever um because we can put them into separate buckets based on their length like we

don’t need to bother searching for something that is for example has the length of like 15 in the eight bucket. Those can just be in separate buckets because there’s no point in like searching the same bucket for that. All of this is based on the observation that we saw in the old Rust compiler where we originally were using hashmaps all over the place and then we tried out using what we were calling vec map at the time which was basically just like put everything in like a linear array and

just search every time and it was just like way faster than than doing all the hashing which was really surprising. >> That’s surprising. >> Yeah. Um I was very surprised. It was like and and the numbers that um this is Folkart Dere was was doing these benchmarks. Um he’s awesome. uh he was shocked by like it was like you needed to get like hundreds of thousands of entries before like the hashmap one. It was like it totally reied all like expectations. Um but anyway, uh so so if that holds true in this

compiler as well, you know, I don’t know, maybe there was some like weird mitigating factor of like all the other memory usage that we were having in that old compiler, which is always possible, but um the hypothesis is that actually like we won’t need to use actual literal hashmaps. Um, I don’t know, maybe at all in in in this version of the compiler. Um, >> we’ll see. >> By the way, uh, Zigg’s array hashmap has a like linear threshold if you saw that where like below that amount. It’s

literally just an array with no hashing. >> I didn’t know that. >> That’s cool. >> It just does that. >> But I think I only set it to be like one cache line size. Oh, >> like maybe we should look into a bigger default, I guess, based on your findings. >> Might as well try some experiments of that. Um, >> yeah. >> Yeah, >> you could also do that. So, if you’re using that type, you could just try bumping that number in the standard

library and see if you get better results. >> True. Yeah, we could experiment with that. Let me know. >> Um, >> yeah, I’m sure we will. Uh, but anyway, so the basic idea is just that like if you think about what is a larger rock project going to look like um in terms of like builds and you know this might seem like a silly thing to optimize for when we’re like nobody has a large rock project. like the biggest like production rock projects that I’m aware of are like thousands of lines of code.

Like no, I don’t I don’t even think this one’s like 10 10,000 lines as far as I know. Um yet uh but also we’re like preo.1. So you know that’s our that’s our get out of jail free card. Um we are planning on launching 0.1 like once we get the zig compiler like basically you know there. Um that probably will not be end of 2025 but I’m hoping it will be like 2026 like first half of ideally first quarter of we’ll see. Um, but basically like the way that I look at um a a a a

programming language’s responsibility is to give people a good user experience. As your project gets bigger, in my experience, like the UX just everything becomes about compile times. If your compile times are slow, it doesn’t matter. Like nothing else about the language matters because you’re just in total agony every time you do a build and like everything’s miserable. So I’m like we need to anticipate that and make sure that never happens to us or else all the other work that we do didn’t

matter like people are still having a terrible experience. Um, so I focus a lot on like how do we make sure that like not just are we able to cache things incrementally and whatnot, but also when you are loading from cache, how fast is that? Like if if you have this gigantic project with like, you know, a million plus lines of rock code, but almost all of what you’re doing is like you’re making a little change to this file or that file before you rebuild, then 99.9% of what you’re doing

is loading cache files from disk and like you know reusing that stuff. So like this that performance is extremely critical as your project gets big enough where you care about that performance and can notice it. So um >> the performance of cache hits. >> Yeah, exactly. That’s like that’s like mostly what the compiler is doing at that point. Um at least for for for the stages in question like some parts of it you you have to redo every time. Um there’s no way to make them incremental

that I’m aware of. Uh like linking for example. Um, >> but well, okay. Okay, that does that does get into a separate topic of like something that we are this doesn’t work yet, but um it’s going to be kind of wild and actually the thing that I just explained as a prerequisite for it. So, uh well, okay, before we get into that, I don’t know, what do you think of all that? Like, how does that line up with what what Zigg is already doing in production for real? >> Uh you you created so many

conversational doorork knobs. I don’t even know which one I want to go in. I’m gonna be like that scene in Rick and Morty with the guy trying all the doorork knobs. Uh well let’s start with linking. So I think that uh that’s quite an interesting topic because we we’ve seen this resurgence of of of like focus on linker performance in the last I don’t know five years. Like we had mold from Ruie. Uh I haven’t checked in on him in a while. How’s he doing? Um we had Apple

seemingly like in response to that just did their own thing and uh now their blinker is faster. Um but all these projects uh they rely on the premise that a linker is like a function that takes like objects as an input and then an executable as an output. But doesn’t have to be that way does it? Because like if you are thinking about the tool chain together uh like if you give me like a zigg build exe command that’s compilation and linking which means that I can start linking immediately before I

have I don’t even why do I need objects? Objects are are something that you only need if you’re doing this like uh you know the linker is a function uh like way of modeling it. So like for people listening at home start linking >> just to clarify when you say objects you mean like object files like the the binary like representation of like a bunch of executable stuff you know and and related data on disk. Yeah. >> Yeah. Elf objects uh cough objects >> macho objects >> macho wom. Yeah. But like so you can if

you if you’re the compiler and the linker um which in Zig’s case we are then you can just start linking immediately like the you start uh like uh you know I look at the main function I generate machine code for the main function I can just write that to disk in a in in the output somewhere >> and then as long as as long as I later like point to that correctly you know in the table of you know functions or whatever it’s going to be good it’s going to work. So, the equation is

totally different if you uh if you don’t assume that your objects are already pre-baked for you. >> Yeah. Which is really cool. I mean, we ran into we tried to do our own linking in the original version of the compiler and now we’re doing something that’s actually a lot simpler but also a lot wilder in some sense, which we can talk about later. But um but yeah, like that that last part that you mentioned about like as long as you’re in control of it, right, uh is is really critical because

the pro the thing that made what we were trying to do with the surgical linker so hard is that we’re taking arbitrary third-party objects that already have their relocations done in a lot of cases. And so we had to go like try to tweak all those and relocate the relocations and that got really hard. >> Do surgery. >> Yeah. Yeah, I mean and actually I mean we to be fair we did you know uh get it working in um in Linux. Shout out to Brendan Hans connect for getting that working and then Fulkrit um ported that

to Windows and got that working but then as soon as we tried to do it for Macco it was just like a total I mean we we never got it across the finish line because Macco was just so totally out there with how they do everything. >> Man, why has Apple got to make development so unnecessarily difficult on their platform? You know, I suspect that’s one of those things that probably I guess I haven’t looked up the history of this, but it’s it seems like the type of thing that was probably from like the

next step era or like the transition out of that or something like that. It’s like some really really old like you know multiple decades. Like at the time it seemed like uh like nobody really knew what they were doing. So it’s like sure, why don’t we do it this way? And then it’s like oh here we are backwards compatibility. We’re stuck with it. >> Yeah. I feel like there had to be a meeting. You know that that that meme with the guy being like thrown out of the building for suggesting like a

reasonable idea? Like there had to be like that person who suggested uh like can we just use elf please, you know, and it’s like >> toss them out the window, you know. >> Yeah. Um but anyway, uh yeah. So okay, so that was uh linking, right? Did you have any other thoughts on linking or uh do you want to pull on a different doororknob from the earlier menu of them? >> Well, yeah, I do have a couple more thoughts on linking. So, one one interesting observation is that uh if you start to do this incremental linking

approach that I that I outlined where you’re trying to um you want to add, you know, add functions, you want to do compiling at the same time as linking. So as you finish uh generating the machine code for a function um you can then send it to the linker and you can you can put it in the binary. Um there’s another angle to this too which is uh like after that compilation completes now I make a change and I want to uh recompile. Well, if I only change one function, then what you should be able

to do is recompile that one function, generate the new machine code and then just go edit that one function in the binary. And if you have to put it somewhere else because it got longer, that’s fine. Put it somewhere else and then update, you know, the like the calls to it. Um, this is totally possible. >> And crucially, you know where all those are. You don’t have to go scan the whole thing looking for them because you can just have those written down in some metadata in memory somewhere.

Yeah, you track that data. Yeah, you track that data. And so what you end up with is this uh kind of like growing executable where you’ve I mean it’s kind of like memory allocation, isn’t it? Like it’s just function memory allocation. It’s the same problem as a memory allocator, >> right? >> And uh what’s interesting about that is that it’s the same it’s it’s like 99% the same problem as hot code swapping, >> right? because you’re all you have to do is do

the same thing you did on disk but also in memory with uh like the like VM uh like a SIS call you can do that just let you like arbitrarily edit the memory of any process, >> right? >> So you can just you can just p you can just pause a running executable and then just go do like literally the same linking that you did on disk but in memory and then unpause the executable and then like boom your new code next time that function’s called it’ll call the new version of that function. Yeah,

I have read that uh you have to be really careful about antivirus when you do that because apparently doing that sort of thing >> looks a lot like you’re trying to do something malicious even though you’re just trying to give people a nice software development experience. >> Yeah, >> but apparently >> that makes me so angry. >> Well, yeah. The state >> I know. Yeah. >> Yeah. But I but having said that I I do believe there are some workarounds you

can do such as um there was something about like if you can make sure that the the page of memory is not marked as even in any like at any point is not marked as both executable and writable then you’re okay. So you can do like pause it make it not executable anymore. Now make it writable okay write to it. Okay put it back you know it’s executable. I don’t know. Like I guess the antivirus virus just do that. >> Oh, right. I I don’t get it. But again, this is this is just something I read. I

haven’t actually gotten to this uh yet. >> I see. Yeah. I don’t know. My philosophy is like if your anti virus software is uh interfering with your software development, that is a skill issue. >> Turn it turn it off. Like what are you doing? Like are you going to download a virus? Don’t do that. Like if you are if you catch a virus with anti virus software, it is too late. Like that’s the wrong solution to the job. Yeah, that’s fair. Um I mean I haven’t used antivirus software since I was like

a kid maybe. I don’t know. It’s been like 20 years probably. >> And maybe I’m riddled with viruses, but I don’t think I have been as far as I know. >> But who knows? I mean, yeah, that also I I do kind of wonder to what extent antivirus software as a concept is just kind of outmoded in the sense that in the early days of the internet before I actually think JavaScript gets a lot of credit for this because JavaScript is actually successfully sandboxed like when you visit a web page and you run

JavaScript code on it I don’t have any fear that I’m going to get a virus and so far I’ve never been burned by that in theory there could be zero day exploits but I am not aware of any that have had any significant impact on the world so that just means that because so much software runs in the browser in practice these days and when you’re not running in the browser it tends to be something that’s you’re very specifically downloading and almost always from some reputable source then I don’t know the

risk of getting a virus is the the surface area is just so much smaller than it used to be um I wonder why you know even people who are not programmers are they at serious risk of getting viruses still maybe they are and I’m just ignorant of that but >> I mean the software antiirus software was dead on arrival Because if if you have a virus, the only like uh like security acceptable solution is to reformat your computer. Like you can’t you you one does not simply delete a virus and then be done with the virus.

You see what I mean? Like you’re compromised. >> Like you have to delete your private keys and make new ones. You have to reformat your computer. You have to change all your passwords to everything. Like you’re done. You’re cooked. Like you can’t just delete a virus. Well, and I I don’t even know if reformatting is enough because aren’t there some viruses that can infiltrate the firmware? And so you >> Oh, geez. Yeah. >> Right. I’ve heard of this. I have not uh

I don’t have firsthand experience. >> That’s unusual >> that I’m aware of, but >> I mean that that that’d be like the hardware or operating system equivalent of like escaping like the today’s JavaScript sandbox or something. >> Yeah. But and I think >> theoretically possible but very rare, >> right? I guess this is why modern systems have like secure boot and stuff like that is to try to prevent things like that from happening. I think um definitely getting out of my comfort

zone of things I feel confident that I actually understand. But >> yeah, that’s their excuse. But then it’s also a DRM thing. It’s mainly a DRM thing. >> Fair. I guess there’s a lot of >> It’s different. It’s different than sandboxing. Yeah. >> Yeah. Okay. Well, we could probably pop that off the stack, though. >> Okay. All right. So, that’s linking and antivirus. >> Link link. Linking goes deep. Yeah. Yeah. >> Okay. So, what was one of the other

things? Uh so we talked about the like pv and stuff like that and um and then and then reading back in and doing like fix ups which is kind of kind of like relocations in a sense uh like tweaking your relocations in a linker. >> Well, let me ask you a more high level question. So you’ve been um it’s it’s been like a few months now since you decided to do some rewriting in zig of rock. >> Um how’s that going? What what’s the progress? And uh is there any pain points that you want to share with

Sure. Um, it’s been going great. Uh, very happy with the decision, very happy with how everything’s been going. The feedback loop is a lot faster, which is one of the main things that we were hoping for. Um, even though we’re all doing development on ARM 64 Max, so we’re we’re not even getting the latest uh like the really fast stuff um that the the x64 ZIG users already get to use. Um, uh, honestly, like the the main thing, and I I love that Zigg does uh tell you what the compiler is doing. So

basically whenever I’m running my loop of like you know rebuild and re and retest um always what I see is emitting LVM object right and then like okay all right we’re waiting on LVM I’m familiar with this um I notice that and then after that’s done then I’m just waiting for the tests to actually execute and that’s our stuff and like we >> we want those tests to do what they’re doing so you know we’re fine with that. Um, we’ve also been using uh Okay, so

painpoint I would say we’re excited about when Zigg has first class fuzzing integration because we’re doing a bunch of fuzzing um and it works but it’s not the smoothest experience. Um, as I’m sure you’re aware like the integration with like AFL fuzz is like um yeah, like it works but it’s not the best user experience. >> Yeah. Well, the integrated fuzzing doesn’t doesn’t work. Uh, that’s how I would categorize it. Um, >> sure. Like but like you know

yeah whatever like using using fuzzing in zigg like is is a possible thing we are doing it so we know it’s it like it works in that sense but like yeah like >> it’s not it’s not like the normal zigg experience of just like oh this is like in standard library and we just like pull it off the shelf and everything just works great right yeah >> um I know you’ll get there >> um yeah so like I said we’re we’re excited >> there >> that’s a good topic because there the

man fuzzing is such a cool concept first of How’s the audience of this podcast? Like is it worth an explanation or should we kind of like uh >> uh Sure. We can briefly briefly. Yeah, >> go for it. >> Okay, let me learn about about how cool fuzzing is for a second. >> Sure. >> So, the idea is you uh you know what sucks? Coming up with unit tests. It’s so annoying. You have to like think, you know, and you have to like figure out all this stuff. Okay, what if instead

the computer could figure out all your unit tests for you? So we start with just uh like randomness. We get like uh we start fuzzing and the fuzzer gives us just a buffer of just literally random bites and your task is to feed that somehow to your software that you want to test. Now you can imagine some stuff being easier to test than others. Like um if you have like a tokenizer, you can literally feed it random bytes and uh and like you could check that it doesn’t crash. Um you can check like that it

certain properties are with like within the constraints of how it’s supposed to work. >> Yeah, we literally do that one. >> You’re already kind of li >> Okay. Yeah, you literally do that one. But if you’re trying to do something more interesting like let’s say the next phase of the compiler like parsing. >> Yeah, >> random strings are not going to help you at all. That’s just going to hit the same like uh you know bad token error over and over and over again like you

know a million monkeys typing on keyboards forever. They’re not going to create the works of Shakespeare. They’re just going to type like ASDF ASDF like over and over again for eternity. >> Right. Now, now in in in defense of that, I would I wouldn’t say it doesn’t help you at all because we actually just by doing that, we we still have not gotten through the backlog of all the bugs that that has turned up just because like you’ll get this thing where it’s like, you know, oh, it turns out if

someone types in a rock program of like question mark close curly brace, you know, like square bracket that like doesn’t do like the compiler doesn’t handle it as that nonsense as gracefully as it should. Um, there’s a bunch of stuff like still catches some stuff, >> right? And and but like on the one hand, you know, uh you know, not not to not to derail what you’re saying, but like um on the one hand, you know, you can make the case for like who cares? People aren’t going to run into that, but the

on the other hand, what’s really nice about burning down all of those and actually fixing them all such that we’re like, yeah, we don’t have any known fuzzing errors. Is that when you add a new feature to the compiler, if you start seeing fuzzing errors, suddenly you’re like, uh oh, I I know I know what caused this. And this has already happened multiple times. where like we’re like, “Hey, I hadn’t seen a fuzzing error in like a week and then we just landed this PR and now I’m starting

to see fuzzing errors again.“ So like I don’t know what what happened like I I know I know what caused it, right? >> Yeah. >> Anyway. >> Yeah. >> Yeah. Well, to get to get through the like uh introduction to fuzzing, um there’s two different approaches that we can take that like really unlocks the power of fuzzing. And the first one is introducing genetic algorithm to the fuzzer. Uh genetic algorithm is like DNA. Um you you you uh you need a fitness function and you evolve your DNA

over time. So the fuzzer will give you these random buffers of data. Um but you instrument your code. the compiler emits like special instructions so that it knows how much code coverage that input um resulted in. And that code coverage is a fitness function. So that powers the fuzzer to pick better and better inputs. And if it can run your tests really fast, it can very quickly like evolve uh those inputs to like find interesting paths because it’s literally checking your if statements. It’s like,

well, when I pass this, it goes it found the interesting path in the if statement, so I’m going to keep doing that, but I’m going to mutate this thing over here so that it goes over there. It’s it’s really cool how like smart it can seem when you when you add this like genetic algorithm to it. >> Yeah. Another thing Sorry, go ahead. Yeah, >> I was going to say another thing you can incorporate into the fitness function is just like um if there are things that are more likely to come up in practice

than others, like for example like valid tokens or like valid, you know, things that like parse validly, that can also be part of a genetic algorithm where you’re just like yeah, like evolve in the direction of like programs that are more correct, but then don’t go to 100% of like they all have to be correct. Um because that like you don’t want to overfit, but yeah. >> Yeah. Yeah. Yeah. Uh okay. And then the other the other like uh ingredient to this like potion is um it’s called a

smith. So you take these random bytes of input and instead of feeding them directly to your to your software. Maybe that doesn’t even make sense. Like maybe my input’s not like random bytes. It’s actually um uh it’s actually like you know two integers that are between the values like zero and 100 each. >> Yeah. uh and and so what you do is you take these random bites from the fuzzer and you you smith them you conform them to the input that your program expects which will help it to find more

interesting code paths. So for instance uh I might create a smith that generates like only syntactically valid like zig parse trees uh like zig yes source code. Um and so now every time the fuzzer gives me an input it is going to pass tokenization. It’s going to pass parsing, but it might hit type errors. Now, now I’m actually fuzzing the type checker because I’ve created a a smith that uh that bypasses like this this first like phase of this pipeline. And like when you add all these ingredients

together, uh it’s like you can just find all your bugs before your users do. Like no bug reports needed. Why would there be a bug report? like we found all the bugs, they’re gone in theory or at least if you now if you do have a bug, it means that you have a bug in your Smith or something like that. Um where like you’re you’re you’re not you’re generating something that seems like it’s supposed to be valid but it’s not or vice versa or something like that. >> Um yeah, we have not done this for type

checking yet. Uh we haven’t gotten that far. Also, our type checker is not done yet. So if we did it would just give us a bunch of errors we you know like oh it’s not done yet. I knew that. Yeah. Um >> yeah but uh yeah you did ask about progress earlier. Um so right now we are intentionally the the milestone that we want to hit is we want to make it so that people can do advent of code this year in the new compiler um and like be successful at it. So we’re intentionally not working

at all on the optimized pipeline. Like we’re just saying like that’s that’s for December at the earliest um or sorry January at the earliest, right? Um, so, uh, that means like no LVM stuff and also no like lambda set specialization or whatnot. Um, we’re preparing for that like to organizing the compiler in the ways that we need to to be able to make that happen. Um, but we’re intentionally not doing it yet because we want to like hit that milestone. Um, but like right now, so parser and like tokenizing and

parsing pretty much done. Canonicalization is mostly done, but not 100% done. Um there’s some stuff we still need to do like uh figuring out um like lambdas and then also you can do top level declarations out of order but we need to sort those into strongly strongly connected components uh so that the type checker checks them in the right order. We don’t do that yet either. So a couple of little things here and there and canonicalization um like um like the name resolution stuff that’s all there. Uh type checking we

have type checking for a lot of types but not 100% also. Um so I would say typeinge is also mostly done. Um, but there’s just kind of a a checklist of things that, you know, still need to be done. Uh, I think like Yeah, I don’t actually know if like recursive uh like functions and recursive data types are always like there’s like there’s like you open the door and you’re like, “Oh, there’s a lot back there. I didn’t realize there was so much behind this door compared to the

other doors.“ Um, and we have some of that, but I know that there’s more just because of yeah, past experience. Um, but yeah, it’s it’s uh it’s getting pretty close on that. And then we have a very very very limited interpreter. Um which is yeah we’ll we can talk about that uh in a bit but like uh previously we were doing the like emit machine code and this time we’re trying out at least as a first pass doing uh an interpreter instead for debug builds. Um and that is

in a very very basic state. It’s like it can do like numbers and booleans and ifs and like that’s it so far. So like it it does some stuff. How does that >> how does that interact with uh platforms which are like >> that’s a that’s a rabbit hole. I’m happy to talk about that but like you know be be careful what you wish for. Uh that’s >> it the design is really really cool. Um but yeah but if you want to go down we can go down that tangent right now. >> Yeah I want to know how your interpreter

strategy works with uh platforms which are like maybe not interpretable. >> Right. So um okay so fundamentally for those who aren’t aware uh rock has this concept of platforms and applications. Platforms being um the sort of like the low-level component of your project and the application being like your actual application. So classic example of this is you have a web server. The platform would be like somebody wrote in Zigg or in Rust or in Go. We’ve we’ve seen examples of all three of those. Um

someone has written a like web server kind of framework for you and then you write your rock application on top of that. As the application author, you’re just writing rock code. we don’t necessarily care what’s going on under the hood, but as the rock compiler author, I have to care about all that. So, um, we have this challenge where the platform author provides us with like here’s just a binary funk, you know, here you go, have fun with that. Um, this is the like pre-ompiled thing that,

uh, that gives you like the web server guts, like the the low-level implementation of the web server and then it has to have sort of bindings to like hooks where the application author can sort of uh, connect in there. Okay, so how does this work with an interpreter? because yeah, like you said, they they just gave me this opaque blob of bites. What am I going to do with that? So, um, at a high level, the strategy that we’re going with for how we’re going to, let’s say I’m doing a like a rock dev build.

I’m like just goal is to like get something up and running as fast as possible. We’re not trying to optimize anything. We’re not using LVM at all. We just want to like get this the fast feedback loop because I’m developing my web server as as a as the rock programmer. So, here’s what the compiler is going to do. First of all, uh this is a change from the old compiler. the platform author is no longer just going to give me a single binary. Uh now the platform author is going to give me a

series of object files. One of the object files is going to be the main bulk of like what they’re doing. So like the actual web server stuff, but also platform authors are now responsible for giving me all of their C runtime libraries that they also need. So like strti and you know all that fun stuff. um that needs to be linked in in a particular order because what’s going to happen is that basically uh ultimately um let’s just pretend we’re doing an optimized build because it’s a little

bit easier to understand this part and we’ll get back to the interpreter part. If this were an optimized build like the rock compiler is like cool run through LVM sped out an object file that’s just like compiled from LVM which is a bunch of bytes of executable stuff. So now I take the ingredients that I’ve gotten of the platform author gave me the host file which is their like low-level binary of their web server thing. They gave me all the C runtime libraries that they need. Also I now have the rock

application file. Their web server is is trying to statically link something called like rock entry point or whatever. And my object file that I spit out provides that rock entry point. Now, um I can just staple all those together like putting the C runtime libraries in the right place where they need to go like in the linking order because they’re annoying like that where they have to be in a particular order. Um and the platform author specifies like okay I gave you these files and they go in

this order um you know relative to the application thing and we’re going to bundle LLD with the compiler to do that. So we can do that not only crossplatform but also we can have cross compilation. So the platform offer gives us everything I just described for all the different targets they support. So, Windows, Mac OS, Linux, whatever. And that means that as the rock application author, I can say, “Cool, build me a compiled web server that works on Mac and also on Linux and also on Windows.”

And I can do any of those things. No matter whether I’m running on a Mac or Linux or Windows, I can always compile for all the different targets that um the platform supports. Okay, let me pause there. Did that all make sense? >> Yeah. Okay. >> Yeah, I think I I already guessed the answer, but yeah. So, well, you you probably guessed part of it, but the thing that we’re actually the specific thing that we’re going to try is pretty wild. Um, I I’d be very impressed if it

turns out you guessed this because certainly was not my first thought. Um, but we ended up there. So, okay. So, now the interpreter part. So the basic idea behind the interpreter is that we do all of that stuff that I just said except that what we want to do is because linking is slow and also LVM is slow. We want to make it so that during your development loop we don’t do the LVM code Jet we also don’t do that linking. So what we want to do is we want to link some hard-coded thing in there where the

application goes once right when you first downloaded the platform. Um and then never again like we just we don’t have to do that ever again. We’re just like cool we link that thing in there and we’re good. So that’s all well and good. That’s that’s an easy enough thing to do. But then the question is like specifically what do we link in there? Because the whole point of this is that your application is changing all the time even if your platform isn’t. So every time I change my web server.roc

files like if we already pre-linked this hard-coded thing it’s not going to pick up that change. So how do you pick up that change? And also how does this work with an interpreter? Okay. So the short answer of what we’re going to do is that hard-coded thing that gets linked in there is basically a very very tiny bit of uh like code. Okay, I shouldn’t say tiny but like much smaller than the actual rock compiler. Um, it’s basically the subset of the rock compiler that is the interpreter. It’s like given these

data structures are in memory. I know how to interpret them. Um, then uh the way that it looks for those data structures is shared memory. So it basically says I have like a named shared memory section that’s like we just tell the platform authors don’t use this shared memory thing. It’s called like rock_shm you know don’t touch this whatever. Um, and and and that’s that’s also hard-coded. Um, but again, like you know, it’s just it’s part of the contract. If you’re doing platform

development, don’t mess with that or else you’re going to mess up people’s dev experience. I don’t know why people would if it starts with rock underscore. Um, but shared memory is named, right? So like shouldn’t be a problem in theory. Um, so basically that’s what that thing does. It basically is like, okay, so we have this entry point. Um, the uh because this thing is hardcoded. Um I think what we will need to do is we’ll need to do uh what’s it called this? There’s some LVM utility that’s

like a crossplatform way to like rename symbols. Um and you can also uh add symbols with that which is a little bit of a like rabbit hole if we want to support multiple entry points which we do in the future but you can do a little tiny bit of like inline assembly and stuff like that. It’ll be fine. But the simple cases where there’s just one entry point. So basically all we do is we just have this hardcoded thing that’s in the rock binary. Um, and we’re like, cool, I will just like copy myself, copy

those bytes onto disk, run the LVM thing to rename it to whatever the platform’s looking for in terms of the entry point symbol, and then everything else just kind of works. All right, so shared memory. So, I’m this little hard-coded thing. I’ve got the entry point wired up to the uh to the platform to to the host. It’s calling me, and the first thing I do is I go look at my shared memory, and I’m like, cool, I found uh all of these bytes that I’m going to now interpret. And because I am an

interpreter, as long as those are the right bytes, I will just go ahead and go interpret them. Okay, so uh this does mean that you have to launch this from the rock compiler because the rock compiler is going to spawn the process and do the shared memory thing and like give give the bytes over to that, right? So the when you say like, you know, rock whatever my program.rock, it’s going to spawn the subprocess of that pre-ompiled stapled together thing that we did once on download. And you know, every single

time you run rock, my app.rock, rock, it’s going to go launch that executable. Um, but it’s going to launch it with, you know, shared memory. So, we can share some memory between the compiler process, which is still running, and this uh this child process. Okay. Now, for the wild part. Um, so the question is, so >> the wild part’s still coming. Okay. >> Yeah. Yeah. We’re not there yet. No, >> everything up to this point was straightforward. I’m sure everyone

listening followed 100% of the problems, right? >> Um, yeah. >> Uh, okay. So, so the wild part is that what we’re planning on doing for what goes in the shared memory. So, the interpreter actually runs directly over our IRS. Like it’s it’s not like we have a bite code. It’s just like no, just go interpret like the canonical IR with type information added to it. >> Yeah. >> Go. Um >> I mean that that is a bite code. That’s the same thing. >> I know. I know. Yeah. I think right.

It’s it’s like I’ve had some discussions about this where it’s like the term bite code is like it’s just it’s just an IR but like it’s an IR that’s like designed to be interpreted but like our IR is designed to be interpreted in some sense. So like isn’t Yeah. Anyway, um but yes, so >> so the cool part about this is that what we’re doing and this is this is an example of something where the fact that we’re using Zigg is a big help. The plan is when you’re doing one of these

development builds, we use a special allocator that’s like put everything like do some gigantic like give me a virtual alica a terabyte so it’s all contiguous and just do all of my writes into there. So now we have this completely contiguous buffer of memory and we’re just like cool let’s just share all of that and then when we get to the point where we’re like hey child process you need to go interpret this. Guess what? We’re in exactly the same situation that we talked about earlier

in the conversation with um deserialization where we’re like okay you have all this memory and the only problem is that like the 20 pointers are at the wrong addresses. So just go do exactly the same fixup that we’re doing for loading our cache things from disk where you just go and update each of those pointers by whatever the address is in your virtual address space of as the child and you’re done. That’s it. The interpreter now has all of that like exactly what it needs to interpret in

memory in that shared memory. No extra work. It’s just like the compiler did all the work that it needed to do and then the the child process just like ended up with all of the the bytes that it needed already in memory. No extra processing work whatsoever. Okay. What’s really cool about that though is that then so that’s like kind of efficient and neat, but then hot code reloading gets like way simpler because if we’re like, “Oh, we want to like do a hot code reloading thing.” It’s like guess what?

All we have to do is keep the parent process running and be like, “Oh, now we have like we’ve detected on disk as a new thing. Just keep all the memory there. Don’t don’t mess with that. Actually, ignore it because probably the child went through and like mutated in place those addresses. So, the parent shouldn’t look at those anymore. That’ be a big mistake because they’ll be the wrong addresses now.” Um, but the parent can just be like, “Oh, cool. We got like

something changed on disk.“ Great. Leave all that memory alone. The child’s using it now. It’s fine. In fact, you can even unlink it if you wanted to. um just spin up a new like do a new a new build, new inmemory, whatever. Um we have like a terabyte to work with, so it’s fine. We like 10 terabytes, whatever. 64 bits is like an insane amount of virtual address space or or 48 bits or whatever you actually have. It’s like you’re never going to use anywhere near all that. Um,

so basically what we can do is we can just say like cool just like make a new like named shared memory segment or even don’t just like do it with an offset or something like that and then just go tell the child like put put some like some of the memory that you are already sharing. Reserve that for informing the child like hey uh there’s new there’s a new version of this code out check it out and it’s right here in this memory that you already have. So if you want at your leisure you know feel free to go

use that instead of what you’ve been using. And then this gets into like because rock is designed to be very non-stateful. Um it’s just like the host just calls this like function and it’s like here’s all the info you need. Here’s how to do allocations blah blah blah. It’s all function pointers. Go run and then you return back. That means you can do stuff like in in the web server example web server’s got these incoming requests. It can the child can be like sort of pulling this um this little like

bit of memory to see like oh is there a new version out? Oh there is. Cool. it can be like well great from now on like new requests start using that and existing requests just keep interpreting the old one it’s still there in memory it’s fine and so it’s all like completely smooth and like no interruption zero downtime type stuff um and then once it’s finally done then it’s like oh okay nobody’s using that anymore great now we can unlink it and we like you know reclaim that memory or

like you know give it back to the to the OS uh now >> that’s really neat >> it gets slightly cooler I’m I’m almost done so that’s the interpreter thing but here’s the really wild In the new design, we don’t actually need any linking for rock code. Like compiled rock code. Now, the entry point is basically like when you call when when the host calls a rock program like a compiled rock program, it’s literally there are no symbols. It’s just like how do you get your allocator? Pass it in.

How like how do you get this? Pass it in. It’s like the host is passing in absolutely everything and just getting back plain data structures. So like we don’t need lib C, we don’t need anything. It’s just there’s zero like symbol lookups anywhere in generated rock code at all. Because of all that, this means that now everything I just described, instead of doing an interpreter, we can just give you executable bytes and you can just like mark those pages as executable and just

go run them. And this everything I just described like the all the hot code reloading and stuff could be done in production as well. At which point if you wanted to you could swap out the shared memory for like a socket and it’s like give me these bytes over the socket and you could like have a remote server running like connected to my laptop and I could just be like beaming it hot code updates that it could just like start swapping in and running like completely optimized um in production and that

could just like keep running as as often as it wanted to and the all the characteristics I just said would apply. So um we’ve implemented almost none of this. Um so this is all like total like you know uh what we’re building towards and not like stuff you can go try out right now. Um but like I said I am actually quite close to having a PR for the um the like fix up approach. Like the hardest part of all this as you know is like organizing your entire compiler using indices so that any of this is

even remotely possible. Like the critical thing is like >> we have like 20 pointers per module instead of like >> a gazillion. >> Yeah. Um if you have a gazillion then like none of this is fast. So we’ve already done that part. Um and so yeah this this is like >> real quick uh >> important question. Are you are you using uh like are you raw dogging like U32s for these indices or are you using like um enums or structures? >> Oh yeah we we wrap them. Yeah. We say

like idx is like the the type name that we wrap them. Yeah. >> And then you can like add like methods to it and stuff. >> Yeah. Uh I don’t I don’t know if we do methods on our indices really. I mean we could but um usually we just use them to like I don’t know go go give me the thing. The the pattern we use is we’ll have like node store which is like has all these things in it and that’s where the actual pointer lives and then node store is like give me an index of like

my my particular node stores type. So like give me a canonical index or you know one of those and then it like hands those out. Yeah. >> Yeah. We don’t we don’t just use >> when I first started doing >> Yeah. I know. When I first started doing all this stuff, I did that. I used like bare U32s and uh yeah, it bit me for sure. So now I’m always wrapping those indices and types aggressively. Yeah, we had already started to do that kind of stuff in the Rust compiler and that was

like one of the reasons for like rewriting in Zigg was we’re sort of like Rust doesn’t give you any memory safety like any more memory safety than Zigg does if you’re already doing that. Um, so and we we knew that like we not only do we want to do more of that, we want to do that everywhere so that we could do stuff like the like >> Yeah. I mean this me this memory sharing stuff sounds pretty unsafe to me. >> Well, yeah. I mean, it’s it’s wildly unsafe. I mean, Russ would be like

unsafe keyword everywhere, right? which was part of like what we I mean we weren’t I didn’t have the idea for that specific thing back then but it was in general just sort of like >> this is like not what Rust was not designed to be used in a way where you’re just constantly saying unsafe all over the place like that’s not and also >> there’s this other aspect to it where um >> there’s a weird mental load at least there is for me of constantly feeling like I’m not supposed to do it this way

I’m not supposed to do it that way I’m not supposed to do it this way because like it’s like I feel like I’m doing something bad and like I should I should spend extra mental cycles thinking about how to not do it that way. Whereas with Zig, I’m like okay given that everything is already unsafe, I’m just thinking about how do I make my program correct and like fast and I’m not thinking about the additional constraint of like how do I try to minimize my use of unsafe here

if that makes sense. It’s like because I’m already priced into like like actually an interesting example of the like the 20 pointers and the like relocations thing is like on the one hand you could look at that and be like wait a minute so you’re telling me you get raw bytes off of the disc you read them into memory you cast it as like something that you’re like hopefully will just work out and then you go through and you have to go and change every single one of those individual

memory slots every single pointer in there to be um to to change its you know address in memory like increment by a certain amount and if you forget to do one of those, your whole program is going to break. Like how could you possibly live like that? I’m like you just described D in it. Like yeah, you have to catch them all. Like that’s just how it works. Like we’re already doing that, right? It’s I mean it’s like we’re doing it in a different way. But >> it’s like compared to Rust where it’s

like all that is taken care of for you and you never think about it versus in zig where it’s like well we’re already making sure that everything has to deinit everything else and you have tools that can help out like the testing allocators and whatnot. Um, but the point is it’s like this is just much it’s just a much more comfortable place to try to do things that are this ambitious from a performance perspective. Doing it in Rust feels like like I’m doing something wrong. Well, yeah, but that that’s a good point

too with D in it because uh I’m guessing like as you’ve been um kind of like aggregating or like batching all of these like allocations together uh your DNA functions get shorter, don’t they? like you know the fact that you have like a million things in your DN function is kind of like a smell in Zigg whereas it’s invisible in you know with like RAI >> oh right and but but like the point is that you know but they also nest right so it’s like everybody is responsible for dnitting the stuff that they are

owning um and similarly everybody’s responsible for doing the relocations of all of their things right so each of their relocation functions is also pretty short um but yeah it’s it’s it’s different when it’s visible versus invisible. It’s different when you’re thinking about it versus conditioning yourself not to think about it and instead conditioning yourself to think about how to think about it less and like try trying to be safe. I guess it’s like there’s this tension between

wanting to make the program go as fast as possible and wanting to use safe Rust as much as possible and that tension doesn’t exist in Zigg. It’s just about like how can I make the program go as fast as possible and catch errors in whatever way seems like a reasonable way to prevent those errors from happening. And I mean to be fair like I will say that I have definitely and and other people who are working on the compiler have said the same thing like have definitely reported running into

segmentation faults during development a lot more often. Um nobody’s using the new compiler in production yet so I don’t know if anyone’s going to like encounter that. But I will say that there there is another consideration there though which is that in the Rust version of the rock compiler people would run into panics on a pretty regular basis. And this gets into something that like is a pet peeve of mine. I I think Rust overall I think that has is like very good in terms of its API design. Um like if you accept

the premises of Rust as a language it’s like within those constraints I think they did a really good job with the standard library um and things like that. um you know async notwithstanding um but like one of the things that I think is a mistake and this is something that I’ve been really adamant that we not repeat in rock is that there’s this function a method called unwrap and the purpose of unwrap is basically to say I am confident that what I have here is not you know null or undefined or like

I’m confident that I’ve got a real thing here even though the type system is telling me that I might not I’m confident that I do and if if I’m wrong panic at run time. So, this is kind of the Zigg equivalent of like or else unreachable. Um, the difference being that in Zigg, if you want to say that >> you’re you’re making a much stronger claim like or else unreachable is not to me doesn’t feel like something that you casually do, but unwrap is just a total totally normal method call. It’s just

like you can just have a long chain of functions and like it doesn’t jump out at you at all if there’s an unwrap in the middle of those. Um, unless you’re like really conditioning yourself to be like anti- unwrap. Also, there’s a synonym for unwrap that’s called expect, which is like that except you give it a string for the error message that it will like panic with. Um, I think this >> kind of a tell, isn’t it? >> That that one’s more of a tell. Yeah, definitely. Um, but to me like the

problem is that it in practice seems to create a cultural norm where it’s just kind of fine to use those and that just leads to panics. like like the number of panics that I encounter from like um in in any Rust programs that I’ve used that are due to unwrap versus like an explicit someone wrote out panic in this you know case for this reason. It’s like unwrap has like a huge percentage of those. Um and I really appreciate that ZigG doesn’t have that. And also Zigg has this culture of like even memory

allocation errors are errors and like we treat those as like error like we really try to handle like all all the different error cases. Um because if you want the compiler to be really reliable like it’s not enough to just say like oh we don’t want a seg fault. It’s like seg faults are only one category of bad user experience. Like in general we want to be trying to be like we don’t want to panic at all. We want to like you know if if we have a bug ideally our bug is can be translated into like a first

class error report where we’re like hey sorry the compiler ran into this thing but let me at least report all the other problems to you that you know you’ve encountered up to this point. The huge pain point with the Rust version of the rock compiler was that like um if a panic happens during type checking or something like that or monomorphization um that would mean that no error reporting would happen because that’s a later stage like that when we actually print out the error reports like that we

we at least in that version of the compiler we weren’t like printing them out as we encountered them. We would like make a big long list of them and then print them out after everything was done. Um and so if you hit a panic you would just have no feedback. it would just be like, “Oh, you’re dead.” And like you can fix this by making reporting happen earlier or whatever. But um that’s just such a serious like negative user experience. And like in comparison, if you can just at least

order it so that like you know you have like streaming errors coming out, it’s like is it a panic versus a Seg fault? I don’t know. Do I do I necessarily care as the end user? I guess you can make an argument about like well seg fault indicates like a memory mistake which could be used as an exploit but I don’t know like people attacking your compiler. I don’t know. It it it it just doesn’t seem like it’s it’s hard for me to make the case that like a seg fault in zig is

like a categorically more serious problem than like a panic in rust at least for the use case of our compiler. >> Um because to me like the end user experience is just very very similar for both of them. And I guess you could say technically is a panic log, >> but >> yeah. >> Yeah, >> it’s I mean it’s not necessarily like seg faults are well defined like if you if you uh dreference like certain addresses like it will seg fault like oh like that is a well well- definfined

behavior. I mean the point is that like >> right well what I what I should say is more like >> if if you’re >> if the seg fault happened because you had a memory arithmetic error like an off by one or something like that that could manifest as a seg fault but also sometimes the same bug could manifest as corrupting memory and you know like there could be >> yeah it’s like you got lucky to get a seg fault actually >> right yeah if you’re unlucky >> it’s like oh good I got a seg fault

right >> but again like so far >> I want to make a uh >> yeah Uh, sorry. I I want to make a um a distinction between because you mentioned unwrap versus catch unreachable. And just for for people who aren’t like, you know, super familiar with Rust and Zigg, uh, I think that it’s nice to like draw the distinction here because um >> the the the Zigg equivalent of of unwrap would be catch panic. It’s but but you’re saying catch unreachable. >> Sure. And the difference there is in

Zigg what we call um illegal behavior and um panic is well defined to trap whereas unreachable is illegal behavior and we say illegal because it’s not necessarily undefined behavior. So in Zigg, illegal behavior will be um well-defined panic in debug and release safe mode, but it will be straight up undefined behavior in release fast or release small mode. >> So when you’re doing catch unreachable, >> you’re saying like I’m it is a like programmer bug. It it’s kind of like

Zig’s unsafe, isn’t it? Like um you’re saying like it’s a programmer bug if uh if I’m wrong about this, >> you know? I’m like I’m promising you as a programmer like you will not hit this line. >> Yes. Yeah. Um and then right and then the difference being that uh if you’re doing release fast that the optimizer can use it and can assume that that will never be reached and can do things like eliminating conditionals because it’s like oh well since this can never be

reached. We don’t need we don’t even need to check it. It’ll just be it’ll be fine if this conditional never runs. >> Yeah. >> Yeah. >> Like if it’s catch unreachable we just like assume it never errors. >> Right. I’ve had a funny experience um like using uh this is a Claude 4 um on uh on like with Zed’s agent mode um on ZG code because one of the things that Claude likes to do which is just so funny because it’s just such a I don’t know a silly mistake to make is like

I’ll have some code and I’ll be like hey like you know set this to undefined because like we’re going to overwrite it later and it’ll be like oh undefined is bad so I’ll just enum from int zero because that’s at least now it’s you It’s like, “No, no, no. That is astronomically worse. Don’t do that.” Right? So, I’m like I’m like yelling at him. I’m like, “Why do you think this is better? This is clearly worse.” Like, >> yeah,

you know, there there’s there’s something that even worse than that that I’ve seen people do sometimes in Zigg, >> which is >> enum from int 0xa. They use the undefined because so in in Zigg um as a as a like a debugging technique. Um when you use undefined zig will set those bytes to like 0x a it’s like ah you know uh and that bite that bit pattern is like one zero one 0 1 0 1 0 which is like never mapped in memory and it always overflows integers. It’s

like a super handy like debug value if you accidentally end up using undefined, >> right? Um but then like people will sometimes like very like ill-advisedly like set something which is undefined to like 0x a aaa rather than like just the word undefined. >> That’s like totally wrong. Like it’s like >> it’s worse in every way. Yeah. because you’re going to you can it’s not undefined but you might trick someone into thinking that it is which is super confusing and a completely unnecessary

scenario that you should never have to encounter >> right and it’s more verbose and the compiler can’t do the right thing with it so >> exactly yeah it’s like it’s like worst in every possible way and I’ve seen it >> um but it is cool that like I don’t know I definitely appreciate all of the affordances Zigg has for being like, “Okay, if you’re gonna like it’s not reasonable to say, I don’t think that just because Zigg doesn’t have a borrow

checker that Zigg is like like doesn’t care about safety or like doesn’t give you tools for safety.“ It’s just that the tools are much more of the nature of like what we’re describing here where it’s like I mean I cannot tell you how many memory leaks we’ve found because we ran our tests and then it was Zigg was like, “Hey, there’s a memory leak.” Like you you initialize this memory and you never de-inited it. Um, I also remember seeing this is from like six years ago,

but I I I don’t remember. Um, I don’t know what the status of it is, but like there was some sort of idea to have like a allocator that could do double uh what was it? Double free detection, I think, something like that. Um, yeah, like there are tools like that that I don’t know they make a lot of sense to me where it’s like the these are categories of mistakes where if you make them they’re very serious, but it’s not like statically catching them is the only way to catch them. And if you’re

not writing any tests, okay, I guess it’s better to have things that will always run. But, you know, we’re talking about like we would like to fuzz basically every stage of the compiler. Like we want to have really really strong like test coverage over everything just because otherwise the compiler is not going to work right. There’s like a gazillion branches in this thing. It’s not going to like be like, oh well, as long as you know there’s no there’s no memory safety issues, then like we’re good. um like

you know we just need to have a much higher degree of confidence that everything works on a logic level and I don’t know I’m just not that worried that like we’re not going to have um that if we succeed in getting our logic quality uh like test coverage as as high as we want it to be that we’re going to have memory issues in practice. I think we’re going to catch those hopefully all uh using using the existing tool set that Zigg has. Yeah, the the zig philosophy is like look you have a

computer your interface to that computer is machine code. Uh we are going to help you write the best possible machine code that you can possibly write that will make your application the best it can possibly be like that that’s that’s like the design principle of the language. >> It’s like we’re not going to leave any performance on the table that you can’t take if you want it. Um, and like if you’re if you’re trying to do something that computers let you do, we’re going

to we’re going to give you like tools to help you like make as little mistakes as possible, but we’re going to enable you to do that. Like sharing the memory across these like different processes or something, >> right? Out of curiosity, what do you think of that whole approach that we’re like working towards? I mean, it it sounds like, you know, it’s obviously very like audacious and like ambitious and has the potential to be very high performance and also very good end user

experience, but those aren’t the only dimensions. I’m just kind of curious like what I don’t know what do you think of the idea as a whole? >> I I have a a basic understanding of of the architecture, but I I I think I might be missing something because um some of the problems that you were solving didn’t seem like they were going to be problems to me. >> Okay. for example. Uh so yeah, so the the um the insight that I didn’t have until you started going into it was that uh these binaries

that the users give you for the platforms, they’re already compiled and so you already can run them. You don’t need to interpret them. You can simply run them and you get to decide uh the interface that they’re going to like have um you know like the externs that they’re going to call as as as they’re provided to you. So you could just write whatever functions you want like at the interface layer you can abstract that like you literally could just embed the entire compiler into the platform.

Yep. >> Um and and and then just detect when it needs to recompile stuff uh you know based on some one like little like polling fd or something like that. So this is actually where we got to >> and I got yeah >> yeah the current design was actually the original idea was like oh we’ll just use shared memory to send like here’s the string of therock file and we’ll embed the entire compiler and then it was kind of like well wait a minute like does that is that actually the best design

and and that led to one thing led to another and now we ended up with this design. >> Yeah. So, so it’s basically like a like the design space that you’re exploring is like you have two processes. You have the uh platform process and you have the compiler process >> and they are going to share some something like they need to communicate to each other somehow and like what’s the what’s the like simplest least errorprone way uh and like most efficient way for that information to

cross that boundary and that’s kind of like the design space that you’re solving. I understand that and I I just uh >> if you know if I spent like hours and hours looking at it like maybe I would like help you like see some I don’t know some simplifications or this or that but I I don’t know that I like understand it enough to be helpful right now. >> Yeah, it’s I mean I I’ve spent an inordinate amount of time thinking about that boundary like all the different

ways that we could uh have >> I guess uh yeah one question that I’m uh that I’m curious about. So um for comparison in the zip compiler one strategy that we use for serialization of these kinds of like memory blobs is we use u array hashmap which stores its keys all in a row in an array its values all in a row in an array and then the like index that helps you get quickly like that helps you learn like which index a key is that’s just kind of like a separate blob of memory. Mhm.

Our strategy is uh when we serialize uh we simply discard the like hashmap index only write these arrays keys values and then when we deserialize we just read only the keys and values and then we just rebuild the hashmap like after we deserialize. >> Interesting. >> Um so you’re doing you’re like repeating like that work. But I kind of like the fact that you’re not like saving it because it’s not needed. It’s like it’s denormalized data, right? like um it’s

it’s an index. You just compute that. >> Um >> so I was I guess uh one thing that I’m curious about is uh the trade-offs between this approach and the one where you’re doing which I don’t fully understand because it sounds like you are trying to send like some of that metadata across this boundary that like I’m I’m used to just discarding and recomputing for example, >> right? I mean so I mean definitely we we could do that. I mean the to in my mind the critical thing that we’re doing is

basically saving the work on read of parsing. Um so if if we isolate just the reads portion of this and we’re just because obviously the writes is like hey what if you just make the pointer be like a little bit different. So you’re just like not writing those at all then it’s like that’s quote unquote discarding is like trivial right from the rights perspective. The read then the read gets more complicated. But the question is like does the read get faster or slower? I is is in my mind the question. So um

I’m just kind of thinking off the top of my head here. So I guess the trade-off would be um if we have a fixed hard-coded number of these hashmaps. So in other words, we’re not having to like traverse through uh we don’t have like a linear number of them which would be true like this would be like on a per module basis at least we would have only one of these. Um then like for the interns for example um then basically what we’ be looking at is like okay when we uh rehydrate this into memory um I

think the actual problem that we would run into with this approach is just that uh so how are these actually stored in memory? Are are they two separate allocations for these or are they is it one allocation like the first you know portion of it is the is the metadata and then right after that is the uh the other stuff >> um when it’s created uh it’s one allocation for all of the arrays like this is how like Zig’s multi-array list works >> um >> yeah I know >> in Zig the array hashmap is uh yeah well

the array hashmap is literally just in a multi-array list with an index um so it’s one allocation multiple arrays inside the allocation and when it has to grow it grows the one allocation and then has to like move the um the arrays however when it serializes it doesn’t leave gaps um so then when you des serialize one of these things you now have a single allocate well yeah you have a single allocation with um with no gaps >> yeah and you usually you don’t need to add to it >> right but there’s there’s one pointer

total right it’s like there’s here’s all my address some portion of that is the metadata that you can rebuild because it’s denormalized and the other portion of that is the non- metadata the the data data I don’t know um right so that’s where I think we run into trouble is that because we’re just reading all this into memory continuously we now don’t have room for the metadata anymore >> unless we like leave a hole in the file that’s like of that shape because we’re

just doing one read sys call >> well you pull it all into memory right >> yeah because you’re trying you’re you’re trying to keep also like basically the hashmap state also shared Right. So what I’m the key question that I’m asking is like why not have that be separate and not shared like h have some like stuff that you like you have to do some rehydrating as you as you put it right so like put all your rehydrated state outside shared memory is basically like my question like why

why not >> um and it is a question because I actually have just never like evaluated this idea. I guess we could uh I I think the the the main thing that comes to my mind as like a potential problem there is just like so let’s say that that the total size of that allocation is like I don’t know 600 bytes. I’m just making this number up. And of those 600 bytes you have like I don’t know 100 is metadata and then like 500 is like data data. Um something like that. Again I’m

making these numbers up. And then so you write it to disk and I guess you have some sort of like load factor so you don’t let it get completely full I’m guessing because it’s a hashmap. Um, right. So you said like there’s gaps, right? And when you serialize, you get rid of the gaps, right? >> Yeah. And you and you discard the metadata, >> right? So So you’d have 600 bytes, 100 was metadata, 500 was non- metadata. But of those 500, let’s say another 100 was gaps. So you have like 400 that you

would write to disk continuously back to back. >> Okay. Then you deserialize it by pulling those 400 bytes out of disk and then you go recreate the metadata. So my concern is that in this design that we’re working towards um we do one read sys call to put everything that was on disk directly into memory contiguously right in into our like allocation. >> So let’s say right in the middle of that are those 400 bytes. >> Well if those 400 bytes are already dense how like literally where do I put

the metadata because the pointer for this thing I only get one pointer. I have to say like >> right so like like I I would need to put it somewhere else basically. >> Does that make sense? >> Yeah. Separate like uh you have your Yeah. So you’d have your shared memory which is like this thing and then you’d have your like private memory which you don’t need to fix up and like you could just compute it uh like on dialization. >> Yeah. But like um Right. Right. But then I but I would

need to copy those separate allocation. I would need to copy the 400 bytes somewhere else. Right? Because in order to have the what was it called the array list hashmap? I haven’t actually used this data structure in zig standard library. Um whatever it is the array >> two pointers. So there’s one pointer to the >> there are two >> there’s one pointer to the keys and values and then there’s another pointer to the index. >> Okay. In the in the data structure

there’s two pointers. >> That that’s what you’re saying. >> Yeah. The hashmap has >> got it. Then that’ll totally work. Yeah. Then there’s no problem. Yeah. Yeah, then we could totally do that. That would be very straightforward then. >> And if it’s and if it’s if it’s if it’s less than the uh like linear scan threshold, there’s only one pointer because there’s no actual index at all. >> Sure. But but then we don’t then we

don’t need it. Yeah. Um yeah, that was that was the the piece I was missing. Um because if there’s two pointers, then it’s no problem. But you see why it would be a problem if there was just one, right? Because it’s like how do we how do we add >> Now I understand. Yeah. Yeah. >> Yeah. Um >> that’s kind of that’s the thing that’s nice though is that Yeah. like the the like you have one pointer for the like uh well- definfined layout like you know that’s why I like this data structure

because you have one allocation which just has these two arrays in it which are just so lovely to work with that’s like if you want to treat it as an array you can treat it as an array and then it just has a different allocation for like all the just mumbo jumbo that like the hashmap needs to look up its index and like you can just discard that data and recmp compute it that’s fine >> right nice um >> and it’s just independent right it’s just like a separate Right. Like Yeah.

Yeah. Yeah. All right. I I got to go try that now because that that sounds Yeah, that that approach sounds really good. Um cool. I I did want to ask you about one other thing. So, this is this is something where it’s not people complain about it a lot. So, I’m sure as soon as I say it, you’ll be like, “Oh, yeah, that thing.” Um I don’t personally mind it. Um I I guess I’ve like run into it once or twice. I was like, “Oh, this is what’s that? >> What was the variables?”

Yeah, exactly. Unused variables. Yeah. So, so I I have run into it once or twice and I’ve been like, “This is annoying.” But then I just like dealt with it and it was fine and I moved on with my life. Um, but like a lot of people complain about it and I want to see I’m curious to just talk to you about it about your like language design philosophy because I think this this actually reveals a really interesting cultural difference between systems programmers and like people like me who

are like relatively new to systems level programming and like much more comfortable with like the world of like manual me or automatic memory management and stuff like that. That’s like almost all my career has been. Um, so, uh, it seems like somehow in the like C and C++ world, there’s like this culture of like you just have warnings that you just choose to ignore all the time. There’s just this like the compiler is telling you, >> it blows my mind. you have this problem and like it’s a it’s a it’s a problem

you should address and people are like no I choose I choose to disregard that problem and sweep it under the rug and shift to production anyway and as I understand it that seems to be where the impetus comes from to say no this is just you cannot proceed until you deal with this warning. Um am I right about that? Is that is that where that comes from? Yeah. I mean, man, did you Well, first of all, if you use like uh um like make or something like this, it’s going to cache uh whether it’s going to cache

successes, and warnings are successes. So, like if you didn’t see that warning scroll by and then you run make again, it will not print again. >> It’s gone. You missed your chance. >> Yeah. >> Which is crazy. Uh so, like what is this? It’s like non it’s like a non um it’s like our development process is non-functional. It’s like mutable state, right? It’s like it’s like set like user saw warning flag equals true and just like go ahead and mutate that memory,

you know, like we’re I’m running the same command and I’m getting a different result. Why? >> Yeah. Yeah. So the the philosophy that I’ve adopted for rock which it’s too early to say whether or not this will turn out to be a good philosophy is um the the mantra has been inform but don’t block. And so the way that we treat actually warnings and errors is both that they always give you a non-zero exit code. So the only way to get a zero exit code is if there’s no errors and no warnings.

However, whether you have warnings or errors both of them we will always like generate the executable anyway. So, and run your tests and whatever. And to the extent possible, we always try to convert even like type mismatches and stuff like that. We’ll generate the code, but we’ll generate like a panic if you get to that code branch. The the hypothesis being that like if I’m just working on one set of my codebase and I’m like, well, I know there’s a bunch of errors as a result of what I’m doing.

I still want to be able to run my tests and stuff like that. Um, so the hope is that how this will get used in practice is that when people are in the transitory state of I’m working on this thing, they’ll be like, “Yeah, yeah, yeah, I know.” and I’m just choosing to ignore it for right now and then when I get everything working, I’ll either fix all of them or I’ll be like, “Oh no, this whole approach sucks. I’m gonna throw everything out and then I’m glad I

didn’t waste time on that.“ Um, and yet because it’s a non-zero exit code, I’ll always fail CI and you’ll, you know, and like we have no we have no knob you can turn to turn that off. It’s just like permanent hardcoded like non-zero exit code. Go fix them before you like ship this to production. Um, and then end users won’t suffer any consequences is the hope. Yeah, we we independently came to the same uh plan, the same conclusion. >> Really? Wow. >> I had the exact same plan. Yeah. Not all

the way all the way down to syntax errors. Like I’m going to delete like parse error from like our parse failure from the error set of like uh parsing. >> Like the only way it can fail is out of memory. >> Uh >> nice. >> And then uh actually I might even be able to remove that error if I just uh require a buffer like as large as the number of tokens. but separate separate problem. Anyway, so uh even a even a syntax error in your code uh you’ll you’ll you obviously

you’ll get the error you’ll get exit code one but you’ll get a binary and like you you can get you’ll get a panic at runtime that says syntax error. >> Right. Right. Exactly. Yeah. >> It’s going to be funny. >> That’s awesome. Okay. That’s that’s great. I mean because that’s that’s exactly like what what we want to do too because um yeah like in every stage of our compiler it’s the same thing. It’s just like we have we have this uh you

know array list of problems that you can push on to that’ll get to get them reported. Um but there is no concept of like I failed stop you know it’s like no keep going like maybe maybe everything is all malformed nodes and so like the next stages is just like throw throwing its hands up like I don’t know just error again at runtime. Um but yeah it’s >> but I I want to complain more on this on this topic. So, the other day on IRC, uh, some some Zig user, uh, they sent me this link to this like, um, this blog

post and said, “Hey, uh, like would Zigg catch this?” So, I was like, “All right, let’s take a look.” Uh, so I click it. It’s this like Open CS ZFS author, nice blog post kind of talking about how um, like it’s a function and some C code and there’s like a bug in there. Like, can you spot it? And I was like, I don’t see it. Let me let me port it to Zigg. So, I port to Zigg and um as part of doing that, I’m obviously I’m converting like all the variables to constants because

that’s how you write zig code. >> Yeah, obviously. Uh and then like as soon as I do that, it’s like okay, well that’s a dead store and like the final constants not used at the end of the function. Like there’s the bug. Found it. Yeah. >> I I didn’t even have to get to type checker. I I got it with like the formatter like zig format. Found the bug. >> Yeah. >> So, so I I go ahead I I did all this trouble. So I was like, “All right, let me just like fully make a blog post.” So

I just made a quick blog post, pushed it out, forgot about it. Next day it’s on lobsters. And people are upset because they’re like, “That’s not a like direct port, you know, to Code.” Um, you didn’t port the bug. >> And and then this guy, some guy, >> I didn’t they didn’t port the bug. And and some guy even made like a parody blog post uh of like porting my Zig code back to C. And he’s like, “See, it caught we caught the bug.” But he didn’t

even show the command that he used to run the C compiler because it doesn’t work. Like if you run the C compiler with their code, it prints nothing. It exits with zero and then it has the bug. Like he had to add like a w unused variables uh in order for that warning to be printed. And even in the blog post, it’s a warning. So it exited with code zero and the bug is still there. if you run it, it will crash or whatever. So, it’s like and then and then I have all these people complaining at me in Zig about like

unused variables like so like it’s like these people can’t agree with each other, right? Because these people are like, “Oh, well, yeah, but C has the problem fixed, but it’s not on by default.” And then I turned it on by they’re assuming it is, right? And then these people are like, “No, turn it off by default. We hate it being on by default.” It’s like you you can’t have it both ways. >> Yeah. I I mean, you know, we could talk for another hour and a half about uh you

know, comments on the internet. Um but but we are I mean, we we have been going for quite a while. Uh is there anything else we should make sure to talk about before we uh wrap up? >> Uh let’s see. I guess I’ll make a plug. Uh, Ziggs Software Foundation is a 501c3 nonprofit. So, if you uh if you’re well off and you uh want to spare a small recurring donation, that will help us keep the lights on. Much appreciated. >> Yeah. And you also use almost all of your funds towards uh paying people to

program Zigg like to to develop the language. >> Yeah, we have a super high Yeah. Yeah. It’s a it’s an efficient organization. Almost all of our money goes into just paying people uh paying contributors. Yep. >> Yeah. Very nice that. >> Yeah. Yeah. Well, check out Zigg uh if you haven’t, anyone who’s listening. And uh yeah, Andrew, thanks so much for taking the time to talk to me about all these things. It’s always a pleasure. And uh yeah, looking forward to the next

time we get to chat. Thanks, Richard. Pleasure. That’s it for this one. I hope you liked it. You can find links to some of the things that we talked about in the description. And if you’d like to become a supporter of Software, please check out patreon.com/softwarunscripted. Until next time.