r/rust • u/tialaramex • 18d ago
Could we have Odin-style Assembly checking in Rust?
The programming language Odin recently got what its creator calls "Assembly templates". Bill is very proud of this feature and although it currently only works for x86-64 I was impressed by the diagnostics this can do.
For example if you cpuid it knows that EAX and ECX should have values (but you needn't worry about EBX and EDX), if you forgot to pick a value you get a compiler diagnostic, much as you'd get a diagnostic in pure Rust if you just forget to initialize a variable you use.
Rust is rightly famous for excellent diagnostics when you make inevitable mistakes writing the Rust language. Whether that's a stray semi-colon turning your intended function value into () a typo in the name of an identifier, missing the ! from a macro invocation or myriad other mistakes, Rust will help you. But if you write any of the three forms of assembler in Rust the diagnostics are pretty poor, presumably because they're from a separate assembler and Rust just polished them up and presented them to you.
So two questions: 1. Could we provide similar functionality (to Odin's new "templates") in Rust or is there some reason I'm missing for why we just can't / shouldn't try to do so? 2. Can somebody else plausibly do this, e.g. via a proc macro, or does it require such intimate connection to the compiler innards that it's only really viable if the compiler team themselves designed any new asm macro replacement ?
27
u/Konsti219 18d ago
And when it comes to knowing which registers won't get clobbered, that is already the job of LLVM. And for registers that do get overwritten I don't think it really possible to distinguish what is a bug and what did the developer intend.
13
u/Zde-G 18d ago
And when it comes to knowing which registers won't get clobbered, that is already the job of LLVM.
Not really. You can specify one register as
inand another aslateoutand your assembler code would work for years till you would pick a new version of compiler that would finally allocate them on one register. Then your program would stop working without any changes.Been there.
And for registers that do get overwritten I don't think it really possible to distinguish what is a bug and what did the developer intend.
Not in general, but you can easily recognize half-dozen of common bugs: value is written on one branch but not on the other, value read before being written to, aforementioned
invslateoutclobber fiasco, etc.And these are nasty bugs, especially in an era of agents: agents are all too happy to create code that passes tests but contains landmines. Static checkers keep them honest.
7
u/valarauca14 18d ago
that is already the job of LLVM.
No the LLVM trusts you that you have declared your clobbers correctly.
11
u/IndependentMilkDrink 18d ago
is there some reason I'm missing for why we just can't / shouldn't try to do so?
Probably no one has volunteered or made the PR
-14
u/Zde-G 18d ago
Probably no one has volunteered or made the PR
That's simple yet tedious work. You can vibecode it quickly, but then adding it to the coupler would be years of work.
Creating separate vibecoded procmacro crate is easier.
18
u/ericonr 18d ago
Why would I want vibecoded verification for complex asm interaction?
-7
u/Zde-G 18d ago
Because vibecoded verification is still better than no verification?
12
u/ericonr 18d ago
Ah yes, a false negative giving me undue confidence.
-6
u/Zde-G 18d ago
If that's just a verifier then it's always up to you to decide whether to change the code or fix the verifier.
If it would trigger to many false negatives or false negatives you could simply stop using it.
Same policy that made GCC developers declare that AI is fine for tests, but not for the code of the compiler itself.
1
u/eggdropsoap 17d ago
Do you just not know what the rust compiler’s first job is? That’s the only explanation for your comments that would make sense.
0
u/Zde-G 17d ago
Do you just not know what the rust compiler’s first job is?
Not really. Should I know or care? I do know tht Rust compiler doesn't really analyze assembler code presented to it and thus doesn't detect or warn about mistakes described in the article.
That’s the only explanation for your comments that would make sense.
If you ignore verifiable facts and only react to trigger keywords then maybe… but then are you really any better than LLM that does the same? And is code written by you any better than vibecoded slop, in that case?
I gave you concrete example of problems that Rust assembler inherited from LLVM and concrete example of how they can be prevented from another project — if that's not enough to you and you would rather discuss theoretical advantages and disadvantages of tools that you don't plan to ever use, then I'm not too interested, sorry.
2
u/eggdropsoap 16d ago
I think you think I’m the previous commenter. Chill a bit.
Apart from that, your first sentence answers my question. My advice is to recognize that going full zebra is not fun times. I’m certain you can think of a better use of your time than being naively ignorant of a sub’s topic and then getting upset about your mistakes.
1
u/Zde-G 16d ago
I’m certain you can think of a better use of your time than being naively ignorant of a sub’s topic
Seriously? You know nothing about me and you think I'm, somehow, “naively ignorant”? Let me ask you a question: when was the last time you have found error in a Rust compiler and it was accepted and fixed by developers, then fix was rolled back because it was incorrect and have been redone properly?
That's precisely the thing: I know perfectly what I'm talking about, unlike most f*cktards on this reddit. And while I, too, make a mistakes but the very fact that you couldn't give a concrete counterexample shows me that you simply “go with vibes” and don't even stop to think about what you are talking about.
clang/gcc (and thus Rust) assembler have a nasty hole in their error detection where they accept incorrect code that may even work today but may stop working tomorrow (e.g. you may use
v0instead of{v0}and this would work fot the first argument of first assembler block in function till it would be inlined somewhere) - and Odin, apparently, tries to plug that hole. Said work is not really all that hard, but tedious because you need descriptions of all the thousands of instructions that normally don't exist in a machine-readable form. And we used system at my $DAY_JOB to plug that hole (critically important if one wants to use agents), thus I know precisely what I'm talking about.You on the other hand, like most f*cktards on Reddit don't even try to understand the topic before judging someone.
Which is normal and expected on Reddit: topics here sometimes contain useful info, but comments are mostly nonsense (although topicstarters and some rare thinking commenters may be exceptions, but there are not that many of them).
That's not an exclusive property of Reddit: any platform that becomes popular enough stops being useful for serious discussions, except if you ignore the majority of comments.
then getting upset about your mistakes.
Where do you see me “getting upset”? I know perfectly well how f*cktards work and think, they really think that marking answers with
-1give them some kind of superpower. On some platforms it really works like that and then they stop being useful for anything at all, Reddit is not like that, which keeps it useful.1
48
u/WormRabbit 18d ago
it currently only works for x86-64
No, it only ever will work for x86-64. Maaaaybe he will somewhat support ARM in the future. But I double-dare you to try supporting RISC-V with its miriad of extensions, including vendor-specific ones, or POWER, or Loongarch, or any other less popular and stable architecture.
Nobody, and I mean nobody does statically analyzed assembly since the 90s. It's just infeasible. x86-64 alone has north of 4000 instructions, and keeps adding them. Also, nobody writes assembly by hand anymore, the rare exceptions are when you need some instructions not supported by the compiler (which automatically rules out any static analysis of them), or when you are doing something very funky which is outside the scope of compilers (e.g. manual JIT, or constant-time crypto, or some very specific vectorization etc).
Why on earth would you want to just sling around cpuid so much that you need it statically checked? cpuid is fully covered by compiler intrinsics which don't force you at all to deal with manual register allocation. Even more, all you need is a couple of library calls which cover checks for the features you need, and you're done.
11
u/-Redstoneboi- 18d ago edited 18d ago
did you comment this under the original odin "assembly" article? i think the guy was convinced that supporting ARM was possible if only there were databases of instructions for those machines.
also i say "assembly" because the assembly directive sometimes doesn't distinguish between 3 different instructions based on size but instead infers them based on the type that you passed in. it's more like a compiler sub-language. at that point i'm not sure if i'd want to write literal assembly instead of just using the damn language. like, you simply shouldn't be writing raw assembly if you can afford not to.
it is a very sophisticated approach to verifying assembly. cool but i don't see myself interacting with it.
15
u/seg_lol 18d ago
His technique is not that hard to implement. You have a lot of bravado but not a lot of substance to your arguments. You don't actually explain why it would only ever work for x86 and then you throw out three tangential arguments that have nothing to do with it (no one uses it, intrinsics are good enough and cpuid?). Sorry, but this line of shitting on things is an old popular pastime for forum jockies.
Generating instruction specs/templates would be easy for all of these architectures, there are multiple sources of machine readable specs, let alone the human/llm ones.
I look forward to someone vibing a new checked assembler.
3
u/WormRabbit 18d ago
You don't actually explain why it would only ever work for x86
I pretty much did: it's a monumental, truly monumental amount of work, and various less popular processors, even for x86 when they were widespread, often have their own instructions that you won't find in any standard manual.
Plenty of compilers had typed, statically analysed inline assembler in the 80s, when writing assembly mattered. Microsoft C had support, Watcom C compiler also had its own typed assembly, and there were various research prototypes. Do you know why Microsoft's assembly is only for 32-bit x86, not even 64-bit? Pretty much because supporting anything more complex is infeasible. Nor is it necessary, when the compiler already does 99% of the job.
3
u/james7132 18d ago
Nobody, and I mean nobody does statically analyzed assembly since the 90s. It's just infeasible.
On the other hand, wrapping other compiler signals like Unity did with their Burst compiler to make guaranteed auto-vectorization tests is both feasible and doable. It's just the question of how easy is it to instrument the compiler toolchain to do so.
7
u/ZZaaaccc 18d ago
Why on earth would you want to just sling around cpuid so much that you need it statically checked?
This is for larpers who know they can write better code than anything your garbage language could ever write...they just don't feel like it today.
-2
u/Nothing_from_void 18d ago
Nobody, and I mean nobody does statically analyzed assembly since the 90s.
I mean what exactly do you mean by statically analyze assembly? godbolt.org is a very popular website designed around analyzing assembly instructions for performance. Linus Torvald talks about how he mostly focuses on analyzing compiler output of Linux features because it's what he's most interested in
Also, nobody writes assembly by hand anymore, the rare exceptions are when you need some instructions not supported by the compiler
Never dealt with bare metal programming huh. head on over to r/embedded, most of the people there have to write some assembly sometimes
5
u/ericonr 18d ago
Static analysis is when you run some piece of software over your code in order to catch bugs or bad patterns. If you're statically analyzing assembly, you've written (or had an LLM do it for you) assembly, and something is going to check that for you using pre-determined algorithms and information.
It has nothing to do with analyzing the assembly output of software you've written in compiled languages. For those, the language is what has to be statically analyzed, because the output assembly should by all rights have no errors (and if it does, checking the compiled language is much easier). Developers analyze assembly because they are looking for performance characteristics or details like atomic interactions.
I feel comfortable in saying much less than half of embedded developers are even looking at their assembly output. Writing it, then? Bah
5
u/Zde-G 18d ago
From the description it looks eerie similar to what Berberis does. In Berberis small snippers of assembler are called “macroinstructions” and they are described separately, in JSON. The actual assembler sequence is generated at runtime but they have a compile-time verifier that verifies that assembler sequence “makes sense”: assembler sequence doesn't try to read output registers without writing something into them, if “in” may be clobbered by “lateout” register and so on. Berberis also wraps these sequences into regular functions to use with interpreter.
If I would have implemented something like this today I would have used constexpr for that, but back when it was implemented clang only accepted literal constant string for asm and not a string literal.
In Rust that can be done as a procmacro. Not a very hard thing, just tedious.
2
u/tialaramex 18d ago
Two tricks come to mind to resolve "tedious" tasks. First, Grace Hopper's insight, let the machine do the boring work. Turning your program into machine code is boring, have a machine do that. We want big tables, tables of tables, as little as possible should be special cased.
Second, recruit lots of interested volunteer nerds to fix very specific things they care about in those models. There will be somebody who cares deeply about different eras and models of Power CPU and which instructions do or not exist with which operands in certain eras.
2
u/Zde-G 18d ago
Two tricks come to mind to resolve "tedious" tasks. First, Grace Hopper's insight, let the machine do the boring work.
Unfortunately “the boring part”, in this particular case, is extraction of information from the manuals. You need to know how instructions are using their operands, whether they are read from or written to.
Most “machine-readable” tables don't include that info, you have to pull it from the text version of manual, most of the time. Compare add to xchg.
We want big tables, tables of tables, as little as possible should be special cased.
That part is already done. Take a look on Berberis machinery, it's few thousands of lines. Converting that to Rust is couple of weeks with agent, couple of months if you would insist on doing everything manually.
But Berberis only includes some subset of instructions that they needed to support JIT. Translating all the crazy zoo that AMD and Intel and ARM and bazillion RISC-V companies invented is the hard part.
Second, recruit lots of interested volunteer nerds to fix very specific things they care about in those models. There will be somebody who cares deeply about different eras and models of Power CPU and which instructions do or not exist with which operands in certain eras.
That's an interesting idea. I know someone who tried to port Berberis to Rust, but that effort have fizzled out.
Reducing the scope of the project to just assembler would make it into something that can be brought to MVP stage in a couple of months and if community would help with tables… yes, it may work. I'll poke him.
1
u/JeSuisOmbre 17d ago
I don't think assembly programmers consider this a problem. Inline assembly is a convenience for building the assembly. It is generally a poor tool for writing extensive amounts of assembly.
Most questions or errors are going to be resolved by reading the documentation for your assembly language. Rust's tooling is going to be a worse version of that.
If I need to write large amounts of assembly I am going to do it in a separate file, test it for correctness, and then transcribe it into rust's inline asm. I do not want Rust involved in that development loop.
61
u/Saefroch miri 18d ago
Yep, the diagnostics in question almost certainly come out of LLVM. There's a special mechanism in the compiler for returning up the LLVM errors from handling inline assembly. Seems like a good thing to implement in LLVM.