Jump to content
IGNORED

Data memory management in Pascal


apersson850

Recommended Posts

Today, there are several ways to get way more memory in the TI 99/4A than it had originally. But to use that memory, you have to write programs that manage it.

 

Back in the days, when 32 K RAM was the only memory expansion available, you had to be careful with how you used memory. One way to make that easier was to use the p-system. It was originally developed with a few specific targets in mind. One of them was to be able to run in systems with small memories. Hence the system has a few available functions for that.

 

Data memory

If you create a global variable in Pascal, it will exist as long as the program runs. It will occupy memory, regardless of whether you use the variable or not. If it's an integer, using two bytes, that may not matter. But if it's a larger buffer, it's not a good idea. One way could be to declare the variable in a subprogram instead.

Compare these two programs. Note that although they are syntactically correct, i.e. they can be compiled without errors, they don't work. Unimportant parts of the logic is just hinted at by comments.

 

program version1;

type
  buftype = packed array[0..1023] of char;
  mempointer = ^integer;

var
  buffer: buftype;
  x,y: mempointer;

procedure copier(a,b: mempointer);

begin
  (* Use buffer to move data from place pointed to by a to position b *)
end;

begin (* version1 *)
  (* Assign addresses to x and y *)
  copier(x,y);
  (* Do more things *)
end.

Here is the other one.

program version2;

type
  buftype = packed array[0..1023] of char;
  mempointer = ^integer;

var
  x,y: mempointer;

procedure copier(a,b: mempointer);

var
  buffer: buftype;

begin
  (* Use buffer to move data from place pointed to by a to position b *)
end;

begin (* version2 *)
  (* Assign addresses to x and y *)
  copier(x,y);
  (* Do more things *)
end.

The only difference in these two examples is where the variable buffer is declared. But that makes a big difference. In version 1, the variable buffer will occupy one kilobyte of memory all the time. In the second version, on the other hand, buffer will only require that kilobyte as long as procedure copier is running. When it's not, the space is released and can be used for other things. The penalty is a little more time consumed at the call to and return from the subprogram copier.

 

This is one example of how simple it is for the programmer to use the memory management available with Pascal and the p-system. Simply by moving the declaration you can control memory usage.

Edited by apersson850
  • Like 4
Link to comment
Share on other sites

10 minutes ago, apersson850 said:

Today, there are several ways to get way more memory in the TI 99/4A than it had originally. But to use that memory, you have to write programs that manage it.

 

Back in the days, when 32 K RAM was the only memory expansion available, you had to be careful with how you used memory. One way to make that easier was to use the p-system. It was originally developed with a few specific targets in mind. One of them was to be able to run in systems with small memories. Hence the system has a few available functions for that.

 

Data memory

If you create a global variable in Pascal, it will exist as long as the program runs. It will occupy memory, regardless of whether you use the variable or not. If it's an integer, using two bytes, that may not matter. But if it's a larger buffer, it's not a good idea. One way could be to declare the variable in a subprogram instead.

Compare these two programs. Note that although they are syntactically correct, i.e. they can be compiled without errors, they don't work. Unimportant parts of the logic is just hinted at by comments.

 


program version1;

type
  buftype = packed array[0..1023] of char;
  mempointer = ^integer;

var
  buffer: buftype;
  x,y: mempointer;

procedure copier(a,b: mempointer);

begin
  (* Use buffer to move data from place pointed to by a to position b *)
end;

begin (* version1 *)
  (* Assign addresses to x and y *)
  copier(x,y);
  (* Do more things *)
end.

Here is the other one.


program version2;

type
  buftype = packed array[0..1023] of char;
  mempointer = ^integer;

var
  x,y: mempointer;

procedure copier(a,b: mempointer);

var
  buffer: buftype;

begin
  (* Use buffer to move data from place pointed to by a to position b *)
end;

begin (* version2 *)
  (* Assign addresses to x and y *)
  copier(x,y);
  (* Do more things *)
end.

The only difference in these two examples is where the variable buffer is declared. But that makes a big difference. In version 1, the variable buffer will occupy one kilobyte of memory all the time. In the second version, on the other hand, buffer will only require that kilobyte as long as procedure copier is running. When it's not, the space is released and can be used for other things. The penalty is a little more time consumed at the call to and return from the subprogram copier.

 

This is one example of how simple it is for the programmer to use the memory management available with Pascal and the p-system. Simply by moving the declaration you can control memory usage.

So to "peek behind the curtain" is it correct to say that memory defined inside the Procedure is allocated on the stack and so it can be collapsed at the end of the procedure?

 

 

  • Like 1
Link to comment
Share on other sites

Here is another way to allocate the memory only when needed.

 

program version3;

type
  buftype = packed array[0..1023] of char;
  mempointer = ^integer;

