Jump to content

Making libraries in cc65


ZeroByte
 Share

Recommended Posts

At some point I'll be making some of my code into libraries and a recent thread about cprintf vs printf started talking about the code sizes of each. That got me thinking.

I notice that code modules seem to be loaded as "function.o" modules.

So does this mean that a good design for libraries is to make each function into its own separate source file? Suppose my library has 2 functions foo and bar.... If I had mylib.c containing both functions, and I used ar65 to make a library containing mylib.o - if a project uses foo but not bar, would bar get linked in anyway since it's in mylib.o?

To avoid this, should each and every function get its own .o file?

Link to comment
Share on other sites

That's a reasonable design for almost every platform (aka machine and toolchain). It really depends on just how advanced the toolchain tries to be.

By putting every function in its own source file, and including those in a library, the linker can include just the object files that have needed functions. But there is not a hard and fast rule that a linker should exclude unused functions (though I think most do). It could just say "you want stdio? I'm pulling everything in!"

Some toolchains are smart enough to write every individual function / declaration as a separately linkable entity, so it doesn't matter if you put everything in one file, it can still optimize the final file size by excluding unused pieces.

Where you're talking about cc65 and X16, then your plan is a sound one.

  • Like 2
Link to comment
Share on other sites

You can go further.  If several of your functions use the same data or variables, in a way that doesn't conflict with each other, then you can put that data/variables into their own source files (those files might not have any code in them).  The idea is that your program won't have two copies of them even if that program is using two functions that need them -- it's less bloat.

  • Like 1
Link to comment
Share on other sites

2 hours ago, Scott Robison said:

Some toolchains are smart enough to write every individual function / declaration as a separately linkable entity, so it doesn't matter if you put everything in one file, it can still optimize the final file size by excluding unused pieces.

I wonder if cc65's tool suite is smart like this - one might expect it to be, given the tight resources of 6502-based systems.

Link to comment
Share on other sites

1 minute ago, ZeroByte said:

I wonder if cc65's tool suite is smart like this - one might expect it to be, given the tight resources of 6502-based systems.

It could be, but I'll guess that it just uses the "individual file" approach. That's probably the very reason why their library is written that way to take advantage of "only link the object files used".

More than anything, I wanted to point out that not all toolchains are exactly alike.

  • Like 1
Link to comment
Share on other sites

Not that it's hard to make a unique C / S file for every function (exported or not).

I suppose it would also make sense to have only the exported stuff (api calls, typedefs, structs, global variables, etc) defined in a common mylib.h / mylib.inc , and then have purely-internal h/inc files which #include the api header files and then go on to declare the internal utility functions/data structures that the API is not going to expose.

 

  • Like 1
Link to comment
Share on other sites

related question: is there a command-line option for cc65 to compile and then assemble directly to .o files w/o having to first build .s and then run ca65 to assemble those .s files into .o files?

Or maybe a way to tell cl65 to stop at .o and skip the linking phase......

Link to comment
Share on other sites

cc65 has a number of different programs. cl65 is the "compile and link" driver that can do all the steps. The documentation says the -c command line option is probably what you want: "This option forces cl65 to stop after the assembly step. That means that C and assembler files given on the command line are translated into object files; but, there is no link step. Object files and libraries given on the command line are ignored."

Link to comment
Share on other sites

On 8/13/2021 at 11:00 AM, Scott Robison said:

Some toolchains are smart enough to write every individual function / declaration as a separately linkable entity, so it doesn't matter if you put everything in one file, it can still optimize the final file size by excluding unused pieces.

The LLVM linker works that way.

Link to comment
Share on other sites

I've done some tinkering and it appears that the cc65 linker pulls in code at the library module level - so any code contained in "module.o" gets linked in regardless of whether it's actually used in the program.

So let's say fubar.c contains functions foo() and bar(), and yolo.c contains functions yo() and lo().
These compile to fubar.o and yolo.o
Archive them as mylib.lib <--- fubar.o yolo.o
If your program uses function foo() but not bar - the object code for bar() will be linked in anyway as a component of module fubar.o

If the library were to have foo.c and bar.c ---> %.o ----> mylib.lib then only the one actually used gets included by the linker.

Further tinkering has revealed that the names of the object libraries in an archive do not clash with the names of object files in the program using the library.
e.g.: if your program has fubar.o and mylib.lib contains a module fubar.o - these do not get confused by the linker.

Identical symbols, however, do cause issues, even if not exported in mylib.h - so if I were to make a function foo() in my program, and foo() wasn't published in mylib.h - the compiler won't bark, and the project will build, but you may be surprised to see strange things happening. When library code tries to call foo(), it will be linked to the program's version of foo() and not the one in the library.

So that's one good reason why libraries often have some universal prefix e.g. sdl_XXXX

Link to comment
Share on other sites

38 minutes ago, ZeroByte said:

I've done some tinkering and it appears that the cc65 linker pulls in code at the library module level - so any code contained in "module.o" gets linked in regardless of whether it's actually used in the program.

