8381149: C2: postalloc: deduplicate vector constants#30500
8381149: C2: postalloc: deduplicate vector constants#30500ruben-arm wants to merge 1 commit intoopenjdk:masterfrom
Conversation
Some vector operations do not have inputs and essentially initialize vectors with a constant value. These operations can be marked for spilling and subsequently rematerialized at every use. The result of the transformation might look as follows: movi v16.2d, #0x0 str q16, [x16, openjdk#64] movi v16.2d, #0x0 str q16, [x16, openjdk#32] movi v16.2d, #0x0 str q16, [x16, openjdk#16] movi v16.2d, #0x0 str q16, [x16] movi v16.2d, #0x0 str q16, [x16, openjdk#48] movi v16.2d, #0x0 str q16, [x16, openjdk#112] movi v16.2d, #0x0 str q16, [x16, openjdk#80] movi v16.2d, #0x0 str q16, [x16, openjdk#96] Introduce deduplication of these rematerialized vector constant initializations reducing the above sequence to: movi v16.2d, #0x0 str q16, [x16, openjdk#64] str q16, [x16, openjdk#32] str q16, [x16, openjdk#16] str q16, [x16] str q16, [x16, openjdk#48] str q16, [x16, openjdk#112] str q16, [x16, openjdk#80] str q16, [x16, openjdk#96]
|
👋 Welcome back ruben-arm! A progress list of the required criteria for merging this PR into |
|
❗ This change is not yet ready to be integrated. |
|
@ruben-arm The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
| // define_is_vector_same_const_value ------------------------------------------ | ||
| void ArchDesc::buildVectorIsSameConstValue(FILE* fp) { | ||
| fprintf(fp, "bool Matcher::vector_is_same_const_value(const MachNode* node1, const MachNode* node2) {\n"); | ||
| fprintf(fp, " assert(node1 != nullptr, \"node1 is unexpectedly nullptr\");\n"); | ||
| fprintf(fp, " assert(node2 != nullptr, \"node2 is unexpectedly nullptr\");\n"); | ||
| fprintf(fp, "\n"); | ||
| fprintf(fp, " if (node1->rule() != node2->rule()) {\n"); | ||
| fprintf(fp, " return false;\n"); | ||
| fprintf(fp, " }\n"); | ||
| fprintf(fp, "\n"); | ||
| fprintf(fp, " assert(node1->num_opnds() == node2->num_opnds(),\n"); | ||
| fprintf(fp, " \"the same rule, so the same number of operands\");\n"); | ||
| fprintf(fp, "\n"); | ||
| fprintf(fp, " const Type* node1_type = node1->bottom_type();\n"); | ||
| fprintf(fp, " const Type* node2_type = node2->bottom_type();\n"); | ||
| fprintf(fp, " if (node1_type != node2_type) {\n"); | ||
| fprintf(fp, " return false;\n"); | ||
| fprintf(fp, " }\n"); | ||
| fprintf(fp, "\n"); | ||
| fprintf(fp, " MachOper* opnd1, *opnd2;\n"); | ||
| fprintf(fp, " switch ( node1->rule() ) {\n"); | ||
|
|
||
| InstructForm* instr; | ||
| _instructions.reset(); | ||
| while ( (instr = (InstructForm*)_instructions.iter()) != nullptr ) { | ||
| if ( instr->ideal_only() ) continue; | ||
| if ( !instr->sets_result() ) continue; | ||
| if ( !instr->is_vector() ) continue; | ||
|
|
||
| // At least one non-result operand | ||
| if ( instr->num_opnds() <= 1 ) continue; | ||
|
|
||
| bool all_operands_suitable = true; | ||
| Component* comp = nullptr; | ||
|
|
||
| instr->_components.reset(); | ||
| while( (comp = instr->_components.iter()) != nullptr ) { | ||
| Form* form = (Form*)_globalNames[comp->_type]; | ||
| assert(form != nullptr, "component type must be a defined form"); | ||
| OperandForm* op = form->is_operand(); | ||
|
|
||
| if ( op == nullptr ) { | ||
| // Not an OperandForm - conservatively reject. | ||
| all_operands_suitable = false; | ||
| break; | ||
| } | ||
|
|
||
| if ( comp->isa(Component::DEF) ) { | ||
| if ( comp->isa(Component::USE) ) { | ||
| // Result register is also an input. | ||
| all_operands_suitable = false; | ||
| break; | ||
| } | ||
|
|
||
| const char* result_type = op->ideal_type(_globalNames); | ||
|
|
||
| // Only consider operations writing to a vector operand. | ||
| if ( strncmp(result_type, "Vec", 3) != 0 ) { | ||
| all_operands_suitable = false; | ||
| break; | ||
| } | ||
|
|
||
| continue; | ||
| } | ||
|
|
||
| if ( op->_interface == nullptr || | ||
| op->_interface->is_ConstInterface() == nullptr || | ||
| op->num_edges(_globalNames) != 0) { | ||
| // Not a constant OperandForm. | ||
| all_operands_suitable = false; | ||
| break; | ||
| } | ||
|
|
||
| Form::DataType dtype = op->is_base_constant(_globalNames); | ||
| switch ( dtype ) { | ||
| case Form::idealH: | ||
| case Form::idealI: | ||
| case Form::idealL: | ||
| case Form::idealF: | ||
| case Form::idealD: | ||
| break; | ||
|
|
||
| default: | ||
| all_operands_suitable = false; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if ( !all_operands_suitable ) continue; | ||
|
|
||
| fprintf(fp, " case %s_rule:\n", instr->_ident); | ||
| instr->_components.reset(); | ||
| while( (comp = instr->_components.iter()) != nullptr ) { | ||
| if (comp->isa(Component::DEF)) continue; | ||
|
|
||
| Form* form = (Form*)_globalNames[comp->_type]; | ||
| assert(form != nullptr, "component type must be a defined form"); | ||
| OperandForm* op = form->is_operand(); | ||
| assert(op != nullptr, "component type must be OperandForm"); | ||
|
|
||
| Form::DataType dtype = op->is_base_constant(_globalNames); | ||
| const char* get_const = ""; | ||
| const char* cast = ""; | ||
| switch ( dtype ) { | ||
| case Form::idealH: | ||
| get_const = "constantH"; | ||
| break; | ||
| case Form::idealI: | ||
| get_const = "constant"; | ||
| break; | ||
| case Form::idealL: | ||
| get_const = "constantL"; | ||
| break; | ||
| case Form::idealF: | ||
| get_const = "constantF"; | ||
| cast = "jint_cast"; | ||
| break; | ||
| case Form::idealD: | ||
| get_const = "constantD"; | ||
| cast = "jlong_cast"; | ||
| break; | ||
|
|
||
| default: | ||
| assert(false, "all other types should have been filtered out above."); | ||
| break; | ||
| } | ||
|
|
||
| int opnd_index = instr->operand_position(comp->_name, Component::USE); | ||
| fprintf(fp, " opnd1 = node1->_opnds[%d];\n", opnd_index); | ||
| fprintf(fp, " opnd2 = node2->_opnds[%d];\n", opnd_index); | ||
| fprintf(fp, " if (%s(opnd1->%s()) != %s(opnd2->%s())) return false;\n", | ||
| cast, get_const, cast, get_const); | ||
| } | ||
| fprintf(fp, " return true;\n"); | ||
| } | ||
|
|
||
| fprintf(fp, " default:\n"); | ||
| fprintf(fp, " return false;\n"); | ||
| fprintf(fp, " }\n"); | ||
| fprintf(fp, "}\n"); | ||
| } | ||
|
|
There was a problem hiding this comment.
Drive-by comment: could you fix spacing here? E.g. if (cond), etc., and align cases with the switch
There was a problem hiding this comment.
Thanks for the advice. I will adjust this in the next update.
galderz
left a comment
There was a problem hiding this comment.
Thanks for the PR! Some minor comments.
| } else if (old->is_Mach()) { | ||
| /* Same node is supplied twice so this is only to check if "old" is | ||
| * a initializer of a vector with a constant value */ | ||
| return Matcher::vector_is_same_const_value(old->as_Mach(), old->as_Mach()); |
There was a problem hiding this comment.
This seems odd to me.
Personally I would make 2 methods. One for here that answer the question "is vector constant initializer" and takes 1 argument, old->as_Mach(). And then keep a second method that takes 2 arguments and keeps the same name asking is the const value is the same. I think this would make things clearer and avoid the comment.
There was a problem hiding this comment.
Thanks for the suggestion. I will change this into two methods in the next update.
Given this is for assertion purpose only, would it be suitable if the new method would be a proxy for the existing vector_is_same_const_value? (we could generate another method in the adlc but that might be more complexity than optimal in this case)
There was a problem hiding this comment.
I think a proxy would be fine, but let's see if reviewers agree with this POV.
| */ | ||
|
|
||
| public class TestPostAllocVectorConstDeduplication { | ||
| private static final VectorSpecies<Integer> I_SPECIES = IntVector.SPECIES_MAX; |
There was a problem hiding this comment.
Judging from the changes above that pattern match on different ideal... forms, should this be testing other types too? Long, float, double and float16?
There was a problem hiding this comment.
Sure. I will extend the test in the next update.
iwanowww
left a comment
There was a problem hiding this comment.
After studying proposed enhancement a bit, I'm curious why it is specific to vector constants. It looks like a peephole optimization opportunity for any repeated value rematerialization. (Unless when a constant value is encoded as an immediate maybe, but then register-based encoding can be more compact.)
|
The total number of required reviews for this PR has been set to 2 based on the presence of this label: |
Some vector operations do not have inputs and essentially initialize vectors with a constant value. These operations can be marked for spilling and subsequently rematerialized at every use. The result of the transformation might look as follows:
Introduce deduplication of these rematerialized vector constant initializations reducing the above sequence to:
Progress
Error
- [x] I confirm that I make this contribution in accordance with the [OpenJDK Interim AI Policy](https://openjdk.org/legal/ai).Issue
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/30500/head:pull/30500$ git checkout pull/30500Update a local copy of the PR:
$ git checkout pull/30500$ git pull https://git.openjdk.org/jdk.git pull/30500/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 30500View PR using the GUI difftool:
$ git pr show -t 30500Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/30500.diff
Using Webrev
Link to Webrev Comment