Add constant == uniform facts. (#2660)
Adds a new (and first) kind of fact to the fact manager, which is that
a specific uniform value is guaranteed to be equal to a specific
constant. The point of this is that such information (if known to be
true by some external source) can be used by spirv-fuzz to transform
the module in interesting ways that a static compiler cannot reverse
via compile-time analysis.
This change introduces protobuf messages for the fact, and adds
capabilities to the fact manager to store this kind of fact and
provide information about it.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index c1c0c1f..e5e48a8 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -30,8 +30,8 @@
fuzzer.h
fuzzer_context.h
fuzzer_pass.h
- fuzzer_pass_add_useful_constructs.h
fuzzer_pass_add_dead_breaks.h
+ fuzzer_pass_add_useful_constructs.h
fuzzer_pass_permute_blocks.h
fuzzer_pass_split_blocks.h
fuzzer_util.h
@@ -48,6 +48,7 @@
transformation_move_block_down.h
transformation_replace_boolean_constant_with_constant_binary.h
transformation_split_block.h
+ uniform_buffer_element_descriptor.h
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
fact_manager.cpp
@@ -71,6 +72,7 @@
transformation_move_block_down.cpp
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_split_block.cpp
+ uniform_buffer_element_descriptor.cpp
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
)
diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp
index 1c19287..3716941 100644
--- a/source/fuzz/fact_manager.cpp
+++ b/source/fuzz/fact_manager.cpp
@@ -12,15 +12,242 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <utility>
-
#include "source/fuzz/fact_manager.h"
+
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
-FactManager::FactManager() = default;
+// The purpose of this struct is to group the fields and data used to represent
+// facts about uniform constants.
+struct FactManager::ConstantUniformFacts {
+ // See method in FactManager which delegates to this method.
+ bool AddFact(const protobufs::FactConstantUniform& fact,
+ opt::IRContext* context);
+
+ // See method in FactManager which delegates to this method.
+ std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
+ opt::IRContext* ir_context, uint32_t type_id) const;
+
+ // See method in FactManager which delegates to this method.
+ const std::vector<protobufs::UniformBufferElementDescriptor>
+ GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
+ uint32_t constant_id) const;
+
+ // See method in FactManager which delegates to this method.
+ uint32_t GetConstantFromUniformDescriptor(
+ opt::IRContext* context,
+ const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
+ const;
+
+ // See method in FactManager which delegates to this method.
+ std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const;
+
+ // Returns true if and only if the words associated with
+ // |constant_instruction| exactly match the words for the constant associated
+ // with |constant_uniform_fact|.
+ bool DataMatches(
+ const opt::Instruction& constant_instruction,
+ const protobufs::FactConstantUniform& constant_uniform_fact) const;
+
+ // Yields the constant words associated with |constant_uniform_fact|.
+ std::vector<uint32_t> GetConstantWords(
+ const protobufs::FactConstantUniform& constant_uniform_fact) const;
+
+ // Yields the id of a constant of type |type_id| whose data matches the
+ // constant data in |constant_uniform_fact|, or 0 if no such constant is
+ // declared.
+ uint32_t GetConstantId(
+ opt::IRContext* context,
+ const protobufs::FactConstantUniform& constant_uniform_fact,
+ uint32_t type_id) const;
+
+ std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>
+ facts_and_type_ids;
+};
+
+uint32_t FactManager::ConstantUniformFacts::GetConstantId(
+ opt::IRContext* context,
+ const protobufs::FactConstantUniform& constant_uniform_fact,
+ uint32_t type_id) const {
+ auto type = context->get_type_mgr()->GetType(type_id);
+ assert(type != nullptr && "Unknown type id.");
+ auto constant = context->get_constant_mgr()->GetConstant(
+ type, GetConstantWords(constant_uniform_fact));
+ return context->get_constant_mgr()->FindDeclaredConstant(constant, type_id);
+}
+
+std::vector<uint32_t> FactManager::ConstantUniformFacts::GetConstantWords(
+ const protobufs::FactConstantUniform& constant_uniform_fact) const {
+ std::vector<uint32_t> result;
+ for (auto constant_word : constant_uniform_fact.constant_word()) {
+ result.push_back(constant_word);
+ }
+ return result;
+}
+
+bool FactManager::ConstantUniformFacts::DataMatches(
+ const opt::Instruction& constant_instruction,
+ const protobufs::FactConstantUniform& constant_uniform_fact) const {
+ assert(constant_instruction.opcode() == SpvOpConstant);
+ std::vector<uint32_t> data_in_constant;
+ for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) {
+ data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i));
+ }
+ return data_in_constant == GetConstantWords(constant_uniform_fact);
+}
+
+std::vector<uint32_t>
+FactManager::ConstantUniformFacts::GetConstantsAvailableFromUniformsForType(
+ opt::IRContext* ir_context, uint32_t type_id) const {
+ std::vector<uint32_t> result;
+ std::set<uint32_t> already_seen;
+ for (auto& fact_and_type_id : facts_and_type_ids) {
+ if (fact_and_type_id.second != type_id) {
+ continue;
+ }
+ if (auto constant_id =
+ GetConstantId(ir_context, fact_and_type_id.first, type_id)) {
+ if (already_seen.find(constant_id) == already_seen.end()) {
+ result.push_back(constant_id);
+ already_seen.insert(constant_id);
+ }
+ }
+ }
+ return result;
+}
+
+const std::vector<protobufs::UniformBufferElementDescriptor>
+FactManager::ConstantUniformFacts::GetUniformDescriptorsForConstant(
+ opt::IRContext* ir_context, uint32_t constant_id) const {
+ std::vector<protobufs::UniformBufferElementDescriptor> result;
+ auto constant_inst = ir_context->get_def_use_mgr()->GetDef(constant_id);
+ assert(constant_inst->opcode() == SpvOpConstant &&
+ "The given id must be that of a constant");
+ auto type_id = constant_inst->type_id();
+ for (auto& fact_and_type_id : facts_and_type_ids) {
+ if (fact_and_type_id.second != type_id) {
+ continue;
+ }
+ if (DataMatches(*constant_inst, fact_and_type_id.first)) {
+ result.emplace_back(
+ fact_and_type_id.first.uniform_buffer_element_descriptor());
+ }
+ }
+ return result;
+}
+
+uint32_t FactManager::ConstantUniformFacts::GetConstantFromUniformDescriptor(
+ opt::IRContext* context,
+ const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
+ // Consider each fact.
+ for (auto& fact_and_type : facts_and_type_ids) {
+ // Check whether the uniform descriptor associated with the fact matches
+ // |uniform_descriptor|.
+ if (UniformBufferElementDescriptorEquals()(
+ &uniform_descriptor,
+ &fact_and_type.first.uniform_buffer_element_descriptor())) {
+ return GetConstantId(context, fact_and_type.first, fact_and_type.second);
+ }
+ }
+ // No fact associated with the given uniform descriptor was found.
+ return 0;
+}
+
+std::vector<uint32_t>
+FactManager::ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown()
+ const {
+ std::vector<uint32_t> result;
+ for (auto& fact_and_type : facts_and_type_ids) {
+ if (std::find(result.begin(), result.end(), fact_and_type.second) ==
+ result.end()) {
+ result.push_back(fact_and_type.second);
+ }
+ }
+ return result;
+}
+
+bool FactManager::ConstantUniformFacts::AddFact(
+ const protobufs::FactConstantUniform& fact, opt::IRContext* context) {
+ auto should_be_uniform_variable = context->get_def_use_mgr()->GetDef(
+ fact.uniform_buffer_element_descriptor().uniform_variable_id());
+ if (!should_be_uniform_variable) {
+ return false;
+ }
+ if (SpvOpVariable != should_be_uniform_variable->opcode()) {
+ return false;
+ }
+ if (SpvStorageClassUniform !=
+ should_be_uniform_variable->GetSingleWordInOperand(0)) {
+ return false;
+ }
+ auto should_be_uniform_pointer_type =
+ context->get_type_mgr()->GetType(should_be_uniform_variable->type_id());
+ if (!should_be_uniform_pointer_type->AsPointer()) {
+ return false;
+ }
+ if (should_be_uniform_pointer_type->AsPointer()->storage_class() !=
+ SpvStorageClassUniform) {
+ return false;
+ }
+ auto should_be_uniform_pointer_instruction =
+ context->get_def_use_mgr()->GetDef(should_be_uniform_variable->type_id());
+ auto element_type =
+ should_be_uniform_pointer_instruction->GetSingleWordInOperand(1);
+
+ for (auto index : fact.uniform_buffer_element_descriptor().index()) {
+ auto should_be_composite_type =
+ context->get_def_use_mgr()->GetDef(element_type);
+ if (SpvOpTypeStruct == should_be_composite_type->opcode()) {
+ if (index >= should_be_composite_type->NumInOperands()) {
+ return false;
+ }
+ element_type = should_be_composite_type->GetSingleWordInOperand(index);
+ } else if (SpvOpTypeArray == should_be_composite_type->opcode()) {
+ auto array_length_constant =
+ context->get_constant_mgr()
+ ->GetConstantFromInst(context->get_def_use_mgr()->GetDef(
+ should_be_composite_type->GetSingleWordInOperand(1)))
+ ->AsIntConstant();
+ if (array_length_constant->words().size() != 1) {
+ return false;
+ }
+ auto array_length = array_length_constant->GetU32();
+ if (index >= array_length) {
+ return false;
+ }
+ element_type = should_be_composite_type->GetSingleWordInOperand(0);
+ } else if (SpvOpTypeVector == should_be_composite_type->opcode()) {
+ auto vector_length = should_be_composite_type->GetSingleWordInOperand(1);
+ if (index >= vector_length) {
+ return false;
+ }
+ element_type = should_be_composite_type->GetSingleWordInOperand(0);
+ } else {
+ return false;
+ }
+ }
+ auto final_element_type = context->get_type_mgr()->GetType(element_type);
+ if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) {
+ return false;
+ }
+ auto width = final_element_type->AsFloat()
+ ? final_element_type->AsFloat()->width()
+ : final_element_type->AsInteger()->width();
+ auto required_words = (width + 32 - 1) / 32;
+ if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) {
+ return false;
+ }
+ facts_and_type_ids.emplace_back(
+ std::pair<protobufs::FactConstantUniform, uint32_t>(fact, element_type));
+ return true;
+}
+
+FactManager::FactManager() {
+ uniform_constant_facts_ = MakeUnique<ConstantUniformFacts>();
+}
FactManager::~FactManager() = default;
@@ -36,11 +263,46 @@
return true;
}
-bool FactManager::AddFact(const spvtools::fuzz::protobufs::Fact&,
- spvtools::opt::IRContext*) {
- assert(0 && "No facts are yet supported.");
+bool FactManager::AddFact(const spvtools::fuzz::protobufs::Fact& fact,
+ spvtools::opt::IRContext* context) {
+ assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact &&
+ "Right now this is the only fact.");
+ if (!uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
+ context)) {
+ return false;
+ }
return true;
}
+std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
+ opt::IRContext* ir_context, uint32_t type_id) const {
+ return uniform_constant_facts_->GetConstantsAvailableFromUniformsForType(
+ ir_context, type_id);
+}
+
+const std::vector<protobufs::UniformBufferElementDescriptor>
+FactManager::GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
+ uint32_t constant_id) const {
+ return uniform_constant_facts_->GetUniformDescriptorsForConstant(ir_context,
+ constant_id);
+}
+
+uint32_t FactManager::GetConstantFromUniformDescriptor(
+ opt::IRContext* context,
+ const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
+ return uniform_constant_facts_->GetConstantFromUniformDescriptor(
+ context, uniform_descriptor);
+}
+
+std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown()
+ const {
+ return uniform_constant_facts_->GetTypesForWhichUniformValuesAreKnown();
+}
+
+const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+FactManager::GetConstantUniformFactsAndTypes() const {
+ return uniform_constant_facts_->facts_and_type_ids;
+}
+
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/fact_manager.h b/source/fuzz/fact_manager.h
index 49b6d6d..2eee9ab 100644
--- a/source/fuzz/fact_manager.h
+++ b/source/fuzz/fact_manager.h
@@ -15,7 +15,9 @@
#ifndef SOURCE_FUZZ_FACT_MANAGER_H_
#define SOURCE_FUZZ_FACT_MANAGER_H_
+#include <memory>
#include <utility>
+#include <vector>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/opt/constants.h"
@@ -45,6 +47,60 @@
// Adds |fact| to the fact manager, checking it for validity with respect to
// |context|. Returns true if and only if the fact is valid.
bool AddFact(const protobufs::Fact& fact, opt::IRContext* context);
+
+ // The fact manager will ultimately be responsible for managing a few distinct
+ // categories of facts. In principle there could be different fact managers
+ // for each kind of fact, but in practice providing one 'go to' place for
+ // facts will be convenient. To keep some separation, the public methods of
+ // the fact manager should be grouped according to the kind of fact to which
+ // they relate. At present we only have one kind of fact: facts about
+ // uniform variables.
+
+ //==============================
+ // Querying facts about uniform constants
+
+ // Provides the distinct type ids for which at least one "constant ==
+ // uniform element" fact is known.
+ std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const;
+
+ // Provides distinct constant ids with type |type_id| for which at least one
+ // "constant == uniform element" fact is known. If multiple identically-
+ // valued constants are relevant, only one will appear in the sequence.
+ std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
+ opt::IRContext* ir_context, uint32_t type_id) const;
+
+ // Provides details of all uniform elements that are known to be equal to the
+ // constant associated with |constant_id| in |ir_context|.
+ const std::vector<protobufs::UniformBufferElementDescriptor>
+ GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
+ uint32_t constant_id) const;
+
+ // Returns the id of a constant whose value is known to match that of
+ // |uniform_descriptor|, and whose type matches the type of the uniform
+ // element. If multiple such constant is exist, the one that is returned
+ // is arbitrary. Returns 0 if no such constant id exists.
+ uint32_t GetConstantFromUniformDescriptor(
+ opt::IRContext* context,
+ const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
+ const;
+
+ // Returns all "constant == uniform element" facts known to the fact
+ // manager, pairing each fact with id of the type that is associated with
+ // both the constant and the uniform element.
+ const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+ GetConstantUniformFactsAndTypes() const;
+
+ // End of uniform constant facts
+ //==============================
+
+ private:
+ // For each distinct kind of fact to be managed, we use a separate opaque
+ // struct type.
+
+ struct ConstantUniformFacts; // Opaque struct for holding data about uniform
+ // buffer elements.
+ std::unique_ptr<ConstantUniformFacts>
+ uniform_constant_facts_; // Unique pointer to internal data.
};
} // namespace fuzz
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index f00c518..4bf8252 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -57,12 +57,63 @@
}
+message UniformBufferElementDescriptor {
+
+ // Represents a data element inside a uniform buffer. The element is
+ // specified via (a) the result id of a uniform variable in which the element
+ // is contained, and (b) a series of indices that need to be followed to get
+ // to the element (via fields and array/vector indices).
+ //
+ // Example: suppose %42 is the id of a uniform variable, and that the uniform
+ // variable has the following type (using GLSL-like syntax):
+ //
+ // struct S {
+ // float f;
+ // vec3 g;
+ // int4 h[10];
+ // };
+ //
+ // Then:
+ // - 42[0] describes the 'f' field.
+ // - 42[1,1] describes the y component of the 'g' field.
+ // - 42[2,7,3] describes the w component of element 7 of the 'h' field
+
+ // The result id of a uniform variable.
+ uint32 uniform_variable_id = 1;
+
+ // An ordered sequence of indices through composite structures in the
+ // uniform buffer.
+ repeated uint32 index = 2;
+
+}
+
message FactSequence {
repeated Fact fact = 1;
}
message Fact {
- // Currently there are no facts.
+ oneof fact {
+ // Order the fact options by numeric id (rather than alphabetically).
+ FactConstantUniform constant_uniform_fact = 1;
+ }
+}
+
+// Keep fact message types in alphabetical order:
+
+message FactConstantUniform {
+
+ // Records the fact that a uniform buffer element is guaranteed to be equal
+ // to a particular constant value. spirv-fuzz can use such guarantees to
+ // obfuscate code, e.g. to manufacture an expression that will (due to the
+ // guarantee) evaluate to a particular value at runtime but in a manner that
+ // cannot be predicted at compile-time.
+
+ // An element of a uniform buffer
+ UniformBufferElementDescriptor uniform_buffer_element_descriptor = 1;
+
+ // The words of the associated constant
+ repeated uint32 constant_word = 2;
+
}
message TransformationSequence {
diff --git a/source/fuzz/uniform_buffer_element_descriptor.cpp b/source/fuzz/uniform_buffer_element_descriptor.cpp
new file mode 100644
index 0000000..cd840d3
--- /dev/null
+++ b/source/fuzz/uniform_buffer_element_descriptor.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor(
+ uint32_t uniform_variable_id, std::vector<uint32_t>&& indices) {
+ protobufs::UniformBufferElementDescriptor result;
+ result.set_uniform_variable_id(uniform_variable_id);
+ for (auto index : indices) {
+ result.add_index(index);
+ }
+ return result;
+}
+
+bool UniformBufferElementDescriptorEquals::operator()(
+ const protobufs::UniformBufferElementDescriptor* first,
+ const protobufs::UniformBufferElementDescriptor* second) const {
+ return first->uniform_variable_id() == second->uniform_variable_id() &&
+ std::equal(first->index().begin(), first->index().end(),
+ second->index().begin());
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/uniform_buffer_element_descriptor.h b/source/fuzz/uniform_buffer_element_descriptor.h
new file mode 100644
index 0000000..b2e5962
--- /dev/null
+++ b/source/fuzz/uniform_buffer_element_descriptor.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
+#define SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Factory method to create a uniform buffer element descriptor message from an
+// id and list of indices.
+protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor(
+ uint32_t uniform_variable_id, std::vector<uint32_t>&& indices);
+
+// Equality function for uniform buffer element descriptors.
+struct UniformBufferElementDescriptorEquals {
+ bool operator()(
+ const protobufs::UniformBufferElementDescriptor* first,
+ const protobufs::UniformBufferElementDescriptor* second) const;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // #define SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 88635c7..d4feaa7 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -18,6 +18,7 @@
fuzz_test_util.h
fuzzer_test.cpp
+ fact_manager_test.cpp
fuzz_test_util.cpp
transformation_add_constant_boolean_test.cpp
transformation_add_constant_scalar_test.cpp
diff --git a/test/fuzz/fact_manager_test.cpp b/test/fuzz/fact_manager_test.cpp
new file mode 100644
index 0000000..0209a60
--- /dev/null
+++ b/test/fuzz/fact_manager_test.cpp
@@ -0,0 +1,534 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+using opt::analysis::BoolConstant;
+using opt::analysis::FloatConstant;
+using opt::analysis::IntConstant;
+using opt::analysis::ScalarConstant;
+
+using opt::analysis::Bool;
+using opt::analysis::Float;
+using opt::analysis::Integer;
+using opt::analysis::Type;
+
+bool AddFactHelper(
+ FactManager* fact_manager, opt::IRContext* context,
+ std::vector<uint32_t>&& words,
+ const protobufs::UniformBufferElementDescriptor& descriptor) {
+ protobufs::FactConstantUniform constant_uniform_fact;
+ for (auto word : words) {
+ constant_uniform_fact.add_constant_word(word);
+ }
+ *constant_uniform_fact.mutable_uniform_buffer_element_descriptor() =
+ descriptor;
+ protobufs::Fact fact;
+ *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
+ return fact_manager->AddFact(fact, context);
+}
+
+TEST(FactManagerTest, ConstantsAvailableViaUniforms) {
+ std::string shader = R"(
+ OpCapability Shader
+ OpCapability Int64
+ OpCapability Float64
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 450
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeInt 32 0
+ %11 = OpTypeInt 32 1
+ %12 = OpTypeInt 64 0
+ %13 = OpTypeInt 64 1
+ %15 = OpTypeFloat 32
+ %16 = OpTypeFloat 64
+ %17 = OpConstant %11 5
+ %18 = OpConstant %11 20
+ %19 = OpTypeVector %10 4
+ %20 = OpConstant %11 6
+ %21 = OpTypeVector %12 4
+ %22 = OpConstant %11 10
+ %23 = OpTypeVector %11 4
+
+ %102 = OpTypeStruct %10 %10 %23
+ %101 = OpTypePointer Uniform %102
+ %100 = OpVariable %101 Uniform
+
+ %203 = OpTypeArray %23 %17
+ %202 = OpTypeArray %203 %18
+ %201 = OpTypePointer Uniform %202
+ %200 = OpVariable %201 Uniform
+
+ %305 = OpTypeStruct %16 %16 %16 %11 %16
+ %304 = OpTypeStruct %16 %16 %305
+ %303 = OpTypeStruct %304
+ %302 = OpTypeStruct %10 %303
+ %301 = OpTypePointer Uniform %302
+ %300 = OpVariable %301 Uniform
+
+ %400 = OpVariable %101 Uniform
+
+ %500 = OpVariable %201 Uniform
+
+ %604 = OpTypeArray %13 %20
+ %603 = OpTypeArray %604 %20
+ %602 = OpTypeArray %603 %20
+ %601 = OpTypePointer Uniform %602
+ %600 = OpVariable %601 Uniform
+
+ %703 = OpTypeArray %13 %20
+ %702 = OpTypeArray %703 %20
+ %701 = OpTypePointer Uniform %702
+ %700 = OpVariable %701 Uniform
+
+ %802 = OpTypeStruct %702 %602 %19 %202 %302
+ %801 = OpTypePointer Uniform %802
+ %800 = OpVariable %801 Uniform
+
+ %902 = OpTypeStruct %702 %802 %19 %202 %302
+ %901 = OpTypePointer Uniform %902
+ %900 = OpVariable %901 Uniform
+
+ %1003 = OpTypeStruct %802
+ %1002 = OpTypeArray %1003 %20
+ %1001 = OpTypePointer Uniform %1002
+ %1000 = OpVariable %1001 Uniform
+
+ %1101 = OpTypePointer Uniform %21
+ %1100 = OpVariable %1101 Uniform
+
+ %1202 = OpTypeArray %21 %20
+ %1201 = OpTypePointer Uniform %1202
+ %1200 = OpVariable %1201 Uniform
+
+ %1302 = OpTypeArray %21 %20
+ %1301 = OpTypePointer Uniform %1302
+ %1300 = OpVariable %1301 Uniform
+
+ %1402 = OpTypeArray %15 %22
+ %1401 = OpTypePointer Uniform %1402
+ %1400 = OpVariable %1401 Uniform
+
+ %1501 = OpTypePointer Uniform %1402
+ %1500 = OpVariable %1501 Uniform
+
+ %1602 = OpTypeArray %1402 %22
+ %1601 = OpTypePointer Uniform %1602
+ %1600 = OpVariable %1601 Uniform
+
+ %1704 = OpTypeStruct %16 %16 %16
+ %1703 = OpTypeArray %1704 %22
+ %1702 = OpTypeArray %1703 %22
+ %1701 = OpTypePointer Uniform %1702
+ %1700 = OpVariable %1701 Uniform
+
+ %1800 = OpVariable %1701 Uniform
+
+ %1906 = OpTypeStruct %16
+ %1905 = OpTypeStruct %1906
+ %1904 = OpTypeStruct %1905
+ %1903 = OpTypeStruct %1904
+ %1902 = OpTypeStruct %1903
+ %1901 = OpTypePointer Uniform %1902
+ %1900 = OpVariable %1901 Uniform
+
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ uint32_t buffer_int32_min[1];
+ uint32_t buffer_int64_1[2];
+ uint32_t buffer_int64_max[2];
+ uint32_t buffer_uint64_1[2];
+ uint32_t buffer_uint64_max[2];
+ uint32_t buffer_float_10[1];
+ uint32_t buffer_double_10[2];
+ uint32_t buffer_double_20[2];
+
+ {
+ int32_t temp = std::numeric_limits<int32_t>::min();
+ std::memcpy(&buffer_int32_min, &temp, sizeof(temp));
+ }
+
+ {
+ int64_t temp = 1;
+ std::memcpy(&buffer_int64_1, &temp, sizeof(temp));
+ }
+
+ {
+ int64_t temp = std::numeric_limits<int64_t>::max();
+ std::memcpy(&buffer_int64_max, &temp, sizeof(temp));
+ }
+
+ {
+ uint64_t temp = 1;
+ std::memcpy(&buffer_uint64_1, &temp, sizeof(temp));
+ }
+
+ {
+ uint64_t temp = std::numeric_limits<uint64_t>::max();
+ std::memcpy(&buffer_uint64_max, &temp, sizeof(temp));
+ }
+
+ {
+ float temp = 10.0f;
+ std::memcpy(&buffer_float_10, &temp, sizeof(float));
+ }
+
+ {
+ double temp = 10.0;
+ std::memcpy(&buffer_double_10, &temp, sizeof(temp));
+ }
+
+ {
+ double temp = 20.0;
+ std::memcpy(&buffer_double_20, &temp, sizeof(temp));
+ }
+
+ FactManager fact_manager;
+
+ uint32_t type_int32_id = 11;
+ uint32_t type_int64_id = 13;
+ uint32_t type_uint32_id = 10;
+ uint32_t type_uint64_id = 12;
+ uint32_t type_float_id = 15;
+ uint32_t type_double_id = 16;
+
+ // Initially there should be no facts about uniforms.
+ ASSERT_TRUE(fact_manager
+ .GetConstantsAvailableFromUniformsForType(context.get(),
+ type_uint32_id)
+ .empty());
+
+ // 100[2][3] == int(1)
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
+ MakeUniformBufferElementDescriptor(100, {2, 3})));
+
+ // 200[1][2][3] == int(1)
+ ASSERT_TRUE(
+ AddFactHelper(&fact_manager, context.get(), {1},
+ MakeUniformBufferElementDescriptor(200, {1, 2, 3})));
+
+ // 300[1][0][2][3] == int(1)
+ ASSERT_TRUE(
+ AddFactHelper(&fact_manager, context.get(), {1},
+ MakeUniformBufferElementDescriptor(300, {1, 0, 2, 3})));
+
+ // 400[2][3] = int32_min
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]},
+ MakeUniformBufferElementDescriptor(400, {2, 3})));
+
+ // 500[1][2][3] = int32_min
+ ASSERT_TRUE(
+ AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]},
+ MakeUniformBufferElementDescriptor(500, {1, 2, 3})));
+
+ // 600[1][2][3] = int64_max
+ ASSERT_TRUE(AddFactHelper(
+ &fact_manager, context.get(), {buffer_int64_max[0], buffer_int64_max[1]},
+ MakeUniformBufferElementDescriptor(600, {1, 2, 3})));
+
+ // 700[1][1] = int64_max
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
+ {buffer_int64_max[0], buffer_int64_max[1]},
+ MakeUniformBufferElementDescriptor(700, {1, 1})));
+
+ // 800[2][3] = uint(1)
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
+ MakeUniformBufferElementDescriptor(800, {2, 3})));
+
+ // 900[1][2][3] = uint(1)
+ ASSERT_TRUE(
+ AddFactHelper(&fact_manager, context.get(), {1},
+ MakeUniformBufferElementDescriptor(900, {1, 2, 3})));
+
+ // 1000[1][0][2][3] = uint(1)
+ ASSERT_TRUE(
+ AddFactHelper(&fact_manager, context.get(), {1},
+ MakeUniformBufferElementDescriptor(1000, {1, 0, 2, 3})));
+
+ // 1100[0] = uint64(1)
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
+ {buffer_uint64_1[0], buffer_uint64_1[1]},
+ MakeUniformBufferElementDescriptor(1100, {0})));
+
+ // 1200[0][0] = uint64_max
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
+ {buffer_uint64_max[0], buffer_uint64_max[1]},
+ MakeUniformBufferElementDescriptor(1200, {0, 0})));
+
+ // 1300[1][0] = uint64_max
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
+ {buffer_uint64_max[0], buffer_uint64_max[1]},
+ MakeUniformBufferElementDescriptor(1300, {1, 0})));
+
+ // 1400[6] = float(10.0)
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
+ MakeUniformBufferElementDescriptor(1400, {6})));
+
+ // 1500[7] = float(10.0)
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
+ MakeUniformBufferElementDescriptor(1500, {7})));
+
+ // 1600[9][9] = float(10.0)
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
+ MakeUniformBufferElementDescriptor(1600, {9, 9})));
+
+ // 1700[9][9][1] = double(10.0)
+ ASSERT_TRUE(AddFactHelper(
+ &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]},
+ MakeUniformBufferElementDescriptor(1700, {9, 9, 1})));
+
+ // 1800[9][9][2] = double(10.0)
+ ASSERT_TRUE(AddFactHelper(
+ &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]},
+ MakeUniformBufferElementDescriptor(1800, {9, 9, 2})));
+
+ // 1900[0][0][0][0][0] = double(20.0)
+ ASSERT_TRUE(AddFactHelper(
+ &fact_manager, context.get(), {buffer_double_20[0], buffer_double_20[1]},
+ MakeUniformBufferElementDescriptor(1900, {0, 0, 0, 0, 0})));
+
+ opt::Instruction::OperandList operands = {
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_int32_id, 50, operands));
+ operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int32_min[0]}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_int32_id, 51, operands));
+ operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int64_max[0]}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int64_max[1]}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_int64_id, 52, operands));
+ operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_uint32_id, 53, operands));
+ operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_1[0]}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_1[1]}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_uint64_id, 54, operands));
+ operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_max[0]}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_max[1]}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_uint64_id, 55, operands));
+ operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_float_10[0]}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_float_id, 56, operands));
+ operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_10[0]}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_10[1]}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_double_id, 57, operands));
+ operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_20[0]}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_20[1]}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_double_id, 58, operands));
+
+ // A duplicate of the constant with id 59.
+ operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
+ context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ context.get(), SpvOpConstant, type_int32_id, 59, operands));
+
+ context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+
+ // Constants 1 and int32_min are available.
+ ASSERT_EQ(2, fact_manager
+ .GetConstantsAvailableFromUniformsForType(context.get(),
+ type_int32_id)
+ .size());
+ // Constant int64_max is available.
+ ASSERT_EQ(1, fact_manager
+ .GetConstantsAvailableFromUniformsForType(context.get(),
+ type_int64_id)
+ .size());
+ // Constant 1u is available.
+ ASSERT_EQ(1, fact_manager
+ .GetConstantsAvailableFromUniformsForType(context.get(),
+ type_uint32_id)
+ .size());
+ // Constants 1u and uint64_max are available.
+ ASSERT_EQ(2, fact_manager
+ .GetConstantsAvailableFromUniformsForType(context.get(),
+ type_uint64_id)
+ .size());
+ // Constant 10.0 is available.
+ ASSERT_EQ(1, fact_manager
+ .GetConstantsAvailableFromUniformsForType(context.get(),
+ type_float_id)
+ .size());
+ // Constants 10.0 and 20.0 are available.
+ ASSERT_EQ(2, fact_manager
+ .GetConstantsAvailableFromUniformsForType(context.get(),
+ type_double_id)
+ .size());
+
+ ASSERT_EQ(std::numeric_limits<int64_t>::max(),
+ context->get_constant_mgr()
+ ->FindDeclaredConstant(
+ fact_manager.GetConstantsAvailableFromUniformsForType(
+ context.get(), type_int64_id)[0])
+ ->AsIntConstant()
+ ->GetS64());
+ ASSERT_EQ(1, context->get_constant_mgr()
+ ->FindDeclaredConstant(
+ fact_manager.GetConstantsAvailableFromUniformsForType(
+ context.get(), type_uint32_id)[0])
+ ->AsIntConstant()
+ ->GetU32());
+ ASSERT_EQ(10.0f,
+ context->get_constant_mgr()
+ ->FindDeclaredConstant(
+ fact_manager.GetConstantsAvailableFromUniformsForType(
+ context.get(), type_float_id)[0])
+ ->AsFloatConstant()
+ ->GetFloat());
+ const std::vector<uint32_t>& double_constant_ids =
+ fact_manager.GetConstantsAvailableFromUniformsForType(context.get(),
+ type_double_id);
+ ASSERT_EQ(10.0, context->get_constant_mgr()
+ ->FindDeclaredConstant(double_constant_ids[0])
+ ->AsFloatConstant()
+ ->GetDouble());
+ ASSERT_EQ(20.0, context->get_constant_mgr()
+ ->FindDeclaredConstant(double_constant_ids[1])
+ ->AsFloatConstant()
+ ->GetDouble());
+
+ const std::vector<protobufs::UniformBufferElementDescriptor>
+ descriptors_for_double_10 = fact_manager.GetUniformDescriptorsForConstant(
+ context.get(), double_constant_ids[0]);
+ ASSERT_EQ(2, descriptors_for_double_10.size());
+ {
+ auto temp = MakeUniformBufferElementDescriptor(1700, {9, 9, 1});
+ ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+ &temp, &descriptors_for_double_10[0]));
+ }
+ {
+ auto temp = MakeUniformBufferElementDescriptor(1800, {9, 9, 2});
+ ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+ &temp, &descriptors_for_double_10[1]));
+ }
+ const std::vector<protobufs::UniformBufferElementDescriptor>
+ descriptors_for_double_20 = fact_manager.GetUniformDescriptorsForConstant(
+ context.get(), double_constant_ids[1]);
+ ASSERT_EQ(1, descriptors_for_double_20.size());
+ {
+ auto temp = MakeUniformBufferElementDescriptor(1900, {0, 0, 0, 0, 0});
+ ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+ &temp, &descriptors_for_double_20[0]));
+ }
+
+ auto constant_1_id = fact_manager.GetConstantFromUniformDescriptor(
+ context.get(), MakeUniformBufferElementDescriptor(1800, {9, 9, 2}));
+ ASSERT_TRUE(constant_1_id);
+
+ auto constant_2_id = fact_manager.GetConstantFromUniformDescriptor(
+ context.get(), MakeUniformBufferElementDescriptor(1900, {0, 0, 0, 0, 0}));
+ ASSERT_TRUE(constant_2_id);
+
+ ASSERT_EQ(double_constant_ids[0], constant_1_id);
+
+ ASSERT_EQ(double_constant_ids[1], constant_2_id);
+}
+
+TEST(FactManagerTest, TwoConstantsWithSameValue) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %8 "x"
+ OpName %10 "buf"
+ OpMemberName %10 0 "a"
+ OpName %12 ""
+ OpDecorate %8 RelaxedPrecision
+ OpMemberDecorate %10 0 RelaxedPrecision
+ OpMemberDecorate %10 0 Offset 0
+ OpDecorate %10 Block
+ OpDecorate %12 DescriptorSet 0
+ OpDecorate %12 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 1
+ %20 = OpConstant %6 1
+ %10 = OpTypeStruct %6
+ %11 = OpTypePointer Uniform %10
+ %12 = OpVariable %11 Uniform
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpStore %8 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+
+ auto uniform_buffer_element_descriptor =
+ MakeUniformBufferElementDescriptor(12, {0});
+
+ // 12[0] = int(1)
+ ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
+ uniform_buffer_element_descriptor));
+ auto constants =
+ fact_manager.GetConstantsAvailableFromUniformsForType(context.get(), 6);
+ ASSERT_EQ(1, constants.size());
+ ASSERT_TRUE(constants[0] == 9 || constants[0] == 20);
+
+ auto constant = fact_manager.GetConstantFromUniformDescriptor(
+ context.get(), uniform_buffer_element_descriptor);
+ ASSERT_TRUE(constant == 9 || constant == 20);
+
+ // Because the constants with ids 9 and 20 are equal, we should get the same
+ // single uniform buffer element descriptor when we look up the descriptors
+ // for either one of them.
+ for (auto constant_id : {9u, 20u}) {
+ auto descriptors = fact_manager.GetUniformDescriptorsForConstant(
+ context.get(), constant_id);
+ ASSERT_EQ(1, descriptors.size());
+ ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+ &uniform_buffer_element_descriptor, &descriptors[0]));
+ }
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools