Finkel, Hal J. via llvm-dev
2019-Jun-29 00:39 UTC
[llvm-dev] [cfe-dev] [RFC] ASM Goto With Output Constraints
On 6/28/19 5:35 PM, James Y Knight via llvm-dev wrote: On Fri, Jun 28, 2019 at 5:53 PM Bill Wendling <isanbard at gmail.com<mailto:isanbard at gmail.com>> wrote: On Fri, Jun 28, 2019 at 1:48 PM James Y Knight <jyknight at google.com<mailto:jyknight at google.com>> wrote: On Fri, Jun 28, 2019 at 3:00 PM Bill Wendling <isanbard at gmail.com<mailto:isanbard at gmail.com>> wrote: On Thu, Jun 27, 2019 at 1:44 PM Bill Wendling <isanbard at gmail.com<mailto:isanbard at gmail.com>> wrote: On Thu, Jun 27, 2019 at 1:29 PM James Y Knight <jyknight at google.com<mailto:jyknight at google.com>> wrote: I think this is fine, except that it stops at the point where things actually start to get interesting and tricky. How will you actually handle the flow of values from the callbr into the error blocks? A callbr can specify requirements on where its outputs live. So, what if two callbr, in different branches of code, specify _different_ constraints for the same output, and list the same block as a possible error successor? How can the resulting phi be codegened? This is where I fall back on the statement about how "the programmer knows what they're doing". Perhaps I'm being too cavalier here? My concern, if you want to call it that, is that we don't be too restrictive on the new behavior. For example, the "asm goto" may set a register to an error value (made up on the spot; may not be a common use). But, if there's no real reason to have the value be valid on the abnormal path, then sure we can declare that it's not valid on the abnormal path. I think I should explain my "programmer knows what they're doing" statement a bit better. I'm specifically referring to inline asm here. The more general "callbr" case may still need to be considered (see Reid's reply). When a programmer uses inline asm, they're implicitly telling the compiler that they *do* know what they're doing (I know this is common knowledge, but I wanted to reiterate it.). In particular, either they need to reference an instruction not readily available from the compiler (e.g. "cpuid") or the compiler isn't able to give them the needed performance in a critical section. I'm extending this sentiment to callbr with output constraints. Let's take your example below and write it as "normal" asm statements one on each branch of an if-then-else (please ignore any syntax errors): if: br i1 %cmp, label %true, label %false true: %0 = call { i32, i32 } asm sideeffect "poetry $0, $1", "={r8},={r9}" () br label %end false: %1 = call { i32, i32 } asm sideeffect "poetry2 $0, $1", "={r10},={r11}" () br label %end end: %vals = phi { i32, i32 } [ %0, %true ], [ %1, %false ] How is this handled in codegen? Is it an error or does the back-end handle it? Whatever's done today for "normal" inline asm is what I *think* should be the behavior for the inline asm callbr variant. If this doesn't seem sensible (and I realize that I may be thinking of an "in a perfect world" scenario), then we'll need to come up with a more sensible solution which may be to disallow the values on the error block until we can think of a better way to handle them. This example is no problem, because instructions can be emitted between what's emitted by "call asm" and the end of the block (be it a fallthrough, or a jump instruction. What gets emitted there is a move of the output register to another location -- either a register or to the stack. And therefore at the beginning of the "end" block, "%vals" is always in a consistent location, no matter how you got to that block. But in the callbr case, there is not a location at which those moves can be emitted, after the callbr, before the jump to "error". I see what you mean. Let's say we create a pseudo-instruction (similar to landingpad, et al) that needs to be lowered by the backend in a reasonable manner. The EH stuff has an external process/library that performs the actual unwinding and which sets the values accordingly. We won't have this. What we could do instead is split the edges and insert the copy-to-<where ever> statements there. Exactly -- except that doing that is potentially an invalid transform, because the address is being used as a value, not simply a jump target. The label list is just a list of _possible_ jump targets, changing those won't actually affect anything. You'd instead need to change the blockaddress constant, but in the general case you don't know where that address came from -- (and it may therefore be required that you have the same address for two separate callbr instructions). I guess this kinda touches on some of the same issues as in the other discussion about the handling of the blockaddress in callbr and inlining, etc... I wonder if we could put some validity restrictions on the IR structure, rather than trying to fix things up after the fact by attempting to split blocks. E.g., we could state that it's invalid to have a phi which uses the value defined by a callbr, if it's conditioned on that same block as predecessor. That is: it's valid to use _other_ values defined in the block ending in callbr, because they can be moved prior to the callbr. It's also valid to use the value defined by the callbr in a phi conditioned on some other intermediate block as predecessor, because then any required moves can happen in the intermediate block. I believe such an IR restriction should be sufficient to make it possible to emit valid code from the IR in all cases, but I'm a bit afraid of how badly adding such odd edge-cases might screw up the rest of the compiler and optimizer. I think that your fear is justified. In any case, if we're going to support forming this kind of callbr in Clang, then Clang still needs a place to put the stack stores after the inline asm in order to represent the output constraints - which are specified in terms of source-level variables and those are always in stack locations when Clang is generating IR. I think that we can make all of this work if we say that the output constraints, and thus the outputs of the callbr, dominate only uses on the normal "fallthrough" branch. Then the compiler has a single place to put the stores (and, later, a place to put register copies, etc.). -Hal _______________________________________________ LLVM Developers mailing list llvm-dev at lists.llvm.org<mailto:llvm-dev at lists.llvm.org> https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev -- Hal Finkel Lead, Compiler Technology and Programming Languages Leadership Computing Facility Argonne National Laboratory -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20190629/9065c7f5/attachment-0001.html>
Bill Wendling via llvm-dev
2019-Jun-29 02:20 UTC
[llvm-dev] [cfe-dev] [RFC] ASM Goto With Output Constraints
On Fri, Jun 28, 2019 at 5:39 PM Finkel, Hal J. <hfinkel at anl.gov> wrote:> On 6/28/19 5:35 PM, James Y Knight via llvm-dev wrote: > > On Fri, Jun 28, 2019 at 5:53 PM Bill Wendling <isanbard at gmail.com> wrote: > >> On Fri, Jun 28, 2019 at 1:48 PM James Y Knight <jyknight at google.com> >> wrote: >> >>> On Fri, Jun 28, 2019 at 3:00 PM Bill Wendling <isanbard at gmail.com> >>> wrote: >>> >>>> On Thu, Jun 27, 2019 at 1:44 PM Bill Wendling <isanbard at gmail.com> >>>> wrote: >>>> >>>>> On Thu, Jun 27, 2019 at 1:29 PM James Y Knight <jyknight at google.com> >>>>> wrote: >>>>> >>>>>> I think this is fine, except that it stops at the point where things >>>>>> actually start to get interesting and tricky. >>>>>> >>>>>> How will you actually handle the flow of values from the callbr into >>>>>> the error blocks? A callbr can specify requirements on where its outputs >>>>>> live. So, what if two callbr, in different branches of code, specify >>>>>> _different_ constraints for the same output, and list the same block as a >>>>>> possible error successor? How can the resulting phi be codegened? >>>>>> >>>>>> This is where I fall back on the statement about how "the programmer >>>>> knows what they're doing". Perhaps I'm being too cavalier here? My concern, >>>>> if you want to call it that, is that we don't be too restrictive on the new >>>>> behavior. For example, the "asm goto" may set a register to an error value >>>>> (made up on the spot; may not be a common use). But, if there's no real >>>>> reason to have the value be valid on the abnormal path, then sure we can >>>>> declare that it's not valid on the abnormal path. >>>>> >>>>> I think I should explain my "programmer knows what they're doing" >>>> statement a bit better. I'm specifically referring to inline asm here. The >>>> more general "callbr" case may still need to be considered (see Reid's >>>> reply). >>>> >>>> When a programmer uses inline asm, they're implicitly telling the >>>> compiler that they *do* know what they're doing (I know this is common >>>> knowledge, but I wanted to reiterate it.). In particular, either they need >>>> to reference an instruction not readily available from the compiler (e.g. >>>> "cpuid") or the compiler isn't able to give them the needed performance in >>>> a critical section. I'm extending this sentiment to callbr with output >>>> constraints. Let's take your example below and write it as "normal" asm >>>> statements one on each branch of an if-then-else (please ignore any syntax >>>> errors): >>>> >>>> if: >>>> br i1 %cmp, label %true, label %false >>>> >>>> true: >>>> %0 = call { i32, i32 } asm sideeffect "poetry $0, $1", "={r8},={r9}" >>>> () >>>> br label %end >>>> >>>> false: >>>> %1 = call { i32, i32 } asm sideeffect "poetry2 $0, $1", >>>> "={r10},={r11}" () >>>> br label %end >>>> >>>> end: >>>> %vals = phi { i32, i32 } [ %0, %true ], [ %1, %false ] >>>> >>>> How is this handled in codegen? Is it an error or does the back-end >>>> handle it? Whatever's done today for "normal" inline asm is what I *think* >>>> should be the behavior for the inline asm callbr variant. If this doesn't >>>> seem sensible (and I realize that I may be thinking of an "in a perfect >>>> world" scenario), then we'll need to come up with a more sensible solution >>>> which may be to disallow the values on the error block until we can think >>>> of a better way to handle them. >>>> >>> >>> This example is no problem, because instructions can be emitted between >>> what's emitted by "call asm" and the end of the block (be it a fallthrough, >>> or a jump instruction. What gets emitted there is a move of the output >>> register to another location -- either a register or to the stack. And >>> therefore at the beginning of the "end" block, "%vals" is always in a >>> consistent location, no matter how you got to that block. >>> >>> But in the callbr case, there is not a location at which those moves can >>> be emitted, after the callbr, before the jump to "error". >>> >> >> I see what you mean. Let's say we create a pseudo-instruction (similar to >> landingpad, et al) that needs to be lowered by the backend in a reasonable >> manner. The EH stuff has an external process/library that performs the >> actual unwinding and which sets the values accordingly. We won't have this. >> > > > >> What we could do instead is split the edges and insert the copy-to-<where >> ever> statements there. >> > > Exactly -- except that doing that is potentially an invalid transform, > because the address is being used as a value, not simply a jump target. The > label list is just a list of _possible_ jump targets, changing those won't > actually affect anything. You'd instead need to change the blockaddress > constant, but in the general case you don't know where that address came > from -- (and it may therefore be required that you have the same address > for two separate callbr instructions). > > I guess this kinda touches on some of the same issues as in the other > discussion about the handling of the blockaddress in callbr and inlining, > etc... > > I wonder if we could put some validity restrictions on the IR structure, > rather than trying to fix things up after the fact by attempting to split > blocks. E.g., we could state that it's invalid to have a phi which uses the > value defined by a callbr, if it's conditioned on that same block as > predecessor. That is: it's valid to use _other_ values defined in the > block ending in callbr, because they can be moved prior to the callbr. It's > also valid to use the value defined by the callbr in a phi conditioned on > some other intermediate block as predecessor, because then any required > moves can happen in the intermediate block. > > I believe such an IR restriction should be sufficient to make it possible > to emit valid code from the IR in all cases, but I'm a bit afraid of how > badly adding such odd edge-cases might screw up the rest of the compiler > and optimizer. > > That may be a reasonable restriction to place on the code.Allow me to wildly speculate a bit. What I would *like* to have happen is to generate assembly akin to this: *Lasm.goto.dest:* ; The original blockaddress destination. Lasm.goto.dest.bb1: mov %..., %... jmp Lasm.goto.dest.body Lasm.goto.dest.bb2: mov %..., %... jmp Lasm.goto.dest.body ; This would be elided, of course. Lasm.goto.dest.body: ... This preserves the blockaddress value. If we create a new instruction, let's say `indirectval' (a horrible name, but used for this example), it could save us from having to deal with edge splitting. It could take values similar to a phi node: <val> = indirectval <ty> [%v1, label %bb1], [%v2, label %bb2] where %v1 and %v2 are from callbr instructions. When we are converting the IR into machine instructions, we can generate something similar to the example above: asm.goto.dest: BR asm.goto.dest.bb1 asm.goto.dest.bb1: MOV ... BR asm.goto.dest.body asm.goto.dest.bb2: MOV ... BR asm.goto.dest.body asm.goto.dest.body: ... The one issue is the precise instruction to add to access the values. Perhaps they could be inserted directly before the indirectval inst. I think that your fear is justified.> > In any case, if we're going to support forming this kind of callbr in > Clang, then Clang still needs a place to put the stack stores after the > inline asm in order to represent the output constraints - which are > specified in terms of source-level variables and those are always in stack > locations when Clang is generating IR. I think that we can make all of this > work if we say that the output constraints, and thus the outputs of the > callbr, dominate only uses on the normal "fallthrough" branch. Then the > compiler has a single place to put the stores (and, later, a place to put > register copies, etc.). >Hal, Are you saying that values should not be used on indirect branches? -bw -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20190628/8eeb098d/attachment.html>
Finkel, Hal J. via llvm-dev
2019-Jun-29 02:27 UTC
[llvm-dev] [cfe-dev] [RFC] ASM Goto With Output Constraints
On 6/28/19 9:20 PM, Bill Wendling wrote: On Fri, Jun 28, 2019 at 5:39 PM Finkel, Hal J. <hfinkel at anl.gov<mailto:hfinkel at anl.gov>> wrote: On 6/28/19 5:35 PM, James Y Knight via llvm-dev wrote: On Fri, Jun 28, 2019 at 5:53 PM Bill Wendling <isanbard at gmail.com<mailto:isanbard at gmail.com>> wrote: On Fri, Jun 28, 2019 at 1:48 PM James Y Knight <jyknight at google.com<mailto:jyknight at google.com>> wrote: On Fri, Jun 28, 2019 at 3:00 PM Bill Wendling <isanbard at gmail.com<mailto:isanbard at gmail.com>> wrote: On Thu, Jun 27, 2019 at 1:44 PM Bill Wendling <isanbard at gmail.com<mailto:isanbard at gmail.com>> wrote: On Thu, Jun 27, 2019 at 1:29 PM James Y Knight <jyknight at google.com<mailto:jyknight at google.com>> wrote: I think this is fine, except that it stops at the point where things actually start to get interesting and tricky. How will you actually handle the flow of values from the callbr into the error blocks? A callbr can specify requirements on where its outputs live. So, what if two callbr, in different branches of code, specify _different_ constraints for the same output, and list the same block as a possible error successor? How can the resulting phi be codegened? This is where I fall back on the statement about how "the programmer knows what they're doing". Perhaps I'm being too cavalier here? My concern, if you want to call it that, is that we don't be too restrictive on the new behavior. For example, the "asm goto" may set a register to an error value (made up on the spot; may not be a common use). But, if there's no real reason to have the value be valid on the abnormal path, then sure we can declare that it's not valid on the abnormal path. I think I should explain my "programmer knows what they're doing" statement a bit better. I'm specifically referring to inline asm here. The more general "callbr" case may still need to be considered (see Reid's reply). When a programmer uses inline asm, they're implicitly telling the compiler that they *do* know what they're doing (I know this is common knowledge, but I wanted to reiterate it.). In particular, either they need to reference an instruction not readily available from the compiler (e.g. "cpuid") or the compiler isn't able to give them the needed performance in a critical section. I'm extending this sentiment to callbr with output constraints. Let's take your example below and write it as "normal" asm statements one on each branch of an if-then-else (please ignore any syntax errors): if: br i1 %cmp, label %true, label %false true: %0 = call { i32, i32 } asm sideeffect "poetry $0, $1", "={r8},={r9}" () br label %end false: %1 = call { i32, i32 } asm sideeffect "poetry2 $0, $1", "={r10},={r11}" () br label %end end: %vals = phi { i32, i32 } [ %0, %true ], [ %1, %false ] How is this handled in codegen? Is it an error or does the back-end handle it? Whatever's done today for "normal" inline asm is what I *think* should be the behavior for the inline asm callbr variant. If this doesn't seem sensible (and I realize that I may be thinking of an "in a perfect world" scenario), then we'll need to come up with a more sensible solution which may be to disallow the values on the error block until we can think of a better way to handle them. This example is no problem, because instructions can be emitted between what's emitted by "call asm" and the end of the block (be it a fallthrough, or a jump instruction. What gets emitted there is a move of the output register to another location -- either a register or to the stack. And therefore at the beginning of the "end" block, "%vals" is always in a consistent location, no matter how you got to that block. But in the callbr case, there is not a location at which those moves can be emitted, after the callbr, before the jump to "error". I see what you mean. Let's say we create a pseudo-instruction (similar to landingpad, et al) that needs to be lowered by the backend in a reasonable manner. The EH stuff has an external process/library that performs the actual unwinding and which sets the values accordingly. We won't have this. What we could do instead is split the edges and insert the copy-to-<where ever> statements there. Exactly -- except that doing that is potentially an invalid transform, because the address is being used as a value, not simply a jump target. The label list is just a list of _possible_ jump targets, changing those won't actually affect anything. You'd instead need to change the blockaddress constant, but in the general case you don't know where that address came from -- (and it may therefore be required that you have the same address for two separate callbr instructions). I guess this kinda touches on some of the same issues as in the other discussion about the handling of the blockaddress in callbr and inlining, etc... I wonder if we could put some validity restrictions on the IR structure, rather than trying to fix things up after the fact by attempting to split blocks. E.g., we could state that it's invalid to have a phi which uses the value defined by a callbr, if it's conditioned on that same block as predecessor. That is: it's valid to use _other_ values defined in the block ending in callbr, because they can be moved prior to the callbr. It's also valid to use the value defined by the callbr in a phi conditioned on some other intermediate block as predecessor, because then any required moves can happen in the intermediate block. I believe such an IR restriction should be sufficient to make it possible to emit valid code from the IR in all cases, but I'm a bit afraid of how badly adding such odd edge-cases might screw up the rest of the compiler and optimizer. That may be a reasonable restriction to place on the code. Allow me to wildly speculate a bit. What I would like to have happen is to generate assembly akin to this: Lasm.goto.dest: ; The original blockaddress destination. Lasm.goto.dest.bb1: mov %..., %... jmp Lasm.goto.dest.body Lasm.goto.dest.bb2: mov %..., %... jmp Lasm.goto.dest.body ; This would be elided, of course. Lasm.goto.dest.body: ... This preserves the blockaddress value. If we create a new instruction, let's say `indirectval' (a horrible name, but used for this example), it could save us from having to deal with edge splitting. It could take values similar to a phi node: <val> = indirectval <ty> [%v1, label %bb1], [%v2, label %bb2] where %v1 and %v2 are from callbr instructions. When we are converting the IR into machine instructions, we can generate something similar to the example above: asm.goto.dest: BR asm.goto.dest.bb1 asm.goto.dest.bb1: MOV ... BR asm.goto.dest.body asm.goto.dest.bb2: MOV ... BR asm.goto.dest.body asm.goto.dest.body: ... The one issue is the precise instruction to add to access the values. Perhaps they could be inserted directly before the indirectval inst. I don't understand how you can perform the renaming in general. Can't the block address to which the code jumps be provided as a pointer value (presumably generated using the labels-as-values extension)? I think that your fear is justified. In any case, if we're going to support forming this kind of callbr in Clang, then Clang still needs a place to put the stack stores after the inline asm in order to represent the output constraints - which are specified in terms of source-level variables and those are always in stack locations when Clang is generating IR. I think that we can make all of this work if we say that the output constraints, and thus the outputs of the callbr, dominate only uses on the normal "fallthrough" branch. Then the compiler has a single place to put the stores (and, later, a place to put register copies, etc.). Hal, Are you saying that values should not be used on indirect branches? Yes. -Hal -bw -- Hal Finkel Lead, Compiler Technology and Programming Languages Leadership Computing Facility Argonne National Laboratory -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20190629/eb67d363/attachment.html>