Author Topic: Variable Subroutines  (Read 6670 times)

0 Members and 1 Guest are viewing this topic.

Offline Terrav

  • LV1 Newcomer (Next: 20)
  • *
  • Posts: 7
  • Rating: +0/-0
    • View Profile
Variable Subroutines
« on: January 02, 2021, 04:18:17 pm »
I was designing a modular program executor when I encountered this problem: The code I was trying to execute was not being executed and instead the calculator reset. I've tried and checked everything; I think the problem is the address. Knowing the TI-84+ only has 24KiB RAM, it puzzled me to learn my program was using memory pointers all the way up to DFFF. Everywhere I have tried to execute has an address somewhere between 8000-BFFF. A brief summary of my test code is printed below.

Code: [Select]
"appvTMPFILE"→Str0
GetCalc(Str0,1)→X
[sub]E[/sub]C9→{X}
(X)()
Note: I know 0xC9 is the assembly equivalent of Return.

If I were to try this with the program data itself or a safe zone (like L1) it works just fine.
Displaying the memory pointer just before calling it shows something like C3F2 or D19A.

My questions are, how is this caused, and how do I work around it?

Offline Deep Toaster

  • So much to do, so much time, so little motivation
  • Administrator
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 8217
  • Rating: +758/-15
    • View Profile
    • ClrHome
Re: Variable Subroutines
« Reply #1 on: January 02, 2021, 10:27:22 pm »
Hi Terrav, welcome to Omnimaga! :)
Knowing the TI-84+ only has 24KiB RAM, it puzzled me to learn my program was using memory pointers all the way up to DFFF.
On the TI-83 Plus series (including the TI-84 Plus), memory addresses 0000h–3FFFh point to a flash page and 4000h–FFFFh point to RAM (hence FFFFh – 3FFFh = 24 KiB).
Everywhere I have tried to execute has an address somewhere between 8000-BFFF.
Funny thing—you pretty much discovered the issue.

Addresses C000h–FFFFh point to a RAM page on which you're not allowed to execute code. That's it. Once you jump to an address beyond BFFFh, the calculator resets. (There is a way around this if you're interested, but you might find a way around this issue without unprotecting the page.)
If I were to try this with the program data itself or a safe zone (like L1) it works just fine.
Both of these zones are guaranteed to be below C000h :) In fact, the reason the TI-OS refuses to execute programs larger than 8192 bytes by default seems to be a precaution against going beyond C000h—9D95h (which is where all programs are moved when they start executing) + 8192 = BD95h, which is a bit below the BFFFh limit.




Offline E37

  • LV6 Super Member (Next: 500)
  • ******
  • Posts: 358
  • Rating: +23/-0
  • Trial and error is the best teacher
    • View Profile
Re: Variable Subroutines
« Reply #2 on: January 04, 2021, 01:58:36 pm »
I'm a bit late, but if you want to have a memory area that holds executable code and not use a fixed area, you can use the Buff(<size>) command to create a buffer at the end of the program where you can store code at. Or, if you know what you want to put there, you can simply include the assembly code as hex and jump to the data. Since Axe already lets you directly use assembly code, I assume you are trying to do something else.

