C2y Proposal: Essential Effects for C
www9.open-std.org
www9.open-std.org
This requires a somewhat broad notion of what types are in the first place. I think most people have a mental model of types that describes data types like int or string, but anyone familiar with Java knows what effects are because Java has the “throws” keyword, which is an effect. If your method in Java throws IOException, the method must be marked “throws IOException” or it is a type error.
So I think our definition of “type” should be broad enough to include effect systems.
Effects describe code. For example, a "panic" effect states that function may trigger panic/exception during its execution. In other words, existence or absence of an effect is a function property. You can view effects as special types which are used only for functions (to be slightly more precise, signature + effects form function type), but you would need to use a slightly more advanced notion of type system which includes subtyping, e.g. function "panic fn(u32) -> u64" can be used in place of "panic io fn(u32) -> u64".
…although I’ll admit that didn’t quite feel the case for type generic macros.
What's with this strange desire to mandate One True Way to implement function calls in every language implementation? If I want to use rbx/r11/r10/r9 for argument passing and return results in rdx/rsi by default and have to use
@if os.LINUX and cpu.X64
import external func[[regseq("rdi,rsi,rdx,rcx|rax"), library("libc.so")]] fwrite(buff ptr, size int64, count int64, stream ptr) int64;
@endif
to interoperate with one particular implementation of libc for one particular OS on one particular ISA, then that should be perfectly fine.Not sure what “standardize ABI” is supposed to mean… each platform has its own ABI, and most of those ABIs are already standardized. The standards are just not part of the C standard.
- It involves making too many decisions on behalf of users. C++ dodges the question somewhat by making hash tables templated, so you can customize them.
- There are a hojillion hash table libraries out there which are written in C—just pick one and use it.
Furthermore, those wheels aren't necessarily interoperable the way something built into the standard library would be; it's not just a convenience.
I hate it but whatever - my love for C ended sharply after C99 fno-strict-aliasing anyway.
(I also want to do things like mutate bytes of the machine code but overall I can make peace with doing that in buffers that aren't currently executing. It's very much not allowed to cast some bytes to a function pointer, even if you've got the ABI right, and that's ridiculous)
It will have machine-native integers, arrays of such integers, arrays of bytes, functions that take/return machine-wide integers, and that's pretty much it for the data types. All arithmetic is two-complement, signed or unsigned as you desire, and pointers are just numbers as well. Quality-of-life features include being able to straight-up include arbitrary binaries as function definitions:
func [[raw]] builtin_assembler_is_too_much_work(a, b) "\x48\x89\xF8\x48\x99\x48\xF7\xEE\xC3";LLVM IR is interesting in that respect. Some libraries are written in it directly (all compiler runtimes afaik but there are probably others). The ergonomics on it are rather poor but writing exactly the control flow graph you want does work. It doesn't give control over register allocation or scheduling but does give control over the CFG and a decent approximation to instruction selection.
Some years ago I wrote compute kernels in terms of vector types and compiler intrinsics, including some that could target DSP style compute-and-branch operations. Adjusting the code and the compiler simultaneously worked really well - matched and thus replaced the handwritten assembly for the simpler cases. I miss that ISA / toolchain combination.
I'd be curious to hear what troglodyte can handle that C with compiler extensions and the type aliasing optimisations discarded cannot. Specifically the "language" I really should get around to writing for x64 and/or amdgpu would be "C" with:
1. an intrinsic for each instruction, with a function type. Probably as a header file with a lot of declarations in it.
2. mark variables as allocated to specific registers, or maybe in a set of registers. Syntax.
3. some notation for bespoke calling conventions. Syntax.
4. some notation for ordering instructions (i.e. scheduling them)
5. a lattice of address spaces (global outlives stack etc, a bit gpu specific)
The intrinsic per instruction definitely works. That gives a pretty way to write AVX code or similar. Marking variables as allocated to specific registers also works (gcc does this to an extent with the asm annotation). Point 3 and onwards is where I get hazy on how to make it work sensibly. Custom calling conventions work really well in a compiler backend, and in C they need to be expressed in the function type, but I'm still obsessing how best to do that.
I'm confused. C99 is a standard of C, while fno-strict-aliasing is a non standard compiler switch for a specific implementation. Did you mean to put those two things next to each other? Especially since that switch appears to mean "violate the standard in a specific way", and that thing to violate (strict aliasing) goes back at least at far back at the original C89 standard.
Specifically calling out 99 as the one before 11 introduced _Generic where it could have been overloadable, and atomic where it should have been the gcc intrinsics. That feels like a tipping point between making the language better and diverging from reality.
The op actually references an implementation (QAC? presumably a C compiler) which is nice. The current ISO language would have been improved if "has been implemented and some people use it" was a requirement on adding things to the language. I cannot believe anyone programmed with _Generic and thought yeah, this is what I want.