uweigand opened issue #3747:
Support for instructions with multiple outputs in ISLE seems to be missing and/or broken at the moment. In general, CLIF instructions may have any number of outputs, each of which may in turn be implemented as one or more registers (i.e. a
ValueRegsstructure).However, with ISLE instruction selection, the
lower_commonhelper routine calls thelowerISLE constructor, which only has a _single_ValueRegsreturn value. Now, there is some code inlower_commonthat seems intended to use the registers provides in that singleValueRegsto implement multiple outputs. But that doesn't appear to be working.In particular, in the common case where we have two outputs with a single register each, and the ISLE
lowerconstructor attempts to handle this by returning two registers in a singleValueRegs, we run into this loop:for output in outputs { let dsts = get_output_reg(isle_ctx.lower_ctx, *output); let ty = isle_ctx.lower_ctx.output_ty(output.insn, output.output); let (_, tys) = <C::I>::rc_for_type(ty).unwrap(); for ((temp, dst), ty) in temp_regs.by_ref().zip(dsts.regs()).zip(tys) { renamer.add_rename(*temp, dst.to_reg(), *ty); } }The problem here is that due to the way the Rust
ziproutine works, the first pass through that loop will already consume both registers intemp_regs(theValueRegsreturned fromlower). The second pass through the loop will findtemp_regsempty and simply not initialize the second output at all.This isn't an issue at the moment since the only multi-output instruction currently implemented in ISLE is
iadd_ifcout, and here the second output is the carry flag, which is ignored as an output anyway (it's handled differently by explicitly matching the combination of theiadd_ifcoutand the carry consumer). But if we want to handle instructions that really use multiple outputs (most notably call instructions), this is a problem.Now, this specific issue can be fixed by doing the
zipthe other way around. B.t.w. this then actually breaksiadd_ifcout- the current implementations rely on the second output being dropped! That problem can also be fixed e.g. by special-casingIFLAGtype outputs. But even then, we can only handle at most two outputs, because any singleValueRegscontains at most two registers.How is this supposed to be handled? Should
lowerbe changed to return something like a vector ofValueRegsinstead of just a single one? Or should there be a different ISLE constructor for multi-output instructions?CC @cfallin
fitzgen labeled issue #3747:
Support for instructions with multiple outputs in ISLE seems to be missing and/or broken at the moment. In general, CLIF instructions may have any number of outputs, each of which may in turn be implemented as one or more registers (i.e. a
ValueRegsstructure).However, with ISLE instruction selection, the
lower_commonhelper routine calls thelowerISLE constructor, which only has a _single_ValueRegsreturn value. Now, there is some code inlower_commonthat seems intended to use the registers provides in that singleValueRegsto implement multiple outputs. But that doesn't appear to be working.In particular, in the common case where we have two outputs with a single register each, and the ISLE
lowerconstructor attempts to handle this by returning two registers in a singleValueRegs, we run into this loop:for output in outputs { let dsts = get_output_reg(isle_ctx.lower_ctx, *output); let ty = isle_ctx.lower_ctx.output_ty(output.insn, output.output); let (_, tys) = <C::I>::rc_for_type(ty).unwrap(); for ((temp, dst), ty) in temp_regs.by_ref().zip(dsts.regs()).zip(tys) { renamer.add_rename(*temp, dst.to_reg(), *ty); } }The problem here is that due to the way the Rust
ziproutine works, the first pass through that loop will already consume both registers intemp_regs(theValueRegsreturned fromlower). The second pass through the loop will findtemp_regsempty and simply not initialize the second output at all.This isn't an issue at the moment since the only multi-output instruction currently implemented in ISLE is
iadd_ifcout, and here the second output is the carry flag, which is ignored as an output anyway (it's handled differently by explicitly matching the combination of theiadd_ifcoutand the carry consumer). But if we want to handle instructions that really use multiple outputs (most notably call instructions), this is a problem.Now, this specific issue can be fixed by doing the
zipthe other way around. B.t.w. this then actually breaksiadd_ifcout- the current implementations rely on the second output being dropped! That problem can also be fixed e.g. by special-casingIFLAGtype outputs. But even then, we can only handle at most two outputs, because any singleValueRegscontains at most two registers.How is this supposed to be handled? Should
lowerbe changed to return something like a vector ofValueRegsinstead of just a single one? Or should there be a different ISLE constructor for multi-output instructions?CC @cfallin
github-actions[bot] commented on issue #3747:
Subscribe to Label Action
cc @cfallin, @fitzgen
<details>
This issue or pull request has been labeled: "isle"Thus the following users have been cc'd because of the following labels:
- cfallin: isle
- fitzgen: isle
To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.
Learn more.
</details>
cfallin commented on issue #3747:
Thanks for writing this up -- it's still an unresolved question in the ISLE integration but we've talked about a few approaches -- the main ones that I think are viable exactly those you named in your last paragraph. To expand a bit more:
- We could add a
lower_2,lower_3, ... entrypoint for those instructions that have a fixed number of results -- the add+carry-like operators, etc. Here we should have a return type that is a container with exactly this manyValueRegs.- We could additionally add a
lower_multipleentrypoint for those instructions that return a dynamic (at compiler-backend-author-time) number of values.calland, if we eventually SSA-ify by creating a pseudoinst that grabs argument values,func_argsare probably the two most prominent examples. Any others that might fit this?- Specifically I think it's important that we don't change the existing
lowerentrypoint -- the clarity of just returning one value, without additional wrappers, should remain for most operators I think.
fitzgen commented on issue #3747:
Thanks for digging into this.
How is this supposed to be handled? Should
lowerbe changed to return something like a vector ofValueRegsinstead of just a single one? Or should there be a different ISLE constructor for multi-output instructions?I think having a different ISLE constructor for multi-output instructions would strike a good balance here. We want the single-output common case to read nicely, and the more we have to repackage and wrap the result (
RegintoValueRegsintoValuesRegsor something) the more difficult it is to read 99% of the lowerings.
uweigand commented on issue #3747:
Hmm. I agree that we shouldn't have to do mass changes of existing back ends. But I don't see how these would be needed just because we change the return type of
lower- basically all existing rules forlowerreturn eithervalue_reg,value_regs, orvalue_regs_none, so as long as we change the return type of those accordingly, the actual written rules wouldn't have to change.So as an example, we could change the output type of
lowerto something likeValueRegsList, and then havevalue_regs_nonereturn an emptyValueRegsListandvalue_regandvalue_regsreturn a single-elementValueRegsList. We'd probably want a newvalue_regs_pairor something for theiadd_ifcoutcase.For variadic instructions like
callwe'd need a new mechanism to generate variable-length lists anyway, probably using some sort of builder pattern, starting from an empty list and appending outputs in a matching rule using tail recursion.
bjorn3 commented on issue #3747:
Would it be possible to migrate call and ret to ISLE at all? They use quite a lot of non-trivial logic to handle the calling convention.
uweigand commented on issue #3747:
Would it be possible to migrate call and ret to ISLE at all? They use quite a lot of non-trivial logic to handle the calling convention.
I believe it is possible. In fact, I have an experimental patch that does so for s390x, and it is working except for functions with multiple return values, which is why I opened this issue.
Once I'm a bit further along with cleaning up the remaining issues, I'd be happy to post a proof-of-concept patch to start further discussion.
cfallin labeled issue #3747:
Support for instructions with multiple outputs in ISLE seems to be missing and/or broken at the moment. In general, CLIF instructions may have any number of outputs, each of which may in turn be implemented as one or more registers (i.e. a
ValueRegsstructure).However, with ISLE instruction selection, the
lower_commonhelper routine calls thelowerISLE constructor, which only has a _single_ValueRegsreturn value. Now, there is some code inlower_commonthat seems intended to use the registers provides in that singleValueRegsto implement multiple outputs. But that doesn't appear to be working.In particular, in the common case where we have two outputs with a single register each, and the ISLE
lowerconstructor attempts to handle this by returning two registers in a singleValueRegs, we run into this loop:for output in outputs { let dsts = get_output_reg(isle_ctx.lower_ctx, *output); let ty = isle_ctx.lower_ctx.output_ty(output.insn, output.output); let (_, tys) = <C::I>::rc_for_type(ty).unwrap(); for ((temp, dst), ty) in temp_regs.by_ref().zip(dsts.regs()).zip(tys) { renamer.add_rename(*temp, dst.to_reg(), *ty); } }The problem here is that due to the way the Rust
ziproutine works, the first pass through that loop will already consume both registers intemp_regs(theValueRegsreturned fromlower). The second pass through the loop will findtemp_regsempty and simply not initialize the second output at all.This isn't an issue at the moment since the only multi-output instruction currently implemented in ISLE is
iadd_ifcout, and here the second output is the carry flag, which is ignored as an output anyway (it's handled differently by explicitly matching the combination of theiadd_ifcoutand the carry consumer). But if we want to handle instructions that really use multiple outputs (most notably call instructions), this is a problem.Now, this specific issue can be fixed by doing the
zipthe other way around. B.t.w. this then actually breaksiadd_ifcout- the current implementations rely on the second output being dropped! That problem can also be fixed e.g. by special-casingIFLAGtype outputs. But even then, we can only handle at most two outputs, because any singleValueRegscontains at most two registers.How is this supposed to be handled? Should
lowerbe changed to return something like a vector ofValueRegsinstead of just a single one? Or should there be a different ISLE constructor for multi-output instructions?CC @cfallin
uweigand closed issue #3747:
Support for instructions with multiple outputs in ISLE seems to be missing and/or broken at the moment. In general, CLIF instructions may have any number of outputs, each of which may in turn be implemented as one or more registers (i.e. a
ValueRegsstructure).However, with ISLE instruction selection, the
lower_commonhelper routine calls thelowerISLE constructor, which only has a _single_ValueRegsreturn value. Now, there is some code inlower_commonthat seems intended to use the registers provides in that singleValueRegsto implement multiple outputs. But that doesn't appear to be working.In particular, in the common case where we have two outputs with a single register each, and the ISLE
lowerconstructor attempts to handle this by returning two registers in a singleValueRegs, we run into this loop:for output in outputs { let dsts = get_output_reg(isle_ctx.lower_ctx, *output); let ty = isle_ctx.lower_ctx.output_ty(output.insn, output.output); let (_, tys) = <C::I>::rc_for_type(ty).unwrap(); for ((temp, dst), ty) in temp_regs.by_ref().zip(dsts.regs()).zip(tys) { renamer.add_rename(*temp, dst.to_reg(), *ty); } }The problem here is that due to the way the Rust
ziproutine works, the first pass through that loop will already consume both registers intemp_regs(theValueRegsreturned fromlower). The second pass through the loop will findtemp_regsempty and simply not initialize the second output at all.This isn't an issue at the moment since the only multi-output instruction currently implemented in ISLE is
iadd_ifcout, and here the second output is the carry flag, which is ignored as an output anyway (it's handled differently by explicitly matching the combination of theiadd_ifcoutand the carry consumer). But if we want to handle instructions that really use multiple outputs (most notably call instructions), this is a problem.Now, this specific issue can be fixed by doing the
zipthe other way around. B.t.w. this then actually breaksiadd_ifcout- the current implementations rely on the second output being dropped! That problem can also be fixed e.g. by special-casingIFLAGtype outputs. But even then, we can only handle at most two outputs, because any singleValueRegscontains at most two registers.How is this supposed to be handled? Should
lowerbe changed to return something like a vector ofValueRegsinstead of just a single one? Or should there be a different ISLE constructor for multi-output instructions?CC @cfallin
Last updated: Dec 13 2025 at 19:03 UTC