2022-06-04 00:12:37 The very idea seems completely backwards to me. The whole point of Forth is that it simply does a forward scan. 2022-06-04 00:13:58 Why would you want to impose a structure on it that makes it harder to parse? 2022-06-04 10:07:45 I haven't t hought this through awfully well, but I think you could regard almost any assembly language in the way you described. Take x86 assembly, for example - a typical instruction might be "mov rax, [rsi]." You could write that "[rsi] rax mov," and that's Forth like. But the thing is, that "implicit stack" you mentioned is never used to connect instructions. It's used only *within* each instruction, 2022-06-04 10:07:46 and as far as I can tell it would only need to be "one deep." I would say it's not "really" a stack machine unless the stack is used for data coordination over a larger scope. 2022-06-04 12:17:55 forth based assemblers are a neat idea 2022-06-04 12:19:09 some of the 6502 guys would do "5 LDA_X" instead of "LDA 5,X" 2022-06-04 12:20:27 it kind of sucks to have to list the addressing mode in the instruction name especially on something like x86 where there are so many 2022-06-04 12:21:01 Yeah; I concocted some Forth source that let me use a good chunk of x86 instructions. Moves, arithmetic, etc. 2022-06-04 12:21:17 I made no attempt, though, to cover the whole instruction set (and I'm quite sure I never will). 2022-06-04 12:21:47 its also tough with something like MIPS where "add t0, t0, t1" is equivalent to "add t0, t1" 2022-06-04 12:22:49 in that case, you have to assume everything on the stack belongs to the current instruction or jave separate add2op and add3op words which is also not ideal 2022-06-04 12:23:25 IMO, its not that useful to keep anything on the stack between aource lines 2022-06-04 12:24:13 for the assembler im planning, the instruction comes first then the arguments are forth style. machine code is output at the end of evrry line 2022-06-04 12:39:09 Yeah, I tend to agree - assembly instructions are independent of one another. 2022-06-04 12:40:02 What you MIGHT keep on the stack might be addresses, for the construction of loops. So long as you ensured your code had proper "structure" to it (no loops and stuff "overlapping" one another) you could follow Forth's methods of handling that kind of thing, and be spared a symbol table. 2022-06-04 12:40:06 That's how I did it. 2022-06-04 12:40:53 How I'll do it again, when I get around to assembly. 2022-06-04 12:41:51 You just need a little mechanism for handling forward jumps and one for handling backward jumps. 2022-06-04 13:37:50 what I've got is https://git.sr.ht/~remexre/rtf/tree/trunk/item/src/compiler/aarch64/asm.fth 2022-06-04 13:37:57 where I have instructions like add_,_,#_ 2022-06-04 13:38:12 and everything is postfix, but the _'s encode what goes where 2022-06-04 13:38:43 and the syntax of aarch64 asm thankfully makes it pretty unambiguous whether something's supposed to be a register or a constant 2022-06-04 13:43:39 one of the things that has made me wonder about writing an assembler is handling unstructured branches, something made much simpler if one is to write, well, structured assembly 2022-06-04 13:43:48 as KipIngram mentions 2022-06-04 13:51:45 yeah, so far I haven't written anything that needed to store more than one jump backwards at a time, so I've been spared having to think about it :) 2022-06-04 13:57:18 Yes - I think (based on only a limited amount of thinking about it) that handling completely unstructured control flow will require a symbol table and multiple passes. 2022-06-04 13:57:40 It does get a lot easier if the flow complies with a "nested" structure. 2022-06-04 13:58:05 Then you can do basically what Forth does - and there's no symbol table and just one pass. 2022-06-04 14:02:44 You have to "name" the jump targets, since you might refer to them in any order. 2022-06-04 14:03:10 and that inherently limits how many jump targets you may have 2022-06-04 14:03:16 because you have to allocate space for them 2022-06-04 14:03:32 Yes, though these days that shouldn't be an issue, really, should it? 2022-06-04 14:03:39 Well, I guess it depends on your platform. 2022-06-04 14:04:09 I work with microcontrollers 2022-06-04 14:04:18 Even in Forth the stack depth limits how many nested control structures you can have open. 2022-06-04 14:04:26 and by default there is no heap 2022-06-04 14:04:45 Yes, so a big symbol table could be a problem. 2022-06-04 14:05:07 I doubt I've ever nested loops deeper than four or five. 2022-06-04 14:05:17 In anything I've ever programmed. 2022-06-04 14:05:52 with loops it's much easier because you don't need a global table 2022-06-04 14:06:16 I wrote a MySQL database schema for storing the results of SSD performance testing at work, and I included four loop variables in the column spec of one table. 2022-06-04 14:06:19 whereas with symbols, a symbol anywhere in your code can be referenced anywhere in your code 2022-06-04 14:06:25 I've never used it for more than two, though. 2022-06-04 14:06:33 Right. 2022-06-04 14:07:07 so you need far more room with symbols than you need with keeping structured code's branch addresses on the stack 2022-06-04 14:07:22 I guess maybe you could avoid multiple passes even in the general case if you set your data structures up right. 2022-06-04 14:07:39 You'd just have to have lists for each forward reference item, that you could go close out when that item became defined. 2022-06-04 14:07:56 Making two passes just simplifies the bookkeeping. 2022-06-04 14:08:10 multiple passes are unnecessary if you have a forward reference table for everything 2022-06-04 14:08:15 Yeah, I agree. Just by not storing any "names" you're ahead. 2022-06-04 14:08:21 Right. 2022-06-04 14:08:36 When you discover the address of a symbol, go back and patch everywhere it was used. 2022-06-04 14:08:53 Though if you have instruction variants that take multiple operand sizes, that might be an issue. 2022-06-04 14:09:09 When you come to a forward jump, how do you know if it's a byte offset or a word offset, etc.? 2022-06-04 14:09:18 You can't know, until you define the label. 2022-06-04 14:09:20 you record the kind of instruction in the table 2022-06-04 14:09:27 And those instructions might be different sizes. 2022-06-04 14:09:34 you assume the biggest distance possible 2022-06-04 14:09:46 But then you'd have to adjust everything in between downward. 2022-06-04 14:09:50 I agree you could do it. 2022-06-04 14:09:52 But what a mess. 2022-06-04 14:09:58 it is messy 2022-06-04 14:10:07 Just biting the second pass is probably a lot simpler and less error prone. 2022-06-04 14:10:11 But... you could do it. 2022-06-04 14:10:15 which is why I haven't written a Forth assembler 2022-06-04 14:10:17 if you're not adjusting the size, I think you could also just make it the linker's problem (if you're generating object files) 2022-06-04 14:10:39 Oh, that's a possibility too, I guess. 2022-06-04 14:10:55 I've only thought about it in terms of writing a "final result." 2022-06-04 14:11:04 but I've decided now that I will write a Forth assembler, for Thumb-1 at least (Thumb2 has just too many instructions) 2022-06-04 14:11:08 But yeah - linkers let you shuffle stuff around. 2022-06-04 14:11:24 not one to generate standalone binaries 2022-06-04 14:11:35 but for inline assembly 2022-06-04 14:11:40 with no symbol tables 2022-06-04 14:12:02 yeah, I'm designing for a linker-less design right now (I'm making an AOT-able, cross-compileable Forth), but i suspect if I ever need mutual recursion i'll need it 2022-06-04 14:12:19 oh, if it's for hand-written asm i might be tempted to just store addresses in VARIABLEs and smudge them after 2022-06-04 14:12:25 if the control flow was really that complex 2022-06-04 14:12:38 for mutual recursion in zeptoforth I just implemented DEFER 2022-06-04 14:12:48 yeah, that's what I've got now too 2022-06-04 14:13:17 this compiler is kinda an experiment in how little UB I can have while still being able to do "classical" compiler optimizations, so I do kinda want to be able to inline thru DEFERs at some point 2022-06-04 14:13:50 like if I do : foo ['] bar ['] deferred DEFER! deferred ; 2022-06-04 14:13:58 I should be able to inline bar into foo 2022-06-04 14:15:35 this is very very nyi tho 2022-06-04 14:16:33 um you're running DEFER! at runtime 2022-06-04 14:17:00 so there's no way a compiler could inline bar into foo 2022-06-04 14:17:29 llvm can; it can prove that deferred will always contain bar at the point that it's called 2022-06-04 14:17:40 so it's legal to inline it 2022-06-04 14:18:49 I wonder if LLVM can solve the halting problem 2022-06-04 14:19:55 the halting problem is necessary to be able to inline thru defer in _all_ cases; in this case, it's way simpler than that 2022-06-04 14:21:45 you can pretty much get there with reasoning that reading from memory you just wrote to gets you back the same value 2022-06-04 14:21:58 and being able to inline ['] something EXECUTE 2022-06-04 14:26:38 think about it this way 2022-06-04 14:26:50 let's say you've got a multitasking environment 2022-06-04 14:27:20 and you have another task which executes ['] baz ['] deferred DEFER! 2022-06-04 14:27:43 you can't trust that memory that you've just written to will have the same value when you read it 2022-06-04 14:27:55 that's where UB comes in /shrug 2022-06-04 14:28:01 if you're gonna do that, you need to explicitly synchronize 2022-06-04 14:28:03 of course C compilers do this 2022-06-04 14:28:14 but that's why they have volatile 2022-06-04 14:28:36 uh my understanding is that volatile does not do this 2022-06-04 14:28:40 what volatile would mean here is, 2022-06-04 14:28:57 : foo2 ['] bar ['] deferred DEFER! deferred ['] baz ['] deferred DEFER ! ; 2022-06-04 14:28:58 volatile is to make no assumptions about RAM's contents 2022-06-04 14:29:37 w/ volatile, it is still legal to inline bar, but it's _not_ legal to optimize away the DEFER! of bar 2022-06-04 14:29:53 think about it this way 2022-06-04 14:29:55 volatile makes memory reads and writes count as side effects in the abstract machine 2022-06-04 14:30:06 tabemann: That's really almost all you need in a lot of cases. And a simple stack-based looping mechanism is the rest. That's enough to render as a primitive anything you need to in a Forth system. 2022-06-04 14:30:42 what about : foo true if ['] baz ['] deferred DEFER! then deferred ; ? 2022-06-04 14:30:43 I thought the halting problem was provably unsolvable, and also thought that if someone could solve it they'd be able to solve the entire class of hard problems that it's in. 2022-06-04 14:31:32 of course, you could say "hey, I can prove that TRUE always returns -1, so I can prove the branch is always taken" 2022-06-04 14:31:34 tabemann: afaict it's legal to inline there (because it's legal to remove the branch) 2022-06-04 14:31:37 ya 2022-06-04 14:32:21 but what if TRUE isn't a CONSTANT but rather some word that executes arbitrary code to come to a constant value of -1 ? 2022-06-04 14:32:46 then whether that optimization is performed or not depends on whether the compiler can reason that the word always returns non-zero 2022-06-04 14:33:11 see, for this very simple case you end up needing a very complex compiler 2022-06-04 14:33:16 yes 2022-06-04 14:33:45 :-) 2022-06-04 14:34:20 I'm fully aware that I'm going to end up with a compiler that's a few orders of magnitude more code than my last Forth compiler 2022-06-04 14:34:31 See, it's when that sort of issue comes in that it starts to feel to me like it's getting esoteric beyond usefulness. Somewhere in there my "pragmatism neuron" fires. 2022-06-04 14:34:41 Referring here to the possibility of TRUE being non-constant. 2022-06-04 14:35:07 ah, yeah; the standard library's true is of course just a constant 2022-06-04 14:35:32 but if you define your own (maybe call that truuuu?) then of course it needs to do analysis 2022-06-04 14:35:57 in practice you're severely limited by the fact that any non-trivial analysis is limited by the halting problem 2022-06-04 14:36:10 you need a more restrictive language than Forth 2022-06-04 14:36:18 like Haskell or Agda or Coq 2022-06-04 14:39:24 I still think you're conflating being able to always do these optimizations vs being able to do it often enough to matter 2022-06-04 14:40:18 (also, Haskell _is_ Turing-complete, and I bet the average piece of Haskell makes it more difficult than the average piece of C to determine halting, thanks to pervasive use of fixpoints) 2022-06-04 14:40:36 Sure - you can just FORCE a case to halt with some sort of a timeout or count-out. Then you can guarantee it halts. 2022-06-04 14:41:09 remexre: Haskell is Turing-complete, but its type system is not, and it's easier to reason about because of a lack of side effects 2022-06-04 14:41:34 but yeah 2022-06-04 14:41:41 you really need Agda or Coq for this kind of thing 2022-06-04 14:41:50 uh GHC Haskell's type system is absolutely turing complete 2022-06-04 14:42:14 Haskell 2010's I think may not be? but like, ~no real-life code doesn't depend on something that uses GHC features somewhere 2022-06-04 14:42:29 it's been a few years since I've kept track of the GHC world, so that could be the case now 2022-06-04 14:43:51 anyway, in practice, optimizing C compilers do exist, and not all of them are gcc/clang large (take cproc, for example) 2022-06-04 14:44:17 yea its not hard to make a tc type system 2022-06-04 14:44:47 I guess Forth's answer to this is "Write your code optimally." I.e., the programmer takes care of it. 2022-06-04 14:45:21 tabemann: there's a whole article about it, https://vitez.me/hts-language 2022-06-04 14:45:32 An awful lot of work over the years has gone toward reducing the skill level required to get various things accomplished. 2022-06-04 14:45:42 yeah, there are a lot of cases where optimial code looks really gross though 2022-06-04 14:45:44 with zeptoforth I basically special-case CONSTANT's and code byte-equivalent with them (i.e. if you write : FOO 255 ; it's equivalent to 255 CONSTANT FOOO) and doesn't bother with anything else 2022-06-04 14:45:51 for example, I represent my IR as a (fairly shallow) tree 2022-06-04 14:45:55 remexre: Very true. 2022-06-04 14:45:56 where you use the visitor pattern to go over it 2022-06-04 14:46:54 if I write a visitor that e.g. just counts how many indirect jumps there are, I can generate much simpler code if I inline the other cases (because they're empty) 2022-06-04 14:47:08 the issue with the whole 'TC precludes analysis' is that these fancy languages induce a lot of complexity for relatively shitty results 2022-06-04 14:47:53 And also one of the beauties of Forth is that it's simple enough that it's easy to understand every stitch of it. 2022-06-04 14:47:57 eris[m]: that's what happens when you try to treat Haskell as a dependently-typed language minus the totality checker 2022-06-04 14:49:09 and yes, if your language is dependently typed without a totality checker it does have a Turing-complete type system 2022-06-04 14:50:18 with all the fancy analysis these languages are meant to give you'd expect concrete improvements 2022-06-04 14:50:44 but they're so damn abstract 2022-06-04 14:52:14 Have you seen Lancet? It adds some primitives to Java to let you control the JIT's optimizations 2022-06-04 14:52:25 eris: I agree. It's really the same all over the software world, though. 2022-06-04 14:52:32 a big issue i take with compilers is a lot of the effort, codesize, complexity, etc is to fix fuckups 2022-06-04 14:52:42 I was griping about the complexity growth over the years of make tools a week ago or so. 2022-06-04 14:52:52 Everything just seems to get ever more obtuse. 2022-06-04 14:53:11 make feels like the wrong abstraction for the modern day 2022-06-04 14:53:22 I sometimes describe as the development of a "priesthood" around the specific arena. 2022-06-04 14:53:30 so your build tool layers are fixing fuckups 2022-06-04 14:53:35 well, 'fixing' 2022-06-04 14:53:49 the experience that's really stood out to me has been optimizing common lisp code with the SBCL compiler; you typically set optimization level per-function, and once you set a high enough "runtime speed" goal for a function, you start getting "perf warnings" where the compiler properly communicates back to you what things it can't prove that are preventing optimizations 2022-06-04 14:53:53 why should a compiler fix TRUE IF 2022-06-04 14:54:06 when TRUE is inlined from e.g. a visitor 2022-06-04 14:54:36 Why would anyone ever WRITE TRUE IF? 2022-06-04 14:54:44 ok, why is it inlining code? 2022-06-04 14:55:06 KipIngram: eg { DROP TRUE } ['] foo DEFER! 2022-06-04 14:55:09 eris[m]: performance 2022-06-04 14:56:02 why cant you do that? 2022-06-04 14:56:05 Well, is that something I'm likely to write? 2022-06-04 14:56:41 Rather, that "someone" is likely to write? I have a hard time seeing myself writing it. 2022-06-04 14:57:38 depends on how much abstraction is present in your code, I suppose? 2022-06-04 14:57:39 the big question to me is why does the compiler need to be that smart 2022-06-04 14:57:42 Expecting the compiler to suss out every case I could possibly contrive seems like setting the bar awfully high. 2022-06-04 14:57:47 yea 2022-06-04 14:58:08 I just want the programmer to be smart. 2022-06-04 14:58:30 And have understandable tools with which to do whatever he/she wants. 2022-06-04 14:59:05 I'm gonna keep coming back to a visitor as my example, because I've got one in hand; IMO it's better code to have a visitor and use it for your 5 different traversals than to write the whole traversal 5 times 2022-06-04 14:59:24 the kind of smart I want out of my compiler is things like nowing to encode 1 + as ADDS R6, #1 2022-06-04 14:59:32 *knowing 2022-06-04 15:00:21 Or INC R6, in the case of 1. 2022-06-04 15:00:32 But in Forth I'd just write 1+ 2022-06-04 15:00:42 I mean, that's why 1+ exists. 2022-06-04 15:00:54 but what if you have 1 CONSTANT FOO 2022-06-04 15:00:57 and you do FOO + 2022-06-04 15:01:04 I wouldn't. 2022-06-04 15:01:11 you want it to turn that into ADDS R6, #1 2022-06-04 15:01:19 Unless I thought FOO might not be 1 in the future. 2022-06-04 15:01:56 let's say you've got a set of bits: 2022-06-04 15:02:03 0 BIT CONSTANT BIT_FOO 2022-06-04 15:02:11 1 BIT CONSTANT BIT_BAR 2022-06-04 15:02:17 2 BIT CONSTANT BIT_BAZ 2022-06-04 15:02:27 and then you do BIT_FOO OR 2022-06-04 15:02:32 And most Forths allow something like find foo pfa ! 2022-06-04 15:02:33 you want it to turn that into: 2022-06-04 15:02:44 MOVS R0, #1 2022-06-04 15:02:48 ORRS R6, R0 2022-06-04 15:02:48 But now I'm contriving things. 2022-06-04 15:03:14 my Forth assumes constants stay constant 2022-06-04 15:04:02 Yeah - like I said, that was deliberately obtuse of me. 2022-06-04 15:04:31 But in all of these cases I'd just say that if I actually need the most stellar performance possible, it's on me to take what I know about my application and write the best code. 2022-06-04 15:05:03 I grant you, though, that that can sometimes make the code more opaque. 2022-06-04 15:05:39 What you're asking for would let one write code in the easiest to understand way and still give you the best performance. 2022-06-04 15:06:06 having to remember all the bits' bit offsets for hardware registers get old fast 2022-06-04 15:06:10 I just think there's a fairly high price to pay for getting that, though, in terms of the complexity of the system itself. 2022-06-04 15:06:30 it's very nice to have 2 BIT CONSTANT BIT_BAZ 2022-06-04 15:06:51 rather than having to remember that BIT_BAZ is bit 2 2022-06-04 15:07:17 but when you do this 2022-06-04 15:07:28 you want BIT_BAZ to be optimized away whenever possible 2022-06-04 15:07:30 see, you can alleviate that with notes 2022-06-04 15:07:38 comments 2022-06-04 15:07:47 I would profile my code, and only spend the extra attention on the important slices. 2022-06-04 15:07:54 Which are usually pretty contained. 2022-06-04 15:08:00 it's a trivial optimization really 2022-06-04 15:08:13 Yeah, I was just thinking that would seemed fairly straightforward. 2022-06-04 15:08:29 and much more useful than knowing that TRUE IF FOO THEN can be reduced to FOO 2022-06-04 15:08:30 Anyway, I get it - there's nothing wrong with wanting this and trying to implement it. 2022-06-04 15:09:07 I've done some things of the same nature in my system. Not exactly the same, but sort of the same in terms of adding complication to the system. 2022-06-04 15:09:07 because in most cases where you have x IF y THEN x isn't constant 2022-06-04 15:09:58 Like I put a fair bit of complexity into my error recovery system, just because I value having a particular behavior. 2022-06-04 15:10:28 In my case I wanted to be in a position post-error to repeat the entire line of code that threw the error. 2022-06-04 15:10:57 So that after I have command history in there I can just note the error and simply up-cursor, fix the error, and hit Enter. 2022-06-04 15:11:53 It's not so much that it's WAY complex - it's a little complex. But it's WAY costly in terms of memory, because in order to implement it at least reasonably simply I snapshot my entire system prior to interpreting each line. 2022-06-04 15:13:05 In the end, Forth is for doing whatever you want with. :-) 2022-06-04 15:13:15 There really aren't any "wrong" things to do. 2022-06-04 15:15:46 yeah, I simply can't do stuff like that with zeptoforth, where memory is measured in kilobytes 2022-06-04 16:44:43 its interesting to me to even think about forth like this. in the worst case performance-wise where your stack width is larger than your registers like on 6502 you end up burning 90%-95% or more cycles on just overhead 2022-06-04 16:45:34 so being smart enough to know when to stick a 1+ in there is completely meaningless 2022-06-04 16:47:16 the guys still doing that type of forth just rewrite the hottest 5-10% if the program in assembly since thats where most of the program spends its time anyway 2022-06-04 16:50:47 at least if we're talking about something other than full optimization like mecrisp 2022-06-04 19:48:58 Anybody have experience installing Pfe on a Linux box? 2022-06-04 22:14:39 a basic forth mostly needs, a peek/poke, simple math, compare, conditional branching, bitwise, ... 2022-06-04 22:15:09 and two stacks :p 2022-06-04 22:24:52 can there be a simple bytecode then? 2022-06-04 22:26:34 or am I making stuff up 2022-06-04 22:26:48 there is such a thing as token-threaded Forths 2022-06-04 22:27:11 cool, what are some usual tokens? 2022-06-04 22:28:40 what if I want immediates and addys for example to be expressed with 0-9a-f 2022-06-04 22:28:48 now I'm making stuff up 2022-06-04 22:29:04 nmm? 2022-06-04 22:29:17 what's nmm? an interjection? 2022-06-04 22:30:23 new memory model? 2022-06-04 22:33:00 not much man 2022-06-04 22:35:08 noy my meaning 2022-06-04 22:35:24 hmm? 2022-06-04 22:43:12 its a mistype of mmmm 2022-06-04 22:46:06 o thx 2022-06-04 22:46:22 so eh, that could result in a bytecode 2022-06-04 22:46:37 except I'm more interested in a traditional approach 2022-06-04 22:47:37 Anybody have experience installing Pfe on a Linux box? 2022-06-04 22:56:39 @ peek : poke + add - sub x mul < shl > shr % mod/div / cmp ! jmp/jxx & and | or , push . drop ^ pop # push_imed $ push_rela * deref 2022-06-04 22:56:48 just dreaming of symbols at this point 2022-06-04 23:01:06 to explain the idea a little I just want a tiny bytecode kernel capable of loading something more complex 2022-06-04 23:05:12 and probably the first thing it would load is an assembler 2022-06-04 23:12:44 here's one little hack if you're making a bytecode... devote a portion of the unused bytes to numeric constants 2022-06-04 23:13:03 and some to adding or subtracting or bitshifting by numeric constants 2022-06-04 23:14:18 so that for common literals like 0, 1, 2, 4, 8, -1, -2, -4, -8 and operations such as 1+ 2+ 4+ 1- 2- 4- 2* 4* 2/ 4/ you won't need to devote extra space in the code to store the literal 2022-06-04 23:21:38 for some reason I kind of like pointing out when I have numbers in my code other than 0-1, because they appear to be magic numbers 2022-06-04 23:34:00 so I have about 16 operators, mebby it's time for a nybble code