Calypsi-tool-chains icon indicating copy to clipboard operation
Calypsi-tool-chains copied to clipboard

Request: Apple IIgs support

Open ksherlock opened this issue 9 months ago • 22 comments
trafficstars

Hello,

I started working on a new linker to demonstrate that calypsi could be used to generate Apple IIgs programs.

The biggest enhancements needed are (and both these features would also be useful in the 68k target to support Classic Mac OS):

  1. Toolbox api support

Here's some official documentation:

  • https://archive.org/details/Apple_Programmers_Introduction_to_the_Apple_IIgs/page/65/mode/2up
  • https://archive.org/details/AppleIIGSToolboxReferenceVolume1/page/n55/mode/2up

A better explanation from Inside Macintosh:

  • https://archive.org/details/bitsavers_applemacIn84_27699101/page/n117/mode/2up

Basically, toolbox calls use the "pascal" calling convention (from Lisa Pascal and MPW Pascal) where the caller pushes space on the stack for any return values; parameters are passed in the order specified; and the callee (toolbox, in our case) cleans up.

For MacOS, the toolcall is made with an A-trap instruction. (In later years they ran out of numbers so there were also extra instructions to load registers with a selector value, etc)

For the IIgs, the toolcall is made by loading the x register with the call number and performing a JSL to a vector ($e10000 for toolbox, $e10008 for user toolbox, $e100b0 for gs/os calls). On return, the A register and carry flag indicate a tool error. Other IIgs compilers (ORCA/C, APW C) store it in the _toolErr global variable.

So given something like a hypothetical __attribute__((pascal, toolbox(0xe10000, 0x1234) )) int toolbox_function(int, int int);

  var = toolbox_function(1,2,3);

would generate

  pha  ; space for result
  pea ##1
  pea ##2
  pea ##3
  ldx ##0x1234
  jsl long: 0xe10000
  sta _toolErr
  pla ; result
  sta var

Example Apple IIgs prototype (ORCA/C):

extern pascal Handle NewHandle(LongWord, Word, Word, Pointer) inline(0x0902,0xe10000);

Example Macintosh prototypes:

extern long GrowWindow(WindowRef window, Point startPt, const Rect *  bBox) ONEWORDINLINE(0xA92B);
extern pascal void CloseWindow(WindowRef window)  ONEWORDINLINE(0xA92D);
extern pascal TimeValue GetMoviePosterTime(Movie theMovie)  TWOWORDINLINE(0x7035, 0xAAAA);
// 0x7035 = moveq #53, d0

