|
@@ -31,15 +31,12 @@ constexpr int kSpvLoadPtrIdInIdx = 0;
|
|
|
constexpr int kSpvAccessChainBaseIdInIdx = 0;
|
|
constexpr int kSpvAccessChainBaseIdInIdx = 0;
|
|
|
constexpr int kSpvAccessChainIndex0IdInIdx = 1;
|
|
constexpr int kSpvAccessChainIndex0IdInIdx = 1;
|
|
|
constexpr int kSpvTypeArrayTypeIdInIdx = 0;
|
|
constexpr int kSpvTypeArrayTypeIdInIdx = 0;
|
|
|
-constexpr int kSpvTypeArrayLengthIdInIdx = 1;
|
|
|
|
|
-constexpr int kSpvConstantValueInIdx = 0;
|
|
|
|
|
constexpr int kSpvVariableStorageClassInIdx = 0;
|
|
constexpr int kSpvVariableStorageClassInIdx = 0;
|
|
|
constexpr int kSpvTypePtrTypeIdInIdx = 1;
|
|
constexpr int kSpvTypePtrTypeIdInIdx = 1;
|
|
|
constexpr int kSpvTypeImageDim = 1;
|
|
constexpr int kSpvTypeImageDim = 1;
|
|
|
constexpr int kSpvTypeImageDepth = 2;
|
|
constexpr int kSpvTypeImageDepth = 2;
|
|
|
constexpr int kSpvTypeImageArrayed = 3;
|
|
constexpr int kSpvTypeImageArrayed = 3;
|
|
|
constexpr int kSpvTypeImageMS = 4;
|
|
constexpr int kSpvTypeImageMS = 4;
|
|
|
-constexpr int kSpvTypeImageSampled = 5;
|
|
|
|
|
} // namespace
|
|
} // namespace
|
|
|
|
|
|
|
|
void InstBindlessCheckPass::SetupInputBufferIds() {
|
|
void InstBindlessCheckPass::SetupInputBufferIds() {
|
|
@@ -135,228 +132,76 @@ void InstBindlessCheckPass::SetupInputBufferIds() {
|
|
|
|
|
|
|
|
// clang-format off
|
|
// clang-format off
|
|
|
// GLSL:
|
|
// GLSL:
|
|
|
-// uint inst_bindless_read_binding_length(uint desc_set_idx, uint binding_idx)
|
|
|
|
|
-// {
|
|
|
|
|
-// if (desc_set_idx >= inst_bindless_input_buffer.desc_sets.length()) {
|
|
|
|
|
-// return 0;
|
|
|
|
|
-// }
|
|
|
|
|
-//
|
|
|
|
|
-// DescriptorSetData set_data = inst_bindless_input_buffer.desc_sets[desc_set_idx];
|
|
|
|
|
-// uvec2 ptr_as_vec = uvec2(set_data);
|
|
|
|
|
-// if ((ptr_as_vec.x == 0u) && (_ptr_as_vec.y == 0u))
|
|
|
|
|
-// {
|
|
|
|
|
-// return 0u;
|
|
|
|
|
-// }
|
|
|
|
|
-// uint num_bindings = set_data.num_bindings;
|
|
|
|
|
-// if (binding_idx >= num_bindings) {
|
|
|
|
|
-// return 0;
|
|
|
|
|
-// }
|
|
|
|
|
-// return set_data.data[binding_idx];
|
|
|
|
|
-// }
|
|
|
|
|
|
|
+//bool inst_bindless_check_desc(uint shader_id, uint line, uvec4 stage_info, uint desc_set_idx, uint binding_idx, uint desc_idx,
|
|
|
|
|
+// uint offset)
|
|
|
|
|
+//{
|
|
|
|
|
+// if (desc_set_idx >= inst_bindless_input_buffer.desc_sets.length()) {
|
|
|
|
|
+// // kInstErrorBindlessBounds
|
|
|
|
|
+// inst_bindless_stream_write_6(shader_id, line, stage_info, 1, desc_set_idx, binding_idx, desc_idx, 0, 0);
|
|
|
|
|
+// return false;
|
|
|
|
|
+// }
|
|
|
|
|
+// DescriptorSetData set_data = inst_bindless_input_buffer.desc_sets[desc_set_idx];
|
|
|
|
|
+// uvec2 ptr_vec = uvec2(set_data);
|
|
|
|
|
+// if (ptr_vec.x == 0 && ptr_vec.y == 0) {
|
|
|
|
|
+// // kInstErrorBindlessBounds
|
|
|
|
|
+// inst_bindless_stream_write_6(shader_id, line, stage_info, 1, desc_set_idx, binding_idx, desc_idx, 0, 0);
|
|
|
|
|
+// return false;
|
|
|
|
|
+// }
|
|
|
|
|
+// uint num_bindings = set_data.num_bindings;
|
|
|
|
|
+// if (binding_idx >= num_bindings) {
|
|
|
|
|
+// // kInstErrorBindlessBounds
|
|
|
|
|
+// inst_bindless_stream_write_6(shader_id, line, stage_info, 1, desc_set_idx, binding_idx, desc_idx, 0, 0);
|
|
|
|
|
+// return false;
|
|
|
|
|
+// }
|
|
|
|
|
+// uint binding_length = set_data.data[binding_idx];
|
|
|
|
|
+// if (desc_idx >= binding_length) {
|
|
|
|
|
+// // kInstErrorBindlessBounds
|
|
|
|
|
+// inst_bindless_stream_write_6(shader_id, line, stage_info, 1, desc_set_idx, binding_idx, desc_idx, binding_length, 0);
|
|
|
|
|
+// return false;
|
|
|
|
|
+// }
|
|
|
|
|
+// uint desc_records_start = set_data.data[num_bindings + binding_idx];
|
|
|
|
|
+// uint init_or_len = set_data.data[desc_records_start + desc_idx];
|
|
|
|
|
+// if (init_or_len == 0) {
|
|
|
|
|
+// // kInstErrorBindlessUninit
|
|
|
|
|
+// inst_bindless_stream_write_6(shader_id, line, stage_info, 2, desc_set_idx, binding_idx, desc_idx, 0, 0);
|
|
|
|
|
+// return false;
|
|
|
|
|
+// }
|
|
|
|
|
+// if (offset >= init_or_len) {
|
|
|
|
|
+// // kInstErrorOOB
|
|
|
|
|
+// inst_bindless_stream_write_6(shader_id, line, stage_info, 4, desc_set_idx, binding_idx, desc_idx, offset,
|
|
|
|
|
+// init_or_len);
|
|
|
|
|
+// return false;
|
|
|
|
|
+// }
|
|
|
|
|
+// return true;
|
|
|
|
|
+//}
|
|
|
// clang-format on
|
|
// clang-format on
|
|
|
-uint32_t InstBindlessCheckPass::GenDebugReadLengthFunctionId() {
|
|
|
|
|
- if (read_length_func_id_ != 0) {
|
|
|
|
|
- return read_length_func_id_;
|
|
|
|
|
- }
|
|
|
|
|
- SetupInputBufferIds();
|
|
|
|
|
- const analysis::Integer* uint_type = GetInteger(32, false);
|
|
|
|
|
- const std::vector<const analysis::Type*> param_types(2, uint_type);
|
|
|
|
|
-
|
|
|
|
|
- const uint32_t func_id = TakeNextId();
|
|
|
|
|
- std::unique_ptr<Function> func =
|
|
|
|
|
- StartFunction(func_id, uint_type, param_types);
|
|
|
|
|
-
|
|
|
|
|
- const std::vector<uint32_t> param_ids = AddParameters(*func, param_types);
|
|
|
|
|
-
|
|
|
|
|
- // Create block
|
|
|
|
|
- auto new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(TakeNextId()));
|
|
|
|
|
- InstructionBuilder builder(
|
|
|
|
|
- context(), new_blk_ptr.get(),
|
|
|
|
|
- IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
|
|
|
|
- Instruction* inst;
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddBinaryOp(
|
|
|
|
|
- GetBoolId(), spv::Op::OpUGreaterThanEqual, param_ids[0],
|
|
|
|
|
- builder.GetUintConstantId(kDebugInputBindlessMaxDescSets));
|
|
|
|
|
- const uint32_t desc_cmp_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- uint32_t error_blk_id = TakeNextId();
|
|
|
|
|
- uint32_t merge_blk_id = TakeNextId();
|
|
|
|
|
- std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
|
|
|
|
|
- std::unique_ptr<Instruction> error_label(NewLabel(error_blk_id));
|
|
|
|
|
- (void)builder.AddConditionalBranch(desc_cmp_id, error_blk_id, merge_blk_id,
|
|
|
|
|
- merge_blk_id);
|
|
|
|
|
-
|
|
|
|
|
- func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
-
|
|
|
|
|
- // error return
|
|
|
|
|
- new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
|
|
|
- builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
- (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue,
|
|
|
|
|
- builder.GetUintConstantId(0));
|
|
|
|
|
- func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
-
|
|
|
|
|
- // check descriptor set table entry is non-null
|
|
|
|
|
- new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
|
|
|
- builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
-
|
|
|
|
|
- analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
|
|
|
|
- const uint32_t desc_set_ptr_ptr = type_mgr->FindPointerToType(
|
|
|
|
|
- desc_set_ptr_id_, spv::StorageClass::StorageBuffer);
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddAccessChain(desc_set_ptr_ptr, input_buffer_id_,
|
|
|
|
|
- {builder.GetUintConstantId(0), param_ids[0]});
|
|
|
|
|
- const uint32_t set_access_chain_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddLoad(desc_set_ptr_id_, set_access_chain_id);
|
|
|
|
|
- const uint32_t desc_set_ptr_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- inst =
|
|
|
|
|
- builder.AddUnaryOp(GetVecUintId(2), spv::Op::OpBitcast, desc_set_ptr_id);
|
|
|
|
|
- const uint32_t ptr_as_uvec_id = inst->result_id();
|
|
|
|
|
|
|
|
|
|
- inst = builder.AddCompositeExtract(GetUintId(), ptr_as_uvec_id, {0});
|
|
|
|
|
- const uint32_t uvec_x = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, uvec_x,
|
|
|
|
|
- builder.GetUintConstantId(0));
|
|
|
|
|
- const uint32_t x_is_zero_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddCompositeExtract(GetUintId(), ptr_as_uvec_id, {1});
|
|
|
|
|
- const uint32_t uvec_y = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, uvec_y,
|
|
|
|
|
- builder.GetUintConstantId(0));
|
|
|
|
|
- const uint32_t y_is_zero_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpLogicalAnd, x_is_zero_id,
|
|
|
|
|
- y_is_zero_id);
|
|
|
|
|
- const uint32_t is_null_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- error_blk_id = TakeNextId();
|
|
|
|
|
- merge_blk_id = TakeNextId();
|
|
|
|
|
- merge_label = NewLabel(merge_blk_id);
|
|
|
|
|
- error_label = NewLabel(error_blk_id);
|
|
|
|
|
- (void)builder.AddConditionalBranch(is_null_id, error_blk_id, merge_blk_id,
|
|
|
|
|
- merge_blk_id);
|
|
|
|
|
- func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
- // error return
|
|
|
|
|
- new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
|
|
|
- builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
- (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue,
|
|
|
|
|
- builder.GetUintConstantId(0));
|
|
|
|
|
- func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
-
|
|
|
|
|
- // check binding is in range
|
|
|
|
|
- new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
|
|
|
- builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
-
|
|
|
|
|
- const uint32_t uint_ptr = type_mgr->FindPointerToType(
|
|
|
|
|
- GetUintId(), spv::StorageClass::PhysicalStorageBuffer);
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddAccessChain(uint_ptr, desc_set_ptr_id,
|
|
|
|
|
- {builder.GetUintConstantId(0)});
|
|
|
|
|
- const uint32_t binding_access_chain_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddLoad(GetUintId(), binding_access_chain_id, 8);
|
|
|
|
|
- const uint32_t num_bindings_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThanEqual,
|
|
|
|
|
- param_ids[1], num_bindings_id);
|
|
|
|
|
- const uint32_t bindings_cmp_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- error_blk_id = TakeNextId();
|
|
|
|
|
- merge_blk_id = TakeNextId();
|
|
|
|
|
- merge_label = NewLabel(merge_blk_id);
|
|
|
|
|
- error_label = NewLabel(error_blk_id);
|
|
|
|
|
- (void)builder.AddConditionalBranch(bindings_cmp_id, error_blk_id,
|
|
|
|
|
- merge_blk_id, merge_blk_id);
|
|
|
|
|
- func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
- // error return
|
|
|
|
|
- new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
|
|
|
- builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
- (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue,
|
|
|
|
|
- builder.GetUintConstantId(0));
|
|
|
|
|
- func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
-
|
|
|
|
|
- // read binding length
|
|
|
|
|
- new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
|
|
|
- builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddAccessChain(uint_ptr, desc_set_ptr_id,
|
|
|
|
|
- {{builder.GetUintConstantId(1), param_ids[1]}});
|
|
|
|
|
- const uint32_t length_ac_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- inst = builder.AddLoad(GetUintId(), length_ac_id, sizeof(uint32_t));
|
|
|
|
|
- const uint32_t length_id = inst->result_id();
|
|
|
|
|
-
|
|
|
|
|
- (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, length_id);
|
|
|
|
|
-
|
|
|
|
|
- func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
- func->SetFunctionEnd(EndFunction());
|
|
|
|
|
-
|
|
|
|
|
- context()->AddFunction(std::move(func));
|
|
|
|
|
- context()->AddDebug2Inst(NewGlobalName(func_id, "read_binding_length"));
|
|
|
|
|
-
|
|
|
|
|
- read_length_func_id_ = func_id;
|
|
|
|
|
- // Make sure this function doesn't get processed by
|
|
|
|
|
- // InstrumentPass::InstProcessCallTreeFromRoots()
|
|
|
|
|
- param2output_func_id_[2] = func_id;
|
|
|
|
|
- return read_length_func_id_;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// clang-format off
|
|
|
|
|
-// GLSL:
|
|
|
|
|
-// result = inst_bindless_read_binding_length(desc_set_id, binding_id);
|
|
|
|
|
-// clang-format on
|
|
|
|
|
-uint32_t InstBindlessCheckPass::GenDebugReadLength(
|
|
|
|
|
- uint32_t var_id, InstructionBuilder* builder) {
|
|
|
|
|
- const uint32_t func_id = GenDebugReadLengthFunctionId();
|
|
|
|
|
-
|
|
|
|
|
- const std::vector<uint32_t> args = {
|
|
|
|
|
- builder->GetUintConstantId(var2desc_set_[var_id]),
|
|
|
|
|
- builder->GetUintConstantId(var2binding_[var_id]),
|
|
|
|
|
|
|
+uint32_t InstBindlessCheckPass::GenDescCheckFunctionId() {
|
|
|
|
|
+ enum {
|
|
|
|
|
+ kShaderId = 0,
|
|
|
|
|
+ kInstructionIndex = 1,
|
|
|
|
|
+ kStageInfo = 2,
|
|
|
|
|
+ kDescSet = 3,
|
|
|
|
|
+ kDescBinding = 4,
|
|
|
|
|
+ kDescIndex = 5,
|
|
|
|
|
+ kByteOffset = 6,
|
|
|
|
|
+ kNumArgs
|
|
|
};
|
|
};
|
|
|
- return GenReadFunctionCall(func_id, args, builder);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// clang-format off
|
|
|
|
|
-// GLSL:
|
|
|
|
|
-// uint inst_bindless_read_desc_init(uint desc_set_idx, uint binding_idx, uint desc_idx)
|
|
|
|
|
-// {
|
|
|
|
|
-// if (desc_set_idx >= uint(inst_bindless_input_buffer.desc_sets.length()))
|
|
|
|
|
-// {
|
|
|
|
|
-// return 0u;
|
|
|
|
|
-// }
|
|
|
|
|
-// DescriptorSetData set_data = inst_bindless_input_buffer.desc_sets[desc_set_idx];
|
|
|
|
|
-// uvec2 ptr_as_vec = uvec2(set_data)
|
|
|
|
|
-// if ((ptr_as_vec .x == 0u) && (ptr_as_vec.y == 0u))
|
|
|
|
|
-// {
|
|
|
|
|
-// return 0u;
|
|
|
|
|
-// }
|
|
|
|
|
-// if (binding_idx >= set_data.num_bindings)
|
|
|
|
|
-// {
|
|
|
|
|
-// return 0u;
|
|
|
|
|
-// }
|
|
|
|
|
-// if (desc_idx >= set_data.data[binding_idx])
|
|
|
|
|
-// {
|
|
|
|
|
-// return 0u;
|
|
|
|
|
-// }
|
|
|
|
|
-// uint desc_records_start = set_data.data[set_data.num_bindings + binding_idx];
|
|
|
|
|
-// return set_data.data[desc_records_start + desc_idx];
|
|
|
|
|
-// }
|
|
|
|
|
-// clang-format on
|
|
|
|
|
-uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
|
|
- if (read_init_func_id_ != 0) {
|
|
|
|
|
- return read_init_func_id_;
|
|
|
|
|
|
|
+ if (desc_check_func_id_ != 0) {
|
|
|
|
|
+ return desc_check_func_id_;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
SetupInputBufferIds();
|
|
SetupInputBufferIds();
|
|
|
|
|
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
|
|
const analysis::Integer* uint_type = GetInteger(32, false);
|
|
const analysis::Integer* uint_type = GetInteger(32, false);
|
|
|
- const std::vector<const analysis::Type*> param_types(3, uint_type);
|
|
|
|
|
|
|
+ const analysis::Vector v4uint(uint_type, 4);
|
|
|
|
|
+ const analysis::Type* v4uint_type = type_mgr->GetRegisteredType(&v4uint);
|
|
|
|
|
+ std::vector<const analysis::Type*> param_types(kNumArgs, uint_type);
|
|
|
|
|
+ param_types[2] = v4uint_type;
|
|
|
|
|
|
|
|
const uint32_t func_id = TakeNextId();
|
|
const uint32_t func_id = TakeNextId();
|
|
|
std::unique_ptr<Function> func =
|
|
std::unique_ptr<Function> func =
|
|
|
- StartFunction(func_id, uint_type, param_types);
|
|
|
|
|
|
|
+ StartFunction(func_id, type_mgr->GetBoolType(), param_types);
|
|
|
|
|
|
|
|
const std::vector<uint32_t> param_ids = AddParameters(*func, param_types);
|
|
const std::vector<uint32_t> param_ids = AddParameters(*func, param_types);
|
|
|
|
|
|
|
@@ -365,10 +210,12 @@ uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
InstructionBuilder builder(
|
|
InstructionBuilder builder(
|
|
|
context(), new_blk_ptr.get(),
|
|
context(), new_blk_ptr.get(),
|
|
|
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
|
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
|
|
|
|
+ const uint32_t false_id = builder.GetBoolConstantId(false);
|
|
|
|
|
+ const uint32_t true_id = builder.GetBoolConstantId(true);
|
|
|
Instruction* inst;
|
|
Instruction* inst;
|
|
|
|
|
|
|
|
inst = builder.AddBinaryOp(
|
|
inst = builder.AddBinaryOp(
|
|
|
- GetBoolId(), spv::Op::OpUGreaterThanEqual, param_ids[0],
|
|
|
|
|
|
|
+ GetBoolId(), spv::Op::OpUGreaterThanEqual, param_ids[kDescSet],
|
|
|
builder.GetUintConstantId(kDebugInputBindlessMaxDescSets));
|
|
builder.GetUintConstantId(kDebugInputBindlessMaxDescSets));
|
|
|
const uint32_t desc_cmp_id = inst->result_id();
|
|
const uint32_t desc_cmp_id = inst->result_id();
|
|
|
|
|
|
|
@@ -383,20 +230,19 @@ uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
// error return
|
|
// error return
|
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
- (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue,
|
|
|
|
|
- builder.GetUintConstantId(0));
|
|
|
|
|
|
|
+ (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, false_id);
|
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
|
|
|
// check descriptor set table entry is non-null
|
|
// check descriptor set table entry is non-null
|
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
|
|
|
- analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
|
|
|
|
const uint32_t desc_set_ptr_ptr = type_mgr->FindPointerToType(
|
|
const uint32_t desc_set_ptr_ptr = type_mgr->FindPointerToType(
|
|
|
desc_set_ptr_id_, spv::StorageClass::StorageBuffer);
|
|
desc_set_ptr_id_, spv::StorageClass::StorageBuffer);
|
|
|
|
|
|
|
|
- inst = builder.AddAccessChain(desc_set_ptr_ptr, input_buffer_id_,
|
|
|
|
|
- {builder.GetUintConstantId(0), param_ids[0]});
|
|
|
|
|
|
|
+ inst = builder.AddAccessChain(
|
|
|
|
|
+ desc_set_ptr_ptr, input_buffer_id_,
|
|
|
|
|
+ {builder.GetUintConstantId(0), param_ids[kDescSet]});
|
|
|
const uint32_t set_access_chain_id = inst->result_id();
|
|
const uint32_t set_access_chain_id = inst->result_id();
|
|
|
|
|
|
|
|
inst = builder.AddLoad(desc_set_ptr_id_, set_access_chain_id);
|
|
inst = builder.AddLoad(desc_set_ptr_id_, set_access_chain_id);
|
|
@@ -434,8 +280,13 @@ uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
// error return
|
|
// error return
|
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
- (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue,
|
|
|
|
|
- builder.GetUintConstantId(0));
|
|
|
|
|
|
|
+ GenDebugStreamWrite(
|
|
|
|
|
+ param_ids[kShaderId], param_ids[kInstructionIndex], param_ids[kStageInfo],
|
|
|
|
|
+ {builder.GetUintConstantId(kInstErrorBindlessBounds), param_ids[kDescSet],
|
|
|
|
|
+ param_ids[kDescBinding], param_ids[kDescIndex],
|
|
|
|
|
+ builder.GetUintConstantId(0), builder.GetUintConstantId(0)},
|
|
|
|
|
+ &builder);
|
|
|
|
|
+ (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, false_id);
|
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
|
|
|
// check binding is in range
|
|
// check binding is in range
|
|
@@ -453,7 +304,7 @@ uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
const uint32_t num_bindings_id = inst->result_id();
|
|
const uint32_t num_bindings_id = inst->result_id();
|
|
|
|
|
|
|
|
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThanEqual,
|
|
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThanEqual,
|
|
|
- param_ids[1], num_bindings_id);
|
|
|
|
|
|
|
+ param_ids[kDescBinding], num_bindings_id);
|
|
|
const uint32_t bindings_cmp_id = inst->result_id();
|
|
const uint32_t bindings_cmp_id = inst->result_id();
|
|
|
|
|
|
|
|
error_blk_id = TakeNextId();
|
|
error_blk_id = TakeNextId();
|
|
@@ -466,16 +317,22 @@ uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
// error return
|
|
// error return
|
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
- (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue,
|
|
|
|
|
- builder.GetUintConstantId(0));
|
|
|
|
|
|
|
+ GenDebugStreamWrite(
|
|
|
|
|
+ param_ids[kShaderId], param_ids[kInstructionIndex], param_ids[kStageInfo],
|
|
|
|
|
+ {builder.GetUintConstantId(kInstErrorBindlessBounds), param_ids[kDescSet],
|
|
|
|
|
+ param_ids[kDescBinding], param_ids[kDescIndex],
|
|
|
|
|
+ builder.GetUintConstantId(0), builder.GetUintConstantId(0)},
|
|
|
|
|
+ &builder);
|
|
|
|
|
+ (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, false_id);
|
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
|
|
|
// read binding length
|
|
// read binding length
|
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
|
|
|
- inst = builder.AddAccessChain(uint_ptr, desc_set_ptr_id,
|
|
|
|
|
- {{builder.GetUintConstantId(1), param_ids[1]}});
|
|
|
|
|
|
|
+ inst = builder.AddAccessChain(
|
|
|
|
|
+ uint_ptr, desc_set_ptr_id,
|
|
|
|
|
+ {{builder.GetUintConstantId(1), param_ids[kDescBinding]}});
|
|
|
const uint32_t length_ac_id = inst->result_id();
|
|
const uint32_t length_ac_id = inst->result_id();
|
|
|
|
|
|
|
|
inst = builder.AddLoad(GetUintId(), length_ac_id, sizeof(uint32_t));
|
|
inst = builder.AddLoad(GetUintId(), length_ac_id, sizeof(uint32_t));
|
|
@@ -483,7 +340,7 @@ uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
|
|
|
|
|
// Check descriptor index in bounds
|
|
// Check descriptor index in bounds
|
|
|
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThanEqual,
|
|
inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThanEqual,
|
|
|
- param_ids[2], length_id);
|
|
|
|
|
|
|
+ param_ids[kDescIndex], length_id);
|
|
|
const uint32_t desc_idx_range_id = inst->result_id();
|
|
const uint32_t desc_idx_range_id = inst->result_id();
|
|
|
|
|
|
|
|
error_blk_id = TakeNextId();
|
|
error_blk_id = TakeNextId();
|
|
@@ -496,15 +353,20 @@ uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
// Error return
|
|
// Error return
|
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
- (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue,
|
|
|
|
|
- builder.GetUintConstantId(0));
|
|
|
|
|
|
|
+ GenDebugStreamWrite(
|
|
|
|
|
+ param_ids[kShaderId], param_ids[kInstructionIndex], param_ids[kStageInfo],
|
|
|
|
|
+ {builder.GetUintConstantId(kInstErrorBindlessBounds), param_ids[kDescSet],
|
|
|
|
|
+ param_ids[kDescBinding], param_ids[kDescIndex], length_id,
|
|
|
|
|
+ builder.GetUintConstantId(0)},
|
|
|
|
|
+ &builder);
|
|
|
|
|
+ (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, false_id);
|
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
|
|
|
// Read descriptor init status
|
|
// Read descriptor init status
|
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
|
|
|
- inst = builder.AddIAdd(GetUintId(), num_bindings_id, param_ids[1]);
|
|
|
|
|
|
|
+ inst = builder.AddIAdd(GetUintId(), num_bindings_id, param_ids[kDescBinding]);
|
|
|
const uint32_t state_offset_id = inst->result_id();
|
|
const uint32_t state_offset_id = inst->result_id();
|
|
|
|
|
|
|
|
inst =
|
|
inst =
|
|
@@ -515,7 +377,7 @@ uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
inst = builder.AddLoad(GetUintId(), state_start_ac_id, sizeof(uint32_t));
|
|
inst = builder.AddLoad(GetUintId(), state_start_ac_id, sizeof(uint32_t));
|
|
|
const uint32_t state_start_id = inst->result_id();
|
|
const uint32_t state_start_id = inst->result_id();
|
|
|
|
|
|
|
|
- inst = builder.AddIAdd(GetUintId(), state_start_id, param_ids[2]);
|
|
|
|
|
|
|
+ inst = builder.AddIAdd(GetUintId(), state_start_id, param_ids[kDescIndex]);
|
|
|
const uint32_t state_entry_id = inst->result_id();
|
|
const uint32_t state_entry_id = inst->result_id();
|
|
|
|
|
|
|
|
// Note: length starts from the beginning of the buffer, not the beginning of
|
|
// Note: length starts from the beginning of the buffer, not the beginning of
|
|
@@ -528,35 +390,90 @@ uint32_t InstBindlessCheckPass::GenDebugReadInitFunctionId() {
|
|
|
inst = builder.AddLoad(GetUintId(), init_ac_id, sizeof(uint32_t));
|
|
inst = builder.AddLoad(GetUintId(), init_ac_id, sizeof(uint32_t));
|
|
|
const uint32_t init_status_id = inst->result_id();
|
|
const uint32_t init_status_id = inst->result_id();
|
|
|
|
|
|
|
|
- (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, init_status_id);
|
|
|
|
|
|
|
+ // Check for uninitialized descriptor
|
|
|
|
|
+ inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpIEqual, init_status_id,
|
|
|
|
|
+ builder.GetUintConstantId(0));
|
|
|
|
|
+ const uint32_t uninit_check_id = inst->result_id();
|
|
|
|
|
+ error_blk_id = TakeNextId();
|
|
|
|
|
+ merge_blk_id = TakeNextId();
|
|
|
|
|
+ merge_label = NewLabel(merge_blk_id);
|
|
|
|
|
+ error_label = NewLabel(error_blk_id);
|
|
|
|
|
+ (void)builder.AddConditionalBranch(uninit_check_id, error_blk_id,
|
|
|
|
|
+ merge_blk_id, merge_blk_id);
|
|
|
|
|
+ func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
+ new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
|
|
|
+ builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
+ GenDebugStreamWrite(
|
|
|
|
|
+ param_ids[kShaderId], param_ids[kInstructionIndex], param_ids[kStageInfo],
|
|
|
|
|
+ {builder.GetUintConstantId(kInstErrorBindlessUninit), param_ids[kDescSet],
|
|
|
|
|
+ param_ids[kDescBinding], param_ids[kDescIndex],
|
|
|
|
|
+ builder.GetUintConstantId(0), builder.GetUintConstantId(0)},
|
|
|
|
|
+ &builder);
|
|
|
|
|
+ (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, false_id);
|
|
|
|
|
+ func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
|
|
|
|
|
+ // Check for OOB.
|
|
|
|
|
+ new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
|
|
|
+ builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
+ inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpUGreaterThanEqual,
|
|
|
|
|
+ param_ids[kByteOffset], init_status_id);
|
|
|
|
|
+ const uint32_t buf_offset_range_id = inst->result_id();
|
|
|
|
|
+
|
|
|
|
|
+ error_blk_id = TakeNextId();
|
|
|
|
|
+ merge_blk_id = TakeNextId();
|
|
|
|
|
+ merge_label = NewLabel(merge_blk_id);
|
|
|
|
|
+ error_label = NewLabel(error_blk_id);
|
|
|
|
|
+ (void)builder.AddConditionalBranch(buf_offset_range_id, error_blk_id,
|
|
|
|
|
+ merge_blk_id, merge_blk_id);
|
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
+ // Error return
|
|
|
|
|
+ new_blk_ptr = MakeUnique<BasicBlock>(std::move(error_label));
|
|
|
|
|
+ builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
+ GenDebugStreamWrite(
|
|
|
|
|
+ param_ids[kShaderId], param_ids[kInstructionIndex], param_ids[kStageInfo],
|
|
|
|
|
+ {builder.GetUintConstantId(kInstErrorOOB), param_ids[kDescSet],
|
|
|
|
|
+ param_ids[kDescBinding], param_ids[kDescIndex], param_ids[kByteOffset],
|
|
|
|
|
+ init_status_id},
|
|
|
|
|
+ &builder);
|
|
|
|
|
+ (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, false_id);
|
|
|
|
|
+ func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
+
|
|
|
|
|
+ // Success return
|
|
|
|
|
+ new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
|
|
|
|
|
+ builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
|
|
+ (void)builder.AddUnaryOp(0, spv::Op::OpReturnValue, true_id);
|
|
|
|
|
+ func->AddBasicBlock(std::move(new_blk_ptr));
|
|
|
|
|
+
|
|
|
func->SetFunctionEnd(EndFunction());
|
|
func->SetFunctionEnd(EndFunction());
|
|
|
|
|
|
|
|
context()->AddFunction(std::move(func));
|
|
context()->AddFunction(std::move(func));
|
|
|
- context()->AddDebug2Inst(NewGlobalName(func_id, "read_desc_init"));
|
|
|
|
|
|
|
+ context()->AddDebug2Inst(NewGlobalName(func_id, "desc_check"));
|
|
|
|
|
|
|
|
- read_init_func_id_ = func_id;
|
|
|
|
|
|
|
+ desc_check_func_id_ = func_id;
|
|
|
// Make sure function doesn't get processed by
|
|
// Make sure function doesn't get processed by
|
|
|
// InstrumentPass::InstProcessCallTreeFromRoots()
|
|
// InstrumentPass::InstProcessCallTreeFromRoots()
|
|
|
param2output_func_id_[3] = func_id;
|
|
param2output_func_id_[3] = func_id;
|
|
|
- return read_init_func_id_;
|
|
|
|
|
|
|
+ return desc_check_func_id_;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// clang-format off
|
|
// clang-format off
|
|
|
// GLSL:
|
|
// GLSL:
|
|
|
-// result = inst_bindless_read_desc_init(desc_set_id, binding_id, desc_idx_id);
|
|
|
|
|
|
|
+// result = inst_bindless_desc_check(shader_id, inst_idx, stage_info, desc_set, binding, desc_idx, offset);
|
|
|
//
|
|
//
|
|
|
// clang-format on
|
|
// clang-format on
|
|
|
-uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
|
|
|
|
|
- uint32_t desc_idx_id,
|
|
|
|
|
- InstructionBuilder* builder) {
|
|
|
|
|
- const uint32_t func_id = GenDebugReadInitFunctionId();
|
|
|
|
|
|
|
+uint32_t InstBindlessCheckPass::GenDescCheckCall(
|
|
|
|
|
+ uint32_t inst_idx, uint32_t stage_idx, uint32_t var_id,
|
|
|
|
|
+ uint32_t desc_idx_id, uint32_t offset_id, InstructionBuilder* builder) {
|
|
|
|
|
+ const uint32_t func_id = GenDescCheckFunctionId();
|
|
|
const std::vector<uint32_t> args = {
|
|
const std::vector<uint32_t> args = {
|
|
|
|
|
+ builder->GetUintConstantId(shader_id_),
|
|
|
|
|
+ builder->GetUintConstantId(inst_idx),
|
|
|
|
|
+ GenStageInfo(stage_idx, builder),
|
|
|
builder->GetUintConstantId(var2desc_set_[var_id]),
|
|
builder->GetUintConstantId(var2desc_set_[var_id]),
|
|
|
builder->GetUintConstantId(var2binding_[var_id]),
|
|
builder->GetUintConstantId(var2binding_[var_id]),
|
|
|
- GenUintCastCode(desc_idx_id, builder)};
|
|
|
|
|
- return GenReadFunctionCall(func_id, args, builder);
|
|
|
|
|
|
|
+ GenUintCastCode(desc_idx_id, builder),
|
|
|
|
|
+ offset_id};
|
|
|
|
|
+ return GenReadFunctionCall(GetBoolId(), func_id, args, builder);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
uint32_t InstBindlessCheckPass::CloneOriginalImage(
|
|
uint32_t InstBindlessCheckPass::CloneOriginalImage(
|
|
@@ -1047,29 +964,30 @@ void InstBindlessCheckPass::GenCheckCode(
|
|
|
// Gen invalid block
|
|
// Gen invalid block
|
|
|
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
|
|
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
|
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
builder.SetInsertPoint(&*new_blk_ptr);
|
|
|
- const uint32_t u_set_id = builder.GetUintConstantId(ref->set);
|
|
|
|
|
- const uint32_t u_binding_id = builder.GetUintConstantId(ref->binding);
|
|
|
|
|
- const uint32_t u_index_id = GenUintCastCode(ref->desc_idx_id, &builder);
|
|
|
|
|
- const uint32_t u_length_id = GenUintCastCode(length_id, &builder);
|
|
|
|
|
- if (offset_id != 0) {
|
|
|
|
|
- const uint32_t u_offset_id = GenUintCastCode(offset_id, &builder);
|
|
|
|
|
- // Buffer OOB
|
|
|
|
|
- GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
|
|
|
|
|
- {error_id, u_set_id, u_binding_id, u_index_id,
|
|
|
|
|
- u_offset_id, u_length_id},
|
|
|
|
|
- &builder);
|
|
|
|
|
- } else if (buffer_bounds_enabled_ || texel_buffer_enabled_) {
|
|
|
|
|
- // Uninitialized Descriptor - Return additional unused zero so all error
|
|
|
|
|
- // modes will use same debug stream write function
|
|
|
|
|
- GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
|
|
|
|
|
- {error_id, u_set_id, u_binding_id, u_index_id,
|
|
|
|
|
- u_length_id, builder.GetUintConstantId(0)},
|
|
|
|
|
- &builder);
|
|
|
|
|
- } else {
|
|
|
|
|
- // Uninitialized Descriptor - Normal error return
|
|
|
|
|
- GenDebugStreamWrite(
|
|
|
|
|
- uid2offset_[ref->ref_inst->unique_id()], stage_idx,
|
|
|
|
|
- {error_id, u_set_id, u_binding_id, u_index_id, u_length_id}, &builder);
|
|
|
|
|
|
|
+ if (error_id != 0) {
|
|
|
|
|
+ const uint32_t u_shader_id = builder.GetUintConstantId(shader_id_);
|
|
|
|
|
+ const uint32_t u_inst_id =
|
|
|
|
|
+ builder.GetUintConstantId(ref->ref_inst->unique_id());
|
|
|
|
|
+ const uint32_t shader_info_id = GenStageInfo(stage_idx, &builder);
|
|
|
|
|
+ const uint32_t u_set_id = builder.GetUintConstantId(ref->set);
|
|
|
|
|
+ const uint32_t u_binding_id = builder.GetUintConstantId(ref->binding);
|
|
|
|
|
+ const uint32_t u_index_id = GenUintCastCode(ref->desc_idx_id, &builder);
|
|
|
|
|
+ const uint32_t u_length_id = GenUintCastCode(length_id, &builder);
|
|
|
|
|
+ if (offset_id != 0) {
|
|
|
|
|
+ const uint32_t u_offset_id = GenUintCastCode(offset_id, &builder);
|
|
|
|
|
+ // Buffer OOB
|
|
|
|
|
+ GenDebugStreamWrite(u_shader_id, u_inst_id, shader_info_id,
|
|
|
|
|
+ {error_id, u_set_id, u_binding_id, u_index_id,
|
|
|
|
|
+ u_offset_id, u_length_id},
|
|
|
|
|
+ &builder);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Uninitialized Descriptor - Return additional unused zero so all error
|
|
|
|
|
+ // modes will use same debug stream write function
|
|
|
|
|
+ GenDebugStreamWrite(u_shader_id, u_inst_id, shader_info_id,
|
|
|
|
|
+ {error_id, u_set_id, u_binding_id, u_index_id,
|
|
|
|
|
+ u_length_id, builder.GetUintConstantId(0)},
|
|
|
|
|
+ &builder);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
// Generate a ConstantNull, converting to uint64 if the type cannot be a null.
|
|
// Generate a ConstantNull, converting to uint64 if the type cannot be a null.
|
|
|
if (new_ref_id != 0) {
|
|
if (new_ref_id != 0) {
|
|
@@ -1106,77 +1024,42 @@ void InstBindlessCheckPass::GenCheckCode(
|
|
|
context()->KillInst(ref->ref_inst);
|
|
context()->KillInst(ref->ref_inst);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void InstBindlessCheckPass::GenDescIdxCheckCode(
|
|
|
|
|
|
|
+void InstBindlessCheckPass::GenDescCheckCode(
|
|
|
BasicBlock::iterator ref_inst_itr,
|
|
BasicBlock::iterator ref_inst_itr,
|
|
|
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
|
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
|
|
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
|
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
|
|
- // Look for reference through indexed descriptor. If found, analyze and
|
|
|
|
|
- // save components. If not, return.
|
|
|
|
|
|
|
+ // Look for reference through descriptor. If not, return.
|
|
|
RefAnalysis ref;
|
|
RefAnalysis ref;
|
|
|
if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
|
|
if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
|
|
|
- Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
|
|
|
|
|
- if (ptr_inst->opcode() != spv::Op::OpAccessChain) return;
|
|
|
|
|
- // If index and bound both compile-time constants and index < bound,
|
|
|
|
|
- // return without changing
|
|
|
|
|
- Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
|
|
|
|
|
- Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
|
|
|
|
|
- uint32_t length_id = 0;
|
|
|
|
|
- if (desc_type_inst->opcode() == spv::Op::OpTypeArray) {
|
|
|
|
|
- length_id =
|
|
|
|
|
- desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
|
|
|
|
|
- Instruction* index_inst = get_def_use_mgr()->GetDef(ref.desc_idx_id);
|
|
|
|
|
- Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
|
|
|
|
|
- if (index_inst->opcode() == spv::Op::OpConstant &&
|
|
|
|
|
- length_inst->opcode() == spv::Op::OpConstant &&
|
|
|
|
|
- index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
|
|
|
|
|
- length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
|
|
|
|
|
- return;
|
|
|
|
|
- } else if (!desc_idx_enabled_ ||
|
|
|
|
|
- desc_type_inst->opcode() != spv::Op::OpTypeRuntimeArray) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- // Move original block's preceding instructions into first new block
|
|
|
|
|
std::unique_ptr<BasicBlock> new_blk_ptr;
|
|
std::unique_ptr<BasicBlock> new_blk_ptr;
|
|
|
|
|
+ // Move original block's preceding instructions into first new block
|
|
|
MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
|
|
MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
|
|
|
InstructionBuilder builder(
|
|
InstructionBuilder builder(
|
|
|
context(), &*new_blk_ptr,
|
|
context(), &*new_blk_ptr,
|
|
|
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
|
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
|
|
new_blocks->push_back(std::move(new_blk_ptr));
|
|
new_blocks->push_back(std::move(new_blk_ptr));
|
|
|
- uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
|
|
|
|
|
- // If length id not yet set, descriptor array is runtime size so
|
|
|
|
|
- // generate load of length from stage's debug input buffer.
|
|
|
|
|
- if (length_id == 0) {
|
|
|
|
|
- assert(desc_type_inst->opcode() == spv::Op::OpTypeRuntimeArray &&
|
|
|
|
|
- "unexpected bindless type");
|
|
|
|
|
- length_id = GenDebugReadLength(ref.var_id, &builder);
|
|
|
|
|
- }
|
|
|
|
|
- // Generate full runtime bounds test code with true branch
|
|
|
|
|
- // being full reference and false branch being debug output and zero
|
|
|
|
|
- // for the referenced value.
|
|
|
|
|
- uint32_t desc_idx_32b_id = Gen32BitCvtCode(ref.desc_idx_id, &builder);
|
|
|
|
|
- uint32_t length_32b_id = Gen32BitCvtCode(length_id, &builder);
|
|
|
|
|
- Instruction* ult_inst = builder.AddBinaryOp(GetBoolId(), spv::Op::OpULessThan,
|
|
|
|
|
- desc_idx_32b_id, length_32b_id);
|
|
|
|
|
- ref.desc_idx_id = desc_idx_32b_id;
|
|
|
|
|
- GenCheckCode(ult_inst->result_id(), error_id, 0u, length_id, stage_idx, &ref,
|
|
|
|
|
- new_blocks);
|
|
|
|
|
- // Move original block's remaining code into remainder/merge block and add
|
|
|
|
|
- // to new blocks
|
|
|
|
|
- BasicBlock* back_blk_ptr = &*new_blocks->back();
|
|
|
|
|
- MovePostludeCode(ref_block_itr, back_blk_ptr);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void InstBindlessCheckPass::GenDescInitCheckCode(
|
|
|
|
|
- BasicBlock::iterator ref_inst_itr,
|
|
|
|
|
- UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
|
|
|
|
- std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
|
|
|
|
- // Look for reference through descriptor. If not, return.
|
|
|
|
|
- RefAnalysis ref;
|
|
|
|
|
- if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
|
|
|
|
|
// Determine if we can only do initialization check
|
|
// Determine if we can only do initialization check
|
|
|
- bool init_check = false;
|
|
|
|
|
- if (ref.desc_load_id != 0 || !buffer_bounds_enabled_) {
|
|
|
|
|
- init_check = true;
|
|
|
|
|
|
|
+ uint32_t ref_id = builder.GetUintConstantId(0u);
|
|
|
|
|
+ spv::Op op = ref.ref_inst->opcode();
|
|
|
|
|
+ if (ref.desc_load_id != 0) {
|
|
|
|
|
+ uint32_t num_in_oprnds = ref.ref_inst->NumInOperands();
|
|
|
|
|
+ if ((op == spv::Op::OpImageRead && num_in_oprnds == 2) ||
|
|
|
|
|
+ (op == spv::Op::OpImageFetch && num_in_oprnds == 2) ||
|
|
|
|
|
+ (op == spv::Op::OpImageWrite && num_in_oprnds == 3)) {
|
|
|
|
|
+ Instruction* image_inst = get_def_use_mgr()->GetDef(ref.image_id);
|
|
|
|
|
+ uint32_t image_ty_id = image_inst->type_id();
|
|
|
|
|
+ Instruction* image_ty_inst = get_def_use_mgr()->GetDef(image_ty_id);
|
|
|
|
|
+ if (spv::Dim(image_ty_inst->GetSingleWordInOperand(kSpvTypeImageDim)) ==
|
|
|
|
|
+ spv::Dim::Buffer) {
|
|
|
|
|
+ if ((image_ty_inst->GetSingleWordInOperand(kSpvTypeImageDepth) == 0) &&
|
|
|
|
|
+ (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageArrayed) ==
|
|
|
|
|
+ 0) &&
|
|
|
|
|
+ (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageMS) == 0)) {
|
|
|
|
|
+ ref_id = GenUintCastCode(ref.ref_inst->GetSingleWordInOperand(1),
|
|
|
|
|
+ &builder);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
// For now, only do bounds check for non-aggregate types. Otherwise
|
|
// For now, only do bounds check for non-aggregate types. Otherwise
|
|
|
// just do descriptor initialization check.
|
|
// just do descriptor initialization check.
|
|
@@ -1184,106 +1067,24 @@ void InstBindlessCheckPass::GenDescInitCheckCode(
|
|
|
Instruction* ref_ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
|
|
Instruction* ref_ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
|
|
|
Instruction* pte_type_inst = GetPointeeTypeInst(ref_ptr_inst);
|
|
Instruction* pte_type_inst = GetPointeeTypeInst(ref_ptr_inst);
|
|
|
spv::Op pte_type_op = pte_type_inst->opcode();
|
|
spv::Op pte_type_op = pte_type_inst->opcode();
|
|
|
- if (pte_type_op == spv::Op::OpTypeArray ||
|
|
|
|
|
- pte_type_op == spv::Op::OpTypeRuntimeArray ||
|
|
|
|
|
- pte_type_op == spv::Op::OpTypeStruct)
|
|
|
|
|
- init_check = true;
|
|
|
|
|
|
|
+ if (pte_type_op != spv::Op::OpTypeArray &&
|
|
|
|
|
+ pte_type_op != spv::Op::OpTypeRuntimeArray &&
|
|
|
|
|
+ pte_type_op != spv::Op::OpTypeStruct) {
|
|
|
|
|
+ ref_id = GenLastByteIdx(&ref, &builder);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- // If initialization check and not enabled, return
|
|
|
|
|
- if (init_check && !desc_init_enabled_) return;
|
|
|
|
|
- // Move original block's preceding instructions into first new block
|
|
|
|
|
- std::unique_ptr<BasicBlock> new_blk_ptr;
|
|
|
|
|
- MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
|
|
|
|
|
- InstructionBuilder builder(
|
|
|
|
|
- context(), &*new_blk_ptr,
|
|
|
|
|
- IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
|
|
|
|
- new_blocks->push_back(std::move(new_blk_ptr));
|
|
|
|
|
- // If initialization check, use reference value of zero.
|
|
|
|
|
- // Else use the index of the last byte referenced.
|
|
|
|
|
- uint32_t ref_id = init_check ? builder.GetUintConstantId(0u)
|
|
|
|
|
- : GenLastByteIdx(&ref, &builder);
|
|
|
|
|
// Read initialization/bounds from debug input buffer. If index id not yet
|
|
// Read initialization/bounds from debug input buffer. If index id not yet
|
|
|
// set, binding is single descriptor, so set index to constant 0.
|
|
// set, binding is single descriptor, so set index to constant 0.
|
|
|
if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u);
|
|
if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u);
|
|
|
- uint32_t init_id = GenDebugReadInit(ref.var_id, ref.desc_idx_id, &builder);
|
|
|
|
|
- // Generate runtime initialization/bounds test code with true branch
|
|
|
|
|
- // being full reference and false branch being debug output and zero
|
|
|
|
|
- // for the referenced value.
|
|
|
|
|
- Instruction* ult_inst =
|
|
|
|
|
- builder.AddBinaryOp(GetBoolId(), spv::Op::OpULessThan, ref_id, init_id);
|
|
|
|
|
- uint32_t error =
|
|
|
|
|
- init_check
|
|
|
|
|
- ? kInstErrorBindlessUninit
|
|
|
|
|
- : (spv::StorageClass(ref.strg_class) == spv::StorageClass::Uniform
|
|
|
|
|
- ? kInstErrorBuffOOBUniform
|
|
|
|
|
- : kInstErrorBuffOOBStorage);
|
|
|
|
|
- uint32_t error_id = builder.GetUintConstantId(error);
|
|
|
|
|
- GenCheckCode(ult_inst->result_id(), error_id, init_check ? 0 : ref_id,
|
|
|
|
|
- init_check ? builder.GetUintConstantId(0u) : init_id, stage_idx,
|
|
|
|
|
- &ref, new_blocks);
|
|
|
|
|
- // Move original block's remaining code into remainder/merge block and add
|
|
|
|
|
- // to new blocks
|
|
|
|
|
- BasicBlock* back_blk_ptr = &*new_blocks->back();
|
|
|
|
|
- MovePostludeCode(ref_block_itr, back_blk_ptr);
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ uint32_t check_id =
|
|
|
|
|
+ GenDescCheckCall(ref.ref_inst->unique_id(), stage_idx, ref.var_id,
|
|
|
|
|
+ ref.desc_idx_id, ref_id, &builder);
|
|
|
|
|
|
|
|
-void InstBindlessCheckPass::GenTexBuffCheckCode(
|
|
|
|
|
- BasicBlock::iterator ref_inst_itr,
|
|
|
|
|
- UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
|
|
|
|
- std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
|
|
|
|
- // Only process OpImageRead and OpImageWrite with no optional operands
|
|
|
|
|
- Instruction* ref_inst = &*ref_inst_itr;
|
|
|
|
|
- spv::Op op = ref_inst->opcode();
|
|
|
|
|
- uint32_t num_in_oprnds = ref_inst->NumInOperands();
|
|
|
|
|
- if (!((op == spv::Op::OpImageRead && num_in_oprnds == 2) ||
|
|
|
|
|
- (op == spv::Op::OpImageFetch && num_in_oprnds == 2) ||
|
|
|
|
|
- (op == spv::Op::OpImageWrite && num_in_oprnds == 3)))
|
|
|
|
|
- return;
|
|
|
|
|
- // Pull components from descriptor reference
|
|
|
|
|
- RefAnalysis ref;
|
|
|
|
|
- if (!AnalyzeDescriptorReference(ref_inst, &ref)) return;
|
|
|
|
|
- // Only process if image is texel buffer
|
|
|
|
|
- Instruction* image_inst = get_def_use_mgr()->GetDef(ref.image_id);
|
|
|
|
|
- uint32_t image_ty_id = image_inst->type_id();
|
|
|
|
|
- Instruction* image_ty_inst = get_def_use_mgr()->GetDef(image_ty_id);
|
|
|
|
|
- if (spv::Dim(image_ty_inst->GetSingleWordInOperand(kSpvTypeImageDim)) !=
|
|
|
|
|
- spv::Dim::Buffer) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- if (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageDepth) != 0) return;
|
|
|
|
|
- if (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageArrayed) != 0) return;
|
|
|
|
|
- if (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageMS) != 0) return;
|
|
|
|
|
- // Enable ImageQuery Capability if not yet enabled
|
|
|
|
|
- context()->AddCapability(spv::Capability::ImageQuery);
|
|
|
|
|
- // Move original block's preceding instructions into first new block
|
|
|
|
|
- std::unique_ptr<BasicBlock> new_blk_ptr;
|
|
|
|
|
- MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
|
|
|
|
|
- InstructionBuilder builder(
|
|
|
|
|
- context(), &*new_blk_ptr,
|
|
|
|
|
- IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
|
|
|
|
- new_blocks->push_back(std::move(new_blk_ptr));
|
|
|
|
|
- // Get texel coordinate
|
|
|
|
|
- uint32_t coord_id =
|
|
|
|
|
- GenUintCastCode(ref_inst->GetSingleWordInOperand(1), &builder);
|
|
|
|
|
- // If index id not yet set, binding is single descriptor, so set index to
|
|
|
|
|
- // constant 0.
|
|
|
|
|
- if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u);
|
|
|
|
|
- // Get texel buffer size.
|
|
|
|
|
- Instruction* size_inst =
|
|
|
|
|
- builder.AddUnaryOp(GetUintId(), spv::Op::OpImageQuerySize, ref.image_id);
|
|
|
|
|
- uint32_t size_id = size_inst->result_id();
|
|
|
|
|
// Generate runtime initialization/bounds test code with true branch
|
|
// Generate runtime initialization/bounds test code with true branch
|
|
|
- // being full reference and false branch being debug output and zero
|
|
|
|
|
|
|
+ // being full reference and false branch being zero
|
|
|
// for the referenced value.
|
|
// for the referenced value.
|
|
|
- Instruction* ult_inst =
|
|
|
|
|
- builder.AddBinaryOp(GetBoolId(), spv::Op::OpULessThan, coord_id, size_id);
|
|
|
|
|
- uint32_t error =
|
|
|
|
|
- (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageSampled) == 2)
|
|
|
|
|
- ? kInstErrorBuffOOBStorageTexel
|
|
|
|
|
- : kInstErrorBuffOOBUniformTexel;
|
|
|
|
|
- uint32_t error_id = builder.GetUintConstantId(error);
|
|
|
|
|
- GenCheckCode(ult_inst->result_id(), error_id, coord_id, size_id, stage_idx,
|
|
|
|
|
- &ref, new_blocks);
|
|
|
|
|
|
|
+ GenCheckCode(check_id, 0, 0, 0, stage_idx, &ref, new_blocks);
|
|
|
|
|
+
|
|
|
// Move original block's remaining code into remainder/merge block and add
|
|
// Move original block's remaining code into remainder/merge block and add
|
|
|
// to new blocks
|
|
// to new blocks
|
|
|
BasicBlock* back_blk_ptr = &*new_blocks->back();
|
|
BasicBlock* back_blk_ptr = &*new_blocks->back();
|
|
@@ -1293,58 +1094,32 @@ void InstBindlessCheckPass::GenTexBuffCheckCode(
|
|
|
void InstBindlessCheckPass::InitializeInstBindlessCheck() {
|
|
void InstBindlessCheckPass::InitializeInstBindlessCheck() {
|
|
|
// Initialize base class
|
|
// Initialize base class
|
|
|
InitializeInstrument();
|
|
InitializeInstrument();
|
|
|
- // If runtime array length support or buffer bounds checking are enabled,
|
|
|
|
|
- // create variable mappings. Length support is always enabled if descriptor
|
|
|
|
|
- // init check is enabled.
|
|
|
|
|
- if (desc_idx_enabled_ || buffer_bounds_enabled_ || texel_buffer_enabled_)
|
|
|
|
|
- for (auto& anno : get_module()->annotations())
|
|
|
|
|
- if (anno.opcode() == spv::Op::OpDecorate) {
|
|
|
|
|
- if (spv::Decoration(anno.GetSingleWordInOperand(1u)) ==
|
|
|
|
|
- spv::Decoration::DescriptorSet) {
|
|
|
|
|
- var2desc_set_[anno.GetSingleWordInOperand(0u)] =
|
|
|
|
|
- anno.GetSingleWordInOperand(2u);
|
|
|
|
|
- } else if (spv::Decoration(anno.GetSingleWordInOperand(1u)) ==
|
|
|
|
|
- spv::Decoration::Binding) {
|
|
|
|
|
- var2binding_[anno.GetSingleWordInOperand(0u)] =
|
|
|
|
|
- anno.GetSingleWordInOperand(2u);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ for (auto& anno : get_module()->annotations()) {
|
|
|
|
|
+ if (anno.opcode() == spv::Op::OpDecorate) {
|
|
|
|
|
+ if (spv::Decoration(anno.GetSingleWordInOperand(1u)) ==
|
|
|
|
|
+ spv::Decoration::DescriptorSet) {
|
|
|
|
|
+ var2desc_set_[anno.GetSingleWordInOperand(0u)] =
|
|
|
|
|
+ anno.GetSingleWordInOperand(2u);
|
|
|
|
|
+ } else if (spv::Decoration(anno.GetSingleWordInOperand(1u)) ==
|
|
|
|
|
+ spv::Decoration::Binding) {
|
|
|
|
|
+ var2binding_[anno.GetSingleWordInOperand(0u)] =
|
|
|
|
|
+ anno.GetSingleWordInOperand(2u);
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Pass::Status InstBindlessCheckPass::ProcessImpl() {
|
|
Pass::Status InstBindlessCheckPass::ProcessImpl() {
|
|
|
- // Perform bindless bounds check on each entry point function in module
|
|
|
|
|
|
|
+ bool modified = false;
|
|
|
InstProcessFunction pfn =
|
|
InstProcessFunction pfn =
|
|
|
[this](BasicBlock::iterator ref_inst_itr,
|
|
[this](BasicBlock::iterator ref_inst_itr,
|
|
|
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
|
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
|
|
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
|
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
|
|
- return GenDescIdxCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
|
|
|
|
|
- new_blocks);
|
|
|
|
|
|
|
+ return GenDescCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
|
|
|
|
|
+ new_blocks);
|
|
|
};
|
|
};
|
|
|
- bool modified = InstProcessEntryPointCallTree(pfn);
|
|
|
|
|
- if (desc_init_enabled_ || buffer_bounds_enabled_) {
|
|
|
|
|
- // Perform descriptor initialization and/or buffer bounds check on each
|
|
|
|
|
- // entry point function in module
|
|
|
|
|
- pfn = [this](BasicBlock::iterator ref_inst_itr,
|
|
|
|
|
- UptrVectorIterator<BasicBlock> ref_block_itr,
|
|
|
|
|
- uint32_t stage_idx,
|
|
|
|
|
- std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
|
|
|
|
- return GenDescInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
|
|
|
|
|
- new_blocks);
|
|
|
|
|
- };
|
|
|
|
|
- modified |= InstProcessEntryPointCallTree(pfn);
|
|
|
|
|
- }
|
|
|
|
|
- if (texel_buffer_enabled_) {
|
|
|
|
|
- // Perform texel buffer bounds check on each entry point function in
|
|
|
|
|
- // module. Generate after descriptor bounds and initialization checks.
|
|
|
|
|
- pfn = [this](BasicBlock::iterator ref_inst_itr,
|
|
|
|
|
- UptrVectorIterator<BasicBlock> ref_block_itr,
|
|
|
|
|
- uint32_t stage_idx,
|
|
|
|
|
- std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
|
|
|
|
- return GenTexBuffCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
|
|
|
|
|
- new_blocks);
|
|
|
|
|
- };
|
|
|
|
|
- modified |= InstProcessEntryPointCallTree(pfn);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ modified = InstProcessEntryPointCallTree(pfn);
|
|
|
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
|
|
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
|
|
|
}
|
|
}
|
|
|
|
|
|