writer: Move sanitizers into the backends

Adds a new single-function API for the generators, which applies the
sanitizing transform and performs the generation in one step, and
returns a result object which contains the generated code and success
status/diagnostics.

The new APIs take an `Option` structure to control backend-specific
generation details (e.g. MSL fixed sample mask). The result objects
also provide backend-specific feedback (e.g. whether a UBO of buffer
lengths was generated).

HLSL needs a list of entry points to validate, and it's the HLSL
sanitizer that generates an entry point for programs that do not have
one. This change makes the HLSL generator return the list of
post-sanitize entry points so that the Tint executable can forward
them to the validation code.

Change-Id: I2d5aa27fda95d7c50c5bef41e206aee38f2fd2eb
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57101
Auto-Submit: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@chromium.org>
This commit is contained in:
James Price
2021-07-08 16:00:23 +00:00
committed by Tint LUCI CQ
parent 08b0ab9b92
commit af89c729ed
15 changed files with 551 additions and 268 deletions

View File

@@ -144,11 +144,12 @@ let declaration_order_check_3 : i32 = 1;
// Regenerate the wgsl for the src program. We use this instead of the
// original source so that reformatting doesn't impact the final wgsl
// comparison.
writer::wgsl::Options options;
std::string src_wgsl;
{
writer::wgsl::Generator src_gen(&src);
ASSERT_TRUE(src_gen.Generate()) << src_gen.error();
src_wgsl = src_gen.result();
auto result = writer::wgsl::Generate(&src, options);
ASSERT_TRUE(result.success) << result.error;
src_wgsl = result.wgsl;
// Move the src program to a temporary that'll be dropped, so that the src
// program is released before we attempt to print the dst program. This
@@ -159,9 +160,9 @@ let declaration_order_check_3 : i32 = 1;
}
// Print the dst module, check it matches the original source
writer::wgsl::Generator dst_gen(&dst);
ASSERT_TRUE(dst_gen.Generate());
auto dst_wgsl = dst_gen.result();
auto result = writer::wgsl::Generate(&dst, options);
ASSERT_TRUE(result.success);
auto dst_wgsl = result.wgsl;
ASSERT_EQ(src_wgsl, dst_wgsl);
#else // #if TINT_BUILD_WGSL_READER && TINT_BUILD_WGSL_WRITER

View File

