C3 0.8.4 The last renaming
C3 0.8.4 is now available. This release expands parameter reflection and contracts, adds configurable stack probing and stack protection, introduces iOS support, and brings a new collection type and stream utilities to the standard library.
This release is titled “The Last Renaming” because it very likely contains the final adjustments to C3’s keyword set – and I think we’ve finally got them right.
Changing keywords is hardly the best way to convey stability, but sometimes it is necessary. In C3's case 0.9.0 means syntactic freeze of the language, so getting the choice right in the 0.8.x cycle is vital.
Of course, as usual the old keywords will still work as part of the backwards compatibility with 0.8.3 and earlier, but moving to the new ones are recommended.
Language changes & improvements¶
Renaming preview for 0.9 – changing the -def family¶
faultdef and attrdef were introduced in 0.7.0 and constdef in 0.7.10. The idea was to unify them under an easy-to-remember "def" suffix. Unfortunately a downside is that this pushes them to retain a similar syntactic shape. After much discussion, we're experimentally introducing new names:
faultdef->excuseattrdef->attrmacroconstdef->constset
excuse comes from the general terminology, where the fault returned for an empty optional is called the "excuse" in the documentation. That this becomes a keyword further cements that term. In addition, excuse gains a shape with {}, not supported by faultdef:
attrmacro was chosen because attrdef takes parameters and in general acts closer to a macro than an alias.
Both cenum and constset were considered. Eventually the latter won out because it establishes a stronger independent feature, whereas the cenum hints at it just being an "enum that works like in C", which isn't correct.
Note that attrdef, constdef and faultdef will work throughout the entire 0.8.x cycle. The new keywords are considered an experimental change, and will be evaluated until 0.9.0. It will become the standard in 0.9.0 only if it's decided that the names are an improvement.
Pinpointing @require failures¶
It is now possible to associate a requirement with a particular parameter by placing it in brackets after @require:
This lets diagnostics point directly to the argument responsible for a failed precondition, making contract failures easier to understand at the call site.
Parameter reflection¶
Parameter reflection is more capable in 0.8.4. $reflect(foo).param_struct and Foo::param_struct provide access to a function's parameters as a struct. This allows macros to build up calls by populating each field, and then use struct splat to make the call:
fn void foo(int a, double b) { }
fn void test()
{
// The parameter struct has fields `a` and `b`
var $ParamStruct = $reflect(foo).param_struct;
// Create a struct
$ParamStruct s;
// Populate them.
s.a = 10;
s.b = 2.3;
// Use struct splatting to make the call
foo(...s);
}
Parameters can now also carry @tag attributes, and reflected parameters support get_tag, tags, and has_tag.
It is also possible to inspect a parameter's default value through .default_value.
As part of this work, FooFn::params now returns reflected references rather than ReflectedParam values. Code using this reflection API may need to be updated.
Conditional aliases¶
Alias definitions now accept ternary expressions. This makes it possible to select an implementation using a feature flag without wrapping the alias in a separate conditional declaration:
Compiler and toolchain¶
Stack hardening and the x86 red zone¶
C3 0.8.4 adds several controls for stack hardening. The new --stack-probe, --stack-probe-size, and --stack-protector options configure stack probing and stack-canary generation from the command line.
Individual functions and lambdas can override these settings with @stackprobe(level), @nostackprobe, @stackprotector(level), and @nostackprotector. Naked functions no longer receive stack protectors by default.
The new @noredzone attribute disables use of the x86-64 red zone for a function. As a temporary safety measure, functions on ELF x86-64 are emitted without red-zone use. ELF and COFF output also gained support for the dso_local attribute.
The --implicit-float setting provides control over the emission of floating-point operations. This is useful for low-level targets where implicitly generated floating-point instructions are undesirable.
Platform and build improvements¶
This release adds support for targeting iOS. LLVM compilation scheduling has also been adjusted so the largest modules are built first, helping make better use of parallel compilation.
Project target types benchmark and test now work correctly with c3c benchmark and c3c test, including targeted benchmark functions. Project names passed to c3c init may now contain -, and the new --keep-obj option preserves object files after building and linking.
The compiler can now warn about unused locals and parameters with --warn-unusedlocal and --warn-unusedparam. Diagnostics have been improved for incorrect panic-function names, excessively long source lines or underlines, and build options incorrectly written with =.
Faster slice equality¶
Equality comparisons between slices of flat types are now lowered to memcmp instead of scalar loops, reducing the cost of comparing suitable slices.
Standard Library Updates¶
Streams and collections¶
The standard library now includes CachedInStream and CachedOutStream, as well as the new std::collections::Tree collection.
InStream.read has been made consistent: it returns 0 at end of file instead of throwing io::EOF, and it now requires a non-empty destination buffer. Code which catches io::EOF from InStream.read should be updated to check the returned byte count instead.
Ranges gained a range::slice macro, and Path.is_link can be used to determine whether a path is a symbolic link.
Networking, logging and memory¶
Sockets can now report both ends of a connection through Socket.peer_address, Socket.peer_port, Socket.local_address, and Socket.local_port. Logging gained log::get_logger.
RefCounted has been reworked slightly to differentiate between finalization and freeing.
Thread pool changes¶
The old deprecated pool::ThreadPool is now disabled by default, and the new thread::ThreadPool is enabled by default. The old implementation is exposed as ThreadPoolOld, with -D OLD_THREADPOOL available for restoring the old name, for projects that do not want to change the name to ThreadPoolOld.
Fixes¶
This release fixes a broad set of compiler crashes and miscompilations, particularly around generics, vectors, bitstruct operations, default arguments, and deferred control flow.
Notable compiler fixes include:
- Generic declarations disabled with
@featare no longer instantiated. - Generic aliases inside functions no longer cause the compiler to hang.
- Generic function pointers are resolved before use regardless of generic resolution order.
- Dead-code detection is preserved correctly when leaving a scope.
- A
switchinsidedeferis no longer copied incorrectly during compilation. - Debug information for default-initialized arguments and code generation for decrementing bitstruct fields no longer crash with LLVM 23.
- Struct initializers containing arrays of vectors are aligned correctly.
- Reversing vectors larger than 128 elements no longer corrupts the heap.
$$modon unsigned integers now emits an unsigned remainder operation.- Optional-vector-to-array casts now generate correct code.
- Overloading from-end subscripts such as
foo[^1] = 2now works correctly. - The configured C compiler is now used consistently.
The standard library also received several correctness fixes. Large left shifts now work for BigInt, copying a DString no longer overwrites its allocator, and FixedBlockPool handles changes to grow_capacity correctly. Fixes also landed for mem::equals, UTF-16 surrogate pairs in String.escape, virtual-memory reserve page sizes, and stale thread IDs returned by thread::current on POSIX.
String case-conversion attributes such as @str_pascalcase and @str_camelcase now retain digits instead of treating them as discarded word separators.
Thank yous¶
Again, this release wouldn't have been possible without the C3 community. I'd like to extend a deep thank you to everyone who contributed through code, issue reports, testing, and discussions.
PR contributors for this release¶
Stdlib: Archishman Nag, m0tholith, Manu Linares, Ricardo Tomasi, Senthilnathan, Velikiy Kirill.
Compiler & toolchain: Alexandru PaniČ™, Darvisim, Manu Linares, neerajnangireddy, rickyadastra, ssimb, Vyacheslav Denisov.
CI/Infrastructure: Darvisim, Fernando LĂłpez Guevara, Manu Linares.
Change Log¶
Click for full change log
Changes / improvements¶
- Improved error message when providing an incorrect name for the panic function.
- Add the ability to pinpoint a
@requireusing the@require [a] a > 0 : "a must be greater than zero"syntax. #1804 benchmarkandtestproject targets now work properly withc3c benchmarkandc3c test.- Add
$reflect(foo).param_structandFoo::param_structproperties. #3099 - Allow the parse
faultset { ... }. Forfaultset,faultconstandexcuse. - Improved error messages when underlined error is too long, or lines are too long. #3383
- Add
--implicit-floatsetting to control emission of floating point operations. #3449 - Allow parameters to have
@tag#3084 FooFn::paramsno longer returnsReflectedParambut a reflected reference. This allows get_tag/tags/has_tag.- Allow parameters to be queried for the default value using
.default_value. - Allow ternary in alias definitions, e.g.
alias foo = $feat(ABC) ? foo_1 : foo_2. #3464 - On elf-x64, add noredzone to functions as a stopgap solution.
- Add
--stack-probe,--stack-probe-sizeand--stack-protectorto configure stack probing and stack canary generation. #3437 - Add
stack-probeandstack-protectorproject options. #3437 - Add
@stackprobe(level),@nostackprobe,@stackprotector(level)and@nostackprotectorattributes on functions and lambdas. #3437 - Naked functions no longer have stack protectors by default.
- Add
@noredzoneattribute. - Add
dso_localattribute on ELF/COFF. - Let LLVM build the biggest modules first.
- Support for iOS.
- Allow
-inc3c init some-project. --keep-objadded, to prevent object files from being deleted after building/linking.- Slice equality for flat types is now lowered to
memcmp, avoiding scalar loops. #3491 - Add
--warn-unusedlocaland--warn-unusedparamto detect unused parameters and locals. #3485 - Improve the error message for build options which use
=. - Add control registers to x86 inline assembly
Stdlib changes¶
CachedInStreamandCachedOutStreamadded.InStream.readnow consistently returns 0 on EOF, and never throws io::EOF, and requires a non-zero buffer target.pool::ThreadPoolnow only available usingThreadPoolOldunless-D OLD_THREADPOOLis used.- Add
std::collections::Tree. std::netaddedSocket.peer_address,peer_port,local_address, andlocal_portfor retrieving remote and local socket address information. #3460- Add a
range::slicemacro. - Add
log::get_logger. RefCountednow correctly makes a difference between dealloc and free.Path.is_linkadded.
Fixes¶
- Vmem incorrectly handled reserve page sizes.
@returnwas accepted in doc comments for non-function/macro declarations.- Can not run targeted benchmark function in project #1651.
- Detection of dead code would get reset after visiting a scope, causing a crash in codegen. #3453.
- Switch was incorrectly copied inside of defer, causing crash in codegen. #3454
- Codegen for debug info was incorrect for default init on arguments, causing crash with LLVM23.
- Codegen for bitstruct
b.foo--was incorrect, causing crash with LLVM23. @str_pascalcase/@str_camelcasetreated digits as word separators and dropped them from the output. #3287- Typedef access resolution preferred inner type field over method. #3457
- Generic gets instantiated despite being disabled with
@feat#3459 - Compiler hangs with generic alias in function. #3470
- Resolution order for generics may cause generic function pointers to not get resolved before use. #3471
- Stale tid in
thread::currenton POSIX. #3472 - Compiler crashes on implicitly casting a
&a - &bto another type. #3477 - BigInt shift left would not work correctly with shifts over 32 bits.
- DString copy would accidentally overwrite the allocator.
- FixedBlockPool would not correctly handle changes to grow_capacity.
- Fix bug in
mem::equalscalculating the last part to compare. - Miscompilation of struct initializers when a struct contained an array of vectors, causing incorrect alignment. #3483
String.escapewould not correctly handle UTF16 pairs.- Heap buffer corruption when reversing vectors larger than 128 elements due to an undersized allocation.
$$modon unsigned integers emitted signed division instead of unsigned remainder.- Improved codegen for casting an optional vector to an array.
- Overload for
^1, e.g.foo[^1] = 2did not work correctly. #3496 - Correctly use defined C compiler in all cases. #3495
Want To Dive Into C3?¶
Check out the documentation or download it and try it out.
https://news.ycombinator.com/item?id=49798843 Have questions? Come and chat with us on Discord.
Discuss this article on Hacker News or Reddit.