 I'm Dan Govan. I'm here to talk about the WASI OS and talk about isolation and communication. So the first question is, what is an OS? I typically, when I think of an OS, we'll think of things like actual operating systems. Like, we have these visual experiences where I show different screens. If you've used these machines, you probably know what these things feel like. Cause like, these represent moments in time, a particular feeling. But at the end of the day, we kind of know that the user experience on operating system isn't just the operating system itself. You can run PowerShell on Linux. You can run Bash on Windows. But when you do that, you get kind of a bit of the experience of one OS running another, but you can still sort of feel it underlying OS kind of shining through in various ways. Like, you always tell, like, okay, this is Bash, but it's running on Windows. It feels a bit different. And so what we're really gonna look for today is like, what is this underlying thing that the OS has? What is this personality? What makes an OS? What an OS is? It's not just the outside skin. It's going to be something more. And it's not just gonna be a particular implementation either. We're not gonna talk about memory management. We're not gonna talk about handleers, device drivers, schedulers, these things. Because we're not gonna talk about a single OS. We're talking about a virtual OS. Wasm is a virtual bytecode. And Wazzy, in the same way, is a virtual OS. It's not about a particular implementation. And we do this all the time. We talk about Unix. We talk about Unix as if it's like a thing. We talk about, like, Unix does this, Unix does that. But what is Unix? Unix is actually about 50 different, actually more than this. Different flavors of Unix that are all kind of simplifications of the same basic set of design principles. And so these design principles also kind of get at this, like, underlying personality of, like, what is an OS? This underlying thing that kind of ties these all together. So Unix actually was one of the very first operating systems that really came out with a clear design philosophy. So we're gonna explore that design philosophy and talk about what would that mean for Wazzy? What is Wazzy's design philosophy? Isolation and communication, to me, are two words that really define what it means to be an OS. What an OS does. And they're kind of opposite to each other. Isolation is all about taking two things and having them not talk to each other, having them be disconnected from each other so they don't conflict with each other, they don't collide. And communication is all about connecting them, having to actually work together and share data. And so we've actually been through many iterations in history of creating, of, like, taking things and making a big show of connecting them. Like, wow, things are so great, they're connected now. And now they're two connected. So we're gonna have to split them apart and put walls between them. And we're gonna poke some holes in the walls because there are too many walls. So we're gonna poke things, holes, and then we have too many holes to build more walls. And every time we do this, we add more complexity. So this kind of fundamental tension between isolation and communication is a place where we can understand how do opera systems work, but through a personality. So UNIX is one of my favorite opera systems and it really is an opera system that comes with philosophy but they actually wrote down in three pretty short words that we'll talk about later. So write a program to do one thing and do it well. When we look across, look across computing industry at all various luminaries and talk about how do we make complex systems more reliable? One of the big things that everyone always talks about is separation of concerns. We have to take things that are mixing concerns together and separate them out so we can do them one thing and one thing at a time and then we'll take these things and we'll combine them. This is the path to making modular systems and to fact making flexible systems. It's also a path that very, very closely aligns with where the component model is going because we wanna take, you know, UNIX called these things programs, we'll call these things components but we're basically talking about the same thing. We're talking about units of code that can run and be somewhat isolated from other units of code but also work together with those units of code. The next step, write programs, we'll call them components to work together. This is literally what we're doing with the component model so this is where we're going. The third step in user philosophy is like write programs that handle text because that is universal interface. And this one takes a bit more understanding because this is an easy thing to say in a time and place when text just meant ASCII and we didn't actually have things like injection of text, XSS didn't really exist yet. And so it's easy to do this at the time and we just had all these ad hoc text formats and things kind of worked. It's actually a really great way to get things up and running pretty quickly. But now I think we look at this and saying, okay, what we're trying to do here is not literally take the 70s and recreate them today. We're taking the ideas and translate them in today's terms. And so I think what we wanna do here instead of saying we're gonna handle text is look at the reason why they wanted to handle text. What was the goal behind this? What was the purpose of this? And so if we read further about UNIX history and why they wanna do this, two things that talk a lot about are loose coupling and human interactivity. So loose coupling is having two programs written by different people that didn't necessarily coordinate up front and then taking the program that they wrote and having them work together without any kind of advanced coordination or writing config files. Human interactivity is about being able to run a program and see what it does and see the output and then take that output and feed it to another program and run it and see what it does and have kind of this interactive experience. And so text worked for that in the UNIX days, but it's not the only way to solve these kind of problems. And so I think we'll translate that philosophy into the body philosophy and say, we're gonna look for the underlying ideas of loose coupling and human interactivity. Pipes in UNIX are the one thing that really crystallized this philosophy in the most obvious way. It's one of the big inventions in UNIX. And this is actually a picture of a book that I have that it's not about UNIX at all, but the authors wrote it in the 80s and they were just so excited about this new opera system called UNIX. They had this pipeline and they wrote their whole book using this command to types out the book. And they thought it was so cool they put that in the forward of the book. I think it's something we take for granted today that we have this pipeline system. We kind of just like, it's this thing, it's like this dusty old part of UNIX that's sitting around and we use it in our shell scripts and sometimes we have some dev tools that use it, but we don't really do it very much. But if we look closely at what's happening here in terms of isolation and communication, there's four different programs running here. PIC, TBL, EQN, TROP, they're all running in their own process. They're all isolated from each other and they're communicating. They're talking to each other over pipes. We didn't write any config files. There's no YAML, there's no FSTab, there's no Demons. We just said, this program, this program, this program, this program, and boom, they're all isolated and communicating. And that's really amazing. We don't get that kind of experience of like taking a piece of software and just jamming them together and having it work very often. This is a really great experience. Why don't we do this more often? What happened? Why is it that when we look at software and production in a lot of places we're not doing this kind of Unix, like write small programs that do one thing well and pipe them to each other. We don't really do that very often outside of specialized, like people writing shell scripts for things. So what happened? So let's take a quick look at, very, very brief look at computing history. And so early computers, there is no OS. When you sit down, you want to do arithmetic, you just do it on the functional units that the computer has. So you want to use memory, you just use the memory banks that the computer has. The pointer is like a physical offset into a particular memory bank or a particular device that they have. There's no virtualization. There's no access control. The machine doesn't make any attempt to hide things from you or presenting the kind of illusion because there's no reason to. It's like a dishwasher. You wouldn't use half of your dishwasher. You just use the dishwasher. There's no access control saying like, oh, you can't use this feature, or at least there shouldn't be. And this time they hadn't really had a need for it. The internet wasn't around, untrusted code wasn't around. There wasn't really any kind of concept of meeting that. So then as computers get more powerful, people start doing more things with them. They started figuring out that okay, what we're actually looking at is programs are not corporeal entities that can reach out and do things or even reach out and observe things themselves. All they can do is give instructions to a machine that will execute them on the behalf. And so we're building a machine and we can make those instructions do different things. And we can actually present an illusion to the program. We can say, you're gonna run this machine with a virtual machine. This is an S390, one of the first machines that had full virtual machine technology. It went a lot. And so now we're gonna get this illusion to programs that you still have, it's still like the good old days of like these old computers where you have the full machine access available to you. But we're gonna virtualize it so you get the illusion, the whole machine was invented just for you even though you're actually one of many things that are running on it. In security terms, we can think of this as like default allow. Everything in the machine is just given to you. And then when it comes time to virtualize it, the virtualization needs to include the everything. So it's a very big virtualization. This general pattern has repeated many times in computing history several different times. And here's another one. PDP11 is one of the most diverse machines that Unix read on. And so Unix had access control. It had, it said no sometimes, but it only said no when it really needed to. It had multiple users. And so it had ways to say no when one user would try to cover another user or if user tried to corrupt the actual operating system. But in every other situation, whenever it could, it said yes. It said files are readable by default. It said you can look at the list of processes that are running. You can look at the system configuration file. Everything is just kind of exposed and out in the open. We're gonna let everything happen. And then when it came time to put things in boxes, we had to make some very big boxes to contain it all because the boxes had to present this very large eludum. So we put this in containers. And now we have this thing called CloudNative where we put things in containers and we say, okay, inside the container, you can pretend like it's in the 70s or maybe it's like the 80s. Or sometimes the 90s though, but still like we can go back in time and pretend the cloud didn't happen and call that CloudNative. So the general pattern here is we have a default allow system. A system that in many cases in the case of both Unix and the older computers that didn't have operating systems. So they say it's default allow. We're gonna give you everything. And then later on we figure out like, oh, now we're gonna virtualize this thing. And that means we need to present a very big elusion to the program. This elusion has a lot of surface area, a lot of features, a lot of things that need to be presented just as if you were the only person in the world running on this computer built just for you. So what did Unix pipes have that was so special? So if you look at this command line, we have program A, pipe to program B, pipe to program C, pipe to program B. They're running by themselves and they're reading this thing called standard in and writing to this thing called standard out. They're running in their own processes which are kind of like these little universes created just themselves. But there's a catch. There's like this wormhole, this portal to another world called studio. And it comes in and says like, even though you're in your own world and inside your own world, there are no other programs there. You can scan through the entire address space of the program. You won't see anything else. How are you gonna talk to someone else? And the way you can talk to someone else is by having this thing that comes in from the outside somehow, you don't really even know where it's talking to. You don't really know where it goes. We don't have to know where it goes. We didn't name the destination where it's gonna point to. It's just gonna be this thing that would say like this is a source of bytes and a sync that you can write bytes to. And that's all you need to know. And when we do this, we get very close to a do one thing and do it well situation because literally the program doesn't have to parse command line options and then try to open files and deal with errors from that. Says no, all I have to do is start up and immediately I'm gonna be reading bytes from standard in and writing both to standard out. That's the most pure kind of version of do one thing and do it well. Wasi is going in a direction that works in a structural way very similar to the way that studio works. So Unix program, we have students that'll sit there like the three standard file descriptors, zero, one and two. And even though it's not encoded in the binary, it's understood in Unix that all executables will have these file descriptors. So that someone on the outside knows when you're running a Unix program, it knows that you can pass in these things and the program will accept them and have them work. In Wasi, we have something similar. We have declarative ports and declared exports. So we know, we actually have an encoding for this. We have the import say, this is what I need you to provide me. And the export say, it's what I'm gonna provide you. And it's very similar to standard out standard out standard error because what we're saying is not, here is the name of a file or here's the name of a service or here's the name of something I'm gonna connect to. It's saying, here is a thing, a name, a thing that I want. And it's gonna be contextually interpreted. The import strings are gonna be things like, maybe it literally is that didn't sit out or maybe it's like, I need a socket. You're gonna pass me a socket or other things. These are my imports and they're typed. So I can request, I want this thing to come in and I have this interface I want it to follow. But I don't need to know where it comes from. I don't need to be globally resolved on some global symbol table. I can just have it resolved according to the context you're gonna run me in. So like in our command line options, we have like A pipe to B pipe to C, the shell is the thing that's doing the linking of like tying together one process by another. It's a very composable interface because you can just there and just like plug things in. And you don't have to worry about the entire system, how the entire global namespace works. You can just focus on one piece at a time because the linking is very local. Why does it import to the next port to work the same way? Another feature we have in Wasm, that if you wanna understand how Wasm differs from, for example, Unix has to do all this. There's like three features in Wasm that I always talk about that people only need to understand really make the difference by why Wasm puts us on this different path from Unix. And those features are the trusted stack, the trusted stack and oops, I mean the trusted stack, this is it. The trusted stack is the thing that makes the whole difference. What's the trusted stack all about? The trusted stack is about, why do exception handling, tail calls, stack twitching, even garbage collection, why are these things all language features instead of just being like features that producer tool chains implement, like you would in any of executables. And maybe somebody would have different forms of these if we didn't have the trusted stack, but the trusted stack is a significant part of the reason why all these features work the way they do, or will work as some of these are future features. The trusted stack is also related to the reason why functions have types. We don't have colleague inventions where you say like, oh, I'm just gonna, put some arguments in certain registers and maybe certain member locations and then jump to a certain address. And the other side will just like, look for arguments in certain registers and certain member locations. As long as my call and convention matches your call and convention, then it's all gonna work up and it's gonna work good. We have a call instruction, and we have a stack that's outside the address space. And so we get that functions have to have types. We have to know what the arguments are, so we can pass them. And this leads to a place where function calls can call of imports. You don't have to call something that's in your address space. We don't have to worry about jumping. I can only jump to things that are in my address space and the thing that I'm calling has to be in my address space because it has to see the same memory that I throw the stack arguments on so I can see things. Like we don't have all those problems, those don't exist. So at the same time where like calls being the fundamental way that we connect code in Unix starts from a place of fundamentally needing to be in the same address space. In Wazzy, it starts from a place where it fundamentally can cross trust boundaries. So the caller and the colleague don't have to trust each other. We start from that. That's like our ground zero. And we've built up a whole system on top of this. It looks somewhat different than Unix, but we can still preserve the same underlying design principles. Virtualization is another part of the story. If we look at virtualization in a popular Unix West like Linux, there's at least six different ways I could think of for how you can, and people in practice do all these different things. To virtualize system calls, we use ptrace, intercept system calls, set comps, sandboxes are a common thing. LD preload to replace things in libc. We do all the different things. These are all different mechanisms that Linux provides that can all be ways to replace a system call through something else. In Waze, we just have this thing called linking that can just do interposition because we have linking that's not based on the idea of a global civil table and our global knowledge of who I'm talking to, but contextual resolution. So an import just says, I need this interface and whoever's wiring me up, like the command line shell, typing A to B to C, just wiring things up locally, we can do the same thing and just wire up, I need a read, I need a socket I can read from, I need a stream I can read from and anyone can provide that to me. It doesn't have to be a particular address or a particular file name. So don't sit there, an old studio system is also limited by being just about byte streams. Bite streams are kind of like the one way we can use to cross across these trust boundaries. We have our process and the process takes all the all the mechanisms we have for communicating with interprocess, whether it's loads and stores or calls are kind of all limited to being within the process. When you go outside the process, we switch to this thing called IPC, interprocess communication. It's a whole different world that we communicate out. And so we have to talk about these very course and dynamically type things like byte streams. But in Waze, we have the ability to cross trust boundaries with just things like calls. And so we can start with a module that has declared imports and exports, and we can start linking it up and build up graphs of systems that are all interconnected and mutually untrusted parts. Using the same local system that made pipes disposable. So one last thing, we translate from the 70s where everything was like default allow because they hadn't invented the internet yet. And security wasn't really a thing, but the auto code in the same way that we tried to go today. And so we have a default deny platform because we think, you know, today we're gonna say that we can use principle as we have authority. We're gonna say, we're not gonna give you the entire world of just like, you can read whatever files you want in the system. You can look at whatever, you know, stuff that's going on. We're gonna say, we're gonna give you just the things you need. And then when we virtualize that, we get small illusions. We don't have to virtualize as much. There's much less stuff that we have to present to you to make sure that you can run in a virtualized way. So these ideas are not fundamentally new. Some of the OS's out there, Mirage OS is an OS that does many of these same things. It actually uses OCaml instead of Wazem, but it's doing a lot of these same kind of things. Copsid OS is another example of this. If people are interested in checking out more examples of other OS's that have gone to similar places. So let's really quickly look at some Wazi OS in practice. Handles and resources are one of the really key ingredients we have that it's also one of the things that I've gotten a lot of questions about so far at this conference. I wanted to briefly touch on it. So we sometimes call these flying grain resources. Handles and resources in terms of Unix are like file descriptors. So like a handle, a dynamic thing, put into a resource, which is like a bit of stream readable state. We sometimes talk about the component model as like we have shared nothing linking, and it's kind of true, but it's also kind of true that we have, we do have shared readable state in the form of resources with handles. And really the only difference between that and passing around pointers is that with resources, we have defined lifetime semantics, so we can protect you against either free problems. And we have a way to have, all access to the shared readable state has to go through an API, through function calls, which can then be virtualized. So we're not doing raw loads in stores, it would require someone to intercept loads in stores if you wanted to put something in the middle. So we have shared readable state, we just need to make sure we go through the proper forms and we can always virtualize it. And then in practice, we often inline those, we'll hopefully in the future, we'll have VMs that can inline these kind of things. We can get efficient code accessing shared readable state. Another key part of the vocabulary for how what it's like to talk to each other is streams. Streams are much like pipes, and this is not a coincidence when I talk about these things. Streams are a pretty fundamental abstraction in lots of different systems. And we'll talk really briefly about distributed systems. This is not a talk about distributed systems, but I wanna take two things about it. One of the easiest ways to build a distributed system is to not build a distributed system. And so one of the ways to do that is not a new idea, move the code to the data, right? Code is usually smaller than data, turns out data is big sometimes. And code is also immutable, data is often immutable. So we can more easily move the code around. And this is especially relevant for WASM, because WASM components, because they have such a limited view of the world, can be embedded inside many different things. People are experimenting with putting WASM inside of databases, and doing all kinds of different things, including WASM in the place that's very close to the data. And when you do that, it means that WASM code doesn't have to think about it, like what if the network goes down? Or what happens if there's some other node and there's some conflict with the data, like all the different things? If you're there inside the data, you can have a lot more access to that. It's a lot easier to solve those problems instead of like bubbling it back up into the application business logic. But that doesn't always work. So your second strategy to avoid a distributed system is to stream the data through the code. To write components that can read data through a stream, and then write the output to a stream. Because when we do this, we give the surrounding system a lot of visibility into how this component will consume data and produce data. It's not the component saying like, I have built it within me some logic that's gonna randomly reach out. It's like, I want this, I want this, I want this, I want this, randomly. It's like, no, I'm consuming a stream, and I'm telling the system with my type system, like I'm consuming a stream. You can see that. And it can intelligently schedule me and put me in a larger context of other programs that are all working together. This is also how we can set up things like the pipeline experience, having multiple things that come close together. If we all know that these components take stream and output and stream and output, we can get back to the same pipe experience of a person sitting in the command line writing A, vertical bar, B, vertical bar, C to pipe things together. Because we'll know the shell, in a situation of hypothetical, wazzy aware shell, would know that you're running things that have stream and input stream output, you can just wire them up. Another key thing about streams that we work on to really make them be the glue that connect things together, are they blocking or are they non-blocking? In Unix and POSIX, streams actually have a mode. They can actually be in blocking mode or non-blocking mode. And this means it's actually kind of awkward because it means when you pass a stream to someone that's not expecting a blocking stream, if you pass a blocking stream to someone that's not expecting it, or pass a non-blocking stream that's not expecting it, you'll get errors. And so wazzy, we're only trying to get to the point where they're just like one kind of stream and not have to worry about like which kind of stream do you have? And so we have different functions. If you want to, if you, the stream is just a stream, there's no blocking mode or non-blocking mode. So if you call the blocking write function, you will block. If you call the write function and the other function associated with doing async, then you'll have non-blocking IEL. And so it's up to the call site, up to the caller to decide if I want to use the stream in a blocking or non-blocking fashion, which means that the stream is just a stream. There are no modes, just a pure stream, and we can access this thing either in a blocking fashion or in a blocking or blocking fashion according to the needs of our program. So where do we use streams in practice? In wazzy sockets, we have a couple of streams. Connect is a function that will reach out to an IP address and make a connection there. And wazzy streams are unidirectional, so you'll get two streams. You'll get an input stream and an output stream, which will correspond to the reading end or the writing end of that socket. And then there's also the accept function in wazzy sockets, which is the corresponding, like, a connection's coming in, I'm gonna accept it. And when I accept it, I also get two streams, input and output, talk to that socket. We're also using streams in wazzy HTTP. So this is a screenshot of the width. There's four different functions here, and kind of like both combinations of incoming and outgoing, request and response. And so we have streams here. And what the streams are doing is they're talking to the body. So the body of HTTP method is can be big, if we wanna stream it. And so these are the functions that will give you the stream you can then read from and write to. So once you get the stream, you can read to it or write from it, depending on which kind of stream it is in the appropriate way. So the stream can allow you to deal with a large body without having to copy the entire body into memory all at once. But in fact, in many cases of all the HTTP, the body might not even have arrived yet. It might be like coming over the network. And you can start processing that early by today because you're streaming. That's possible because the system around you understands the data access pattern. So looking forward, we have these types in wazzy today using resources called input stream and output stream. This is the preview two answer for streams. It's fairly simplistic. We already know that this is not gonna be sufficient for a long time. So preview three of wazzy is coming along and we're looking forward to adding new types to the width language, which will have streams as just a regular type in the language. You could pass a stream in or something. And so I think of this as like the IOU ring of wazzy. In fact, that's kind of a somewhat leading phrase and deliberately because the streams in preview three look a lot like IOU ring. And hopefully we can implement that on a wide ring as well. So that's kind of a quick peek at where we're going. Also, another advantage of building streams in as a type is that we can go beyond just streams of bytes. And we can have streams of arbitrary type objects, streams of T, which it's marginally cool. We can send our own streams of like records of data, but it starts to get really interesting when you talk about sending around streams of handles, where you're gonna get a stream of things coming in and each of these handles is then potentially references to things that will be able to access. So there's lots of cool things we can do when we put stream in the language. All right, to wrap it all up, Unix philosophy, write programs that do one thing and do it well. While we're gonna do that, we're gonna write components and write programs that work together. While we're gonna do that, we're gonna use the imports and exports, which are contextually resolved. We're gonna connect things with streams and we're gonna write programs that support loose coupling. You can write programs with streams and any program that uses a stream, input, stream output can connect with each other in even more so than in Unix because we have solved this blocking, non-blocking discrepancy that we just like. Any input stream can be connected to any output stream. Not on the same stack today. That'll require actual async, that'll require preview three, but that's where we're going. In interactivity, I didn't actually say much about this. There's a lot more we can say about this, but when you have explicit types for things, especially when you talk about type streams, but then also before that with typed imports with functions having signatures, it means that when you call function, it's not like the VM has enough information about the values flowing in, the values flowing out, that it can format those things and present a human readable kind of presentation of what's going on with components. So instead of saying that the component itself is gonna do all the work of parsing and formatting things to spit out the byte stream, we're gonna say the program itself can error operate with a typed form and the VM has enough integration to turn that type form into a human readable byte stream if we want that. That also means that instead of just byte stream, we can also translate component input output into things like GUIs or HTML forms or other kinds of things. All right, and so completely wrap it all up. Isolation communication. Default deny leads to small illusions, leads to lightweight, leads to scalability. Contextual name resolution, being able to say we don't have a single global name space we're gonna resolve things in, we're gonna allow things to be wired up locally without considering the whole system as to how we get the causability. And stream abstractions are just awesome. That's the bossy OS. All right, are there any questions we'll have on here? Yes, go ahead. I know of some projects that will automatically generate JSON from component model types and that's moderately human readable. I've seen some people poke around with experiments of there's a pretty natural syntax that kind of falls out if you look at WITS, the kind of the WITS syntax, you kind of think both numbers, if you just like render them in the obvious way and like lists, you put them in brackets or something and you kind of do a pretty obvious rendering. You can get pretty far with decisions that shouldn't be surprising. And so, I don't know about any actual spec for this or implementation of this, but I and others have definitely napkin designed basically that. And so I'm expecting that as we push forward with more people interacting with the component model directly, we're gonna build some of those tools of like taking the WITS syntax and kind of extrapolating from like, we start here with this kind of general syntactic form and said what would values look like in this language? And there's a pretty obvious answer. Kind of like, there's a pretty obvious syntax that kind of emerges from that I think. So that's my answer there, it's like stuff's coming. All right, any other questions? All right, thanks a lot.