@@ -1549,9 +1549,6 @@ TEST_F(InspectorGetEntryPointTest, OverridableConstantReferencedByEntryPoint) {
Inspector& inspector = Build();
tint::writer::wgsl::Generator writer(program_.get());
writer.Generate();
auto result = inspector.GetEntryPoints();
ASSERT_EQ(1u, result.size());

View File

@@ -82,12 +82,13 @@ class TransformTestBase : public BASE {
return diag::Formatter(style).format(output.program.Diagnostics());
}
writer::wgsl::Generator generator(&output.program);
if (!generator.Generate()) {
return "WGSL writer failed:\n" + generator.error();
writer::wgsl::Options options;
auto result = writer::wgsl::Generate(&output.program, options);
if (!result.success) {
return "WGSL writer failed:\n" + result.error;
}
auto res = generator.result();
auto res = result.wgsl;
if (res.empty()) {
return res;
}

View File

@@ -14,8 +14,6 @@
#include "src/val/val.h"
#include "src/ast/module.h"
#include "src/program.h"
#include "src/utils/io/command.h"
#include "src/utils/io/tmpfile.h"
@@ -33,7 +31,7 @@ namespace val {
Result HlslUsingDXC(const std::string& dxc_path,
const std::string& source,
Program* program) {
const EntryPointList& entry_points) {
Result result;
auto dxc = utils::Command(dxc_path);
@@ -48,48 +46,42 @@ Result HlslUsingDXC(const std::string& dxc_path,
utils::TmpFile file;
file << source;
bool found_an_entrypoint = false;
for (auto* func : program->AST().Functions()) {
if (func->IsEntryPoint()) {
found_an_entrypoint = true;
for (auto ep : entry_points) {
const char* profile = "";
const char* profile = "";
switch (func->pipeline_stage()) {
case ast::PipelineStage::kNone:
result.output = "Invalid PipelineStage";
result.failed = true;
return result;
case ast::PipelineStage::kVertex:
profile = "-T vs_6_0";
break;
case ast::PipelineStage::kFragment:
profile = "-T ps_6_0";
break;
case ast::PipelineStage::kCompute:
profile = "-T cs_6_0";
break;
}
auto name = program->Symbols().NameFor(func->symbol());
auto res = dxc(profile, "-E " + name, file.Path());
if (!res.out.empty()) {
if (!result.output.empty()) {
result.output += "\n";
}
result.output += res.out;
}
if (!res.err.empty()) {
if (!result.output.empty()) {
result.output += "\n";
}
result.output += res.err;
}
result.failed = (res.error_code != 0);
switch (ep.second) {
case ast::PipelineStage::kNone:
result.output = "Invalid PipelineStage";
result.failed = true;
return result;
case ast::PipelineStage::kVertex:
profile = "-T vs_6_0";
break;
case ast::PipelineStage::kFragment:
profile = "-T ps_6_0";
break;
case ast::PipelineStage::kCompute:
profile = "-T cs_6_0";
break;
}
auto res = dxc(profile, "-E " + ep.first, file.Path());
if (!res.out.empty()) {
if (!result.output.empty()) {
result.output += "\n";
}
result.output += res.out;
}
if (!res.err.empty()) {
if (!result.output.empty()) {
result.output += "\n";
}
result.output += res.err;
}
result.failed = (res.error_code != 0);
}
if (!found_an_entrypoint) {
if (entry_points.empty()) {
result.output = "No entrypoint found";
result.failed = true;
return result;
@@ -99,7 +91,8 @@ Result HlslUsingDXC(const std::string& dxc_path,
}
#ifdef _WIN32
Result HlslUsingFXC(const std::string& source, Program* program) {
Result HlslUsingFXC(const std::string& source,
const EntryPointList& entry_points) {
Result result;
// This library leaks if an error happens in this function, but it is ok
@@ -122,45 +115,38 @@ Result HlslUsingFXC(const std::string& source, Program* program) {
result.source = source;
bool found_an_entrypoint = false;
for (auto* func : program->AST().Functions()) {
if (func->IsEntryPoint()) {
found_an_entrypoint = true;
const char* profile = "";
switch (func->pipeline_stage()) {
case ast::PipelineStage::kNone:
result.output = "Invalid PipelineStage";
result.failed = true;
return result;
case ast::PipelineStage::kVertex:
profile = "vs_5_1";
break;
case ast::PipelineStage::kFragment:
profile = "ps_5_1";
break;
case ast::PipelineStage::kCompute:
profile = "cs_5_1";
break;
}
auto name = program->Symbols().NameFor(func->symbol());
ComPtr<ID3DBlob> compiledShader;
ComPtr<ID3DBlob> errors;
if (FAILED(d3dCompile(source.c_str(), source.length(), nullptr, nullptr,
nullptr, name.c_str(), profile, 0, 0,
&compiledShader, &errors))) {
result.output = static_cast<char*>(errors->GetBufferPointer());
for (auto ep : entry_points) {
const char* profile = "";
switch (ep.second) {
case ast::PipelineStage::kNone:
result.output = "Invalid PipelineStage";
result.failed = true;
return result;
}
case ast::PipelineStage::kVertex:
profile = "vs_5_1";
break;
case ast::PipelineStage::kFragment:
profile = "ps_5_1";
break;
case ast::PipelineStage::kCompute:
profile = "cs_5_1";
break;
}
ComPtr<ID3DBlob> compiledShader;
ComPtr<ID3DBlob> errors;
if (FAILED(d3dCompile(source.c_str(), source.length(), nullptr, nullptr,
nullptr, ep.first.c_str(), profile, 0, 0,
&compiledShader, &errors))) {
result.output = static_cast<char*>(errors->GetBufferPointer());
result.failed = true;
return result;
}
}
FreeLibrary(fxcLib);
if (!found_an_entrypoint) {
if (entry_points.empty()) {
result.output = "No entrypoint found";
result.failed = true;
return result;

View File

@@ -16,6 +16,10 @@
#define SRC_VAL_VAL_H_
#include <string>
#include <utility>
#include <vector>
#include "src/ast/pipeline_stage.h"
// Forward declarations
namespace tint {
@@ -25,6 +29,8 @@ class Program;
namespace tint {
namespace val {
using EntryPointList = std::vector<std::pair<std::string, ast::PipelineStage>>;
/// The return structure of Validate()
struct Result {
/// True if validation passed
@@ -39,19 +45,20 @@ struct Result {
/// compiles successfully.
/// @param dxc_path path to DXC
/// @param source the generated HLSL source
/// @param program the HLSL program
/// @param entry_points the list of entry points to validate
/// @return the result of the compile
Result HlslUsingDXC(const std::string& dxc_path,
const std::string& source,
Program* program);
const EntryPointList& entry_points);
#ifdef _WIN32
/// Hlsl attempts to compile the shader with FXC, verifying that the shader
/// compiles successfully.
/// @param source the generated HLSL source
/// @param program the HLSL program
/// @param entry_points the list of entry points to validate
/// @return the result of the compile
Result HlslUsingFXC(const std::string& source, Program* program);
Result HlslUsingFXC(const std::string& source,
const EntryPointList& entry_points);
#endif // _WIN32
/// Msl attempts to compile the shader with the Metal Shader Compiler,

View File

@@ -14,12 +14,46 @@
#include "src/writer/hlsl/generator.h"
#include "src/transform/hlsl.h"
#include "src/writer/hlsl/generator_impl.h"
namespace tint {
namespace writer {
namespace hlsl {
Result::Result() = default;
Result::~Result() = default;
Result::Result(const Result&) = default;
Result Generate(const Program* program, const Options&) {
Result result;
// Run the HLSL sanitizer.
transform::Hlsl sanitizer;
auto output = sanitizer.Run(program);
if (!output.program.IsValid()) {
result.success = false;
result.error = output.program.Diagnostics().str();
return result;
}
// Generate the HLSL code.
auto impl = std::make_unique<GeneratorImpl>(&output.program);
result.success = impl->Generate();
result.error = impl->error();
result.hlsl = impl->result();
// Collect the list of entry points in the sanitized program.
for (auto* func : output.program.AST().Functions()) {
if (func->IsEntryPoint()) {
auto name = output.program.Symbols().NameFor(func->symbol());
result.entry_points.push_back({name, func->pipeline_stage()});
}
}
return result;
}
Generator::Generator(const Program* program)
: impl_(std::make_unique<GeneratorImpl>(program)) {}

View File

@@ -17,16 +17,59 @@
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "src/ast/pipeline_stage.h"
#include "src/writer/text.h"
namespace tint {
// Forward declarations
class Program;
namespace writer {
namespace hlsl {
// Forward declarations
class GeneratorImpl;
/// Configuration options used for generating HLSL.
struct Options {};
/// The result produced when generating HLSL.
struct Result {
/// Constructor
Result();
/// Destructor
~Result();
/// Copy constructor
Result(const Result&);
/// True if generation was successful.
bool success = false;
/// The errors generated during code generation, if any.
std::string error;
/// The generated HLSL.
std::string hlsl = "";
/// The list of entry points in the generated HLSL.
std::vector<std::pair<std::string, ast::PipelineStage>> entry_points;
};
/// Generate HLSL for a program, according to a set of configuration options.
/// The result will contain the HLSL, as well as success status and diagnostic
/// information.
/// @param program the program to translate to HLSL
/// @param options the configuration options to use when generating HLSL
/// @returns the resulting HLSL and supplementary information
Result Generate(const Program* program, const Options& options);
// TODO(jrprice): Remove this once Dawn is using the new interface.
/// Class to generate HLSL source
class Generator : public Text {
public:

View File

@@ -13,12 +13,45 @@
// limitations under the License.
#include "src/writer/msl/generator.h"
#include "src/transform/msl.h"
#include "src/writer/msl/generator_impl.h"
namespace tint {
namespace writer {
namespace msl {
Result::Result() = default;
Result::~Result() = default;
Result::Result(const Result&) = default;
Result Generate(const Program* program, const Options& options) {
Result result;
// Run the MSL sanitizer.
transform::Msl sanitizer;
transform::DataMap transform_input;
transform_input.Add<transform::Msl::Config>(options.buffer_size_ubo_index,
options.fixed_sample_mask);
auto output = sanitizer.Run(program, transform_input);
if (!output.program.IsValid()) {
result.success = false;
result.error = output.program.Diagnostics().str();
return result;
}
auto* transform_output = output.data.Get<transform::Msl::Result>();
result.needs_storage_buffer_sizes =
transform_output->needs_storage_buffer_sizes;
// Generate the MSL code.
auto impl = std::make_unique<GeneratorImpl>(&output.program);
result.success = impl->Generate();
result.error = impl->error();
result.msl = impl->result();
return result;
}
Generator::Generator(const Program* program)
: impl_(std::make_unique<GeneratorImpl>(program)) {}

View File

@@ -21,11 +21,59 @@
#include "src/writer/text.h"
namespace tint {
// Forward declarations
class Program;
namespace writer {
namespace msl {
class GeneratorImpl;
/// Configuration options used for generating MSL.
struct Options {
/// The index to use when generating a UBO to receive storage buffer sizes.
/// Defaults to 30, which is the last valid buffer slot.
uint32_t buffer_size_ubo_index = 30;
/// The fixed sample mask to combine with fragment shader outputs.
/// Defaults to 0xFFFFFFFF.
uint32_t fixed_sample_mask = 0xFFFFFFFF;
};
/// The result produced when generating MSL.
struct Result {
/// Constructor
Result();
/// Destructor
~Result();
/// Copy constructor
Result(const Result&);
/// True if generation was successful.
bool success = false;
/// The errors generated during code generation, if any.
std::string error;
/// The generated MSL.
std::string msl = "";
/// True if the shader needs a UBO of buffer sizes.
bool needs_storage_buffer_sizes = false;
};
/// Generate MSL for a program, according to a set of configuration options. The
/// result will contain the MSL, as well as success status and diagnostic
/// information.
/// @param program the program to translate to MSL
/// @param options the configuration options to use when generating MSL
/// @returns the resulting MSL and supplementary information
Result Generate(const Program* program, const Options& options);
// TODO(jrprice): Remove this once Dawn is using the new interface.
/// Class to generate MSL source
class Generator : public Text {
public:

View File

@@ -14,12 +14,49 @@
#include "src/writer/spirv/generator.h"
#include "src/transform/spirv.h"
#include "src/writer/spirv/binary_writer.h"
namespace tint {
namespace writer {
namespace spirv {
Result::Result() = default;
Result::~Result() = default;
Result::Result(const Result&) = default;
Result Generate(const Program* program, const Options& options) {
Result result;
// Run the SPIR-V sanitizer.
transform::Spirv sanitizer;
transform::DataMap transform_input;
transform_input.Add<transform::Spirv::Config>(options.emit_vertex_point_size);
auto output = sanitizer.Run(program, transform_input);
if (!output.program.IsValid()) {
result.success = false;
result.error = output.program.Diagnostics().str();
return result;
}
// Generate the SPIR-V code.
auto builder = std::make_unique<Builder>(&output.program);
auto writer = std::make_unique<BinaryWriter>();
if (!builder->Build()) {
result.success = false;
result.error = builder->error();
return result;
}
writer->WriteHeader(builder->id_bound());
writer->WriteBuilder(builder.get());
result.success = true;
result.spirv = writer->result();
return result;
}
Generator::Generator(const Program* program)
: builder_(std::make_unique<Builder>(program)),
writer_(std::make_unique<BinaryWriter>()) {}

View File

@@ -22,6 +22,10 @@
#include "src/writer/writer.h"
namespace tint {
// Forward declarations
class Program;
namespace writer {
namespace spirv {
@@ -29,6 +33,43 @@ namespace spirv {
class Builder;
class BinaryWriter;
/// Configuration options used for generating SPIR-V.
struct Options {
/// Set to `true` to generate a PointSize builtin and have it set to 1.0
/// from all vertex shaders in the module.
bool emit_vertex_point_size = true;
};
/// The result produced when generating SPIR-V.
struct Result {
/// Constructor
Result();
/// Destructor
~Result();
/// Copy constructor
Result(const Result&);
/// True if generation was successful.
bool success = false;
/// The errors generated during code generation, if any.
std::string error;
/// The generated SPIR-V.
std::vector<uint32_t> spirv;
};
/// Generate SPIR-V for a program, according to a set of configuration options.
/// The result will contain the SPIR-V, as well as success status and diagnostic
/// information.
/// @param program the program to translate to SPIR-V
/// @param options the configuration options to use when generating SPIR-V
/// @returns the resulting SPIR-V and supplementary information
Result Generate(const Program* program, const Options& options);
// TODO(jrprice): Remove this once Dawn is using the new interface.
/// Class to generate SPIR-V from a Tint program
class Generator : public writer::Writer {
public:

View File

@@ -19,6 +19,22 @@ namespace tint {
namespace writer {
namespace wgsl {
Result::Result() = default;
Result::~Result() = default;
Result::Result(const Result&) = default;
Result Generate(const Program* program, const Options&) {
Result result;
// Generate the WGSL code.
auto impl = std::make_unique<GeneratorImpl>(program);
result.success = impl->Generate();
result.error = impl->error();
result.wgsl = impl->result();
return result;
}
Generator::Generator(const Program* program)
: impl_(std::make_unique<GeneratorImpl>(program)) {}

View File

@@ -21,11 +21,48 @@
#include "src/writer/text.h"
namespace tint {
// Forward declarations
class Program;
namespace writer {
namespace wgsl {
class GeneratorImpl;
/// Configuration options used for generating WGSL.
struct Options {};
/// The result produced when generating WGSL.
struct Result {
/// Constructor
Result();
/// Destructor
~Result();
/// Copy constructor
Result(const Result&);
/// True if generation was successful.
bool success = false;
/// The errors generated during code generation, if any.
std::string error;
/// The generated WGSL.
std::string wgsl = "";
};
/// Generate WGSL for a program, according to a set of configuration options.
/// The result will contain the WGSL, as well as success status and diagnostic
/// information.
/// @param program the program to translate to WGSL
/// @param options the configuration options to use when generating WGSL
/// @returns the resulting WGSL and supplementary information
Result Generate(const Program* program, const Options& options);
// TODO(jrprice): Remove this once Dawn is using the new interface.
/// Class to generate WGSL source
class Generator : public Text {
public:

View File

@@ -17,8 +17,6 @@
#include <string>
#include "src/program.h"
namespace tint {
namespace writer {