var
  buffer: ^buftype;
  x,y: mempointer;

procedure copier(a,b: mempointer);

begin
  (* Use buffer to move data from place pointed to by a to position b *)
end;

begin (* version3 *)
  (* Assign addresses to x and y *)
  new(buffer);
  copier(x,y);
  dispose(buffer);
  (* Do more things *)
end.

And a different version

program version4;

type
  buftype = packed array[0..1023] of char;
  mempointer = ^integer;

var
  buffer: ^buftype;
  x,y: mempointer;

procedure copier(a,b: mempointer);

begin
  (* Use buffer to move data from place pointed to by a to position b *)
end;

begin (* version4 *)
  new(buffer);
  (* Assign addresses to x and y *)
  copier(x,y);
  (* Do more things *)
  dispose(buffer);
end.

Here the buffer, represented by a pointer to it, and thus accessed as buffer^, is created on the fly by the main program when it's needed. When the program no longer has use for it, it's disposed, and then the memory occupied by the variable is free for other use. The pointer variable buffer itself exist all the time, but it's only 2 bytes long. The buffer area it's pointing at is reserved with new and released back to the system with dispose.

 

In the two versions, the lifetime of the buffer is different. In the first version, it lives just across the call to copier. In the second example, it lives also during the preparations before calling copier, and during the procedures following that call. The third version is, from a memory allocation point of view, identical to version 2. The fourth version may or may not be relevant, depending on how long you need the buffer. Dynamic memory allocation gives you the flexibility to decide on your own, without adding any more complexity than using the commands new and dispose.

  • Like 4
Link to comment
Share on other sites

4 minutes ago, TheBF said:

So to "peek behind the curtain" is it correct to say that memory defined inside the Procedure is allocated on the stack and so it can be collapsed at the end of the procedure?

That is actually true for the main program too. From a logical point of view, the main program is a subprogram to the operating system.

So when the operating system launches a user's program, or even another part of the operating system, it will push an activation record and within that allocate the variables declared in the main program on the stack, set a frame pointer to identify where the global variables are and then launch the program. Once the main program ends, the activation recored is popped from the stack.

The same thing happens inside the user's program, when it calls a subprogram. The local variables are allocated in an activation record, the frame pointer is created, the procedure runs. When it exists, the activation record is removed from the stack and the data memory is free for other use.

  • Like 4
  • Thanks 1
Link to comment
Share on other sites

Yes, that's true. Some of the functionality here is developed at UCSD, though.

Like handling of constants in a program.

Regardless of whether you explicity declare constants, like:

constant

maxcost = 22;

defaulttext = 'We are happy';

 

Or if you declare implicit constants like this:

writeln('The score is ',total);

 

These constants end up as a constant pool in the program. That's a separate part of the code file in the p-system. When you execute the code file, and the operating system is loading it in memory for you, the constant pool from the code file is allocated a separate memory area in the heap. Then all constants are referenced from that place. The reason for doing this is that the constant pool is then kept in that place, so all references to the constant pool in the program and not affected by the code segments being moved around, if the system runs into a memory fault and thus tries to free memory for other activities.

 

Another aspect of this is that even if you only use the same text string twice in a program, like if you have two lines with write('Enter data'); in the entire program, it's better to declare the constant dataentry = 'Enter data'; and then do write(dataentry); instead. You'll not win any speed by having the text inline, since the system will generate an implicit constant and make a reference to that anyway. But if you have inline text strings like this, the simplistic compiler in the 99/4A will generate two separate constants with 'Enter data', each referenced from each write statement. Here you can do some manual work to save some memory when the code is running.

  • Like 4
  • Thanks 1
Link to comment
Share on other sites

Now note that the UCSD p-system does not provide virtual memory in the general context. Not like VAX VMS, for example. The latter provides every user with a large memory space, where the product of the number of concurrent users and their memory vastly exceeds the total memory in the machine. There it doesn't matter if you use it for data or code. Memory images are rolled in and out to disks as needed.

 

When the p-system is running, you have a bit over 20 Kbytes available for data and/or code, and another 12 Kbytes for code only. There is no automation in handling memory for data, except that variables declared on lower lexical levels (in subroutines) is allocated and released as needed. If you call a subroutine, which requires 5 Kbytes of local data, you allocate and then release that on exit automagically. But there's nothing the system can do if 5 Kbytes aren't available for data. The stack will overflow and the system will halt with the dreaded STACK OVERFLOW - REBOOT message.

But if you call the procedure a thousand times, then you have of course been able to allocate 5 Megabytes of data memory on a 48 Kbyte RAM machine. Sounds impressive, until you consider you have returned the same amount to the system too...

 

It's a different case for code. So different that I dedicated another thread to that. The corresponding answer for code fits better in that thread.

Edited by apersson850
  • Like 3
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...