BigDumbDinosaur wrote:
Speaking of the user guide, I did see some information that was presented about the 65C816's memory model that I'd like to discuss in the interest of clarity. Also, I have some curiosity about how the compiler resolves some things that are more-or-less 65C816-specific. Please note that the following is not criticism, just an attempt to reconcile what the guide says with what I know about the 816's behavior.
[...]
In 6502-family architecture, a "page" is defined as 256 contiguous bytes, not 64KB. In 65C816 architecture, a "bank" is defined as 256 contiguous pages, with any given bank starting at $xx0000, where $xx is the bank number, $00-$FF.¹
Thank you for the input. I especially appreciate getting input on the documentation. Having proof read documentation in the past I know it takes time to make it good.
I agree about the 'page' being incorrect. However, while bank is probably according to the 65816, I think it can be misleading too, as I often regard bank to be something that is switched in and out in some address range.
I will give it some further thought (sleep on it), but I think segment may be a better universal term as you suggest in the footnote. Not everyone that uses the compiler can be expected to be familiar with the target and one reason for using a C compiler is to avoid having to learn and write in the native assembly language.
Quote:
As for the statement about efficiency, I question that. The 65C816 assembly language makes it possible to treat the full address space as linear space for data purposes, using succinct code. This possibility is fully exploited if "long" pointers are handled as 32-bit entities instead of 24-bit so frequent use of REP and SEP in pointer arithmetic sequences can be avoided.²[/color]
The compiler uses 32 bit data pointers. Using the data memory as a linear address space and allow objects to cross 64K boundaries is possible with Huge pointers in this compiler. The somewhat related Far pointer is also 32 bits wide, but assumes that objects are placed so that they never cross a 64K boundary (enforced by the linker rules file).
Accessing as memory location using either a Far or Huge pointer will most likely result in the same code. There are two cases where they differ. The most important one is in pointer arithmetic, which can be adding a value to the pointer (stepping it). In some cases this does not matter as an addressing mode can be used, but in other cases it needs to actually perform pointer arithmetic and with a Far pointer this is done on the low 16 bits, while a Huge pointer needs 32 bits arithmetic. It also matters for size_t, which depending on how the compiler is configured can be 16 bits when Far is used, but if Huge is enabled size_t is 32 bits. The size_t type is used in some C library functions.
As an example, consider adding one to a pointer and passing it on. In this case the pointer needs to be updated, there is no addressing mode to help as it is going to pass an update pointer value. With a Far pointer this can typically be done using a single INC instruction. With a Huge pointer it would need an INC-BNE-INC sequence.
So I think that there is a performance difference between them, but I suppose it depends on what operations you actually do.
Quote:
Is the "stack" being referred to in the above the MPU's hardware stack or a LIFO in bank $00 being treated as a stack, e.g., like the data stack commonly used in Forth? If the latter, perhaps that should be clarified for the benefit of the reader.
If the former, I'm a little confused by the As we may want to point to object in the stack, they must be in the same page. statement. Wouldn't such an object be accessible with <offset>,S (stack-relative) addressing, which implicitly refers to bank $00 and does not involve DB in any way?
It is the hardware MPU stack, there is only that stack used by the compiler runtime.
The Small data model assumes a 16 bits default data pointer. Yes, you can access an object on the stack using an addressing mode even the bank register points elsewhere. This is utilized in all data models.
You an also place a static or global object in a bank that is not 0 and address it using the absolute addressing mode.
Now consider that you want the address of either of these objects, a stack object or a data object in some other bank pointed to by the bank register. It may be a buffer that to pass to sprintf(). If you make the pointer 16 bits, which bank should sprintf() access? There is no way for it to know and both can be used by normal C code.
To make it work, both are forced to be in bank 0 in the Small data model. This allows for 16 bits default (untyped) pointers that can point to any data object.
If you want to use separate banks, then you need a 32 bits pointer and this is what the Medium data model is all about.
Quote:
[color=#000000]What defines a "memory-constrained" system? In other words, is there a total RAM threshold below which a system becomes memory-constrained from the compiler's perspective? Aside from the possibility of the heap becoming fragmented from numerous malloc() and free() calls (which is really a separate issue, I think), what other (possibly bad) things could happen in a memory-constrained environment? Also, how and when is the size of the heap determined? Is the heap limited to a single bank or can it be defined to span multiple banks to accommodate malloc() calls that request more than 64KB?
Memory constrained systems are any system that does not have seemingly infinite amounts of memory. A modern desktop computer has multiple GB of memory today. We use abstraction and care little about how much memory we use. If you write programs that need to fit a certain memory budget and this might influence how you approach the problem, you need to take it in account when you write your program, then you are on a memory constrained system as I see it.
The bad thing that can happen in a memory constrained system apart from heap fragmentation (as you mention) is that you try to fit too much program into the available memory and run out of memory space. If you write a program for you Linux desktop, I doubt you worry that will happen.
The heap is set up in the linker memory rules file (.scm extension). You can set the heap size and stack size there.
At the moment there is only one heap and it is not possible (in its prebuilt form) to make it span multiple banks. The library C code itself can actually handle it, but it needs to be properly configured and rebuilt for it. I need to fix the library build tool so end user can do it. This is one thing that is not done yet and it is mentioned in the release notes. If you actually use the compiler and run into issues with this limitation, I will put higher priority on fixing it, otherwise it will come at some point.
Quote:
[color=#000080]The tiny address space is 256 bytes of memory located somewhere in the first 64K of memory. It has an address range 0x00-0xff...Being only 256 bytes and also shared with pseudo registers, you are somewhat limited on how much you can store in the tiny area.
I interpret the above to mean a data structure could be placed on direct page, which is unusual in most code—direct page is generally considered to be too valuable to be used in that fashion, except in narrowly-defined cases. Please clarify. Also, if I write REGISTER INT X=0 in my C program, where is X being stored?
You can put small static or global data objects on the direct page and benefit from the fast direct page addressing.
If you write 'register int x=0' inside a function it will either be located in a register or on the hardware stack.
Quote:
[color=#000000]I've not seen any 65C816 system with more than 4MB of RAM. How does the compiler reconcile the theoretical 16MB address space to the actual address space? For example, if I were to compile a program using "huge" addressing to run on my POC V1.3 unit (128KB, of which 64KB are in bank $01), how would the program "know" that the highest-accessible physical address is $01FFFF? Also, does the compiler have a way to handle memory "holes" caused by idiosyncratic address decoding?
The compiler has no idea, it will just create data objects that go into different sections. You need to tell the linker about your memory system. It will place sections in the available memory space and resolve relocations to make a working program. If there is need for gaps, perhaps due to alignment constraints or you give it very specific directions on how to place things, that will be done. There may be gaps if needed or desired.
The Foenix C256 comes with 6Mb and the coming GenX model will fill the entire 16Mb address space and then add some banked (!) memory on top of that (it has some 64MB in total), but I do not plan to support that anytime soon. I am happy to limit it to 16MB and I doubt I will write any program I want to run on my 65816 system that uses all that memory.
Quote:
[color=#000080]Assembly language files usually have the file extension .s or .asm, but it varies wildly as assembly language itself is not standardized in any way.
[color=#000000]Although source code file naming conventions do vary, the 6502 assembly language itself is standardized—MOS Technology wrote the original standard, which compliant assemblers will implement. The 65C816 assembly language is also standardized—WDC promulgates that standard in the data sheet. The language standards intentionally ignore assembler pseudo-ops, which is nothing unique to the 6502 universe.
Regrettably, there are all sorts of 6502 assembly language bastardizations in hobbyist-developed assemblers, some borne of ignorance of the MOS Technology/WDC standards that have existed some 46 years, and others the result of the "I don't like it, so I'm going to come up with my own 'standard'." line of thinking. So it probably should be clarified that there is a standard, even though it may not seem so to the casual observer.
I am aware of the there are standards or ways that people expect assemblers to behave. However, this is a C compiler and the most important products in the package are the compiler and the debugger as I see it. The assembler is mainly there to support the compiler. I did not write this to be a 6502/65816 compiler tool chain, that sort of happened by accident. The code base will be used to make additional targets, this work is in progress. I decided not to care too much about assembler expressions and specific vendor features. I do try to follow defined mnemonics and addressing mode syntax. In the end I provide what is needed for myself and the compiler in a way that makes it efficient for me to work with it.
I have little hope for that my assemblers will make anyone switch from their favourite tools they been using for a decade or two. My take is that people who use a C compiler tool chain do it because they want to write as little assembly language as possible.
Quote:
[color=#000000]Can or does the compiler treat the run-time data that was generated during compilation, e.g., a lookup table associated with, say, a
switch() statement, as read-only data, as in:
Code:
switch (i) {
case 1: printf("i = 1.\n");
break;
case 2: printf("i = 2.\n");
break;
...etc...
default: printf("i is not 1 or 2.\n");
}
At least in assembly language, there would have to be some sort of address lookup table to vector the MPU according to the result of the
switch() statement. Would your compiler place that lookup table in the
rodata segment, even though no ROM may be involved?
Yes, switch tables are placed in a separate 'switch' section that is rodata and can be placed in the same place as code. It can actually be placed in any bank and does not need to be in the same bank as the function that use it.