Rendered at 17:42:04 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
rurban 9 hours ago [-]
> If FilPizlonator determined that the inline assembly is not safe, then it'll replace it with a Fil-C panic. That panic will provide diagnostics about why the assembly was rejected.
Most stupid thing I ever heard. If a safety violation is known at compile-time, you error at compile-time. You might never catch it in a test, and there you have the panic at the customer. He will be pleased.
mrgriffin 8 hours ago [-]
Steelmanning this decision:
I would guess for the use-case of "I have a C project and I want to run it in Fil-C" the ability for this to be a warning + run-time panic is very helpful for quickly getting started. Reminds me of GHC's -fdefer-type-errors.
I agree that I wouldn't want to deploy a program where those panics are reachable*, but it's still handy for local development and/or maybe the developer knows they aren't reachable.
I haven't checked, but I'd guess there's a warning and a -Werror -style flag to opt-in to having a hard error for unsafe assembly?
* Obviously a panic is better than not. But guaranteed safeness is better than either of those.
flotzam 4 hours ago [-]
They acknowledge that it's odd, and give this justification:
"Using runtime panics has the nice property that inline assembly in dead code doesn't get in the way of porting software to Fil-C. Also, it's consistent with how Fil-C usually reports errors."
pizlonator 2 hours ago [-]
Yeah
Also, like, this is the initial implementation so I kept it consistent with the rest of the error checking
If someone wants a compile time diagnostic they can submit a patch
Jweb_Guru 3 hours ago [-]
You have to understand that Fil-C primarily exists as a marketing stunt. Once you do, you'll understand a lot of its technical decisions better. Its definition of "memory safety" involves translating C's semantics into those of a related language with arbitrary but technically defined behavior instead of having UB (for example, under some circumstances Fil-C considers it legal to read a value from a totally different, possibly-inaccessible pointer when dereferencing an unrelated pointer). The justification in all cases is that this is not only better than UB, but better than using a safe systems language that prevents these behaviors to begin with, because it applies at the executable level, while the large slowdown is justified because it allows people to use existing C applications unchanged instead of rewriting in a safe language (never mind that these two claims contradict each other). It's also supposedly better than one of the many solutions for C-in-a-runtime (like wasm, or Graal-C) because instead of using a hardened runtime that actually exists in security critical contexts, you get to trust system call wrappers written mostly solo by the author of Fil-C instead.
Against this background, crashing when inline assembly is determined to be doing something the author isn't sure how to deal with is pretty much par for the course -- it's a way to continue to claim that you can port over your old buggy C applications unchanged. You aren't supposed to actually use it.
pizlonator 2 hours ago [-]
> You have to understand that Fil-C primarily exists as a marketing stunt.
What a bitter way to analyze a technology.
> under some circumstances Fil-C considers it legal to read a value from a totally different, possibly-inaccessible pointer when dereferencing an unrelated pointer
Fil-C does not consider such a thing to be legal.
You can only access capabilities that you legitimately loaded from the heap, from other capabilities you legitimately loaded, and so on. So, if you access a pointer, it's because you had access to the capability.
> You aren't supposed to actually use it.
Wat
layer8 1 hours ago [-]
> translating C's semantics into those of a related language with arbitrary but technically defined behavior instead of having UB
There is no “instead” here. A C implementation defining behavior for certain UB still falls under what a C implementation is allowed to do for UB.
I don’t know if Fil-C is a fully conforming C implementation, but it could well be. In what way is it nonconforming that would warrant describing it as implementing “a related language” and not C?
wavemode 1 hours ago [-]
This comment is astonishingly uncharitable and also mostly wrong. Do better.
mananaysiempre 8 hours ago [-]
> While reviewing folks' C and C++ code, I've found the following reasons for inline assembly, where 1 is most common:
3a. rdpru (similar issues to cpuid) or rdpmc perhaps surrounded with lfence or cpuid inside the same assembly chunk
For obvious reasons, this is somewhat niche and may not even make it into production code, but it’s also important when you do need it. It’s also memory safe. I guess in such cases you’d use fast C rather than Fil-C though.
4a. rseq
Probably even less feasible than atomics TBH, as such blocks will usually also contain control flow (at least that implied by to the nature of rseqs).
> Before the advent of AI, writing a parser for x86_64 assembly would have been such an annoying task that I might have never gotten around to implementing support for memory safe inline assembly [...].
It is annoying, but even before the advent of AI that didn’t stop the developers of TCC for instance.
With that said, given Fil-C is Clang/LLVM-based, shouldn’t an assembly parser, at least, be already available somewhere? I was under the impression that Clang (unlike GCC) actually parsed asm blocks.
lifthrasiir 7 hours ago [-]
I had yet another reason to use inline assemblies in WAH [1]. In some compilers (I think it was GCC) intrinsics are imbued with `target("foo")` attributes, which cause forced-inline via `always_inline` attribute to fail somehow. I really needed that though because I was writing a fast bytecode compiler and being unable to force-inline meant each supported SIMD instruction had to pay function call overhead (which can be significant when your bytecode is literally a single native SIMD operation!).
I’m having vague flashbacks here so I might be guessing wrong, but: the only reason I’ve ever seen an “inlining failed” error from GCC when using intrinsics (and it was an actual error) was when it actually meant “you can’t use this intrinsic in this configuration” (the second half of the message, “target specific option mismatch”, is more helpful if still cryptic). Thus the fix was to change the argument of the -march= option or (for dynamic dispatch) decorate the caller with the correct __attribute__((target)). E.g. if you pass -march=x86-64-v1 but try to use AVX you’ll get such an inlining error. (This is unlike MSVC which will always allow you to use any intrinsic supported by the compiler.)
lifthrasiir 5 hours ago [-]
I think you are right in the interpretation. In my case though dynamic dispatch was already integrated with bytecode lowering so the error wasn't helpful :(
mananaysiempre 4 hours ago [-]
Ah, so you want the function containing the interpreter loop to be compiled for the baseline architecture but some of the bytecode implementations inside it to use more advanced intrinsics? Yeah, I don’t think GCC has a good answer to that one. It also sounds gnarly from a general calling-convention perspective—how is the VZEROUPPER on exit supposed to be emitted if you can’t count on AVX?..
We recently (finally) got __attribute__((musttail)) in GCC[1], I’ve just tried it between functions with mismatched __attribute__((target))s and it does work, so theoretically you could code your interpreter that way. But it seems like you’re still bound to keep loading and storing vector state from and to memory and VZEROUPPERing your registers after each bytecode, and that doesn’t sound like a particularly good time.
I find it charming that to distinguish Fil-C from the K&R language they use the term 'Yolo-C'. I have never used inline asm before, I actually didn't realise how much behaviour it's specifying! (When I've needed asm it was on non-gcc compilers)
Edit to add: If I'm understanding this correctly we should be able to run this against projects and detect asm violations, I feel like this would be very valuable to be able to feed these back to maintainers
mananaysiempre 8 hours ago [-]
Fun fact: Watcom could also do fairly flexible inline assembly (and custom calling conventions) through its rather unassumingly named #pragma aux.
jdw64 15 hours ago [-]
What is more frightening about this than safe C assembly is that this level of implementation is achievable not with a SOTA model, but with a cost effective model like KIMI. There was human judgment involved in the middle, but reading the article, My reading of the process is as follows:
1.A developer identified the necessity of inline assembly.
2.Defined the safety boundaries for 'memory-safe' inline assembly.
3.Established strict policies for memory access.
4.Curated an allowlist of permissible instructions.
5.Set rigorous test criteria and 'done' conditions.
In short, with the overall guardrails in place, a sub agent loop was run, and this level of code was produced. This raises a number of interesting points about how we should use AI. I haven't looked at all the code, but the idea of passing assembly through safe zones without memory access, and using that as a foundation to achieve this level of implementation through AI, is quite impressive
throwaway27448 9 hours ago [-]
The utility margin of SOTA models is greatly overstated. You have to pick seriously niche puzzles to make the money worth spending.
Anyway, this is also very useful for humans to use, so it's mostly a lovely coincidence this level of safety arrived with useful chatbots.
saagarjha 11 hours ago [-]
I honestly would be surprised if someone used AI in any other way to achieve this
jdw64 10 hours ago [-]
Same here.
anitil 13 hours ago [-]
Do we have a sense for how many of the programs that work [0] are now detected as having asm violations?
Zero, since I made those programs work back when all inline asm was an error.
So currently most of those still have the hacks to go down the no-inlineasm path when building with Fil-C
For the few where I reinstated the inline assembly, there were no bugs found.
It would be a good experiment to try to reinstate the inlineasm paths in all of the programs that had them. I suspect there’s a low chance of finding a bug if it’s in inline assembly that’s on the critical path.
anitil 12 hours ago [-]
Interesting, I was wondering where catching these errors would fall between 'silently wrong on certain inputs' to 'how did this ever work!?'
IAmLiterallyAB 14 hours ago [-]
I wonder if an adversarial user could bypass the checks and achieve memory corruption / code execution. Maybe not a practical attack in most situations but a fun exercise.
> This includes things like asm volatile("" : : : "memory"), which is an old-school way of saying atomic_signal_fence(memory_order_seq_cst).
Not quite. AIUI, the first is just a barrier for the compiler, while the second is also a CPU memory barrier. Godbolt seems to confirm that.
If you find a way to bypass my checks, file a bug. I tried very hard to break it. My agent loops tried even harder
jancsika 13 hours ago [-]
> My agent loops tried even harder
What happens if you ask to find the strings that will erroneously return True from validateSafeInlineAsm for disallowed asm? :)
pizlonator 13 hours ago [-]
It’s surprisingly hard to define „erroneously”, but that’s not far off from what I did
Example of a bug found most recently was that sahf was allowed without a cc constraint.
Anyway, if you find bugs, file them. Would be fun to see if there’s a case me and my agents missed
IAmLiterallyAB 14 hours ago [-]
Oops, you're right. I was thinking of those as nearly interchangeable but they're actually pretty different.
I might give it a try when I have a chance, I'll let you know if anything comes of it.
IshKebab 7 hours ago [-]
I don't think Fil-C is designed for adversarial users.
torginus 3 hours ago [-]
> we can validate if an inline assembly expression is safe by ... Ensuring that the assembly's effects are fully captured by the constraints. For example, if an assembly instruction modifies a register, then the constraints must capture that register mutation...
I mean, I'm not sure if LLVM parses the assembly (I strongly suspect it does, I remember inline GCC assembly allowed stuff like referencing variables in asm), shouldn't LLVM figure out that the asm modifies things its not supposed to?
If you clobber a register in asm the compiler stores something into, your code certainly won't work right.
leni536 55 minutes ago [-]
One reason to use inline assembly is to use instructions that are basically unknown to the compiler, in which case it can't really tell what is being accessed/modified.
petesergeant 5 hours ago [-]
Unrelated to this specific post, but on the subject of Fil-C and Programs That Work[0].
Let's say I compile curl using Fil-C, and later an exploitable memory bug is found in curl. The implication here is that my fil-c-compiled curl will crash safely, rather than be able to be exploited? And the "cost" to me is that my curl executable will be slower than the standard one?
I'm confused; did you reply on the wrong thread, or am I misunderstanding something? I was merely suggesting it'd be good to mention Boost on your website and how well it works with Fil-C. This suggestion was not specifically regarding anything called assembly; it was just a general comment. I merely mentioned it because you were here.
pizlonator 4 hours ago [-]
Ooh - I thought you meant adding boosts use of assembly to the list of inline asm use cases
I get you now
sureglymop 11 hours ago [-]
While we're at it, does anyone else want something like a good LSP but for assembly?
I mean one that infers as much context as possible and tries to help as much as possible.
This has to be assembler specific of course. For example, I use fasm which has higher level macros. An LSP could suggest struct fields and other stuff.
stefantalpalaru 5 hours ago [-]
[dead]
ozgrakkurt 7 hours ago [-]
You are not thinking straight if you are making out of bounds errors in inline asm.
Inline asm should take 10x or more effort compared to writing the surrounding c++ code and should be tested with protected pages at the edges if possible. It should always have assertions before/after that check invariants too.
Also there are at a lot of cases that this won’t work. One example is implementing strlen using avx512 where you want to align the address down to a multiple of 64 and run until the end of the page, so you can do simd while avoiding segfault.
Another example is just handling loop remainders with masking in avx512.
Also it is pretty naive to think an LLM got this right
Overall it seems like a huge waste of time.
If you are writing inline asm and want to make it better, just get as many LLMs or, even better, humans to review it. LLMs are really good at finding mistakes in inline asm, with a high false positive rate though, so you have to understand the concept.
For example one bug I had was about not consuming the inputs before writing to the outputs. Compiler can assign the same register to input and outputs unless outputs are marked with & (or something like that). It was super frustrating to debug this until I asked an LLM and it found the problem.
Most stupid thing I ever heard. If a safety violation is known at compile-time, you error at compile-time. You might never catch it in a test, and there you have the panic at the customer. He will be pleased.
I would guess for the use-case of "I have a C project and I want to run it in Fil-C" the ability for this to be a warning + run-time panic is very helpful for quickly getting started. Reminds me of GHC's -fdefer-type-errors.
I agree that I wouldn't want to deploy a program where those panics are reachable*, but it's still handy for local development and/or maybe the developer knows they aren't reachable.
I haven't checked, but I'd guess there's a warning and a -Werror -style flag to opt-in to having a hard error for unsafe assembly?
* Obviously a panic is better than not. But guaranteed safeness is better than either of those.
"Using runtime panics has the nice property that inline assembly in dead code doesn't get in the way of porting software to Fil-C. Also, it's consistent with how Fil-C usually reports errors."
Also, like, this is the initial implementation so I kept it consistent with the rest of the error checking
If someone wants a compile time diagnostic they can submit a patch
Against this background, crashing when inline assembly is determined to be doing something the author isn't sure how to deal with is pretty much par for the course -- it's a way to continue to claim that you can port over your old buggy C applications unchanged. You aren't supposed to actually use it.
What a bitter way to analyze a technology.
> under some circumstances Fil-C considers it legal to read a value from a totally different, possibly-inaccessible pointer when dereferencing an unrelated pointer
Fil-C does not consider such a thing to be legal.
You can only access capabilities that you legitimately loaded from the heap, from other capabilities you legitimately loaded, and so on. So, if you access a pointer, it's because you had access to the capability.
> You aren't supposed to actually use it.
Wat
There is no “instead” here. A C implementation defining behavior for certain UB still falls under what a C implementation is allowed to do for UB.
I don’t know if Fil-C is a fully conforming C implementation, but it could well be. In what way is it nonconforming that would warrant describing it as implementing “a related language” and not C?
3a. rdpru (similar issues to cpuid) or rdpmc perhaps surrounded with lfence or cpuid inside the same assembly chunk
For obvious reasons, this is somewhat niche and may not even make it into production code, but it’s also important when you do need it. It’s also memory safe. I guess in such cases you’d use fast C rather than Fil-C though.
4a. rseq
Probably even less feasible than atomics TBH, as such blocks will usually also contain control flow (at least that implied by to the nature of rseqs).
> Before the advent of AI, writing a parser for x86_64 assembly would have been such an annoying task that I might have never gotten around to implementing support for memory safe inline assembly [...].
It is annoying, but even before the advent of AI that didn’t stop the developers of TCC for instance.
With that said, given Fil-C is Clang/LLVM-based, shouldn’t an assembly parser, at least, be already available somewhere? I was under the impression that Clang (unlike GCC) actually parsed asm blocks.
[1] https://github.com/lifthrasiir/wah/
We recently (finally) got __attribute__((musttail)) in GCC[1], I’ve just tried it between functions with mismatched __attribute__((target))s and it does work, so theoretically you could code your interpreter that way. But it seems like you’re still bound to keep loading and storing vector state from and to memory and VZEROUPPERing your registers after each bytecode, and that doesn’t sound like a particularly good time.
[1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119616
Edit to add: If I'm understanding this correctly we should be able to run this against projects and detect asm violations, I feel like this would be very valuable to be able to feed these back to maintainers
1.A developer identified the necessity of inline assembly.
2.Defined the safety boundaries for 'memory-safe' inline assembly.
3.Established strict policies for memory access.
4.Curated an allowlist of permissible instructions.
5.Set rigorous test criteria and 'done' conditions.
In short, with the overall guardrails in place, a sub agent loop was run, and this level of code was produced. This raises a number of interesting points about how we should use AI. I haven't looked at all the code, but the idea of passing assembly through safe zones without memory access, and using that as a foundation to achieve this level of implementation through AI, is quite impressive
Anyway, this is also very useful for humans to use, so it's mostly a lovely coincidence this level of safety arrived with useful chatbots.
[0] https://fil-c.org/programs_that_work
So currently most of those still have the hacks to go down the no-inlineasm path when building with Fil-C
For the few where I reinstated the inline assembly, there were no bugs found.
It would be a good experiment to try to reinstate the inlineasm paths in all of the programs that had them. I suspect there’s a low chance of finding a bug if it’s in inline assembly that’s on the critical path.
> This includes things like asm volatile("" : : : "memory"), which is an old-school way of saying atomic_signal_fence(memory_order_seq_cst).
Not quite. AIUI, the first is just a barrier for the compiler, while the second is also a CPU memory barrier. Godbolt seems to confirm that.
https://godbolt.org/z/a844zKej8
The quote uses atomic_signal_fence.
If you find a way to bypass my checks, file a bug. I tried very hard to break it. My agent loops tried even harder
What happens if you ask to find the strings that will erroneously return True from validateSafeInlineAsm for disallowed asm? :)
Example of a bug found most recently was that sahf was allowed without a cc constraint.
Anyway, if you find bugs, file them. Would be fun to see if there’s a case me and my agents missed
I might give it a try when I have a chance, I'll let you know if anything comes of it.
I mean, I'm not sure if LLVM parses the assembly (I strongly suspect it does, I remember inline GCC assembly allowed stuff like referencing variables in asm), shouldn't LLVM figure out that the asm modifies things its not supposed to?
If you clobber a register in asm the compiler stores something into, your code certainly won't work right.
Let's say I compile curl using Fil-C, and later an exploitable memory bug is found in curl. The implication here is that my fil-c-compiled curl will crash safely, rather than be able to be exploited? And the "cost" to me is that my curl executable will be slower than the standard one?
0: https://fil-c.org/programs_that_work
There was some debugging thing where it embeds debug info using module level assembly that you have to disable.
[1] https://fil-c.org/programs_that_work
It’s module assembly
They’re different
I get you now
I mean one that infers as much context as possible and tries to help as much as possible.
This has to be assembler specific of course. For example, I use fasm which has higher level macros. An LSP could suggest struct fields and other stuff.
Inline asm should take 10x or more effort compared to writing the surrounding c++ code and should be tested with protected pages at the edges if possible. It should always have assertions before/after that check invariants too.
Also there are at a lot of cases that this won’t work. One example is implementing strlen using avx512 where you want to align the address down to a multiple of 64 and run until the end of the page, so you can do simd while avoiding segfault.
Another example is just handling loop remainders with masking in avx512.
Also it is pretty naive to think an LLM got this right
Overall it seems like a huge waste of time.
If you are writing inline asm and want to make it better, just get as many LLMs or, even better, humans to review it. LLMs are really good at finding mistakes in inline asm, with a high false positive rate though, so you have to understand the concept.
For example one bug I had was about not consuming the inputs before writing to the outputs. Compiler can assign the same register to input and outputs unless outputs are marked with & (or something like that). It was super frustrating to debug this until I asked an LLM and it found the problem.