2016-06-28 14:23:13 +00:00
|
|
|
// Copyright (c) 2016 Google Inc.
|
|
|
|
//
|
2016-09-01 19:33:59 +00:00
|
|
|
// 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
|
2016-06-28 14:23:13 +00:00
|
|
|
//
|
2016-09-01 19:33:59 +00:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2016-06-28 14:23:13 +00:00
|
|
|
//
|
2016-09-01 19:33:59 +00:00
|
|
|
// 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.
|
2016-06-28 14:23:13 +00:00
|
|
|
|
2018-08-03 12:05:33 +00:00
|
|
|
#ifndef INCLUDE_SPIRV_TOOLS_LIBSPIRV_HPP_
|
|
|
|
#define INCLUDE_SPIRV_TOOLS_LIBSPIRV_HPP_
|
2016-06-28 14:23:13 +00:00
|
|
|
|
2016-09-16 20:12:04 +00:00
|
|
|
#include <functional>
|
2016-09-09 14:46:23 +00:00
|
|
|
#include <memory>
|
2016-06-28 14:23:13 +00:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "spirv-tools/libspirv.h"
|
|
|
|
|
|
|
|
namespace spvtools {
|
|
|
|
|
2016-09-16 20:12:04 +00:00
|
|
|
// Message consumer. The C strings for source and message are only alive for the
|
|
|
|
// specific invocation.
|
|
|
|
using MessageConsumer = std::function<void(
|
|
|
|
spv_message_level_t /* level */, const char* /* source */,
|
|
|
|
const spv_position_t& /* position */, const char* /* message */
|
|
|
|
)>;
|
|
|
|
|
2018-01-03 00:54:55 +00:00
|
|
|
// C++ RAII wrapper around the C context object spv_context.
|
|
|
|
class Context {
|
|
|
|
public:
|
|
|
|
// Constructs a context targeting the given environment |env|.
|
|
|
|
//
|
2022-01-26 20:13:08 +00:00
|
|
|
// See specific API calls for how the target environment is interpreted
|
2020-01-24 21:26:07 +00:00
|
|
|
// (particularly assembly and validation).
|
|
|
|
//
|
2018-01-03 00:54:55 +00:00
|
|
|
// The constructed instance will have an empty message consumer, which just
|
|
|
|
// ignores all messages from the library. Use SetMessageConsumer() to supply
|
|
|
|
// one if messages are of concern.
|
|
|
|
explicit Context(spv_target_env env);
|
|
|
|
|
|
|
|
// Enables move constructor/assignment operations.
|
|
|
|
Context(Context&& other);
|
|
|
|
Context& operator=(Context&& other);
|
|
|
|
|
|
|
|
// Disables copy constructor/assignment operations.
|
|
|
|
Context(const Context&) = delete;
|
|
|
|
Context& operator=(const Context&) = delete;
|
|
|
|
|
|
|
|
// Destructs this instance.
|
|
|
|
~Context();
|
|
|
|
|
|
|
|
// Sets the message consumer to the given |consumer|. The |consumer| will be
|
|
|
|
// invoked once for each message communicated from the library.
|
|
|
|
void SetMessageConsumer(MessageConsumer consumer);
|
|
|
|
|
|
|
|
// Returns the underlying spv_context.
|
|
|
|
spv_context& CContext();
|
|
|
|
const spv_context& CContext() const;
|
|
|
|
|
|
|
|
private:
|
|
|
|
spv_context context_;
|
|
|
|
};
|
|
|
|
|
2017-03-16 19:06:12 +00:00
|
|
|
// A RAII wrapper around a validator options object.
|
|
|
|
class ValidatorOptions {
|
|
|
|
public:
|
|
|
|
ValidatorOptions() : options_(spvValidatorOptionsCreate()) {}
|
|
|
|
~ValidatorOptions() { spvValidatorOptionsDestroy(options_); }
|
|
|
|
// Allow implicit conversion to the underlying object.
|
|
|
|
operator spv_validator_options() const { return options_; }
|
|
|
|
|
|
|
|
// Sets a limit.
|
|
|
|
void SetUniversalLimit(spv_validator_limit limit_type, uint32_t limit) {
|
|
|
|
spvValidatorOptionsSetUniversalLimit(options_, limit_type, limit);
|
|
|
|
}
|
|
|
|
|
2017-10-24 19:13:13 +00:00
|
|
|
void SetRelaxStructStore(bool val) {
|
|
|
|
spvValidatorOptionsSetRelaxStoreStruct(options_, val);
|
|
|
|
}
|
|
|
|
|
2018-07-11 20:53:19 +00:00
|
|
|
// Enables VK_KHR_relaxed_block_layout when validating standard
|
2018-10-10 00:43:09 +00:00
|
|
|
// uniform/storage buffer/push-constant layout. If true, disables
|
|
|
|
// scalar block layout rules.
|
2018-05-17 07:49:19 +00:00
|
|
|
void SetRelaxBlockLayout(bool val) {
|
|
|
|
spvValidatorOptionsSetRelaxBlockLayout(options_, val);
|
|
|
|
}
|
|
|
|
|
2019-05-08 22:01:10 +00:00
|
|
|
// Enables VK_KHR_uniform_buffer_standard_layout when validating standard
|
|
|
|
// uniform layout. If true, disables scalar block layout rules.
|
|
|
|
void SetUniformBufferStandardLayout(bool val) {
|
|
|
|
spvValidatorOptionsSetUniformBufferStandardLayout(options_, val);
|
|
|
|
}
|
|
|
|
|
2018-10-10 00:43:09 +00:00
|
|
|
// Enables VK_EXT_scalar_block_layout when validating standard
|
|
|
|
// uniform/storage buffer/push-constant layout. If true, disables
|
|
|
|
// relaxed block layout rules.
|
|
|
|
void SetScalarBlockLayout(bool val) {
|
|
|
|
spvValidatorOptionsSetScalarBlockLayout(options_, val);
|
|
|
|
}
|
|
|
|
|
2021-01-28 00:38:38 +00:00
|
|
|
// Enables scalar layout when validating Workgroup blocks. See
|
|
|
|
// VK_KHR_workgroup_memory_explicit_layout.
|
|
|
|
void SetWorkgroupScalarBlockLayout(bool val) {
|
|
|
|
spvValidatorOptionsSetWorkgroupScalarBlockLayout(options_, val);
|
|
|
|
}
|
|
|
|
|
2018-10-10 00:43:09 +00:00
|
|
|
// Skips validating standard uniform/storage buffer/push-constant layout.
|
2018-07-11 20:53:19 +00:00
|
|
|
void SetSkipBlockLayout(bool val) {
|
|
|
|
spvValidatorOptionsSetSkipBlockLayout(options_, val);
|
|
|
|
}
|
|
|
|
|
2021-08-26 18:33:19 +00:00
|
|
|
// Enables LocalSizeId decorations where the environment would not otherwise
|
|
|
|
// allow them.
|
|
|
|
void SetAllowLocalSizeId(bool val) {
|
|
|
|
spvValidatorOptionsSetAllowLocalSizeId(options_, val);
|
|
|
|
}
|
|
|
|
|
2018-01-07 15:50:01 +00:00
|
|
|
// Records whether or not the validator should relax the rules on pointer
|
|
|
|
// usage in logical addressing mode.
|
|
|
|
//
|
|
|
|
// When relaxed, it will allow the following usage cases of pointers:
|
|
|
|
// 1) OpVariable allocating an object whose type is a pointer type
|
|
|
|
// 2) OpReturnValue returning a pointer value
|
|
|
|
void SetRelaxLogicalPointer(bool val) {
|
|
|
|
spvValidatorOptionsSetRelaxLogicalPointer(options_, val);
|
|
|
|
}
|
|
|
|
|
2019-05-13 17:48:17 +00:00
|
|
|
// Records whether or not the validator should relax the rules because it is
|
|
|
|
// expected that the optimizations will make the code legal.
|
|
|
|
//
|
|
|
|
// When relaxed, it will allow the following:
|
|
|
|
// 1) It will allow relaxed logical pointers. Setting this option will also
|
|
|
|
// set that option.
|
|
|
|
// 2) Pointers that are pass as parameters to function calls do not have to
|
|
|
|
// match the storage class of the formal parameter.
|
2022-01-26 20:13:08 +00:00
|
|
|
// 3) Pointers that are actual parameters on function calls do not have to
|
2019-05-13 17:48:17 +00:00
|
|
|
// point to the same type pointed as the formal parameter. The types just
|
|
|
|
// need to logically match.
|
2021-03-31 18:26:36 +00:00
|
|
|
// 4) GLSLstd450 Interpolate* instructions can have a load of an interpolant
|
|
|
|
// for a first argument.
|
2019-05-13 17:48:17 +00:00
|
|
|
void SetBeforeHlslLegalization(bool val) {
|
|
|
|
spvValidatorOptionsSetBeforeHlslLegalization(options_, val);
|
|
|
|
}
|
|
|
|
|
2017-03-16 19:06:12 +00:00
|
|
|
private:
|
|
|
|
spv_validator_options options_;
|
|
|
|
};
|
|
|
|
|
2018-09-10 15:49:41 +00:00
|
|
|
// A C++ wrapper around an optimization options object.
|
|
|
|
class OptimizerOptions {
|
|
|
|
public:
|
|
|
|
OptimizerOptions() : options_(spvOptimizerOptionsCreate()) {}
|
|
|
|
~OptimizerOptions() { spvOptimizerOptionsDestroy(options_); }
|
|
|
|
|
|
|
|
// Allow implicit conversion to the underlying object.
|
|
|
|
operator spv_optimizer_options() const { return options_; }
|
|
|
|
|
|
|
|
// Records whether or not the optimizer should run the validator before
|
|
|
|
// optimizing. If |run| is true, the validator will be run.
|
|
|
|
void set_run_validator(bool run) {
|
|
|
|
spvOptimizerOptionsSetRunValidator(options_, run);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Records the validator options that should be passed to the validator if it
|
|
|
|
// is run.
|
|
|
|
void set_validator_options(const ValidatorOptions& val_options) {
|
|
|
|
spvOptimizerOptionsSetValidatorOptions(options_, val_options);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Records the maximum possible value for the id bound.
|
|
|
|
void set_max_id_bound(uint32_t new_bound) {
|
|
|
|
spvOptimizerOptionsSetMaxIdBound(options_, new_bound);
|
|
|
|
}
|
|
|
|
|
2019-07-10 18:12:19 +00:00
|
|
|
// Records whether all bindings within the module should be preserved.
|
|
|
|
void set_preserve_bindings(bool preserve_bindings) {
|
|
|
|
spvOptimizerOptionsSetPreserveBindings(options_, preserve_bindings);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Records whether all specialization constants within the module
|
|
|
|
// should be preserved.
|
|
|
|
void set_preserve_spec_constants(bool preserve_spec_constants) {
|
|
|
|
spvOptimizerOptionsSetPreserveSpecConstants(options_,
|
|
|
|
preserve_spec_constants);
|
|
|
|
}
|
|
|
|
|
2018-09-10 15:49:41 +00:00
|
|
|
private:
|
|
|
|
spv_optimizer_options options_;
|
|
|
|
};
|
|
|
|
|
2018-11-21 19:03:09 +00:00
|
|
|
// A C++ wrapper around a reducer options object.
|
|
|
|
class ReducerOptions {
|
|
|
|
public:
|
|
|
|
ReducerOptions() : options_(spvReducerOptionsCreate()) {}
|
|
|
|
~ReducerOptions() { spvReducerOptionsDestroy(options_); }
|
|
|
|
|
|
|
|
// Allow implicit conversion to the underlying object.
|
2019-03-26 13:22:31 +00:00
|
|
|
operator spv_reducer_options() const { // NOLINT(google-explicit-constructor)
|
|
|
|
return options_;
|
|
|
|
}
|
2018-11-21 19:03:09 +00:00
|
|
|
|
2019-03-26 13:22:31 +00:00
|
|
|
// See spvReducerOptionsSetStepLimit.
|
2018-11-21 19:03:09 +00:00
|
|
|
void set_step_limit(uint32_t step_limit) {
|
|
|
|
spvReducerOptionsSetStepLimit(options_, step_limit);
|
|
|
|
}
|
|
|
|
|
2019-03-26 13:22:31 +00:00
|
|
|
// See spvReducerOptionsSetFailOnValidationError.
|
|
|
|
void set_fail_on_validation_error(bool fail_on_validation_error) {
|
|
|
|
spvReducerOptionsSetFailOnValidationError(options_,
|
|
|
|
fail_on_validation_error);
|
|
|
|
}
|
2018-11-21 19:03:09 +00:00
|
|
|
|
2020-09-11 05:29:43 +00:00
|
|
|
// See spvReducerOptionsSetTargetFunction.
|
|
|
|
void set_target_function(uint32_t target_function) {
|
|
|
|
spvReducerOptionsSetTargetFunction(options_, target_function);
|
|
|
|
}
|
|
|
|
|
2018-11-21 19:03:09 +00:00
|
|
|
private:
|
|
|
|
spv_reducer_options options_;
|
|
|
|
};
|
|
|
|
|
2019-05-27 13:34:55 +00:00
|
|
|
// A C++ wrapper around a fuzzer options object.
|
|
|
|
class FuzzerOptions {
|
|
|
|
public:
|
|
|
|
FuzzerOptions() : options_(spvFuzzerOptionsCreate()) {}
|
|
|
|
~FuzzerOptions() { spvFuzzerOptionsDestroy(options_); }
|
|
|
|
|
|
|
|
// Allow implicit conversion to the underlying object.
|
|
|
|
operator spv_fuzzer_options() const { // NOLINT(google-explicit-constructor)
|
|
|
|
return options_;
|
|
|
|
}
|
|
|
|
|
2019-09-20 09:54:09 +00:00
|
|
|
// See spvFuzzerOptionsEnableReplayValidation.
|
|
|
|
void enable_replay_validation() {
|
|
|
|
spvFuzzerOptionsEnableReplayValidation(options_);
|
|
|
|
}
|
|
|
|
|
2019-05-27 13:34:55 +00:00
|
|
|
// See spvFuzzerOptionsSetRandomSeed.
|
|
|
|
void set_random_seed(uint32_t seed) {
|
|
|
|
spvFuzzerOptionsSetRandomSeed(options_, seed);
|
|
|
|
}
|
|
|
|
|
2020-07-15 11:13:23 +00:00
|
|
|
// See spvFuzzerOptionsSetReplayRange.
|
|
|
|
void set_replay_range(int32_t replay_range) {
|
|
|
|
spvFuzzerOptionsSetReplayRange(options_, replay_range);
|
|
|
|
}
|
|
|
|
|
2019-07-07 07:55:30 +00:00
|
|
|
// See spvFuzzerOptionsSetShrinkerStepLimit.
|
|
|
|
void set_shrinker_step_limit(uint32_t shrinker_step_limit) {
|
|
|
|
spvFuzzerOptionsSetShrinkerStepLimit(options_, shrinker_step_limit);
|
|
|
|
}
|
|
|
|
|
2019-11-27 18:05:56 +00:00
|
|
|
// See spvFuzzerOptionsEnableFuzzerPassValidation.
|
|
|
|
void enable_fuzzer_pass_validation() {
|
|
|
|
spvFuzzerOptionsEnableFuzzerPassValidation(options_);
|
|
|
|
}
|
|
|
|
|
2020-09-18 14:51:35 +00:00
|
|
|
// See spvFuzzerOptionsEnableAllPasses.
|
|
|
|
void enable_all_passes() { spvFuzzerOptionsEnableAllPasses(options_); }
|
|
|
|
|
2019-05-27 13:34:55 +00:00
|
|
|
private:
|
|
|
|
spv_fuzzer_options options_;
|
|
|
|
};
|
|
|
|
|
2016-06-28 14:23:13 +00:00
|
|
|
// C++ interface for SPIRV-Tools functionalities. It wraps the context
|
|
|
|
// (including target environment and the corresponding SPIR-V grammar) and
|
2016-09-09 14:46:23 +00:00
|
|
|
// provides methods for assembling, disassembling, and validating.
|
2016-06-28 14:23:13 +00:00
|
|
|
//
|
2016-09-09 14:46:23 +00:00
|
|
|
// Instances of this class provide basic thread-safety guarantee.
|
2016-09-16 19:56:30 +00:00
|
|
|
class SpirvTools {
|
2016-06-28 14:23:13 +00:00
|
|
|
public:
|
2016-09-09 14:46:23 +00:00
|
|
|
enum {
|
2017-04-11 23:46:15 +00:00
|
|
|
// Default assembling option used by assemble():
|
|
|
|
kDefaultAssembleOption = SPV_TEXT_TO_BINARY_OPTION_NONE,
|
|
|
|
|
2016-09-09 14:46:23 +00:00
|
|
|
// Default disassembling option used by Disassemble():
|
|
|
|
// * Avoid prefix comments from decoding the SPIR-V module header, and
|
|
|
|
// * Use friendly names for variables.
|
|
|
|
kDefaultDisassembleOption = SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
|
|
|
|
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES
|
|
|
|
};
|
2016-06-28 14:23:13 +00:00
|
|
|
|
2016-09-09 14:46:23 +00:00
|
|
|
// Constructs an instance targeting the given environment |env|.
|
|
|
|
//
|
|
|
|
// The constructed instance will have an empty message consumer, which just
|
|
|
|
// ignores all messages from the library. Use SetMessageConsumer() to supply
|
|
|
|
// one if messages are of concern.
|
2016-09-16 19:56:30 +00:00
|
|
|
explicit SpirvTools(spv_target_env env);
|
2016-06-28 14:23:13 +00:00
|
|
|
|
2016-09-09 14:46:23 +00:00
|
|
|
// Disables copy/move constructor/assignment operations.
|
2016-09-16 19:56:30 +00:00
|
|
|
SpirvTools(const SpirvTools&) = delete;
|
|
|
|
SpirvTools(SpirvTools&&) = delete;
|
|
|
|
SpirvTools& operator=(const SpirvTools&) = delete;
|
|
|
|
SpirvTools& operator=(SpirvTools&&) = delete;
|
2016-09-09 14:46:23 +00:00
|
|
|
|
|
|
|
// Destructs this instance.
|
2016-09-16 19:56:30 +00:00
|
|
|
~SpirvTools();
|
2016-09-09 14:46:23 +00:00
|
|
|
|
|
|
|
// Sets the message consumer to the given |consumer|. The |consumer| will be
|
|
|
|
// invoked once for each message communicated from the library.
|
2016-09-02 22:06:18 +00:00
|
|
|
void SetMessageConsumer(MessageConsumer consumer);
|
2016-06-28 14:23:13 +00:00
|
|
|
|
|
|
|
// Assembles the given assembly |text| and writes the result to |binary|.
|
2016-09-09 14:46:23 +00:00
|
|
|
// Returns true on successful assembling. |binary| will be kept untouched if
|
|
|
|
// assembling is unsuccessful.
|
2020-01-24 21:26:07 +00:00
|
|
|
// The SPIR-V binary version is set to the highest version of SPIR-V supported
|
|
|
|
// by the target environment with which this SpirvTools object was created.
|
2017-04-11 23:46:15 +00:00
|
|
|
bool Assemble(const std::string& text, std::vector<uint32_t>* binary,
|
|
|
|
uint32_t options = kDefaultAssembleOption) const;
|
2016-09-20 22:03:37 +00:00
|
|
|
// |text_size| specifies the number of bytes in |text|. A terminating null
|
|
|
|
// character is not required to present in |text| as long as |text| is valid.
|
2020-01-24 21:26:07 +00:00
|
|
|
// The SPIR-V binary version is set to the highest version of SPIR-V supported
|
|
|
|
// by the target environment with which this SpirvTools object was created.
|
2016-09-20 22:03:37 +00:00
|
|
|
bool Assemble(const char* text, size_t text_size,
|
2017-04-11 23:46:15 +00:00
|
|
|
std::vector<uint32_t>* binary,
|
|
|
|
uint32_t options = kDefaultAssembleOption) const;
|
2016-09-09 14:46:23 +00:00
|
|
|
|
|
|
|
// Disassembles the given SPIR-V |binary| with the given |options| and writes
|
2020-01-24 21:26:07 +00:00
|
|
|
// the assembly to |text|. Returns true on successful disassembling. |text|
|
2016-09-09 14:46:23 +00:00
|
|
|
// will be kept untouched if diassembling is unsuccessful.
|
|
|
|
bool Disassemble(const std::vector<uint32_t>& binary, std::string* text,
|
|
|
|
uint32_t options = kDefaultDisassembleOption) const;
|
2016-09-20 22:03:37 +00:00
|
|
|
// |binary_size| specifies the number of words in |binary|.
|
|
|
|
bool Disassemble(const uint32_t* binary, size_t binary_size,
|
|
|
|
std::string* text,
|
|
|
|
uint32_t options = kDefaultDisassembleOption) const;
|
2016-09-09 14:46:23 +00:00
|
|
|
|
|
|
|
// Validates the given SPIR-V |binary|. Returns true if no issues are found.
|
|
|
|
// Otherwise, returns false and communicates issues via the message consumer
|
|
|
|
// registered.
|
2020-01-24 21:26:07 +00:00
|
|
|
// Validates for SPIR-V spec rules for the SPIR-V version named in the
|
|
|
|
// binary's header (at word offset 1). Additionally, if the target
|
|
|
|
// environment is a client API (such as Vulkan 1.1), then validate for that
|
|
|
|
// client API version, to the extent that it is verifiable from data in the
|
|
|
|
// binary itself.
|
2016-09-09 14:46:23 +00:00
|
|
|
bool Validate(const std::vector<uint32_t>& binary) const;
|
2020-01-24 21:26:07 +00:00
|
|
|
// Like the previous overload, but provides the binary as a pointer and size:
|
2016-09-20 22:03:37 +00:00
|
|
|
// |binary_size| specifies the number of words in |binary|.
|
2020-01-24 21:26:07 +00:00
|
|
|
// Validates for SPIR-V spec rules for the SPIR-V version named in the
|
|
|
|
// binary's header (at word offset 1). Additionally, if the target
|
|
|
|
// environment is a client API (such as Vulkan 1.1), then validate for that
|
|
|
|
// client API version, to the extent that it is verifiable from data in the
|
|
|
|
// binary itself.
|
2016-09-20 22:03:37 +00:00
|
|
|
bool Validate(const uint32_t* binary, size_t binary_size) const;
|
2017-03-16 19:06:12 +00:00
|
|
|
// Like the previous overload, but takes an options object.
|
2020-01-24 21:26:07 +00:00
|
|
|
// Validates for SPIR-V spec rules for the SPIR-V version named in the
|
|
|
|
// binary's header (at word offset 1). Additionally, if the target
|
|
|
|
// environment is a client API (such as Vulkan 1.1), then validate for that
|
|
|
|
// client API version, to the extent that it is verifiable from data in the
|
|
|
|
// binary itself, or in the validator options.
|
2017-04-11 23:46:15 +00:00
|
|
|
bool Validate(const uint32_t* binary, size_t binary_size,
|
2018-09-10 15:49:41 +00:00
|
|
|
spv_validator_options options) const;
|
2016-06-28 14:23:13 +00:00
|
|
|
|
2019-01-24 14:45:09 +00:00
|
|
|
// Was this object successfully constructed.
|
|
|
|
bool IsValid() const;
|
|
|
|
|
2016-06-28 14:23:13 +00:00
|
|
|
private:
|
2016-09-09 14:46:23 +00:00
|
|
|
struct Impl; // Opaque struct for holding the data fields used by this class.
|
|
|
|
std::unique_ptr<Impl> impl_; // Unique pointer to implementation data.
|
2016-06-28 14:23:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace spvtools
|
|
|
|
|
2018-08-03 12:05:33 +00:00
|
|
|
#endif // INCLUDE_SPIRV_TOOLS_LIBSPIRV_HPP_
|