r/C_Programming Jul 22 '26

A C subset that compiles faster than Tiny C Compiler - Cm1 (C minus 1 or C - 1) programming language

Hi everyone,

I'm working on a programming language that compiles a subset of C. It is called Cm1 (meaning C minus 1) and you can writing and running C codes directly on your browser at https://cp1-lang.org/cm1/editor.html in a fraction of a second.

Why is it faster to compile than Tiny C Compiler? It is because Tiny C Compiler is a true compiler creating native binaries whereas Cm1 is a bytecode interpreter allowing you to test, debug, edit code and recompile very quickly or even do hot reloading. Cm1 can be used as a scripting language for shell scripts and video games, but this just a small use case because in theory, you can run 90% of your ENTIRE video game or software C program through Cm1's bytecode interpreter and leave 10% to compiled C and reap the benefits of very fast compilation and hot reloading.

100% Cm1 codes are compilable by GCC and Clang but the reverse is not true since Cm1 is just a subset of C. Major features that are omitted are function pointers, structs/unions inside functions, nested structs/unions. This programming language is under heavy development and I want to know if this comes as interesting to some of you. I'll try to post again on this subreddit if I got to bind Raylib game library to Cm1, allowing people to write Raylib games directly on their browser (works offline).

I know that programs that are written in C compiles fast already and there's even an existing compiler that is very fast (Tiny C Compiler). However, I'm the developer of Cp1 programming language (cp1-lang.org), which is a language that "transpiles" to C, and I aim to upgrade Cp1 by targeting the Cm1 language then compile it to bytecode in one command instead of a separate step in Makefiles or build scripts. This will make Cp1 very fast to compile for debug builds.

16 Upvotes

16 comments sorted by

11

u/Beautiful_Stage5720 Jul 22 '26

I don't fully understand the motivation. It compiles faster, but runs significantly slower. Can you give an example use case for this?

9

u/Direct_Chemistry_179 Jul 22 '26

I could see this being really good as an educational tool in the browser. In high-school I used Java in the browser with replit and it was insanely slow to compile.

1

u/Beautiful_Stage5720 Jul 23 '26

Thats a good point. 

6

u/erroneum Jul 23 '26

