bson@rice-chex.ai.mit.edu (Jan Brittenson) (02/28/91)
Joe Ervin asked me some questions in a letter. It seemed to me that the questions were fundamental enough to be answered on the net - I haven't seen any in-depth discussions of these topics before; I hope Joe will excuse me, and the rest of you bear with me. If you hate everything related to HP-48 internals, hit the proper Junk key now. I will stick to STAR syntax and AG mnemonics. > In all the examples of internal RPL I have seen, I have NOT seen any > loops or IF-THEN constructs, although I believe that Bill Wickes has > IF-THEN in his ASC routines. I recently posted a set of FFT-aiding routines here on comp.sys.handhelds. Two of them use if-then constructs, and one has a loop as well as local variables. The latter took quite some exper- imentation to get right! Here is one of them, it collapses an array into a one-dimensional list (I have reduced it to bare bones here - let's not bother with the actual addresses): RPL Prg ; PROGRAM SaveLast_Need_1_arg ; Housekeeping stuff ArrayTo_array ; Explode array ListAlgPrgTo ; Explode dimensions list Equal2p If_then_else ; Then: (real->short) * (real->short) Prg Real_to_short_Swap Real_to_short Mul End Real_to_short ; Else: real->short ToList ; Combine to list End ; END ENDRPL Now, broken down step by step: RPL Enters STAR RPL mode. Prg Start of program object (2d9d). SaveLast_Need_1_arg Makes sure we have at least one argument. ArrayTo_array Explodes the array - ARRY-> on an array. This yields a list of reals containing dimensional info, e.g. {2 3} for a 2-by-3 array. ListAlgPrgTo Explodes the list - LIST-> on a list. Like the user-mode command, expcept the the counter on level 1 is a short. Equal2p If level 1 is the short 2, then return True, else False. True is #3a81 and False is #3ac0. These both appear as "External" on the stack, and can't be entered from user code. Like shorts (system binaries) are preferred internally for small integers, such as the list count above, True and False is the system RPL preferred way of passing around boolean flags. If_then_else (#61ad8) If level 1 is True (#3a81) then execute the next object, otherwise execute the second next object. Prg Start of program object (2d9d). This program object is the then-clause. This gets evaluated if the list contained two elements, i.e. was 2-dimensional. Otherwise it was one- dimensional. Real_to_short_Swap (Then-clause) Convert a real (from the array dimensions list) to a short and do a swap. Real_to_short (Then-clause) Convert the second real to a short. Mul (Then-clause) Multiply the two shorts. Notice that we could just as well have multiplied the two reals (using the user- code real multiplication function) and then converted the result to a short. But short (20-bit unsigned integer) mulitplication is magnitudes faster than real (64-bit BCD) multiplication. End End (#312b) of the then-clause program object. Real_to_short This is the else-clause. The array was 1-dimensional, so we just convert the size to a short. ToList Finally we put it all back to a list. The item count on level 1 is assumed to be a short. End This is the end of our program, and the end of the object. ENDRPL Signals end of STAR RPL mode. > It looks like in the internal RPL, you cannot simply push an object > onto the stack the way you do in user code. Sure you can. System RPL executes almost identically to user RPL. The big difference is that you can't automatically interrupt it with ATTN - the system RPL has to explicitly check for this. System RPL also has a lot of constructs for implementing control and branch functions - you can, for instance, write the If_then_else above in system RPL by calling more primitive functions like Skip (which skips the next token in the *caller's* thread), or pick the next token from the caller's thread, or evaluate the token in the caller's thread. A token here is either an object or the 20-bit address of an object. In fact, If_then_else *is* written in system RPL if I remember correctly. There are more specialized examples; we could, for instance, have written an If_equal2p_then_else function if we wanted (and had the option of putting it in a fixed location in ROM!). > This is apparantly because internal RPL consists strictly (I think) of > nibble addresses. Because of this, you can't really put an object > into the thread, because the "threader", or whatever you call the code > which does the JUMP @(next rpl token) won't know what to do with an > object it wasn't expecting. A token is always either a pointer to a _type_ or an object, in which case its first subtoken, the prefix, is a type. There is never more than one level of indirection here. The types (#2d92 for programs, #2a2c for strings, #2dcc for code, etc) all begin with #28fc, which indicates that "this is a type." The code at #28fc implements the semantics of the _type_. What it mostly does is to call the type-dependent code implementing the type. I have to confess I am on very weak ice here, so don't take this as the ultimate truth, it's merely based on my observations. It's fairly clear that what most data types do, is to simply push themselves on the stack. Then there are others (lists and symbolics) that sometimes push themselves on the stack, and sometimes behave as programs. Finally there are those that always result in code being executed - programs (#2d9d) and code (#2dcc). Then we have global and local names that evaluate their values. I think that exactly how this works is best illustrated by an example. The code at #28fc looks like this: move.p2 10, c sub.a c, a jump @a Assume that the next token is a program object. Then the sequence: move.a @d0, a add 5, d0 jump @a Will 1) put the prefix (#2d9d) of the program in A. 2) Advance the pointer (which now points immediately following the prefix). 3) Read the contents of #2d9d (which is the "type" prefix #28fc) and jump there (i.e. to #28fc). Upon arriving at #28fc, A will contain #2d9d. 10 is subtracted from it (the #28fc "type" prefix at #2d9d is preceded by two addresses that implement certain semantics of "programs") to yield the address of a pointer to the Eval-specific semantics for "programs," i.e. a piece of ML code that does whatever "programs" are supposed to do when they are executed (EVALed). This is where it then jumps. D0 still points to the word following the prefix, and A is #2d9d minus 10. As to the composition of RPL objects, I would recommend you to take a look at one of Derek Nickel's postings which explicitly addresses this. It should suffice to mention here that program, symbolic, and list objects are almost identical apart from the prefixes. > There are some RPL tokens that require an argument, and the cases I > have seen of this always have the argument appear as the next token. > This is the case for the "type_check" token which appeared in your > recent example code, and I strongly suspect that this works with some > other tokens. Quite correct. Sometimes it's even more complicated, like the type check routines that take any paired sequence of short tokens and tokens to be executed if a match was made; up to an end (#312b). If a match is made the matching process ends and the subsequent token is executed. Otherwise the current address (in D0) is advanced to the next pair. When it reaches an end (#312b) a "bad argument type" error is issued. Some of these type matching functions also play tricks with the LAST ARG data. > When I started looking at internal RPL, I was of the belief that the > internal RPL would be a lot like the user RPL. This does not appear to > be true. It seems that for any looping structure or IF-THEN structure, > or any more complicated structure, it looks like the internal RPL will > require different techniques. They are not really that much more complicated, just undocumented and slightly different. They sometimes require knowledge of exactly how they work to be taken full advantage of. One illustrative example is loops. My understanding is that they basically consist of a head and a tail. The head enters the loop (similar to START...NEXT in its simpler form) and the tail terminates it. Now, what the head does more exactly is to set the return address *to itself* and call the first word of the loop. The end of the loop basically does an END, which causes it to return to the loop head and reexecute it! Since a loop call frame looks like any other call frame, the possibilities are almost endless for implementing you own specialized NEXT, or STEP, or UNTIL, or whatever your heart may desire. The tail can exit the loop by unwinding one call frame level and continuing. You can, if you like, implement a specialized NEXT (or other loop controls) and put it in a global variable. You can even implement specialized IFs, skips, what may you desire, as well, by putting them in a global variables. A small example (make sure your HP-48 is set to HEX!): Key in PUSH (ASC format) and de-ASC it: "CCD20310008F146608DC2016C1CD" Type: #61ad8 #5a03 SYSEVAL @ --> <61ad8> PUSH @ --> External 'IFFTE' STO Now we have stored the If_then_else routine (#61ad8) in IFFTE, or more precisely, a pointer to it. Type in the following program (notice that True (#3a81) and False (#3ac0) push themselves on the stack when evaluated): << IFFTE "TRUE" "FALSE" >> 'X' STO Then let's try it out (the @#XXXX notation used means "the object at address XXXX" - the display will actually read "External" in our example below): #3a81 SYSEVAL @ --> @#3a81 (True) X @ --> "TRUE" DROP #3ac0 SYSEVAL @ --> @#3ac0 (False) X @ --> "FALSE" Voila! A new user RPL control structure! Notice that our user program X above is identical to the following (STAR syntax), with the exception that << and >> tokens have been added to the user program; also note that an initial "_" character escapes the implicit STAR DATA.A of RPL mode: RPL 2d9d ; Program object _global `IFFTE' ; Token: Global symbol object _string `TRUE' ; Token: String object _string `FALSE' ; Token: String object 312b ; End ENDRPL For clarification, the global name and strings above are made in-line objects, not pointers. > In fact, it appears that for many tasks, the normal user code is much, > much simpler, and will probably run nearly as fast. Simpler because they are more well-documented, yes. But not faster by a long shot! System RPL mostly makes type checking superfluous, except initially, and does most of its arithmetic using 20-bit integers. > Surely there are many highly useful ROM routines that will be nice to > use, but aren't those generally accessible from user code using > SYSEVAL? Yes, of course. But who can read a program consisting solely on SYSEVALs? Binary integers and SYSEVALs take a lot more space and are much slower, although they can be "compiled" using Joseph Horn's PACK program. I personally tend to test algorithms and generally make prototypes in user RPL, and then simply recode it in system RPL for robustness, speed, and size. Not everything of course, I find it generally desirable to code the "workhorse" routines in system RPL and call them from user RPL that can easily be customized. > One key advantage with internal RPL for me is that it can hold a CODE > object; a feat of which user code does not appear to be capable. This is most useful indeed. And while you're at it, with assemblers and all, you may just as well do the rest in system RPL... Or at least embed the ML in some argument checking. > I will refrain from asking you anymore direct questions for now, for > fear that I will totally overwhelm you. Au contraire; I'm glad you posed these questions. I remember wondering over this too, and I'm sure a lot of people want to know. I don't claim to know that much about how the HP-48 works; Rick Grevelle and a dozen other net people probably know much more than I do. Perhaps explaining the basics will get others started, I hope so - which is why I spent a little extra effort on this reply (as well as posted it to the net). I hope I am correct - I'm sure I will quickly be corrected if I'm not! -- Jan Brittenson bson@ai.mit.edu
akcs.dnickel@hpcvbbs.UUCP (Derek S. Nickel) (03/03/91)
Jan Brittenson writes: >posted it to the net). I hope I am correct - I'm sure I will quickly >be corrected if I'm not! You also made a comment anout "very weak ice"... The value 028FC is a "prolog signature", not an address of some code. Try unassembling (not unthreading) at, for example, 02911 and you will see that the fist two instructions (D=D-1 and some HST stuff) are "unusual", if fact the HST stuff is a NOP. But those first five nibbles are used by other parts of the OS to identify a prolog object. Is this clear (I fear its not). My point is that 028FC is not an address but actual machine code. (unassembling at address 028FC only leads to madness - trust me - I tried it before I realized what was going on). Another point: these prolog addresses (like 02D9D) are just like prefixed machine code, only the "prefix" is far removed from the routine that it is prefixing, but the data is close at hand (data follows the prolog prefix). exam[;es examples: 02D9D program prolog (machine code at 02D9D) program data 0312bB End Marker... (ugh! this example is getting out of hand - maybe later) My most recent "HP 48SX Internals" manual covers this stuff with an example. Derek S. Nickel ,
bson@rice-chex.ai.mit.edu (Jan Brittenson) (03/04/91)
In a posting of [3 Mar 91 06:40:04 GMT] akcs.dnickel@hpcvbbs.UUCP (Derek S. Nickel) writes: > The value 028FC is a "prolog signature", not an address of some code. > Try unassembling (not unthreading) at, for example, 02911 and you will > see that the fist two instructions (D=D-1 and some HST stuff) are > unusual", if fact the HST stuff is a NOP. But those first five > nibbles are used by other parts of the OS to identify a prolog object. This is what it looks like: 028fc Type: 028fc 31a0 move.p2 #a, c 02900 b6a sub.b c, a 02903 808c jump.a @a 02907 82c20 data.a span10nib ; ; #2c28 0290c e8030 data.a L_0308e 02911 type_Short: 02911 cf820 TYPE 02916 487 brcs L_0298f 02919 1c4 sub.a 5, d1 0291c 141 move.a a, @d1 0291f 142 move.a @d0, a 02922 164 add.a 5, d0 02925 808c jump.a @a 02929 94c20 data.a span21nib ; ; #2c49 0292e f7030 data.a L_0307f 02933 type_Real: 02933 cf820 TYPE 02938 465 brcs L_0298f 0293b 1c4 sub.a 5, d1 0293e 141 move.a a, @d1 02941 142 move.a @d0, a 02944 164 add.a 5, d0 02947 808c jump.a @a Where is the "D=D-1 and some HST stuff"? (D=D-1 f = DEC.f D; "f" is the field) The prefix at #2911 is #28fc. If A contains #2911, then the threading instruction "JUMP.A @A" will jump to #28fc. At #28fc, 10 (decimal) will get subtracted from A, after which it will contain #2911-10 = #2907. The "JUMP.A @A" then jumps indirect #2907, which is #2c28, where a spanning routine for 10-nybble objects resides. D0 (which points to wherever the #2911 was found, i.e. where the object is) is advanced by 10 past the 10-nybble object. Similarly, the ML code at #2c48 advances D0 by 21, which is the size of a real. > Is this clear (I fear its not). My point is that 028FC is not an > address but actual machine code. (unassembling at address 028FC only > leads to madness - trust me - I tried it before I realized what was > going on). - Mr. Spock, any madness readings? - No Sir. Just plain logic. -- Jan Brittenson bson@ai.mit.edu Read my lisp: no new classes!
jurjen@cwi.nl (Jurjen NE Bos) (03/04/91)
[A discussion on whether 028FC is an address or two instructions CF and 820] Well guys! Stop arguing! You both don't understand it. It is Both! That's the fun of the whole RPL. Look at the two examples: Program Program ... ... 2A2B4 02911 <digits> ... ... End End Both programs contain a real. The left program contains a 0, which is on address 2A2B4 in ROM. The right program contains the real directly. Both programs execute the same way, that is that an instruction gets executed by the well-known sequence: MOVE.a (D0),A ADD.a #5,D0 JUMP (A) In the left case, this sequence jumps to the address 2911, executing code that pushes the value 2A2B4 on stack (a pointer to the real). This sequence starts with decrementing D (the free space counter), and does this effective NOP 820. In the right case, the sequence jumps to 28FC, executing code that jumps indirect to the address at 2911-#10, which is 2C28. This code pushes the address of the real on stack, just as the previous case. Understood?
akcs.dnickel@hpcvbbs.UUCP (Derek S. Nickel) (03/06/91)
Jurjen, I don't think we were arguing... Just "networking". Tossing ideas back and forth (be sure to duck) is what this group is about, no? Posting your ideas gets them out in the open (so they can be blasted apart by the next poster). Sooner or later it will a come together (the imfamous "Unified HP48SX Therom"). And, thank you for your comments. Derek S. Nickel