dawn-cmake/src/writer/hlsl/generator_impl.h
Ben Clayton 575c4efe2f Remove ScalarConstructorExpression
Just make Literal an expression. The ScalarConstructorExpression
provides no real value, aside from having a ConstructorExpression base
class that's common between ScalarConstructorExpression and
TypeConstructorExpression. TypeConstructorExpression will be folded into
CallExpression, so this hierarchy will serve no purpose.

First step in resolving the parser ambiguity of type-constructors vs
type-casts vs function calls.

Bug: tint:888
Change-Id: I2585d5ddbf6c0619a8f24c503e61ebf27c182ebe
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/68524
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
2021-11-09 09:35:00 +00:00

462 lines
20 KiB
C++

// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_WRITER_HLSL_GENERATOR_IMPL_H_
#define SRC_WRITER_HLSL_GENERATOR_IMPL_H_
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "src/ast/assignment_statement.h"
#include "src/ast/bitcast_expression.h"
#include "src/ast/break_statement.h"
#include "src/ast/continue_statement.h"
#include "src/ast/discard_statement.h"
#include "src/ast/for_loop_statement.h"
#include "src/ast/if_statement.h"
#include "src/ast/loop_statement.h"
#include "src/ast/return_statement.h"
#include "src/ast/switch_statement.h"
#include "src/ast/unary_op_expression.h"
#include "src/program_builder.h"
#include "src/scope_stack.h"
#include "src/sem/binding_point.h"
#include "src/transform/decompose_memory_access.h"
#include "src/utils/hash.h"
#include "src/writer/text_generator.h"
namespace tint {
// Forward declarations
namespace sem {
class Call;
class Intrinsic;
} // namespace sem
namespace writer {
namespace hlsl {
/// The result of sanitizing a program for generation.
struct SanitizedResult {
/// The sanitized program.
Program program;
};
/// Sanitize a program in preparation for generating HLSL.
/// @param root_constant_binding_point the binding point to use for information
/// that will be passed via root constants
/// @param disable_workgroup_init `true` to disable workgroup memory zero
/// @returns the sanitized program and any supplementary information
SanitizedResult Sanitize(const Program* program,
sem::BindingPoint root_constant_binding_point = {},
bool disable_workgroup_init = false);
/// Implementation class for HLSL generator
class GeneratorImpl : public TextGenerator {
public:
/// Constructor
/// @param program the program to generate
explicit GeneratorImpl(const Program* program);
~GeneratorImpl();
/// @returns true on successful generation; false otherwise
bool Generate();
/// Handles an array accessor expression
/// @param out the output of the expression stream
/// @param expr the expression to emit
/// @returns true if the array accessor was emitted
bool EmitArrayAccessor(std::ostream& out,
const ast::ArrayAccessorExpression* expr);
/// Handles an assignment statement
/// @param stmt the statement to emit
/// @returns true if the statement was emitted successfully
bool EmitAssign(const ast::AssignmentStatement* stmt);
/// Handles generating a binary expression
/// @param out the output of the expression stream
/// @param expr the binary expression
/// @returns true if the expression was emitted, false otherwise
bool EmitBinary(std::ostream& out, const ast::BinaryExpression* expr);
/// Handles generating a bitcast expression
/// @param out the output of the expression stream
/// @param expr the as expression
/// @returns true if the bitcast was emitted
bool EmitBitcast(std::ostream& out, const ast::BitcastExpression* expr);
/// Emits a list of statements
/// @param stmts the statement list
/// @returns true if the statements were emitted successfully
bool EmitStatements(const ast::StatementList& stmts);
/// Emits a list of statements with an indentation
/// @param stmts the statement list
/// @returns true if the statements were emitted successfully
bool EmitStatementsWithIndent(const ast::StatementList& stmts);
/// Handles a block statement
/// @param stmt the statement to emit
/// @returns true if the statement was emitted successfully
bool EmitBlock(const ast::BlockStatement* stmt);
/// Handles a break statement
/// @param stmt the statement to emit
/// @returns true if the statement was emitted successfully
bool EmitBreak(const ast::BreakStatement* stmt);
/// Handles generating a call expression
/// @param out the output of the expression stream
/// @param expr the call expression
/// @returns true if the call expression is emitted
bool EmitCall(std::ostream& out, const ast::CallExpression* expr);
/// Handles generating a call expression to a
/// transform::DecomposeMemoryAccess::Intrinsic for a uniform buffer
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the transform::DecomposeMemoryAccess::Intrinsic
/// @returns true if the call expression is emitted
bool EmitUniformBufferAccess(
std::ostream& out,
const ast::CallExpression* expr,
const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
/// Handles generating a call expression to a
/// transform::DecomposeMemoryAccess::Intrinsic for a storage buffer
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the transform::DecomposeMemoryAccess::Intrinsic
/// @returns true if the call expression is emitted
bool EmitStorageBufferAccess(
std::ostream& out,
const ast::CallExpression* expr,
const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
/// Handles generating a barrier intrinsic call
/// @param out the output of the expression stream
/// @param intrinsic the semantic information for the barrier intrinsic
/// @returns true if the call expression is emitted
bool EmitBarrierCall(std::ostream& out, const sem::Intrinsic* intrinsic);
/// Handles generating an atomic intrinsic call for a storage buffer variable
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the atomic intrinsic
/// @returns true if the call expression is emitted
bool EmitStorageAtomicCall(
std::ostream& out,
const ast::CallExpression* expr,
const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
/// Handles generating an atomic intrinsic call for a workgroup variable
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the semantic information for the atomic intrinsic
/// @returns true if the call expression is emitted
bool EmitWorkgroupAtomicCall(std::ostream& out,
const ast::CallExpression* expr,
const sem::Intrinsic* intrinsic);
/// Handles generating a call to a texture function (`textureSample`,
/// `textureSampleGrad`, etc)
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the semantic information for the texture intrinsic
/// @returns true if the call expression is emitted
bool EmitTextureCall(std::ostream& out,
const ast::CallExpression* expr,
const sem::Intrinsic* intrinsic);
/// Handles generating a call to the `select()` intrinsic
/// @param out the output of the expression stream
/// @param expr the call expression
/// @returns true if the call expression is emitted
bool EmitSelectCall(std::ostream& out, const ast::CallExpression* expr);
/// Handles generating a call to the `modf()` intrinsic
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the semantic information for the intrinsic
/// @returns true if the call expression is emitted
bool EmitModfCall(std::ostream& out,
const ast::CallExpression* expr,
const sem::Intrinsic* intrinsic);
/// Handles generating a call to the `frexp()` intrinsic
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the semantic information for the intrinsic
/// @returns true if the call expression is emitted
bool EmitFrexpCall(std::ostream& out,
const ast::CallExpression* expr,
const sem::Intrinsic* intrinsic);
/// Handles generating a call to the `isNormal()` intrinsic
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the semantic information for the intrinsic
/// @returns true if the call expression is emitted
bool EmitIsNormalCall(std::ostream& out,
const ast::CallExpression* expr,
const sem::Intrinsic* intrinsic);
/// Handles generating a call to data packing intrinsic
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the semantic information for the texture intrinsic
/// @returns true if the call expression is emitted
bool EmitDataPackingCall(std::ostream& out,
const ast::CallExpression* expr,
const sem::Intrinsic* intrinsic);
/// Handles generating a call to data unpacking intrinsic
/// @param out the output of the expression stream
/// @param expr the call expression
/// @param intrinsic the semantic information for the texture intrinsic
/// @returns true if the call expression is emitted
bool EmitDataUnpackingCall(std::ostream& out,
const ast::CallExpression* expr,
const sem::Intrinsic* intrinsic);
/// Handles a case statement
/// @param s the switch statement
/// @param case_idx the index of the switch case in the switch statement
/// @returns true if the statement was emitted successfully
bool EmitCase(const ast::SwitchStatement* s, size_t case_idx);
/// Handles generating constructor expressions
/// @param out the output of the expression stream
/// @param expr the constructor expression
/// @returns true if the expression was emitted
bool EmitConstructor(std::ostream& out,
const ast::ConstructorExpression* expr);
/// Handles generating a discard statement
/// @param stmt the discard statement
/// @returns true if the statement was successfully emitted
bool EmitDiscard(const ast::DiscardStatement* stmt);
/// Handles emitting a type constructor
/// @param out the output of the expression stream
/// @param expr the type constructor expression
/// @returns true if the constructor is emitted
bool EmitTypeConstructor(std::ostream& out,
const ast::TypeConstructorExpression* expr);
/// Handles a continue statement
/// @param stmt the statement to emit
/// @returns true if the statement was emitted successfully
bool EmitContinue(const ast::ContinueStatement* stmt);
/// Handles generate an Expression
/// @param out the output of the expression stream
/// @param expr the expression
/// @returns true if the expression was emitted
bool EmitExpression(std::ostream& out, const ast::Expression* expr);
/// Handles generating a function
/// @param func the function to generate
/// @returns true if the function was emitted
bool EmitFunction(const ast::Function* func);
/// Handles emitting a global variable
/// @param global the global variable
/// @returns true on success
bool EmitGlobalVariable(const ast::Variable* global);
/// Handles emitting a global variable with the uniform storage class
/// @param var the global variable
/// @returns true on success
bool EmitUniformVariable(const sem::Variable* var);
/// Handles emitting a global variable with the storage storage class
/// @param var the global variable
/// @returns true on success
bool EmitStorageVariable(const sem::Variable* var);
/// Handles emitting a global variable with the handle storage class
/// @param var the global variable
/// @returns true on success
bool EmitHandleVariable(const sem::Variable* var);
/// Handles emitting a global variable with the private storage class
/// @param var the global variable
/// @returns true on success
bool EmitPrivateVariable(const sem::Variable* var);
/// Handles emitting a global variable with the workgroup storage class
/// @param var the global variable
/// @returns true on success
bool EmitWorkgroupVariable(const sem::Variable* var);
/// Handles emitting the entry point function
/// @param func the entry point
/// @returns true if the entry point function was emitted
bool EmitEntryPointFunction(const ast::Function* func);
/// Handles an if statement
/// @param stmt the statement to emit
/// @returns true if the statement was successfully emitted
bool EmitIf(const ast::IfStatement* stmt);
/// Handles a literal
/// @param out the output stream
/// @param lit the literal to emit
/// @returns true if the literal was successfully emitted
bool EmitLiteral(std::ostream& out, const ast::Literal* lit);
/// Handles a loop statement
/// @param stmt the statement to emit
/// @returns true if the statement was emitted
bool EmitLoop(const ast::LoopStatement* stmt);
/// Handles a for loop statement
/// @param stmt the statement to emit
/// @returns true if the statement was emitted
bool EmitForLoop(const ast::ForLoopStatement* stmt);
/// Handles generating an identifier expression
/// @param out the output of the expression stream
/// @param expr the identifier expression
/// @returns true if the identifeir was emitted
bool EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr);
/// Handles a member accessor expression
/// @param out the output of the expression stream
/// @param expr the member accessor expression
/// @returns true if the member accessor was emitted
bool EmitMemberAccessor(std::ostream& out,
const ast::MemberAccessorExpression* expr);
/// Handles return statements
/// @param stmt the statement to emit
/// @returns true if the statement was successfully emitted
bool EmitReturn(const ast::ReturnStatement* stmt);
/// Handles statement
/// @param stmt the statement to emit
/// @returns true if the statement was emitted
bool EmitStatement(const ast::Statement* stmt);
/// Handles generating a switch statement
/// @param stmt the statement to emit
/// @returns true if the statement was emitted
bool EmitSwitch(const ast::SwitchStatement* stmt);
// Handles generating a switch statement with only a default case
/// @param stmt the statement to emit
/// @returns true if the statement was emitted
bool EmitDefaultOnlySwitch(const ast::SwitchStatement* stmt);
/// Handles generating type
/// @param out the output stream
/// @param type the type to generate
/// @param storage_class the storage class of the variable
/// @param access the access control type of the variable
/// @param name the name of the variable, used for array emission.
/// @param name_printed (optional) if not nullptr and an array was printed
/// then the boolean is set to true.
/// @returns true if the type is emitted
bool EmitType(std::ostream& out,
const sem::Type* type,
ast::StorageClass storage_class,
ast::Access access,
const std::string& name,
bool* name_printed = nullptr);
/// Handles generating type and name
/// @param out the output stream
/// @param type the type to generate
/// @param storage_class the storage class of the variable
/// @param access the access control type of the variable
/// @param name the name to emit
/// @returns true if the type is emitted
bool EmitTypeAndName(std::ostream& out,
const sem::Type* type,
ast::StorageClass storage_class,
ast::Access access,
const std::string& name);
/// Handles generating a structure declaration
/// @param buffer the text buffer that the type declaration will be written to
/// @param ty the struct to generate
/// @returns true if the struct is emitted
bool EmitStructType(TextBuffer* buffer, const sem::Struct* ty);
/// Handles a unary op expression
/// @param out the output of the expression stream
/// @param expr the expression to emit
/// @returns true if the expression was emitted
bool EmitUnaryOp(std::ostream& out, const ast::UnaryOpExpression* expr);
/// Emits the zero value for the given type
/// @param out the output stream
/// @param type the type to emit the value for
/// @returns true if the zero value was successfully emitted.
bool EmitZeroValue(std::ostream& out, const sem::Type* type);
/// Handles generating a variable
/// @param var the variable to generate
/// @returns true if the variable was emitted
bool EmitVariable(const ast::Variable* var);
/// Handles generating a program scope constant variable
/// @param var the variable to emit
/// @returns true if the variable was emitted
bool EmitProgramConstVariable(const ast::Variable* var);
/// Emits call to a helper vector assignment function for the input assignment
/// statement and vector type. This is used to work around FXC issues where
/// assignments to vectors with dynamic indices cause compilation failures.
/// @param stmt assignment statement that corresponds to a vector assignment
/// via an accessor expression
/// @param vec the vector type being assigned to
/// @returns true on success
bool EmitDynamicVectorAssignment(const ast::AssignmentStatement* stmt,
const sem::Vector* vec);
/// Handles generating a builtin method name
/// @param intrinsic the semantic info for the intrinsic
/// @returns the name or "" if not valid
std::string generate_builtin_name(const sem::Intrinsic* intrinsic);
/// Converts a builtin to an attribute name
/// @param builtin the builtin to convert
/// @returns the string name of the builtin or blank on error
std::string builtin_to_attribute(ast::Builtin builtin) const;
/// Converts interpolation attributes to a HLSL modifiers
/// @param type the interpolation type
/// @param sampling the interpolation sampling
/// @returns the string name of the attribute or blank on error
std::string interpolation_to_modifiers(
ast::InterpolationType type,
ast::InterpolationSampling sampling) const;
private:
enum class VarType { kIn, kOut };
struct EntryPointData {
std::string struct_name;
std::string var_name;
};
struct DMAIntrinsic {
transform::DecomposeMemoryAccess::Intrinsic::Op op;
transform::DecomposeMemoryAccess::Intrinsic::DataType type;
bool operator==(const DMAIntrinsic& rhs) const {
return op == rhs.op && type == rhs.type;
}
/// Hasher is a std::hash function for DMAIntrinsic
struct Hasher {
/// @param i the DMAIntrinsic to hash
/// @returns the hash of `i`
inline std::size_t operator()(const DMAIntrinsic& i) const {
return utils::Hash(i.op, i.type);
}
};
};
/// CallIntrinsicHelper will call the intrinsic helper function, creating it
/// if it hasn't been built already. If the intrinsic needs to be built then
/// CallIntrinsicHelper will generate the function signature and will call
/// `build` to emit the body of the function.
/// @param out the output of the expression stream
/// @param call the call expression
/// @param intrinsic the semantic information for the intrinsic
/// @param build a function with the signature:
/// `bool(TextBuffer* buffer, const std::vector<std::string>& params)`
/// Where:
/// `buffer` is the body of the generated function
/// `params` is the name of all the generated function parameters
/// @returns true if the call expression is emitted
template <typename F>
bool CallIntrinsicHelper(std::ostream& out,
const ast::CallExpression* call,
const sem::Intrinsic* intrinsic,
F&& build);
TextBuffer helpers_; // Helper functions emitted at the top of the output
std::function<bool()> emit_continuing_;
std::unordered_map<DMAIntrinsic, std::string, DMAIntrinsic::Hasher>
dma_intrinsics_;
std::unordered_map<const sem::Intrinsic*, std::string> intrinsics_;
std::unordered_map<const sem::Struct*, std::string> structure_builders_;
std::unordered_map<const sem::Vector*, std::string> dynamic_vector_write_;
};
} // namespace hlsl
} // namespace writer
} // namespace tint
#endif // SRC_WRITER_HLSL_GENERATOR_IMPL_H_