Skip to content

Inline Assembly. #1041

Open
Open

Description

@lachlansneff

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.

Activity

  1. 71 commented on Sep 8, 2018

    @71

    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).

  2. lachlansneff commented on Sep 8, 2018

    @lachlansneff
    ContributorAuthor

    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:

    asm(eax: v1, ebx: v2) -> eax:
      01 c0          ; add eax,eax
      0f af c3       ; imul eax,ebx
    

    or

    asm(v1 -> eax, v2 -> ebx) -> eax:
      01 c0          ; add eax,eax
      0f af c3       ; imul eax,ebx
    
  3. lachlansneff commented on Sep 8, 2018

    @lachlansneff
    ContributorAuthor

    I'd like to see what @sunfishcode has to say on this.

  4. sunfishcode commented on Sep 9, 2018

    @sunfishcode
    Member

    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.

  5. lachlansneff commented on Sep 9, 2018

    @lachlansneff
    ContributorAuthor

    I'd use it for inline stuff on Nebulet. Like talking to io ports, etc.

  6. sunfishcode commented on Sep 11, 2018

    @sunfishcode
    Member

    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/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.

  7. nbp commented on Sep 11, 2018

    @nbp
    Contributor

    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

  8. sunfishcode commented on Sep 12, 2018

    @sunfishcode
    Member

    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:

    struct InlineAsm {
        /// the raw bytes to start with
        data: vec<u8>,
        /// descriptions of all explicit input and output values
        constraints: vec<Constraint>, 
        /// extra registers, "memory", or other machine state which is clobbered
        clobbers: vec<String>,
        /// patches which can change or add bytes
        patches: vec<Patch>,
    }
    
    struct Constraint {
        // TODO: in/out/inout, tied, earlyclobber, hints, alternatives, register/memory/immediate classes, etc.
    }
    
    struct Patch {
        offset: u64, // byte offset in `data` *before* any patches are applied
        contents: PatchDetails,
    }
    
    enum PatchDetails {
        X86RexPrefix {
            w: bool, // REX.w
            inputs: 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.

  9. lygstate commented on Oct 31, 2018

    @lygstate

    Any idea? Inline assembly are essential for constructing OS or bare-metal environment.

  10. sunfishcode commented on Oct 31, 2018

    @sunfishcode
    Member

    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!

  11. skyne98 commented on Nov 15, 2018

    @skyne98

    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.

  12. nbp commented on Nov 15, 2018

    @nbp
    Contributor

    @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.

  13. skyne98 commented on Nov 15, 2018

    @skyne98

    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.

  14. sunfishcode commented on Nov 15, 2018

    @sunfishcode
    Member

    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.

  15. skyne98 commented on Nov 16, 2018

    @skyne98

    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?

  16. 24 remaining items

  17. ufoscout commented on Jun 9, 2020

    @ufoscout

    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.

  18. bjorn3 commented on Jan 26, 2023

    @bjorn3
    Contributor

    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.

  19. stevefan1999-personal commented on Oct 14, 2023

    @stevefan1999-personal

    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.

  20. cfallin commented on Oct 14, 2023

    @cfallin
    Member

    @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.

  21. stevefan1999-personal commented on Oct 14, 2023

    @stevefan1999-personal

    @cfallin Looks like we should better off moving this into a GH discussion...rather than an issue

  22. cfallin commented on Oct 14, 2023

    @cfallin
    Member

    @stevefan1999-personal we don't use the GitHub Discussions feature here; this issue is perfectly fine for now, I think!

  23. stevefan1999-personal commented on Oct 14, 2023

    @stevefan1999-personal

    @stevefan1999-personal we don't use the GitHub Discussions feature here; this issue is perfectly fine for now, I think!

    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.

  24. cfallin commented on Oct 14, 2023

    @cfallin
    Member

    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!

  25. bjorn3 commented on Oct 14, 2023

    @bjorn3
    Contributor

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions