Over 25 Years of C99: Making a Concession

In the interest of bragging rights, we've tried to say that Ren-C builds on C89.

Not quite true, since we use // comments instead of just /* */ comments. But a lot of compilers before C99 supported that, and you could just strip them out.

Also not quite true because we don't declare all locals at the top of blocks in a function. But a lot of compilers before C99 supported that, and if not you could write a preprocessor to do it.

But a big feature that would helps if we permitted using it is C99 variadic macros. It means we can automatically add terminator signals to API calls:

So instead of writing things like:

if (rebNot("action?", rebQ(main_startup), rebEND))
    rebJumps("panic-value", rebQ(main_startup), rebEND);

We can say:

if (rebNot("action?", rebQ(main_startup)))
    rebJumps("panic-value", rebQ(main_startup));

Right now we support both modes, but hold off on using the nicer mode in the internal implementation.

The C++ build checks to make sure that the rebENDs are there if you mark a file as "C89-compatible" and that it should have them. So that makes it easier to not forget them if you meant them to be there. But they're still an eyesore.

I Think It's Time to Adjust The Rule

The original idea was that "public-facing" code would all use the nice mode, but we would bear the burden of rebEND in the internal code for the sake of making it compile on older systems.

However, the code for the interpreter itself is supposed to be a showcase of its features. If you are looking at the startup sequence in main.c and are hit with these rebEND-based API calls, that's will prevent appreciating it fully.

We should preserve the ability to call these APIs from C89. e.g. you should be able to include libRebol from a C89 compiler. That API file should have a mode you can use to include it in a C89 program, where you have to provide the rebENDs explicitly.

But the internal implementation should allow the usage of the macro form. Not leveraging it makes reading the code for Ren-C itself not as pleasing as it should be. I want the code for stepping through the boot process and extensions to make the best impression. And rebEND detracts from an otherwise profoundly distinct experience.

The answer for people who want to build the sources on a very old compiler would be the same answer as for those who want to build it on a system that doesn't have //-comments...run the source through a preprocessor. Expand the rebXXX() macros to make a new version of the source, and then compile that.

Seems fair.

2 Likes

Since it's been 5 years since I made the post conceding to C99, I've updated the title of this post to point out the standard has been around for 25 years now... :roll_eyes:


Besides variadic macros, I haven't used anything too strange from C99.

But there's an important C99 thing that I think we kind of can't live without in C builds, and that's va_copy():

https://en.cppreference.com/w/c/variadic/va_copy.html

The problem is that garbage collection has to visit all the cells that have been spliced into a variadic API feed in order to protect from for GC.

Historically we've paid to convert the feeds to blocks if a GC happens... since traversing the va_list would lose our access to the items.

There've been improvements to the technique for handling antiforms in va_list as source code (clever quasiform-substitutions interacting with ^ and @). This technique is much less error prone and clearer than its predecessor, with intelligent failures.

But since it can fail intelligently... it can fail. And you don't want failures during GC.

With va_copy we can clean this up, visiting the variadics without reifying them... leaving the failures to happen synchronously when the evaluation happens.

There Are Other Ways

If it became important to build for a system that didn't have va_copy, you could just reify all API va_list to arrays at the API entry points.

In any event, taking advantage of va_copy is generally a performance win, by avoiding the creation of arrays.

(Although I have wondered if we should try taking out all the conditional handling of the va_list at runtime and seeing if using plain arrays is better...using variadics only at the entry points. It's a test worth doing.)

3 Likes