Совет, кажется, слияние сообщения, так что я не могу получить комнату, чтобы поставить русских в, извините: (
= english ==
It actually comes down to how the language itself works.
One source file per function is exactly what is required by assembly language and C (no surprise as C was meant to model assembly language programming) and the incremental linking method invented in the 1960s is exactly sufficient for "smart linking" C and asm programs.
A C program not destined for a library would organize related functions on a per file basis and in user programs, it's ok to have more than one function per file because presumably the user only writes functions he uses. When it comes time to make a library, it's not a big deal to take all the functions from one file and separate them into one file per function in a directory by hand. The directory acts like the class or module name, the functions in the directory are clearly visible to anyone looking for what they can do with the library and the incremental linking makes sure only what is needed is attached to the binary. The code is organized and easier to use as it's easier to see what is available.
BUT, this only works because asm and C have just two scopes -- local and global. With imperative languages like Pascal and object oriented languages, there is more than one scope. Taking Pascal as an example, a single module (? it's been a long time since I've programmed in pascal) can have many nested functions and variables in different scopes. The functions in such a module cannot be simply separated by hand into individual files for the linker -- you have to modify names to enforce scoping rules. Object oriented languages require something similar that is not easily done by hand. "Smart linking" solves this problem by automatically extracting individual functions from these nested scopes or files and breaking them into units that works for the incremental linker.
So I agree with you here, you need this for Pascal-like languages and oo languages. Modern day linkers still work in an incremental fashion and "smart linking" is something done by the compiler. A C compiler (like sdcc) will never need such functionality so your adding it to the toolchain as a separate utility is the right thing to do. Placing it at the C source level is a good way to do it too, and may be an occasionally useful tool for C programmers. It's how modern day compilers do it (or it was at one time), although they may not show you all the hundreds of individual files they generate and instead directly generate a library file.I need to have Pascal/Oberon point of view to this problem.
Q-Master's alternative of generating a define to opt in and out of parts of the source code is another way to go that does not run into scoping issues (below). However, it only works at the source level. If you wanted to write a library in Pascal using this method and export it for use by C programmers or asm programmers, you couldn't without also exporting the source of this library in Pascal and the build environment. That's not what you want.
The difficulty with "smart linking" is it changes the scope of functions and variables. By extracting each individual function into its own file, you make that function global.
Problems in C:
static int a = 5;
static int multiply(int a, int b)
{
return a*b;
}
main()
{
return multiply(a,6);
}
If you extract that multiply() function, you're adding it to the global scope but the C code is specifically making it local. You could end up with more than one function with name "multiply" and the linker may either complain or incorrectly link the first one it finds. You've also got the issue of how you are going to properly scope the local variable "a" above.
In Pascal-derived languages the problems are even worse because you can have many nested scopes.
Usually this is solved with judicious use of name-mangling. Above, the local variable "a" would be renamed to "xyz_a" and the local function "multiply" would be similarly renamed to "xyz_multiply". The references in main to these things would also be changed. "xyz" is some identifier that uniquely identifies the translation unit those functions are found in but it cannot be something totally random as, when building a library, you want those names to be predictable if it is to be accessed by another program or the symbols are to be understood while debugging.
Doing this requires the "smart linker" to have a lot of knowledge of the language it is splitting up and it's not a trivial program to write. So although Q-Master's method is less desirable, it does avoid these scoping problems.
I prefer your method but I don't think you have solved the scoping issues? Maybe that's what everyone is talking about when they mention clang, etc. The automatic translation is not always easy to follow.
---------- Post added at 10:33 ---------- Previous post was at 09:52 ----------
Yes, that is the right way to do things for C libraries and adding a "FLOS" scope to functions helps to prevent name clashes
For functions implemented in asm, I'd suggest a slightly different approach from what I have seen here that separates the asm implementation from the C interface.
Separating the asm implementation has a couple of advantages:
1. You can easily accommodate different C linkages. Right now sdcc only has one linkage but it's not a good one for interfacing asm code and sometime down the road there will be alternative linkages. In z88dk we have many different linkages: sdcc R->L param order, small C L->R param order, fastcall, callee. We also have two different sdcc C interfaces: one for the library using ix and one for the library using iy. The latter leads to smaller code because the library does not have to preserve the value of ix. All of these C interfaces use the same asm implementation without any overhead.
2. Your library code becomes attractive to asm programmers. The first thing many asm programmers do when they look at C libraries is turn up their noses because of the bulky C interface code. sdcc's C interface is very bulky and this discourages asm programmers from using library code. In z88dk we completely separate the asm implementation from the C interface so that is possible to build an asm library without any C overhead.
3. The C libraries have entry points for both C code and asm code. This allows easy mingling of C and asm in programs.
The last thing I would change from some of the code I've seen is to stop using ix to index parameters passed to an asm function. It's very slow and leads to large code. It is almost always better to pop passed parameters into registers before proceeding with the asm implementation.
An example of what I suggest, using an asm implementation of strcpy():
File: asm_strcpy.asm
The sdcc C interface:Код:PUBLIC asm_strcpy asm_strcpy: ; enter : hl = char *s2 = src ; de = char *s1 = dst ; ; exit : hl = char *s1 = dst ; de = ptr to terminating NUL in s1 ; ; uses : af, bc, de, hl push de xor a loop: cp (hl) ldi jr nz, loop pop hl dec de ret
File: strcpy.asm
The z88dk C interface using callee linkage, just to show that the same asm code is being used:Код:; char *strcpy(char * restrict s1, const char * restrict s2) PUBLIC _strcpy _strcpy: pop af pop de pop hl push hl push de push af INCLUDE "string/z80/asm_strcpy.asm"
File: strcpy_callee.asm
The header file for the compiler:Код:; char *strcpy(char * restrict s1, const char * restrict s2) PUBLIC strcpy_callee strcpy_callee: pop af pop hl pop de push af INCLUDE "string/z80/asm_strcpy.asm"
FILE: string.h
The INCLUDEs mean there is no JP or CALL overhead to the asm implementation. The same labels from the asm implementation are available so that the C interface version also has the asm interface.Код:extern char *strcpy(void *dst, void *src);
Several versions of the library are built: one for asm programmers where the asm implementation is used, one for the sdcc library where the sdcc C interface is used, etc.
Notice that none of it is embedded into a C wrapper -- this is unnecessary when a function is implemented in asm. You scan skip the compiler entirely and use the assembler directly to make the library. Using a C wrapper is not entirely harmless as sometimes preamble and postamble code may be inserted by the compiler.