using structs from C in Assembly

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
using structs from C in Assembly
by on (#197140)
Is there a way to use a struct from C inside the Assembly code?

When I have this in C:
Code:
struct MyStruct
{
    unsigned char Value1;
    unsigned char Value2;
    unsigned char Value3;
};

struct MyStruct StructVar;

then I'd like to do something like this in Assembly:
Code:
LDA _StructVar._Value1

I wouldn't mind having to import each variable separately:
Code:
.import _StructVar._Value1
.import _StructVar._Value2
.import _StructVar._Value3

But one thing I wouldn't want to do is using absolute address offsets, the way the automatically generated code does it:
Code:
LDA _StructVar + 1 ; Load StructVar.Value2

Because that's just crap since you have to manually keep an eye on every possible reordering of the variables.
Re: using structs from C in Assembly
by on (#197145)
ca65 does have a .struct directive that you can use to duplicate the C structure definition and use it in assembly.

http://cc65.github.io/doc/ca65.html#structs

The C compilation doesn't appear to use .struct in its generated code, so there's no automatic pass-through of the definition, but you could maybe make macros for C and assembly that let you include the same header in C and assembly at the same time?
Re: using structs from C in Assembly
by on (#197147)
That's not really what I'm talking about.

Duplicating a struct would require manual synchronization between the two definitions just as well.
I would have to declare a struct in C and the same struct in Assembly again and pay attention that every little change is carried over from one struct to the other one.

I'm talking about a way of importing the separate members of a C struct into Assembly.

If I have a struct called Character and the character has a member named Energy and I have a global variable called Player, then I want to be able to use something like Player.Energy inside Assembly and let the compiler/linker find out what actual address this is.

Alternately, I would also be content with building something with the help of C preprocessors etc.
For example, if I could do something like this in C:
Code:
#define PlayerEnergy (&Player.Energy)
and then import this in Assembly:
Code:
.import _PlayerEnergy
LDA _PlayerEnergy

this would be fine as well.

However, something like this:
Code:
byte *const PlayerEnergy = &Player.Energy;

(or however a pointer has to be declared whose address is fixed, but whose value can be written)
would not be good.
Because not only does the pointer occupy actual space in ROM, but in Assembly, I would have to use it this way:
Code:
.import _PlayerEnergy
LDY #0
LDA (_PlayerEnergy), Y
Re: using structs from C in Assembly
by on (#197148)
About using the same header file: This could be quite a hassle, right?

I mean, how do you create a file that looks like this:
Code:
struct MyStruct
{
    byte Abc;
    int Def;
};

in C and like this:
Code:
.struct MyStruct
    Abc .byte
    Def .word
.endstruct

in Assembly?
Re: using structs from C in Assembly
by on (#197149)
You can keep using the + 1 type of notation, but change the 1 into an equate for the member offset. Name it something like _VALUE1 or something, so you use someStruct + _VALUE1.
Re: using structs from C in Assembly
by on (#197152)
This doesn't really solve the problem, though. You still have to keep everything in sync with each other and have a redundancies in your code.
Re: using structs from C in Assembly
by on (#197154)
Well, if you don't like the macro suggestion I made in my first post...

You can export some structure member location from your assembly file and use asserts to make sure they are equivalent to the redundant structure definition. You'd have two definitions to maintain but you'd know immediately when they don't match.
Re: using structs from C in Assembly
by on (#197158)
Well, I can try the macro version and try to build something with it. But everything else isn't really that elegant.

Actually, this would have been a task for the compiler: Exporting the offsets for struct members.
But the inline assembly doesn't even allow statements like ".export".

Is there some command in C that forces the compiler to include a literal statement which is ignored when checking the C syntax, but that gets written into the Assembly file as-is?
Similar to inline-assembly, only that it isn't checked for correct syntax by the C compiler and only gets checked when the assembly code gets handled.
Something like this:
Code:
#literal ".export PlayerEnergy = %v + %b", Player, offsetof(Character, Energy)
Re: using structs from C in Assembly
by on (#197161)
I don't think C has the capability to export a constant like that. Anything that's not static is automatically exported, but I think all exports are either addresses of variables or functions, so I can't think of a way to pass a particular constant directly to the linker for use via assembly.

You could do something like this:

Code:
// store the structure member offset in a variable
const void* const mystruct_member = &((struct mystruct *)NULL)->member;

; fetch from that variable in assembly:
.import _mystruct_member
ldy _mystruct_member
lda mystruct_instance, Y


Not has good as being able to ldy immediate, or skip using Y entirely, but you can at least fetch it from the variable at runtime.
Re: using structs from C in Assembly
by on (#197163)
Yeah, I try to avoid these kinds of things: Storing values into the DATA or RAM segment that could usually be used directly. Or using the X or Y register for values that are known at compile time.

After all, my problem is really just a code style issue. And I don't like to complicate the behavior of the program, just so that I can enforce a certain style on the source code level.
(I'd rather abolish the struct altogether and simply use a bunch of separate variables. At least this would be a workaround purely on the code level while the ROM would still work exactly the same.

Also, some of my structs are not just structs, but structs with arrays inside (as a quicker alternative to an array of structs). So, the X or Y register is often already needed for the actual array index. Therefore, using it for an offset that's known at compile-time is not only ugly, but pretty much impossible.

I solved my current problem by writing the function inside C, but with 100 % inline assembly. This way, I can use the member names to turn their offsets into the literal values, like in the example from the compiler documentation:

Code:
typedef struct
{
    unsigned char x;
    unsigned char y;
    unsigned char color;
} pixel_t;

static pixel_t pixel;

__asm__ ("ldy #%b", offsetof(pixel_t, color));
Re: using structs from C in Assembly
by on (#197165)
I would have done what Dwedit suggested.

MyStruct: .res 3

Value1 = 0
Value2 = 1
Value3 = 2

Then in the asm code...

(MyStruct.Value2 = 1)...

LDA #1
STA MyStruct+Value2
Re: using structs from C in Assembly
by on (#197168)
DRW wrote:
About using the same header file: This could be quite a hassle, right?

I mean, how do you create a file that looks like this:

(example omitted)

Thinking about it some more, I believe it's doable but the fact that ca65 macros have no () enclosing parameters would make any attempted solution a little hairy.

You could also make a separate structure definitions header file with just the C or assembly structures in it and write a little parser utility to convert it to the other. You don't need a very complicated parser just to translate a struct definition.


It seems like it would be useful to be able to .export a constant from C with a #pragma or something. Might be worth making a feature request at its github project.
Re: using structs from C in Assembly
by on (#197171)
Who needs macros when you can do this?

Code:
; typedef struct { /*
.struct MyStruct ; */
    ; byte Abc; /*
    Abc .byte ; */
    ; byte Def; /*
    Def .word ; */
    ; unsigned char color; /*
.endstruct ;*/ } pixel_t;


Requires your C compiler to allow empty ';' statements though.
Re: using structs from C in Assembly
by on (#197172)
Ha! That's devious.

CC65 doesn't appear to like an empty ; within a struct definition though.
Re: using structs from C in Assembly
by on (#197173)
Hmm. This maybe?
Code:
; typedef struct { /*
.struct MyStruct ; */ byte Abc; /*
    Abc .byte ; */ byte Def; /*
    Def .word ;
.endstruct ;*/ } pixel_t;

I'd test this silliness but I don't have CC65 on this computer.
Re: using structs from C in Assembly
by on (#197174)
Yeah, that builds.
Re: using structs from C in Assembly
by on (#197177)
rainwarrior wrote:

You could also make a separate structure definitions header file with just the C or assembly structures in it and write a little parser utility to convert it to the other. You don't need a very complicated parser just to translate a struct definition.


That would be my recommendation. Have your makefile (or whatever build system) run a script to auto-create the assembly struct file from the C.
Re: using structs from C in Assembly
by on (#197189)
A parser would be a bit overkill for me. But maybe I'll give it another try to create a code that can be parsed on the source level for both languages.

I'm already using this for constant values that I need in Assembly and in C:

Code:
#ifdef COMPILE_FOR_ASSEMBLY
#define DeclareConstant(name, value) name = value
#else
#define DeclareConstant(name, value) enum { name = value };
#endif


Maybe I can do a similar thing for the struct:
Code:
#ifdef COMPILE_FOR_ASSEMBLY
#define TypeByte byte
#define TypeUint word
#define DeclareStructStart(name) .struct name
#define DeclareStructEnd() .endstruct
#define DeclareMember(type, name) name: .type
#else
#define TypeByte unsigned char
#define TypeUint unsigned int
#define DeclareStructStart(name) struct name {
#define DeclareStructEnd() };
#define DeclareMember(type, name) type name;
#endif

DeclareStructStart(Character)
    DeclareMember(TypeByte, Energy)
DeclareStructEnd()

etc.

And then you call cc65 with that one specific command line parameter that only preprocesses the file instead of compiling it, so that it outputs a valid Assembly file if you define COMPILE_FOR_ASSEMBLY.
This file can be used in your code then. (The C file itself can be used anyway since it is already a valid C file if you don't define COMPILE_FOR_ASSEMBLY.)