(Comp.sys.handhelds) Item: 2282 by bson at rice-chex.ai.mit.edu Author: [Jan Brittenson] Subj: Some HP-48 Internals Answers Date: Fri Mar 01 1991 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 system 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 system 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 system 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 system RPL, I was of the belief that the > system 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 system 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 system 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