You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Since cranelift is soon to be a backend for rust, it will need to support inline assembly. There is no good way to solve this right now, since rust uses the llvm inline asm syntax right now. I'm making this issue so we can think about this in the long-term.
I just had an idea that's very simple, but also very "raw," but I suppose it would be pretty good for the time being.
What if we simply allowed inline bytes, with the added ability to interpolate Cranelift values?
For example:
01 C0 ; add eax, eax
0F AF $v1 ; imul eax, v1
C3 ; ret
Edit: Obviously there is the problem of Cranelift values being encoded either as registers or stack variables for instance, but maybe some annotations could be added for a value to be in a register, like $(v1:reg) or $(ebb2:rel8).
I'd be all for this for the time being actually, with a small modification.
Treat a block of code as an ebb essentially. Say what inputs it gets and what they should be stored into (registers, stack offsets, etc). The actual code block would be completely opaque. This would prevent cranelift from needing a disassembler.
Interesting idea. My first question is, how do you envision using this?
Looking at the idea itself, a factor to take into consideration is that machine encodings are really complex. A register doesn't typically end up being encoded in a byte; it's usually smaller than a byte, its position in its containing bytes will depend on which operand of the instruction it's for, and sometimes it requires other changes elsewhere in the instruction. Some examples:
$ cat t.s
movq (%rcx), %rcx # The encoding of %rcx depends on where it appears in the instruction!
movq (%rsi), %rcx # These use different REX prefix bits than the instructions below!
movq (%r11), %rcx
movq (%r12), %rcx # This needs a extra byte!
movq (%r13), %rcx # This needs an extra byte in a different way!
movq (%r14), %rcx
$ cc -c t.s
$ objdump -d t.o
...
I think we could still make this system work, if we defined a sufficiently elaborate manifest that could describe all the edits that one would need to make once one knows what registers and stack slots everything will be in. That'd be fairly elaborate, but it might still be simpler than fully parsing instructions from text.
I missed this above: in @lachlansneff's variant above, the IR specifies all the registers, so it wouldn't actually require Cranelift to fill in register values. It'd literally be a block of bytes that simply requires certain values in certain fixed registers at input and output. And maybe a list of clobbers. That seems like it wouldn't be too hard to implement. That said, it might actually be too simple, in the sense that it'd work for very simple things, but would be difficult to evolve into something that does more.
If you're willing to specify all the register inputs and outputs yourself, it wouldn't be that much different to just put the code you want in a .s file and then call it, with a specialized calling convention if you want. Support for custom calling conventions is something we do have other uses for, so we wouldn't mind improving that.
Or, for in/out or similar instructions on x86, would it be enough if we exposed whatever instructions you need as instructions in Cranelift, so that you could use them, similar to compiler intrinsics? This would be easy to implement, and much easier for anyone writing an OS to use.
For your information, there is a rustc issue to stabilize how to do inline assembly. In particular, I suggested on this other thread a similar idea as @6a and @sunfishcode suggested, i-e. to have a set of bytes with constraints.
This idea got some push back on the basis that some people want to do inline assembly with a register allocation handled by the compiler, but I honestly do not see how this can be done without standardizing some form of assembly code.
In case anyone wants to see where the idea above might go, here's a slightly more complete sketch. Here's a hypothetical IR structure for this form of inline asm:
structInlineAsm{/// the raw bytes to start withdata:vec<u8>,/// descriptions of all explicit input and output valuesconstraints:vec<Constraint>,/// extra registers, "memory", or other machine state which is clobberedclobbers:vec<String>,/// patches which can change or add bytespatches:vec<Patch>,}structConstraint{// TODO: in/out/inout, tied, earlyclobber, hints, alternatives, register/memory/immediate classes, etc.}structPatch{offset:u64,// byte offset in `data` *before* any patches are appliedcontents:PatchDetails,}enumPatchDetails{X86RexPrefix{w:bool,// REX.winputs:vec<u64>,// indices in `constraints` for register operands which determine if a REX prefix is needed and if so what bits it should have.}X86ModRMRegField{input: u64,// index in `constraints` for the register to encode}X86ModRMRMField{input: u64,// similar}P2Align{// Insert fill bytes to align the following code to a 1<<p2align boundary.p2align: u8,// The byte value to insert.fill: u8
}// TODO: lots more stuff here}
I think this would deliver most of what LLVM/GCC-style inline asm do, assuming we filled it out. And it would eliminate the need for the backend to parse assembly text. That said, it's still fairly complex. And of course, no existing assemblers are built to work this way, including LLVM's assembler, so it would be a bunch of work to implement even in LLVM.
I'm not aware of any easy answers. Inline asm is a huge, complex, and nebulous set of features.
From a broader perspective, there's a form of an XY problem. If you ask for "inline asm", it may be years before we can deliver it. We want to help, but you're asking for a lot, and we don't know of any way to do it faster while still achieving our other goals. If you instead were to ask for access to certain machine instructions, or control registers, or ways to guide the register allocator around key pieces of code, or other specific things, plausible solutions sometimes could take just a few days to implement. With no exaggeration, more specific features can often be a thousand times easier to implement, and typically end up more robust, with more well defined interfaces, and better-understood implementations.
We may still implement inline asm eventually. People interested in seeing it happen are encouraged to get involved and help out!
Hey, @sunfishcode, as far as I understand we cannot just allow using common register names like eax, ebx and stuff, right? It will require compiling the code with some of the already existing assembly compilers.
@skyne98 , you can and you should because some instructions require special registers. However, many uses of inline assembly are meant to provide an assembly template with which a register allocator can work with. For example, making a SIMD library which is using inline assembly does not want to hard-code a specific register allocation, as multiple consecutive uses of this inline assembly would cause too much register congestion that would penalize the performance of SIMD.
So, what I mean is that the template you provide does not map directly to some binary instruction, it depends on things -- therefore you cannot just parse them, convert to binary and put into the "executable", right?
Otherwise, what is the complexity @sunfishcode was talking about? SimpleJIT is already "jitting" out some binary code.
That's right. In general, the template cannot be parsed until the substitutions are made. And it may contain more than just instructions; it may use arbitrary assembler directives like .align or .section
or other things which have effects other than just contributing bytes to add to the output. And they may make use of arbitrary symbolic expressions that need to be evaluated and/or patched in later. They may rely on numerous historical assembler parsing quirks. They may use complex and interdependent operand constraints, and they may require the compiler's register allocator to find optimal solutions to those constraints. They may have ambiguous constraints like "memory" which ostensibly clobbers all memory but in practice compilers treat as not literally all memory. And so on.
Also, @sunfishcode, what do you think about this project. To me seems like it can be at least partially helpful, however I have to admit I haven't read the source yet.
Also, I don't think we need all the features you mentioned above. What I was trying to ask before is that if doing substitutions is possible and not too hard to do, then introducing only adding basic instructions like stack pushes, pops and 'mov'-s (just add 20 most used instructions in kernels as well as some important ones, we may even try to analyze code for that) will help A LOT and make writing bare metal code at least possible to do.
Rustc_codegen_cranelift has solved this by wrapping inline assembly with a prologue and epilogue and the passing it to an external assembler. It can then be called as regular function from the Cranelift side.
I'm also very much in need for this. I'm using Cranelift as a backend for my C compiler project. The lack of an assembler is quite unfortunate, which means I have to consider hacking a bit to either bypass Cranelift on assembly/raw/naked functions, or I have to write my own codegen using dynasm.
@stevefan1999-personal you or anyone else interested is welcome to contribute! As outlined by sunfishcode above, this is a big design question, so any effort should probably start with an RFC; and then the implementation effort would probably be 3-6 months of fulltime work, I would estimate. Currently none of the core developers have the time/resources to build this, so it would likely have to come from a motivated contributor.
Basically, LLVM is also treating the inline assembly as string, replace the parameters, captures used registers and feed it to the register allocator, and then insert the output assembly at the specific point. I'm not sure if I missed something.
Basically, LLVM is also treating the inline assembly as string, replace the parameters, captures used registers and feed it to the register allocator, and then insert the output assembly at the specific point. I'm not sure if I missed something.
Indeed, it sounds kind of simple when described at a high level like that. I'd recommend reading sunfishcode's comment above (and really, this whole thread): there are a huge number of complexities here that need to be fully appreciated and handled in any robust solution.
As one example, we currently generate machine code directly, rather than using any existing assembler at the backend. And our machine-code representation is close but not exactly 1-to-1 with instructions, and we don't have representations for instructions we don't use. So at the very least, such a project would require (i) writing a new textual assembler that uses our emission backend, or adopting an existing assembler library in Rust to work with our backend and provide the same interface and metadata to the register allocator, and (ii) extending our instruction representation to support all known CPU instructions. That's an enormous effort.
To state it more explicitly: the input that is most needed here is not ideas or brainstorming or problem-solving, nor confirmation that this is a useful feature; we have plenty of ideas, and we know this is a useful feature that some people want. The input that is needed is human time to actually develop the feature; something like 3-6 months of fulltime engineering. Cranelift is developed by folks who use it for various applications, and we're all quite busy, and don't have the free time for that. Anyone who does, and is sufficiently motivated to need this feature, is welcome to help out!
Activity
71 commentedon Sep 8, 2018
I just had an idea that's very simple, but also very "raw," but I suppose it would be pretty good for the time being.
What if we simply allowed inline bytes, with the added ability to interpolate Cranelift values?
For example:
Edit: Obviously there is the problem of Cranelift values being encoded either as registers or stack variables for instance, but maybe some annotations could be added for a value to be in a register, like
$(v1:reg)or$(ebb2:rel8).lachlansneff commentedon Sep 8, 2018
I'd be all for this for the time being actually, with a small modification.
Treat a block of code as an ebb essentially. Say what inputs it gets and what they should be stored into (registers, stack offsets, etc). The actual code block would be completely opaque. This would prevent cranelift from needing a disassembler.
For example:
or
lachlansneff commentedon Sep 8, 2018
I'd like to see what @sunfishcode has to say on this.
sunfishcode commentedon Sep 9, 2018
Interesting idea. My first question is, how do you envision using this?
Looking at the idea itself, a factor to take into consideration is that machine encodings are really complex. A register doesn't typically end up being encoded in a byte; it's usually smaller than a byte, its position in its containing bytes will depend on which operand of the instruction it's for, and sometimes it requires other changes elsewhere in the instruction. Some examples:
I think we could still make this system work, if we defined a sufficiently elaborate manifest that could describe all the edits that one would need to make once one knows what registers and stack slots everything will be in. That'd be fairly elaborate, but it might still be simpler than fully parsing instructions from text.
lachlansneff commentedon Sep 9, 2018
I'd use it for inline stuff on Nebulet. Like talking to io ports, etc.
sunfishcode commentedon Sep 11, 2018
A few more thoughts here:
I missed this above: in @lachlansneff's variant above, the IR specifies all the registers, so it wouldn't actually require Cranelift to fill in register values. It'd literally be a block of bytes that simply requires certain values in certain fixed registers at input and output. And maybe a list of clobbers. That seems like it wouldn't be too hard to implement. That said, it might actually be too simple, in the sense that it'd work for very simple things, but would be difficult to evolve into something that does more.
If you're willing to specify all the register inputs and outputs yourself, it wouldn't be that much different to just put the code you want in a .s file and then call it, with a specialized calling convention if you want. Support for custom calling conventions is something we do have other uses for, so we wouldn't mind improving that.
Or, for
in/outor similar instructions on x86, would it be enough if we exposed whatever instructions you need as instructions in Cranelift, so that you could use them, similar to compiler intrinsics? This would be easy to implement, and much easier for anyone writing an OS to use.nbp commentedon Sep 11, 2018
For your information, there is a rustc issue to stabilize how to do inline assembly. In particular, I suggested on this other thread a similar idea as @6a and @sunfishcode suggested, i-e. to have a set of bytes with constraints.
This idea got some push back on the basis that some people want to do inline assembly with a register allocation handled by the compiler, but I honestly do not see how this can be done without standardizing some form of assembly code.
rust-lang/rust#29722
sunfishcode commentedon Sep 12, 2018
In case anyone wants to see where the idea above might go, here's a slightly more complete sketch. Here's a hypothetical IR structure for this form of inline asm:
I think this would deliver most of what LLVM/GCC-style inline asm do, assuming we filled it out. And it would eliminate the need for the backend to parse assembly text. That said, it's still fairly complex. And of course, no existing assemblers are built to work this way, including LLVM's assembler, so it would be a bunch of work to implement even in LLVM.
lygstate commentedon Oct 31, 2018
Any idea? Inline assembly are essential for constructing OS or bare-metal environment.
sunfishcode commentedon Oct 31, 2018
I'm not aware of any easy answers. Inline asm is a huge, complex, and nebulous set of features.
From a broader perspective, there's a form of an XY problem. If you ask for "inline asm", it may be years before we can deliver it. We want to help, but you're asking for a lot, and we don't know of any way to do it faster while still achieving our other goals. If you instead were to ask for access to certain machine instructions, or control registers, or ways to guide the register allocator around key pieces of code, or other specific things, plausible solutions sometimes could take just a few days to implement. With no exaggeration, more specific features can often be a thousand times easier to implement, and typically end up more robust, with more well defined interfaces, and better-understood implementations.
We may still implement inline asm eventually. People interested in seeing it happen are encouraged to get involved and help out!
skyne98 commentedon Nov 15, 2018
Hey, @sunfishcode, as far as I understand we cannot just allow using common register names like eax, ebx and stuff, right? It will require compiling the code with some of the already existing assembly compilers.
nbp commentedon Nov 15, 2018
@skyne98 , you can and you should because some instructions require special registers. However, many uses of inline assembly are meant to provide an assembly template with which a register allocator can work with. For example, making a SIMD library which is using inline assembly does not want to hard-code a specific register allocation, as multiple consecutive uses of this inline assembly would cause too much register congestion that would penalize the performance of SIMD.
skyne98 commentedon Nov 15, 2018
So, what I mean is that the template you provide does not map directly to some binary instruction, it depends on things -- therefore you cannot just parse them, convert to binary and put into the "executable", right?
Otherwise, what is the complexity @sunfishcode was talking about? SimpleJIT is already "jitting" out some binary code.
sunfishcode commentedon Nov 15, 2018
That's right. In general, the template cannot be parsed until the substitutions are made. And it may contain more than just instructions; it may use arbitrary assembler directives like .align or .section
or other things which have effects other than just contributing bytes to add to the output. And they may make use of arbitrary symbolic expressions that need to be evaluated and/or patched in later. They may rely on numerous historical assembler parsing quirks. They may use complex and interdependent operand constraints, and they may require the compiler's register allocator to find optimal solutions to those constraints. They may have ambiguous constraints like "memory" which ostensibly clobbers all memory but in practice compilers treat as not literally all memory. And so on.
skyne98 commentedon Nov 16, 2018
Also, @sunfishcode, what do you think about this project. To me seems like it can be at least partially helpful, however I have to admit I haven't read the source yet.
Also, I don't think we need all the features you mentioned above. What I was trying to ask before is that if doing substitutions is possible and not too hard to do, then introducing only adding basic instructions like stack pushes, pops and 'mov'-s (just add 20 most used instructions in kernels as well as some important ones, we may even try to analyze code for that) will help A LOT and make writing bare metal code at least possible to do.
What do you think?
24 remaining items
Add more documentation for entities, fixes bytecodealliance#1038 (byt…
ufoscout commentedon Jun 9, 2020
I guess this one strictly relates to rust-lang/rust#69171 , the good news is that the new
asm!syntax is not linked to LLVM any more.bjorn3 commentedon Jan 26, 2023
Rustc_codegen_cranelift has solved this by wrapping inline assembly with a prologue and epilogue and the passing it to an external assembler. It can then be called as regular function from the Cranelift side.
stevefan1999-personal commentedon Oct 14, 2023
I'm also very much in need for this. I'm using Cranelift as a backend for my C compiler project. The lack of an assembler is quite unfortunate, which means I have to consider hacking a bit to either bypass Cranelift on assembly/raw/naked functions, or I have to write my own codegen using dynasm.
cfallin commentedon Oct 14, 2023
@stevefan1999-personal you or anyone else interested is welcome to contribute! As outlined by sunfishcode above, this is a big design question, so any effort should probably start with an RFC; and then the implementation effort would probably be 3-6 months of fulltime work, I would estimate. Currently none of the core developers have the time/resources to build this, so it would likely have to come from a motivated contributor.
stevefan1999-personal commentedon Oct 14, 2023
@cfallin Looks like we should better off moving this into a GH discussion...rather than an issue
cfallin commentedon Oct 14, 2023
@stevefan1999-personal we don't use the GitHub Discussions feature here; this issue is perfectly fine for now, I think!
stevefan1999-personal commentedon Oct 14, 2023
Sure. I just read how Cland and LLVM handles inline assembly and I find it maybe useful towards the end goal:
https://youtu.be/MeB7Dp3G2UE?si=2boJuSqM-JLH2f7o&t=510
Basically, LLVM is also treating the inline assembly as string, replace the parameters, captures used registers and feed it to the register allocator, and then insert the output assembly at the specific point. I'm not sure if I missed something.
cfallin commentedon Oct 14, 2023
Indeed, it sounds kind of simple when described at a high level like that. I'd recommend reading sunfishcode's comment above (and really, this whole thread): there are a huge number of complexities here that need to be fully appreciated and handled in any robust solution.
As one example, we currently generate machine code directly, rather than using any existing assembler at the backend. And our machine-code representation is close but not exactly 1-to-1 with instructions, and we don't have representations for instructions we don't use. So at the very least, such a project would require (i) writing a new textual assembler that uses our emission backend, or adopting an existing assembler library in Rust to work with our backend and provide the same interface and metadata to the register allocator, and (ii) extending our instruction representation to support all known CPU instructions. That's an enormous effort.
To state it more explicitly: the input that is most needed here is not ideas or brainstorming or problem-solving, nor confirmation that this is a useful feature; we have plenty of ideas, and we know this is a useful feature that some people want. The input that is needed is human time to actually develop the feature; something like 3-6 months of fulltime engineering. Cranelift is developed by folks who use it for various applications, and we're all quite busy, and don't have the free time for that. Anyone who does, and is sufficiently motivated to need this feature, is welcome to help out!
bjorn3 commentedon Oct 14, 2023
In rustc_codegen_cranelift I have a bunch of code which wraps inline asm in a function which uses the SystemV calling convention and then pass this whole asm block to an external assembler. This may or may not work for your use case: https://github.com/bjorn3/rustc_codegen_cranelift/blob/master/src/inline_asm.rs