-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[GlobalISel] Generic MachineInstrs need a type but this increases the memory footprint of the related objects #26950
Comments
Hi Chandler, Chris, David, Eric, and Jim, Any ideas for this? Thanks. |
I was thinking the side table may be the most natural approach here. My problem was where do we make it live, but now I think it would be reasonable to have it on the MachineFunctionInfo or something. Thoughts? |
There's way not enough context to know what the heck you need the type for and so where to put it :p That said, it seems reasonable to put it as part of the instruction, but perhaps layer the generic instructions differently so only they need the type rather than change the generic type? Possibly make the generic instructions inherit from MachineInstr? or a unified base? |
Good point, I am so into it that I did not realize. Anyhow, it does not scale. The solution to this problem is then to have the generic instructions to be typed.
Having inheritance was one of the option, but I rejected it because when we after legalization, the select phase will likely be just a matter of switching an opcode (G_ADD i16 for instance to ADDi16rr). |
I don't understand why this is such a problem... MachineInstr has 6 pointer sized fields I spot by inspection, plus a 32-bit field and 3 8-bit fields. Glancing at it, I think there may be 5 bytes of padding as well. It seems surprising to me that it would be so problematic to increase the memory consumed by this one class by 1/7th... Do you have specific measurements that concern you here? The opcode isn't stored inline in MachineInstr though, so maybe the type shouldn't be either. If you look at MCInstrDesc, it has 6 >= pointer sized fields as well, plus 8 bytes packed into it. So you could also create one of these per opcode/type pair, and only grow the MCInstrDesc size by 1/7th (this would create some really amazing layering challenges though, please be extremely careful if you go this route). But I also suspect you don't need all the things in MCInstrDesc when you have a generic MachineInstr. In fact, I don't think you need almost any of the stuff in there. most of it is about MC physical registers and other MC-level information which seems definitionally irrelevant for your case? Similarly the encoding size? Why not make the pointer to an MCInstrDesc instead point to a custom struct for the generic MI, with your own opcode and type pointer and other metadata you need? |
(Since it wasn't obvious, the last paragraph was suggesting something like PointerUnion<const MCInstrDesc, MyOtherInstrDescType>. |
Thanks Chandler! I like the analysis and the way you found space for me ;). |
I gave this some more thoughts. Another alternative that is less intrusive than messing with the MCDescription field and more general is to define a new kind of MachineOperand: Type. It is less intrusive in the sense that we do not have to play a dance with the MC description held by TRI and generated by tablegen. (Generic instructions do have MSCDesc.) Moreover, the new MachineOperand kind wouldn’t impact the size of the MachineOperand class. (We would get a new type pointer in Content union.) It is more general in the sense that we would support generic instruction with several types for free. In terms of space, the machine operands arrays are allocated for size of power of 2. I expect that, in most cases, we will have spare space to store them an additional machine operand, but yes, in the worst case, we will bump the size of the machine operands arrays at runtime. PS: Thanks to Ahmed for double checking the allocation strategy of MachineOperand after I through the idea. |
There is no 'MCDescription' so I'm not sure what you're referring to here at all... Perhaps you're imagining changing the contents of the MCInstrDesc class itself? As I said in my post, that doesn't seem necessary or clean, and isn't what I suggest (although it does seem possible). I suggested that you take the first field of MachineInstr: const MCInstrDesc *MCID; And make it instead: PointerUnion<const MachineGenericOp *, const MCInstDesc *> ID; This wouldn't require any changes to MCInstDesc. It wouldn't require any changes to anything in MC, which seems like a clear non-starter. It wouldn't require any more bits that I can see and it would allow re-pointing the MachineInstr to a concrete MCInstDesc whenever you like. Is there a problem with this approach that you're struggling with? (Other than perhaps needing better names than the ones I've suggested?)
I don't know what you really mean by MSCDesc, do you mean MCInstrDesc? If so, that seems weird and unlikely to be the right design. MCInstrDesc (and generally all of MC) doesn't make a whole lot of sense for generic nodes here. And it isn't clear why this would be necessary either, but maybe I just don't understand some inner coupling between MI and MC?
Note that MachineOperand is already crazy expensive, and I'm somewhat worried about packing more into the Content union, but OK.
I don't really understand this statement. Can you elaborate on what you mean? I don't think I've suggested an approach that has a cost associated with generic instructions with several types, but maybe I've missed something...
How confident are you? I would expect there to be a reasonably large number of instructions with one or two operands. But maybe all of this is irrelevant, as it isn't clear what problem this design is setting out to solve... |
Yes, I’ve got that :).
Yeah, I meant MCInstDesc. That’s the dance I was talking about and why I think it is intrusive.
Unless it was not clear, it feels intrusive and unnatural to me :).
Is it clearer now?
Indeed, you didn’t suggested a cost associated with generic instructions with several types. I was just thinking that if we want several types for the instruction (e.g., mul_lohi), we could have several “type” machine operand at least at first. I.e., this is more or less free implementation wise.
I haven’t actually measured, but I would expect that the instructions needing types (i.e., anything that is not a move or a target specific instructions) have around 3 operands (1 def and 2 arguments). I may be wrong of course and this is something we will measure.
I think you got it right: this is primarily runtime size. However, I think it is important that the handling of generic MachineInstr feels natural and I don’t think the dance between MCInstDesc and MachineGenericOp falls into that category. Anyhow, that was a proposed alternative, I am of course open to discuss and actually, I think this kind of debates are good for the end result design. In other words, thanks for your time in discussing, evaluating the different approaches! |
OK, If you need to have MCInstrDesc for generic MIs, then it seems really easy to have that type contain the the type in addition to the opcode. Even if it makes those objects larger, it wouldn't make the MachineInstr larger. Perhaps we need to better separate the different bits of MCInstrDesc from things that are table generated, but it sounds like you'll need to do that anyways if you need to have MCInstrDesc objects for the generic ops. I pretty strongly feel like the correct location for the type is either in the generic MI analog to MCInstrDesc or in MCInstrDesc itself. It seems really awkward to try to hide the type inside the operand list. I think it will add a lot of complexity to the end result even if it is easier to cut in today. |
(Note: Actually we already have that with target independent instructions like COPY. The new generic instructions are special in the sense that they should not appear after ISel, but other than that, this is really the same thing.)
That does not work with the current scheme. The MCInstrDesc are shared between similar instructions. If we put the type here, then all those instructions share the same type, whereas it is probably not what we want.
Like I said in my note in the beginning of that post, generic operations already exist and are well supported with the current MCInstrDesc. What is missing is “just" the type and as just stated, it feels wrong to keep it there. At some point, I was thinking a side table could do as well. I’ll come back to that. Again the MachineOperand felt nice in the sense that I believe we already have unused space in the array in MachineInstr instances.
Maybe what we need is a MachineInstrInfo that could hold the information we are missing (I.e., the side table idea). That would have the benefit to be consistent with what we do for TargetRegisterInfo vs. MachineRegisterInfo. What I don’t like is the fact that an instruction is not self contained, but that’s already the case anyway.
Possibly yeah, though prototyping is good to answer this kind of questions. How do you feel about the side table/MachineInstrInfo idea? |
Right, sorry I wasn't more explicit about this, I was of course assuming that you'd generate the types you need.
Ok, I see the reason you're reluctant to go this route now, but I still think something like this is the right long term design. To try to give you an idea of why, let's take a look at what is in MCInstrDesc: class MCInstrDesc { Totally fine so far... unsigned char Size; // Number of bytes in encoding. These ill fitting for the target independent phase... uint64_t Flags; // Flags identifying machine instr class But this one makes sense... uint64_t TSFlags; // Target Specific Flag values It says on the tin that this is for targets... const MCPhysReg *ImplicitUses; // Registers implicitly read by this instr Pretty sure you aren't going to end up with physical registers for these... const MCOperandInfo *OpInfo; // 'NumOperands' entries about operands This actually makes sense though... // Subtarget feature that this is deprecated on, if any But this seems again really unnecessary. So I feel like here we have some 4 pointer-sized (or larger!) fields, plus some smaller fields, which have no utility for target independent instructions. But the separate object to describe the opcode + generic information (operand number, type, etc) makes lots of sense to me, especially as it lets us avoid repeating that information in each 64-bit integer add instruction (which we will have soooo many of), etc. But pretty clearly generating statically all possible combinations doesn't make sense. I'm totally in agreement there. Ultimately, I think that in order for the design to make sense, you'll have to change at least one of the following properties:
The reason I was pushing toward #1 is because it really feels like MCInstrDesc is a poor fit for MI during the phase where you have types and are in a completely target independent space. I suspect the nodes could be made much smaller (and thus memory efficient) by separating them from the MCInstrDesc type, and it seems it would also solve the problem of having these things not fit the statically-generated-table model that uses. It also helps clarify that these constructs simply can't exist at the MC level at all. It makes the lowering from them to actual MC representable instructions make much more sense IMO. To an extent, doing #3 would also achieve much of this, and maybe that's a better path. If so, I'm fine with that too, they seem pretty interchangeable from a high level design and mostly a difference of how the bits are represented. That said, I hear you that this would be an invasive and big change. I also think it is the right design, and I don't think we should compromise that.
I'm surprised you feel this way. Looking at MCInstrDesc, it seems like a rather poor fit for generic operations. My comments above probably elaborate why I see it that way. If this is the root cause of us preferring different designs this might really be what we need to focus on...
A side table also feels like side stepping the core design issue here: MachineInstr currently is hard coded to an MC level construct that doesn't support type-full MI. I think we will eventually need to undo that coupling for the design to make any sense. |
[…]
Although I agree with you, for the prototype I wanted to avoid big invasive change like this.
Actually, I did not look into the MCInstrDesc. Thank for the patience you put in showing me where I was wrong!
That sounds legit, yes. |
Just a drive-by note that more than one type will probably be needed for some instructions (icmp, cmpxchg and especially intrinsics spring to mind). |
Closing this as an old issue not to be addressed. |
Extended Description
As r260558, we added a Type * field to the MachineInstr class. This field is actually used only for generic MachineInstr and moreover, only during the instruction selection process.
In other words, we negatively impact the memory footprint of the whole backend pipeline for generic instructions that are used only at the beginning of such pipeline.
The question is then, is it possible to have that information in a union or
something?
My initial thought was that we could have a derived class GenericMachineInstr
with additional information, but in practice it makes little to no sense since
generic MachineInstrs are likely turned into non-generic ones by just switching
the opcode. In other words, we don't want to go through the process of creating
a new, non-generic MachineInstr, object each time we do this switch. The memory
benefit probably is not worth the extra compile time.
Another option would be to keep the type of the MachineInstr in a side table.
This would induce an extra indirection though.
That being said, when we have the basic pipeline lay down, we will be able to make measurements for the different option.
However, if you have ideas, please share!
The text was updated successfully, but these errors were encountered: