mirror of
https://github.com/openjdk/jdk.git
synced 2026-07-24 18:03:10 +00:00
8387073: C2: StoreNode::Ideal wrongly optimizes away scalar store before masked vector store
Reviewed-by: epeter, thartmann, qamai, chagedorn
This commit is contained in:
parent
436e3647cb
commit
4e30a933bf
@ -3525,8 +3525,9 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) {
|
||||
Node* address = in(MemNode::Address);
|
||||
Node* value = in(MemNode::ValueIn);
|
||||
// Back-to-back stores to same address? Fold em up. Generally
|
||||
// unsafe if I have intervening uses.
|
||||
{
|
||||
// unsafe if I have intervening uses. Also unsafe for masked or
|
||||
// scatter vector stores as the wider store.
|
||||
if (!this->is_StoreVector() || this->Opcode() == Op_StoreVector) {
|
||||
Node* st = mem;
|
||||
// If Store 'st' has more than one use, we cannot fold 'st' away.
|
||||
// For example, 'st' might be the final state at a conditional
|
||||
@ -3534,15 +3535,14 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) {
|
||||
// the same time 'st' is live, which might be unschedulable. So,
|
||||
// require exactly ONE user until such time as we clone 'mem' for
|
||||
// each of 'mem's uses (thus making the exactly-1-user-rule hold
|
||||
// true).
|
||||
while (st->is_Store() && st->outcnt() == 1) {
|
||||
// true). Further, 'st' must be a contiguous store, otherwise
|
||||
// memory_size does not make sense for measuring overlap.
|
||||
while (st->is_Store() && st->outcnt() == 1 && (!st->is_StoreVector() || st->Opcode() == Op_StoreVector)) {
|
||||
// Looking at a dead closed cycle of memory?
|
||||
assert(st != st->in(MemNode::Memory), "dead loop in StoreNode::Ideal");
|
||||
assert(Opcode() == st->Opcode() ||
|
||||
st->Opcode() == Op_StoreVector ||
|
||||
Opcode() == Op_StoreVector ||
|
||||
st->Opcode() == Op_StoreVectorScatter ||
|
||||
Opcode() == Op_StoreVectorScatter ||
|
||||
phase->C->get_alias_index(adr_type()) == Compile::AliasIdxRaw ||
|
||||
(Opcode() == Op_StoreL && st->Opcode() == Op_StoreI) || // expanded ClearArrayNode
|
||||
(Opcode() == Op_StoreI && st->Opcode() == Op_StoreL) || // initialization by arraycopy
|
||||
@ -3551,6 +3551,11 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) {
|
||||
|
||||
if (st->in(MemNode::Address)->eqv_uncast(address) &&
|
||||
st->as_Store()->memory_size() <= this->memory_size()) {
|
||||
assert(!is_predicated_vector() && !is_StoreVectorMasked() &&
|
||||
!is_StoreVectorScatter() && !is_StoreVectorScatterMasked() &&
|
||||
!st->is_predicated_vector() && !st->is_StoreVectorMasked() &&
|
||||
!st->is_StoreVectorScatter() && !st->is_StoreVectorScatterMasked(),
|
||||
"optimization only correct for full-width stores without holes");
|
||||
Node* use = st->raw_out(0);
|
||||
if (phase->is_IterGVN()) {
|
||||
phase->is_IterGVN()->rehash_node_delayed(use);
|
||||
|
||||
@ -184,7 +184,8 @@ tier1_compiler_2 = \
|
||||
-compiler/classUnloading/methodUnloading/TestOverloadCompileQueues.java \
|
||||
-compiler/codecache/stress \
|
||||
-compiler/codegen/aes \
|
||||
-compiler/gcbarriers/PreserveFPRegistersTest.java
|
||||
-compiler/gcbarriers/PreserveFPRegistersTest.java \
|
||||
-compiler/igvn/TestMaskedStoreIdealization.java
|
||||
|
||||
tier1_compiler_3 = \
|
||||
compiler/intrinsics/ \
|
||||
|
||||
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8387073
|
||||
* @key randomness
|
||||
* @summary Arrays.copyOf(Range) must pad overized elements with 0 with partial inlining as well.
|
||||
* @library /test/lib /
|
||||
* @run driver ${test.main.class}
|
||||
*/
|
||||
|
||||
package compiler.arraycopy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.stream.*;
|
||||
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.template_framework.*;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
import compiler.lib.template_framework.library.*;
|
||||
|
||||
public class TestSubwordPartialInlining {
|
||||
private static final Random RANDOM = Utils.getRandomInstance();
|
||||
private static final String PACKAGE = "compiler.arraycopy.generated";
|
||||
private static final String CLASS_NAME = "TestSubwordPartialInliningGenerated";
|
||||
|
||||
public static void main(String[] args) {
|
||||
final CompileFramework comp = new CompileFramework();
|
||||
comp.addJavaSourceCode(PACKAGE + "." + CLASS_NAME, generate(comp));
|
||||
comp.compile();
|
||||
comp.invoke(PACKAGE + "." + CLASS_NAME, "main", new Object[] { args });
|
||||
}
|
||||
|
||||
private static String generate(CompileFramework comp) {
|
||||
final Set<String> imports = Set.of("java.util.Arrays",
|
||||
"java.util.Random",
|
||||
"jdk.test.lib.Utils",
|
||||
"compiler.lib.generators.*");
|
||||
|
||||
final List<TemplateToken> tests = new ArrayList<>();
|
||||
tests.addAll(Stream.of(CodeGenerationDataNameType.booleans(), CodeGenerationDataNameType.bytes(), CodeGenerationDataNameType.shorts(), CodeGenerationDataNameType.chars())
|
||||
.map(pty -> new TestPerType(pty).generate())
|
||||
.toList());
|
||||
tests.add(PrimitiveType.generateLibraryRNG());
|
||||
|
||||
return TestFrameworkClass.render(PACKAGE, CLASS_NAME, imports, comp.getEscapedClassPathOfCompiledClasses(), tests);
|
||||
}
|
||||
|
||||
enum Operation {
|
||||
COPY_OF,
|
||||
COPY_OF_RANGE
|
||||
}
|
||||
|
||||
record TestPerType(PrimitiveType pty) {
|
||||
private String getTestName(Operation op) {
|
||||
return switch (op) {
|
||||
case COPY_OF -> "CopyOf" + pty.boxedTypeName();
|
||||
case COPY_OF_RANGE -> "CopyOfRange" + pty.boxedTypeName();
|
||||
};
|
||||
}
|
||||
|
||||
TemplateToken generate() {
|
||||
final int maxSize = RANDOM.nextInt(0, 4) == 0 ? RANDOM.nextInt(5, 100) : 4;
|
||||
final int inputSize = RANDOM.nextInt(1, maxSize);
|
||||
final int copySize = RANDOM.nextInt(inputSize + 1, maxSize + 1);
|
||||
|
||||
|
||||
var runTemplate = Template.make("op", (Operation op) -> scope(
|
||||
let("pty", pty),
|
||||
let("testName", getTestName(op)),
|
||||
"""
|
||||
@Run(test = "test#{testName}", mode = RunMode.STANDALONE)
|
||||
static void run#{testName}() {
|
||||
final #pty[] intRes = test#{testName}();
|
||||
for (int i = 0; i < 10_000; i++) {
|
||||
test#{testName}();
|
||||
}
|
||||
final #pty[] compRes = test#{testName}();
|
||||
if (!Arrays.equals(intRes, compRes)) {
|
||||
throw new RuntimeException("wrong result:\\n" +
|
||||
" interpreter result: " + Arrays.toString(intRes) + "\\n" +
|
||||
" compiled result: " + Arrays.toString(compRes));
|
||||
}
|
||||
}
|
||||
"""
|
||||
));
|
||||
|
||||
var testTemplate = Template.make("op", (Operation op) -> scope(
|
||||
let("pty", pty),
|
||||
let("testName", getTestName(op)),
|
||||
let("len", copySize),
|
||||
let("minRange", RANDOM.nextInt(0, 4) == 0 ? RANDOM.nextInt(0, inputSize) : 0),
|
||||
"""
|
||||
@Test
|
||||
static #pty[] test#{testName}() {
|
||||
""",
|
||||
" return ",
|
||||
switch (op) {
|
||||
case COPY_OF -> "Arrays.copyOf(#{pty}Arr, #len)";
|
||||
case COPY_OF_RANGE -> "Arrays.copyOfRange(#{pty}Arr, #minRange, #len)";
|
||||
},
|
||||
";\n",
|
||||
"""
|
||||
}
|
||||
|
||||
"""
|
||||
));
|
||||
|
||||
return Template.make(() -> scope(
|
||||
Stream.of(Operation.COPY_OF, Operation.COPY_OF_RANGE)
|
||||
.map(op -> scope(runTemplate.asToken(op), testTemplate.asToken(op)))
|
||||
.toList(),
|
||||
Hooks.CLASS_HOOK.insert(scope(
|
||||
let("pty", pty),
|
||||
let("arr", String.join(", ", Collections.nCopies(inputSize, (String) pty.callLibraryRNG()))),
|
||||
"""
|
||||
private static #pty[] #{pty}Arr = { #arr };
|
||||
"""
|
||||
))
|
||||
)).asToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,522 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8387073
|
||||
* @key randomness
|
||||
* @summary Narrower stores preceding masked vector stores must not be eliminated.
|
||||
* @modules jdk.incubator.vector
|
||||
* @library /test/lib /
|
||||
* @run driver ${test.main.class}
|
||||
*/
|
||||
|
||||
package compiler.igvn;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.stream.*;
|
||||
|
||||
import jdk.incubator.vector.VectorShape;
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
import compiler.lib.compile_framework.*;
|
||||
import compiler.lib.template_framework.*;
|
||||
import static compiler.lib.template_framework.Template.scope;
|
||||
import static compiler.lib.template_framework.Template.let;
|
||||
import compiler.lib.template_framework.library.*;
|
||||
|
||||
public class TestMaskedStoreIdealization {
|
||||
private static final Random RANDOM = Utils.getRandomInstance();
|
||||
private static final String PACKAGE = "compiler.igvn.generated";
|
||||
private static final String CLASS_NAME = "TestMaskedStoreIdealizationGenerated";
|
||||
|
||||
public static void main(String[] args) {
|
||||
final CompileFramework comp = new CompileFramework();
|
||||
comp.addJavaSourceCode(PACKAGE + "." + CLASS_NAME, generate(comp));
|
||||
comp.compile("--add-modules=jdk.incubator.vector");
|
||||
|
||||
List<String> vmArgs = new ArrayList<>(List.of(
|
||||
"--add-modules=jdk.incubator.vector",
|
||||
"--add-opens", "jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED"
|
||||
));
|
||||
vmArgs.addAll(Arrays.asList(args)); // Forward args
|
||||
vmArgs.add("-XX:-StressReflectiveCode"); // Temporarily disable stress flag that causes unrelated failures.
|
||||
String[] vmArgsArray = vmArgs.toArray(new String[0]);
|
||||
|
||||
comp.invoke(PACKAGE + "." + CLASS_NAME, "main", new Object[] { vmArgsArray });
|
||||
}
|
||||
|
||||
private static String generate(CompileFramework comp) {
|
||||
final Set<String> imports = Set.of("java.util.Arrays",
|
||||
"java.util.Random",
|
||||
"jdk.incubator.vector.*",
|
||||
"jdk.test.lib.Utils",
|
||||
"compiler.lib.generators.*");
|
||||
|
||||
// The preferred vector shape is the largest possible vector size.
|
||||
final int maxVecByteSize = VectorShape.preferredShape().vectorBitSize() / 8;
|
||||
|
||||
final List<TemplateToken> tests = new ArrayList<>();
|
||||
// Add tests only for the vector shapes that
|
||||
tests.addAll(CodeGenerationDataNameType.VECTOR_VECTOR_TYPES
|
||||
.stream()
|
||||
.filter(vec -> vec.byteSize() <= maxVecByteSize && vec.elementType instanceof PrimitiveType)
|
||||
.map(vec -> new TestPerShape(vec).generate())
|
||||
.collect(Collectors.toList()));
|
||||
tests.add(PrimitiveType.generateLibraryRNG());
|
||||
|
||||
return TestFrameworkClass.render(PACKAGE, CLASS_NAME, imports, comp.getEscapedClassPathOfCompiledClasses(), tests);
|
||||
}
|
||||
|
||||
enum Operation {
|
||||
STORE_SCATTER,
|
||||
STORE_MASK,
|
||||
STORE_SCATTER_MASK,
|
||||
STORE_VECTOR_AFTER_SCATTER,
|
||||
RANDOM
|
||||
}
|
||||
|
||||
record TestPerShape(VectorType.Vector vec) {
|
||||
TemplateToken generate() {
|
||||
final String testName = vec.elementType.boxedTypeName() + vec.length;
|
||||
|
||||
// Select the index where we set the mask to false. The index is biased to
|
||||
// zero, as the original bug only triggered with the first element.
|
||||
final int idx = RANDOM.nextBoolean() ? RANDOM.nextInt(0, vec.length) : 0;
|
||||
|
||||
var irVerification = Template.make("op", "arraySize", (Operation op, Integer arraySize) -> {
|
||||
// No IR-verification for random test cases.
|
||||
if (op == Operation.RANDOM) {
|
||||
return scope("");
|
||||
}
|
||||
|
||||
final PrimitiveType pty = (PrimitiveType) vec.elementType;
|
||||
final String ptyIR = pty.abbrev().equals("S") ? "C" : pty.abbrev();
|
||||
|
||||
// Verify that the method contains two VectorStore{Masked|Scatter} nodes.
|
||||
var opVerification = Template.make(() -> {
|
||||
if (vec.length <= 2) {
|
||||
return scope(" // No Vector nodes are emitted for vectors of length 2 or shorter.\n");
|
||||
}
|
||||
|
||||
if (Set.of(Operation.STORE_SCATTER, Operation.STORE_SCATTER_MASK, Operation.STORE_VECTOR_AFTER_SCATTER).contains(op) &&
|
||||
vec.elementType.byteSize() < 4) {
|
||||
return scope(" // StoreVectorScatter is not emitted for vectors of subword types.\n");
|
||||
}
|
||||
|
||||
final String opIR = switch (op) {
|
||||
case STORE_SCATTER -> "STORE_VECTOR_SCATTER";
|
||||
case STORE_MASK -> "STORE_VECTOR_MASKED";
|
||||
case STORE_SCATTER_MASK -> "STORE_VECTOR_SCATTER_MASKED";
|
||||
case STORE_VECTOR_AFTER_SCATTER -> "STORE_VECTOR_SCATTER";
|
||||
case RANDOM -> "";
|
||||
};
|
||||
|
||||
final int numMatches = switch (op) {
|
||||
case STORE_VECTOR_AFTER_SCATTER -> 1;
|
||||
default -> 2;
|
||||
};
|
||||
|
||||
return scope(
|
||||
let("opIR", opIR),
|
||||
let("matches", numMatches),
|
||||
"""
|
||||
@IR(counts = {IRNode.#{opIR}, "=#{matches}"},
|
||||
applyIfCPUFeatureOr = {"avx512", "true", "sve", "true"})
|
||||
"""
|
||||
);
|
||||
});
|
||||
|
||||
return scope(
|
||||
let("pty", vec.elementType.name()),
|
||||
let("ptyIR", ptyIR),
|
||||
let("idx", idx),
|
||||
switch (op) {
|
||||
case STORE_MASK, STORE_SCATTER_MASK ->
|
||||
// For masked operations, depending on the generated mask and index map C2 does not manage to elide a branch from
|
||||
// if (mask.allTrue()) {
|
||||
// intoArray(a, offset);
|
||||
// } else {
|
||||
// intoArray(a, offset, mask);
|
||||
// }
|
||||
// leading to both stores of the diamond being live. This is highly profile dependent and cannot be predicted.
|
||||
"""
|
||||
@IR(counts = {IRNode.START + "Store#{ptyIR}" + IRNode.MID + "(Memory: @aryptr:#{pty}\\\\[int:#{arraySize}\\\\]).*(:NotNull:exact\\\\[\\\\d+\\\\]).*" + IRNode.END, ">=1",
|
||||
IRNode.START + "Store#{ptyIR}" + IRNode.MID + "(Memory: @aryptr:#{pty}\\\\[int:#{arraySize}\\\\]).*(:NotNull:exact\\\\[\\\\d+\\\\]).*" + IRNode.END, "<=2"},
|
||||
applyIfCPUFeatureOr = {"avx512", "true", "sve", "true"},
|
||||
phase = CompilePhase.BEFORE_MATCHING)
|
||||
""";
|
||||
case STORE_SCATTER ->
|
||||
"""
|
||||
@IR(counts = {IRNode.START + "Store#{ptyIR}" + IRNode.MID + "(Memory: @aryptr:#{pty}\\\\[int:#{arraySize}\\\\]).*(:NotNull:exact\\\\[\\\\d+\\\\]).*" + IRNode.END, "=1"},
|
||||
applyIfCPUFeatureOr = {"avx512", "true", "sve", "true"},
|
||||
phase = CompilePhase.BEFORE_MATCHING)
|
||||
""";
|
||||
case STORE_VECTOR_AFTER_SCATTER, RANDOM -> "";
|
||||
},
|
||||
opVerification.asToken()
|
||||
);
|
||||
});
|
||||
|
||||
var testBody = Template.make("testCaseRandom", "op", "arraySize", (Random testCaseRandom, Operation op, Integer arraySize) -> {
|
||||
if (op == Operation.RANDOM) {
|
||||
return scope(generateRandomTest(testCaseRandom).asToken());
|
||||
}
|
||||
|
||||
var maskGeneration = Template.make(() -> scope(
|
||||
let("idx", idx),
|
||||
let("boxedTy", vec.elementType.boxedTypeName()),
|
||||
let("species", vec.speciesName),
|
||||
" VectorMask<#boxedTy> mask = VectorMask.fromLong(#species, ",
|
||||
testCaseRandom.nextInt(0, 10) == 0 ? testCaseRandom.nextLong() : "-1 - (1 << #idx)",
|
||||
");\n"
|
||||
));
|
||||
|
||||
var indexMapGeneration = Template.make(() -> {
|
||||
// For the scatter tests, the array is one larger than the number of lanes so we can
|
||||
// map indices starting at idx to the next index, omitting idx.
|
||||
int[] indexMap = IntStream.range(0, vec.length)
|
||||
.map(i -> i >= idx ? i + 1 : i)
|
||||
.toArray();
|
||||
String indexMapStr = Arrays.toString(indexMap)
|
||||
.replace('[', '{')
|
||||
.replace(']', '}');
|
||||
return scope(
|
||||
let("idx", idx),
|
||||
let("len", vec.length),
|
||||
let("idxMap", indexMapStr),
|
||||
"""
|
||||
final int[] indexMap = #{idxMap};
|
||||
"""
|
||||
);
|
||||
});
|
||||
|
||||
var generation = switch (op) {
|
||||
case STORE_SCATTER, STORE_VECTOR_AFTER_SCATTER -> indexMapGeneration.asToken();
|
||||
case STORE_MASK -> maskGeneration.asToken();
|
||||
case STORE_SCATTER_MASK ->
|
||||
Template.make(() -> scope(indexMapGeneration.asToken(), maskGeneration.asToken())).asToken();
|
||||
case RANDOM -> throw new RuntimeException("unreachable");
|
||||
};
|
||||
|
||||
var initStore = switch (op) {
|
||||
case STORE_SCATTER, STORE_VECTOR_AFTER_SCATTER -> " v.intoArray(a, 0, indexMap, 0);\n";
|
||||
case STORE_MASK -> " v.intoArray(a, 0, mask);\n";
|
||||
case STORE_SCATTER_MASK -> " v.intoArray(a, 0, indexMap, 0, mask);\n";
|
||||
case RANDOM -> throw new RuntimeException("unreachable");
|
||||
};
|
||||
|
||||
var keepStore = switch (op) {
|
||||
case STORE_MASK, STORE_SCATTER, STORE_SCATTER_MASK -> " a[#idx] = arrVal;\n";
|
||||
case STORE_VECTOR_AFTER_SCATTER -> " v.intoArray(a, 0);\n";
|
||||
case RANDOM -> throw new RuntimeException("unreachable");
|
||||
};
|
||||
|
||||
var holeStore = switch (op) {
|
||||
case STORE_SCATTER -> " v.intoArray(a, 0, indexMap, 0);\n";
|
||||
case STORE_MASK -> " v.intoArray(a, 0, mask);\n";
|
||||
case STORE_SCATTER_MASK -> " v.intoArray(a, 0, indexMap, 0, mask);\n";
|
||||
case STORE_VECTOR_AFTER_SCATTER -> "";
|
||||
case RANDOM -> throw new RuntimeException("unreachable");
|
||||
};
|
||||
|
||||
return scope(
|
||||
let("pty", vec.elementType.name()),
|
||||
let("vecTy", vec.name()),
|
||||
let("species", vec.speciesName),
|
||||
let("idx", idx),
|
||||
"""
|
||||
#pty[] a = new #pty[#arraySize];
|
||||
""",
|
||||
generation,
|
||||
"""
|
||||
|
||||
var v = #vecTy.broadcast(#species, broadcastVal);
|
||||
""",
|
||||
initStore,
|
||||
keepStore,
|
||||
holeStore,
|
||||
"""
|
||||
return a;
|
||||
"""
|
||||
);
|
||||
});
|
||||
|
||||
var testCase = Template.make("op", (Operation op) -> {
|
||||
// To get the same test body twice, use a new random instance for each generated test body with a seed fixed per test case.
|
||||
final int testCaseSeed = RANDOM.nextInt();
|
||||
|
||||
String testCaseName = testName + switch (op) {
|
||||
case STORE_SCATTER -> "Scatter";
|
||||
case STORE_MASK -> "Mask";
|
||||
case STORE_SCATTER_MASK -> "ScatterMask";
|
||||
case STORE_VECTOR_AFTER_SCATTER -> "VectorAfterScatter";
|
||||
case RANDOM -> "Random";
|
||||
};
|
||||
|
||||
// The array size needs to be one larger than the number of lanes for scatter tests, so we can not write to one element.
|
||||
final int arraySize = switch (op) {
|
||||
case STORE_SCATTER, STORE_SCATTER_MASK, STORE_VECTOR_AFTER_SCATTER -> vec.length + 1;
|
||||
case STORE_MASK, RANDOM -> vec.length;
|
||||
};
|
||||
|
||||
return scope(
|
||||
let("pty", vec.elementType.name()),
|
||||
let("testCaseName", testCaseName),
|
||||
let("boxedTy", vec.elementType.boxedTypeName()),
|
||||
let("vecTy", vec.name()),
|
||||
let("lanes", vec.length),
|
||||
let("species", vec.speciesName),
|
||||
let("idx", idx),
|
||||
let("rngCall", vec.elementType.callLibraryRNG()),
|
||||
let("broadcastVal", vec.elementType.con()),
|
||||
let("arrVal", vec.elementType.con()),
|
||||
"""
|
||||
@Run(test = "test#{testCaseName}")
|
||||
@Warmup(15_000)
|
||||
static void run#{testCaseName}(RunInfo info) {
|
||||
final #pty broadcastVal = #broadcastVal;
|
||||
final #pty arrVal = #arrVal;
|
||||
final #pty[] compiledResult = test#{testCaseName}(broadcastVal, arrVal);
|
||||
|
||||
if (!info.isWarmUp()) {
|
||||
final #pty[] interpreterResult = reference#{testCaseName}(broadcastVal, arrVal);
|
||||
if (!Arrays.equals(interpreterResult, compiledResult)) {
|
||||
throw new RuntimeException("wrong result for test${testCaseName}:\\n" +
|
||||
" interpreter result: " + Arrays.toString(interpreterResult) + "\\n" +
|
||||
" compiled result: " + Arrays.toString(compiledResult));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
""",
|
||||
irVerification.asToken(op, arraySize),
|
||||
"""
|
||||
static #pty[] test#{testCaseName}(#pty broadcastVal, #pty arrVal) {
|
||||
""",
|
||||
testBody.asToken(new Random(testCaseSeed), op, arraySize),
|
||||
"""
|
||||
}
|
||||
|
||||
@DontCompile
|
||||
static #pty[] reference#{testCaseName}(#pty broadcastVal, #pty arrVal) {
|
||||
""",
|
||||
testBody.asToken(new Random(testCaseSeed), op, arraySize),
|
||||
"""
|
||||
}
|
||||
|
||||
"""
|
||||
);
|
||||
});
|
||||
|
||||
return Template.make(() -> scope(
|
||||
Stream.of(Operation.class.getEnumConstants())
|
||||
.map(op -> testCase.asToken(op))
|
||||
.toList()
|
||||
)).asToken();
|
||||
}
|
||||
|
||||
Template.ZeroArgs generateRandomTest(Random testCaseRandom) {
|
||||
final int arraySize = testCaseRandom.nextInt(vec.length + 1, 5 * vec.length);
|
||||
var maskHook = new Hook("MaskHook");
|
||||
var genRandomMask = Template.make("maskName", "vecTy", (String maskName, VectorType.Vector vecTy) -> scope(
|
||||
let("boxedTy", vecTy.elementType.boxedTypeName()),
|
||||
let("species", vecTy.speciesName),
|
||||
let("maskVal", testCaseRandom.nextLong()),
|
||||
"""
|
||||
VectorMask<#{boxedTy}> #maskName = VectorMask.fromLong(#species, #maskVal);
|
||||
"""
|
||||
));
|
||||
var genRandomIdxMap = Template.make("mapName", "maxIdx", "len", (String mapName, Integer maxIdx, Integer len) -> {
|
||||
ArrayList<Integer> possibleIndices = new ArrayList(IntStream.range(0, maxIdx).boxed().toList());
|
||||
Collections.shuffle(possibleIndices, testCaseRandom);
|
||||
return scope(
|
||||
let("map", String.join(", ", possibleIndices.stream().limit(vec.length).map(i -> i.toString()).toList())),
|
||||
"""
|
||||
int[] #mapName = { #map };
|
||||
"""
|
||||
);
|
||||
});
|
||||
var initialStore = Template.make(() -> {
|
||||
final int offset = testCaseRandom.nextInt(0, 4) == 0 ? testCaseRandom.nextInt(0, arraySize - vec.length) : 0;
|
||||
return scope(
|
||||
let("offset", offset),
|
||||
switch (testCaseRandom.nextInt(0,4)) {
|
||||
case 0 -> scope(
|
||||
"""
|
||||
v.intoArray(a, #offset);
|
||||
"""
|
||||
);
|
||||
case 1 -> scope(
|
||||
maskHook.insert(genRandomMask.asToken("initMask", vec)),
|
||||
"""
|
||||
v.intoArray(a, #offset, initMask);
|
||||
"""
|
||||
);
|
||||
case 2 -> scope(
|
||||
maskHook.insert(genRandomIdxMap.asToken("initMap", arraySize - offset, vec.length)),
|
||||
"""
|
||||
v.intoArray(a, #offset, initMap, 0);
|
||||
"""
|
||||
);
|
||||
case 3 -> scope(
|
||||
maskHook.insert(scope(
|
||||
genRandomIdxMap.asToken("initMap", arraySize - offset, vec.length),
|
||||
genRandomMask.asToken("initMask", vec)
|
||||
)),
|
||||
"""
|
||||
v.intoArray(a, #offset, initMap, 0, initMask);
|
||||
"""
|
||||
);
|
||||
default -> throw new RuntimeException("unreachable");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
var storeThatShouldNotBeLost = Template.make(() -> {
|
||||
final int selection = testCaseRandom.nextInt(0,6);
|
||||
final VectorType.Vector narrowerVector = CodeGenerationDataNameType.VECTOR_VECTOR_TYPES
|
||||
.stream()
|
||||
.filter(v -> v.elementType == vec.elementType && v.length <= vec.length)
|
||||
// Flip a coin on each reduction step to pick a random element.
|
||||
.reduce(null, (l, r) -> l == null ? r : (testCaseRandom.nextBoolean() ? l : r));
|
||||
final int offset = testCaseRandom.nextInt(0, 4) == 0 ? testCaseRandom.nextInt(0, arraySize - narrowerVector.length) : 0;
|
||||
return scope(
|
||||
let("offset", offset),
|
||||
switch (selection) {
|
||||
case 0, 1, 2, 3 -> scope(
|
||||
let("vecTy", narrowerVector),
|
||||
let("species", narrowerVector.speciesName),
|
||||
"""
|
||||
var vKeep = #vecTy.broadcast(#species, arrVal);
|
||||
"""
|
||||
);
|
||||
default -> "";
|
||||
},
|
||||
switch (selection) {
|
||||
case 0 -> scope(
|
||||
"""
|
||||
vKeep.intoArray(a, #offset);
|
||||
"""
|
||||
);
|
||||
case 1 -> scope(
|
||||
maskHook.insert(genRandomMask.asToken("keepMask", narrowerVector)),
|
||||
"""
|
||||
vKeep.intoArray(a, #offset, keepMask);
|
||||
"""
|
||||
);
|
||||
case 2 -> scope(
|
||||
maskHook.insert(genRandomIdxMap.asToken("keepMap", arraySize - offset, narrowerVector.length)),
|
||||
"""
|
||||
vKeep.intoArray(a, #offset, keepMap, 0);
|
||||
"""
|
||||
);
|
||||
case 3 -> scope(
|
||||
maskHook.insert(scope(
|
||||
genRandomIdxMap.asToken("keepMap", arraySize - offset, narrowerVector.length),
|
||||
genRandomMask.asToken("keepMask", narrowerVector)
|
||||
)),
|
||||
"""
|
||||
vKeep.intoArray(a, #offset, keepMap, 0, keepMask);
|
||||
"""
|
||||
);
|
||||
case 4 -> scope(
|
||||
let("idx", testCaseRandom.nextInt(0, arraySize)),
|
||||
"""
|
||||
a[#idx] = arrVal;
|
||||
"""
|
||||
);
|
||||
case 5 -> scope(
|
||||
let("idx", testCaseRandom.nextInt(0, arraySize - vec.length)),
|
||||
let("len", testCaseRandom.nextInt(1, vec.length + 1)),
|
||||
"""
|
||||
Arrays.fill(a, #idx, #idx + #len, arrVal);
|
||||
"""
|
||||
);
|
||||
default -> throw new RuntimeException("unreachable");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
var storeWithHole = Template.make(() -> {
|
||||
final int offset = testCaseRandom.nextInt(0, 4) == 0 ? testCaseRandom.nextInt(0, arraySize - vec.length) : 0;
|
||||
return scope(
|
||||
let("offset", offset),
|
||||
switch (testCaseRandom.nextInt(0,3)) {
|
||||
case 0 -> scope(
|
||||
maskHook.insert(genRandomMask.asToken("holeMask", vec)),
|
||||
"""
|
||||
v.intoArray(a, #offset, holeMask);
|
||||
"""
|
||||
);
|
||||
case 1 -> scope(
|
||||
maskHook.insert(genRandomIdxMap.asToken("holeMap", arraySize - offset, vec.length)),
|
||||
"""
|
||||
v.intoArray(a, #offset, holeMap, 0);
|
||||
"""
|
||||
);
|
||||
case 2 -> scope(
|
||||
maskHook.insert(scope(
|
||||
genRandomIdxMap.asToken("holeMap", arraySize - offset, vec.length),
|
||||
genRandomMask.asToken("holeMask", vec)
|
||||
)),
|
||||
"""
|
||||
v.intoArray(a, #offset, holeMap, 0, holeMask);
|
||||
"""
|
||||
);
|
||||
default -> throw new RuntimeException("unreachable");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return Template.make(() -> scope(
|
||||
let("pty", vec.elementType.name()),
|
||||
let("arraySize", arraySize),
|
||||
"""
|
||||
#pty[] a = new #pty[#arraySize];
|
||||
|
||||
""",
|
||||
maskHook.anchor(scope(
|
||||
// The code for generating masks and index maps goes here.
|
||||
let("vecTy", vec.name()),
|
||||
let("species", vec.speciesName),
|
||||
"""
|
||||
var v = #vecTy.broadcast(#species, broadcastVal);
|
||||
""",
|
||||
initialStore.asToken(),
|
||||
storeThatShouldNotBeLost.asToken(),
|
||||
storeWithHole.asToken()
|
||||
)),
|
||||
"""
|
||||
return a;
|
||||
"""
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user