6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Oct 05, 2024 9:56 pm

All times are UTC




Post new topic Reply to topic  [ 15 posts ] 
Author Message
PostPosted: Thu Feb 06, 2020 5:50 pm 
Offline

Joined: Thu Feb 06, 2020 5:37 pm
Posts: 6
Hey all, wondering if there exists a heap & memory allocator implementation for the 65816, so that I can use it for a PoC project of mine (Heaps are not my thing of expertise, would be real glad if somebody's already done this for me.)

I only need one bank for the allocator, and preferably it's code should fit in one bank as well, although it's not a strict requirement. Unfortunately I cannot work without the heap, it's a requirement (this is a PoC after all.)


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 06, 2020 6:13 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1468
Location: Scotland
mid wrote:
Hey all, wondering if there exists a heap & memory allocator implementation for the 65816, so that I can use it for a PoC project of mine (Heaps are not my thing of expertise, would be real glad if somebody's already done this for me.)

I only need one bank for the allocator, and preferably it's code should fit in one bank as well, although it's not a strict requirement. Unfortunately I cannot work without the heap, it's a requirement (this is a PoC after all.)


I wrote a simple first-fit malloc type thing recently for the 6502 - however I wrote it in sweet-16, so this may or may not be of-use to you.

I'm currently (slowly) re-writing it in 65816 native code for my '816 system, however I only plan to write the malloc part as the free part will be written in a higher level language. I plan to make it multi-bank, but only have the ability to allocate a max. block size of about 63KB due to some limitations I have for now. It doesn't coalesce adjacent free blocks - that was a task for later...

If you want the sweet-16 version just let me know. (I also wrote my own sw16 interpreter which is a bit quicker than Woz's one at the expense of size)

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 06, 2020 6:32 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10949
Location: England
Welcome, mid! There was a fun thread setting a challenge to write an allocator - you might find some pointers in that discussion.
Programming challenge: dynamic memory allocator


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 06, 2020 7:36 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1468
Location: Scotland
BigEd wrote:
Welcome, mid! There was a fun thread setting a challenge to write an allocator - you might find some pointers in that discussion.
Programming challenge: dynamic memory allocator


I remember reading that and didn't find any real code which is why I embarked on my own... I started it in 65C02 code and found I was spending a lot of time doing 16-bit manipulations, then the lightbulb moment when I remembered sweet16 - an promptly went down that rabbit hole writing my own implementation of sweet16 which, now that I'm using the '816 I'll probably never use again. Ah well...

Although it was handy having a dozen general purpose 16-bit registers...

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 06, 2020 7:52 pm 
Offline

Joined: Thu Feb 06, 2020 5:37 pm
Posts: 6
Sweet16 is fine, perhaps I'll switch to a pure 65816 one later on as this is only in the prototype stage, but I just want it to work at the moment.


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 06, 2020 9:25 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1468
Location: Scotland
mid wrote:
Sweet16 is fine, perhaps I'll switch to a pure 65816 one later on as this is only in the prototype stage, but I just want it to work at the moment.


Here it is...

Attachment:
malloc.tgz [4.15 KiB]
Downloaded 108 times


If nothing else it might give you ideas and hints about the structure and so on.

One thing it lacks is 'guard' words - e.g. putting a known word/byte value in the header of each segment allocated and another at the end (not counted in the allocation size, so the caller gets the bytes they ask for). This can be used during free to check for corruption/overflow.

You might want to explicitly set HIMEM (top of heap) and LOMEM (start of the heap) somewhere.

It contains references to RubyOS system calls, but that's mainly for printing debug information, but if you can't work out anything that looks weird just let me know.

Enjoy,

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 06, 2020 11:02 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8411
Location: Midwestern USA
mid wrote:
I only need one bank for the allocator, and preferably it's code should fit in one bank as well, although it's not a strict requirement. Unfortunately I cannot work without the heap, it's a requirement (this is a PoC after all.)

By design, a 65C816 program cannot span banks. When PC reaches $FFFF it will wrap to $0000, but PB will not be incremented. So your allocation code would be forced to fit in one bank (although a long JuMP into the next bank would work). In any case, a 64KB program is huge by 65xx standards, so it's not likely to be a significant issue.

Not knowing how much you know about the 65C816's architecture, I will mention that bank $00 must be used for the hardware stack (or stacks—you can have more than one), direct (zero) page, reset code, and the front end of your interrupt handlers. There are a few other such requirements as well, but the ones I enumerated are most important to know.

As for the heap itself, that can span as many banks as you wish, since during data fetches and stores the 65C816 treats memory as flat space. Somewhere in your POST code you will need to make a determination of how much "extended" RAM (RAM beyond $00FFFF) is present so your allocator can report an "out of memory" error when the heap space has been exhausted.

Let us know how your project is coming along.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 06, 2020 11:13 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8521
Location: Southern California
BigEd wrote:
Welcome, mid! There was a fun thread setting a challenge to write an allocator - you might find some pointers in that discussion.
Programming challenge: dynamic memory allocator

An offshoot idea from that topic is dynamic memory allocator, methods, uses, limitations, etc.. It's not specific to the '816, but might be worth considering anyway.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 07, 2020 11:23 pm 
Offline

Joined: Thu Feb 06, 2020 5:37 pm
Posts: 6
BigDumbDinosaur wrote:
So your allocation code would be forced to fit in one bank (although a long JuMP into the next bank would work)

Yup, that's what I meant. I just don't know how complex a simple memory allocator can get, so I noted that.

I'd say I'm a 4/10 at the 6502, I've dabbled with NES and SNES programming before and only read about it mostly, but I have experience with other architectures (mostly z80 and x86).

BigDumbDinosaur wrote:
Let us know how your project is coming along.

Will do (if nothing goes wrong). I may have been a bit ambitious, though, so I don't guarantee anything.


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 08, 2020 1:50 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8411
Location: Midwestern USA
mid wrote:
BigDumbDinosaur wrote:
Let us know how your project is coming along.

Will do (if nothing goes wrong). I may have been a bit ambitious, though, so I don't guarantee anything.

Better to be ambitious and fail than to be lazy and accomplish nothing. :D

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 08, 2020 4:41 am 
Offline
User avatar

Joined: Sat Dec 01, 2018 1:53 pm
Posts: 727
Location: Tokyo, Japan
BigDumbDinosaur wrote:
Better to be ambitious and fail than to be lazy and accomplish nothing. :D

I see nothing stopping one from doing all of the above. :-)

_________________
Curt J. Sampson - github.com/0cjs


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 08, 2020 10:15 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
Okay, let's have a stab at this. I'm going to assume that you pass the 64K data bank in question in DBR - so that absolute addressing lands there - and that you will dedicate the *entire* such bank to be managed by these routines. The code will be quite small, so I'll use RTS instead of RTL; you can add four-byte stubs which consist of "JSR xxxx ; RTL" to permit cross-bank calling if you need it.

The very simplest type of memory allocator is one that can't return any memory on free(). All you need to implement this is a pointer to the next free byte, which you can insert at the beginning of the data bank being managed. This also serves as a (two's complement, negative) counter of how much space is still free, and helps to define an invalid NULL pointer to return when there's not enough memory left:
Code:
; Assume both indexes and accumulator are 16-bit
.LongA
.LongX

InitHeap:
  LDA ##2    ; reserve first two bytes for freelist pointer
  STA |$0000
  RTS

; Pass number of bytes required in A (16-bit).
; Returns zero in X on failure, otherwise returns 16-bit pointer to allocated block in X.
AllocHeap:
  LDX |$0000
  BEQ :+ ; no space left
  CLC
  ADC |$0000
  BCS :+ ; not enough space left
  STA |$0000 ; update freelist pointer
  RTS
: LDX ##0
  RTS

; Return an allocated block to the heap.  Should pass pointer to block in X.  Actually a no-op.
FreeHeap:
  RTS
This should be enough to get simple programs running.

However, most good allocators have the ability to free memory and return it to the heap. This at minimum requires a little extra sophistication, in the form of a second counter which tracks the net number of allocations made. When the number of frees equals the number of allocations, you can reset the heap to the empty state:
Code:
; Assume both indexes and accumulator are 16-bit
.LongA
.LongX

InitHeap:
  LDA ##4    ; reserve first four bytes for freelist pointer and allocation counter
  STA |$0000
  STZ |$0002
  RTS

; Pass number of bytes required in A (16-bit).
; Returns zero in X on failure, otherwise returns 16-bit pointer to allocated block in X.
AllocHeap:
  LDX |$0000
  BEQ :+ ; no space left
  CLC
  ADC |$0000
  BCS :+ ; not enough space left
  STA |$0000 ; update freelist pointer
  INC |$0002 ; update allocation counter
  RTS
: LDX ##0
  RTS

; Return an allocated block to the heap.  Should pass pointer to block in X.  Does not verify validity.
FreeHeap:
  DEC |$0002 ; update allocation counter
  BEQ InitHeap
  RTS
This method is crude in the extreme, but might be useful in limited circumstances due to its simplicity.

Another, more sophisticated approach is to keep all free blocks of memory in a linked list. This means any block can be returned to the heap by adding it to that list. The size of each block must be remembered to make this work, so the overheads become significant; this method should be used to allocate significant chunks of memory, not individual small objects. If the freelist is kept in sorted order, that makes it easier to coalesce consecutive blocks and thus prevent fragmentation, but for simplicity the following code doesn't actually coalesce blocks.
Code:
; Assume both indexes and accumulator are 16-bit
.LongA
.LongX

InitHeap:
  ; reserve first two bytes for freelist pointer, and initialise it to point at first block
  LDA ##2
  STA |$0000
  ; first block occupies the rest of the bank (minus 4 bytes overhead), and has no following free block
  LDA ##$FFFC
  STA |$0002
  STZ |$0004
  RTS

; Pass number of bytes required in A (16-bit).
; Returns zero in X on failure, otherwise returns 16-bit pointer to allocated block in X.
AllocHeap:
  ; First-fit algorithm - traverse freelist until we find a block big enough, or get to the end of the list
  LDY ##0
  LDX |$0000
  BEQ @allocFail
@checkBlock:
  CMP |$0000,X
  BCS @nextBlock
  ; Block found, determine if appropriate to split
  PHA
  LDA |$0000,X
  SEC
  SBC 0,S
  CMP ##4 ; minimum viable size of split-off block
  BCC @noSplit
  ; Split block into a new free block and a new allocated block
  ; Because single-linked list, easier to put our block at end of this one
  PHA
  DEC A
  DEC A
  STA |$0000,X ; usable size of spare block
  TXA
  CLC
  ADC 0,S
  TAX ; now points to header of newly allocated block
  PLA
  STA |$0000,X ; usable size of this block - no next pointer!
  INX
  INX
  RTS
@noSplit:
  ; Update previous next-pointer in freelist
  ; Leave block size alone, so we get it all back on free
  LDA |$0002,X
  STA |$0000,Y
  INX
  INX
  RTS
@nextBlock:
  TXY
  INY
  INY
  LDX |$0000,Y ; Y now points to previous next-pointer
  BNE checkBlock
@allocFail:
  RTS

; Return an allocated block to the heap.  Should pass pointer to block in X.  Does not verify validity.  Does not coalesce.
FreeHeap:
  ; Adjust pointer to header
  TXA
  DEC A
  DEC A
  ; sorted insertion on singly linked list
  LDY ##0
: CMP |$0000,Y
  LDX |$0000,Y
  BCS :+ ; next block is after freed one
  BEQ :+ ; no more blocks
  TXY
  INY
  INY
  BRA :- ; next block
: STA |$0000,Y ; previous pointer to freed block
  TAY
  STX |$0002,Y ; freed block's next-pointer to following block (or NULL)
  RTS
I leave coalescing blocks as an exercise for the reader. You could do it on free, in which case you should remember that consecutive blocks may appear before, after, or both sides of a block being freed, and that the block size in the header is two bytes less than the offset between blocks. Or you could do it as a hail-mary pass upon allocation failure, which may be easier to implement (you only have to recursively coalesce a block with a following one) but may also end up with more failures due to memory fragmentation.

(Edited to ensure that the Z flag reflects the state of the X register on return of AllocHeap, to simplify calling.)

The above are by no means the only reasonable approaches to an allocating heap, but they have a reasonable chance of working and are simple enough for hobby use.


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 08, 2020 11:53 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10949
Location: England
I haven't checked your code, but that's a nice writeup! I particularly like the cheapest-possible reclaim, which waits until nothing is left claimed.


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 08, 2020 12:13 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
As a supplement, here's a routine which coalesces free blocks in the current heap, for use with the third allocator in my previous post. This could be called manually after a sequence of FreeHeap calls which may have left consecutive free blocks, and also upon allocation failure followed by a retry. Or you could just fall through to it from FreeHeap, ensuring the free space is always coalesced at the expense of some performance.
Code:
CoalesceHeap:
  LDY ##0
  LDX |$0000,Y
  BNE :+
  RTS        ; heap is full, no free blocks to coalesce
  ; Search for blocks where the next-pointer coincides with the end of the block as predicted by the size field
: TXA
  INC A
  SEC
  ADC |$0000,X
  LDY |$0002,X
  CMP |$0002,X
  BEQ :+ ; next block is contiguous with this one
  TYX
  BNE :-
  RTS ; end of freelist
: LDA |$0000,X ; concatenate blocks
  ADC |$0000,Y
  INC A
  INC A
  STA |$0000,X
  LDA |$0002,Y
  STA |$0002,X
  BNE :--
  RTS ; end of freelist

AllocHeapWithRetry:
  JSR AllocHeap
  BEQ :+
  RTS
: PHA
  JSR CoalesceHeap
  PLA
  JMP AllocHeap
As with the rest of this, it's completely untested. I don't even have an '816 running yet…


Top
 Profile  
Reply with quote  
PostPosted: Sat Feb 08, 2020 1:36 pm 
Offline

Joined: Thu Feb 06, 2020 5:37 pm
Posts: 6
Jesus, man, I didn't expect that, will test that when I can


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 15 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 19 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: