mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-23 20:20:06 +00:00
84 lines
7.7 KiB
Markdown
84 lines
7.7 KiB
Markdown
# Guide to writing a spirv-fuzz fuzzer pass
|
|
|
|
Writing a spirv-fuzz fuzzer pass usually requires two main contributions:
|
|
|
|
- A *transformation*, capturing a small semantics-preserving change that can be made to a SPIR-V module. This requires adding a protobuf message representing the transformation, and a corresponding class that implements the `Transformation` interface.
|
|
- A new *fuzzer pass* class, implementing the `FuzzerPass` interface, that knows how to walk a SPIR-V module and apply the new transformation in a randomized fashion.
|
|
|
|
In some cases, more than one kind of transformation is required for a single fuzzer pass, and in some cases the transformations that a new fuzzer pass requires have already been introduced by existing passes. But the most common case is to introduce a transformation and fuzzer pass together.
|
|
|
|
As an example, let's consider the `TransformationSetSelectionControl` transformation. In SPIR-V, an `OpSelectionMerge` instruction (which intuitively indicates the start of an `if` or `switch` statement in a function) has a *selection control* mask, that can be one of `None`, `Flatten` or `DontFlatten`. The details of these do not matter much for this little tutorial, but in brief, this parameter provides a hint to the shader compiler as to whether it would be profitable to attempt to flatten a piece of conditional code so that all of its statements are executed in a predicated fashion.
|
|
|
|
As the selection control mask is just a hint, changing the value of this mask should have no semantic impact on the module. The `TransformationSelectionControl` transformation specifies a new value for a given selection control mask.
|
|
|
|
## Adding a new protobuf message
|
|
|
|
Take a look at the `Transformation` message in `spvtoolsfuzz.proto`. This has a `oneof` field that can be any one of the different spirv-fuzz transformations. Observe that one of the options is `TransformationSetSelectionControl`. When adding a transformation you first need to add an option for your transformation to the end of the `oneof` declaration.
|
|
|
|
Now look at the `TransformationSetSelectionControl` message. If adding your own transformation you need to add a new message for your transformation, and it should be placed alphabetically with respect to other transformations.
|
|
|
|
The fields of `TransformationSetSelectionControl` provide just enough information to (a) determine whether a given example of this transformation is actually applicable, and (b) apply the transformation in the case that it is applicable. The details of the transformation message will vary a lot between transformations. In this case, the message has a `block_id` field, specifying a block that must end with `OpSelectionMerge`, and a `selection_control` field, which is the new value for the selection control mask of the `OpSelectionMerge` instruction.
|
|
|
|
## Adding a new transformation class
|
|
|
|
If your transformation is called `TransformationSomeThing`, you need to add `transformation_some_thing.h` and `transformation_some_thing.cpp` to `source/fuzz` and the corresponding `CMakeLists.txt` file. So for `TransformationSetSelectionControl` we have `transformation_selection_control.h` and `transformation_selection_control.cpp`, and we will use this as an example to illustrate the expected contents of these files.
|
|
|
|
The header file contains the specification of a class, `TransformationSetSelectionControl`, that implements the `Transformation` interface (from `transformation.h`).
|
|
|
|
A transformation class should always have a single field, which should be the associated protobuf message; in our case:
|
|
|
|
```
|
|
private:
|
|
protobufs::TransformationSetSelectionControl message_;
|
|
```
|
|
|
|
and two public constructors, one that takes a protobuf message; in our case:
|
|
|
|
```
|
|
explicit TransformationSetSelectionControl(
|
|
const protobufs::TransformationSetSelectionControl& message);
|
|
```
|
|
|
|
and one that takes a parameter for each protobuf message field; in our case:
|
|
|
|
```
|
|
TransformationSetSelectionControl(uint32_t block_id);
|
|
```
|
|
|
|
The first constructor allows an instance of the class to be created from a corresponding protobuf message. The second should provide the ingredients necessary to populate a protobuf message.
|
|
|
|
The class should also override the `IsApplicable`, `Apply` and `ToMessage` methods from `Transformation`.
|
|
|
|
See `transformation_set_selection_control.h` for an example.
|
|
|
|
The `IsApplicable` method should have a comment in the header file describing the conditions for applicability in simple terms. These conditions should be implemented in the body of this method in the `.cpp` file.
|
|
|
|
In the case of `TransformationSetSelectionControl`, `IsApplicable` involves checking that `block_id` is indeed the id of a block that has an `OpSelectionMerge` instruction, and that `selection_control` is a valid selection mask.
|
|
|
|
The `Apply` method should have a comment in the header file summarising the result of applying the transformation. It should be implemented in the `.cpp` file, and you should assume that `IsApplicable` holds whenever `Apply` is invoked.
|
|
|
|
## Writing tests for the transformation class
|
|
|
|
Whenever you add a transformation class, `TransformationSomeThing`, you should add an associated test file, `transformation_some_thing_test.cpp`, under `test/fuzz`, adding it to the associated `CMakeLists.txt` file.
|
|
|
|
For example `test/fuzz/transformation_set_selection_control_test.cpp` contains tests for `TransformationSetSelectionControl`. Your tests should aim to cover one example from each scenario where the transformation is inapplicable, and check that it is indeed deemed inapplicable, and then check that the transformation does the right thing when applied in a few different ways.
|
|
|
|
For example, the tests for `TransformationSetSelectionControl` check that a transformation of this kind is inapplicable if the `block_id` field of the transformation is not a block, or does not end in `OpSelectionMerge`, or if the `selection_control` mask has an illegal value. It also checks that applying a sequence of valid transformations to a SPIR-V shader leads to a shader with appropriately modified selection controls.
|
|
|
|
## Adding a new fuzzer pass class
|
|
|
|
A *fuzzer pass* traverses a SPIR-V module looking for places to apply a certain kind of transformation, and randomly decides at which of these points to actually apply the transformation. It might be necessary to apply other transformations in order to apply a given transformation (for example, if a transformation requires a certain type to be present in the module, said type can be added if not already present via another transformation).
|
|
|
|
A fuzzer pass implements the `FuzzerPass` interface, and overrides its `Apply` method. If your fuzzer pass is named `FuzzerPassSomeThing` then it should be represented by `fuzzer_pass_some_thing.h` and `fuzzer_pass_some_thing.cpp`, under `source/fuzz`; these should be added to the associated `CMakeLists.txt` file.
|
|
|
|
Have a look at the source filed for `FuzzerPassAdjustSelectionControls`. This pass considers every block that ends with `OpSelectionMerge`. It decides randomly whether to adjust the selection control of this merge instruction via:
|
|
|
|
```
|
|
if (!GetFuzzerContext()->ChoosePercentage(
|
|
GetFuzzerContext()->GetChanceOfAdjustingSelectionControl())) {
|
|
continue;
|
|
}
|
|
```
|
|
|
|
The `GetChanceOfAddingSelectionControl()` method has been added to `FuzzerContext` specifically to support this pass, and returns a percentage between 0 and 100. It returns the `chance_of_adjusting_selection_control_` of `FuzzerContext`, which is randomly initialized to lie with the interval defined by `kChanceOfAdjustingSelectionControl` in `fuzzer_context.cpp`. For any pass you write, you will need to add an analogous `GetChanceOf...` method to `FuzzerContext`, backed by an appropriate field, and you will need to decide on lower and upper bounds for this field and specify these via a `kChanceOf...` constant.
|