You could use the Memkit axiom to create space somewhere you know will be under the execution limit. Its command New( is usually used to resize existing programs or appvars, but it doesn't have to. You can call New( on anywhere you want (although bad things would probably happen if you tried it under 9D95h) to get a safe area of memory. For example, if you have an app you can do New(E9D95, 0, 1000) to create 1000 bytes at the start of where programs usually execute. You can then do:
Code: [Select]
:GetCalc("prgmWHATEVER", Y1) .Get pointer to the program
:Copy(Y1, E9D95, 1000) .Copy it to the ram area. Since programs are normally executed from E9D95, you can use absolute jumps and calls without problem
:(E9D95)() .Run the program. Once it is finished you should use Delete( to restore the 1000 bytes of memory you took. Or you can copy a different program there and run it first
This assumes you are using an app. If you are using a program you are running at 9D95h and bad thing will happen if you try to push it around and write code over yourself. You don't have to use 9D95h. You can use some area after the end of your program instead.

Alternatively, if you are using a ti-84+ and don't care about 83+ compatibility, you could swap the extra ram page into 4000h-7FFFh and just put whatever you want in that 16k block of ram. Then you have 40k of ram to work with! I could be wrong but I'm pretty sure that BCALLs use that block so you should probably swap it back every time you want to read or run from it. Although they may swap it back for you.

I can give more detailed examples and code for any of this, but I don't know what you are trying to do.
I'm still around... kind of.

Offline Terrav

  • LV1 Newcomer (Next: 20)
  • *
  • Posts: 7
  • Rating: +0/-0
    • View Profile
Re: Variable Subroutines
« Reply #3 on: January 04, 2021, 04:18:57 pm »
Thank you for the suggestion; I'm basically trying to create a launcher that can run assembly programs apart from TI-OS. The allowed program size built in my program should be variable, but I guess I should watch out for the 8K program size limitation as well. Also, is there any article about the actual structure of memory/Flash/addresses that I can read? I originally thought it was 24K RAM + 1M Flash and that's it, but it appears there's a lot more I need to learn before I go digging deep in memory.

Offline Deep Toaster

  • So much to do, so much time, so little motivation
  • Administrator
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 8217
  • Rating: +758/-15
    • View Profile
    • ClrHome
Re: Variable Subroutines
« Reply #4 on: January 04, 2021, 04:35:27 pm »
Thank you for the suggestion; I'm basically trying to create a launcher that can run assembly programs apart from TI-OS.
I don't know your architecture so I wouldn't know if you're addressing this issue already, but something to keep in mind is that assembly programs in general must be loaded (moved) to a known location in memory. This is because a common instruction in assembly code is to jump to a label, which is a absolute address (calculated relative to the location the program was loaded to). (The TI-OS loads all assembly programs to 9D95h, as I mentioned.)
The allowed program size built in my program should be variable, but I guess I should watch out for the 8K program size limitation as well. Also, is there any article about the actual structure of memory/Flash/addresses that I can read? I originally thought it was 24K RAM + 1M Flash and that's it, but it appears there's a lot more I need to learn before I go digging deep in memory.
Check out https://wikiti.brandonw.net/index.php?title=83Plus:Memory_Mapping for an overview of how the pages are laid out. (Like you said, there's 24K RAM and 1M flash, but there's only 65,536 addresses (this is called virtual address space), so to access anything beyond the first first 16K (page 0) of flash, pages are swapped in and out as necessary. https://en.wikipedia.org/wiki/Paging#Main_memory_larger_than_virtual_address_space sort of describes the situation the TI calculators are in.

Check out https://wikiti.brandonw.net/index.php?title=83Plus:OS:Memory_Layout for an overview of how the RAM (in general, 4000h through FFFFh in the virtual address space) is organized. If you need the actual values of the addresses listed by name, check out https://wikiti.brandonw.net/index.php?title=83Plus:OS:ti83plus.inc.

In general, I'd recommend taking a gander at Learn TI-83 Plus Assembly in 28 Days if you haven't already. It's a fantastic guide to how assembly programs work.




Offline E37

  • LV6 Super Member (Next: 500)
  • ******
  • Posts: 358
  • Rating: +23/-0
  • Trial and error is the best teacher
    • View Profile
Re: Variable Subroutines
« Reply #5 on: January 05, 2021, 04:19:02 pm »
Here is a subroutine I have thrown together really quickly. I haven't tested it but it should be able to run any program or appvar whose name is passed to it. It needs to be compiled as an app!

:Lbl Execute .Call it like :Execute("prgmNAME")
:Return!If GetCalc(r1,Y1) .Get the variable and return if we can't find it.
:New(E9D95,0,{Y1-2}r-2->r1) .Create space for it to be copied to. Since we don't need the program's name anymore, we can store its size to r1. The -2 is for the program header
:r1:Asm(E5) .Push the value of the program's size. E5 *should* be the code for 'push hl' We need to push it because we don't know if the program we are running will destroy the variable we save it to. You could probably save it to E9D93, but it is safer this way
:Copy(Y1+2,E9D95,r1) .Copy the program to the area of memory we just created. The +2 is to skip over the program header BBh, 6Dh
: (E9D95)() .Run the program
:Asm(E1)->r1 .Get the saved value of size of the program.
:Delete(E9D95,0,r1) .Restore memory to how it was before we copied the program in.
:Return .Done!

Edit: Forgot about the 2 bytes for the program header
« Last Edit: January 05, 2021, 05:47:08 pm by E37 »
I'm still around... kind of.

Offline Terrav

  • LV1 Newcomer (Next: 20)
  • *
  • Posts: 7
  • Rating: +0/-0
    • View Profile
Re: Variable Subroutines
« Reply #6 on: January 05, 2021, 06:08:29 pm »
That looks good, but I can't do that given my Axe version doesn't have New( and Delete( (and also only supports two-character labels).
However, I think I have this figured out now. What I did was put Lbl A0 at the beginning of my code and Buff(1)→GDB3ND at the very end, and use this little subroutine (I left out a lot of code to make it short:
Code: [Select]
.This is my execution subroutine
Lbl X
.Get the program, use X and Y0
Return!If X
If LA0=E9D95
 .We are running in a program
 Return!If {Y0-2}r>EC000-GDB3ND .Too large, can't execute
 Copy(Y0,GDB3ND,{Y0-2}r) .assuming this is a special format
 Goto (GDB3ND) .The program will automatically exit this subroutine
ElseIf LA0/4096=4
 .This is an app, let's just use 9D95!
 Return!If {Y0-2}r>8811 .This is the real limit from 9D95h to BFFFh
 Copy(Y0,E9D95,{Y0-2}r)
 Goto (E9D95)
End .Still haven't bothered working with shells at this point

Of course, this assumes the executable data begins right with the file.

Offline Deep Toaster

  • So much to do, so much time, so little motivation
  • Administrator
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 8217
  • Rating: +758/-15
    • View Profile
    • ClrHome
Re: Variable Subroutines
« Reply #7 on: January 05, 2021, 06:25:27 pm »
The program version has the issue I called out earlier, which is that almost all assembly programs will crash if run from any address that's not 9D95h.

A way to get around this would be to copy the shell's code to a safe RAM area, jump there, move the program you want to run to 9D95h, and then call 9D95h.

Regardless, you can't just Copy() code to 9D95h, because there's likely to be variables there which you would be overwriting. (The TI-OS, as well as New() in E37's code, actually move any variables there out of the way so that that space is safe to use.)
« Last Edit: January 05, 2021, 06:30:21 pm by Deep Toaster »




Offline E37

  • LV6 Super Member (Next: 500)
  • ******
  • Posts: 358
  • Rating: +23/-0
  • Trial and error is the best teacher
    • View Profile
Re: Variable Subroutines
« Reply #8 on: January 06, 2021, 09:13:46 am »
That looks good, but I can't do that given my Axe version doesn't have New( and Delete( (and also only supports two-character labels).
However, I think I have this figured out now. What I did was put Lbl A0 at the beginning of my code and Buff(1)→GDB3ND at the very end, and use this little subroutine (I left out a lot of code to make it short:
Code: [Select]
.This is my execution subroutine
Lbl X
.Get the program, use X and Y0
Return!If X
If LA0=E9D95
 .We are running in a program
 Return!If {Y0-2}r>EC000-GDB3ND .Too large, can't execute
 Copy(Y0,GDB3ND,{Y0-2}r) .assuming this is a special format
 Goto (GDB3ND) .The program will automatically exit this subroutine
ElseIf LA0/4096=4
 .This is an app, let's just use 9D95!
 Return!If {Y0-2}r>8811 .This is the real limit from 9D95h to BFFFh
 Copy(Y0,E9D95,{Y0-2}r)
 Goto (E9D95)
End .Still haven't bothered working with shells at this point

Of course, this assumes the executable data begins right with the file.

To get the New( and Delete( you need to do #Axiom(MEMKIT) assuming you have it installed. If you don't, download Axe again and go to Axe Parser/Tools/Memkit. The tokens are located in the vars menu. When adding axioms, the tokens don't show up as soon as you add the #Axiom( line. You will have to quit and reopen the program to get them to show up.

If you Axe version only supports two character labels you must be using some ancient version. Here, get the current version.

Like Deep Toaster said, you can't just copy code to any location and expect it to run. Your 24k of normal memory starts at 9D95h. There may be a program there already that you are overwriting. Additionally, in the ram version of your program, any kind of complex code won't run from that buffer. If you try to run code that does :Goto (E9D95+20) then it won't goto 20 bytes after the start of its code, but 20 bytes after the start of your code. And then bad things happen.
Additionally the program can be bigger than 8811 bytes large, they just can't have executable code after that. Having data over that limit is fine.


I would also like to pick on your axe code a bit. I get it is going to be messy because you are still trying to get the code to work but I can't help myself. This will just be style and optimization stuff.
First off, you can use up to like 14 upper and lowercase letters for label (and variable) names. No need to make them short like in TI-BASIC.
The first line of your subroutine is Return!If X and X is never used again after that. What's the point?
Your line :If LA0=E9D95 can be turned into a conditional comment. For example:
Code: [Select]
:...If LA0=E9D95  .I'm using a conditional comment. Since this will only ever be an app or a program and never swap, why compile the code for the other?
:.Stuff for program version here
:...Else .No need for an ElseIf here. If we aren't a program, we have to be an app.
:.Stuff for app here
:...
You need parenthesis for  Return!If {Y0-2}r>EC000-GDB3ND. It should look like  ReturnIf {Y0-2}r>(EC000-GDB3ND). Axe has no order of operations and always goes from left to right. I'm not sure why you made it Return!If since you are trying to return if it is greater and not if it isn't.
Again, the ! makes means you are returning when you don't want to and running when you do. Return!If {Y0-2}r>8811 .Why return if it IS less than that size?
In general, it is more efficient to do :!If X-5 instead of :If X=5
You can put the line :[]->GBD3ND at the end of your program instead of Buff(1)->GBD3ND. It gets the pointer to the end of your program just the same and doesn't add that additional byte.
I'm still around... kind of.

Offline Terrav

  • LV1 Newcomer (Next: 20)
  • *
  • Posts: 7
  • Rating: +0/-0
    • View Profile
Re: Variable Subroutines
« Reply #9 on: January 06, 2021, 01:44:58 pm »
OK, I see. I made a mistake in the code. You're right, it is ReturnIf. However, I tried this and it works! as long as the program is just one byte. I tried it with a program that just returns, and it works fine. But when I compiled a Minesweeper game to run with this, it resets either when the program starts, or shortly after (after user input). There's absolutely no difference each time. To make things weirder, when the calculator resets, it deletes a certain appvar I use. What's weird is the appvar is only ever in the RAM immediately after the program finishes, then goes right back into the archive. (I use this var to keep track of which programs were last run) I thought maybe there's a C9h inserted in the wrong spot (my shell is supposed to add a C9 to the end of a program when it executes it for safety) and the calculator resets when it returns to the app without unsetting Fix 7 (my program uses this).

Offline Deep Toaster

  • So much to do, so much time, so little motivation
  • Administrator
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 8217
  • Rating: +758/-15
    • View Profile
    • ClrHome
Re: Variable Subroutines
« Reply #10 on: January 07, 2021, 01:25:40 am »
Sorry to keep prodding at this question, but are you copying the program you'd like to run to 9D95h every time? Are you compiling to a program or as an app? (I'm assuming since you can't use New(), you're compiling as an app?)




Offline Terrav

  • LV1 Newcomer (Next: 20)
  • *
  • Posts: 7
  • Rating: +0/-0
    • View Profile
Re: Variable Subroutines
« Reply #11 on: January 07, 2021, 01:26:09 pm »
Well, I tried both. When I try it from a program compilation method, it goes absolutely nuts. First, it looks like it does nothing. Then, when you exit the shell, it returns back to the program selection screen (in my shell). Then, when you try to exit the shell again, it does one of two things. 1) it stays on with a blank screen and executes a seemingly infinite loop. 2) It goes through what I can only describe it as: the calculator is on drugs. The user input is about 1/4-normal; you can point out various misplaced parts of some recognizable menus; and finally, if you hit On and other keys enough, it "burns out" with that weird blue line across the screen (I've seen this before: see the footnote) and resets after about 2 minutes. I've double-checked that the program I'm working with is within the size range (nothing hanging past BFFFh). This seems absolutely nuts, so as soon as I can I will get the MEMKIT Axiom library and use that.

Footnote: That weird blue line on the screen is very frightening and I've seen it before; that is the MAJOR indicator that you have done something horribly wrong. I believe that may be a side effect of executing bad code, that sends absolute nonsense to your LCD, causing it to shut off and display a blue line across your screen. I only call it "burning out" because it's generally what it does after an absolute overload of bad data, and the line fades away in tatters before eventually returning your calculator to a fresh RAM wipe. I have found nothing about this on YouTube.

Offline NonstickAtom785

  • LV3 Member (Next: 100)
  • ***
  • Posts: 78
  • Rating: +4/-0
  • Just live life. Cal-cu-lat-or style!
    • View Profile
Re: Variable Subroutines
« Reply #12 on: January 07, 2021, 01:56:04 pm »
Wow okay. For one you are wrong about a lot of things right now. You haven't done anything absolutely wrong. You just don't understand what is going on. Let me first say that the Blue Line of Death is really just the LCD receiving an abnormal amount of energy. It's called test mode. Check out this page for more info on that. I would help you more but I'm busy right now.
Grammer2 is Good!

Offline Deep Toaster

  • So much to do, so much time, so little motivation
  • Administrator
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 8217
  • Rating: +758/-15
    • View Profile
    • ClrHome
Re: Variable Subroutines
« Reply #13 on: January 07, 2021, 02:07:19 pm »
Well, I tried both. When I try it from a program compilation method, it goes absolutely nuts.
Terrav, like E37 and I have been saying, this will happen 100% of the time with any program that has any logic (such as any if statements) unless you've done the following:
  • copy your shell's code (that's the code you're writing) to a safe RAM location (such as L1) and Goto there
  • use New() to clear out space around 9D95h
  • copy the program you want to run to 9D95h
  • call the program at 9D95h
If you compile as an app, you can skip the first step.
First, it looks like it does nothing. Then, when you exit the shell, it returns back to the program selection screen (in my shell). Then, when you try to exit the shell again, it does one of two things. 1) it stays on with a blank screen and executes a seemingly infinite loop. 2) It goes through what I can only describe it as: the calculator is on drugs. The user input is about 1/4-normal; you can point out various misplaced parts of some recognizable menus; and finally, if you hit On and other keys enough, it "burns out" with that weird blue line across the screen (I've seen this before: see the footnote) and resets after about 2 minutes.
This (or any number of weird effects) is pretty much expected. What your program is doing is equivalent to generating a random address in memory and jumping to it; in fact, you can probably get a similar effect to occur by doing exactly that.
I've double-checked that the program I'm working with is within the size range (nothing hanging past BFFFh).
That's something to worry about later; if you execute any code beyond BFFFh, the effect is just an immediate RAM clear.
Footnote: That weird blue line on the screen is very frightening and I've seen it before; that is the MAJOR indicator that you have done something horribly wrong. I believe that may be a side effect of executing bad code, that sends absolute nonsense to your LCD, causing it to shut off and display a blue line across your screen. I only call it "burning out" because it's generally what it does after an absolute overload of bad data, and the line fades away in tatters before eventually returning your calculator to a fresh RAM wipe. I have found nothing about this on YouTube.
This is called "test mode," or colloquially the "blue line of death" (BLoD). You're right in that it's effectively burning out your LCD; if it happens, the best thing to do is to take out your batteries quickly so no permanent damage occurs.

Like I said, I highly recommend reading Learn TI-83 Plus Assembly in 28 Days. What you're writing is called a shell, and it's one of the more technically involved things you can write in assembly, let alone Axe.
« Last Edit: January 07, 2021, 02:12:15 pm by Deep Toaster »