1 00:00:02,490 --> 00:00:06,249 [Music] 2 00:00:09,920 --> 00:00:13,400 Hello, PyCon. 3 00:00:14,400 --> 00:00:18,400 Welcome. Welcome back to another round 4 00:00:16,240 --> 00:00:20,720 of PyCon sessions. I hope you've all had 5 00:00:18,400 --> 00:00:24,480 a good break and that you're ready for 6 00:00:20,720 --> 00:00:26,720 another block of amazing talks. I do 7 00:00:24,480 --> 00:00:29,119 have a programming note here um which is 8 00:00:26,720 --> 00:00:31,199 that Tessa Bradbury who was going to be 9 00:00:29,119 --> 00:00:34,880 talking about open telemetry here 10 00:00:31,199 --> 00:00:38,160 unfortunately could not make it um and 11 00:00:34,880 --> 00:00:40,879 luckily Noah was able to step in and 12 00:00:38,160 --> 00:00:43,360 he's going to talk about the long hello 13 00:00:40,879 --> 00:00:48,280 world now give him a bigger round of 14 00:00:43,360 --> 00:00:48,280 applause and then we'll get going 15 00:00:56,640 --> 00:01:00,719 All right. 16 00:00:58,239 --> 00:01:03,440 As I said, uh I am Noah Canrretz. I'm an 17 00:01:00,719 --> 00:01:05,600 SR at Geomagical Labs, uh which is a 18 00:01:03,440 --> 00:01:07,200 part of IKEA. I do computer vision and 19 00:01:05,600 --> 00:01:09,600 augmented reality stuff for IKEA, but 20 00:01:07,200 --> 00:01:11,760 I'm not here to talk about that today. 21 00:01:09,600 --> 00:01:13,280 Hello World is a starting point. Now, 22 00:01:11,760 --> 00:01:15,600 most of the time it's a starting point 23 00:01:13,280 --> 00:01:17,600 for talking about functions and strings 24 00:01:15,600 --> 00:01:18,720 and all of programming, but we're going 25 00:01:17,600 --> 00:01:21,200 to use it as a starting point for 26 00:01:18,720 --> 00:01:23,759 something else. Rather than looking 27 00:01:21,200 --> 00:01:25,200 outward, we're going to look at uh the 28 00:01:23,759 --> 00:01:27,119 stack below this. We're going to look 29 00:01:25,200 --> 00:01:30,159 inward at all of the moving pieces that 30 00:01:27,119 --> 00:01:31,680 make up this innocuous line of code. 31 00:01:30,159 --> 00:01:33,040 To set the stage a bit, we're going to 32 00:01:31,680 --> 00:01:35,200 look at the specific case of Linux and 33 00:01:33,040 --> 00:01:36,799 Gibb C. Python runs on many other 34 00:01:35,200 --> 00:01:38,240 platforms and combinations, but we want 35 00:01:36,799 --> 00:01:40,560 to look at specifics, so we've got to 36 00:01:38,240 --> 00:01:42,799 pick one. All code samples are correct 37 00:01:40,560 --> 00:01:44,240 as of writing in September 2025. If 38 00:01:42,799 --> 00:01:47,840 you're watching this in a video in the 39 00:01:44,240 --> 00:01:49,600 future, please check the repositories. 40 00:01:47,840 --> 00:01:51,200 And our point of departure, we are in a 41 00:01:49,600 --> 00:01:53,119 Python interactive shell. We have typed 42 00:01:51,200 --> 00:01:55,600 in print hello world and we have just 43 00:01:53,119 --> 00:01:57,439 pressed enter. What happens next? Not 44 00:01:55,600 --> 00:02:00,000 saying it prints hello world. What 45 00:01:57,439 --> 00:02:02,399 really happens next? This is our first 46 00:02:00,000 --> 00:02:04,079 stop. Slightly simplified. The heart of 47 00:02:02,399 --> 00:02:06,399 the interactive interpreter is going to 48 00:02:04,079 --> 00:02:07,920 be a while loop that it's calling input 49 00:02:06,399 --> 00:02:09,200 gets our line of code which in this case 50 00:02:07,920 --> 00:02:11,280 is going to be print hello world and 51 00:02:09,200 --> 00:02:14,160 then it does something with it. In this 52 00:02:11,280 --> 00:02:15,440 case calling self.push. 53 00:02:14,160 --> 00:02:16,640 Every one of these layers is going to 54 00:02:15,440 --> 00:02:18,080 involve a lot of calls. So, we're going 55 00:02:16,640 --> 00:02:19,680 to skip around a little bit just this 56 00:02:18,080 --> 00:02:21,599 doesn't take hours. But following down 57 00:02:19,680 --> 00:02:23,520 the call stack from that push method, 58 00:02:21,599 --> 00:02:26,160 here's the part we care about. We have 59 00:02:23,520 --> 00:02:28,239 the code and we're going to call the xc 60 00:02:26,160 --> 00:02:30,720 builtin on it. This is going to parse 61 00:02:28,239 --> 00:02:33,360 and execute our line of code. What does 62 00:02:30,720 --> 00:02:35,280 that mean? It's theoretically possible 63 00:02:33,360 --> 00:02:37,360 to build a language which just executes 64 00:02:35,280 --> 00:02:39,440 code one bite at a time. But this makes 65 00:02:37,360 --> 00:02:41,440 any kind of complex syntax really hard 66 00:02:39,440 --> 00:02:43,120 to implement. So basically all major 67 00:02:41,440 --> 00:02:46,239 languages go through a number of steps 68 00:02:43,120 --> 00:02:47,920 to add additional uh information to the 69 00:02:46,239 --> 00:02:49,599 code as it goes through the processing. 70 00:02:47,920 --> 00:02:51,040 Decoding bytes to unic code characters 71 00:02:49,599 --> 00:02:52,720 technically happens first but that's 72 00:02:51,040 --> 00:02:54,000 been covered many times in many places. 73 00:02:52,720 --> 00:02:56,800 So let's look at the other steps in a 74 00:02:54,000 --> 00:02:58,000 little more detail. Lexing or tokenizing 75 00:02:56,800 --> 00:03:00,000 which are the same thing just different 76 00:02:58,000 --> 00:03:01,840 words. That's the step that goes from a 77 00:03:00,000 --> 00:03:04,080 sequence of characters to a sequence of 78 00:03:01,840 --> 00:03:05,920 tokens. We'll talk about tokens. And 79 00:03:04,080 --> 00:03:08,560 then parsing turns a list of tokens into 80 00:03:05,920 --> 00:03:10,159 a syntax tree. 81 00:03:08,560 --> 00:03:12,640 To reach the lecture, we need to jump a 82 00:03:10,159 --> 00:03:14,480 ways down the stack. Uh, and in 83 00:03:12,640 --> 00:03:15,920 practical terms, the lecture is actually 84 00:03:14,480 --> 00:03:18,080 more like a Python generator. Although 85 00:03:15,920 --> 00:03:20,640 it is written in C, it produces tokens 86 00:03:18,080 --> 00:03:22,640 on demand as the parser needs it. But to 87 00:03:20,640 --> 00:03:24,159 get normal mode is the bulk of the 88 00:03:22,640 --> 00:03:25,519 logic. So what's this going to do? It's 89 00:03:24,159 --> 00:03:28,000 going to go through each character one 90 00:03:25,519 --> 00:03:30,159 at a time, group them together, and tag 91 00:03:28,000 --> 00:03:32,720 them with this is the kind of thing that 92 00:03:30,159 --> 00:03:34,239 this is. The token module in the 93 00:03:32,720 --> 00:03:35,920 standard library exposes all of the 94 00:03:34,239 --> 00:03:38,000 available token types. So we can see a 95 00:03:35,920 --> 00:03:39,840 few kinds of patterns here. So the first 96 00:03:38,000 --> 00:03:41,440 are names. This is anything that looks 97 00:03:39,840 --> 00:03:44,239 like an identifier, function names, 98 00:03:41,440 --> 00:03:46,239 variable names, class names. Uh numbers 99 00:03:44,239 --> 00:03:48,000 and strings. Those are just literals. 100 00:03:46,239 --> 00:03:50,720 And then syntactically significant 101 00:03:48,000 --> 00:03:53,599 punctuation, parentheses, colons, the 102 00:03:50,720 --> 00:03:55,440 plus sign. The lex also emits indent and 103 00:03:53,599 --> 00:03:57,120 ddent tokens so the parser doesn't have 104 00:03:55,440 --> 00:03:58,879 to care about tabs versus spaces. And 105 00:03:57,120 --> 00:04:00,239 this is where line continuations happen 106 00:03:58,879 --> 00:04:02,319 again so the parser doesn't have to 107 00:04:00,239 --> 00:04:04,159 worry about that. 108 00:04:02,319 --> 00:04:05,760 The tokenize module lets us experiment 109 00:04:04,159 --> 00:04:07,439 with alexer and see the resulting token 110 00:04:05,760 --> 00:04:09,519 list on the command line. So let's feed 111 00:04:07,439 --> 00:04:11,519 it our line of code. We end up with six 112 00:04:09,519 --> 00:04:13,519 tokens. So the first is a name token 113 00:04:11,519 --> 00:04:16,560 which matches up with the characters P R 114 00:04:13,519 --> 00:04:18,560 I N and T. Then a left PN token with the 115 00:04:16,560 --> 00:04:20,720 left PN character. Then our string 116 00:04:18,560 --> 00:04:22,639 literal token, a right PN, a new line, 117 00:04:20,720 --> 00:04:24,240 and an end of input token, which is just 118 00:04:22,639 --> 00:04:26,800 an opaque way of saying you have run out 119 00:04:24,240 --> 00:04:28,000 of input. This is more structured than 120 00:04:26,800 --> 00:04:29,280 our original string. We know a little 121 00:04:28,000 --> 00:04:31,199 bit more about what's going on here, but 122 00:04:29,280 --> 00:04:34,320 this isn't really ready to execute as 123 00:04:31,199 --> 00:04:35,759 code. We've still got parsing to go. 124 00:04:34,320 --> 00:04:36,800 So far the representations we've been 125 00:04:35,759 --> 00:04:38,160 talking about have been linear. A 126 00:04:36,800 --> 00:04:39,919 sequence of bytes, then a sequence of 127 00:04:38,160 --> 00:04:41,360 characters, then a sequence of tokens. 128 00:04:39,919 --> 00:04:43,600 But when we think about complex code, 129 00:04:41,360 --> 00:04:45,360 it's not really flat. We would think 130 00:04:43,600 --> 00:04:47,360 about the body of a function being 131 00:04:45,360 --> 00:04:48,800 inside the function definition or the 132 00:04:47,360 --> 00:04:51,440 parameters for a function call are 133 00:04:48,800 --> 00:04:54,479 inside the function call. An abstract 134 00:04:51,440 --> 00:04:56,800 syntax tree extends this concept. So we 135 00:04:54,479 --> 00:05:00,240 want to build it up as a recursive tree 136 00:04:56,800 --> 00:05:01,919 of all of the syntax in our code. 137 00:05:00,240 --> 00:05:04,479 Python's parser is built around a 138 00:05:01,919 --> 00:05:08,000 parsing expression grammar or peg input. 139 00:05:04,479 --> 00:05:10,240 This file python.gg uh it has all of the 140 00:05:08,000 --> 00:05:12,639 different syntax of Python in terms of 141 00:05:10,240 --> 00:05:14,080 the tokens we saw before. Now parsing is 142 00:05:12,639 --> 00:05:15,680 an incredibly dense topic. This is going 143 00:05:14,080 --> 00:05:18,160 to be a surface level read on all of 144 00:05:15,680 --> 00:05:20,880 these pieces. So the short version is 145 00:05:18,160 --> 00:05:23,120 that we want to match them from left to 146 00:05:20,880 --> 00:05:24,880 right in a particular order. Each RAM 147 00:05:23,120 --> 00:05:26,639 rule will then say what type of syntax 148 00:05:24,880 --> 00:05:28,080 node to create and has a little bit of 149 00:05:26,639 --> 00:05:30,240 extra information about how to recurse 150 00:05:28,080 --> 00:05:32,800 down into the next piece of syntax that 151 00:05:30,240 --> 00:05:35,199 it should be parsing for. In the end, we 152 00:05:32,800 --> 00:05:38,639 end up with this tree structure that is 153 00:05:35,199 --> 00:05:40,160 our abstract syntax tree for the code. 154 00:05:38,639 --> 00:05:41,759 As with the tokenizer before it, we have 155 00:05:40,160 --> 00:05:44,000 a command line module we can use to play 156 00:05:41,759 --> 00:05:46,960 around here. So, we pass our code into 157 00:05:44,000 --> 00:05:49,360 the a module. We end up with a module 158 00:05:46,960 --> 00:05:51,919 containing a single expression which is 159 00:05:49,360 --> 00:05:54,560 a call with a name of print and a single 160 00:05:51,919 --> 00:05:56,639 argument with a constant of hello world. 161 00:05:54,560 --> 00:05:58,479 Now this is as structured as Python code 162 00:05:56,639 --> 00:06:01,360 gets and it's ready to march forward on 163 00:05:58,479 --> 00:06:02,720 its journey towards running. 164 00:06:01,360 --> 00:06:04,160 Many words have been said over the years 165 00:06:02,720 --> 00:06:06,560 about how Python is or isn't an 166 00:06:04,160 --> 00:06:08,639 interpreted language. And obviously I've 167 00:06:06,560 --> 00:06:10,400 lost interest at this point. But we can 168 00:06:08,639 --> 00:06:12,240 show very clearly here that Python is a 169 00:06:10,400 --> 00:06:14,800 compiled language because our next stop 170 00:06:12,240 --> 00:06:16,960 is the Python compiler. And before we 171 00:06:14,800 --> 00:06:18,800 can talk about compiling Python, what is 172 00:06:16,960 --> 00:06:20,800 that compiler for? Now, when we compile 173 00:06:18,800 --> 00:06:22,479 CC code, we are emitting CPU 174 00:06:20,800 --> 00:06:24,560 instructions for the physical hardware 175 00:06:22,479 --> 00:06:26,240 in your machine. 176 00:06:24,560 --> 00:06:27,440 CPython has what's called a virtual 177 00:06:26,240 --> 00:06:28,880 machine. Now, this is different than a 178 00:06:27,440 --> 00:06:31,039 virtual machine like running Windows on 179 00:06:28,880 --> 00:06:34,160 your Mac laptop, but it's the same idea 180 00:06:31,039 --> 00:06:36,400 of being a very fancy fake computer that 181 00:06:34,160 --> 00:06:38,479 we can write instructions for. The core 182 00:06:36,400 --> 00:06:40,639 of the CPython virtual machine is the 183 00:06:38,479 --> 00:06:42,720 stack. If you've worked with fourth or 184 00:06:40,639 --> 00:06:44,560 an RPN calculator before, you are one, 185 00:06:42,720 --> 00:06:47,680 very old, but two, this might look 186 00:06:44,560 --> 00:06:49,360 familiar. Uh, in a stackbased system, 187 00:06:47,680 --> 00:06:50,560 basically every operation is one of 188 00:06:49,360 --> 00:06:52,080 three things. You're either pushing 189 00:06:50,560 --> 00:06:53,440 something onto the stack, you are 190 00:06:52,080 --> 00:06:54,720 operating on the top of the stack, or 191 00:06:53,440 --> 00:06:57,280 you are popping something from the 192 00:06:54,720 --> 00:06:59,520 stack. CPython does track some 193 00:06:57,280 --> 00:07:00,960 additional things, call frame, metadata, 194 00:06:59,520 --> 00:07:04,240 thread state, stuff like that. But the 195 00:07:00,960 --> 00:07:05,919 heart of it is really the stack. 196 00:07:04,240 --> 00:07:07,759 And because our virtual machine can be 197 00:07:05,919 --> 00:07:09,680 way more complicated than any physical 198 00:07:07,759 --> 00:07:11,199 computer we could build, our low-level 199 00:07:09,680 --> 00:07:13,199 instructions can be pretty specific to 200 00:07:11,199 --> 00:07:15,280 Python's patterns. They're called op 201 00:07:13,199 --> 00:07:17,360 codes, and they are the building blocks 202 00:07:15,280 --> 00:07:19,680 of all of Python. The disc module 203 00:07:17,360 --> 00:07:21,199 documentation lists all of them. Uh, and 204 00:07:19,680 --> 00:07:23,360 here's a few to set the tone. You can 205 00:07:21,199 --> 00:07:25,919 see they vary widely in complexity from 206 00:07:23,360 --> 00:07:28,160 that simple push pop to as complicated 207 00:07:25,919 --> 00:07:29,759 as create a generator object. That is 208 00:07:28,160 --> 00:07:33,120 one assembly instruction as far as 209 00:07:29,759 --> 00:07:34,800 Python is concerned. Uh, new op codes 210 00:07:33,120 --> 00:07:36,240 get added as new features are added, old 211 00:07:34,800 --> 00:07:37,759 ones are removed, and any given time 212 00:07:36,240 --> 00:07:39,759 there's usually about a 100 Python op 213 00:07:37,759 --> 00:07:42,080 codes. 214 00:07:39,759 --> 00:07:44,479 Okay, so we're back into run mod in 215 00:07:42,080 --> 00:07:45,919 Python Run.c. And we want to call down 216 00:07:44,479 --> 00:07:47,360 into the compiler. Again, this is just 217 00:07:45,919 --> 00:07:49,919 showing the call stack from top to 218 00:07:47,360 --> 00:07:50,960 bottom. Uh, the core of the codegen 219 00:07:49,919 --> 00:07:52,880 system, which is the heart of the 220 00:07:50,960 --> 00:07:54,639 compiler, is a visitor pattern. So, it's 221 00:07:52,880 --> 00:07:56,800 going to walk down the tree, and it has 222 00:07:54,639 --> 00:07:59,440 a callback for each type of syntax node 223 00:07:56,800 --> 00:08:01,599 that the parser can emit. With each of 224 00:07:59,440 --> 00:08:03,840 those nodes, it's going to create zero 225 00:08:01,599 --> 00:08:05,280 or more op codes and push them into a 226 00:08:03,840 --> 00:08:06,639 code object that'll eventually get 227 00:08:05,280 --> 00:08:08,000 bundled into a function which will get 228 00:08:06,639 --> 00:08:10,319 bundled into something else like a 229 00:08:08,000 --> 00:08:11,759 module. 230 00:08:10,319 --> 00:08:13,280 Again, the compiler could be a whole 231 00:08:11,759 --> 00:08:15,440 talk, but let's just look at our bite 232 00:08:13,280 --> 00:08:17,120 code for our hello world. Again, the 233 00:08:15,440 --> 00:08:18,479 disc module has a command line interface 234 00:08:17,120 --> 00:08:20,240 just like the other two so that we can 235 00:08:18,479 --> 00:08:22,479 pass our code in and see what bite code 236 00:08:20,240 --> 00:08:24,960 we get out. So, we end up with these op 237 00:08:22,479 --> 00:08:27,360 codes. Load name looking for something 238 00:08:24,960 --> 00:08:29,280 called print. Then a pushnull. This is 239 00:08:27,360 --> 00:08:30,960 new in 313 because the calling 240 00:08:29,280 --> 00:08:32,399 convention changed. All that is is 241 00:08:30,960 --> 00:08:34,640 saying Python doesn't actually take a 242 00:08:32,399 --> 00:08:37,440 self argument. So we just give it none 243 00:08:34,640 --> 00:08:39,599 instead. Uh then we load our hello world 244 00:08:37,440 --> 00:08:42,320 constant string. Then we call the 245 00:08:39,599 --> 00:08:44,159 function print. Uh we ignore the return 246 00:08:42,320 --> 00:08:46,000 value because we don't care if print 247 00:08:44,159 --> 00:08:47,360 fails. And then we return none because 248 00:08:46,000 --> 00:08:49,600 everything in Python has to return 249 00:08:47,360 --> 00:08:52,080 something. All right. Finally, this is a 250 00:08:49,600 --> 00:08:54,320 program. We can actually run this. 251 00:08:52,080 --> 00:08:56,000 So, we're back in runmod, but we're 252 00:08:54,320 --> 00:08:57,839 going to descend down a different piece 253 00:08:56,000 --> 00:08:59,519 of the code tree. Uh, we're looking for 254 00:08:57,839 --> 00:09:01,920 the part of Python that actually runs 255 00:08:59,519 --> 00:09:04,480 this stuff. This leads us to the 256 00:09:01,920 --> 00:09:06,560 function pie eval eval frame default, 257 00:09:04,480 --> 00:09:09,279 which is possibly the most complicated C 258 00:09:06,560 --> 00:09:11,600 function in all of Python. This function 259 00:09:09,279 --> 00:09:13,680 and its friends unpack each compiled op 260 00:09:11,600 --> 00:09:16,080 code and perform whatever computation it 261 00:09:13,680 --> 00:09:17,760 requires. So, as an example, here's a 262 00:09:16,080 --> 00:09:19,600 little bit of how the load name op code 263 00:09:17,760 --> 00:09:21,279 is implemented. We've got a little bit 264 00:09:19,600 --> 00:09:22,720 of weird stuff with stack things, but 265 00:09:21,279 --> 00:09:25,760 the core of it is the bottom three 266 00:09:22,720 --> 00:09:27,200 lines. We call pi eval loadname to get 267 00:09:25,760 --> 00:09:31,240 some kind of object and then we push 268 00:09:27,200 --> 00:09:31,240 that object into the stack. 269 00:09:32,000 --> 00:09:35,279 This is also where the JIT happens and I 270 00:09:33,839 --> 00:09:37,120 wanted to show some code samples but 271 00:09:35,279 --> 00:09:38,560 honestly the JIT is spread out over a 272 00:09:37,120 --> 00:09:40,640 million places and there isn't really 273 00:09:38,560 --> 00:09:43,040 one thing to show you. So the really 274 00:09:40,640 --> 00:09:45,839 quick version, whenever a jump backwards 275 00:09:43,040 --> 00:09:47,519 op code is executed, the exeutor will 276 00:09:45,839 --> 00:09:50,320 look to see if it is part of a 277 00:09:47,519 --> 00:09:53,279 frequently used loop. If it is, it will 278 00:09:50,320 --> 00:09:55,120 pause execution, cop snip out all of the 279 00:09:53,279 --> 00:09:57,440 op codes that are part of that loop, 280 00:09:55,120 --> 00:09:59,680 convert them from Python op codes into 281 00:09:57,440 --> 00:10:01,279 CPU assembly for your specific platform, 282 00:09:59,680 --> 00:10:02,880 and then paste them back in where they 283 00:10:01,279 --> 00:10:06,240 came from. This is extremely 284 00:10:02,880 --> 00:10:08,160 complicated, but also extremely useful. 285 00:10:06,240 --> 00:10:10,160 All right, so we're back to executing 286 00:10:08,160 --> 00:10:12,640 our first op code. We want to load 287 00:10:10,160 --> 00:10:14,880 something named print onto the stack 288 00:10:12,640 --> 00:10:17,200 like we saw two slides ago that uses pi 289 00:10:14,880 --> 00:10:18,880 eval loadname. In here is where we see 290 00:10:17,200 --> 00:10:20,399 the three lookup scopes that exist in 291 00:10:18,880 --> 00:10:22,240 Python. So first it's going to check the 292 00:10:20,399 --> 00:10:24,880 locals then the globals and then the 293 00:10:22,240 --> 00:10:26,959 built-ins. Now our hello world doesn't 294 00:10:24,880 --> 00:10:28,160 declare any variables local or global. 295 00:10:26,959 --> 00:10:31,200 So it's probably going to come from that 296 00:10:28,160 --> 00:10:32,880 third one the built-ins. And indeed in 297 00:10:31,200 --> 00:10:35,360 the list of built-in methods registered 298 00:10:32,880 --> 00:10:37,760 when Python starts up we find one named 299 00:10:35,360 --> 00:10:39,360 print. load name will push a function 300 00:10:37,760 --> 00:10:41,519 object referencing this onto the stack 301 00:10:39,360 --> 00:10:44,160 and then that load const op code will 302 00:10:41,519 --> 00:10:45,920 push our literal string hello world and 303 00:10:44,160 --> 00:10:47,680 then we get to that call op code. When 304 00:10:45,920 --> 00:10:49,920 we execute the call op code, it's going 305 00:10:47,680 --> 00:10:52,320 to start running this function built-in 306 00:10:49,920 --> 00:10:53,680 print imple. Now most of the code in 307 00:10:52,320 --> 00:10:55,279 there is dealing with parsing and 308 00:10:53,680 --> 00:10:56,480 validating the arguments passed into it 309 00:10:55,279 --> 00:10:58,640 because print is an extremely 310 00:10:56,480 --> 00:11:00,160 complicated function. But deep down we 311 00:10:58,640 --> 00:11:01,440 see some of the shape that we expect. 312 00:11:00,160 --> 00:11:03,600 We're getting a reference to something 313 00:11:01,440 --> 00:11:05,839 called std out from the CIS module and 314 00:11:03,600 --> 00:11:08,399 then we're calling write object on each 315 00:11:05,839 --> 00:11:09,600 argument in turn. We're 24 slides in. 316 00:11:08,399 --> 00:11:11,680 We've got to be close to seeing our 317 00:11:09,600 --> 00:11:13,040 hello world, right? 318 00:11:11,680 --> 00:11:16,480 All right. If we're getting a reference 319 00:11:13,040 --> 00:11:18,880 to sedd out, where did that come from? 320 00:11:16,480 --> 00:11:22,000 When any program starts, it receives 321 00:11:18,880 --> 00:11:24,399 three special already open file handles 322 00:11:22,000 --> 00:11:25,839 from its parent. These are standard in, 323 00:11:24,399 --> 00:11:28,800 out, and error. And they are 324 00:11:25,839 --> 00:11:30,320 respectively numbered 0, 1, and two. 325 00:11:28,800 --> 00:11:32,320 Now, Python doesn't know anything about 326 00:11:30,320 --> 00:11:33,839 what's in those. All it's doing is 327 00:11:32,320 --> 00:11:35,519 grabbing it, wrapping it in some 328 00:11:33,839 --> 00:11:38,640 encoding and buffering helpers, and 329 00:11:35,519 --> 00:11:40,399 stores a reference in the CIS module. 330 00:11:38,640 --> 00:11:41,839 Another small aside, we are definitely 331 00:11:40,399 --> 00:11:44,880 down in Code. We're going to be seeing 332 00:11:41,839 --> 00:11:46,560 mostly Code from this point out, but uh 333 00:11:44,880 --> 00:11:48,399 a lot of this reads really similar to 334 00:11:46,560 --> 00:11:50,000 Python. That is not by accident, and it 335 00:11:48,399 --> 00:11:51,200 is very useful. There going to be some 336 00:11:50,000 --> 00:11:53,760 other things in there. You'll see ref 337 00:11:51,200 --> 00:11:56,000 counting, macros, and error checks. But 338 00:11:53,760 --> 00:11:58,160 a good uh mental trick when you are 339 00:11:56,000 --> 00:12:00,079 reading through Python's C code, you can 340 00:11:58,160 --> 00:12:01,680 usually just recompile this in your head 341 00:12:00,079 --> 00:12:04,880 into the equivalent Python. It makes it 342 00:12:01,680 --> 00:12:06,720 a lot easier to read. 343 00:12:04,880 --> 00:12:08,160 All right, once again, let's fast 344 00:12:06,720 --> 00:12:09,519 forward down the stack a little bit. 345 00:12:08,160 --> 00:12:11,120 We're going to see those encoding and 346 00:12:09,519 --> 00:12:12,880 buffering helpers that we wrapped 347 00:12:11,120 --> 00:12:14,399 standard out in before. We're going to 348 00:12:12,880 --> 00:12:15,920 eventually end up in this method py 349 00:12:14,399 --> 00:12:17,279 write imple. Now, this is a core 350 00:12:15,920 --> 00:12:19,680 function. It's used for all kinds of 351 00:12:17,279 --> 00:12:21,279 file IO inside Python. So it is 352 00:12:19,680 --> 00:12:23,680 absolutely covered in special cases for 353 00:12:21,279 --> 00:12:25,519 weird operating systems. Fortunately, 354 00:12:23,680 --> 00:12:27,279 Linux is pretty simple. All it's going 355 00:12:25,519 --> 00:12:28,959 to do is call this function write with 356 00:12:27,279 --> 00:12:31,519 the file descriptor for standard out, 357 00:12:28,959 --> 00:12:33,600 which as we said before is one, a buffer 358 00:12:31,519 --> 00:12:35,200 with the bytes in hello world and the 359 00:12:33,600 --> 00:12:36,959 count of the number of bytes to write, 360 00:12:35,200 --> 00:12:39,519 which is 12. Don't forget there's a new 361 00:12:36,959 --> 00:12:41,200 line. This is the outer edge of Python. 362 00:12:39,519 --> 00:12:43,040 We could stop here, but what fun would 363 00:12:41,200 --> 00:12:44,399 that be? 364 00:12:43,040 --> 00:12:47,040 All right, to dive into the write 365 00:12:44,399 --> 00:12:49,680 function, we need to talk about lib C. 366 00:12:47,040 --> 00:12:51,120 Libby Cy is a standardized API exposing 367 00:12:49,680 --> 00:12:53,120 helper methods, operating system 368 00:12:51,120 --> 00:12:55,279 interfaces, math functions, and lots 369 00:12:53,120 --> 00:12:57,120 more stuff. Libby is really just a 370 00:12:55,279 --> 00:12:58,480 specification anyone can implement, 371 00:12:57,120 --> 00:13:00,079 right? All you have to do is implement 372 00:12:58,480 --> 00:13:02,160 some kind of function that matches that 373 00:13:00,079 --> 00:13:04,000 declaration. But it's been documented 374 00:13:02,160 --> 00:13:06,079 and formalized a few times. Most 375 00:13:04,000 --> 00:13:08,240 commonly is the portable operating 376 00:13:06,079 --> 00:13:10,720 system interface or POSIX specification. 377 00:13:08,240 --> 00:13:14,720 So when you hear pix, that is a type of 378 00:13:10,720 --> 00:13:16,079 libc specifying document. Now, in terms 379 00:13:14,720 --> 00:13:18,399 of implementations, we're usually going 380 00:13:16,079 --> 00:13:20,880 to be using GNUIB C, which is shortened 381 00:13:18,399 --> 00:13:22,800 to GIB C. You will, just for 382 00:13:20,880 --> 00:13:24,639 completeness, sometimes also see muscle. 383 00:13:22,800 --> 00:13:26,880 If you ever use the Alpine container 384 00:13:24,639 --> 00:13:28,399 images, those use muscle instead of gibb 385 00:13:26,880 --> 00:13:30,079 C, but we're not talking about muscle 386 00:13:28,399 --> 00:13:31,519 today. 387 00:13:30,079 --> 00:13:33,600 All right, this is the implementation of 388 00:13:31,519 --> 00:13:35,200 write in GIC C. We see the expected 389 00:13:33,600 --> 00:13:36,720 three arguments. The file descriptor 390 00:13:35,200 --> 00:13:38,320 we're writing to, the address, the 391 00:13:36,720 --> 00:13:39,839 number of bytes, but this function is 392 00:13:38,320 --> 00:13:41,120 not really doing very much. It's just 393 00:13:39,839 --> 00:13:44,160 forwarding our arguments something 394 00:13:41,120 --> 00:13:46,480 called a sis call. What's that? 395 00:13:44,160 --> 00:13:48,079 Our programs cannot be trusted. All 396 00:13:46,480 --> 00:13:50,240 normal programs run with a bunch of 397 00:13:48,079 --> 00:13:52,160 hardware level security restrictions. 398 00:13:50,240 --> 00:13:53,600 For example, we can only read memory 399 00:13:52,160 --> 00:13:55,040 that is assigned to our process. We 400 00:13:53,600 --> 00:13:57,279 can't just willy-nilly start reading 401 00:13:55,040 --> 00:13:59,680 from someone else's RAM. That would be 402 00:13:57,279 --> 00:14:01,120 very bad. This is called user space. It 403 00:13:59,680 --> 00:14:03,360 is incredibly important for digital 404 00:14:01,120 --> 00:14:05,040 security. But sometimes we want our 405 00:14:03,360 --> 00:14:06,560 programs to be able to do something a 406 00:14:05,040 --> 00:14:10,160 little bit more dangerous like touching 407 00:14:06,560 --> 00:14:12,480 a file. So there is kernel space. This 408 00:14:10,160 --> 00:14:14,800 is a different mode when you are running 409 00:14:12,480 --> 00:14:16,959 the actual kernel, the the core of the 410 00:14:14,800 --> 00:14:19,360 operating system with all of its 411 00:14:16,959 --> 00:14:22,079 elevated permissions. Okay, back to sis 412 00:14:19,360 --> 00:14:24,240 calls. A sis call you can think of as 413 00:14:22,079 --> 00:14:26,079 the window in front of a bank teller. It 414 00:14:24,240 --> 00:14:28,000 is a way that you can pass a request 415 00:14:26,079 --> 00:14:29,600 into the kernel to do something using 416 00:14:28,000 --> 00:14:31,199 all of its fancy permissions on your 417 00:14:29,600 --> 00:14:32,720 behalf. Just like at a bank teller, you 418 00:14:31,199 --> 00:14:34,079 can hand them a dollar bill, but you 419 00:14:32,720 --> 00:14:36,880 cannot jump over the counter and do 420 00:14:34,079 --> 00:14:38,800 whatever you want. 421 00:14:36,880 --> 00:14:40,079 All right, Gibbsc has a couple wrapper 422 00:14:38,800 --> 00:14:42,079 functions to get through in the call 423 00:14:40,079 --> 00:14:44,240 stack. Uh because write is a special 424 00:14:42,079 --> 00:14:45,600 kind of sys call a cancelellable sis 425 00:14:44,240 --> 00:14:47,680 call. For example, you could have a 426 00:14:45,600 --> 00:14:49,839 blocking right that has a timeout in 427 00:14:47,680 --> 00:14:51,839 user space. So we need extra helpers to 428 00:14:49,839 --> 00:14:53,519 be able to smoothly abort it just in 429 00:14:51,839 --> 00:14:55,120 case. But eventually we get to the sys 430 00:14:53,519 --> 00:14:57,199 call itself. Now this is written in 431 00:14:55,120 --> 00:15:00,399 assembly for each language sorry for 432 00:14:57,199 --> 00:15:02,959 each platform that gibbc supports. For 433 00:15:00,399 --> 00:15:05,839 x8664, the way sis calls work is that 434 00:15:02,959 --> 00:15:07,760 you put the sis call ID into the rax CPU 435 00:15:05,839 --> 00:15:09,440 register. That ID is going to be one for 436 00:15:07,760 --> 00:15:11,680 write. Then you put your other 437 00:15:09,440 --> 00:15:14,079 parameters into a bunch of other uh CPU 438 00:15:11,680 --> 00:15:16,560 registers and then you make the sys call 439 00:15:14,079 --> 00:15:18,880 hardware instruction that tells the 440 00:15:16,560 --> 00:15:20,639 hardware to jump into kernel space for 441 00:15:18,880 --> 00:15:22,399 you. 442 00:15:20,639 --> 00:15:24,000 As we land from the sysol CPU 443 00:15:22,399 --> 00:15:25,360 instruction, we are now inside the Linux 444 00:15:24,000 --> 00:15:27,920 kernel. We are running in kernel space. 445 00:15:25,360 --> 00:15:29,760 We have unlimited cosmic powers. There 446 00:15:27,920 --> 00:15:31,440 is a single landing pad for all sis 447 00:15:29,760 --> 00:15:33,360 calls and inside that is a dispatch 448 00:15:31,440 --> 00:15:35,279 table. So it's going to look at the ra 449 00:15:33,360 --> 00:15:37,760 register and figure out what sis call 450 00:15:35,279 --> 00:15:40,399 was this and where should I send it. Sis 451 00:15:37,760 --> 00:15:42,880 call ID1 that's right that is mapped to 452 00:15:40,399 --> 00:15:44,959 the cis write function inside the kernel 453 00:15:42,880 --> 00:15:48,000 which is a oneline wrapper for ksis 454 00:15:44,959 --> 00:15:49,440 write for some reason. Um what that's 455 00:15:48,000 --> 00:15:51,920 going to do is look up our file 456 00:15:49,440 --> 00:15:53,440 descriptor in the list of files that the 457 00:15:51,920 --> 00:15:55,680 colonel has been kindly keeping on our 458 00:15:53,440 --> 00:15:57,279 behalf behind the scenes. This is the 459 00:15:55,680 --> 00:15:58,880 literal meaning of file descriptor 1. 460 00:15:57,279 --> 00:16:01,839 That means that we want the second entry 461 00:15:58,880 --> 00:16:03,040 in that files list. 462 00:16:01,839 --> 00:16:04,720 Then it's going to pass that down to 463 00:16:03,040 --> 00:16:06,480 something called VFS, write, short for 464 00:16:04,720 --> 00:16:09,120 virtual file system, write, because we 465 00:16:06,480 --> 00:16:11,040 are now in VFS land. 466 00:16:09,120 --> 00:16:12,560 Just like we had a dispatch to a call 467 00:16:11,040 --> 00:16:13,920 back based on the sys call ID. We're 468 00:16:12,560 --> 00:16:16,079 going to do that again, but this time 469 00:16:13,920 --> 00:16:17,600 based on what type of file it is. 470 00:16:16,079 --> 00:16:19,440 Roughly a million years ago, I said that 471 00:16:17,600 --> 00:16:20,880 our Python process receives its standard 472 00:16:19,440 --> 00:16:22,720 out from its parent. Now, it's usually 473 00:16:20,880 --> 00:16:25,120 going to be a shell of some kind. bash 474 00:16:22,720 --> 00:16:27,759 or zsh or fish if you're extra fancy. 475 00:16:25,120 --> 00:16:30,240 But what did it give us? 476 00:16:27,759 --> 00:16:32,240 The os.fstat method lets us investigate 477 00:16:30,240 --> 00:16:33,680 any open file descriptor. So looking at 478 00:16:32,240 --> 00:16:36,639 standard out, we can see it's got a 479 00:16:33,680 --> 00:16:38,800 funky file mode that identifies it as a 480 00:16:36,639 --> 00:16:41,680 device file, which is how Linux exposes 481 00:16:38,800 --> 00:16:43,360 drivers. Each device file has a device 482 00:16:41,680 --> 00:16:45,759 number, which is broken down into a 483 00:16:43,360 --> 00:16:48,079 major in the high bite and the a minor 484 00:16:45,759 --> 00:16:50,639 in the low bite. So here we have major 485 00:16:48,079 --> 00:16:51,920 136 and minor zero. This is new 486 00:16:50,639 --> 00:16:54,639 information but doesn't really tell us 487 00:16:51,920 --> 00:16:56,320 anything about what this file is yet. 488 00:16:54,639 --> 00:16:57,759 To figure out where VFS write is going 489 00:16:56,320 --> 00:16:59,759 to send our call, we need to look at the 490 00:16:57,759 --> 00:17:02,160 device driver registrations. So these 491 00:16:59,759 --> 00:17:04,480 happen in the kernel at startup. Each 492 00:17:02,160 --> 00:17:06,720 subsystem can register a driver with a 493 00:17:04,480 --> 00:17:09,280 given major and also attach a set of 494 00:17:06,720 --> 00:17:12,480 function pointers to that device driver. 495 00:17:09,280 --> 00:17:15,280 So because our file has major 136, it is 496 00:17:12,480 --> 00:17:17,919 going to populate the fops or file ops 497 00:17:15,280 --> 00:17:19,679 in any open file handle with these 498 00:17:17,919 --> 00:17:23,439 functions. So we can see that the next 499 00:17:19,679 --> 00:17:24,880 stop for our right is TTY write. 500 00:17:23,439 --> 00:17:26,480 In the olden days, the way you 501 00:17:24,880 --> 00:17:28,559 interacted with a computer was through a 502 00:17:26,480 --> 00:17:30,799 modified electric typewriter called a 503 00:17:28,559 --> 00:17:32,640 teletype. This is a way to send input to 504 00:17:30,799 --> 00:17:35,039 your program by typing on it and receive 505 00:17:32,640 --> 00:17:36,720 output by printing to literal paper. Uh 506 00:17:35,039 --> 00:17:39,600 this one here was the model 33. It was 507 00:17:36,720 --> 00:17:41,520 quite snazzy for 1963. Teletype 508 00:17:39,600 --> 00:17:43,120 eventually got shortened to TTY because 509 00:17:41,520 --> 00:17:45,120 our industry has literally always been 510 00:17:43,120 --> 00:17:46,480 like this. 511 00:17:45,120 --> 00:17:47,919 But I'm guessing most of you don't work 512 00:17:46,480 --> 00:17:49,440 this way anymore. Or at least I hope 513 00:17:47,919 --> 00:17:52,000 not. Usually we'll instead have a 514 00:17:49,440 --> 00:17:53,520 software terminal. Xterm terminal.app 515 00:17:52,000 --> 00:17:55,520 command.exe. 516 00:17:53,520 --> 00:17:56,960 But because everything in computers is 517 00:17:55,520 --> 00:17:58,640 built on the bones of the past, we've 518 00:17:56,960 --> 00:18:00,559 left all of the terminal interfaces 519 00:17:58,640 --> 00:18:02,640 pretty much unchanged since the 70s and 520 00:18:00,559 --> 00:18:06,400 just added an emulated version. This is 521 00:18:02,640 --> 00:18:07,840 a pseudo TTY shortened to pty. 522 00:18:06,400 --> 00:18:09,520 For the most part, it's just a fancy 523 00:18:07,840 --> 00:18:11,679 pipe. Anything that you write into one 524 00:18:09,520 --> 00:18:13,120 side, you read on the other. In terms of 525 00:18:11,679 --> 00:18:15,039 implementation, this means it's a shared 526 00:18:13,120 --> 00:18:17,039 memory buffer of some kind. one side can 527 00:18:15,039 --> 00:18:18,799 read can write into and another can read 528 00:18:17,039 --> 00:18:20,480 from. Technically, this is also 529 00:18:18,799 --> 00:18:21,679 birectional. There's one buffer for each 530 00:18:20,480 --> 00:18:22,880 direction, but we're only looking at 531 00:18:21,679 --> 00:18:24,720 output. So, we're just going to kind of 532 00:18:22,880 --> 00:18:26,080 skip past that for right now. This is 533 00:18:24,720 --> 00:18:28,640 what our standard out actually points 534 00:18:26,080 --> 00:18:31,440 to. It is a pty device running in Unix 535 00:18:28,640 --> 00:18:33,360 98 compatibility mode. 536 00:18:31,440 --> 00:18:35,919 All right, back to the kernel in our 537 00:18:33,360 --> 00:18:37,919 call stack. So, vfs write is calling to 538 00:18:35,919 --> 00:18:40,480 tty write, which is then going to call 539 00:18:37,919 --> 00:18:43,120 into pty write. And if we go one layer 540 00:18:40,480 --> 00:18:46,720 deeper, we find the somewhat wordy TTY 541 00:18:43,120 --> 00:18:49,520 insert flip string flags. This has a 542 00:18:46,720 --> 00:18:52,080 call to the helper function memcopy. So 543 00:18:49,520 --> 00:18:54,640 in here we have our helper string in 544 00:18:52,080 --> 00:18:57,280 cars and we have our memory buffer in 545 00:18:54,640 --> 00:18:58,480 TB. This is the final destination for 546 00:18:57,280 --> 00:19:00,559 our print. This is where it's actually 547 00:18:58,480 --> 00:19:02,080 going. But there's one more step. If 548 00:19:00,559 --> 00:19:04,160 you're familiar with C programming, you 549 00:19:02,080 --> 00:19:06,240 may recognize memcopy as a helper 550 00:19:04,160 --> 00:19:08,080 function to copy uh bytes from one 551 00:19:06,240 --> 00:19:09,919 location to another in memory. However, 552 00:19:08,080 --> 00:19:11,039 the kernel cannot depend on lib C 553 00:19:09,919 --> 00:19:13,600 because that would cause an infinite 554 00:19:11,039 --> 00:19:15,760 loop. That is bad. So the kernel has its 555 00:19:13,600 --> 00:19:17,200 own implementation in assembly. Hello 556 00:19:15,760 --> 00:19:18,880 world plus a new line as previously 557 00:19:17,200 --> 00:19:20,640 mentioned is 12 bytes. So we're going to 558 00:19:18,880 --> 00:19:22,480 end up in this section of the kernel 559 00:19:20,640 --> 00:19:25,440 implementation of mencopy designed for 560 00:19:22,480 --> 00:19:27,440 copying between 8 and 15 bytes. So the 561 00:19:25,440 --> 00:19:29,840 final destination for our hello world is 562 00:19:27,440 --> 00:19:32,559 these four move Q instructions. That is 563 00:19:29,840 --> 00:19:34,960 short for move quadword. This is it. 564 00:19:32,559 --> 00:19:36,960 This is where our hello world ends up. 565 00:19:34,960 --> 00:19:38,880 We could technically go even deeper. We 566 00:19:36,960 --> 00:19:41,440 could look at uh what transistors 567 00:19:38,880 --> 00:19:43,760 implement move Q or how do transistors 568 00:19:41,440 --> 00:19:45,600 work at all. Unfortunately, AMD and 569 00:19:43,760 --> 00:19:47,039 Intel don't actually release very much 570 00:19:45,600 --> 00:19:48,799 information about how their cores work 571 00:19:47,039 --> 00:19:50,400 anymore. So, I'd have to get really 572 00:19:48,799 --> 00:19:53,600 handwavy. I think this is probably as 573 00:19:50,400 --> 00:19:55,600 far down as we can go today. All right, 574 00:19:53,600 --> 00:19:56,960 our right has completed. We're going to 575 00:19:55,600 --> 00:19:59,360 rush back up the stack. We're going to 576 00:19:56,960 --> 00:20:01,360 see every layer in reverse. We clean up 577 00:19:59,360 --> 00:20:03,760 some kernel locks. We jump from kernel 578 00:20:01,360 --> 00:20:06,000 space back to user space. We finish this 579 00:20:03,760 --> 00:20:07,679 call op code. Our print function 580 00:20:06,000 --> 00:20:10,160 returns. We've been in this for 20 581 00:20:07,679 --> 00:20:12,880 minutes. The ripple loops and just waits 582 00:20:10,160 --> 00:20:14,400 for future input. But okay, we kind of 583 00:20:12,880 --> 00:20:16,960 skipped. We showed where the write goes, 584 00:20:14,400 --> 00:20:19,200 but how does it get to the screen? 585 00:20:16,960 --> 00:20:21,120 All right, we talked about pty is just a 586 00:20:19,200 --> 00:20:23,600 fancy pipe. One side writes, another 587 00:20:21,120 --> 00:20:25,679 side reads. Okay, so in our terminal 588 00:20:23,600 --> 00:20:27,200 application, it's going to be reading in 589 00:20:25,679 --> 00:20:29,679 a loop somewhere. This is going to use 590 00:20:27,200 --> 00:20:31,840 the read sis call. that's going to dive 591 00:20:29,679 --> 00:20:34,240 all the way back down through lib C into 592 00:20:31,840 --> 00:20:36,400 the uh kernel, make a SIS call, get to 593 00:20:34,240 --> 00:20:37,679 the TTY subsystem, the PTY subsystem. 594 00:20:36,400 --> 00:20:39,360 It's going to eventually find that exact 595 00:20:37,679 --> 00:20:41,039 same memory buffer as we copied our 596 00:20:39,360 --> 00:20:42,480 hello world into. And it's going to do 597 00:20:41,039 --> 00:20:44,000 the same thing in reverse. Return those 598 00:20:42,480 --> 00:20:45,600 hello world bytes all the way back out 599 00:20:44,000 --> 00:20:47,039 to our terminal program. And then your 600 00:20:45,600 --> 00:20:48,559 terminal program will have to render 601 00:20:47,039 --> 00:20:50,000 them on screen. Exactly how that works 602 00:20:48,559 --> 00:20:52,799 is up to your terminal. Here's just an 603 00:20:50,000 --> 00:20:54,960 example from Xterm. 604 00:20:52,799 --> 00:20:57,120 And so at long last, we can see our 605 00:20:54,960 --> 00:20:59,580 hello world exactly as expected. Simple, 606 00:20:57,120 --> 00:21:02,700 right? 607 00:20:59,580 --> 00:21:02,700 [Music] 608 00:21:05,600 --> 00:21:09,760 All right. Uh, so why do we care about 609 00:21:07,280 --> 00:21:11,360 all of this? Uh, everyone here could 610 00:21:09,760 --> 00:21:14,400 probably have told me what hello world 611 00:21:11,360 --> 00:21:15,520 does. This wasn't a puzzle to be solved. 612 00:21:14,400 --> 00:21:16,799 Programming is full of amazing 613 00:21:15,520 --> 00:21:18,080 abstractions and we couldn't build 614 00:21:16,799 --> 00:21:19,440 things without them. But there will come 615 00:21:18,080 --> 00:21:21,200 a day where every single one of those 616 00:21:19,440 --> 00:21:22,880 abstractions will fail you. Maybe your 617 00:21:21,200 --> 00:21:24,400 program is crashing in a weird new way, 618 00:21:22,880 --> 00:21:25,840 or you need to squeak out a little bit 619 00:21:24,400 --> 00:21:27,440 more performance out of your web app, or 620 00:21:25,840 --> 00:21:29,600 something new we haven't even imagined 621 00:21:27,440 --> 00:21:31,039 will fail. Uh, everyone should feel 622 00:21:29,600 --> 00:21:32,559 comfortable peeling back the layers of 623 00:21:31,039 --> 00:21:34,240 abstractions until you find what you 624 00:21:32,559 --> 00:21:36,320 need. This was certainly overstatement 625 00:21:34,240 --> 00:21:37,760 for dramatic effect, but I've had to 626 00:21:36,320 --> 00:21:38,880 work with basically every one of these 627 00:21:37,760 --> 00:21:41,840 layers at some point in my career, 628 00:21:38,880 --> 00:21:44,000 except 629 00:21:41,840 --> 00:21:45,360 uh, play with your tools. Tinker, fix, 630 00:21:44,000 --> 00:21:46,559 experiment. You aren't just a Python 631 00:21:45,360 --> 00:21:48,400 developer, you are an everything 632 00:21:46,559 --> 00:21:50,480 developer. And inside every abstraction 633 00:21:48,400 --> 00:21:53,720 is a new lesson to be learned. Thank you 634 00:21:50,480 --> 00:21:53,720 very much. 635 00:22:00,000 --> 00:22:04,480 Thank you very much for that, Noah, for 636 00:22:02,240 --> 00:22:06,480 that uh worldw 637 00:22:04,480 --> 00:22:08,240 through the Python internals. I was 638 00:22:06,480 --> 00:22:10,000 going to make a joke about here about 639 00:22:08,240 --> 00:22:13,679 quantum physics, but you already took 640 00:22:10,000 --> 00:22:15,520 that away. We do have plenty of time for 641 00:22:13,679 --> 00:22:18,159 questions. See if anybody's brain is not 642 00:22:15,520 --> 00:22:22,320 as scrambled as mine after this. Please 643 00:22:18,159 --> 00:22:25,919 um raise your hands. Yes. Uh just wait 644 00:22:22,320 --> 00:22:28,000 for a microphone here. 645 00:22:25,919 --> 00:22:29,840 Not a question, but I've used fourth. 646 00:22:28,000 --> 00:22:31,600 How dare you? 647 00:22:29,840 --> 00:22:33,679 And I am old. 648 00:22:31,600 --> 00:22:36,640 Thank you. 649 00:22:33,679 --> 00:22:38,799 All right, any other questions? 650 00:22:36,640 --> 00:22:42,720 Let me have a quick look to see if we 651 00:22:38,799 --> 00:22:45,600 got questions on Discord. 652 00:22:42,720 --> 00:22:47,760 Um so of course why is Python slow? Um 653 00:22:45,600 --> 00:22:49,520 we are built on a stack of abstractions 654 00:22:47,760 --> 00:22:50,320 of abstractions of abstractions. 655 00:22:49,520 --> 00:22:52,159 So many 656 00:22:50,320 --> 00:22:54,960 which ones of those could we strip out 657 00:22:52,159 --> 00:22:58,240 if we actually started clean and and 658 00:22:54,960 --> 00:23:02,000 make Python fast all of a sudden? 659 00:22:58,240 --> 00:23:03,280 I mean so there are a suite of 660 00:23:02,000 --> 00:23:07,039 technologies. It's not really one thing 661 00:23:03,280 --> 00:23:09,600 but unicernels are a way to try to build 662 00:23:07,039 --> 00:23:11,280 all of this into kernel space. Um I do 663 00:23:09,600 --> 00:23:14,400 not believe there are any currently 664 00:23:11,280 --> 00:23:16,159 active Python unicel projects. Um there 665 00:23:14,400 --> 00:23:19,039 were in the past I think they are all 666 00:23:16,159 --> 00:23:22,080 sleeping right now. Um most unicronel 667 00:23:19,039 --> 00:23:24,799 work is being done in okamel and rust at 668 00:23:22,080 --> 00:23:26,640 this point. Um so the idea being that if 669 00:23:24,799 --> 00:23:29,039 there is only the kernel there are no 670 00:23:26,640 --> 00:23:30,960 sis calls. We can just dispense with all 671 00:23:29,039 --> 00:23:34,080 of that weirdness and dispatching and 672 00:23:30,960 --> 00:23:35,840 just compile it all into one thing. U 673 00:23:34,080 --> 00:23:37,679 that is certainly a very compelling 674 00:23:35,840 --> 00:23:43,039 story. I don't think any Unicernel 675 00:23:37,679 --> 00:23:45,120 project has quite worked out a set of 676 00:23:43,039 --> 00:23:47,280 smoothing tools to make this not 677 00:23:45,120 --> 00:23:49,760 extremely disruptive. But certainly if I 678 00:23:47,280 --> 00:23:53,880 needed absolute maximum performance, 679 00:23:49,760 --> 00:23:53,880 that is the direction I would go. 680 00:23:55,520 --> 00:23:59,120 Awesome talk, Noah. Thank you very much 681 00:23:57,120 --> 00:24:01,840 for that. Have you got some time just to 682 00:23:59,120 --> 00:24:05,360 go and talk about um the JIT side that 683 00:24:01,840 --> 00:24:06,799 you um briefly went over? If you can 684 00:24:05,360 --> 00:24:07,440 take another half an hour, that would be 685 00:24:06,799 --> 00:24:10,320 really interesting. 686 00:24:07,440 --> 00:24:14,159 I I slightly understand the JIT. Uh I'm 687 00:24:10,320 --> 00:24:16,880 not sure uh I am qualified. So the the 688 00:24:14,159 --> 00:24:18,480 general idea is what I said of so it's 689 00:24:16,880 --> 00:24:20,400 looking for hot loops. So it means that 690 00:24:18,480 --> 00:24:22,080 the first time it runs a jump backwards, 691 00:24:20,400 --> 00:24:24,480 it's not going to do anything. Uh it 692 00:24:22,080 --> 00:24:27,039 waits until a certain threshold of 693 00:24:24,480 --> 00:24:29,840 identical jump backwards have happened. 694 00:24:27,039 --> 00:24:31,600 So in each jump backward op code, it is 695 00:24:29,840 --> 00:24:34,640 basically keeping track of how often 696 00:24:31,600 --> 00:24:37,200 have I run. Uh, and if it sees, oh, this 697 00:24:34,640 --> 00:24:39,520 jump backward has happened 50 times in 698 00:24:37,200 --> 00:24:41,279 the last, I don't know, thousand uh, 699 00:24:39,520 --> 00:24:42,960 instructions, okay, this is probably a 700 00:24:41,279 --> 00:24:46,320 hot loop. You've got a for loop or a Y 701 00:24:42,960 --> 00:24:48,240 loop that's going really fast. So, 702 00:24:46,320 --> 00:24:51,200 that's what activates the JIT as far as 703 00:24:48,240 --> 00:24:52,720 I know. Um, and so it will pause. It 704 00:24:51,200 --> 00:24:53,760 will try to I mean, so this is the op 705 00:24:52,720 --> 00:24:55,440 code level. So, it doesn't really know 706 00:24:53,760 --> 00:24:56,720 what a for loop is anymore. That's 707 00:24:55,440 --> 00:24:59,360 already been expanded, but it's trying 708 00:24:56,720 --> 00:25:02,159 to reverse engineer what probably was 709 00:24:59,360 --> 00:25:03,760 the body of the for loop. put all of 710 00:25:02,159 --> 00:25:05,600 those op codes into an array, ship them 711 00:25:03,760 --> 00:25:07,520 off to the actual JIT thing, the thing 712 00:25:05,600 --> 00:25:09,360 that turns the op codes into assembly, 713 00:25:07,520 --> 00:25:10,400 which itself is kind of complicated, but 714 00:25:09,360 --> 00:25:12,720 you can imagine how that works. Like 715 00:25:10,400 --> 00:25:15,520 that's just doing all of the same code 716 00:25:12,720 --> 00:25:18,080 as the op code execution would be, but 717 00:25:15,520 --> 00:25:19,840 hardwired into assembly. And then there 718 00:25:18,080 --> 00:25:22,240 is a special thing that it can shove 719 00:25:19,840 --> 00:25:24,720 back where the op codes were. That is, 720 00:25:22,240 --> 00:25:27,120 no, actually, please just literally jump 721 00:25:24,720 --> 00:25:30,440 into this block of assembly. I promise 722 00:25:27,120 --> 00:25:30,440 it's safe. 723 00:25:31,440 --> 00:25:34,640 That's that's basically how the JIT 724 00:25:32,960 --> 00:25:37,039 works as far as I know. Uh it is 725 00:25:34,640 --> 00:25:38,799 evolving extremely rapidly. So again, if 726 00:25:37,039 --> 00:25:40,960 you are watching this on a video and it 727 00:25:38,799 --> 00:25:42,720 is past September 2025, please go check 728 00:25:40,960 --> 00:25:45,600 the documentation because it might be 729 00:25:42,720 --> 00:25:47,360 something new by then. 730 00:25:45,600 --> 00:25:49,600 All right, another question in front 731 00:25:47,360 --> 00:25:50,720 here. 732 00:25:49,600 --> 00:25:54,000 Yeah, 733 00:25:50,720 --> 00:25:55,760 PEP 744 if you want to look up more info 734 00:25:54,000 --> 00:25:58,000 about the JIT. And I'll chuck a link 735 00:25:55,760 --> 00:26:00,720 into the Discord as well. 736 00:25:58,000 --> 00:26:03,520 uh highly highly recommend. She is far 737 00:26:00,720 --> 00:26:05,360 more knowledgeable than I. 738 00:26:03,520 --> 00:26:08,559 All right, we've got time for one or two 739 00:26:05,360 --> 00:26:11,600 more questions if there are any. 740 00:26:08,559 --> 00:26:14,720 No. Uh in that case, uh let's all give 741 00:26:11,600 --> 00:26:18,279 another hand to Noah for his wonderful, 742 00:26:14,720 --> 00:26:18,279 wonderful talk.