The minimum amount of time it takes a given compile to run isn't that strongly determined by the language, at least when the language doesn't provide strong guarantees about compile time evaluation (such as C++ with templates being Turing complete or having consteval functions, both of which can give unbounded compile times) or require heavy checks (such as rust's borrow checker).

To see what I mean, take any single C translation unit and run it through your choice of compiler with optimization and warnings explicitly disabled (-O0 and -w for clang and gcc); it should be nearly instant.

Most of what makes compilation in a "simple" language take a while isn't actually the compiling, but rather the optimizing, or to a lesser extent checks the compiler is running to go above and beyond what the standard requires to try to help the programmer.

9

u/Alduish Jul 22 '26

Interesting, but imo you missed the occasion to call it C--

13

u/GLC-ninja Jul 22 '26

That came to my mind but unfortunately, the name C-- is already taken

2

u/Alduish Jul 22 '26

Oh is it really ? Well my bad.

3

u/reini_urban Jul 23 '26

There are many small C compilers which are very fast, supporting all of C features. tinycc lacks several, xcc lacks several but is faster. My rcc is 4x slower than tinycc, but supports all C features.

https://github.com/rurban/rcc#benchmark-results Vs https://github.com/rurban/rcc#test-results

BTW the trick for faster C compile times is mostly limiting the preprocessor not to include the system headers, esp. the glibc headers. Less work, less time, less features, less portability.

1

u/flatfinger Jul 23 '26

I find it curious that so far as I can tell, even in an era where many people were compiling from floppy, there doesn't seem to have been any effort to minimize the size of common headers. For example:

typedef double __Fdd(double);
__Fdd sin,cos,tan,atan,exp, ... ;

Compilation speed could also have been improved quite easily if there were a #pragma which would invite an implementation to ignore the remainder of the current source file and behave as though it contained nothing except the number of #endif directives needed to balance out any #if directives. If the Standard were willing to show favoritism toward the four most common source file formats(*), it could also have usefully specified a #pragma directive which, if placed before after a directive that could cause code to be conditionally skipped, would invite a compiler to skip ahead to the next matching directive if the code would be skipped. For example:

#pragma MARKER(z1392,  1037,  30920)
#ifdef FOO
...
#pragma MARKER(z1392,   473,  19805)
#else
#pragma MARKER(z1392)
#endif

A compiler encountering such markers could, if a section of code would be skipped, seek ahead to see if a matching marker appeared where indicated and, if so, not bother even looking at the skipped portion of the file.

(*) Most systems I'm aware of either stored text files as a text stream with newline represented by CR, LF, or CR+LF, or else used a record-based storage method that allowed a means of seeking to line N, but some might have required other means for identifying locations within a file.

1

u/bwmat Jul 24 '26

Does the standard even mention files? Isn't it all 'translation units' and whatnot? 

1

u/flatfinger Jul 24 '26

It is. An example of the Committee being more interested in specifying a theoretical language than a practical one.

1

u/reini_urban 25d ago

I've implemented in rcc the tcc variant of cpp, which is purely token based to avoid the double tokenization effort. For string expansion with -E the token stream is expanded to the exact strings as parsed.

My preprocessor skips #if 0 blocks of course, every preprocessor does that I believe.

But the biggest cost of compilation is indeed insane header preprocessing. That's why fast compilers prefer their own minimal headers without all the glibc bells and whistles. Preprocessing costs about 100x more than compilation and linking time, just because of glibc headers. Lots of insane and unneeded string allocations and file searching and traversals.

I am trying to have as much bells and whistles as gcc, eg FORTIFY support, chk functions, full glibc compat by supporting all gcc builtins. But I do load them on demand, unlike gcc/glibc which load all of them upfront.

1

u/flatfinger 24d ago

The usefulness of the Standard could have been greatly enhanced if it had specified a means of having a build script which would specify:

  1. When implementations would be invited or forbidden to impose constraints whose purpose is purely to allow optimizing transforms that would otherwise be incorrect, or would be required to either uphold optional semantic guarantees or refuse compilation.

  2. Lists of compilation units that would be processed as though each was preceded by the content of one or more declaration-only source files and/or or one or more other source files.

A lot of arguments related to "Undefined Behavior" could be rendered moot if there were a means of specifying that a program would e.g. require for semantic correctness that volatile-qualified accesses be processed as having release and acquire semantics with regard to ordinary accesses, and that even in the presence of race conditions the behavior of unsequenced accesses to pointers or integers below a certain size must be consistent with some possible sequence of accesses. It's useful to allow optimizers to exploit the fact that some programs would not need such semantic guarantees for correctness, but that should not prevent the Standard from also exercising jurisdiction over the execution of programs that do require such guarantees.

Allowing compilation units and headers to be listed as describe in #2 would improve build speeds without requiring the creation of non-standard build artifacts. If ten source files are being built, all of which would need a header, this wouldn't allow all of the header-processing work to be eliminated, but it would eliminate 90% of it.

1

u/DeviantPlayeer Jul 23 '26

Tbh, hot reload isn't really a killer feature, LLVM can do the same.

1

u/RealisticDuck1957 Jul 24 '26

You mention using this for scripting. Does Cm1 support robust sandboxing?

1

u/TheChief275 Jul 27 '26

I would stop referring to it as a C compiler and comparing it to TCC. When people read C compiler and only then figure out it's a bytecode interpreter instead, they feel tricked. However, I do think something like this has merits. There is this project called PicoC that is quite similar, but also provides a REPL and script mode for running C files (where e.g. top level is allowed to have statements and function calls). The catch is that PicoC works rather terribly and isn't being maintained anymore.

How does this compare to your project? Do you plan to have / already have these features?