(There were macros for ONEWORDINLINE through TWELVEWORDINLINE although I don't see anything above six inline words in use for toolbox calls)

MacOS also had register-based OS calls where parameters are passed and returned in registers instead of the stack but that didn't apply to the Apple IIgs so I won't say much more about that.

#pragma parameter __A0 NewHandle(__D0)
extern pascal Handle NewHandle(Size byteCount) ONEWORDINLINE(0xA122);

1.5 much lower priority, but there are some tool calls where you pass in a function which gets called later, and they use the pascal calling convention, so supporting that would be nice. I suppose callback functions would potentially need to save and restore the direct page scratch registers like interrupt functions do

  1. pascal strings

Within a string, a leading \p escape sequence will generate a length byte. (clang -fpascal-strings supports pascal strings.)

"\phello" is equivalent to "\005hello"

Thanks for your consideration. Calypsi is quite interesting.

ksherlock avatar Feb 19 '25 05:02 ksherlock

You could try to either generate OS calls using the inline assembler or making assembly stubs. There are various board supports on my Github:

https://github.com/hth313/Calypsi-6502-Commodore https://github.com/hth313/Calypsi-m68k-Foenix https://github.com/hth313/Calypsi-65816-Foenix https://github.com/hth313/Calypsi-Amiga

hth313 avatar Feb 19 '25 05:02 hth313

I did use assembly stubs but that gets annoying fast. I'll put in another ticket for issues with the inline assembly

ksherlock avatar Feb 19 '25 22:02 ksherlock

It seems like a better idea to have some metadata and have the code generator emit the call sequences. On the Amiga I used "fd" files for this that describes the function call. This was implemented as a separate parser for such files, which is fairly easy for me to do,.

You mention C pragmas or some attribute syntax. The problem is that I want to touch that kind of stuff as little as possible. Are you interested in doing that? I use the Clang frontend which is where it would need to done. I do not use LLVM at all, but that is another story.

hth313 avatar Feb 20 '25 04:02 hth313

I am interested in doing that. It looks like clang already recognizes __attribute__((pascal)) but I didn't see any actual support. It may have been added for Borland's clang fork.

cc65816 -S p.c
p.c:2:16: warning: 'pascal' calling convention is not supported for this target [-Wignored-attributes]
__attribute__((pascal)) int bleh(int a) { return a + 1; }
               ^
p.c:2:16: warning: unknown attribute 'pascal' ignored [-Wunknown-attributes]
p.c:2:16: warning: unknown attribute 'pascal' ignored [-Wunknown-attributes]

ksherlock avatar Feb 21 '25 03:02 ksherlock

Nice, I think there are two ways attributes work, there is a bitfield which I use and then you can attach attribute objects somehow, but I am not familiar with how that is done. In lang/include/clang/AST/Type.h the class ExtInfo is defined, it expresses some attributes using bits. I have added most of my function attributes here, I expanded the size from 16 to 32. For attributes with arguments you will need to use the other way with objects.

There is also clang/include/clang/Basic/Attr.td where attribute syntax is introduced. It the attribute takes arguments there should be parser related things, but I do not know/remember where.

The warning you get means it may recognize the attribute, but it also needs to activate it somewhere.

Some other related files: clang/lib/AST/TypePrinter.cpp clang/lib/AST/Expr.cpp

It has some places to pretty print and name mangle based on attributes.

I am currently using an old Clang version, but I this month I have worked on updating it, moving to llvmorg-19.1.6. I just got it to compile and link, but it core dumps, so more work is needed.

If you can help with setting attributes and provide a diff on llvmorg-19.1.6 I can work from there.

hth313 avatar Feb 21 '25 04:02 hth313

For the pascal attribute (and all other calling conventions), TargetInfo::checkCallingConvention determines if it's allowed or not. Something like --target=i386-pc-linux-gnu will allow it

Here's a diff that adds iigs_inline(x, vector) and mac_inline(instruction [, instruction]*) function attributes.

calypsi.diff.txt

$ cat p.c
__attribute__((pascal, iigs_inline(0x0202, 0xe10000))) unsigned  MMStartUp(void);

__attribute__((pascal, mac_inline(0xa974))) short Button(void);
__attribute__((pascal, mac_inline(0x2eb8, 0x02f4))) unsigned long GetCaretTime(void);



$ ./bin/clang p.c -Xclang -ast-dump -fsyntax-only --target=i386-pc-linux-gnu
TranslationUnitDecl 0x146052208 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x146052cf0 <<invalid sloc>> <invalid sloc> implicit __NSConstantString 'struct __NSConstantString_tag'
| `-RecordType 0x146052ac0 'struct __NSConstantString_tag'
|   `-Record 0x146052a40 '__NSConstantString_tag'
|-TypedefDecl 0x146052d98 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'char *'
| `-PointerType 0x146052d50 'char *'
|   `-BuiltinType 0x1460522b0 'char'
|-FunctionDecl 0x146052f70 <p.c:1:1, col:80> col:66 MMStartUp 'unsigned int (void) __attribute__((pascal))'
| `-IIgsInlineAttr 0x146053018 <col:24, col:52> 514 14745600
|-FunctionDecl 0x1460ae230 <line:3:1, col:62> col:51 Button 'short (void) __attribute__((pascal))'
| `-MacInlineAttr 0x1460ae2d8 <col:24, col:41> 43380
`-FunctionDecl 0x1460ae4c0 <line:4:1, col:84> col:67 GetCaretTime 'unsigned long (void) __attribute__((pascal))'
  `-MacInlineAttr 0x1460ae568 <col:24, col:49> 11960 756

ksherlock avatar Feb 27 '25 03:02 ksherlock

I started a bit on the IIgs calling sequence. I decided to backport to my existing Clang-10 to get something out quicker as the Clang-19 migration will still take weeks. I get the attribute through and can generate different call sequences, though they need more work.

  1. Are parameters and return values even number of bytes, what about single and three byte values, are they used in the calling sequence?
  2. It seems arguments are pushed left to right (which is opposite to all other uses I have ever seen).
  3. The carry indicates an error, should that be handled in some way?

I plan to omit the 'pascal' attribute and let the IIgs attribute imply the pascal calling convention. Unless there is an alternative C calling convention as well, otherwise it does not make much sense to have it. Is this naming with 'iigs_inline' and 'mac_inline' what people are used to? Otherwise I could imagine other attribute names, such as 'iigs' and 'macos'.

hth313 avatar Mar 01 '25 18:03 hth313

I found the answer to the push order, it is reversed. I also found a better description of the carry flag and error code handling, so it seems either (or both) can be used.

If the attribute is changed to 'macos', then maybe 'gsos' for consistency, but everyone calls it the IIgs, so maybe not.

hth313 avatar Mar 02 '25 17:03 hth313

The arguments are pushed in reversed order (from a C perspective).

parameters are always an even number of bytes. There are a couple of tool calls that have a struct parameter or return value. The actual struct, not a pointer to it. Existing C compilers can't handle them natively so not supporting that is entirely reasonable.

Toolcalls usually end with a CMP #0 (sometimes SEC / LDA #errornumber, sometimes CLC / LDA #0). Apple's C compiler would do:

    jsl $e10000
    bcs +
    lda ##0
+   sta long: _toolErr

but sta long: _toolErr is sufficient.

The IIgs/macintosh toolbox calls were all pascal style so that is redundant.

Apple loved the word inline.

IIgs:

extern pascal void SetWTitle() inline(0x0D0E,dispatcher);

Mac pascal:

PROCEDURE SetWTitle(theWindow: WindowPtr;title: Str255);
    INLINE $A91A;

Mac C:

pascal void SetWTitle(WindowPtr theWindow,ConstStr255Param title)
    = 0xA91A;

EXTERN_API( void )
SetWTitle(
  WindowRef          window,
  ConstStr255Param   title)                                   ONEWORDINLINE(0xA91A);

I don't think the name matters a whole lot. You include the header, you call the function. They only need to be converted once.

Thanks!

ksherlock avatar Mar 02 '25 19:03 ksherlock

Release 5.9 contains initial/experimental support for IIgs and MacOS.

On the 65816 I tested with:

__attribute__((iigs(0x0202, 0xe10000))) unsigned  MMStartUp(int,int);

void foo() {
  MMStartUp(10,20);
}

To see that it generates calls that look right. The error variable is somewhat different to what you described (as _ToolErr). Mainly because of my framework, and in part because it is how a compiler vendor should name non-standard things.

For the Mac this is the test code I used:

__attribute__((macos(0x2eb8, 0x02f4))) unsigned long GetCaretTime(int,int);

int foo() {
  return GetCaretTime(10, 20);
}

Hopefully it will allow you to get started on header files. Let me know if there are issues and what needs to be fixed.

hth313 avatar Mar 05 '25 05:03 hth313

Thanks, that's working great. I was able to replace all the asm tool code in my test programs.

Image

Issues I've encountered:

  • -02 will optimize out the ldx ##
  • --code-model small / --code-model compact generates an internal error: illegal instruction: JSR long:0xe10000. It would be nice if toolcalls could still be made in compact and even small memory models. Most GS/OS programs don't exceed 64k code or data so compact should be fine. Anything that runs under ProDOS 8 will be in bank 0 and much like an embedded system but tool calls are still available in ROM.
  • The assembly/elf files have extern references to the toolcall functions. That probably doesn't matter because there are no relocation records referencing it.
  • Pascal strings pretty please?
  • (macos) -O2 causes an internal error: unhandled instruction MacCall [...] error

ksherlock avatar Mar 06 '25 00:03 ksherlock

Thank you for the coffee! 😺

Nice to see you get some output on Mac.

I had a couple of busy days but finally found some time to take a look at the things you reported. I believe that I have fixed all issues. The small/compact code model should still use jsl? I have implemented Pascal strings. There is now a --pascal-strings option and the leading character is ^p (or 16 decimal). It does not work to type \p as that comes out as p from Clang.

What host platform do you use? I can make a build for you to try out, but I rather not build for every platform as it takes a long time.

hth313 avatar Mar 09 '25 05:03 hth313

How do you interface with clang? I was assuming, based on strings (-ffreestanding, -fno-signed-char, -fsigned-char) you could just pass in -fpascal-strings and clang would take care of it. If you have to use something else, I'll survive though :)

IIgs toolcalls will always be a jsl.

I'm testing on x64 MacOS. Thanks!

ksherlock avatar Mar 10 '25 03:03 ksherlock

I have some custom entry points in libclang, I pass over settings and there is a couple of command line options. It return by passing over the AST using a custom serialization. Calypsi is mostly written in Haskell.

I could possibly have used -fpascal-strings, now I already did it on the Haskell side. I will keep the way I did for the moment, but may revise it later.

hth313 avatar Mar 10 '25 04:03 hth313

Here are updated versions to try out. I have added --pascal-strings which requires ^P as first character and you need to get a decimal 16 character in there, not an escape sequence \p. https://drive.google.com/file/d/1CtN9tcpfJfKEy-23RF2Ucoy_a3ZH-9mV/view?usp=sharing https://drive.google.com/file/d/1nenFgXX0lS3neaKhPLgX-v8AhDd_TQBQ/view?usp=sharing

hth313 avatar Mar 12 '25 00:03 hth313

Thanks.

A couple issues with pascal strings -

char *pstr_first = "\x10" "abc";
char *cstr_second = "abc";

char *cstr_first = "def";
char *pstr_second = "\x10" "def";

char pstr_array[] = "\x10" "xyz";
  • pascal strings should still have the trailing 0
  • pascal and non-pascal strings are inappropriately merged together (_StringLiteral_abc, _StringLiteral_def)
  • in array form (pstr_array) the length byte is missing
;
;  char *pstr_first = "\x10" "abc";
            .section data,data
            .public pstr_first
pstr_first: .word   _StringLiteral_abc
;  char *cstr_second = "abc";
            .section data,data
            .public cstr_second
cstr_second:
            .word   _StringLiteral_abc
;
;  char *cstr_first = "def";
            .section data,data
            .public cstr_first
cstr_first: .word   _StringLiteral_def
;  char *pstr_second = "\x10" "def";
            .section data,data
            .public pstr_second
pstr_second:
            .word   _StringLiteral_def
;
;  char pstr_array[] = "\x10" "xyz";
            .section data,data
            .public pstr_array
pstr_array: .byte   120,121,122,0,0

            .section cdata,rodata
            .pubweak _StringLiteral_abc
_StringLiteral_abc:
            .byte   3,97,98,99
            .section cdata,rodata
            .pubweak _StringLiteral_def
_StringLiteral_def:
            .byte   100,101,102,0
;
;

ksherlock avatar Mar 13 '25 01:03 ksherlock

-O2 still optimizes out the ldx ##

__attribute__((iigs(0x0102, 0xe10000))) void MMBootInit(void);

void test(void) {
	MMBootInit();
}
cc65816 -S -O2 test1.c
__attribute__((iigs(0x200c, 0xe10000))) void WriteCString(const char __far *theString);
__attribute__((iigs(0x180c, 0xe10000))) void WriteChar(unsigned theChar);

unsigned _ToolErr;

void puts(const char *cp) {

	WriteCString(cp);
	WriteChar('\r');
}


int main(int argc, char **argv) {
  puts("hello, world!");
  return 0;
}
cc65816 -S -O2 --code-model compact --data-model medium hello.c
internal error: bad jumpFun size/op
Terminating due to errors

(nice inlining with -O1 though!)

ksherlock avatar Mar 13 '25 01:03 ksherlock

I have corrected these two issues and also now make use of -fpascal-strings internally, so "\psome text" is now used to mark a Pascal string.

hth313 avatar Mar 14 '25 03:03 hth313

Do you want trailing null on Pascal strings too?

hth313 avatar Mar 14 '25 03:03 hth313

The Pascal strings are still c strings so the trailing null should be present. Thanks.

ksherlock avatar Mar 15 '25 16:03 ksherlock

I have updated the files with the fixes: https://drive.google.com/file/d/1CtN9tcpfJfKEy-23RF2Ucoy_a3ZH-9mV/view?usp=sharing https://drive.google.com/file/d/1nenFgXX0lS3neaKhPLgX-v8AhDd_TQBQ/view?usp=drive_link

hth313 avatar Mar 17 '25 06:03 hth313

Wonderful. I haven't encountered any issues with this build. Thanks!

ksherlock avatar Mar 17 '25 21:03 ksherlock