6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Wed Oct 23, 2024 8:33 pm

All times are UTC




Post new topic Reply to topic  [ 11 posts ] 
Author Message
PostPosted: Mon Sep 05, 2016 3:29 am 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 578
I'm trying to use CC65 to compile some C code I found online. The code uses a struct with union and gets some odd errors. I was wondering if anyone has experience with CC65. The code and the errors are below.

Code:
struct _Cell;
typedef struct _Cell Cell;

typedef Cell *(*Subr_t)(Cell *args, Cell *env);

Cell *apply(Cell *fn, Cell *args, Cell *env);

struct _Cell
{
  Tag       mTag;
  union {
    long    mNumber;
    const char   *mString;
    const char   *mSymbol;
    struct {
      Cell   *a;
      Cell   *d;
    }       mCons;
    Subr_t    mSubr;
    struct {
      Cell   *expr;
      Cell   *env;
    }       mExpr;
  };
};

Cell *mkNumber(long n) { Cell *self= balloc(sizeof(Cell));  self->mTag= Number;  self->mNumber= n; return self; }


cl65 -DBDWGC=0 -DCC65 -t none -C sbc2rom.cfg -o lysp.65b -m lysp.map -l lysp.c c:\cc65\lib\sbc2rom.lib
lysp.c(84): Warning: Declaration does not declare anything
lysp.c(87): Error: Struct/union has no field named `mNumber'
lysp.c(88): Error: Struct/union has no field named `mString'
lysp.c(88): Warning: Converting pointer to integer without a cast
lysp.c(89): Error: Struct/union has no field named `mString'
lysp.c(89): Warning: Converting pointer to integer without a cast


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 05, 2016 9:40 am 
Offline

Joined: Tue Sep 03, 2002 12:58 pm
Posts: 331
You're trying to use an anonymous union, which isn't standard C. Well, it might be these days, since it's a jolly useful feature, but in the past it wasn't.

Try this:
Code:
struct _Cell
{
  Tag       mTag;
  union {
    long    mNumber;
    etc...
  } data;
};


And to use it,
Code:
  cell.data.mNumber = 5;


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 05, 2016 4:38 pm 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 578
Thanks, that was it. It's been a while since I worked in vanilla C, me in 1990 would have been wise to that one. The other gotcha so far was inline functions which aren't in CC65 either.


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 05, 2016 7:04 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8451
Location: Midwestern USA
Martin_H wrote:
Thanks, that was it. It's been a while since I worked in vanilla C, me in 1990 would have been wise to that one. The other gotcha so far was inline functions which aren't in CC65 either.

CC65 is more like K&R C than ANSI C.

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


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 05, 2016 7:21 pm 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 578
BigDumbDinosaur wrote:
CC65 is more like K&R C than ANSI C.

Yes, the author liked to intersperse local variable allocations as they are needed. Gcc is fine with this, but CC65 will have none of it. It's only moving a line of code here and there, but it is tedious. In total this is about 1200 lines across two modules, so the effort mounts up.


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 05, 2016 8:06 pm 
Offline
User avatar

Joined: Thu Jun 23, 2011 2:12 am
Posts: 229
Location: Rancho Cucamonga, California
Martin_H wrote:
Code:
struct _Cell;
typedef struct _Cell Cell;


lysp.c(84): Warning: Declaration does not declare anything


You already got the solution for the errors, but I wanted to add some information. This applies to standard C compilers, I know there are some differences in cc65 so I'm not sure if this is useful in this context.

The first line in that code snippet doesn't declare anything. The name that follows the "struct" is the name of the struct type. This can be followed by a list of members, or a name of a variable if the compiler already knows which members are in the struct. If you follow the struct keyword with just the name of a struct type, it doesn't give the compiler any new information, so it gives you a warning because it thinks you may have forgotten something. A line that just contains "struct _Cell" is like a line with just "int;" in it.

The second line tells the compiler that your custom type "Cell" is the same as a "struct _Cell". Even if you haven't given the compiler a member list for struct _Cell, it's okay to declare an equivalent type. It's even okay to declare a pointer to a Cell or a struct _Cell, but you can't declare a variable of the struct until the compiler knows what members it has.

In fact, declaring a struct type and using pointers to them without letting the compiler know what the members are, is called "opaque structs" and is a great way to implement object-oriented software in C. Example:

Code:
/* xyz.h */

typedef struct xyz *pxyz;

/* Create an instance of struct xyz */
pxyz xyz_Create(void);

/* Destroy an instance of struct xyz */
void xyz_Destroy(pxyz p);

/* Some operation on an instance of struct xyz */
void xyz_Rotate(pxyz p);

/* Print a struct xyz */
void xyz_Print(pxyz p);


Code:
/* xyz.c */

#include <stdio.h>
#include "xyz.h"

struct xyz
{
  int x;
  int y;
  int z;
};

/* Create an instance of struct xyz */
pxyz xyz_Create(void)
{
  pxyz result = calloc(1, sizeof(*pxyz));

  if (result)
  {
    result->x = 1;
    result->y = 2;
    result->z = 3;
  }

  return result;
}

/* Destroy an instance of struct xyz */
void xyz_Destroy(pxyz p)
{
  free(p);
}

/* Some operation on an instance of struct xyz */
void xyz_Rotate(pxyz p)
{
  if (p)
  {
    int r = p->x;

    p->x = p->y;
    p->y = p->z;
    p->z = r;
  }
}

/* Print a struct xyz */
void xyz_Print(pxyz p)
{
  if (p)
  {
    printf("x=%d, y=%d, z=%d\n", p->x, p->y, p->z);
  }
  else
  {
    printf("NULL\n");
  }
}


Code:
/* abc.c */

#include "xyz.h"

int main(void)
{
  /*
   * A pointer to a struct xyz. Note that this module has no idea what members are in there!
   */
  pxyz p = xyz_Create( );

  xyz_Print(p); /* x=1, y=2, z=3 */
  xyz_Rotate(p);
  xyz_Print(p); /* x=2, y=3, z=1 */

  xyz_Destroy;
  /*
   * Note: p is now a dangling pointer. Exercise for the reader: change the xyz_Destroy
   * function so that it changes p to NULL. Take care that it does the right thing if
   * pass NULL pointers to the function.
   */
}


Top
 Profile  
Reply with quote  
PostPosted: Tue Sep 06, 2016 12:47 am 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 578
Thanks for the information Jac.

I've made great progress today and have nearly all 927 lines of the first module compiling. I'm down to code which uses macros to take a variable argument list and call another function with the same argument list. I'm not sure what the CC65 replacement should be, but if this was assembler it would be a JMP instruction. Here's the C code which uses some gcc buitins to do it:

Code:
long primcall(apply_t fn, arglist_t args, int size)
{
  void *ret= __builtin_apply(fn, args, size);
  __builtin_return(ret);
}


cl65 -DBDWGC=0 -DCC65 -W -t none -C sbc2rom.cfg -o lysp.65b -m lysp.map -l lysp.c c:\cc65\lib\sbc2rom.lib
lysp.c(457): Error: Call to undefined function `__builtin_apply'
lysp.c(458): Error: Call to undefined function `__builtin_return'

Not sure what to do with this one.


Top
 Profile  
Reply with quote  
PostPosted: Tue Sep 06, 2016 1:10 am 
Offline
User avatar

Joined: Thu Jun 23, 2011 2:12 am
Posts: 229
Location: Rancho Cucamonga, California
We'll need more context to figure this out. How is the macro defined? What function does it call? What is it trying to accomplish?

Basically the __builtin_apply and __builtin_return are GCC-specific intrinsic functions to forward the arguments of the current function to another function. Google for __builtin_apply and __builtin_return for more information (but it may not help you very much unless you have a thorough understanding of how compilers work, sorry).

In many older C compilers, the preprocessor can't even do variable arguments. Usually it's possible to work around this by using tricks. For example, I have a logging system that basically does this:

Code:

__declspec(thread) const char *logfile;
__declspec(thread) unsigned logline;

#define LOG(x) do { logfile = __FILE__; logline = __LINE__; logprint x; } while(0)

logprint(const char *fmt, ...)
{
  va_list args;

  printf("%s(%u) : ", logfile, logline);
  va_start(args, fmt);
  vprintf(fmt, args);
  va_end(args);
}


/* Say this source code is in file xyz.c line 123 */
LOG(("Hello %s!\n", "world")); /* prints: "xyz.c(123) : Hello world!\n"


Hope this helps.

===Jac


Last edited by jac_goudsmit on Tue Sep 06, 2016 2:59 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Tue Sep 06, 2016 2:59 am 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 578
I forked a Lisp interpreter that's supposed to fit in about 21 kb. So it seems perfect to port to an eight bit machine. Here's a link to the module in question:

https://github.com/Martin-H1/lysp/blob/master/lysp.c

What is happening is that each time a function is defined, a pointer to that function is retained and bound to its name. When an input token matches a function name, the function pointer is pulled from the binding and dispatched to the function using those GCC built-ins (which I thought were macros). The variable arguments is because a function has a user defined number of arguments, so the generic dispatch mechanism passes the entire set of parameters defined by the call in the program source.

So what is needed is a way to call a function pointer with the list of variable arguments already on the stack.

Update: in reading the code again it traverses the input argument list and pushed it on the stack. So if the psubr function type changed to accept arguments as a list this might eliminate the problem also.


Top
 Profile  
Reply with quote  
PostPosted: Tue Sep 06, 2016 4:30 pm 
Offline
User avatar

Joined: Thu Jun 23, 2011 2:12 am
Posts: 229
Location: Rancho Cucamonga, California
I have to say I never used the __builtin_apply and __builtin_return functions before. Now that I look at it a little closer, it looks like they do a little magic to make it possible to call a function indirectly without knowing what its parameters are, passing the parameters from a variable argument list.

I'm not thoroughly familiar with Lisp/Lysp or CC65 and I haven't studied the code long enough to see the circumstances in which the apply( ) function can get called. But I see that the code that handles a cell with an mTag of PSubr builds a local array of 32 void pointers on the stack and then calls the primcall function with the function pointer from the cell. Since the function pointer is always the same type, it seems to me that it should be possible to call the function pointer directly, using the array.

Code:
/* This replaces lines 498-503 inclusive */
    case Psubr:   {
      void *argv[32];   /* fixme: count the args, then alloca() */
      int i;
      /* NOTE: following check that i < 32 !!! [JG] */
      for (i= 1;  args;  args= cdr(args), ++i) argv[i]= cellToPrim(eval(car(args), env));
      argv[0]= &argv[1];
      /* return mkNumber(primcall((apply_t)psubr(fn), (void*)argv, sizeof(void *) * i)); */
      return psubr(fn)((Cell *)argv, env);


You can then remove the entire primcall function, I think.

Note that there is a danger of stack overflow in the apply( ) function because while it fills the local argv array, it doesn't check if there's an overrun. I didn't dig into what "eval(car(args), env)" does but if it returns nonzero too many times, you have a stack trashing zombie on your hands :). However, I also didn't know what the best way would be to bail out if there were too many arguments, so I just inserted a comment so you can keep it in mind for later. It may be easy to figure out how long the list of arguments is without getting them (again, I didn't dig into the code) but the comment is right: it should determine the length and alloca to allocate argv on the stack instead of trusting the caller to set up 31 arguments or less.

I also didn't dig into the reason why the arguments are stored from index 1 and argv[0] is set to the address of argv[1]. The functions that can be called through this mechanism had better know what they are doing :).

===Jac


Top
 Profile  
Reply with quote  
PostPosted: Wed Sep 07, 2016 2:39 am 
Offline

Joined: Wed Jan 08, 2014 3:31 pm
Posts: 578
So I have a work around for the builtins. I will comment them out for now and add them to the to do list. That way I can continue getting the code to compile and link. When I start debugging I have a lot of things to worry about before getting anyway near that code.

The latest gem is that GCC allows pointer arithmetic on void *, which isn't allowed by other C compilers. It is also used throughout the garbage collector. So this should be fun to replace.


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

All times are UTC


Who is online

Users browsing this forum: AdsBot [Google] and 9 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: