2022-04-05 05:24:40 Here's an overview document for that system: 2022-04-05 05:24:42 https://github.com/microCore-VHDL/microCore/blob/master/documents/uCore_overview.pdf 2022-04-05 05:26:01 A sequence of literal instruction bytes builds a wide literal. The first opcode after such a sequence operates on that literal. if you want to push multiple literals you issue the bytes for the first literal, then a nop instruction, then the bytes for the second literal, etc. 2022-04-05 05:28:58 A short branch or call, within -64..63 range, would just be . Additional items extend the range - those are relatively addressed. An absolute jump or call would be . 2022-04-05 07:11:31 You know, I'm pretty sure this is my favorite bit of Forth code, out of all I've written: 2022-04-05 07:11:36 .: end?; .w@ x:7FFC and .0=;; - ; 2022-04-05 07:11:39 .: align 3 + 3 not and ; 2022-04-05 07:11:41 .: gotit s2 @ align |} ; 2022-04-05 07:11:43 .: nudge s2 1+! s1 1+! 1- ; 2022-04-05 07:11:45 .: check s2 @ c@ x:7F and s1 @ c@ =; ;; 2022-04-05 07:11:47 .: (chars) check nudge .0>me gotit ; 2022-04-05 07:11:49 .: setup dup wbuf .c0 1+ ; 2022-04-05 07:11:51 .: chars setup { (chars) 3 } ; 2022-04-05 07:11:53 .: words dup 2+ { chars 1 } end?; me [ 2022-04-05 07:11:55 .: vocs .@ { words 1 } 8+ @ .0>me ; 2022-04-05 07:11:57 .: (find) path @ {| vocs 0 |} ; 2022-04-05 07:11:59 : find bl word (find) ; 2022-04-05 07:12:55 That defines 'find' - the rest is just factored temporary names. I like it because it's very clean, it does something powerful, and it exhibits *all* of the "unusual" features I've added to the language. The frames, the conditional and double returns, and the "exception exit" (not sure that's the best name). It's all "in there." 2022-04-05 07:13:19 And that does the entire dictionary search - it iterates over vocabularies, iterates over words in vocabularies, and iterates over characters in word names. 2022-04-05 07:58:15 your lines aren't very long :-) 2022-04-05 08:12:11 You should make a blog KipIngram 2022-04-05 08:12:53 What's .: ? 2022-04-05 08:23:26 I do think about it sometimes. I've done it in the past. Maybe I'll roll my sleeves up and give it a try again. 2022-04-05 08:23:46 .: is precisely like :, except it sets a particular bit in the header. 2022-04-05 08:24:10 Later, I run a word called .wipe that goes through the whole list and "unlinks" words with that bit set from the word list. 2022-04-05 08:24:34 So the .: words are words that I need momentarily - once find is defined, they're no longer needed and I can dump them. 2022-04-05 08:24:51 I could have also done it like this: 2022-04-05 08:24:58 : foo ... ; TEMPORARY 2022-04-05 08:25:07 But .: is more compact. 2022-04-05 08:26:00 My favorite part of that bit is {| ... |} 2022-04-05 08:26:26 If the word isn't found, then everything executes the way you'd expect it to and we return from vocs, put a 0 failure on the stack, and return to find. 2022-04-05 08:26:39 But if the word is found, we call gotit 2022-04-05 08:27:17 And when gotit executes |}, it basically makes the stacks so that when gotit returns it returns all the way back to find. 2022-04-05 08:27:36 That means none of those intermediate words have to concern themselves with the success case - they're written as though failure is guaranteed. 2022-04-05 08:36:03 Another nice perk of the features is that chars, words, and voc don't have to worry about cleaning garbage off the stack - the { ... } bit just "takes care of it. 2022-04-05 08:36:55 E.g., vocs doesn't care what words leaves on the stack - once it executes } the stack is back to how it was at the { plus 1 more item deleted (since } got a 1 parameter). 2022-04-05 08:38:16 KipIngram: how do you plan (or did yet) to implement vocabularies? 2022-04-05 08:39:07 should I have a hash table or alike for vocabularies and a dictionary for every vocabulary? 2022-04-05 08:39:32 or every word in the dictionary has a flag to note which vocabulary belongs to 2022-04-05 08:39:43 I haven't done the actual vocabulary word itself yet, or any of the words that will manipulate the path list. But 'find' alread knows how to do it. Basically I have a variable, 'path'. It points to the first vocabulary in the search order. Each vocabulary word has two cells of data - the first is a pointer to the latest word definedin that vocabulary, and the second is a pointer to the next vocabulary in 2022-04-05 08:39:46 the search order. 2022-04-05 08:40:01 I had a bad realization about that in the last couple of days. 2022-04-05 08:40:27 In my last implementation path was an array, and I "pushed" vocabularies onto it like a stack. 2022-04-05 08:40:42 This time it's a linked list, and the list is embeded with the vocabulary words themselves. 2022-04-05 08:41:10 But I realized that means it will be hard for me to do the little trick crc does, where he can create a whole new search context, use it, and hten restore the previous one. 2022-04-05 08:41:11 I don't like to have a flag and only one dictionary for all the vocabularies as it means I have to search the whole dictionary and check if the word pertains to the current vocabulary in the order 2022-04-05 08:41:20 My "new" context would overwrite all those link fields. 2022-04-05 08:41:38 having a hash table with it's own dictionary looks a bit better for me 2022-04-05 08:41:49 So I can still do that trick, but it will be harder - I'll have to basically copy the whole path list if I want to restore it later. 2022-04-05 08:42:18 In my system each vocabulary will have its own linked list of words. 2022-04-05 08:42:34 They're all together in one region, but I can have multiple linked lists in there. 2022-04-05 08:42:45 then it's how I was going to do it 2022-04-05 08:43:01 to have a dictionary for every vocabulary 2022-04-05 08:43:21 Each vocabulary word designates an "entry point" for a list, and the lists are mutually exclusive - a word can only be in one list. 2022-04-05 08:43:34 well in your case I suppose it's just one dictionary and vocabularies have a list of memory addresses 2022-04-05 08:43:48 Yeah - mine's basically like that; the lists are distinct from one another. 2022-04-05 08:44:58 I'll still use perl but this time I'll try to attach to a real forth implementaation 2022-04-05 08:45:02 I believe Forth 79 had only one level of hierarch - vocabulary words were always defined in the Forth vocabulary. I never liked that - I can have a full general tree. 2022-04-05 08:45:13 or at least to something more similar than what I've done before 2022-04-05 08:46:11 I've read a bit of the threaded interpretive languages book, I've just read the first pages, but I think I want to start again and go implementing while I read it 2022-04-05 08:46:11 Also, I think F79 had it so when you got to the end of a vocabulary list it automatically chained into the parent. I don't do that either - the end of a list is the end of that list, and I move on to the next path item. 2022-04-05 08:46:27 So all vocabularies will start out empty. 2022-04-05 08:46:57 actually vocabularies are not really my concern, but it's something that if I don't care now I'll have to rewrite everything if I want to implement them later 2022-04-05 08:47:07 So, I say that 'find' already knows how to do this - it's intended to at least. But so far I've only had it search a path one vocabulary long. 2022-04-05 08:47:30 I know that it terminates when a vocabulary's link field is zero. But I've not yet actually had it search a second vocabulary. 2022-04-05 08:47:48 in my case I have a hash table with the vocabularies and every vocabulary is just an array 2022-04-05 08:47:57 But "8+ @" is pretty simple code - I don't see why it wouldn't work. 2022-04-05 08:48:14 I start reading from the last word by using the length of that array so I can have forget and alike 2022-04-05 08:48:19 Ok. 2022-04-05 08:48:43 but now I realize I should do it like you're doing it 2022-04-05 08:48:47 So what does the hash actually do? 2022-04-05 08:49:02 atm nothing I was just wondering how to implement it 2022-04-05 08:49:10 What I'm doing is mostly just "how Forth has always worked." 2022-04-05 08:49:20 Not terribly creative or anything. 2022-04-05 08:49:21 https://termbin.com/otvk 2022-04-05 08:49:44 I know you are likely to not understand perl, but it's just a for loop 2022-04-05 08:50:24 well two, by using the order array and then looking at them, and words for now are just a string, so 0 functionality 2022-04-05 08:50:52 My cats are nice too. 2022-04-05 08:50:54 :-) 2022-04-05 08:51:12 but it's wrong, I should just have a list of "memory" addresses (which in my case will be an index) and one dictionary 2022-04-05 08:52:10 thanks KipIngram you've helped me to realize the way I should implement them 2022-04-05 08:52:11 :D 2022-04-05 08:52:27 :-) 2022-04-05 08:53:22 If you do it like this, then remember that when you FORGET you'll need to visit every vocabulary and make sure you trim each one back to below the forget point. 2022-04-05 08:53:46 I was thinking in make forget use the current vocabulary 2022-04-05 08:53:52 I think I'm going to have to add another link field to the vocabularies for that. Or have some other way of finding "every vocabulary." 2022-04-05 08:54:04 Right now the list only gives me the ones in my current searchorder. 2022-04-05 08:54:05 and since the vocabulary is just a list of memory adresses I just have to remove them 2022-04-05 08:55:19 I haven't done forget yet. 2022-04-05 08:55:21 I think there is no problem in perl by having inexistent elements in some array like having 1 2 5 7 8 2022-04-05 08:55:29 re: tricks; I no longer do this as I've stopped using vocabularies. 2022-04-05 08:55:39 crc: why? 2022-04-05 08:55:41 Oh, what are you doing these days? 2022-04-05 08:55:48 but you cheat, you use prefix 2022-04-05 08:56:08 as vms14 noted, a short namespacing prefix 2022-04-05 08:56:17 prefix let's you have similar functionality 2022-04-05 08:56:25 string words all start with s:, numeric words with n:, etc 2022-04-05 08:56:40 I see. 2022-04-05 08:56:46 crc: why sockets don't work in my netbsd retroforth binary? 2022-04-05 08:57:00 the socket words just say ok, but seem to do nothing 2022-04-05 08:57:16 did you enable sockets in the vm? 2022-04-05 08:57:33 no, I thought that was a compile time option 2022-04-05 08:57:37 (I don't currently have a netbsd setup to test on) 2022-04-05 08:57:49 it is compile time, they need to be in the vm and the image 2022-04-05 08:58:47 I think I've tried this example http://retroforth.org/examples/socket-server.retro.html 2022-04-05 08:58:54 Later on (as in, I'm not putting this in the base system) I plan to support "types" by letting a 64-bit cell have a pointer in its low part and some type bits in its high part. Those will be able to sit on the stack just fine. I envision future applications that are basically replacement interpreters that take the "type signature" of the top stack items into account when doing word searches. 2022-04-05 08:59:00 but never blocked nor did anything, tried with netcat 2022-04-05 08:59:02 in the makefile, you'd need to uncomment the: 2022-04-05 08:59:04 # ENABLED += -DENABLE_SOCKETS 2022-04-05 08:59:05 and 2022-04-05 08:59:11 # DEVICES += interface/sockets.retro 2022-04-05 08:59:39 I've only actually tested the sockets stuff on OpenBSD 2022-04-05 09:00:53 https://termbin.com/hczg 2022-04-05 09:01:03 that's the makefile, it's a pkgsrc makefile 2022-04-05 09:02:10 ACTION needs to update that; it's still pointing to a 2019 release, not the current one 2022-04-05 09:02:40 you can talk to the maintainers by mail listing, or #netbsd or better yet in #pkgsrc 2022-04-05 09:03:42 or send a pr 2022-04-05 09:03:44 http://www.netbsd.org/cgi-bin/sendpr.cgi?gndb=netbsd 2022-04-05 09:04:11 you don't even need to have netbsd installed as it's an online cgi 2022-04-05 09:06:08 does it have support for database? 2022-04-05 09:06:18 netbsd comes with sqlite3 in base 2022-04-05 09:06:38 and lua xD you can write kernel modules in lua just because a user asked for that 2022-04-05 09:08:00 crc: the 2019 release was before a main change in the vm? 2022-04-05 09:08:09 maybe the new one just breaks 2022-04-05 09:09:07 database: not built in, though I do use sqlite3 via pipes for some things 2022-04-05 09:09:20 the VM has evolved a lot since 2019 2022-04-05 09:10:12 KipIngram: when you forget stuff from a vocabulary, you have also to forget all other words from other vocabularies, not? 2022-04-05 09:10:30 i mean other words could refer to other vocabularies so they should die too 2022-04-05 09:11:18 so forgot shouldn't even care about the vocabulary, just remmove everything from that word to the end 2022-04-05 09:11:22 forget* 2022-04-05 09:12:21 I was going to make forget only remove words from that vocabulary, but now it seems wrong because of this 2022-04-05 09:16:28 KipIngram: why do you want types? 2022-04-05 09:17:22 I was thinking in have a string type, but I think for now I'll try to stick to a normal forth implementation, even if it's slow in that way because I'll be faking memory and alike 2022-04-05 09:17:29 memory will be an array 2022-04-05 09:18:01 but it will help me to understand how to implement forth and serve as a playground so then I can move to another language 2022-04-05 09:19:21 sockets are working on openbsd with the current source tree (running the example now; telnet forth.works 9998) 2022-04-05 09:19:24 Yes, that was my point a minute ago. You have to forget EVERYTHING that is at a higher location in RAM than the word you're specifying to FORGET. 2022-04-05 09:19:26 anyways I think perl can be the implementation language after all, once I understand the stuff well I can do it in my way, idk how slow will be, but I like the fact the stack can accept any type of data 2022-04-05 09:20:05 crc: by using the openbsd makefile I think you can get the netbsd one, maybe some minor changes, but after all theyr 2022-04-05 09:20:13 they're very similar 2022-04-05 09:20:19 Which means you have to visit each vocabulary and check to see if they have any of those. 2022-04-05 09:20:35 I'll setup a NetBSD vm this week and get the pkgsrc updated :) 2022-04-05 09:20:44 crc: :0 2022-04-05 09:21:01 I don't want types in my *Forth*; I want to keep it very lean. But at the application level I'd like to be able to create, say, a tool that I can use like Octave / Matlab. 2022-04-05 09:21:29 So in such an environment when I say * for example, the code it runs will need to depend on what I've just put on the stack. 2022-04-05 09:21:47 * on a pair of integers is one thing, * on a pair of floats is another, * on a pair of matrices yet another. 2022-04-05 09:22:04 KipIngram: aren't words consecutive? if they are I can just remove all those words 2022-04-05 09:22:08 I fully recognize that's not "the Forth way," but I still want it. 2022-04-05 09:22:24 I mean why do I have to go for every vocabulary? 2022-04-05 09:22:45 Yes, it's easy to cut back the dictionary RAM. But vocabularies point at those words - if a vocabulary points at a word you remove, and you do the wrong thing, you'll crash. 2022-04-05 09:22:57 You might have defined the vocabulary much earlier, and then added a word to it later. 2022-04-05 09:23:07 ahh now I get it 2022-04-05 09:23:22 These are lessons I learned the hard way over the years. 2022-04-05 09:23:57 hahah I like to steal knowledge from people just because of this 2022-04-05 09:24:15 you take some time to learn stuff but only one minute to teach it to me 2022-04-05 09:24:46 Hey - that makes things better for everybody. I've sure gotten a lot of tips over the years. 2022-04-05 09:26:40 after all, all we learn from others or read it's knowledge that they have gained through investing time and they wanted to explain it 2022-04-05 09:26:51 some people does not want to share knowledge btw 2022-04-05 09:27:24 Oh, I know. Either because they are overtly hogging it or because they can't be bothered to take the time. I try not to ever be like that. 2022-04-05 09:27:44 vms14: We're all stealing knowledge constantly, some are just more aware, and some more willing to take it! 2022-04-05 09:27:44 but I like to explain what I learn because it helps me to understand it better, the only problem is I can explain wrong things because I don't fully know what I've just learned 2022-04-05 09:27:53 We used to have an IT guy at the office that was very much that way. He was 100% focused on advancing himself. 2022-04-05 09:28:22 vms14: I'm not proud and I'll explain stuff, admitting ignorance, and ready to be corrected 2022-04-05 09:28:28 And I learn more that way I think 2022-04-05 09:29:02 Sure, it's like economics. By cooperating in the economy we all wind up better off that if we each tried to grow his own food, sew his own clothes, etc. 2022-04-05 09:29:07 yes, specially when someone corrects you and makes you understand something you weren't aware of 2022-04-05 09:29:29 That's the "beautiful" part of capitalism. Then you get big greedy corporations and that's the ugly part of it. 2022-04-05 09:30:05 but even if you were correct, the only fact of explaining it to others (specially when you try to make it simple to understand) improves your knowledge of what you already know 2022-04-05 09:30:46 in order to make it simple to understand you need a higher knowledge of the matter 2022-04-05 09:31:13 Yeah, I think the best way to know you're really clear on something is to try explaining it to someone else. 2022-04-05 09:31:31 If you can do that well, then you "know it." 2022-04-05 09:42:04 KipIngram: Yep I'm not an ideologue but I do think markets have their place 2022-04-05 09:44:29 Well, I just think that on a small scale it's the approach most in keeping with people being "free." It's all voluntary, etc. I think over time, though, there's a tendency for power and wealth to "centralize," and when that gets extreme then the big guys can use that power to manipulate people. So even though the people are still technically "free," they're still being manipulated, and that's undesirable. 2022-04-05 09:44:32 I think the idea system would somehow limit that growth of centralized power. When you get to a point where some market only has a handful of big companies operating in it, it can be problematic. 2022-04-05 09:44:51 Ideally there would be a huge number of competitors, and that forces them to think in a a "customer first" sort of way to survive. 2022-04-05 09:45:13 But... it's not like I'm some sage wise philosopher. I don't really know. 2022-04-05 09:45:56 Meanwhile, that doesn't say anything at all about whether or not we redistribute some of the wealth to help out the people who fall through the cracks. That's a completely different question. 2022-04-05 10:12:34 how many stuff I need in a word header? 2022-04-05 10:12:50 I can just think about { name len, name, immediate flag, } 2022-04-05 10:13:47 I think to put the vocabulary it belongs to in the header would be to duplicate data and I don't see the need, but I know I'm missing more stuff 2022-04-05 10:14:23 the name len would help a bit when searching as I can check first if the len matches and skip that word if it doesn't 2022-04-05 10:15:51 You need 1) name (usually count byte and character bytes), 2) a way of finding the next word in that list, 3) pointer to code for handling the word (CFA) and 4) a way to find the "contents" of the word (value for a variable, definition for a : word, etc.) (PFA). 2022-04-05 10:16:20 A week ago or so we decided that putting the name last in RAM order is best; it's the "variable part. 2022-04-05 10:16:44 I think the ideal order for me would be PFA pointer, CFA pointer, link field, count byte, name chars, padding for alignment. 2022-04-05 10:16:58 And external pointers to the word would designate the CFA. 2022-04-05 10:17:19 I'd put PFA below that because it's an optional field - primitives don't need a PFA pointer. 2022-04-05 10:17:41 So the CFA is the fixed point - forward to the link, forward more to the name, or backward to the PFA if it's there. 2022-04-05 10:18:02 Those are the basics - you might add other things if you wanted to do exotic stuff. 2022-04-05 10:18:18 Some systems have a field that calls out the disk block # the word is defined in. 2022-04-05 10:18:23 That sort of thing. 2022-04-05 10:18:30 the dictionary is an array, so the words would be an element, the next word would be index--, idk if that would be a trobule later 2022-04-05 10:18:55 Ok, then you wouldn't need a "link" field. 2022-04-05 10:19:26 a primary word would be a header + a subroutine which would be executed 2022-04-05 10:19:54 primitive* xD and a colon word would be the same but the subroutine would be an array instead 2022-04-05 10:20:35 I don't need to identify the kind of word it is as I can just check if the subroutine is a procedure or an array 2022-04-05 10:20:56 All words need to point to code. If it's a primitive it's the code for the primitive. If it's a : definition the code is what stores the IP on the return stack and sets it to the new definition. 2022-04-05 10:21:14 but it seems "faster" to encode that in the header so I don't have to call the function which checks the type of the body 2022-04-05 10:21:23 That's the really universal thing - for every single word, the CFA points to code to run that will "get that word's job done or started." 2022-04-05 10:21:45 a colon word would be just a list of index numbers I suppose 2022-04-05 10:21:54 It doesnt have to check the type - you are really specifying the type at conpile time and that's how the CFA pointer gets chosen. 2022-04-05 10:22:15 Variables point to one bit of code, constants to another, : words to another, and primitives are individually specific for each word. 2022-04-05 10:22:22 They each have their specific code. 2022-04-05 10:22:43 A vocabulary word would point to code that puts that vocabulary at the start of the search order. 2022-04-05 10:23:27 I don't like forget anymore since I feel it expensive for forcing me to update all the existing vocabularies 2022-04-05 10:25:19 Well, you can always just say COLD and recompile everything. 2022-04-05 10:25:26 I think that's where Chuck wound up. 2022-04-05 10:25:39 It is a pain, but on the other hand you still don't wind up "waiting for it." 2022-04-05 10:25:49 btw now I know why I call them primary and secondary, it's how the threaded interpretive calls them 2022-04-05 10:26:14 KipIngram: would end being faster to just recompile everything than updating all the vocabularies? why would I want to do that 2022-04-05 10:26:20 just because it's easier? 2022-04-05 10:26:26 Yes - "primary" / primitives have unique machine code; "secondaries" all share one bit of code but have unique definitions (specified by the PFA). 2022-04-05 10:27:04 Well, if you want to replace some of your compiled code, your only options are to 1) FORGET properly or 2) start over. 2022-04-05 10:27:36 I don't like having to visit every single vocabulary, but I'll do it; it's just what's called for. 2022-04-05 10:27:53 Usually you're not going to have very many vocabularies. 2022-04-05 10:28:09 I'll think about, I'd like to avoid to have to look at every vocabulary, but I don't see a way 2022-04-05 10:28:33 Or you could just not do that - you could just have a FORGET that only works on current vocabulary, and it's up to you not to do things that will break your system. 2022-04-05 10:29:03 If you haven't defined words in other vocabularies then a one-vocabulary forget will work fine. 2022-04-05 10:29:08 nah, forget needs to remove all and a vocabulary needs to not point to a forgotten word 2022-04-05 10:29:23 Yeah, that's the right way to do the job. 2022-04-05 14:33:23 I've developed an itch the last couple of days to fire up one of these "Forth on an FPGA" systems. 2022-04-05 14:33:39 It looks like it's gotten so easy these days. 2022-04-05 14:35:04 I'd prefer one that's done using structural Verilog rather than VHDL or behavioral Verilog. 2022-04-05 14:35:23 I'd like to be able to look at the Verilog and actually see the circuitry. 2022-04-05 14:48:47 decided to learn rust over the last week - wrote an rpn calculator (with command line completion and history) as an exercise - https://asciinema.org/a/484627 2022-04-05 17:07:23 Hey, remember a few weeks ago when I noted that words can play games with >IN to do "loops" in the input stream? 2022-04-05 17:07:43 I just used that to time some aspects of the interpreter operation, and with a little thought I can measure much more. 2022-04-05 17:07:51 I think this would work on almost any system. 2022-04-05 17:07:56 I did this as a quick test: 2022-04-05 17:08:35 : tib-loop dup . 1- .0=; >in off ; 2022-04-05 17:08:51 So .0=; returns if top of stack is zero, and it does not consume the argument. 2022-04-05 17:09:12 So, put a number on the stack and hit enter. 2022-04-05 17:09:56 Then just type tib-loop and hit enter, and it prints out all the numbers from your number down to zero. But it does it the hard way - each iteration it parses tib-loop out of the input stream, looks it up in the dictionary, and executes it. 2022-04-05 17:10:04 Took 22 seconds to run down from a million. 2022-04-05 17:10:38 I can play around with what I put on the command line before tib-loop to explore how fast various parts of the system are. 2022-04-05 17:10:47 All that matters is that that code leave the stack unaffected. 2022-04-05 17:11:23 On the other hand, this: 2022-04-05 17:11:45 : compiled-loop 1- .0>me drop ; 2022-04-05 17:12:04 which loops inside the compiled word, counted down from 10 billion in 20 seconds. 2022-04-05 19:05:36 So, does gforth search the dictionary before it attempts number conversion? 2022-04-05 19:06:23 It appears to - if I do 2022-04-05 19:06:33 : 182300 ." word" ; 2022-04-05 19:06:47 and then run that, it prints "word." 2022-04-05 19:06:53 And doesn't stack a number. 2022-04-05 20:17:24 Ok, I've started a list of "improvements" to make at some point. They cascade, actually - early ones enable later ones. First one will be the header layout change. That will remove the need for the name string count bytes to have the MSB set - that's there now to allow me to "scan backwards" across the names. Once that's no longer there, I'll be able to a true byte-by-byte compare of the two strings, 2022-04-05 20:17:27 which is a great candidate for a primitive using repe cmpsb. 2022-04-05 20:18:02 That's in the innermost loop of find, so it'll probably really pay dividends.