So let's say fubar.c contains functions foo() and bar(), and yolo.c contains functions yo() and lo().
These compile to fubar.o and yolo.o
Archive them as mylib.lib <--- fubar.o yolo.o
If your program uses function foo() but not bar - the object code for bar() will be linked in anyway as a component of module fubar.o

If the library were to have foo.c and bar.c ---> %.o ----> mylib.lib then only the one actually used gets included by the linker.

Further tinkering has revealed that the names of the object libraries in an archive do not clash with the names of object files in the program using the library.
e.g.: if your program has fubar.o and mylib.lib contains a module fubar.o - these do not get confused by the linker.

Identical symbols, however, do cause issues, even if not exported in mylib.h - so if I were to make a function foo() in my program, and foo() wasn't published in mylib.h - the compiler won't bark, and the project will build, but you may be surprised to see strange things happening. When library code tries to call foo(), it will be linked to the program's version of foo() and not the one in the library.

So that's one good reason why libraries often have some universal prefix e.g. sdl_XXXX

This is how I suspected it. And the fact that you can create conflicting symbols in your program is sometimes / often used to good effect. malloc & free in particular are often replaced because memory access patterns can vary a lot between programs. A special purpose allocator tailored to the needs of a program can greatly increase overall performance, and replacing malloc and free is often a "cheap" way to do it.

Link to comment
Share on other sites

13 hours ago, Scott Robison said:

And the fact that you can create conflicting symbols in your program is sometimes / often used to good effect.

This fact is not lost on me. I've been pondering whether it might make sense for my zwidgits library- instead of using function pointers, just override the code entirely with routines specific to the project. I'm thinking no, as each type of widgit you define will potentially need its own unique version of the routine e.g. draw_widgit(id).. but that application certainly sprung right to mind. (Poor man's inheritance/subclassing)

Link to comment
Share on other sites

I settled on a Makefile format that works for me, and now I copy it with every new project.  All I have to change is the SOURCES and PROGRAM.

At one time I knew what each bit did.  Now I'm kinda murky on the CFLAGS.

Mind the mangled whitespace and lost tabs... Make is finicky... I'll attach it too.

SOURCES = main.c petscii-panel.c

PROGRAM = BLINKENLIGHTS

CC65_TARGET = cx16

CC    = cl65 
CFLAGS  = --cpu 65c02 -t $(CC65_TARGET) --create-dep $(<:.c=.d) -Ors 
LDFLAGS = -t $(CC65_TARGET) -m $(PROGRAM).map
OBJDIR  = .obj

#############################################
OBJECTS = $(SOURCES:%.c=$(OBJDIR)/%.o)

.SUFFIXES: 
all: $(PROGRAM)

ifneq (($MAKECMDGOALS),clean)
-include $(SOURCES:.c=.d)
endif

clean:
    $(RM) $(OBJECTS) $(SOURCES:.c=.d) $(PROGRAM) $(PROGRAM).map

.PHONY: all clean

$(PROGRAM): $(OBJECTS)
    $(CC) $(LDFLAGS) -o $@ $^

$(OBJECTS): $(OBJDIR)/%.o: %.c
    $(CC) -c $(CFLAGS) -o $@ $<

Makefile

Edited by rje
Link to comment
Share on other sites

One thing I've had issues with in my adventures with Makefiles is the .h files. If I make changes to an .h file, most of the time it doesn't cause Make to realize that the c files which include it should be rebuilt.

In Makefiles where I went through and defined each and every dependency myself, I can make that happen, but these self-directing Makefiles tend to miss that. Is that what the --create-dep option helps with?

  • Like 1
Link to comment
Share on other sites

35 minutes ago, ZeroByte said:

One thing I've had issues with in my adventures with Makefiles is the .h files. If I make changes to an .h file, most of the time it doesn't cause Make to realize that the c files which include it should be rebuilt.

In Makefiles where I went through and defined each and every dependency myself, I can make that happen, but these self-directing Makefiles tend to miss that. Is that what the --create-dep option helps with?

I think so. Every tool chain has its own way of dealing with dependencies if they are designed to be used in a makefile. Back when I first started with C (Turbo C 2.0), the IDE (if I recall correctly) had dark magic to make things "Just Work(TM)". Such has been true with every IDE I've ever used where the IDE and toolchain was provided by the same company (Turbo C, Turbo C++, Borland C++, all the various Visual C++ versions I've used).

When it comes to make files though, they can't just know how every language does "internal" dependencies. I've seen projects create their makefiles to include explicit listings of header files along with dependent c files. I've seen things like create-dep. I think CMake has some dark arts in it to know how certain languages do it and ensure they include that information, since not every toolchain will do it the same way.

Link to comment
Share on other sites

  • 3 months later...

So back to Zero's talking about library files.   Looks like the rule is:

1. If the very-common pattern is to USE all of the functions, then gang them together.
2. If someone might just want to use ONE function, then split them apart.
3. And make the common data its own library as well!

This could make for a very leggy set of libraries!

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

Please review our Terms of Use