Add Chromium-only Blender WebEngine parity work
This commit is contained in:
59
blender-5.2.0/source/blender/gpu/shader_tool/CMakeLists.txt
Normal file
59
blender-5.2.0/source/blender/gpu/shader_tool/CMakeLists.txt
Normal file
@@ -0,0 +1,59 @@
|
||||
# SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build `shader_tool` executable.
|
||||
set(SRC
|
||||
attribute.cc
|
||||
enum.cc
|
||||
flow_control.cc
|
||||
function.cc
|
||||
grammar.cc
|
||||
intermediate.cc
|
||||
metadata.cc
|
||||
namespace.cc
|
||||
processor.cc
|
||||
resource_table.cc
|
||||
shader_tool.cc
|
||||
string.cc
|
||||
struct.cc
|
||||
template.cc
|
||||
union.cc
|
||||
|
||||
enums.hh
|
||||
expression.hh
|
||||
intermediate.hh
|
||||
metadata.hh
|
||||
processor.hh
|
||||
scope.hh
|
||||
time_it.hh
|
||||
token.hh
|
||||
token_stream.hh
|
||||
utils.hh
|
||||
|
||||
lexit/lexit.cc
|
||||
|
||||
lexit/identifier.hh
|
||||
lexit/lexit.hh
|
||||
lexit/simd.hh
|
||||
lexit/tables.hh
|
||||
lexit/types.hh
|
||||
lexit/vector.hh
|
||||
)
|
||||
|
||||
if(WITH_GPU_SHADER_ASSERT)
|
||||
add_definitions(-DWITH_GPU_SHADER_ASSERT)
|
||||
endif()
|
||||
|
||||
# `SRC_DNA_INC` is defined in the parent directory.
|
||||
add_executable(shader_tool ${SRC})
|
||||
if(EMSCRIPTEN)
|
||||
target_link_options(shader_tool PRIVATE
|
||||
"-sMODULARIZE=0" "-sEXPORT_ES6=0" "-sENVIRONMENT=node" "-sEXIT_RUNTIME=1"
|
||||
"-sNODERAWFS=1")
|
||||
add_custom_command(TARGET shader_tool POST_BUILD
|
||||
COMMAND chmod +x "$<TARGET_FILE:shader_tool>")
|
||||
endif()
|
||||
optimize_debug_target(shader_tool)
|
||||
182
blender-5.2.0/source/blender/gpu/shader_tool/attribute.cc
Normal file
182
blender-5.2.0/source/blender/gpu/shader_tool/attribute.cc
Normal file
@@ -0,0 +1,182 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
void SourceProcessor::lower_maybe_unused(Parser &parser)
|
||||
{
|
||||
using namespace metadata;
|
||||
|
||||
parser().foreach_token(SquareOpen, [&](Token par_open) {
|
||||
if (par_open.next() != '[') {
|
||||
return;
|
||||
}
|
||||
Scope attributes = par_open.next().scope();
|
||||
attributes.foreach_attribute([&](Token attr, Scope) {
|
||||
if (attr.str() == "maybe_unused") {
|
||||
if (attr.next() == ',') {
|
||||
parser.erase(attr, attr.next());
|
||||
}
|
||||
else if (attr.prev() == ',') {
|
||||
parser.erase(attr.prev(), attr);
|
||||
}
|
||||
else if (attr.next() == ']') {
|
||||
parser.erase(attributes.scope());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
void SourceProcessor::lint_attributes(Parser &parser)
|
||||
{
|
||||
parser().foreach_token(SquareOpen, [&](Token par_open) {
|
||||
if (par_open.next() != '[') {
|
||||
return;
|
||||
}
|
||||
Scope attributes = par_open.next().scope();
|
||||
bool invalid = false;
|
||||
attributes.foreach_attribute([&](Token attr, Scope attr_scope) {
|
||||
string attr_str = string(attr.str());
|
||||
if (attr_str == "base_instance" || attr_str == "clip_distance" ||
|
||||
attr_str == "compilation_constant" || attr_str == "compute" || attr_str == "shared" ||
|
||||
attr_str == "early_fragment_tests" || attr_str == "flat" || attr_str == "frag_coord" ||
|
||||
attr_str == "frag_stencil_ref" || attr_str == "fragment" || attr_str == "front_facing" ||
|
||||
attr_str == "global_invocation_id" || attr_str == "in" || attr_str == "instance_id" ||
|
||||
attr_str == "instance_index" || attr_str == "layer" ||
|
||||
attr_str == "local_invocation_id" || attr_str == "local_invocation_index" ||
|
||||
attr_str == "no_perspective" || attr_str == "num_work_groups" || attr_str == "out" ||
|
||||
attr_str == "subpass_in" || attr_str == "point_coord" || attr_str == "point_size" ||
|
||||
attr_str == "position" || attr_str == "push_constant" || attr_str == "resource_table" ||
|
||||
attr_str == "smooth" || attr_str == "vertex_id" || attr_str == "legacy_info" ||
|
||||
attr_str == "vertex" || attr_str == "viewport_index" || attr_str == "work_group_id" ||
|
||||
attr_str == "maybe_unused" || attr_str == "fallthrough" || attr_str == "nodiscard" ||
|
||||
attr_str == "node" || attr_str == "clip_control" || attr_str == "texture_atomic")
|
||||
{
|
||||
if (attr_scope.is_valid()) {
|
||||
report_error(attr, "This attribute requires no argument");
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
else if (attr_str == "attribute" || attr_str == "index" || attr_str == "frag_color" ||
|
||||
attr_str == "frag_depth" || attr_str == "uniform" || attr_str == "condition" ||
|
||||
attr_str == "raster_order_group" || attr_str == "frequency" ||
|
||||
attr_str == "sampler" || attr_str == "specialization_constant")
|
||||
{
|
||||
if (attr_scope.is_invalid()) {
|
||||
report_error(attr, "This attribute requires 1 argument");
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
else if (attr_str == "storage" || attr_str == "subpass_input") {
|
||||
if (attr_scope.is_invalid()) {
|
||||
report_error(attr, "This attribute requires 2 arguments");
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
else if (attr_str == "image") {
|
||||
if (attr_scope.is_invalid()) {
|
||||
report_error(attr, "This attribute requires 3 arguments");
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
else if (attr_str == "local_size") {
|
||||
if (attr_scope.is_invalid()) {
|
||||
report_error(attr, "This attribute requires at least 1 argument");
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
else if (attr_str == "metal_max_total_threads_per_threadgroup") {
|
||||
if (attr_scope.is_invalid()) {
|
||||
report_error(attr, "This attribute requires at least 1 argument");
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
else if (attr_str == "host_shared") {
|
||||
if (attributes.front().prev().prev() != Struct && attributes.front().prev().prev() != Enum)
|
||||
{
|
||||
report_error(
|
||||
attr, "host_shared attributes must be placed after a struct or an enum definition");
|
||||
invalid = true;
|
||||
}
|
||||
/* Placement already checked. */
|
||||
return;
|
||||
}
|
||||
else if (attr_str == "unroll" || attr_str == "unroll_n") {
|
||||
if (attributes.front().prev().prev().scope().front().prev() != For) {
|
||||
report_error(attr, "[[unroll]] attribute must be declared after a 'for' statement");
|
||||
invalid = true;
|
||||
}
|
||||
/* Placement already checked. */
|
||||
return;
|
||||
}
|
||||
else if (attr_str == "static_branch") {
|
||||
if (attributes.front().prev().prev().scope().front().prev() != If) {
|
||||
report_error(attr,
|
||||
"[[static_branch]] attribute must be declared after a 'if' condition");
|
||||
invalid = true;
|
||||
}
|
||||
/* Placement already checked. */
|
||||
return;
|
||||
}
|
||||
else {
|
||||
report_error(attr, "Unrecognized attribute");
|
||||
invalid = true;
|
||||
/* Attribute already invalid, don't check placement. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr_str == "fallthrough") {
|
||||
/* Placement is too complicated to check. C++ compilation should already have checked. */
|
||||
return;
|
||||
}
|
||||
|
||||
Token prev_tok = attributes.front().prev().prev();
|
||||
if (prev_tok == '(' || prev_tok == '{' || prev_tok == ';' || prev_tok == ',' ||
|
||||
prev_tok == '}' || prev_tok == ')' || prev_tok == '\n' || prev_tok == ' ' ||
|
||||
prev_tok == '>' || prev_tok.is_invalid() ||
|
||||
prev_tok.scope().type() == ScopeType::Preprocessor)
|
||||
{
|
||||
/* Placement is maybe correct. Could refine a bit more. */
|
||||
}
|
||||
else {
|
||||
report_error(attr, "attribute must be declared at a start of a declaration");
|
||||
invalid = true;
|
||||
}
|
||||
});
|
||||
if (invalid) {
|
||||
/* Erase invalid attributes to avoid spawning more errors. */
|
||||
parser.erase(attributes.scope());
|
||||
}
|
||||
});
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/* Merge attribute scopes. They are equivalent in the C++ standard.
|
||||
* This allow to simplify parsing later on.
|
||||
* `[[a]] [[b]]` > `[[a, b]]` */
|
||||
void SourceProcessor::lower_attribute_sequences(Parser &parser)
|
||||
{
|
||||
do {
|
||||
parser().foreach_match("[[..]][[..]]", [&](vector<Token> toks) {
|
||||
parser.insert_before(toks[4], ",");
|
||||
parser.erase(toks[4], toks[7]);
|
||||
});
|
||||
} while (parser.apply_mutations());
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
142
blender-5.2.0/source/blender/gpu/shader_tool/enum.cc
Normal file
142
blender-5.2.0/source/blender/gpu/shader_tool/enum.cc
Normal file
@@ -0,0 +1,142 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
void SourceProcessor::lower_enums(Parser &parser)
|
||||
{
|
||||
/**
|
||||
* Transform C,C++ enum declaration into GLSL compatible defines and constants:
|
||||
*
|
||||
* \code{.cpp}
|
||||
* enum MyEnum : uint {
|
||||
* ENUM_1 = 0u,
|
||||
* ENUM_2 = 1u,
|
||||
* ENUM_3 = 2u,
|
||||
* };
|
||||
* \endcode
|
||||
*
|
||||
* becomes
|
||||
*
|
||||
* \code{.glsl}
|
||||
* #define MyEnum uint
|
||||
* constant static constexpr uint ENUM_1 = 0u;
|
||||
* constant static constexpr uint ENUM_2 = 1u;
|
||||
* constant static constexpr uint ENUM_3 = 2u;
|
||||
*
|
||||
* \endcode
|
||||
*
|
||||
* It is made like so to avoid messing with error lines, allowing to point at the exact
|
||||
* location inside the source file.
|
||||
*
|
||||
* IMPORTANT: This has some requirements:
|
||||
* - Enums needs to have underlying types set to uint32_t to make them usable in UBO and SSBO.
|
||||
*/
|
||||
|
||||
auto missing_underlying_type = [&](vector<Token> tokens) {
|
||||
report_error(tokens[0], "enum declaration must explicitly use an underlying type");
|
||||
};
|
||||
|
||||
parser().foreach_match("MA{", missing_underlying_type);
|
||||
parser().foreach_match("MSA{", missing_underlying_type);
|
||||
|
||||
const string placeholder_value = "=__auto__";
|
||||
|
||||
auto placeholder = [&](Scope enum_scope) {
|
||||
const string &value = placeholder_value;
|
||||
const string start = " = 0" + string(enum_scope.front().prev().str()[0] == 'u' ? "u" : "");
|
||||
|
||||
auto insert = [&](Token name, const string &replacement) {
|
||||
if (name.next() == ',' || name.next() == '}') {
|
||||
parser.insert_after(name, replacement);
|
||||
}
|
||||
};
|
||||
enum_scope.foreach_match("{A", [&](const Tokens &t) { insert(t[1], start); });
|
||||
enum_scope.foreach_match(",A", [&](const Tokens &t) { insert(t[1], value); });
|
||||
};
|
||||
|
||||
parser().foreach_match("MSA:A{", [&](const Tokens &t) { placeholder(t[5].scope()); });
|
||||
parser().foreach_match("MA:A{", [&](const Tokens &t) { placeholder(t[4].scope()); });
|
||||
parser().foreach_match("MS[[A]]A:A{", [&](const Tokens &t) { placeholder(t[10].scope()); });
|
||||
parser().foreach_match("M[[A]]A:A{", [&](const Tokens &t) { placeholder(t[9].scope()); });
|
||||
|
||||
parser.apply_mutations();
|
||||
|
||||
auto process_enum = [&](Token enum_tok,
|
||||
Token class_tok,
|
||||
Token enum_name,
|
||||
Token enum_type,
|
||||
Scope enum_scope,
|
||||
const bool is_host_shared) {
|
||||
const string type_str(enum_type.str());
|
||||
const string enum_name_str(enum_name.str());
|
||||
|
||||
string previous_value = "error_invalid_first_value";
|
||||
enum_scope.foreach_scope(ScopeType::Assignment, [&](Scope scope) {
|
||||
Token name_tok = scope.front().prev();
|
||||
string name(name_tok.str());
|
||||
string value(scope.str());
|
||||
if (value == placeholder_value) {
|
||||
value = "= " + previous_value + " + 1" + (enum_type.str()[0] == 'u' ? "u" : "");
|
||||
}
|
||||
if (class_tok.is_valid()) {
|
||||
name = enum_name_str + "::" + name;
|
||||
}
|
||||
string decl = "constant static constexpr " + type_str + " " + name + " " + value + ";\n";
|
||||
parser.insert_line_number(enum_tok.prev(), name_tok.line_number());
|
||||
parser.insert_after(enum_tok.prev(), decl);
|
||||
|
||||
previous_value = name;
|
||||
});
|
||||
parser.insert_directive(enum_tok.prev(),
|
||||
"#define " + enum_name_str + " " + string(enum_type.str()) + "\n");
|
||||
if (is_host_shared) {
|
||||
if (type_str != "uint32_t" && type_str != "int32_t") {
|
||||
report_error(enum_type,
|
||||
"Host shared enum declaration must use uint32_t or int32_t underlying type");
|
||||
return;
|
||||
}
|
||||
|
||||
string define = "#define ";
|
||||
define += enum_name_str + linted_struct_suffix + " " + enum_name_str + "\n";
|
||||
parser.insert_directive(enum_tok.prev(), define);
|
||||
}
|
||||
const string ctor_decl = enum_name_str + " " + enum_name_str + "_ctor_() { return " +
|
||||
enum_name_str + "(0); }";
|
||||
parser.insert_directive(enum_tok.prev(), ctor_decl);
|
||||
parser.erase(enum_tok, enum_scope.back().next());
|
||||
};
|
||||
|
||||
parser().foreach_match("MSA:A{", [&](vector<Token> tokens) {
|
||||
process_enum(tokens[0], tokens[1], tokens[2], tokens[4], tokens[5].scope(), false);
|
||||
});
|
||||
parser().foreach_match("MA:A{", [&](vector<Token> tokens) {
|
||||
process_enum(tokens[0], Token(parser), tokens[1], tokens[3], tokens[4].scope(), false);
|
||||
});
|
||||
parser().foreach_match("MS[[A]]A:A{", [&](vector<Token> tokens) {
|
||||
process_enum(tokens[0], tokens[1], tokens[7], tokens[9], tokens[10].scope(), true);
|
||||
});
|
||||
parser().foreach_match("M[[A]]A:A{", [&](vector<Token> tokens) {
|
||||
process_enum(tokens[0], Token(parser), tokens[6], tokens[8], tokens[9].scope(), true);
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
|
||||
parser().foreach_token(Enum, [&](Token tok) {
|
||||
report_error(tok, "invalid enum declaration, likely missing underlying type");
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
56
blender-5.2.0/source/blender/gpu/shader_tool/enums.hh
Normal file
56
blender-5.2.0/source/blender/gpu/shader_tool/enums.hh
Normal file
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lexit/lexit.hh"
|
||||
|
||||
namespace blender::gpu::shader::parser {
|
||||
|
||||
using namespace lexit;
|
||||
|
||||
enum class ScopeType : char {
|
||||
Invalid = 0,
|
||||
/* Use ascii chars to store them in string, and for easy debugging / testing. */
|
||||
Global = 'G',
|
||||
Namespace = 'N',
|
||||
Struct = 'S',
|
||||
Function = 'F',
|
||||
LoopArgs = 'l',
|
||||
LoopBody = 'p',
|
||||
SwitchArg = 'w',
|
||||
SwitchBody = 'W',
|
||||
FunctionArgs = 'f',
|
||||
FunctionCall = 'c',
|
||||
Template = 'T',
|
||||
TemplateArg = 't',
|
||||
Subscript = 'A',
|
||||
Preprocessor = 'P',
|
||||
Assignment = 'a',
|
||||
Attributes = 'B',
|
||||
Attribute = 'b',
|
||||
/* Added scope inside function body. */
|
||||
Local = 'L',
|
||||
/* Added scope inside FunctionArgs. */
|
||||
FunctionArg = 'g',
|
||||
/* Added scope inside FunctionCall. */
|
||||
FunctionParam = 'm',
|
||||
/* Added scope inside LoopArgs. */
|
||||
LoopArg = 'r',
|
||||
|
||||
Statement = 's',
|
||||
Separator = 'o',
|
||||
/* Undetermined. */
|
||||
Angle = Template,
|
||||
Bracket = Local,
|
||||
Parenthesis = FunctionArgs,
|
||||
Square = Subscript,
|
||||
};
|
||||
|
||||
} // namespace blender::gpu::shader::parser
|
||||
254
blender-5.2.0/source/blender/gpu/shader_tool/expression.hh
Normal file
254
blender-5.2.0/source/blender/gpu/shader_tool/expression.hh
Normal file
@@ -0,0 +1,254 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*
|
||||
* Simple integer logic expression using a Pratt-parser.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "token.hh"
|
||||
#include "token_stream.hh"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace blender::gpu::shader::parser {
|
||||
|
||||
/**
|
||||
* Same as FullLexer but considers angle brackets as multitokens, allowing identification of
|
||||
* operators using them.
|
||||
*/
|
||||
struct ExpressionLexer {
|
||||
static void lexical_analysis(LexerBase &lex, std::string_view input)
|
||||
{
|
||||
lex.process(input, LexerBase::default_char_class_table.data());
|
||||
lex.merge_complex_literals();
|
||||
lex.identify_keywords();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple expression parsing and evaluation.
|
||||
* Will evaluate starting the given token until the end of the token stream.
|
||||
* As this is supposed to be use for preprocessor directives, unknown identifiers (words) will
|
||||
* evaluate to 0.
|
||||
*/
|
||||
class ExpressionParser : public Parser<ExpressionLexer, NullParser> {
|
||||
public:
|
||||
int64_t eval() const
|
||||
{
|
||||
assert(size_ > 0);
|
||||
EvalContext ctx((*this)[0]);
|
||||
|
||||
int64_t v = ctx.expr(0);
|
||||
if (ctx.peek() != Invalid) {
|
||||
throw std::runtime_error("Trailing input");
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
private:
|
||||
struct EvalContext {
|
||||
private:
|
||||
Token tok;
|
||||
|
||||
public:
|
||||
EvalContext(Token tok) : tok(tok) {}
|
||||
|
||||
int64_t expr(int right_binding_power)
|
||||
{
|
||||
/* Parse unary operator, evaluate parenthesis, evaluate constant. */
|
||||
int64_t left = nud(consume());
|
||||
/* While left binding power is greater than the right, continue consuming binary operations.
|
||||
*/
|
||||
while (left_binding_power(peek().type()) > right_binding_power) {
|
||||
left = led(left, consume());
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
/* How a token evaluates without left context (e.g. unary operator).
|
||||
* Also known as Null-Denotation or NUD. */
|
||||
int64_t nud(const Token &t)
|
||||
{
|
||||
/* Unary operators must have the highest precedence. */
|
||||
static constexpr int unary_binding_power = 1000;
|
||||
/* Let parenthesis evaluate everything until a closing parenthesis. */
|
||||
static constexpr int parenthesis_binding_power = 0;
|
||||
|
||||
switch (t.type()) {
|
||||
case Word:
|
||||
/* Undefined identifier (not macro substituted). Evaluate to 0. */
|
||||
return 0;
|
||||
case Number:
|
||||
return std::stol(std::string(t.str()));
|
||||
case Plus:
|
||||
return +expr(unary_binding_power);
|
||||
case Minus:
|
||||
return -expr(unary_binding_power);
|
||||
case Not: {
|
||||
int v = expr(unary_binding_power);
|
||||
/* Note that '!' token is of MultiTok class and can contain many unary '!'. */
|
||||
return (t.str().size() & 1) ? !v : !!v;
|
||||
}
|
||||
case BitwiseNot:
|
||||
return ~expr(unary_binding_power);
|
||||
case ParOpen: {
|
||||
/* Parse the whole parenthesis expression. */
|
||||
int64_t v = expr(parenthesis_binding_power);
|
||||
/* Consume the closing parenthesis. */
|
||||
if (consume() != ParClose) {
|
||||
throw std::runtime_error("Expected ')'");
|
||||
}
|
||||
return v;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("Invalid expression");
|
||||
}
|
||||
}
|
||||
|
||||
/* How a token evaluates from left-to-right, on two operands.
|
||||
* Also known as Left-Denotation or LED. */
|
||||
int64_t led(int64_t left, const Token &t)
|
||||
{
|
||||
switch (t.type()) {
|
||||
case Multiply:
|
||||
return left * expr(left_binding_power(Multiply));
|
||||
case Divide: {
|
||||
int64_t right = expr(left_binding_power(Divide));
|
||||
if (right == 0) {
|
||||
throw std::runtime_error("Division by zero");
|
||||
}
|
||||
return left / right;
|
||||
}
|
||||
case Modulo: {
|
||||
int64_t right = expr(left_binding_power(Modulo));
|
||||
if (right == 0) {
|
||||
throw std::runtime_error("Modulo by zero");
|
||||
}
|
||||
return left % right;
|
||||
}
|
||||
case Plus:
|
||||
return left + expr(left_binding_power(Plus));
|
||||
case Minus:
|
||||
return left - expr(left_binding_power(Minus));
|
||||
#if 0 /* Not implemented yet. */
|
||||
case LShift:
|
||||
return left << expression(binding_power(LShift));
|
||||
case RShift:
|
||||
return left >> expression(binding_power(RShift));
|
||||
#endif
|
||||
case LThan:
|
||||
return left < expr(left_binding_power(LThan));
|
||||
case LEqual:
|
||||
return left <= expr(left_binding_power(LEqual));
|
||||
case GThan:
|
||||
return left > expr(left_binding_power(GThan));
|
||||
case GEqual:
|
||||
return left >= expr(left_binding_power(GEqual));
|
||||
case Equal:
|
||||
return left == expr(left_binding_power(Equal));
|
||||
case NotEqual:
|
||||
return left != expr(left_binding_power(NotEqual));
|
||||
case And:
|
||||
return left & expr(left_binding_power(And));
|
||||
case Xor:
|
||||
return left ^ expr(left_binding_power(Xor));
|
||||
case Or:
|
||||
return left | expr(left_binding_power(Or));
|
||||
case LogicalAnd: {
|
||||
/* Avoid short circuit. */
|
||||
int right = expr(left_binding_power(LogicalAnd));
|
||||
return left && right;
|
||||
}
|
||||
case LogicalOr: {
|
||||
/* Avoid short circuit. */
|
||||
int right = expr(left_binding_power(LogicalOr));
|
||||
return left || right;
|
||||
}
|
||||
case Question: {
|
||||
/* The middle expression can be almost anything.
|
||||
* We use 0 so it only stops at the ':' (since Colon has a precedence of 0). */
|
||||
int64_t tval = expr(0);
|
||||
if (consume().type() != Colon) {
|
||||
throw std::runtime_error("Expected ':'");
|
||||
}
|
||||
/* Use (Precedence - 1) to handle right-associativity. */
|
||||
int64_t fval = expr(left_binding_power(Question) - 1);
|
||||
return left ? tval : fval;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("Invalid operator");
|
||||
}
|
||||
}
|
||||
|
||||
int left_binding_power(TokenType type)
|
||||
{
|
||||
switch (type) {
|
||||
case Multiply:
|
||||
case Divide:
|
||||
case Modulo:
|
||||
return 110;
|
||||
case Plus:
|
||||
case Minus:
|
||||
return 100;
|
||||
#if 0 /* Not implemented yet. */
|
||||
case LShift:
|
||||
case RShift:
|
||||
return 90;
|
||||
#endif
|
||||
case LThan:
|
||||
case LEqual:
|
||||
case GThan:
|
||||
case GEqual:
|
||||
return 80;
|
||||
case Equal:
|
||||
case NotEqual:
|
||||
return 70;
|
||||
case And:
|
||||
return 60;
|
||||
case Xor:
|
||||
return 50;
|
||||
case Or:
|
||||
return 40;
|
||||
case LogicalAnd:
|
||||
return 30;
|
||||
case LogicalOr:
|
||||
return 20;
|
||||
case Question:
|
||||
return 10;
|
||||
case Colon:
|
||||
case ParOpen:
|
||||
case ParClose:
|
||||
return 0;
|
||||
case Not:
|
||||
case BitwiseNot:
|
||||
/* Prefix operators don't bind to the left! */
|
||||
return 0;
|
||||
case Invalid: /* EndOfFile */
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw std::runtime_error("Invalid token");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Token peek() const
|
||||
{
|
||||
return tok;
|
||||
}
|
||||
|
||||
Token consume()
|
||||
{
|
||||
Token t = tok;
|
||||
tok = tok.next();
|
||||
return t;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace blender::gpu::shader::parser
|
||||
355
blender-5.2.0/source/blender/gpu/shader_tool/flow_control.cc
Normal file
355
blender-5.2.0/source/blender/gpu/shader_tool/flow_control.cc
Normal file
@@ -0,0 +1,355 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
void SourceProcessor::lower_loop_unroll(Parser &parser)
|
||||
{
|
||||
struct ArgParseResult {
|
||||
Scope init, condition, iter;
|
||||
};
|
||||
auto parse_for_args = [&](const Scope loop_args) -> ArgParseResult {
|
||||
ArgParseResult result{Scope(parser), Scope(parser), Scope(parser)};
|
||||
loop_args.foreach_scope(ScopeType::LoopArg, [&](const Scope arg) {
|
||||
if (arg.front().prev() == '(' && arg.back().next() == ';') {
|
||||
result.init = arg;
|
||||
}
|
||||
else if (arg.front().prev() == ';' && arg.back().next() == ';') {
|
||||
result.condition = arg;
|
||||
}
|
||||
else if (arg.front().prev() == ';' && arg.back().next() == ')') {
|
||||
result.iter = arg;
|
||||
}
|
||||
else {
|
||||
report_error(arg.front(), "Invalid loop declaration.");
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
auto process_loop = [&](const Token loop_start,
|
||||
const int iter_count,
|
||||
const int iter_init,
|
||||
const int iter_incr,
|
||||
const bool condition_is_trivial,
|
||||
const bool iteration_is_trivial,
|
||||
const Scope init,
|
||||
const Scope cond,
|
||||
const Scope iter,
|
||||
const Scope body,
|
||||
const string body_prefix = "",
|
||||
const string body_suffix = "") {
|
||||
/* Check that there is no unsupported keywords in the loop body. */
|
||||
bool error = false;
|
||||
/* Checks if `continue` exists, even in switch statement inside the unrolled loop. */
|
||||
body.foreach_token(Continue, [&](const Token token) {
|
||||
if (token.scope().first_scope_of_type(ScopeType::LoopBody) == body) {
|
||||
report_error(token, "Unrolled loop cannot contain \"continue\" statement.");
|
||||
error = true;
|
||||
}
|
||||
});
|
||||
/* Checks if `break` exists directly the unrolled loop scope. Switch statements are ok. */
|
||||
body.foreach_token(Break, [&](const Token token) {
|
||||
if (token.scope().first_scope_of_type(ScopeType::LoopBody) == body) {
|
||||
const Scope switch_scope = token.scope().first_scope_of_type(ScopeType::SwitchBody);
|
||||
if (switch_scope.is_invalid() || !body.contains(switch_scope)) {
|
||||
report_error(token, "Unrolled loop cannot contain \"break\" statement.");
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parser.replace_try(loop_start, body.back(), "", true)) {
|
||||
/* This is the case of nested loops. This loop will be processed in another parser pass. */
|
||||
return;
|
||||
}
|
||||
|
||||
string indent_init, indent_cond, indent_iter;
|
||||
if (init.is_valid()) {
|
||||
indent_init = string(init.front().char_number() - 1, ' ');
|
||||
}
|
||||
if (cond.is_valid()) {
|
||||
indent_cond = string(cond.front().char_number() - 3, ' ');
|
||||
}
|
||||
if (iter.is_valid()) {
|
||||
indent_iter = string(iter.front().char_number(), ' ');
|
||||
}
|
||||
string indent_body = string(body.front().char_number(), ' ');
|
||||
string indent_end = string(body.back().char_number(), ' ');
|
||||
|
||||
/* If possible, replaces the index of the loop iteration inside the given string. */
|
||||
auto replace_index = [&](const string &str, int loop_index) {
|
||||
if (iter.is_invalid() || !iteration_is_trivial || str.empty()) {
|
||||
return str;
|
||||
}
|
||||
Parser str_parser(str, error_handler);
|
||||
str_parser().foreach_token(Word, [&](const Token tok) {
|
||||
if (tok.str() == iter[0].str()) {
|
||||
str_parser.replace(tok, to_string(loop_index), true);
|
||||
}
|
||||
});
|
||||
return str_parser.result_get();
|
||||
};
|
||||
|
||||
parser.insert_after(body.back(), "\n");
|
||||
if (init.is_valid() && !iteration_is_trivial) {
|
||||
parser.insert_line_number(body.back(), init.front().line_number());
|
||||
parser.insert_after(body.back(),
|
||||
indent_init + "{" + string(init.str_with_whitespace()) + ";\n");
|
||||
}
|
||||
else {
|
||||
parser.insert_after(body.back(), "{\n");
|
||||
}
|
||||
for (int64_t i = 0, value = iter_init; i < iter_count; i++, value += iter_incr) {
|
||||
if (cond.is_valid() && !condition_is_trivial) {
|
||||
parser.insert_line_number(body.back(), cond.front().line_number());
|
||||
parser.insert_after(body.back(),
|
||||
indent_cond + "if(" + string(cond.str_with_whitespace()) + ")\n");
|
||||
}
|
||||
parser.insert_after(body.back(), replace_index(body_prefix, value));
|
||||
parser.insert_line_number(body.back(), body.front().line_number());
|
||||
parser.insert_after(body.back(),
|
||||
indent_body + replace_index(string(body.str_with_whitespace()), value) +
|
||||
"\n");
|
||||
parser.insert_after(body.back(), body_suffix);
|
||||
if (iter.is_valid() && !iteration_is_trivial) {
|
||||
parser.insert_line_number(body.back(), iter.front().line_number());
|
||||
parser.insert_after(body.back(), indent_iter + string(iter.str_with_whitespace()) + ";\n");
|
||||
}
|
||||
}
|
||||
parser.insert_line_number(body.back(), body.back().line_number());
|
||||
parser.insert_after(body.back(), indent_end + string(body.back().str_with_whitespace()));
|
||||
};
|
||||
|
||||
do {
|
||||
/* WORKAROUND: We need to differentiate for and switch statements apart for proper break and
|
||||
* continue statement usage linting. For this, we modify the body scope types to be able to
|
||||
* detect which loop or switch body the break and continue statements are part of. */
|
||||
parser().foreach_match("f(..)[[..]]{..}", [&](const vector<Token> tokens) {
|
||||
tokens[11].scope().set_type(ScopeType::LoopBody);
|
||||
});
|
||||
parser().foreach_match("f(..){..}", [&](const vector<Token> tokens) {
|
||||
tokens[5].scope().set_type(ScopeType::LoopBody);
|
||||
});
|
||||
parser().foreach_match("h(..){..}", [&](const vector<Token> tokens) {
|
||||
tokens[5].scope().set_type(ScopeType::SwitchBody);
|
||||
});
|
||||
|
||||
/* [[unroll]]. */
|
||||
parser().foreach_match("f(..)[[A]]{..}", [&](const vector<Token> tokens) {
|
||||
if (tokens[6].scope().str_with_whitespace() != "[unroll]") {
|
||||
return;
|
||||
}
|
||||
const Token for_tok = tokens[0];
|
||||
const Scope loop_args = tokens[1].scope();
|
||||
const Scope loop_body = tokens[10].scope();
|
||||
|
||||
auto [init, cond, iter] = parse_for_args(loop_args);
|
||||
|
||||
/* Init statement. */
|
||||
const Token var_type = init[0];
|
||||
const Token var_name = init[1];
|
||||
const Token var_init = init[2];
|
||||
if (var_type.str() != "int" && var_type.str() != "uint") {
|
||||
report_error(var_init, "Can only unroll integer based loop.");
|
||||
return;
|
||||
}
|
||||
if (var_init != '=') {
|
||||
report_error(var_init, "Expecting assignment here.");
|
||||
return;
|
||||
}
|
||||
if (init[3] != Number && init[3] != '-') {
|
||||
report_error(init[3], "Expecting integer literal here.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Conditional statement. */
|
||||
int t = 0;
|
||||
const Token cond_var = cond[t++];
|
||||
const Token cond_type = cond[t++];
|
||||
if (cond_type.next() == '=') {
|
||||
t++; /* Skip equal sign. */
|
||||
}
|
||||
const Token cond_sign = (cond[t] == '+' || cond[t] == '-') ? cond[t++] : Token(parser);
|
||||
const Token cond_end = cond[t];
|
||||
if (cond_var.str() != var_name.str()) {
|
||||
report_error(cond_var, "Non matching loop counter variable.");
|
||||
return;
|
||||
}
|
||||
if (cond_end != Number) {
|
||||
report_error(cond_end, "Expecting integer literal here.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Iteration statement. */
|
||||
const Token iter_var = iter[0];
|
||||
const Token iter_type = iter[1];
|
||||
const Token iter_end = iter[1];
|
||||
int iter_incr = 0;
|
||||
if (iter_var.str() != var_name.str()) {
|
||||
report_error(iter_var, "Non matching loop counter variable.");
|
||||
return;
|
||||
}
|
||||
if (iter_type == Increment) {
|
||||
iter_incr = +1;
|
||||
if (cond_type == '>') {
|
||||
report_error(for_tok, "Unsupported condition in unrolled loop.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (iter_type == Decrement) {
|
||||
iter_incr = -1;
|
||||
if (cond_type == '<') {
|
||||
report_error(for_tok, "Unsupported condition in unrolled loop.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
report_error(iter_type, "Unsupported loop expression. Expecting ++ or --.");
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t init_value = stol(
|
||||
parser.substr_range_inclusive(var_init.next(), var_init.scope().back()));
|
||||
int64_t end_value = stol(
|
||||
parser.substr_range_inclusive(cond_sign.is_valid() ? cond_sign : cond_end, cond_end));
|
||||
/* TODO(fclem): Support arbitrary strides (aka, arbitrary iter statement). */
|
||||
int iter_count = abs(end_value - init_value);
|
||||
if (cond_type.next() == '=') {
|
||||
iter_count += 1;
|
||||
}
|
||||
|
||||
bool condition_is_trivial = (cond_end == cond.back());
|
||||
bool iteration_is_trivial = (iter_end == iter.back());
|
||||
|
||||
process_loop(tokens[0],
|
||||
iter_count,
|
||||
init_value,
|
||||
iter_incr,
|
||||
condition_is_trivial,
|
||||
iteration_is_trivial,
|
||||
init,
|
||||
cond,
|
||||
iter,
|
||||
loop_body);
|
||||
});
|
||||
|
||||
/* [[unroll_n(n)]]. */
|
||||
parser().foreach_match("f(..)[[A(1)]]{..}", [&](const vector<Token> tokens) {
|
||||
if (tokens[7].str() != "unroll_n") {
|
||||
return;
|
||||
}
|
||||
const Scope loop_args = tokens[1].scope();
|
||||
const Scope loop_body = tokens[13].scope();
|
||||
|
||||
auto [init, cond, iter] = parse_for_args(loop_args);
|
||||
|
||||
int iter_count = stol(string(tokens[9].str()));
|
||||
|
||||
process_loop(tokens[0], iter_count, 0, 0, false, false, init, cond, iter, loop_body);
|
||||
});
|
||||
} while (parser.apply_mutations());
|
||||
|
||||
/* Check for remaining keywords. */
|
||||
parser().foreach_match("[[A", [&](const vector<Token> tokens) {
|
||||
if (tokens[2].str().find("unroll") != string::npos) {
|
||||
report_error(tokens[0], "Incompatible loop format for [[unroll]].");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SourceProcessor::lower_static_branch(Parser &parser)
|
||||
{
|
||||
|
||||
do {
|
||||
/* Transform `if constexpr (...)` into `if (...) [[static_branch]]`. */
|
||||
parser().foreach_match("iC(..)", [&](const vector<Token> &tokens) {
|
||||
parser.erase(tokens[1]);
|
||||
parser.insert_after(tokens[5], "[[static_branch]]");
|
||||
});
|
||||
} while (parser.apply_mutations());
|
||||
|
||||
do {
|
||||
parser().foreach_match("i(..)[[A]]{..}", [&](const vector<Token> &tokens) {
|
||||
Token if_tok = tokens[0];
|
||||
Scope condition = tokens[1].scope();
|
||||
Token attribute = tokens[7];
|
||||
Scope body = tokens[10].scope();
|
||||
|
||||
if (attribute.str() != "static_branch") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (condition.str().find("&&") != string::npos || condition.str().find("||") != string::npos)
|
||||
{
|
||||
report_error(condition[0], "Expecting single condition.");
|
||||
return;
|
||||
}
|
||||
|
||||
const int i = condition[1].str() == "!" ? 2 : 1;
|
||||
/* TODO(fclem): Make full check of all params. */
|
||||
const bool is_constexpr = condition[i].str() == "true" || condition[i].str() == "false";
|
||||
const bool constexpr_val = is_constexpr ? (condition[i].str() == "true") ^ (i == 2) : false;
|
||||
|
||||
if (condition[i].str() != "srt_access" && !is_constexpr) {
|
||||
report_error(if_tok,
|
||||
"Expecting compilation or specialization constant. Make sure SRT arguments "
|
||||
"have the [[resource_table]] attribute.");
|
||||
return;
|
||||
}
|
||||
|
||||
Token before_body = body.front().prev();
|
||||
|
||||
string test = is_constexpr ? string(constexpr_val ? "1" : "0") :
|
||||
string(i == 2 ? "!" : "") + "SRT_CONSTANT_" +
|
||||
string(condition[4 + i].str()) + " ";
|
||||
if (condition[is_constexpr ? 2 : 7] != condition.back().prev()) {
|
||||
test += parser.substr_range_inclusive(condition[is_constexpr ? 2 : 7],
|
||||
condition.back().prev());
|
||||
}
|
||||
string directive = (if_tok.prev() == Else ? "#elif " : "#if ");
|
||||
|
||||
parser.insert_directive(before_body, directive + test);
|
||||
parser.erase(if_tok, before_body);
|
||||
|
||||
if (body.back().next() == Else) {
|
||||
Token else_tok = body.back().next();
|
||||
parser.erase(else_tok);
|
||||
if (else_tok.next() == If) {
|
||||
/* Will be processed later. */
|
||||
Token next_if = else_tok.next();
|
||||
/* Ensure the rest of the if clauses also have the attribute. */
|
||||
Scope attributes = next_if.next().scope().back().next().scope();
|
||||
if (attributes.type() != ScopeType::Subscript ||
|
||||
attributes.front().next().scope().str_exclusive() != "static_branch")
|
||||
{
|
||||
report_error(next_if, "Expecting next if statement to also be a static branch.");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
body = else_tok.next().scope();
|
||||
|
||||
parser.insert_directive(else_tok, "#else");
|
||||
}
|
||||
parser.insert_directive(body.back(), "#endif");
|
||||
});
|
||||
} while (parser.apply_mutations());
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
541
blender-5.2.0/source/blender/gpu/shader_tool/function.cc
Normal file
541
blender-5.2.0/source/blender/gpu/shader_tool/function.cc
Normal file
@@ -0,0 +1,541 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
/* Parse entry point definitions and mutating all parameter usage to global resources. */
|
||||
void SourceProcessor::lower_entry_points(Parser &parser)
|
||||
{
|
||||
using namespace metadata;
|
||||
|
||||
auto to_uppercase = [](string str) {
|
||||
for (char &c : str) {
|
||||
c = toupper(c);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
parser().foreach_function([&](bool, Token type, Token fn_name, Scope args, bool, Scope fn_body) {
|
||||
bool is_entry_point = false;
|
||||
bool is_compute_func = false;
|
||||
bool is_vertex_func = false;
|
||||
bool is_fragment_func = false;
|
||||
bool use_early_frag_test = false;
|
||||
bool use_clip_control = false;
|
||||
bool use_texture_atomic = false;
|
||||
string metal_max_total_threads_per_threadgroup;
|
||||
string local_size;
|
||||
|
||||
if (type.prev() == ']' && type.prev().scope().type() == ScopeType::Subscript) {
|
||||
Scope attributes = type.prev().prev().scope();
|
||||
attributes.foreach_attribute([&](Token attr, Scope attr_scope) {
|
||||
const string attr_str(attr.str());
|
||||
if (attr_str == "vertex") {
|
||||
is_vertex_func = true;
|
||||
is_entry_point = true;
|
||||
}
|
||||
else if (attr_str == "fragment") {
|
||||
is_fragment_func = true;
|
||||
is_entry_point = true;
|
||||
}
|
||||
else if (attr_str == "compute") {
|
||||
is_compute_func = true;
|
||||
is_entry_point = true;
|
||||
}
|
||||
else if (attr_str == "early_fragment_tests") {
|
||||
use_early_frag_test = true;
|
||||
}
|
||||
else if (attr_str == "local_size") {
|
||||
local_size = attr_scope.str();
|
||||
}
|
||||
else if (attr_str == "metal_max_total_threads_per_threadgroup") {
|
||||
metal_max_total_threads_per_threadgroup = attr_scope.str();
|
||||
}
|
||||
else if (attr_str == "clip_control") {
|
||||
use_clip_control = true;
|
||||
}
|
||||
else if (attr_str == "texture_atomic") {
|
||||
use_texture_atomic = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (is_entry_point && type.str() != "void") {
|
||||
report_error(type, "Entry point function must return void.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto parse_condition = [&](const Scope &attributes) {
|
||||
string cond;
|
||||
attributes.foreach_attribute([&](Token attribute_name, Scope attribute_parameters) {
|
||||
if (attribute_name.str() == "condition") {
|
||||
if (!cond.empty()) {
|
||||
report_error(attribute_name, "Only one condition attribute is allowed.");
|
||||
return;
|
||||
}
|
||||
attribute_parameters[1].scope().foreach_token(Word, [&](const Token tok) {
|
||||
cond += "int " + string(tok.str()) + " = ";
|
||||
cond += "ShaderCreateInfo::find_constant(constants, \"" + string(tok.str()) + "\"); ";
|
||||
});
|
||||
cond += "return " + string(attribute_parameters[1].scope().str()) + ";";
|
||||
}
|
||||
});
|
||||
|
||||
if (!cond.empty()) {
|
||||
cond = ", [](blender::Span<CompilationConstant> constants) { " + cond + "}";
|
||||
}
|
||||
return cond;
|
||||
};
|
||||
|
||||
auto replace_word = [&](const string &replaced, const string &replacement) {
|
||||
fn_body.foreach_token(Word, [&](const Token tok) {
|
||||
if (tok.str() == replaced) {
|
||||
parser.replace(tok, replacement, true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
auto replace_word_and_accessor = [&](const string &replaced, const string &replacement) {
|
||||
fn_body.foreach_token(Word, [&](const Token tok) {
|
||||
if (tok.next().type() == Dot && tok.str() == replaced) {
|
||||
parser.replace(tok, tok.next(), replacement);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* For now, just emit good old create info macros. */
|
||||
string create_info_decl;
|
||||
|
||||
if (!local_size.empty()) {
|
||||
if (!is_compute_func) {
|
||||
report_error(type, "Only compute entry point function can use [[local_size(x,y,z)]].");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "LOCAL_GROUP_SIZE" + local_size + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (use_early_frag_test) {
|
||||
if (!is_fragment_func) {
|
||||
report_error(type, "Only fragment entry point function can use [[use_early_frag_test]].");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "EARLY_FRAGMENT_TEST(true)\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (use_clip_control) {
|
||||
if (!is_vertex_func) {
|
||||
report_error(type, "Only vertex entry point function can use [[clip_control]].");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::CLIP_CONTROL)\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (use_texture_atomic) {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::TEXTURE_ATOMIC)\n";
|
||||
}
|
||||
|
||||
if (!metal_max_total_threads_per_threadgroup.empty()) {
|
||||
if (!is_compute_func) {
|
||||
report_error(type,
|
||||
"Only compute entry point function can use "
|
||||
"[[metal_max_total_threads_per_threadgroup(x)]].");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "MTL_MAX_TOTAL_THREADS_PER_THREADGROUP" +
|
||||
metal_max_total_threads_per_threadgroup + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
auto process_argument = [&](Token type, Token var, Scope attributes) {
|
||||
const bool is_const = type.prev() == Const;
|
||||
string srt_type(type.str());
|
||||
string srt_var(var.str());
|
||||
string srt_attr(attributes[1].str());
|
||||
|
||||
if (srt_attr == "vertex_id" && is_entry_point) {
|
||||
if (!is_vertex_func) {
|
||||
report_error(attributes[1], "[[vertex_id]] is only supported in vertex functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "int") {
|
||||
report_error(type, "[[vertex_id]] must be declared as `const int`.");
|
||||
}
|
||||
replace_word(srt_var, "gl_VertexID");
|
||||
metadata_.builtins.emplace_back(Builtin(hash("gl_VertexID")));
|
||||
create_info_decl += "BUILTINS(BuiltinBits::VERTEX_ID)\n";
|
||||
}
|
||||
else if (srt_attr == "instance_id" && is_entry_point) {
|
||||
if (!is_vertex_func) {
|
||||
report_error(attributes[1], "[[instance_id]] is only supported in vertex functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "int") {
|
||||
report_error(type, "[[instance_id]] must be declared as `const int`.");
|
||||
}
|
||||
replace_word(srt_var, "gl_InstanceID");
|
||||
metadata_.builtins.emplace_back(Builtin(hash("gl_InstanceID")));
|
||||
create_info_decl += "BUILTINS(BuiltinBits::INSTANCE_ID)\n";
|
||||
}
|
||||
else if (srt_attr == "instance_index" && is_entry_point) {
|
||||
if (!is_vertex_func) {
|
||||
report_error(attributes[1], "[[instance_index]] is only supported in vertex functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "int") {
|
||||
report_error(type, "[[instance_index]] must be declared as `const int`.");
|
||||
}
|
||||
replace_word(srt_var, "gpu_InstanceIndex");
|
||||
metadata_.builtins.emplace_back(Builtin(hash("gpu_InstanceIndex")));
|
||||
create_info_decl += "BUILTINS(BuiltinBits::INSTANCE_ID)\n";
|
||||
}
|
||||
else if (srt_attr == "base_instance" && is_entry_point) {
|
||||
if (!is_vertex_func) {
|
||||
report_error(attributes[1], "[[base_instance]] is only supported in vertex functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "int") {
|
||||
report_error(type,
|
||||
"[[base_instance]] must be declared as "
|
||||
"`const int`.");
|
||||
}
|
||||
replace_word(srt_var, "gpu_BaseInstance");
|
||||
metadata_.builtins.emplace_back(Builtin(hash("gpu_BaseInstance")));
|
||||
create_info_decl += "BUILTINS(BuiltinBits::INSTANCE_ID)\n";
|
||||
}
|
||||
else if (srt_attr == "point_size" && is_entry_point) {
|
||||
if (!is_vertex_func) {
|
||||
report_error(attributes[1], "[[point_size]] is only supported in vertex functions.");
|
||||
}
|
||||
else if (is_const || srt_type != "float") {
|
||||
report_error(type,
|
||||
"[[point_size]] must be declared as non-const reference (aka `float &`).");
|
||||
}
|
||||
replace_word(srt_var, "gl_PointSize");
|
||||
create_info_decl += "BUILTINS(BuiltinBits::POINT_SIZE)\n";
|
||||
}
|
||||
else if (srt_attr == "clip_distance" && is_entry_point) {
|
||||
if (!is_vertex_func) {
|
||||
report_error(attributes[1], "[[clip_distance]] is only supported in vertex functions.");
|
||||
}
|
||||
else if (is_const || srt_type != "float") {
|
||||
report_error(type,
|
||||
"[[clip_distance]] must be declared as non-const reference "
|
||||
"(aka `float (&)[]`).");
|
||||
}
|
||||
replace_word(srt_var, "gl_ClipDistance");
|
||||
create_info_decl += "BUILTINS(BuiltinBits::CLIP_DISTANCES)\n";
|
||||
}
|
||||
else if (srt_attr == "layer" && is_entry_point) {
|
||||
if (is_compute_func) {
|
||||
report_error(attributes[1],
|
||||
"[[layer]] is only supported in vertex and fragment functions.");
|
||||
}
|
||||
else if (is_vertex_func && (is_const || srt_type != "int")) {
|
||||
report_error(type,
|
||||
"[[layer]] must be declared as non-const reference "
|
||||
"(aka `int &`).");
|
||||
}
|
||||
else if (is_fragment_func && (!is_const || srt_type != "int")) {
|
||||
report_error(type,
|
||||
"[[layer]] must be declared as const reference "
|
||||
"(aka `const int &`).");
|
||||
}
|
||||
replace_word(srt_var, "gl_Layer");
|
||||
create_info_decl += "BUILTINS(BuiltinBits::LAYER)\n";
|
||||
}
|
||||
else if (srt_attr == "viewport_index" && is_entry_point) {
|
||||
if (is_compute_func) {
|
||||
report_error(attributes[1],
|
||||
"[[viewport_index]] is only supported in vertex and "
|
||||
"fragment functions.");
|
||||
}
|
||||
else if (is_vertex_func && (is_const || srt_type != "int")) {
|
||||
report_error(type,
|
||||
"[[viewport_index]] must be declared as non-const reference "
|
||||
"(aka `int &`).");
|
||||
}
|
||||
else if (is_fragment_func && (!is_const || srt_type != "int")) {
|
||||
report_error(type,
|
||||
"[[viewport_index]] must be declared as const reference "
|
||||
"(aka `const int &`).");
|
||||
}
|
||||
replace_word(srt_var, "gpu_ViewportIndex");
|
||||
create_info_decl += "BUILTINS(BuiltinBits::VIEWPORT_INDEX)\n";
|
||||
}
|
||||
else if (srt_attr == "position" && is_entry_point) {
|
||||
if (!is_vertex_func) {
|
||||
report_error(attributes[1], "[[position]] is only supported in vertex functions.");
|
||||
}
|
||||
else if (is_const || srt_type != "float4") {
|
||||
report_error(type,
|
||||
"[[position]] must be declared as non-const reference (aka `float4 &`).");
|
||||
}
|
||||
else {
|
||||
replace_word(srt_var, "gl_Position");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "frag_coord" && is_entry_point) {
|
||||
if (!is_fragment_func) {
|
||||
report_error(attributes[1], "[[frag_coord]] is only supported in fragment functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "float4") {
|
||||
report_error(type, "[[frag_coord]] must be declared as `const float4`.");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::FRAG_COORD)\n";
|
||||
replace_word(srt_var, "gl_FragCoord");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "point_coord" && is_entry_point) {
|
||||
if (!is_fragment_func) {
|
||||
report_error(attributes[1], "[[point_coord]] is only supported in fragment functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "float2") {
|
||||
report_error(type, "[[point_coord]] must be declared as `const float2`.");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::POINT_COORD)\n";
|
||||
replace_word(srt_var, "gl_PointCoord");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "front_facing" && is_entry_point) {
|
||||
if (!is_fragment_func) {
|
||||
report_error(attributes[1], "[[front_facing]] is only supported in fragment functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "bool") {
|
||||
report_error(type, "[[front_facing]] must be declared as `const bool`.");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::FRONT_FACING)\n";
|
||||
replace_word(srt_var, "gl_FrontFacing");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "global_invocation_id" && is_entry_point) {
|
||||
if (!is_compute_func) {
|
||||
report_error(attributes[1],
|
||||
"[[global_invocation_id]] is only supported in compute functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "uint3") {
|
||||
report_error(type, "[[global_invocation_id]] must be declared as `const uint3`.");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::GLOBAL_INVOCATION_ID)\n";
|
||||
replace_word(srt_var, "gl_GlobalInvocationID");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "local_invocation_id" && is_entry_point) {
|
||||
if (!is_compute_func) {
|
||||
report_error(attributes[1],
|
||||
"[[local_invocation_id]] is only supported in compute functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "uint3") {
|
||||
report_error(type, "[[local_invocation_id]] must be declared as `const uint3`.");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::LOCAL_INVOCATION_ID)\n";
|
||||
replace_word(srt_var, "gl_LocalInvocationID");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "local_invocation_index" && is_entry_point) {
|
||||
if (!is_compute_func) {
|
||||
report_error(attributes[1],
|
||||
"[[local_invocation_index]] is only supported in compute functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "uint") {
|
||||
report_error(type, "[[local_invocation_index]] must be declared as `const uint`.");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::LOCAL_INVOCATION_INDEX)\n";
|
||||
replace_word(srt_var, "gl_LocalInvocationIndex");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "work_group_id" && is_entry_point) {
|
||||
if (!is_compute_func) {
|
||||
report_error(attributes[1], "[[work_group_id]] is only supported in compute functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "uint3") {
|
||||
report_error(type,
|
||||
"[[work_group_id]] must be declared as "
|
||||
"`const uint3`.");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::WORK_GROUP_ID)\n";
|
||||
replace_word(srt_var, "gl_WorkGroupID");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "num_work_groups" && is_entry_point) {
|
||||
if (!is_compute_func) {
|
||||
report_error(attributes[1],
|
||||
"[[num_work_groups]] is only supported in compute functions.");
|
||||
}
|
||||
else if (!is_const || srt_type != "uint3") {
|
||||
report_error(type,
|
||||
"[[num_work_groups]] must be declared as "
|
||||
"`const uint3`.");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::NUM_WORK_GROUP)\n";
|
||||
replace_word(srt_var, "gl_NumWorkGroups");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "in") {
|
||||
if (is_compute_func) {
|
||||
report_error(attributes[1],
|
||||
"[[in]] is only supported in vertex and fragment functions.");
|
||||
}
|
||||
else if (!is_const) {
|
||||
report_error(type, "[[in]] must be declared as const reference.");
|
||||
}
|
||||
else if (is_vertex_func) {
|
||||
replace_word_and_accessor(srt_var, "");
|
||||
create_info_decl += "ADDITIONAL_INFO(" + srt_type + ")\n";
|
||||
}
|
||||
else if (is_fragment_func) {
|
||||
replace_word_and_accessor(srt_var, srt_type + "_");
|
||||
// create_info_decl += "VERTEX_OUT(" + srt_type + ")\n";
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "subpass_in") {
|
||||
if (is_compute_func) {
|
||||
report_error(attributes[1], "[[subpass_in]] is only supported in fragment functions.");
|
||||
}
|
||||
else if (!is_const) {
|
||||
report_error(type, "[[subpass_in]] must be declared as const reference.");
|
||||
}
|
||||
else if (is_fragment_func) {
|
||||
replace_word_and_accessor(srt_var, srt_type + "_");
|
||||
create_info_decl += "ADDITIONAL_INFO(" + srt_type + ")\n";
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "out") {
|
||||
if (is_compute_func) {
|
||||
report_error(attributes[1],
|
||||
"[[out]] is only supported in vertex and fragment functions.");
|
||||
}
|
||||
else if (is_const) {
|
||||
report_error(type, "[[out]] must be declared as non-const reference.");
|
||||
}
|
||||
else if (is_vertex_func) {
|
||||
replace_word_and_accessor(srt_var, srt_type + "_");
|
||||
create_info_decl += "VERTEX_OUT(" + srt_type + "_t)\n";
|
||||
}
|
||||
else if (is_fragment_func) {
|
||||
replace_word_and_accessor(srt_var, srt_type + "_");
|
||||
create_info_decl += "ADDITIONAL_INFO(" + srt_type + ")\n";
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "resource_table") {
|
||||
if (is_entry_point) {
|
||||
/* Add dummy var at start of function body. */
|
||||
parser.insert_after(fn_body.front().str_index_start(),
|
||||
" " + srt_type + " " + srt_var + "{};");
|
||||
string res_condition_lambda = parse_condition(attributes);
|
||||
if (res_condition_lambda.empty()) {
|
||||
create_info_decl += "ADDITIONAL_INFO(" + srt_type + ")\n";
|
||||
}
|
||||
else {
|
||||
create_info_decl += ".additional_info_with_condition(\"" + srt_type + "\"" +
|
||||
res_condition_lambda + ")\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "frag_depth") {
|
||||
if (srt_type != "float") {
|
||||
report_error(type, "[[frag_depth]] needs to be declared as float");
|
||||
}
|
||||
const string mode(attributes[3].str());
|
||||
|
||||
if (mode != "any" && mode != "greater" && mode != "less") {
|
||||
report_error(attributes[3], "unrecognized mode, expecting 'any', 'greater' or 'less'");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "DEPTH_WRITE(DepthWrite::" + to_uppercase(mode) + ")\n";
|
||||
replace_word(srt_var, "gl_FragDepth");
|
||||
}
|
||||
}
|
||||
else if (srt_attr == "frag_stencil_ref") {
|
||||
if (srt_type != "int") {
|
||||
report_error(type, "[[frag_stencil_ref]] needs to be declared as int");
|
||||
}
|
||||
else {
|
||||
create_info_decl += "BUILTINS(BuiltinBits::STENCIL_REF)\n";
|
||||
replace_word(srt_var, "gl_FragStencilRefARB");
|
||||
}
|
||||
}
|
||||
else {
|
||||
report_error(attributes[1], "Invalid attribute.");
|
||||
}
|
||||
};
|
||||
|
||||
args.foreach_match("[[..]]c?AA", [&](const vector<Token> toks) {
|
||||
process_argument(toks[8], toks[9], toks[1].scope());
|
||||
});
|
||||
args.foreach_match("[[..]]c?A&A", [&](const vector<Token> toks) {
|
||||
process_argument(toks[8], toks[10], toks[1].scope());
|
||||
});
|
||||
|
||||
args.foreach_match("[[..]]c?A(&A)", [&](const vector<Token> toks) {
|
||||
process_argument(toks[8], toks[11], toks[1].scope());
|
||||
});
|
||||
|
||||
if (is_entry_point) {
|
||||
if (create_info_decl.empty()) {
|
||||
/* Add unused define to avoid warning about unused expression. */
|
||||
create_info_decl += "DEFINE(\"EMPTY_CREATE_INFO\")\n";
|
||||
}
|
||||
create_info_decl = "GPU_SHADER_CREATE_INFO(" + string(fn_name.str()) + "_infos_)\n" +
|
||||
create_info_decl + "GPU_SHADER_CREATE_END()\n";
|
||||
metadata_.create_infos_declarations.emplace_back(create_info_decl);
|
||||
}
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/* Removes entry point arguments to make it compatible with the legacy code.
|
||||
* Has to run after mutation related to function arguments. */
|
||||
void SourceProcessor::lower_entry_points_signature(Parser &parser)
|
||||
{
|
||||
using namespace metadata;
|
||||
|
||||
parser().foreach_function([&](bool, Token type, Token name, Scope args, bool, Scope fn_body) {
|
||||
bool is_entry_point = false;
|
||||
|
||||
if (type.prev() == ']' && type.prev().scope().type() == ScopeType::Subscript) {
|
||||
Scope attributes = type.prev().prev().scope();
|
||||
attributes.foreach_attribute([&](Token attr, Scope) {
|
||||
const string attr_str(attr.str());
|
||||
if (attr_str == "vertex" || attr_str == "fragment" || attr_str == "compute") {
|
||||
is_entry_point = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (is_entry_point && args.str() != "()") {
|
||||
parser.erase(args.front().next(), args.back().prev());
|
||||
}
|
||||
|
||||
/* Mute entry points when not enabled.
|
||||
* Could be lifted at some point, but for now required because of stage_in/out parameters. */
|
||||
if (is_entry_point) {
|
||||
/* Take attributes into account. */
|
||||
parser.insert_directive(type.prev().scope().front().prev(),
|
||||
"#if defined(ENTRY_POINT_" + string(name.str()) + ")");
|
||||
parser.insert_directive(fn_body.back(), "#endif");
|
||||
}
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
} // namespace blender::gpu::shader
|
||||
1201
blender-5.2.0/source/blender/gpu/shader_tool/grammar.cc
Normal file
1201
blender-5.2.0/source/blender/gpu/shader_tool/grammar.cc
Normal file
File diff suppressed because it is too large
Load Diff
467
blender-5.2.0/source/blender/gpu/shader_tool/intermediate.cc
Normal file
467
blender-5.2.0/source/blender/gpu/shader_tool/intermediate.cc
Normal file
@@ -0,0 +1,467 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*
|
||||
*/
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "scope.hh"
|
||||
#include "token.hh"
|
||||
#include "token_stream.hh"
|
||||
|
||||
#include "lexit/lexit.hh"
|
||||
#include "lexit/tables.hh"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define always_inline __forceinline
|
||||
#else
|
||||
# define always_inline inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
namespace blender::gpu::shader::parser {
|
||||
|
||||
size_t line_number(const std::string_view &str, size_t pos)
|
||||
{
|
||||
std::string_view directive = "#line ";
|
||||
/* String to count the number of line. */
|
||||
std::string_view sub_str = str.substr(0, pos);
|
||||
size_t nearest_line_directive = sub_str.rfind(directive);
|
||||
size_t line_count = 1;
|
||||
if (nearest_line_directive != std::string::npos) {
|
||||
sub_str = sub_str.substr(nearest_line_directive + directive.size());
|
||||
line_count = std::stoll(std::string(sub_str)) - 1;
|
||||
}
|
||||
return line_count + std::count(sub_str.begin(), sub_str.end(), '\n');
|
||||
}
|
||||
|
||||
size_t char_number(const std::string_view &str, size_t pos)
|
||||
{
|
||||
std::string_view sub_str = str.substr(0, pos);
|
||||
size_t nearest_line_directive = sub_str.rfind('\n');
|
||||
return (nearest_line_directive == std::string::npos) ?
|
||||
(sub_str.size()) :
|
||||
(sub_str.size() - nearest_line_directive - 1);
|
||||
}
|
||||
|
||||
std::string filename(const std::string_view &str, size_t pos)
|
||||
{
|
||||
std::string_view directive = "#line ";
|
||||
std::string_view sub_str = str.substr(0, pos);
|
||||
|
||||
while (!sub_str.empty()) {
|
||||
size_t nearest_line_directive = sub_str.rfind(directive);
|
||||
/* If no directive is found, break and return an empty string. */
|
||||
if (nearest_line_directive == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
/* Extract just the line containing the directive. */
|
||||
size_t line_end = sub_str.find('\n', nearest_line_directive);
|
||||
std::string_view directive_line = sub_str.substr(nearest_line_directive,
|
||||
line_end - nearest_line_directive);
|
||||
/* Look for the quotes containing the filepath. */
|
||||
size_t first_quote = directive_line.find('"');
|
||||
if (first_quote != std::string_view::npos) {
|
||||
size_t second_quote = directive_line.find('"', first_quote + 1);
|
||||
if (second_quote != std::string_view::npos) {
|
||||
return std::string(directive_line.substr(first_quote + 1, second_quote - first_quote - 1));
|
||||
}
|
||||
}
|
||||
/* If this directive didn't have a filename, shrink the search space to look further up. */
|
||||
if (nearest_line_directive == 0) {
|
||||
break;
|
||||
}
|
||||
sub_str = sub_str.substr(0, nearest_line_directive);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string line_str(const std::string_view &str, size_t pos)
|
||||
{
|
||||
size_t start = str.rfind('\n', pos);
|
||||
size_t end = str.find('\n', pos);
|
||||
start = (start != std::string::npos) ? start + 1 : 0;
|
||||
return std::string(str.substr(start, end - start));
|
||||
}
|
||||
|
||||
Scope Token::scope() const
|
||||
{
|
||||
const ParserBase &parser = static_cast<const ParserBase &>(*buf_);
|
||||
if (this->is_invalid()) {
|
||||
return Scope(parser, -1);
|
||||
}
|
||||
return Scope(parser, parser.token_scope[index_]);
|
||||
}
|
||||
|
||||
Scope Token::attribute_before() const
|
||||
{
|
||||
const ParserBase &parser = static_cast<const ParserBase &>(*buf_);
|
||||
if (is_invalid()) {
|
||||
return Scope(parser, -1);
|
||||
}
|
||||
Token prev = this->prev();
|
||||
if (prev == ']' && prev.prev().scope().type() == ScopeType::Attributes) {
|
||||
return prev.prev().scope();
|
||||
}
|
||||
return Scope(parser, -1);
|
||||
}
|
||||
|
||||
Scope Token::attribute_after() const
|
||||
{
|
||||
const ParserBase &parser = static_cast<const ParserBase &>(*buf_);
|
||||
if (is_invalid()) {
|
||||
return Scope(parser, -1);
|
||||
}
|
||||
Token next = this->next();
|
||||
if (next == '[' && next.next().scope().type() == ScopeType::Attributes) {
|
||||
return next.next().scope();
|
||||
}
|
||||
return Scope(parser, -1);
|
||||
}
|
||||
|
||||
void ErrorHandler::report(Token tok, std::string_view message)
|
||||
{
|
||||
/* Only log the first error. */
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string token_filename = tok.filename();
|
||||
std::string full_report = token_filename.empty() ? default_filename : token_filename;
|
||||
full_report += ":" + std::to_string(tok.line_number());
|
||||
full_report += ":" + std::to_string(tok.char_number() + 1);
|
||||
full_report += ": " + std::string(message);
|
||||
if (tok.is_valid()) {
|
||||
full_report += "\n";
|
||||
full_report += tok.line_str() + "\n";
|
||||
if (tok.str().size() > 0) {
|
||||
full_report += std::string(tok.char_number(), ' ') + "^" +
|
||||
std::string(tok.str().size() - 1, '~');
|
||||
}
|
||||
}
|
||||
err = {std::string(message), full_report};
|
||||
}
|
||||
|
||||
void ErrorHandler::report(int row, int column, std::string line, std::string_view message)
|
||||
{
|
||||
/* Only log the first error. */
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string full_report = default_filename;
|
||||
full_report += ":" + std::to_string(row);
|
||||
full_report += ":" + std::to_string(column + 1);
|
||||
full_report += ": " + std::string(message) + "\n";
|
||||
full_report += line;
|
||||
err = {std::string(message), full_report};
|
||||
}
|
||||
|
||||
alignas(128) const std::array<CharClass, 128> LexerBase::default_char_class_table = [] {
|
||||
std::array<CharClass, 128> table;
|
||||
memcpy(table.data(), lexit::char_class_table, sizeof(lexit::char_class_table));
|
||||
return table;
|
||||
}();
|
||||
|
||||
/* Same thing as default table but consider numbers as words to avoid second merging pass. */
|
||||
alignas(128) const std::array<CharClass, 128> LexerBase::bsl_char_class_table = [] {
|
||||
std::array<CharClass, 128> table;
|
||||
memcpy(table.data(), lexit::char_class_table, sizeof(lexit::char_class_table));
|
||||
|
||||
table['\n'] = CharClass::WhiteSpace;
|
||||
/* Make < and > separators in order to support template.
|
||||
* That means >= and <= need to be manually handled. */
|
||||
table['<'] = CharClass::Separator;
|
||||
table['>'] = CharClass::Separator;
|
||||
return table;
|
||||
}();
|
||||
|
||||
static always_inline TokenType multi_tok_lookup(TokenType input, std::string_view s)
|
||||
{
|
||||
switch (s.size()) {
|
||||
case 2:
|
||||
switch (s[0]) {
|
||||
case '=':
|
||||
return (s[1] == '=') ? Equal : input;
|
||||
case '!':
|
||||
return (s[1] == '=') ? NotEqual : input;
|
||||
case '|':
|
||||
return (s[1] == '|') ? LogicalOr : input;
|
||||
case '&':
|
||||
return (s[1] == '&') ? LogicalAnd : input;
|
||||
case '<':
|
||||
return (s[1] == '=') ? LEqual : input;
|
||||
case '>':
|
||||
return (s[1] == '=') ? GEqual : input;
|
||||
case '+':
|
||||
return (s[1] == '+') ? Increment : input;
|
||||
case '-':
|
||||
return (s[1] == '-') ? Decrement : input;
|
||||
case '#':
|
||||
return (s[1] == '#') ? DoubleHash : input;
|
||||
default:
|
||||
return input;
|
||||
}
|
||||
default:
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr always_inline uint8_t perfect_hash(std::string_view s)
|
||||
{
|
||||
return s.size() * (s[0] - s.back() * 2);
|
||||
}
|
||||
|
||||
static always_inline TokenType type_lookup(std::string_view s)
|
||||
{
|
||||
switch (perfect_hash(s)) {
|
||||
case perfect_hash("do"):
|
||||
return (s == "do") ? Do : Word;
|
||||
case perfect_hash("if"):
|
||||
return (s == "if") ? If : Word;
|
||||
case perfect_hash("for"):
|
||||
return (s == "for") ? For : Word;
|
||||
case perfect_hash("case"):
|
||||
return (s == "case") ? Case : Word;
|
||||
case perfect_hash("else"):
|
||||
return (s == "else") ? Else : Word;
|
||||
case perfect_hash("enum"):
|
||||
return (s == "enum") ? Enum : Word;
|
||||
case perfect_hash("this"):
|
||||
return (s == "this") ? This : Word;
|
||||
case perfect_hash("break"):
|
||||
return (s == "break") ? Break : Word;
|
||||
case perfect_hash("class"):
|
||||
return (s == "class") ? Class : Word;
|
||||
case perfect_hash("const"):
|
||||
return (s == "const") ? Const : Word;
|
||||
case perfect_hash("union"):
|
||||
return (s == "union") ? Union : Word;
|
||||
case perfect_hash("using"):
|
||||
return (s == "using") ? Using : Word;
|
||||
case perfect_hash("while"):
|
||||
return (s == "while") ? While : Word;
|
||||
case perfect_hash("inline"):
|
||||
return (s == "inline") ? Inline : Word;
|
||||
case perfect_hash("public"):
|
||||
return (s == "public") ? Public : Word;
|
||||
case perfect_hash("return"):
|
||||
return (s == "return") ? Return : Word;
|
||||
case perfect_hash("static"):
|
||||
return (s == "static") ? Static : Word;
|
||||
case perfect_hash("struct"):
|
||||
return (s == "struct") ? Struct : Word;
|
||||
case perfect_hash("switch"):
|
||||
return (s == "switch") ? Switch : Word;
|
||||
case perfect_hash("private"):
|
||||
return (s == "private") ? Private : Word;
|
||||
case perfect_hash("continue"):
|
||||
return (s == "continue") ? Continue : Word;
|
||||
case perfect_hash("template"):
|
||||
return (s == "template") ? Template : Word;
|
||||
case perfect_hash("constexpr"):
|
||||
return (s == "constexpr") ? Constexpr : Word;
|
||||
case perfect_hash("namespace"):
|
||||
return (s == "namespace") ? Namespace : Word;
|
||||
default:
|
||||
return Word;
|
||||
}
|
||||
}
|
||||
|
||||
void LexerBase::identify_keywords()
|
||||
{
|
||||
for (auto tok : *this) {
|
||||
switch (tok.type()) {
|
||||
case Word:
|
||||
tok.type() = type_lookup(tok.str());
|
||||
break;
|
||||
case Number:
|
||||
break;
|
||||
default:
|
||||
tok.type() = multi_tok_lookup(tok.type(), tok.str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LexerBase::identify_template_tokens()
|
||||
{
|
||||
for (int i = 1; i < size(); ++i) {
|
||||
TokenMut tok = (*this)[i];
|
||||
TokenType type = tok.type();
|
||||
if (type == '<' || type == '>') {
|
||||
Token prev = (*this)[i - 1];
|
||||
const bool preceded_by_space = prev.followed_by_whitespace();
|
||||
/* Rely on the fact that template are formatted without spaces but comparison isn't. */
|
||||
if (type == '<') {
|
||||
if ((!preceded_by_space && prev != AngleOpen) || prev == Template) {
|
||||
tok.type() = TemplateOpen;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!preceded_by_space && (prev != AngleClose) && (prev != Minus)) {
|
||||
tok.type() = TemplateClose;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LexerBase::reset_template_tokens()
|
||||
{
|
||||
for (int i = 1; i < size(); ++i) {
|
||||
TokenMut tok = (*this)[i];
|
||||
switch (tok.type()) {
|
||||
case TemplateOpen:
|
||||
tok.type() = lexit::AngleOpen;
|
||||
break;
|
||||
case TemplateClose:
|
||||
tok.type() = lexit::AngleClose;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ScopeStack {
|
||||
struct Item {
|
||||
ScopeType type;
|
||||
size_t start;
|
||||
int index;
|
||||
};
|
||||
|
||||
int scope_index = 0;
|
||||
std::vector<ScopeStack::Item> scopes;
|
||||
/* Output. */
|
||||
std::vector<IndexRange> ranges;
|
||||
std::vector<ScopeType> types;
|
||||
|
||||
ScopeStack(size_t predicted_scope_count)
|
||||
{
|
||||
/* Predicted max nesting depth. */
|
||||
scopes.reserve(128);
|
||||
|
||||
ranges.reserve(predicted_scope_count);
|
||||
types.reserve(predicted_scope_count);
|
||||
}
|
||||
|
||||
void always_inline enter_scope(ScopeType type, size_t start_tok_id)
|
||||
{
|
||||
scopes.emplace_back(Item{type, start_tok_id, scope_index++});
|
||||
ranges.emplace_back(start_tok_id, 1);
|
||||
types.emplace_back(type);
|
||||
};
|
||||
|
||||
void always_inline exit_scope(int end_tok_id)
|
||||
{
|
||||
if (scopes.empty()) {
|
||||
return;
|
||||
}
|
||||
Item scope = scopes.back();
|
||||
ranges[scope.index].size = end_tok_id - scope.start + 1;
|
||||
scopes.pop_back();
|
||||
};
|
||||
|
||||
Item always_inline back() const
|
||||
{
|
||||
return scopes.back();
|
||||
}
|
||||
|
||||
bool always_inline empty() const
|
||||
{
|
||||
return scopes.empty();
|
||||
}
|
||||
};
|
||||
|
||||
void ParserBase::build_token_to_scope_map()
|
||||
{
|
||||
token_scope.clear();
|
||||
token_scope.resize(scope_ranges[0].size);
|
||||
|
||||
int scope_id = 0;
|
||||
for (const IndexRange &range : scope_ranges) {
|
||||
std::fill(token_scope.begin() + range.start,
|
||||
token_scope.begin() + range.start + range.size,
|
||||
scope_id);
|
||||
scope_id++;
|
||||
}
|
||||
|
||||
update_string_view();
|
||||
}
|
||||
|
||||
Token ParserBase::operator[](int i) const
|
||||
{
|
||||
return Token(*this, i);
|
||||
}
|
||||
|
||||
void ParserBase::update_string_view()
|
||||
{
|
||||
assert(this->scope_types.data() != nullptr);
|
||||
assert(this->scope_types.size() > 0);
|
||||
this->scope_types_str = std::string_view(reinterpret_cast<char *>(this->scope_types.data()),
|
||||
this->scope_types.size());
|
||||
}
|
||||
|
||||
bool MutableString::apply_mutations(LexerBase &lexer, const bool all_mutation_ordered)
|
||||
{
|
||||
if (mutations_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!all_mutation_ordered) {
|
||||
/* Order mutations so that they can be applied in one pass. */
|
||||
std::stable_sort(mutations_.begin(), mutations_.end());
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
else {
|
||||
assert(std::is_sorted(mutations_.begin(), mutations_.end()));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Make sure to pad the input string in case of insertion after the last char. */
|
||||
bool added_trailing_new_line = false;
|
||||
if (str_.back() != '\n') {
|
||||
str_ += '\n';
|
||||
added_trailing_new_line = true;
|
||||
}
|
||||
|
||||
std::string result;
|
||||
result.reserve(str_.size());
|
||||
|
||||
int64_t offset = 0;
|
||||
for (const Mutation &mut : mutations_) {
|
||||
size_t start = mut.src_range.start;
|
||||
size_t end = start + mut.src_range.size;
|
||||
/* Copy unchanged text. */
|
||||
result.append(str_.data() + offset, start - offset);
|
||||
/* Append replacement. */
|
||||
result.append(mut.replacement);
|
||||
offset = end;
|
||||
}
|
||||
result.append(str_.data() + offset, str_.size() - offset);
|
||||
|
||||
str_ = std::move(result);
|
||||
|
||||
mutations_.clear();
|
||||
|
||||
if (added_trailing_new_line) {
|
||||
str_.pop_back();
|
||||
}
|
||||
/* String have changed. Update string view. */
|
||||
lexer.str_ = str_;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader::parser
|
||||
407
blender-5.2.0/source/blender/gpu/shader_tool/intermediate.hh
Normal file
407
blender-5.2.0/source/blender/gpu/shader_tool/intermediate.hh
Normal file
@@ -0,0 +1,407 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*
|
||||
* Very simple parsing of our shader file that are a subset of C++. It allows to traverse the
|
||||
* semantic using tokens and scopes instead of trying to match string patterns throughout the whole
|
||||
* input string.
|
||||
*
|
||||
* The goal of this representation is to output code that doesn't modify the style of the input
|
||||
* string and keep the same line numbers (to match compilation error with input source).
|
||||
*
|
||||
* The `TokenStream` class contain a copy of the given string to apply string substitutions (called
|
||||
* `Mutation`). It is usually faster to record all of them and apply them all at once after
|
||||
* scanning through the whole semantic representation. In the rare case where mutation need to
|
||||
* overlap (recursive processing), it is better to do them in passes until there is no mutation to
|
||||
* do.
|
||||
*
|
||||
* `Token` and `Scope` are read only interfaces to the data stored inside the `TokenStream`.
|
||||
* The data is stored as SoA (Structure of Arrays) for fast traversal.
|
||||
* The types of token and scopes are defined as readable chars to easily create sequences of token
|
||||
* type.
|
||||
*
|
||||
* The parsing phase doesn't apply any preprocessor. All preprocessor directive are parsed as
|
||||
* `Preprocessor` scope but they are not expanded.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scope.hh"
|
||||
#include "token.hh"
|
||||
#include "token_stream.hh"
|
||||
#include "utils.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
namespace blender::gpu::shader::parser {
|
||||
|
||||
struct MutableString {
|
||||
std::string str_;
|
||||
|
||||
struct Mutation {
|
||||
/* Range of the original string to replace. */
|
||||
IndexRange src_range;
|
||||
/* The replacement string. */
|
||||
std::string replacement;
|
||||
|
||||
Mutation(IndexRange src_range, std::string replacement)
|
||||
: src_range(src_range), replacement(replacement)
|
||||
{
|
||||
assert(src_range.size >= 0);
|
||||
assert(src_range.start >= 0);
|
||||
}
|
||||
|
||||
/* Define operator in order to sort the mutation by starting position.
|
||||
* Otherwise, applying them in one pass will not work. */
|
||||
friend bool operator<(const Mutation &a, const Mutation &b)
|
||||
{
|
||||
return a.src_range.start < b.src_range.start;
|
||||
}
|
||||
};
|
||||
std::vector<Mutation> mutations_;
|
||||
|
||||
MutableString(const std::string_view input) : str_(input) {}
|
||||
|
||||
/* Disable copy construction and assignment. */
|
||||
MutableString(const MutableString &other) = delete;
|
||||
MutableString &operator=(const MutableString &other) = delete;
|
||||
/* Explicitly enable default construction, move construction and move assignment. */
|
||||
MutableString(MutableString &&other) = default;
|
||||
MutableString &operator=(MutableString &&other) = default;
|
||||
|
||||
/* Access internal string without applying pending mutations. */
|
||||
std::string substr_range_inclusive(size_t start, size_t end)
|
||||
{
|
||||
return str_.substr(start, end - start + 1);
|
||||
}
|
||||
/* Access internal string without applying pending mutations. */
|
||||
std::string substr_range_inclusive(Token start, Token end)
|
||||
{
|
||||
return substr_range_inclusive(start.str_index_start(), end.str_index_last());
|
||||
}
|
||||
|
||||
/* Access internal string without applying pending mutations. */
|
||||
std::string_view substr_range_inclusive_view(size_t start, size_t end)
|
||||
{
|
||||
return std::string_view(str_).substr(start, end - start + 1);
|
||||
}
|
||||
/* Access internal string without applying pending mutations. */
|
||||
std::string_view substr_range_inclusive_view(Token start, Token end)
|
||||
{
|
||||
return substr_range_inclusive_view(start.str_index_start(), end.str_index_last());
|
||||
}
|
||||
|
||||
/* Replace everything from `from` to `to` (inclusive).
|
||||
* Return true on success. */
|
||||
bool replace_try(size_t from, size_t to, const std::string &replacement)
|
||||
{
|
||||
IndexRange range = IndexRange(from, to + 1 - from);
|
||||
for (const Mutation &mut : mutations_) {
|
||||
if (mut.src_range.overlaps(range)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
mutations_.emplace_back(range, replacement);
|
||||
return true;
|
||||
}
|
||||
/* Replace everything from `from` to `to` (inclusive).
|
||||
* Return true on success. */
|
||||
bool replace_try(Token from,
|
||||
Token to,
|
||||
const std::string &replacement,
|
||||
bool keep_trailing_whitespaces = false)
|
||||
{
|
||||
if (keep_trailing_whitespaces) {
|
||||
return replace_try(from.str_index_start(), to.str_index_last_no_whitespace(), replacement);
|
||||
}
|
||||
return replace_try(from.str_index_start(), to.str_index_last(), replacement);
|
||||
}
|
||||
|
||||
/* Replace everything from `from` to `to` (inclusive). */
|
||||
void replace(size_t from, size_t to, const std::string &replacement)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
bool success = replace_try(from, to, replacement);
|
||||
assert(success);
|
||||
(void)success;
|
||||
#else
|
||||
/* No check in release. */
|
||||
IndexRange range = IndexRange(from, to + 1 - from);
|
||||
mutations_.emplace_back(range, replacement);
|
||||
#endif
|
||||
}
|
||||
/* Replace everything from `from` to `to` (inclusive). */
|
||||
void replace(Token from,
|
||||
Token to,
|
||||
const std::string &replacement,
|
||||
bool keep_trailing_whitespaces = false)
|
||||
{
|
||||
if (keep_trailing_whitespaces) {
|
||||
replace(from.str_index_start(), to.str_index_last_no_whitespace(), replacement);
|
||||
}
|
||||
else {
|
||||
replace(from.str_index_start(), to.str_index_last(), replacement);
|
||||
}
|
||||
}
|
||||
|
||||
/* Replace token by string. */
|
||||
void replace(Token tok, const std::string &replacement, bool keep_trailing_whitespaces = false)
|
||||
{
|
||||
if (keep_trailing_whitespaces) {
|
||||
replace(tok.str_index_start(), tok.str_index_last_no_whitespace(), replacement);
|
||||
}
|
||||
else {
|
||||
replace(tok.str_index_start(), tok.str_index_last(), replacement);
|
||||
}
|
||||
}
|
||||
/* Replace Scope by string. */
|
||||
void replace(Scope scope, const std::string &replacement, bool keep_trailing_whitespaces = false)
|
||||
{
|
||||
if (keep_trailing_whitespaces) {
|
||||
replace(scope.front().str_index_start(),
|
||||
scope.back().str_index_last_no_whitespace(),
|
||||
replacement);
|
||||
}
|
||||
else {
|
||||
replace(scope.front(), scope.back(), replacement);
|
||||
}
|
||||
}
|
||||
|
||||
/* Replace the content from `from` to `to` (inclusive) by whitespaces without changing
|
||||
* line count and keep the remaining indentation spaces. */
|
||||
bool erase_try(size_t from, size_t to)
|
||||
{
|
||||
IndexRange range = IndexRange(from, to + 1 - from);
|
||||
std::string content = str_.substr(range.start, range.size);
|
||||
size_t lines = std::count(content.begin(), content.end(), '\n');
|
||||
size_t spaces = content.find_last_of("\n");
|
||||
if (spaces != std::string::npos) {
|
||||
spaces = content.length() - (spaces + 1);
|
||||
}
|
||||
else {
|
||||
spaces = content.length();
|
||||
}
|
||||
return replace_try(from, to, std::string(lines, '\n') + std::string(spaces, ' '));
|
||||
}
|
||||
void erase(size_t from, size_t to)
|
||||
{
|
||||
bool result = erase_try(from, to);
|
||||
assert(result);
|
||||
(void)result;
|
||||
}
|
||||
/* Replace the content from `from` to `to` (inclusive) by whitespaces without changing
|
||||
* line count and keep the remaining indentation spaces. */
|
||||
bool erase_try(Token from, Token to)
|
||||
{
|
||||
if (from.is_invalid() || to.is_invalid()) {
|
||||
return true;
|
||||
}
|
||||
assert(from.index_ <= to.index_);
|
||||
return erase_try(from.str_index_start(), to.str_index_last());
|
||||
}
|
||||
void erase(Token from, Token to)
|
||||
{
|
||||
bool result = erase_try(from, to);
|
||||
assert(result);
|
||||
(void)result;
|
||||
}
|
||||
/* Replace the content from `from` to `to` (inclusive) by whitespaces without changing
|
||||
* line count and keep the remaining indentation spaces. */
|
||||
bool erase_try(Token tok)
|
||||
{
|
||||
if (tok.is_invalid()) {
|
||||
return true;
|
||||
}
|
||||
return erase_try(tok, tok);
|
||||
}
|
||||
void erase(Token tok)
|
||||
{
|
||||
bool result = erase_try(tok);
|
||||
assert(result);
|
||||
(void)result;
|
||||
}
|
||||
/* Replace the content of the scope by whitespaces without changing
|
||||
* line count and keep the remaining indentation spaces. */
|
||||
bool erase_try(Scope scope)
|
||||
{
|
||||
return erase_try(scope.front(), scope.back());
|
||||
}
|
||||
void erase(Scope scope)
|
||||
{
|
||||
bool result = erase_try(scope);
|
||||
assert(result);
|
||||
(void)result;
|
||||
}
|
||||
|
||||
/* If prepend is true, will prepend the new content to the list of modifications.
|
||||
* With this enabled, in case of overlapping mutation, the last one added will be first. */
|
||||
void insert_before(size_t at, const std::string &content, bool prepend = false)
|
||||
{
|
||||
IndexRange range = IndexRange(at, 0);
|
||||
if (prepend) {
|
||||
mutations_.insert(mutations_.begin(), {range, content});
|
||||
}
|
||||
else {
|
||||
mutations_.emplace_back(range, content);
|
||||
}
|
||||
}
|
||||
void insert_before(Token at, const std::string &content, bool prepend = false)
|
||||
{
|
||||
insert_before(at.str_index_start(), content, prepend);
|
||||
}
|
||||
|
||||
void insert_after(size_t at, const std::string &content)
|
||||
{
|
||||
IndexRange range = IndexRange(at + 1, 0);
|
||||
mutations_.emplace_back(range, content);
|
||||
}
|
||||
void insert_after(Token at, const std::string &content)
|
||||
{
|
||||
insert_after(at.str_index_last(), content);
|
||||
}
|
||||
|
||||
void insert_line_number(size_t at, int line, std::string_view filename = "")
|
||||
{
|
||||
std::string str = "\n#line " + std::to_string(line);
|
||||
if (!filename.empty()) {
|
||||
str = str + " \"" + std::string(filename) + "\"";
|
||||
}
|
||||
insert_after(at, str + "\n");
|
||||
}
|
||||
void insert_line_number(Token at, int line, std::string_view filename = "")
|
||||
{
|
||||
insert_line_number(at.str_index_last(), line, filename);
|
||||
}
|
||||
|
||||
/* Insert a preprocessor directive after the given token.
|
||||
* This also insert a line directive to keep correct error reporting. */
|
||||
void insert_directive(Token at, const std::string directive)
|
||||
{
|
||||
insert_after(at, "\n" + directive + "\n");
|
||||
insert_line_number(at, at.line_number(true));
|
||||
size_t line_break = str_.find_last_of("\n", at.str_index_last() + 1);
|
||||
size_t spaces = at.str_index_last() - line_break;
|
||||
insert_after(at, std::string(spaces, ' '));
|
||||
}
|
||||
|
||||
/* Return true if any mutation was applied.
|
||||
* Update lexer string view if needed. */
|
||||
bool apply_mutations(LexerBase &lexer, const bool all_mutation_ordered = false);
|
||||
|
||||
/* Get internal string. Does not apply pending mutation. */
|
||||
const std::string &str()
|
||||
{
|
||||
return str_;
|
||||
}
|
||||
|
||||
/* For testing. */
|
||||
std::string serialize_mutations() const
|
||||
{
|
||||
std::string out;
|
||||
for (const Mutation &mut : mutations_) {
|
||||
out += "Replace ";
|
||||
out += std::to_string(mut.src_range.start);
|
||||
out += " - ";
|
||||
out += std::to_string(mut.src_range.size);
|
||||
out += " \"";
|
||||
out += str_.substr(mut.src_range.start, mut.src_range.size);
|
||||
out += "\" by \"";
|
||||
out += mut.replacement;
|
||||
out += "\"\n";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, const std::vector<int> &v)
|
||||
{
|
||||
if (!v.empty()) {
|
||||
out << '[';
|
||||
for (auto val : v) {
|
||||
out << val << ',';
|
||||
}
|
||||
out << "\b]";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Structure holding an intermediate form of the source code.
|
||||
* It is made for fast traversal and mutation of source code. */
|
||||
template<typename LexerFn, typename ParserFn>
|
||||
struct IntermediateForm : MutableString, Parser<LexerFn, ParserFn> {
|
||||
protected:
|
||||
ErrorHandler &report_error;
|
||||
|
||||
public:
|
||||
IntermediateForm(const std::string_view input, ErrorHandler &report_error)
|
||||
: MutableString(input), report_error(report_error)
|
||||
{
|
||||
parse(report_error);
|
||||
}
|
||||
|
||||
/* Main access operator. Returns the root scope (aka global scope). */
|
||||
Scope operator()() const
|
||||
{
|
||||
if (this->scope_types.empty()) {
|
||||
return Scope(*this);
|
||||
}
|
||||
return Scope(*this, 0);
|
||||
}
|
||||
|
||||
/* Return true if any mutation was applied. */
|
||||
bool only_apply_mutations(const bool all_mutation_ordered = false)
|
||||
{
|
||||
return static_cast<MutableString *>(this)->apply_mutations(*this, all_mutation_ordered);
|
||||
}
|
||||
|
||||
/* Apply pending mutation and parse the resulting string.
|
||||
* Return true if any mutation was applied. */
|
||||
bool apply_mutations(const bool all_mutation_ordered = false)
|
||||
{
|
||||
bool applied = only_apply_mutations(all_mutation_ordered);
|
||||
if (applied) {
|
||||
parse(report_error);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
/* Apply mutations if any and get resulting string. */
|
||||
const std::string &result_get(const bool all_mutation_ordered = false)
|
||||
{
|
||||
only_apply_mutations(all_mutation_ordered);
|
||||
return str_;
|
||||
}
|
||||
|
||||
void parse(ErrorHandler &report_error)
|
||||
{
|
||||
this->lexical_analysis(str_);
|
||||
this->semantic_analysis(report_error);
|
||||
}
|
||||
|
||||
void debug_print()
|
||||
{
|
||||
std::cout << "Input: \n" << str_ << " \nEnd of Input\n" << std::endl;
|
||||
std::cout << "Token Types: \"" << this->token_types_str() << "\"" << std::endl;
|
||||
std::cout << "Token scopes: \"" << this->token_scope << "\"" << std::endl;
|
||||
std::cout << "Scope Types: \"" << this->scope_types_str << "\"" << std::endl;
|
||||
}
|
||||
|
||||
void debug_print_tokens()
|
||||
{
|
||||
for (auto tok : *this) {
|
||||
std::cout << "id:" << int(tok) << " start:" << this->offsets_[int(tok)]
|
||||
<< " end:" << this->offsets_end_[int(tok)] << " type:" << tok.type()
|
||||
<< " scope:" << this->token_scope[int(tok)] << "("
|
||||
<< this->scope_types_str[this->token_scope[int(tok)]] << ")"
|
||||
<< " atom:" << tok.atom() << " str:\"" << tok.str() << "\""
|
||||
<< " followed_by_whitespace:" << tok.followed_by_whitespace() << "\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::gpu::shader::parser
|
||||
219
blender-5.2.0/source/blender/gpu/shader_tool/lexit/identifier.hh
Normal file
219
blender-5.2.0/source/blender/gpu/shader_tool/lexit/identifier.hh
Normal file
@@ -0,0 +1,219 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Clement Foucault
|
||||
*
|
||||
* SPDX-License-Identifier: MIT */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "types.hh"
|
||||
#include "vector.hh"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define INLINE_METHOD __forceinline
|
||||
#else
|
||||
# define INLINE_METHOD inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
namespace lexit {
|
||||
|
||||
static constexpr uint64_t padded_string_masks[8] = {
|
||||
uint64_t(0xFFFFFFFFFFFFFFFF),
|
||||
uint64_t(0x00000000000000FF),
|
||||
uint64_t(0x000000000000FFFF),
|
||||
uint64_t(0x0000000000FFFFFF),
|
||||
uint64_t(0x00000000FFFFFFFF),
|
||||
uint64_t(0x000000FFFFFFFFFF),
|
||||
uint64_t(0x0000FFFFFFFFFFFF),
|
||||
uint64_t(0x00FFFFFFFFFFFFFF),
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy of a small string onto aligned bytes.
|
||||
* This avoids the cost of calling memcmp during comparison.
|
||||
* For performance reason, this needs to be constructed on string whose size falls into
|
||||
* the ((Size-1) * 8, Size * 8] range.
|
||||
* IMPORTANT: The string view given to the constructor *MUST* point to at least Size * 8
|
||||
* addressable byte region regardless of the string size.
|
||||
*/
|
||||
template<int Size> struct PaddedString {
|
||||
uint64_t data[Size];
|
||||
uint32_t size;
|
||||
|
||||
PaddedString() = default;
|
||||
|
||||
/* Caller need to ensure enough bytes are accessible after the end of string. */
|
||||
explicit PaddedString(std::string_view str)
|
||||
{
|
||||
size = str.size();
|
||||
assert(str.size() > 8 * (Size - 1));
|
||||
assert(str.size() <= 8 * Size);
|
||||
|
||||
std::memcpy(data, (const char *)str.data(), sizeof(data));
|
||||
/* Fast way of masking the excess chars. */
|
||||
data[Size - 1] &= padded_string_masks[str.size() & (sizeof(data[0]) - 1)];
|
||||
}
|
||||
|
||||
operator std::string_view() const
|
||||
{
|
||||
return {(const char *)data, size};
|
||||
}
|
||||
};
|
||||
|
||||
struct Keyword {
|
||||
std::string_view str;
|
||||
TokenType type;
|
||||
TokenAtom atom;
|
||||
|
||||
Keyword() = default;
|
||||
|
||||
Keyword(std::string_view str, TokenType type, TokenAtom atom) : str(str), type(type), atom(atom)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct IdentifierMap {
|
||||
struct alignas(8) Identifier {
|
||||
uint16_t next;
|
||||
uint16_t size;
|
||||
uint32_t hash;
|
||||
|
||||
/** WORKAROUND: MSVC does not support trailing null size array. */
|
||||
uint64_t *data() const
|
||||
{
|
||||
return (uint64_t *)(&hash + 1);
|
||||
}
|
||||
|
||||
/* Caller must ensure size matches. */
|
||||
template<int Size> bool operator==(const PaddedString<Size> &str) const
|
||||
{
|
||||
if (size != str.size) {
|
||||
return false;
|
||||
}
|
||||
return std::equal(data(), data() + Size, str.data);
|
||||
}
|
||||
|
||||
bool operator==(std::string_view str) const
|
||||
{
|
||||
return std::string_view{(const char *)data(), size} == str;
|
||||
}
|
||||
};
|
||||
|
||||
lexit::Vector<Identifier> identifier_buffer;
|
||||
|
||||
/* Note: Must be power of two size. */
|
||||
static constexpr uint32_t hash_table_size = 16384;
|
||||
static constexpr uint32_t hash_table_index_mask = (hash_table_size - 1);
|
||||
std::array<uint16_t, hash_table_size> hash_table;
|
||||
|
||||
IdentifierMap()
|
||||
{
|
||||
/* Set invalid values for all the table. */
|
||||
std::memset(hash_table.data(), 0xFFu, sizeof(uint16_t) * hash_table.size());
|
||||
}
|
||||
|
||||
void reserve(int token_count)
|
||||
{
|
||||
identifier_buffer.reserve(token_count);
|
||||
}
|
||||
|
||||
/* Return the maximum value for the currently allocated atoms. */
|
||||
TokenAtom max_atom_value() const
|
||||
{
|
||||
return identifier_buffer.size();
|
||||
}
|
||||
|
||||
static constexpr uint32_t str_hash(std::string_view s)
|
||||
{
|
||||
uint32_t hash = 5381;
|
||||
hash = ((hash << 5) + hash) + s.size();
|
||||
hash = ((hash << 5) + hash) + static_cast<uint8_t>(s[0]);
|
||||
hash = ((hash << 5) + hash) + static_cast<uint8_t>(s[s.size() / 2]);
|
||||
hash = ((hash << 5) + hash) + static_cast<uint8_t>(s.back());
|
||||
return static_cast<uint16_t>(hash);
|
||||
}
|
||||
|
||||
template<typename StringT> INLINE_METHOD TokenAtom lookup_or_add(StringT str)
|
||||
{
|
||||
uint32_t hash = str_hash(str);
|
||||
uint16_t index = hash_table[hash & hash_table_index_mask];
|
||||
|
||||
Identifier *id = nullptr;
|
||||
for (; index != 0xFFFFu; index = id->next) {
|
||||
id = &identifier_buffer[index];
|
||||
if (id->hash == hash && *id == str) [[likely]] {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return add_after(hash, str, id);
|
||||
}
|
||||
|
||||
TokenAtom add_after(uint32_t hash, std::string_view str, Identifier *id = nullptr)
|
||||
{
|
||||
/* Cache miss. Add new. */
|
||||
uint16_t new_index = identifier_buffer.size();
|
||||
if (id) {
|
||||
/* Update previous element in the list. */
|
||||
id->next = new_index;
|
||||
}
|
||||
else {
|
||||
/* Update entry in table. */
|
||||
hash_table[hash & hash_table_index_mask] = new_index;
|
||||
}
|
||||
|
||||
{
|
||||
/* Fast malloc replacement. */
|
||||
int str_as_id_size = ((str.size() + (sizeof(Identifier) - 1)) / sizeof(Identifier));
|
||||
identifier_buffer.reserve(new_index + 1 + str_as_id_size);
|
||||
Identifier &id = *identifier_buffer.end();
|
||||
identifier_buffer.increase_size_by_unchecked(1 + str_as_id_size);
|
||||
/* Construct new identifier. */
|
||||
id.next = 0xFFFFu;
|
||||
id.size = str.size();
|
||||
id.hash = hash;
|
||||
/* Zero the end of the memcpy for the fast comparison. */
|
||||
id.data()[((str.size() - 1) / sizeof(Identifier))] = 0;
|
||||
std::memcpy(id.data(), str.data(), str.size());
|
||||
}
|
||||
return new_index;
|
||||
}
|
||||
|
||||
Keyword make_keyword(std::string_view str, TokenType type)
|
||||
{
|
||||
return Keyword(str, type, lookup_or_add(str));
|
||||
}
|
||||
};
|
||||
|
||||
/* Convert Atom to Keyword types. */
|
||||
struct KeywordTable {
|
||||
/* Indexed by string TokenAtom. */
|
||||
alignas(64) std::array<TokenType, 64> map;
|
||||
|
||||
KeywordTable(const std::vector<Keyword> &vector)
|
||||
{
|
||||
/* We only lookup words, so a word mismatch should be a Noop. */
|
||||
map.fill(Word);
|
||||
for (auto keyword : vector) {
|
||||
/* Check for overflow. */
|
||||
assert(keyword.atom < 128);
|
||||
/* Check default case not being overwritten. */
|
||||
assert(keyword.atom != 0);
|
||||
map[keyword.atom / 2] = keyword.type;
|
||||
}
|
||||
}
|
||||
|
||||
TokenType operator[](TokenAtom atom) const
|
||||
{
|
||||
/* Identifier are always allocated in 2 or more consecutive slots. Which means TokenAtom values
|
||||
* always increment by at least 2. Avoid wasting slots in the map by dividing the atom by 2.
|
||||
* If atom is bigger than the table, revert to 0 atom (invalid) which becomes a Noop by
|
||||
* returning Word. */
|
||||
return map[(atom / 2) * (atom < 128)];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lexit
|
||||
965
blender-5.2.0/source/blender/gpu/shader_tool/lexit/lexit.cc
Normal file
965
blender-5.2.0/source/blender/gpu/shader_tool/lexit/lexit.cc
Normal file
@@ -0,0 +1,965 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Clement Foucault
|
||||
*
|
||||
* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "lexit.hh"
|
||||
#include "identifier.hh"
|
||||
#include "simd.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#ifdef _MSC_VER
|
||||
# include <intrin.h>
|
||||
#endif
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
# define count_bits_i(i) __builtin_popcount(i)
|
||||
#elif defined(_MSC_VER)
|
||||
# define count_bits_i(i) __popcnt(i)
|
||||
#else
|
||||
# include <bitset>
|
||||
# define count_bits_i(i) (std::bitset<8>{i}.count())
|
||||
#endif
|
||||
|
||||
namespace lexit {
|
||||
|
||||
static int builtin_ctzll(uint64_t a)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
unsigned long ctz;
|
||||
_BitScanForward64(&ctz, a);
|
||||
return ctz;
|
||||
#else
|
||||
return __builtin_ctzll(a);
|
||||
#endif
|
||||
} // namespace lexit
|
||||
|
||||
static uint32_t divide_ceil(uint32_t a, uint32_t b)
|
||||
{
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
|
||||
/* Helper function to realloc aligned array keeping elem_count data. */
|
||||
template<typename T>
|
||||
void realloc_aligned_array(AlignedArrayPtr<T> &ptr, size_t elem_count, size_t new_size)
|
||||
{
|
||||
assert(new_size >= elem_count);
|
||||
AlignedArrayPtr<T> new_ptr(new_size);
|
||||
if (ptr.get()) {
|
||||
std::memcpy(new_ptr.get(), ptr.get(), elem_count * sizeof(T));
|
||||
}
|
||||
ptr = std::move(new_ptr);
|
||||
}
|
||||
|
||||
void TokenBuffer::clear()
|
||||
{
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
void TokenBuffer::reserve(const uint32_t count)
|
||||
{
|
||||
if (allocated_size_ >= count + 1) {
|
||||
return;
|
||||
}
|
||||
allocated_size_ = count + 1;
|
||||
realloc_aligned_array(types_, size_ + 1, allocated_size_);
|
||||
realloc_aligned_array(offsets_, size_ + 1, allocated_size_);
|
||||
realloc_aligned_array(offsets_end_, size_ + 1, allocated_size_);
|
||||
realloc_aligned_array(atoms_, size_ + 1, allocated_size_);
|
||||
realloc_aligned_array(lengths_, size_ + 1, allocated_size_);
|
||||
}
|
||||
|
||||
#if defined(USE_NEON) || defined(USE_SSE4_2)
|
||||
|
||||
/* Shuffle table used for stream compaction.
|
||||
* For a given 8bit pattern (where each 1 bit represents an element to keep)
|
||||
* encode the index of the source register for each of the 8 destination registers.
|
||||
* Every 0 bit (representing a discarded element) will be sourced from the 0th element.
|
||||
* This is to be used with table. */
|
||||
alignas(16) static const uint8_t shuffle_table_8[256][8] = {
|
||||
/* [0b00000000] = */ {0, 0, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00000001] = */ {0, 0, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00000010] = */ {1, 0, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00000011] = */ {0, 1, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00000100] = */ {2, 0, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00000101] = */ {0, 2, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00000110] = */ {1, 2, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00000111] = */ {0, 1, 2, 0, 0, 0, 0, 0},
|
||||
/* [0b00001000] = */ {3, 0, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00001001] = */ {0, 3, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00001010] = */ {1, 3, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00001011] = */ {0, 1, 3, 0, 0, 0, 0, 0},
|
||||
/* [0b00001100] = */ {2, 3, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00001101] = */ {0, 2, 3, 0, 0, 0, 0, 0},
|
||||
/* [0b00001110] = */ {1, 2, 3, 0, 0, 0, 0, 0},
|
||||
/* [0b00001111] = */ {0, 1, 2, 3, 0, 0, 0, 0},
|
||||
/* [0b00010000] = */ {4, 0, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00010001] = */ {0, 4, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00010010] = */ {1, 4, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00010011] = */ {0, 1, 4, 0, 0, 0, 0, 0},
|
||||
/* [0b00010100] = */ {2, 4, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00010101] = */ {0, 2, 4, 0, 0, 0, 0, 0},
|
||||
/* [0b00010110] = */ {1, 2, 4, 0, 0, 0, 0, 0},
|
||||
/* [0b00010111] = */ {0, 1, 2, 4, 0, 0, 0, 0},
|
||||
/* [0b00011000] = */ {3, 4, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00011001] = */ {0, 3, 4, 0, 0, 0, 0, 0},
|
||||
/* [0b00011010] = */ {1, 3, 4, 0, 0, 0, 0, 0},
|
||||
/* [0b00011011] = */ {0, 1, 3, 4, 0, 0, 0, 0},
|
||||
/* [0b00011100] = */ {2, 3, 4, 0, 0, 0, 0, 0},
|
||||
/* [0b00011101] = */ {0, 2, 3, 4, 0, 0, 0, 0},
|
||||
/* [0b00011110] = */ {1, 2, 3, 4, 0, 0, 0, 0},
|
||||
/* [0b00011111] = */ {0, 1, 2, 3, 4, 0, 0, 0},
|
||||
/* [0b00100000] = */ {5, 0, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00100001] = */ {0, 5, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00100010] = */ {1, 5, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00100011] = */ {0, 1, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00100100] = */ {2, 5, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00100101] = */ {0, 2, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00100110] = */ {1, 2, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00100111] = */ {0, 1, 2, 5, 0, 0, 0, 0},
|
||||
/* [0b00101000] = */ {3, 5, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00101001] = */ {0, 3, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00101010] = */ {1, 3, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00101011] = */ {0, 1, 3, 5, 0, 0, 0, 0},
|
||||
/* [0b00101100] = */ {2, 3, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00101101] = */ {0, 2, 3, 5, 0, 0, 0, 0},
|
||||
/* [0b00101110] = */ {1, 2, 3, 5, 0, 0, 0, 0},
|
||||
/* [0b00101111] = */ {0, 1, 2, 3, 5, 0, 0, 0},
|
||||
/* [0b00110000] = */ {4, 5, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b00110001] = */ {0, 4, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00110010] = */ {1, 4, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00110011] = */ {0, 1, 4, 5, 0, 0, 0, 0},
|
||||
/* [0b00110100] = */ {2, 4, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00110101] = */ {0, 2, 4, 5, 0, 0, 0, 0},
|
||||
/* [0b00110110] = */ {1, 2, 4, 5, 0, 0, 0, 0},
|
||||
/* [0b00110111] = */ {0, 1, 2, 4, 5, 0, 0, 0},
|
||||
/* [0b00111000] = */ {3, 4, 5, 0, 0, 0, 0, 0},
|
||||
/* [0b00111001] = */ {0, 3, 4, 5, 0, 0, 0, 0},
|
||||
/* [0b00111010] = */ {1, 3, 4, 5, 0, 0, 0, 0},
|
||||
/* [0b00111011] = */ {0, 1, 3, 4, 5, 0, 0, 0},
|
||||
/* [0b00111100] = */ {2, 3, 4, 5, 0, 0, 0, 0},
|
||||
/* [0b00111101] = */ {0, 2, 3, 4, 5, 0, 0, 0},
|
||||
/* [0b00111110] = */ {1, 2, 3, 4, 5, 0, 0, 0},
|
||||
/* [0b00111111] = */ {0, 1, 2, 3, 4, 5, 0, 0},
|
||||
/* [0b01000000] = */ {6, 0, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b01000001] = */ {0, 6, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b01000010] = */ {1, 6, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b01000011] = */ {0, 1, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01000100] = */ {2, 6, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b01000101] = */ {0, 2, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01000110] = */ {1, 2, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01000111] = */ {0, 1, 2, 6, 0, 0, 0, 0},
|
||||
/* [0b01001000] = */ {3, 6, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b01001001] = */ {0, 3, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01001010] = */ {1, 3, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01001011] = */ {0, 1, 3, 6, 0, 0, 0, 0},
|
||||
/* [0b01001100] = */ {2, 3, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01001101] = */ {0, 2, 3, 6, 0, 0, 0, 0},
|
||||
/* [0b01001110] = */ {1, 2, 3, 6, 0, 0, 0, 0},
|
||||
/* [0b01001111] = */ {0, 1, 2, 3, 6, 0, 0, 0},
|
||||
/* [0b01010000] = */ {4, 6, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b01010001] = */ {0, 4, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01010010] = */ {1, 4, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01010011] = */ {0, 1, 4, 6, 0, 0, 0, 0},
|
||||
/* [0b01010100] = */ {2, 4, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01010101] = */ {0, 2, 4, 6, 0, 0, 0, 0},
|
||||
/* [0b01010110] = */ {1, 2, 4, 6, 0, 0, 0, 0},
|
||||
/* [0b01010111] = */ {0, 1, 2, 4, 6, 0, 0, 0},
|
||||
/* [0b01011000] = */ {3, 4, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01011001] = */ {0, 3, 4, 6, 0, 0, 0, 0},
|
||||
/* [0b01011010] = */ {1, 3, 4, 6, 0, 0, 0, 0},
|
||||
/* [0b01011011] = */ {0, 1, 3, 4, 6, 0, 0, 0},
|
||||
/* [0b01011100] = */ {2, 3, 4, 6, 0, 0, 0, 0},
|
||||
/* [0b01011101] = */ {0, 2, 3, 4, 6, 0, 0, 0},
|
||||
/* [0b01011110] = */ {1, 2, 3, 4, 6, 0, 0, 0},
|
||||
/* [0b01011111] = */ {0, 1, 2, 3, 4, 6, 0, 0},
|
||||
/* [0b01100000] = */ {5, 6, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b01100001] = */ {0, 5, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01100010] = */ {1, 5, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01100011] = */ {0, 1, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01100100] = */ {2, 5, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01100101] = */ {0, 2, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01100110] = */ {1, 2, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01100111] = */ {0, 1, 2, 5, 6, 0, 0, 0},
|
||||
/* [0b01101000] = */ {3, 5, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01101001] = */ {0, 3, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01101010] = */ {1, 3, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01101011] = */ {0, 1, 3, 5, 6, 0, 0, 0},
|
||||
/* [0b01101100] = */ {2, 3, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01101101] = */ {0, 2, 3, 5, 6, 0, 0, 0},
|
||||
/* [0b01101110] = */ {1, 2, 3, 5, 6, 0, 0, 0},
|
||||
/* [0b01101111] = */ {0, 1, 2, 3, 5, 6, 0, 0},
|
||||
/* [0b01110000] = */ {4, 5, 6, 0, 0, 0, 0, 0},
|
||||
/* [0b01110001] = */ {0, 4, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01110010] = */ {1, 4, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01110011] = */ {0, 1, 4, 5, 6, 0, 0, 0},
|
||||
/* [0b01110100] = */ {2, 4, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01110101] = */ {0, 2, 4, 5, 6, 0, 0, 0},
|
||||
/* [0b01110110] = */ {1, 2, 4, 5, 6, 0, 0, 0},
|
||||
/* [0b01110111] = */ {0, 1, 2, 4, 5, 6, 0, 0},
|
||||
/* [0b01111000] = */ {3, 4, 5, 6, 0, 0, 0, 0},
|
||||
/* [0b01111001] = */ {0, 3, 4, 5, 6, 0, 0, 0},
|
||||
/* [0b01111010] = */ {1, 3, 4, 5, 6, 0, 0, 0},
|
||||
/* [0b01111011] = */ {0, 1, 3, 4, 5, 6, 0, 0},
|
||||
/* [0b01111100] = */ {2, 3, 4, 5, 6, 0, 0, 0},
|
||||
/* [0b01111101] = */ {0, 2, 3, 4, 5, 6, 0, 0},
|
||||
/* [0b01111110] = */ {1, 2, 3, 4, 5, 6, 0, 0},
|
||||
/* [0b01111111] = */ {0, 1, 2, 3, 4, 5, 6, 0},
|
||||
/* [0b10000000] = */ {7, 0, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b10000001] = */ {0, 7, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b10000010] = */ {1, 7, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b10000011] = */ {0, 1, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10000100] = */ {2, 7, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b10000101] = */ {0, 2, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10000110] = */ {1, 2, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10000111] = */ {0, 1, 2, 7, 0, 0, 0, 0},
|
||||
/* [0b10001000] = */ {3, 7, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b10001001] = */ {0, 3, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10001010] = */ {1, 3, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10001011] = */ {0, 1, 3, 7, 0, 0, 0, 0},
|
||||
/* [0b10001100] = */ {2, 3, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10001101] = */ {0, 2, 3, 7, 0, 0, 0, 0},
|
||||
/* [0b10001110] = */ {1, 2, 3, 7, 0, 0, 0, 0},
|
||||
/* [0b10001111] = */ {0, 1, 2, 3, 7, 0, 0, 0},
|
||||
/* [0b10010000] = */ {4, 7, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b10010001] = */ {0, 4, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10010010] = */ {1, 4, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10010011] = */ {0, 1, 4, 7, 0, 0, 0, 0},
|
||||
/* [0b10010100] = */ {2, 4, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10010101] = */ {0, 2, 4, 7, 0, 0, 0, 0},
|
||||
/* [0b10010110] = */ {1, 2, 4, 7, 0, 0, 0, 0},
|
||||
/* [0b10010111] = */ {0, 1, 2, 4, 7, 0, 0, 0},
|
||||
/* [0b10011000] = */ {3, 4, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10011001] = */ {0, 3, 4, 7, 0, 0, 0, 0},
|
||||
/* [0b10011010] = */ {1, 3, 4, 7, 0, 0, 0, 0},
|
||||
/* [0b10011011] = */ {0, 1, 3, 4, 7, 0, 0, 0},
|
||||
/* [0b10011100] = */ {2, 3, 4, 7, 0, 0, 0, 0},
|
||||
/* [0b10011101] = */ {0, 2, 3, 4, 7, 0, 0, 0},
|
||||
/* [0b10011110] = */ {1, 2, 3, 4, 7, 0, 0, 0},
|
||||
/* [0b10011111] = */ {0, 1, 2, 3, 4, 7, 0, 0},
|
||||
/* [0b10100000] = */ {5, 7, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b10100001] = */ {0, 5, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10100010] = */ {1, 5, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10100011] = */ {0, 1, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10100100] = */ {2, 5, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10100101] = */ {0, 2, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10100110] = */ {1, 2, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10100111] = */ {0, 1, 2, 5, 7, 0, 0, 0},
|
||||
/* [0b10101000] = */ {3, 5, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10101001] = */ {0, 3, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10101010] = */ {1, 3, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10101011] = */ {0, 1, 3, 5, 7, 0, 0, 0},
|
||||
/* [0b10101100] = */ {2, 3, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10101101] = */ {0, 2, 3, 5, 7, 0, 0, 0},
|
||||
/* [0b10101110] = */ {1, 2, 3, 5, 7, 0, 0, 0},
|
||||
/* [0b10101111] = */ {0, 1, 2, 3, 5, 7, 0, 0},
|
||||
/* [0b10110000] = */ {4, 5, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b10110001] = */ {0, 4, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10110010] = */ {1, 4, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10110011] = */ {0, 1, 4, 5, 7, 0, 0, 0},
|
||||
/* [0b10110100] = */ {2, 4, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10110101] = */ {0, 2, 4, 5, 7, 0, 0, 0},
|
||||
/* [0b10110110] = */ {1, 2, 4, 5, 7, 0, 0, 0},
|
||||
/* [0b10110111] = */ {0, 1, 2, 4, 5, 7, 0, 0},
|
||||
/* [0b10111000] = */ {3, 4, 5, 7, 0, 0, 0, 0},
|
||||
/* [0b10111001] = */ {0, 3, 4, 5, 7, 0, 0, 0},
|
||||
/* [0b10111010] = */ {1, 3, 4, 5, 7, 0, 0, 0},
|
||||
/* [0b10111011] = */ {0, 1, 3, 4, 5, 7, 0, 0},
|
||||
/* [0b10111100] = */ {2, 3, 4, 5, 7, 0, 0, 0},
|
||||
/* [0b10111101] = */ {0, 2, 3, 4, 5, 7, 0, 0},
|
||||
/* [0b10111110] = */ {1, 2, 3, 4, 5, 7, 0, 0},
|
||||
/* [0b10111111] = */ {0, 1, 2, 3, 4, 5, 7, 0},
|
||||
/* [0b11000000] = */ {6, 7, 0, 0, 0, 0, 0, 0},
|
||||
/* [0b11000001] = */ {0, 6, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b11000010] = */ {1, 6, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b11000011] = */ {0, 1, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11000100] = */ {2, 6, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b11000101] = */ {0, 2, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11000110] = */ {1, 2, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11000111] = */ {0, 1, 2, 6, 7, 0, 0, 0},
|
||||
/* [0b11001000] = */ {3, 6, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b11001001] = */ {0, 3, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11001010] = */ {1, 3, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11001011] = */ {0, 1, 3, 6, 7, 0, 0, 0},
|
||||
/* [0b11001100] = */ {2, 3, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11001101] = */ {0, 2, 3, 6, 7, 0, 0, 0},
|
||||
/* [0b11001110] = */ {1, 2, 3, 6, 7, 0, 0, 0},
|
||||
/* [0b11001111] = */ {0, 1, 2, 3, 6, 7, 0, 0},
|
||||
/* [0b11010000] = */ {4, 6, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b11010001] = */ {0, 4, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11010010] = */ {1, 4, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11010011] = */ {0, 1, 4, 6, 7, 0, 0, 0},
|
||||
/* [0b11010100] = */ {2, 4, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11010101] = */ {0, 2, 4, 6, 7, 0, 0, 0},
|
||||
/* [0b11010110] = */ {1, 2, 4, 6, 7, 0, 0, 0},
|
||||
/* [0b11010111] = */ {0, 1, 2, 4, 6, 7, 0, 0},
|
||||
/* [0b11011000] = */ {3, 4, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11011001] = */ {0, 3, 4, 6, 7, 0, 0, 0},
|
||||
/* [0b11011010] = */ {1, 3, 4, 6, 7, 0, 0, 0},
|
||||
/* [0b11011011] = */ {0, 1, 3, 4, 6, 7, 0, 0},
|
||||
/* [0b11011100] = */ {2, 3, 4, 6, 7, 0, 0, 0},
|
||||
/* [0b11011101] = */ {0, 2, 3, 4, 6, 7, 0, 0},
|
||||
/* [0b11011110] = */ {1, 2, 3, 4, 6, 7, 0, 0},
|
||||
/* [0b11011111] = */ {0, 1, 2, 3, 4, 6, 7, 0},
|
||||
/* [0b11100000] = */ {5, 6, 7, 0, 0, 0, 0, 0},
|
||||
/* [0b11100001] = */ {0, 5, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11100010] = */ {1, 5, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11100011] = */ {0, 1, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11100100] = */ {2, 5, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11100101] = */ {0, 2, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11100110] = */ {1, 2, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11100111] = */ {0, 1, 2, 5, 6, 7, 0, 0},
|
||||
/* [0b11101000] = */ {3, 5, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11101001] = */ {0, 3, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11101010] = */ {1, 3, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11101011] = */ {0, 1, 3, 5, 6, 7, 0, 0},
|
||||
/* [0b11101100] = */ {2, 3, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11101101] = */ {0, 2, 3, 5, 6, 7, 0, 0},
|
||||
/* [0b11101110] = */ {1, 2, 3, 5, 6, 7, 0, 0},
|
||||
/* [0b11101111] = */ {0, 1, 2, 3, 5, 6, 7, 0},
|
||||
/* [0b11110000] = */ {4, 5, 6, 7, 0, 0, 0, 0},
|
||||
/* [0b11110001] = */ {0, 4, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11110010] = */ {1, 4, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11110011] = */ {0, 1, 4, 5, 6, 7, 0, 0},
|
||||
/* [0b11110100] = */ {2, 4, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11110101] = */ {0, 2, 4, 5, 6, 7, 0, 0},
|
||||
/* [0b11110110] = */ {1, 2, 4, 5, 6, 7, 0, 0},
|
||||
/* [0b11110111] = */ {0, 1, 2, 4, 5, 6, 7, 0},
|
||||
/* [0b11111000] = */ {3, 4, 5, 6, 7, 0, 0, 0},
|
||||
/* [0b11111001] = */ {0, 3, 4, 5, 6, 7, 0, 0},
|
||||
/* [0b11111010] = */ {1, 3, 4, 5, 6, 7, 0, 0},
|
||||
/* [0b11111011] = */ {0, 1, 3, 4, 5, 6, 7, 0},
|
||||
/* [0b11111100] = */ {2, 3, 4, 5, 6, 7, 0, 0},
|
||||
/* [0b11111101] = */ {0, 2, 3, 4, 5, 6, 7, 0},
|
||||
/* [0b11111110] = */ {1, 2, 3, 4, 5, 6, 7, 0},
|
||||
/* [0b11111111] = */ {0, 1, 2, 3, 4, 5, 6, 7},
|
||||
};
|
||||
|
||||
/* Popcount for a uint8_t. */
|
||||
alignas(16) static const uint8_t mask_popcount[256] = {
|
||||
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3,
|
||||
4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4,
|
||||
4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4,
|
||||
5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,
|
||||
4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2,
|
||||
3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5,
|
||||
5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4,
|
||||
5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
|
||||
|
||||
template<int Size> struct ShuffleIndicesResult {
|
||||
simd::u8_base<Size> indices;
|
||||
int popcount;
|
||||
};
|
||||
|
||||
inline ShuffleIndicesResult<1> shuffle_indices_from_emit_mask(uint16_t emit_mask)
|
||||
{
|
||||
const uint8_t emit_mask_lo = emit_mask & 0xFFu;
|
||||
const uint8_t emit_mask_hi = emit_mask >> 8;
|
||||
const uint8_t mask_popcount_lo = mask_popcount[emit_mask_lo];
|
||||
const uint8_t mask_popcount_hi = mask_popcount[emit_mask_hi];
|
||||
/* Lookup the shuffle vector in 2 halves. */
|
||||
uint64_t v0 = *(uint64_t *)shuffle_table_8[emit_mask_lo];
|
||||
uint64_t v1 = *(uint64_t *)shuffle_table_8[emit_mask_hi] | uint64_t(0x0808080808080808);
|
||||
/* Combine the two halves into one contiguous index array.
|
||||
* We don't care about values after the last valid index. */
|
||||
alignas(16) uint8_t combined[16];
|
||||
*(uint64_t *)combined = v0;
|
||||
*(uint64_t *)(combined + mask_popcount_lo) = v1;
|
||||
|
||||
return {simd::u8x16::load((const uint8_t *)&combined), mask_popcount_lo + mask_popcount_hi};
|
||||
}
|
||||
|
||||
inline ShuffleIndicesResult<4> shuffle_indices_from_emit_mask(uint64_t emit_mask)
|
||||
{
|
||||
const uint8_t emit_mask_0 = (emit_mask >> 0) & 0xFFu;
|
||||
const uint8_t emit_mask_1 = (emit_mask >> 8) & 0xFFu;
|
||||
const uint8_t emit_mask_2 = (emit_mask >> 16) & 0xFFu;
|
||||
const uint8_t emit_mask_3 = (emit_mask >> 24) & 0xFFu;
|
||||
const uint8_t emit_mask_4 = (emit_mask >> 32) & 0xFFu;
|
||||
const uint8_t emit_mask_5 = (emit_mask >> 40) & 0xFFu;
|
||||
const uint8_t emit_mask_6 = (emit_mask >> 48) & 0xFFu;
|
||||
const uint8_t emit_mask_7 = (emit_mask >> 56) & 0xFFu;
|
||||
const uint8_t mask_popcount_0 = mask_popcount[emit_mask_0];
|
||||
const uint8_t mask_popcount_1 = mask_popcount[emit_mask_1];
|
||||
const uint8_t mask_popcount_2 = mask_popcount[emit_mask_2];
|
||||
const uint8_t mask_popcount_3 = mask_popcount[emit_mask_3];
|
||||
const uint8_t mask_popcount_4 = mask_popcount[emit_mask_4];
|
||||
const uint8_t mask_popcount_5 = mask_popcount[emit_mask_5];
|
||||
const uint8_t mask_popcount_6 = mask_popcount[emit_mask_6];
|
||||
const uint8_t mask_popcount_7 = mask_popcount[emit_mask_7];
|
||||
/* Lookup the shuffle vector in multiple part. */
|
||||
uint64_t v0 = *(uint64_t *)shuffle_table_8[emit_mask_0];
|
||||
uint64_t v1 = *(uint64_t *)shuffle_table_8[emit_mask_1] | uint64_t(0x0808080808080808);
|
||||
uint64_t v2 = *(uint64_t *)shuffle_table_8[emit_mask_2] | uint64_t(0x1010101010101010);
|
||||
uint64_t v3 = *(uint64_t *)shuffle_table_8[emit_mask_3] | uint64_t(0x1818181818181818);
|
||||
uint64_t v4 = *(uint64_t *)shuffle_table_8[emit_mask_4] | uint64_t(0x2020202020202020);
|
||||
uint64_t v5 = *(uint64_t *)shuffle_table_8[emit_mask_5] | uint64_t(0x2828282828282828);
|
||||
uint64_t v6 = *(uint64_t *)shuffle_table_8[emit_mask_6] | uint64_t(0x3030303030303030);
|
||||
uint64_t v7 = *(uint64_t *)shuffle_table_8[emit_mask_7] | uint64_t(0x3838383838383838);
|
||||
/* Combine the parts into one contiguous index array.
|
||||
* We don't care about values after the last valid index. */
|
||||
alignas(64) uint8_t combined[64];
|
||||
int popcount = 0;
|
||||
*(uint64_t *)combined = v0, popcount += mask_popcount_0;
|
||||
*(uint64_t *)(combined + popcount) = v1, popcount += mask_popcount_1;
|
||||
*(uint64_t *)(combined + popcount) = v2, popcount += mask_popcount_2;
|
||||
*(uint64_t *)(combined + popcount) = v3, popcount += mask_popcount_3;
|
||||
*(uint64_t *)(combined + popcount) = v4, popcount += mask_popcount_4;
|
||||
*(uint64_t *)(combined + popcount) = v5, popcount += mask_popcount_5;
|
||||
*(uint64_t *)(combined + popcount) = v6, popcount += mask_popcount_6;
|
||||
*(uint64_t *)(combined + popcount) = v7, popcount += mask_popcount_7;
|
||||
|
||||
return {simd::u8x64::load((const uint8_t *)&combined), popcount};
|
||||
}
|
||||
#endif
|
||||
|
||||
inline TokenType select(char char_value, char char_class, bool cond)
|
||||
{
|
||||
return TokenType((cond) ? char_class : char_value);
|
||||
}
|
||||
|
||||
inline void TokenBuffer::tokenize_scalar(uint32_t &__restrict offset,
|
||||
uint32_t &__restrict cursor_begin,
|
||||
uint32_t &__restrict cursor_end,
|
||||
CharClass &__restrict prev_char_class,
|
||||
bool &__restrict prev_whitespace,
|
||||
uint32_t end,
|
||||
const CharClass char_class_table[128])
|
||||
{
|
||||
for (; offset < end; offset += 1) {
|
||||
const char c = str_[offset];
|
||||
const CharClass curr_char_class = char_class_table[c];
|
||||
const TokenType curr_tok_type = select(
|
||||
c, char(curr_char_class), curr_char_class > CharClass::ClassToTypeThreshold);
|
||||
/* It is faster to overwrite the previous value with the same value
|
||||
* as having a condition. */
|
||||
types_[cursor_begin] = curr_tok_type;
|
||||
offsets_[cursor_begin] = offset;
|
||||
offsets_end_[cursor_end] = offset;
|
||||
/**
|
||||
* Split if no class in common.
|
||||
* Example:
|
||||
* str : i n t i 2 = 0 . 0 f ; i 2 + + ;
|
||||
* emit : 1 0 0 1 1 0 1 1 1 1 1 1 0 1 1 1 0 1 0 1
|
||||
*/
|
||||
const bool emit = (uint8_t(curr_char_class) & uint8_t(prev_char_class) &
|
||||
uint8_t(CharClass::CanMerge)) == 0;
|
||||
prev_char_class = curr_char_class;
|
||||
|
||||
/**
|
||||
* These are the emit mask we want to achieve:
|
||||
* str : i n t i 2 = 0 . 0 f ; i 2 + + ;
|
||||
* emit start : 1 0 0 0 1 0 0 1 0 1 1 1 0 1 0 1 0 1 0 1
|
||||
* emit end : 0 0 0 1 0 0 1 0 1 0 1 1 0 1 1 0 0 1 0 1
|
||||
*/
|
||||
/* : 1 1 1 0 1 1 0 1 0 1 1 1 1 1 0 1 1 1 1 1 */
|
||||
const bool curr_ws = (curr_char_class == CharClass::WhiteSpace);
|
||||
/* : 0 0 0 1 0 0 1 0 1 0 0 0 0 0 1 0 0 0 0 0 */
|
||||
const bool emit_ws = emit && curr_ws;
|
||||
/* : 1 0 0 0 1 0 0 1 0 1 1 1 0 1 0 1 0 1 0 1 */
|
||||
const bool emit_start = emit && !curr_ws;
|
||||
/* : 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 1 0 1 */
|
||||
const bool follow_non_ws = emit_start && !prev_whitespace;
|
||||
/* : 0 0 0 1 0 0 1 0 1 0 1 1 0 1 1 0 0 1 0 1 */
|
||||
const bool emit_end = emit_ws || follow_non_ws;
|
||||
|
||||
prev_whitespace = curr_ws;
|
||||
|
||||
#ifdef LEXIT_DEBUG
|
||||
if (emit_start) {
|
||||
int start = offsets_[cursor_begin - 1];
|
||||
int end = offsets_[cursor_begin];
|
||||
token_str_with_whitespace_debug_.emplace_back(str_.data() + start, end - start);
|
||||
}
|
||||
if (emit_end) {
|
||||
int start = offsets_[cursor_end];
|
||||
int end = offsets_end_[cursor_end];
|
||||
token_str_debug_.emplace_back(str_.data() + start, end - start);
|
||||
}
|
||||
#endif
|
||||
|
||||
cursor_begin += emit_start;
|
||||
cursor_end += emit_end;
|
||||
}
|
||||
}
|
||||
|
||||
void TokenBuffer::tokenize(const CharClass char_class_table[128])
|
||||
{
|
||||
/* Ensure enough space for the worse scenario, which is one token per character.
|
||||
* This is done in order to avoid allocation and check inside the hot loop. */
|
||||
reserve(str_.size());
|
||||
|
||||
if (str_.size() == 0) {
|
||||
size_ = 0;
|
||||
types_[0] = EndOfFile;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The goal of this function is to build the offset and type buffers describing the tokens.
|
||||
* The type of the token is decided by the character class of its first character. This is a
|
||||
* simple lookup from the `char_class_table` combined with a `select`. It is then merged with the
|
||||
* preceding character if the classes are compatible (see `CharClass::CanMerge`).
|
||||
*
|
||||
* The offsets define the boundaries of the tokens.
|
||||
* If whitespaces are treated as tokens, one offset is emitted for the beginning of each token.
|
||||
*
|
||||
* str: i n t a = 0 ; EndOfFile
|
||||
* emit: 1 0 0 0 1 1 1 1 1 1
|
||||
* offsets: 0 4 6 7 8 9
|
||||
*
|
||||
* If whitespaces are merged with preceding tokens, one offset is emitted for the begining of
|
||||
* each token *and* at either the start of the next token or the start of the next whitespace.
|
||||
*
|
||||
* str: i n t a = 0 ; EndOfFile
|
||||
* emit start: 1 0 0 0 1 1 1 1 0 1
|
||||
* emit end: 1 0 0 1 0 1 1 1 1 1
|
||||
* offsets start: 0 4 5 6 7 9
|
||||
* offsets end: 3 5 6 7 8 9
|
||||
*/
|
||||
|
||||
CharClass prev_value = CharClass::None;
|
||||
bool prev_whitespace;
|
||||
{
|
||||
/* First iteration needs to always emit start of the token. */
|
||||
const CharClass curr = char_class_table[str_[0]];
|
||||
types_[0] = select(str_[0], char(curr), curr > CharClass::ClassToTypeThreshold);
|
||||
offsets_[0] = 0;
|
||||
offsets_end_[0] = 0;
|
||||
prev_value = curr;
|
||||
prev_whitespace = curr == CharClass::WhiteSpace;
|
||||
}
|
||||
|
||||
uint32_t offset = 1, cursor = 1, cursor_end = prev_whitespace ? 1 : 0;
|
||||
|
||||
int stride = 64;
|
||||
|
||||
/* Process until alignment to SIMD size is met. */
|
||||
size_t index_at_align = ((stride - (uintptr_t(str_.data() + 1) & (stride - 1))) & (stride - 1)) +
|
||||
1;
|
||||
tokenize_scalar(offset,
|
||||
cursor,
|
||||
cursor_end,
|
||||
prev_value,
|
||||
prev_whitespace,
|
||||
str_.size() > index_at_align ? index_at_align : str_.size(),
|
||||
char_class_table);
|
||||
|
||||
#if defined(USE_NEON) || defined(USE_SSE4_2)
|
||||
using namespace lexit::simd;
|
||||
const uint8_t *str = (const uint8_t *)str_.data();
|
||||
const u8x128_table char_to_class = u8x128_table::load((const uint8_t *)char_class_table);
|
||||
|
||||
for (; offset + stride <= str_.size(); offset += stride) {
|
||||
# ifdef LEXIT_DEBUG
|
||||
const std::string_view char_simd{str_.data() + offset, size_t(stride)};
|
||||
# endif
|
||||
const u8x64 c = u8x64::load(str + offset);
|
||||
const u8x64 curr_char_class = char_to_class[c];
|
||||
/* Shift and add the last iteration end token at the start of the vector. */
|
||||
const u8x64 prev_char_class = shift_lanes_right<1>(curr_char_class, uint8_t(prev_value));
|
||||
|
||||
const u8x64 to_type_threshold{uint8_t(CharClass::ClassToTypeThreshold)};
|
||||
const u8x64 can_merge{uint8_t(CharClass::CanMerge)};
|
||||
const u8x64 curr_tok_type = select(c, curr_char_class, curr_char_class > to_type_threshold);
|
||||
const u8x64 emit = is_zero(curr_char_class & prev_char_class & can_merge);
|
||||
/* Store for next iteration. */
|
||||
prev_value = CharClass(curr_char_class.last());
|
||||
/* Start and end of token. See scalar version for documentation. */
|
||||
const u8x64 curr_ws = (curr_char_class == uint8_t(CharClass::WhiteSpace));
|
||||
const u8x64 curr_non_ws = ~curr_ws;
|
||||
const u8x64 emit_ws = emit & curr_ws;
|
||||
const u8x64 emit_start = emit & curr_non_ws;
|
||||
const u8x64 prev_non_ws = shift_lanes_right<1>(curr_non_ws, prev_whitespace ? 0x0 : 0xFF);
|
||||
const u8x64 follow_non_ws = emit_start & prev_non_ws;
|
||||
const u8x64 emit_end = emit_ws | follow_non_ws;
|
||||
/* Store for next iteration. */
|
||||
prev_whitespace = !curr_non_ws.last();
|
||||
uint64_t emit_end_mask = movemask(emit_end);
|
||||
uint64_t emit_start_mask = movemask(emit_start);
|
||||
|
||||
/* Stream compaction of data based on the emit mask (0xFF == emit, 0x00 == skip).
|
||||
* Stores `data` compacted inside `data_out` starting from `data_out + cursor` and advance
|
||||
* `cursor` by the number of element compacted. */
|
||||
{
|
||||
auto [shuffle, popcount] = shuffle_indices_from_emit_mask(emit_start_mask);
|
||||
/* Move data to destination elements (compaction). */
|
||||
const u8x64 data_packed = u8x64_table(curr_tok_type)[shuffle];
|
||||
/* Write 16 types in the stream. */
|
||||
data_packed.store_unaligned((uint8_t *)&types_[cursor]);
|
||||
/* The offsets are contained inside the 8 bit shuffle vector.
|
||||
* We need to promote it to 32 bit before adding the base offset. */
|
||||
(u32x16(shuffle.lane(0)) + offset).store_unaligned(&offsets_[cursor + 0]);
|
||||
(u32x16(shuffle.lane(1)) + offset).store_unaligned(&offsets_[cursor + 16]);
|
||||
(u32x16(shuffle.lane(2)) + offset).store_unaligned(&offsets_[cursor + 32]);
|
||||
(u32x16(shuffle.lane(3)) + offset).store_unaligned(&offsets_[cursor + 48]);
|
||||
# ifdef LEXIT_DEBUG
|
||||
for (int i = cursor; i < cursor + popcount; i++) {
|
||||
int start = offsets_[i - 1];
|
||||
int end = offsets_[i];
|
||||
token_str_with_whitespace_debug_.emplace_back(str_.data() + start, end - start);
|
||||
}
|
||||
# endif
|
||||
cursor += popcount;
|
||||
}
|
||||
{
|
||||
auto [shuffle, popcount] = shuffle_indices_from_emit_mask(emit_end_mask);
|
||||
/* The offsets are contained inside the 8 bit shuffle vector.
|
||||
* We need to promote it to 32 bit before adding the base offset. */
|
||||
(u32x16(shuffle.lane(0)) + offset).store_unaligned(&offsets_end_[cursor_end + 0]);
|
||||
(u32x16(shuffle.lane(1)) + offset).store_unaligned(&offsets_end_[cursor_end + 16]);
|
||||
(u32x16(shuffle.lane(2)) + offset).store_unaligned(&offsets_end_[cursor_end + 32]);
|
||||
(u32x16(shuffle.lane(3)) + offset).store_unaligned(&offsets_end_[cursor_end + 48]);
|
||||
# ifdef LEXIT_DEBUG
|
||||
for (int i = cursor_end; i < cursor_end + popcount; i++) {
|
||||
int start = offsets_[i];
|
||||
int end = offsets_end_[i];
|
||||
token_str_debug_.emplace_back(str_.data() + start, end - start);
|
||||
}
|
||||
# endif
|
||||
cursor_end += popcount;
|
||||
}
|
||||
assert(cursor_end == cursor || cursor_end + 1 == cursor);
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(cursor_end == cursor || cursor_end + 1 == cursor);
|
||||
|
||||
/* Finish tail using scalar loop. */
|
||||
tokenize_scalar(
|
||||
offset, cursor, cursor_end, prev_value, prev_whitespace, str_.size(), char_class_table);
|
||||
|
||||
assert(cursor_end == cursor || cursor_end + 1 == cursor);
|
||||
|
||||
/* Set end of last token. */
|
||||
offsets_[cursor] = str_.size();
|
||||
offsets_end_[cursor_end] = str_.size();
|
||||
/* Set end of file token. */
|
||||
types_[cursor] = EndOfFile;
|
||||
size_ = cursor;
|
||||
}
|
||||
|
||||
void TokenBuffer::compute_lengths()
|
||||
{
|
||||
uint32_t tok_id = 0;
|
||||
#if defined(USE_NEON) || defined(USE_SSE4_2)
|
||||
using namespace simd;
|
||||
static constexpr int stride = 64;
|
||||
for (; tok_id + stride <= size_; tok_id += stride) {
|
||||
const u32x64 str_start = u32x64::load(&offsets_[tok_id]);
|
||||
const u32x64 str_end = u32x64::load(&offsets_end_[tok_id]);
|
||||
const u32x64 str_size_32 = str_end - str_start;
|
||||
const u8x64 str_large = u8x64(str_size_32 > 127);
|
||||
/* Saturate the size to max int8_t since SSE comparison is signed. */
|
||||
const u8x64 str_size = select(u8x64(str_size_32), u8x64(127), str_large);
|
||||
str_size.store(&lengths_[tok_id]);
|
||||
}
|
||||
/* Finish tail using scalar loop. */
|
||||
#endif
|
||||
for (; tok_id < size_; ++tok_id) {
|
||||
const uint32_t str_start = offsets_[tok_id];
|
||||
const uint32_t str_end = offsets_end_[tok_id];
|
||||
const uint32_t str_size_32 = str_end - str_start;
|
||||
/* Saturate the size to max int8_t since SSE comparison is signed. */
|
||||
const uint8_t str_size = select(uint8_t(str_size_32), 127, str_size_32 > 127);
|
||||
lengths_[tok_id] = str_size;
|
||||
}
|
||||
}
|
||||
|
||||
void TokenBuffer::atomize_words(IdentifierMap &identifiers, const KeywordTable &keywords)
|
||||
{
|
||||
static constexpr int stride = 64;
|
||||
|
||||
struct Masks {
|
||||
uint64_t mask8, mask16, mask24, mask32;
|
||||
};
|
||||
/* First scan to create a bitmap of potential matches to avoid wasting cycles iterating over
|
||||
* non-words. */
|
||||
lexit::Vector<Masks> masks_small_id;
|
||||
lexit::Vector<uint64_t> masks_large_id;
|
||||
masks_small_id.resize(divide_ceil(size_, stride));
|
||||
masks_large_id.resize(divide_ceil(size_, stride));
|
||||
|
||||
{
|
||||
uint32_t chunk_id = 0;
|
||||
uint32_t tok_id = 0;
|
||||
#if defined(USE_NEON) || defined(USE_SSE4_2)
|
||||
using namespace simd;
|
||||
for (; tok_id + stride <= size_; tok_id += stride, ++chunk_id) {
|
||||
const u8x64 size = u8x64::load(&lengths_[tok_id]);
|
||||
const u8x64 type = u8x64::load((uint8_t *)&types_[tok_id]);
|
||||
const uint64_t is_word = movemask(type == Word);
|
||||
const uint64_t less_8 = movemask(size < 9);
|
||||
const uint64_t less_16 = movemask(size < 17);
|
||||
const uint64_t less_24 = movemask(size < 25);
|
||||
const uint64_t less_32 = movemask(size < 33);
|
||||
|
||||
Masks small;
|
||||
small.mask8 = is_word & less_8;
|
||||
small.mask16 = is_word & less_16 & ~less_8;
|
||||
small.mask24 = is_word & less_24 & ~less_16;
|
||||
small.mask32 = is_word & less_32 & ~less_24;
|
||||
masks_small_id[chunk_id] = small;
|
||||
masks_large_id[chunk_id] = is_word & ~less_32;
|
||||
}
|
||||
#endif
|
||||
for (; tok_id < size_; tok_id += stride, ++chunk_id) {
|
||||
uint64_t is_word = 0, less_8 = 0, less_16 = 0, less_24 = 0, less_32 = 0;
|
||||
|
||||
for (int i = 0; i < stride && tok_id + i < size_; ++i) {
|
||||
const uint8_t size = lengths_[tok_id + i];
|
||||
const TokenType type = types_[tok_id + i];
|
||||
const uint64_t bit = uint64_t(1) << i;
|
||||
/* Generate a masks of all 1s if true, all 0s if false. */
|
||||
is_word |= bit & -uint64_t(type == Word);
|
||||
less_8 |= bit & -uint64_t(size < 9);
|
||||
less_16 |= bit & -uint64_t(size < 17);
|
||||
less_24 |= bit & -uint64_t(size < 25);
|
||||
less_32 |= bit & -uint64_t(size < 33);
|
||||
}
|
||||
|
||||
Masks small;
|
||||
small.mask8 = is_word & less_8;
|
||||
small.mask16 = is_word & less_16 & ~less_8;
|
||||
small.mask24 = is_word & less_24 & ~less_16;
|
||||
small.mask32 = is_word & less_32 & ~less_24;
|
||||
masks_small_id[chunk_id] = small;
|
||||
masks_large_id[chunk_id] = is_word & ~less_32;
|
||||
}
|
||||
}
|
||||
{
|
||||
/* Iterate over the bitmasks. */
|
||||
const int end = divide_ceil(size_, stride);
|
||||
/* The atomize_short_tokens_in_mask can read past the end of each token by 8 bytes.
|
||||
* For this reason we process the last chunks that contains the last 8 tokens separately. */
|
||||
const int end_safe = divide_ceil(int(size_) - 8, stride) - 1;
|
||||
int chunk = 0;
|
||||
for (; chunk < end_safe; ++chunk) {
|
||||
const Masks small = masks_small_id[chunk];
|
||||
atomize_short_tokens_in_mask<1>(small.mask8, chunk * stride, identifiers, keywords);
|
||||
atomize_short_tokens_in_mask<2>(small.mask16, chunk * stride, identifiers, keywords);
|
||||
atomize_short_tokens_in_mask<3>(small.mask24, chunk * stride, identifiers, keywords);
|
||||
atomize_short_tokens_in_mask<4>(small.mask32, chunk * stride, identifiers, keywords);
|
||||
atomize_tokens_in_mask(masks_large_id[chunk], chunk * stride, identifiers, keywords);
|
||||
}
|
||||
for (; chunk < end; ++chunk) {
|
||||
const Masks small = masks_small_id[chunk];
|
||||
atomize_tokens_in_mask(small.mask8, chunk * stride, identifiers, keywords);
|
||||
atomize_tokens_in_mask(small.mask16, chunk * stride, identifiers, keywords);
|
||||
atomize_tokens_in_mask(small.mask24, chunk * stride, identifiers, keywords);
|
||||
atomize_tokens_in_mask(small.mask32, chunk * stride, identifiers, keywords);
|
||||
atomize_tokens_in_mask(masks_large_id[chunk], chunk * stride, identifiers, keywords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INLINE_METHOD void TokenBuffer::atomize_tokens_in_mask(uint64_t mask,
|
||||
uint32_t tok_id_base,
|
||||
IdentifierMap &id_map,
|
||||
const KeywordTable &kw_table)
|
||||
{
|
||||
if (mask == 0) [[likely]] {
|
||||
return;
|
||||
}
|
||||
while (mask != 0) {
|
||||
const int index = builtin_ctzll(mask);
|
||||
const int tok_id = tok_id_base + index;
|
||||
|
||||
const int str_start = offsets_[tok_id];
|
||||
const int str_size = offsets_end_[tok_id] - str_start;
|
||||
const std::string_view str = {str_.data() + str_start, size_t(str_size)};
|
||||
|
||||
const TokenAtom atom = id_map.lookup_or_add(str);
|
||||
atoms_[tok_id] = atom;
|
||||
types_[tok_id] = kw_table[atom];
|
||||
/* Pop last bit. */
|
||||
mask &= (mask - 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<int Size>
|
||||
INLINE_METHOD void TokenBuffer::atomize_short_tokens_in_mask(uint64_t mask,
|
||||
uint32_t tok_id_base,
|
||||
IdentifierMap &id_map,
|
||||
const KeywordTable &kw_table)
|
||||
{
|
||||
while (mask != 0) {
|
||||
const int index = builtin_ctzll(mask);
|
||||
const int tok_id = tok_id_base + index;
|
||||
|
||||
const int str_start = offsets_[tok_id];
|
||||
const int str_size = lengths_[tok_id]; /* PaddedString is for small identifier. */
|
||||
|
||||
const std::string_view str = {str_.data() + str_start, size_t(str_size)};
|
||||
const PaddedString<Size> padded_str(str);
|
||||
|
||||
const TokenAtom atom = id_map.lookup_or_add(padded_str);
|
||||
atoms_[tok_id] = atom;
|
||||
types_[tok_id] = kw_table[atom];
|
||||
/* Pop last bit. */
|
||||
mask &= (mask - 1);
|
||||
}
|
||||
}
|
||||
|
||||
template void TokenBuffer::atomize_short_tokens_in_mask<1>(uint64_t,
|
||||
uint32_t,
|
||||
IdentifierMap &,
|
||||
const KeywordTable &);
|
||||
template void TokenBuffer::atomize_short_tokens_in_mask<2>(uint64_t,
|
||||
uint32_t,
|
||||
IdentifierMap &,
|
||||
const KeywordTable &);
|
||||
template void TokenBuffer::atomize_short_tokens_in_mask<3>(uint64_t,
|
||||
uint32_t,
|
||||
IdentifierMap &,
|
||||
const KeywordTable &);
|
||||
template void TokenBuffer::atomize_short_tokens_in_mask<4>(uint64_t,
|
||||
uint32_t,
|
||||
IdentifierMap &,
|
||||
const KeywordTable &);
|
||||
|
||||
static void lex_string(const TokenType *types, uint32_t &cursor)
|
||||
{
|
||||
const TokenType *ptr = types + cursor;
|
||||
while (true) {
|
||||
cursor++;
|
||||
ptr++;
|
||||
if (*ptr == '\\') {
|
||||
/* Escaped character. Skip next. */
|
||||
cursor++;
|
||||
ptr++;
|
||||
continue;
|
||||
}
|
||||
if (*ptr == String || *ptr == EndOfFile) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void lex_float(const std::string_view str,
|
||||
const TokenType *types,
|
||||
const uint32_t *offsets,
|
||||
const uint32_t *offsets_end_,
|
||||
uint32_t &cursor)
|
||||
{
|
||||
const TokenType *type = types + cursor;
|
||||
const uint32_t *offset = offsets + cursor;
|
||||
const uint32_t *offset_end_ = offsets_end_ + cursor;
|
||||
|
||||
/* If number is followed by whitespace itself. */
|
||||
const bool followed_by_whitespace = offset_end_[0] != offset[1];
|
||||
if (followed_by_whitespace) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
cursor++;
|
||||
type++;
|
||||
offset++;
|
||||
offset_end_++;
|
||||
|
||||
/* Check if the previous char was an exponent "e" char. */
|
||||
if ((*type == '+' || *type == '-') && str[*offset - 1] != 'e') {
|
||||
break;
|
||||
}
|
||||
if (!(*type == Word || *type == Number || *type == '.' || *type == '+' || *type == '-')) {
|
||||
break;
|
||||
}
|
||||
/* Break if this token is part of the number but followed by a whitespace. */
|
||||
const bool followed_by_whitespace = offset_end_[0] != offset[1];
|
||||
if (followed_by_whitespace) {
|
||||
/* Note: we don't want to do the cursor roll back since we want to merge this token. */
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* We need to evaluate the token we broke on. */
|
||||
cursor--;
|
||||
}
|
||||
|
||||
static void lex_comment(const std::string_view str,
|
||||
const TokenType *types,
|
||||
const uint32_t *offsets,
|
||||
uint32_t &cursor,
|
||||
TokenType &out_type)
|
||||
{
|
||||
const uint32_t start = offsets[cursor];
|
||||
const TokenType *type = types + cursor;
|
||||
const char c = str[start + 1];
|
||||
|
||||
size_t end_pos;
|
||||
if (c == '/') {
|
||||
/* Single-line comment. Search for end of line. */
|
||||
end_pos = str.find('\n', start + 2);
|
||||
}
|
||||
else if (c == '*') {
|
||||
/* Multi-line comment. Search for termination. */
|
||||
end_pos = str.find("*/", start + 2);
|
||||
/* Search for the closing slash. */
|
||||
end_pos += (end_pos != std::string::npos) ? 1 : 0;
|
||||
}
|
||||
else {
|
||||
/* Not a comment. */
|
||||
return;
|
||||
}
|
||||
|
||||
out_type = Comment;
|
||||
|
||||
while (*type != EndOfFile) {
|
||||
const uint32_t tok_start = offsets[cursor];
|
||||
/* Skip tokens until we find the one that starts after the end of the comment. */
|
||||
if (tok_start > end_pos) {
|
||||
break;
|
||||
}
|
||||
cursor++;
|
||||
type++;
|
||||
}
|
||||
/* We need to evaluate the token we broke on. */
|
||||
cursor--;
|
||||
}
|
||||
|
||||
/* Ideally this should not be needed and the `tokenize` step should just parse them correctly.
|
||||
* But this would much harder to implement. */
|
||||
void TokenBuffer::merge_complex_literals()
|
||||
{
|
||||
const TokenType *in_types = types_.get();
|
||||
TokenType *out_type = types_.get();
|
||||
const uint32_t *in_offsets = offsets_.get();
|
||||
const uint32_t *in_offset_end = offsets_end_.get();
|
||||
uint32_t *out_offset = offsets_.get();
|
||||
uint32_t *out_offset_end = offsets_end_.get();
|
||||
|
||||
for (uint32_t i = 0; i < size_; i++, out_type++, out_offset++, out_offset_end++) {
|
||||
const TokenType type = in_types[i];
|
||||
const uint32_t offset = in_offsets[i];
|
||||
const uint32_t offset_end = in_offset_end[i];
|
||||
*out_type = type;
|
||||
*out_offset = offset;
|
||||
*out_offset_end = offset_end;
|
||||
|
||||
switch (type) {
|
||||
case String:
|
||||
lex_string(in_types, i);
|
||||
break;
|
||||
case Number:
|
||||
lex_float(str_, in_types, in_offsets, in_offset_end, i);
|
||||
break;
|
||||
case Slash:
|
||||
lex_comment(str_, in_types, in_offsets, i, *out_type);
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
/* Set the correct end for the complex token that have just been lexed. */
|
||||
*out_offset_end = in_offset_end[i];
|
||||
}
|
||||
|
||||
assert(in_types <= out_type);
|
||||
assert(out_type - in_types < 0xFFFFFFFFu);
|
||||
size_ = out_type - in_types;
|
||||
types_[size_] = EndOfFile;
|
||||
offsets_[size_] = str_.size();
|
||||
}
|
||||
|
||||
} // namespace lexit
|
||||
409
blender-5.2.0/source/blender/gpu/shader_tool/lexit/lexit.hh
Normal file
409
blender-5.2.0/source/blender/gpu/shader_tool/lexit/lexit.hh
Normal file
@@ -0,0 +1,409 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Clement Foucault
|
||||
*
|
||||
* SPDX-License-Identifier: MIT */
|
||||
|
||||
/**
|
||||
* LexIt is a lexer tool library focus on simplicity and efficiency.
|
||||
*
|
||||
* It is aimed at building source code processors without requiring huge dependencies like LLVM.
|
||||
* It only supports unextended-ASCII inputs that are under 4GB (because of 32bit offsets).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <string_view>
|
||||
|
||||
#include "identifier.hh"
|
||||
#include "types.hh"
|
||||
#include "vector.hh"
|
||||
|
||||
// #define LEXIT_DEBUG
|
||||
|
||||
#ifdef LEXIT_DEBUG
|
||||
/* Make it a warning to avoid shipping with it. */
|
||||
# warning "Lexit debug mode enabled"
|
||||
|
||||
# include <vector>
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define INLINE_METHOD __forceinline
|
||||
#else
|
||||
# define INLINE_METHOD inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
namespace lexit {
|
||||
|
||||
struct TokenBuffer;
|
||||
|
||||
struct Token {
|
||||
#ifdef LEXIT_DEBUG
|
||||
std::string_view debug_str_;
|
||||
const TokenType *debug_type_;
|
||||
const TokenAtom *debug_atom_;
|
||||
#endif
|
||||
const TokenBuffer *buf_;
|
||||
int32_t index_;
|
||||
/* General purpose flag. */
|
||||
int32_t flag = 0;
|
||||
|
||||
Token(const TokenBuffer *buf, int32_t index);
|
||||
|
||||
/* Invalid token that can still be compared with other tokens. */
|
||||
static Token invalid(const TokenBuffer *buf);
|
||||
|
||||
explicit operator int32_t() const
|
||||
{
|
||||
return index_;
|
||||
}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return type() != EndOfFile;
|
||||
}
|
||||
bool is_invalid() const
|
||||
{
|
||||
return type() == EndOfFile;
|
||||
}
|
||||
|
||||
const TokenType &type() const;
|
||||
const TokenAtom &atom() const;
|
||||
|
||||
std::string_view str() const;
|
||||
std::string_view str_with_whitespace() const;
|
||||
|
||||
bool followed_by_whitespace() const;
|
||||
|
||||
Token next(int i = 1) const;
|
||||
Token prev(int i = 1) const;
|
||||
|
||||
friend bool operator==(const Token &a, const Token &b)
|
||||
{
|
||||
assert(a.buf_ == b.buf_);
|
||||
return a.index_ == b.index_;
|
||||
}
|
||||
friend bool operator!=(const Token &a, const Token &b)
|
||||
{
|
||||
assert(a.buf_ == b.buf_);
|
||||
return a.index_ != b.index_;
|
||||
}
|
||||
|
||||
friend bool operator==(const Token &a, TokenType b)
|
||||
{
|
||||
return a.type() == b;
|
||||
}
|
||||
friend bool operator!=(const Token &a, TokenType b)
|
||||
{
|
||||
return a.type() != b;
|
||||
}
|
||||
};
|
||||
|
||||
/* Same as Token but allow type assignment. */
|
||||
struct TokenMut : public Token {
|
||||
TokenMut(TokenBuffer *buf, int32_t index) : Token(buf, index) {}
|
||||
|
||||
TokenType &type();
|
||||
TokenAtom &atom();
|
||||
};
|
||||
|
||||
template<typename T> struct AlignedDeleter {
|
||||
void operator()(T *p) const
|
||||
{
|
||||
::operator delete[](p, std::align_val_t{64});
|
||||
}
|
||||
};
|
||||
|
||||
struct TokenBuffer {
|
||||
/* Input string. */
|
||||
std::string_view str_;
|
||||
/* Type of each token. */
|
||||
AlignedArrayPtr<TokenType> types_;
|
||||
/* Starting character index of each token. */
|
||||
AlignedArrayPtr<uint32_t> offsets_;
|
||||
/* Original character index of each token before whitespace merging. */
|
||||
AlignedArrayPtr<uint32_t> offsets_end_;
|
||||
/* Length in characters of each token. A value of 127 means the real size is over 126. */
|
||||
AlignedArrayPtr<uint8_t> lengths_;
|
||||
/* Unique id for identifiers (Words). Externally set (optional). */
|
||||
AlignedArrayPtr<TokenAtom> atoms_;
|
||||
/* Number of tokens inside the buffer excluding the terminating EndOfFile token. */
|
||||
uint32_t size_ = 0;
|
||||
/* Number of tokens that can be contained. */
|
||||
uint32_t allocated_size_ = 0;
|
||||
|
||||
#ifdef LEXIT_DEBUG
|
||||
std::vector<std::string_view> token_str_debug_;
|
||||
std::vector<std::string_view> token_str_with_whitespace_debug_;
|
||||
#endif
|
||||
|
||||
TokenBuffer() = default;
|
||||
|
||||
TokenBuffer(const std::string_view str, const CharClass char_class_table[128])
|
||||
{
|
||||
process(str, char_class_table);
|
||||
}
|
||||
|
||||
/*
|
||||
* The given string lifetime should outlive the #TokenBuffer. No copy is done.
|
||||
*/
|
||||
void process(const std::string_view str, const CharClass char_class_table[128])
|
||||
{
|
||||
assert(str.size() < UINT_MAX);
|
||||
str_ = str;
|
||||
clear();
|
||||
tokenize(char_class_table);
|
||||
compute_lengths();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Discard all currently held data. Does not reallocate.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* \brief Allocate backing memory for the given number of tokens and move currently held data.
|
||||
*
|
||||
* Does nothing if allocation is already large enough.
|
||||
*/
|
||||
void reserve(const uint32_t count);
|
||||
|
||||
/**
|
||||
* \brief Tokenizes the input string by grouping contiguous characters of the same class.
|
||||
*
|
||||
* This function iterates through the input string and identifies "runs" of characters
|
||||
* that map to the same CharClass. For each new group, it records the type and the
|
||||
* starting byte offset into the result arrays.
|
||||
*
|
||||
* Only characters with the #CanMerge flag are merged together.
|
||||
* Characters with a class greater than #ClassToTypeThreshold will just be assigned their class
|
||||
* as #TokenType. Otherwise, the first character of the token will be used as #TokenType.
|
||||
*
|
||||
* If the input string contains characters that are not inside the ASCII range, the result of
|
||||
* the operation is undefined and might cause segmentation fault.
|
||||
*
|
||||
* \param char_class_table A lookup table mapping ASCII values (0-127) to an 8-bit CharClass.
|
||||
*/
|
||||
void tokenize(const CharClass char_class_table[128]);
|
||||
|
||||
/**
|
||||
* \brief Merge complex literals such as floats, strings and comments.
|
||||
*/
|
||||
void merge_complex_literals();
|
||||
|
||||
/**
|
||||
* \brief Assign keyword types and atoms for a small set of identifier.
|
||||
*/
|
||||
void atomize_words(IdentifierMap &identifiers, const KeywordTable &keywords);
|
||||
|
||||
/**
|
||||
* \brief Compute small token length for speeding up certain tasks.
|
||||
*/
|
||||
void compute_lengths();
|
||||
|
||||
/**
|
||||
* \brief Return the amount of token inside the buffer.
|
||||
*/
|
||||
uint32_t size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the substring between the start and end tokens (included).
|
||||
*
|
||||
* \param with_trailing_whitespaces If true, include the trailing whitespaces.
|
||||
*/
|
||||
std::string_view substr(const Token &start,
|
||||
const Token &end,
|
||||
const bool with_trailing_whitespaces = false) const
|
||||
{
|
||||
int start_char = offsets_[int(start)];
|
||||
int end_char = (!with_trailing_whitespaces) ? offsets_end_[int(end)] : offsets_[int(end) + 1];
|
||||
return str_.substr(start_char, end_char - start_char);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Append a Token at the end of the buffer.
|
||||
*/
|
||||
void append(TokenType type, TokenAtom atom, int32_t str_size, int32_t str_size_with_witespaces)
|
||||
{
|
||||
reserve(size_ + 1);
|
||||
types_[size_] = type;
|
||||
atoms_[size_] = atom;
|
||||
offsets_[size_ + 1] = offsets_[size_] + str_size_with_witespaces;
|
||||
offsets_end_[size_] = offsets_[size_] + str_size;
|
||||
size_++;
|
||||
}
|
||||
|
||||
Token operator[](int index) const
|
||||
{
|
||||
return Token(this, index);
|
||||
}
|
||||
TokenMut operator[](int index)
|
||||
{
|
||||
return TokenMut(this, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Token iterator.
|
||||
*/
|
||||
struct TokenIt {
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
private:
|
||||
TokenBuffer *buf_;
|
||||
int32_t index_;
|
||||
|
||||
public:
|
||||
TokenIt(TokenBuffer *buf, const int index) : buf_(buf), index_(index) {}
|
||||
|
||||
TokenMut operator*() const
|
||||
{
|
||||
return TokenMut(buf_, index_);
|
||||
}
|
||||
|
||||
TokenIt &operator++()
|
||||
{
|
||||
index_++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int32_t index() const
|
||||
{
|
||||
return index_;
|
||||
}
|
||||
|
||||
bool operator==(const TokenIt &other) const
|
||||
{
|
||||
return index_ == other.index_;
|
||||
}
|
||||
bool operator!=(const TokenIt &other) const
|
||||
{
|
||||
return index_ != other.index_;
|
||||
}
|
||||
bool operator<(const TokenIt &other) const
|
||||
{
|
||||
return index_ < other.index_;
|
||||
}
|
||||
};
|
||||
|
||||
TokenIt begin()
|
||||
{
|
||||
return TokenIt(this, 0);
|
||||
}
|
||||
TokenIt end()
|
||||
{
|
||||
return TokenIt(this, size_);
|
||||
}
|
||||
|
||||
Token front()
|
||||
{
|
||||
return (*this)[0];
|
||||
}
|
||||
Token back()
|
||||
{
|
||||
return (*this)[size_ - 1];
|
||||
}
|
||||
|
||||
private:
|
||||
inline void tokenize_scalar(uint32_t &__restrict offset,
|
||||
uint32_t &__restrict cursor_begin,
|
||||
uint32_t &__restrict cursor_end,
|
||||
CharClass &__restrict prev_char_class,
|
||||
bool &__restrict prev_whitespace,
|
||||
uint32_t end,
|
||||
const CharClass char_class_table[128]);
|
||||
|
||||
template<int Size = 0>
|
||||
INLINE_METHOD void atomize_short_tokens_in_mask(uint64_t mask,
|
||||
uint32_t tok_id_base,
|
||||
IdentifierMap &id_map,
|
||||
const KeywordTable &kw_table);
|
||||
|
||||
INLINE_METHOD void atomize_tokens_in_mask(uint64_t mask,
|
||||
uint32_t tok_id_base,
|
||||
IdentifierMap &id_map,
|
||||
const KeywordTable &kw_table);
|
||||
};
|
||||
|
||||
inline Token::Token(const TokenBuffer *buf, int32_t index) : buf_(buf)
|
||||
{
|
||||
assert(buf_ != nullptr);
|
||||
/* Set to Invalid / EndOfFile token if out of range. */
|
||||
index_ = (index < 0 || index > buf_->size_) ? buf_->size_ : index;
|
||||
#ifdef LEXIT_DEBUG
|
||||
debug_str_ = str_with_whitespace();
|
||||
debug_type_ = &type();
|
||||
debug_atom_ = &atom();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline Token Token::invalid(const TokenBuffer *buf)
|
||||
{
|
||||
return Token(buf, buf->size_);
|
||||
}
|
||||
|
||||
inline const TokenType &Token::type() const
|
||||
{
|
||||
return buf_->types_[index_];
|
||||
}
|
||||
inline TokenType &TokenMut::type()
|
||||
{
|
||||
return const_cast<TokenBuffer *>(buf_)->types_[index_];
|
||||
}
|
||||
|
||||
inline const TokenAtom &Token::atom() const
|
||||
{
|
||||
return buf_->atoms_[index_];
|
||||
}
|
||||
inline TokenAtom &TokenMut::atom()
|
||||
{
|
||||
return const_cast<TokenBuffer *>(buf_)->atoms_[index_];
|
||||
}
|
||||
|
||||
inline Token Token::next(int i) const
|
||||
{
|
||||
return Token(buf_, index_ + i);
|
||||
}
|
||||
inline Token Token::prev(int i) const
|
||||
{
|
||||
return is_valid() ? Token(buf_, index_ - i) : Token(buf_, -1);
|
||||
}
|
||||
|
||||
inline std::string_view Token::str() const
|
||||
{
|
||||
int start = buf_->offsets_[index_];
|
||||
int end = buf_->offsets_end_[index_];
|
||||
return {buf_->str_.data() + start, size_t(end - start)};
|
||||
}
|
||||
|
||||
inline std::string_view Token::str_with_whitespace() const
|
||||
{
|
||||
int start = buf_->offsets_[index_];
|
||||
int end = buf_->offsets_[index_ + 1];
|
||||
return {buf_->str_.data() + start, size_t(end - start)};
|
||||
}
|
||||
|
||||
inline bool Token::followed_by_whitespace() const
|
||||
{
|
||||
assert(is_valid());
|
||||
return buf_->offsets_end_[index_] != buf_->offsets_[index_ + 1];
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &os, const Token &tok)
|
||||
{
|
||||
os << "Token(";
|
||||
os << "type='" << tok.type() << "', ";
|
||||
os << "atom=" << tok.atom() << ", ";
|
||||
os << "str=\"" << tok.str() << "\", ";
|
||||
os << "str_with_whitespace=\"" << tok.str_with_whitespace() << "\"";
|
||||
os << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace lexit
|
||||
829
blender-5.2.0/source/blender/gpu/shader_tool/lexit/simd.hh
Normal file
829
blender-5.2.0/source/blender/gpu/shader_tool/lexit/simd.hh
Normal file
@@ -0,0 +1,829 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT */
|
||||
|
||||
/**
|
||||
* Small SIMD library to avoid duplicating code.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
#if defined(__ARM_NEON)
|
||||
# define USE_NEON
|
||||
# include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#if (defined(__x86_64__) || defined(_M_X64)) && defined(__SSE4_2__)
|
||||
# define USE_SSE4_2
|
||||
# include <immintrin.h>
|
||||
#endif
|
||||
|
||||
#if defined(USE_NEON) || defined(USE_SSE4_2)
|
||||
|
||||
namespace lexit::simd {
|
||||
|
||||
/* Size must be power of 2. */
|
||||
template<int Size> struct u8_base {
|
||||
# if defined(USE_NEON)
|
||||
uint8x16_t lanes[Size];
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i lanes[Size];
|
||||
# endif
|
||||
|
||||
u8_base() = default;
|
||||
|
||||
explicit u8_base(const uint8_t scalar)
|
||||
{
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
lanes[i] = vdupq_n_u8(scalar);
|
||||
# elif defined(USE_SSE4_2)
|
||||
lanes[i] = _mm_set1_epi8(scalar);
|
||||
# endif
|
||||
}
|
||||
}
|
||||
|
||||
static u8_base load_unaligned(const uint8_t *src)
|
||||
{
|
||||
u8_base result;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[i] = vld1q_u8(src + i * 16);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result.lanes[i] = _mm_loadu_si128((const __m128i *)src + i);
|
||||
# endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static u8_base load(const uint8_t *src)
|
||||
{
|
||||
assert((intptr_t(src) & (16 - 1)) == 0);
|
||||
u8_base result;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[i] = vld1q_u8(src + i * 16);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result.lanes[i] = _mm_load_si128((const __m128i *)src + i);
|
||||
# endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void store_unaligned(uint8_t *dst) const
|
||||
{
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
vst1q_u8(dst + i * 16, lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
_mm_storeu_si128((__m128i *)dst + i, lanes[i]);
|
||||
# endif
|
||||
}
|
||||
}
|
||||
|
||||
void store(uint8_t *dst) const
|
||||
{
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
vst1q_u8(dst + i * 16, lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
_mm_store_si128((__m128i *)dst + i, lanes[i]);
|
||||
# endif
|
||||
}
|
||||
}
|
||||
|
||||
u8_base<1> lane(int i) const
|
||||
{
|
||||
u8_base<1> result;
|
||||
result.lanes[0] = lanes[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Get content of end lane */
|
||||
uint8_t last() const
|
||||
{
|
||||
# if defined(USE_NEON)
|
||||
return vgetq_lane_u8(lanes[Size - 1], 15);
|
||||
# elif defined(USE_SSE4_2)
|
||||
auto lane = lanes[Size - 1];
|
||||
return _mm_extract_epi8(lane, 15);
|
||||
# endif
|
||||
}
|
||||
|
||||
/* Get content of end lane */
|
||||
uint8_t first() const
|
||||
{
|
||||
# if defined(USE_NEON)
|
||||
return vgetq_lane_u8(lanes[0], 0);
|
||||
# elif defined(USE_SSE4_2)
|
||||
return _mm_extract_epi8(lanes[0], 0);
|
||||
# endif
|
||||
}
|
||||
|
||||
/* --- Bitwise Operators --- */
|
||||
|
||||
friend u8_base operator^(u8_base a, u8_base b)
|
||||
{
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = veorq_u8(a.lanes[i], b.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_xor_si128(a.lanes[i], b.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
friend u8_base operator|(u8_base a, u8_base b)
|
||||
{
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vorrq_u8(a.lanes[i], b.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_or_si128(a.lanes[i], b.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
u8_base &operator|=(u8_base b)
|
||||
{
|
||||
*this = *this | b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend u8_base operator&(u8_base a, u8_base b)
|
||||
{
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vandq_u8(a.lanes[i], b.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_and_si128(a.lanes[i], b.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
friend u8_base operator&(u8_base a, uint8_t b)
|
||||
{
|
||||
# if defined(USE_NEON)
|
||||
uint8x16_t ref = vdupq_n_u8(b);
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i ref = _mm_set1_epi8(b);
|
||||
# endif
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vandq_u8(a.lanes[i], ref);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_and_si128(a.lanes[i], ref);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
u8_base operator~() const
|
||||
{
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vmvnq_u8(lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_xor_si128(lanes[i], _mm_set1_epi8(-1));
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/* --- Arithmetic Operators --- */
|
||||
|
||||
friend u8_base operator+(u8_base a, u8_base b)
|
||||
{
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vaddq_u8(a.lanes[i], b.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_add_epi8(a.lanes[i], b.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
friend u8_base operator+(u8_base a, uint8_t b)
|
||||
{
|
||||
# if defined(USE_NEON)
|
||||
uint8x16_t ref = vdupq_n_u8(b);
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i ref = _mm_set1_epi8(b);
|
||||
# endif
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vaddq_u8(a.lanes[i], ref);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_add_epi8(a.lanes[i], ref);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
friend u8_base operator-(u8_base a, u8_base b)
|
||||
{
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vsubq_u8(a.lanes[i], b.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_sub_epi8(a.lanes[i], b.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/* --- Comparison Operators --- */
|
||||
|
||||
/** WARNING: Signed comparison on SSE. Will not work for input greater than 127. */
|
||||
friend u8_base operator>(u8_base a, u8_base b)
|
||||
{
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vcgtq_u8(a.lanes[i], b.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_cmpgt_epi8(a.lanes[i], b.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** WARNING: Signed comparison on SSE. Will not work for input greater than 127. */
|
||||
friend u8_base operator<(u8_base a, u8_base b)
|
||||
{
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vcltq_u8(a.lanes[i], b.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_cmplt_epi8(a.lanes[i], b.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** WARNING: Signed comparison on SSE. Will not work for input greater than 127. */
|
||||
friend u8_base operator<(u8_base a, uint8_t b)
|
||||
{
|
||||
# if defined(USE_NEON)
|
||||
uint8x16_t ref = vdupq_n_u8(b);
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i ref = _mm_set1_epi8(b);
|
||||
# endif
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vcltq_u8(a.lanes[i], ref);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_cmplt_epi8(a.lanes[i], ref);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
friend u8_base operator==(u8_base a, uint8_t b)
|
||||
{
|
||||
# if defined(USE_NEON)
|
||||
uint8x16_t ref = vdupq_n_u8(b);
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i ref = _mm_set1_epi8(b);
|
||||
# endif
|
||||
u8_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vceqq_u8(a.lanes[i], ref);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_cmpeq_epi8(a.lanes[i], ref);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
using u8x16 = u8_base<1>;
|
||||
using u8x32 = u8_base<2>;
|
||||
using u8x64 = u8_base<4>;
|
||||
|
||||
struct u8x16_table {
|
||||
u8x16 table;
|
||||
|
||||
u8x16_table() = default;
|
||||
u8x16_table(u8x16 table) : table(table) {}
|
||||
|
||||
static u8x16_table load_unaligned(const uint8_t *src)
|
||||
{
|
||||
u8x16_table table;
|
||||
table.table = u8x16::load_unaligned(src);
|
||||
return table;
|
||||
}
|
||||
|
||||
static u8x16_table load(const uint8_t *src)
|
||||
{
|
||||
u8x16_table table;
|
||||
table.table = u8x16::load(src);
|
||||
return table;
|
||||
}
|
||||
|
||||
template<int Size> u8_base<Size> operator[](u8_base<Size> index) const
|
||||
{
|
||||
u8_base<Size> result;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[i] = vqtbl1q_u8(table.lanes[0], index.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
/* Make sure to mimic the NEON behavior and return 0 on overflow.
|
||||
* _mm_shuffle_epi8 will only return zero if the MSB is high. */
|
||||
__m128i out_of_range = _mm_cmpgt_epi8(index.lanes[i], _mm_set1_epi8(15));
|
||||
__m128i safe_indices = _mm_or_si128(index.lanes[i], out_of_range);
|
||||
result.lanes[i] = _mm_shuffle_epi8(table.lanes[0], safe_indices);
|
||||
# endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Only valid if all indices are less than 16. */
|
||||
template<int Size> u8_base<Size> unsafe_shuffle(u8_base<Size> index) const
|
||||
{
|
||||
u8_base<Size> result;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[i] = vqtbl1q_u8(table.lanes[0], index.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result.lanes[i] = _mm_shuffle_epi8(table.lanes[0], index.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct u8x64_table {
|
||||
# if defined(USE_NEON)
|
||||
uint8x16x4_t table;
|
||||
# elif defined(USE_SSE4_2)
|
||||
u8x16_table tables[4];
|
||||
# endif
|
||||
|
||||
u8x64_table() = default;
|
||||
# if defined(USE_NEON)
|
||||
u8x64_table(u8x64 table)
|
||||
: table({table.lanes[0], table.lanes[1], table.lanes[2], table.lanes[3]})
|
||||
{
|
||||
}
|
||||
# elif defined(USE_SSE4_2)
|
||||
u8x64_table(u8x64 table)
|
||||
{
|
||||
tables[0].table = table.lane(0);
|
||||
tables[1].table = table.lane(1);
|
||||
tables[2].table = table.lane(2);
|
||||
tables[3].table = table.lane(3);
|
||||
}
|
||||
# endif
|
||||
|
||||
static u8x64_table load_unaligned(const uint8_t *src)
|
||||
{
|
||||
u8x64_table table;
|
||||
# if defined(USE_NEON)
|
||||
table.table = vld1q_u8_x4(src);
|
||||
# elif defined(USE_SSE4_2)
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
table.tables[i] = u8x16_table::load_unaligned(src + i * 16);
|
||||
}
|
||||
# endif
|
||||
return table;
|
||||
}
|
||||
|
||||
static u8x64_table load(const uint8_t *src)
|
||||
{
|
||||
assert((intptr_t(src) & 63) == 0);
|
||||
u8x64_table table;
|
||||
# if defined(USE_NEON)
|
||||
table.table = vld1q_u8_x4(src);
|
||||
# elif defined(USE_SSE4_2)
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
table.tables[i] = u8x16_table::load(src + i * 16);
|
||||
}
|
||||
# endif
|
||||
return table;
|
||||
}
|
||||
|
||||
template<int Size> u8_base<Size> operator[](u8_base<Size> index) const
|
||||
{
|
||||
u8_base<Size> result;
|
||||
# if defined(USE_NEON)
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
result.lanes[i] = vqtbl4q_u8(table, index.lanes[i]);
|
||||
}
|
||||
# elif defined(USE_SSE4_2)
|
||||
result = tables[0][index];
|
||||
result |= tables[1][index ^ u8_base<Size>(0x10)];
|
||||
result |= tables[2][index ^ u8_base<Size>(0x20)];
|
||||
result |= tables[3][index ^ u8_base<Size>(0x30)];
|
||||
# endif
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct u8x128_table {
|
||||
u8x64_table tables[2];
|
||||
|
||||
static u8x128_table load_unaligned(const uint8_t *src)
|
||||
{
|
||||
u8x128_table table;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
table.tables[i] = u8x64_table::load_unaligned(src + i * 64);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
static u8x128_table load(const uint8_t *src)
|
||||
{
|
||||
u8x128_table table;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
table.tables[i] = u8x64_table::load(src + i * 64);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/** Perform a 128 bytes table lookup for each lane of the input vector. */
|
||||
template<int Size> u8_base<Size> operator[](u8_base<Size> index) const
|
||||
{
|
||||
/* https://lemire.me/blog/2019/07/23/arbitrary-byte-to-byte-maps-using-arm-neon/
|
||||
* Table lookup on NEON will return 0 on overflow. Leverage this using XOR to swap which range
|
||||
* we are looking up and combine result using OR.
|
||||
* Note we make sure that SSE lookup have the same behavior Which is more costly
|
||||
* (more than 3x the number of instructions) it is then preferable to avoid this path. */
|
||||
return tables[0][index] | tables[1][index ^ u8_base<Size>(0x40)];
|
||||
}
|
||||
};
|
||||
|
||||
/* Select A if mask is 0, B otherwise.
|
||||
* Mask is expected to be 0xFF or 0x00 for each component. */
|
||||
template<int Size>
|
||||
inline u8_base<Size> select(u8_base<Size> a, u8_base<Size> b, u8_base<Size> mask)
|
||||
{
|
||||
u8_base<Size> result;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[i] = vbslq_u8(mask.lanes[i], b.lanes[i], a.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result.lanes[i] = _mm_blendv_epi8(a.lanes[i], b.lanes[i], mask.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* fill_value is the lanes to shift in. */
|
||||
template<int Shift, int Size>
|
||||
inline u8_base<Size> shift_lanes_right(u8_base<Size> a, uint8_t fill_value)
|
||||
{
|
||||
u8_base<Size> result;
|
||||
for (int i = Size - 1; i > 0; --i) {
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[i] = vextq_u8(a.lanes[i - 1], a.lanes[i], 16 - Shift);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result.lanes[i] = _mm_alignr_epi8(a.lanes[i], a.lanes[i - 1], 16 - Shift);
|
||||
# endif
|
||||
}
|
||||
u8_base<1> fill{fill_value};
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[0] = vextq_u8(fill.lanes[0], a.lanes[0], 16 - Shift);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result.lanes[0] = _mm_alignr_epi8(a.lanes[0], fill.lanes[0], 16 - Shift);
|
||||
# endif
|
||||
return result;
|
||||
}
|
||||
|
||||
template<int Size> inline u8_base<Size> is_zero(u8_base<Size> a)
|
||||
{
|
||||
# if defined(USE_SSE4_2)
|
||||
__m128i zero = _mm_setzero_si128();
|
||||
# endif
|
||||
u8_base<Size> result;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[i] = vceqzq_u8(a.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result.lanes[i] = _mm_cmpeq_epi8(a.lanes[i], zero);
|
||||
# endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Create a bitmask from a u8x64 mask containing 0xFF or 0x00 inside each lane. */
|
||||
inline uint64_t movemask(u8x64 mask)
|
||||
{
|
||||
uint64_t result;
|
||||
|
||||
# if defined(USE_NEON)
|
||||
const uint8x16_t bits = {1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128};
|
||||
/* Merge lanes with their neighbors (e.g. [1, 2, 4, 8, ...] > [3, 12, 48, ...]).
|
||||
* This is equivalent to merging using | and >> operations.
|
||||
* Doing it 3 time to collapse the 8 bits. */
|
||||
uint8x16_t sum0 = vpaddq_u8(vandq_u8(mask.lanes[0], bits), vandq_u8(mask.lanes[1], bits));
|
||||
uint8x16_t sum1 = vpaddq_u8(vandq_u8(mask.lanes[2], bits), vandq_u8(mask.lanes[3], bits));
|
||||
sum0 = vpaddq_u8(sum0, sum1);
|
||||
sum0 = vpaddq_u8(sum0, sum0);
|
||||
result = vgetq_lane_u64(vreinterpretq_u64_u8(sum0), 0);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result = _mm_movemask_epi8(mask.lanes[3]);
|
||||
result = _mm_movemask_epi8(mask.lanes[2]) | (result << 16);
|
||||
result = _mm_movemask_epi8(mask.lanes[1]) | (result << 16);
|
||||
result = _mm_movemask_epi8(mask.lanes[0]) | (result << 16);
|
||||
# endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Create a bitmask from a u8x32 mask containing 0xFF or 0x00 inside each lane. */
|
||||
inline uint32_t movemask(u8x32 mask)
|
||||
{
|
||||
uint32_t result;
|
||||
|
||||
# if defined(USE_NEON)
|
||||
const uint8x16_t bits = {1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128};
|
||||
/* Merge lanes with their neighbors (e.g. [1, 2, 4, 8, ...] > [3, 12, 48, ...]).
|
||||
* This is equivalent to merging using | and >> operations.
|
||||
* Doing it 3 time to collapse the 8 bits. */
|
||||
uint8x16_t sum = vpaddq_u8(vandq_u8(mask.lanes[0], bits), vandq_u8(mask.lanes[1], bits));
|
||||
sum = vpaddq_u8(sum, sum);
|
||||
sum = vpaddq_u8(sum, sum);
|
||||
result = vgetq_lane_u64(vreinterpretq_u64_u8(sum), 0);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result = _mm_movemask_epi8(mask.lanes[1]);
|
||||
result = _mm_movemask_epi8(mask.lanes[0]) | (result << 16);
|
||||
# endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Create a bitmask from a u8x64 mask containing 0xFF or 0x00 inside each lane. */
|
||||
inline uint16_t movemask(u8x16 mask)
|
||||
{
|
||||
uint16_t result;
|
||||
|
||||
# if defined(USE_NEON)
|
||||
const uint8x16_t bits = {1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128};
|
||||
uint8x16_t sum = vandq_u8(mask.lanes[0], bits);
|
||||
/* Merge lanes with their neighbors (e.g. [1, 2, 4, 8, ...] > [3, 12, 48, ...]).
|
||||
* This is equivalent to merging using | and >> operations.
|
||||
* Doing it 3 time to collapse the 8 bits. */
|
||||
sum = vpaddq_u8(sum, sum);
|
||||
sum = vpaddq_u8(sum, sum);
|
||||
sum = vpaddq_u8(sum, sum);
|
||||
result = vgetq_lane_u64(vreinterpretq_u64_u8(sum), 0);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result = _mm_movemask_epi8(mask.lanes[0]);
|
||||
# endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Size must be power of 2. */
|
||||
template<int Size> struct u16_base {
|
||||
# if defined(USE_NEON)
|
||||
uint16x8_t lanes[Size];
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i lanes[Size];
|
||||
# endif
|
||||
|
||||
u16_base() = default;
|
||||
|
||||
explicit u16_base(const u8_base<Size / 2> v)
|
||||
{
|
||||
# if defined(USE_SSE4_2)
|
||||
__m128i zero = _mm_setzero_si128();
|
||||
# endif
|
||||
for (int i = 0; i < Size / 2; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
lanes[i * 2 + 0] = vmovl_u8(vget_low_u8(v.lanes[i]));
|
||||
lanes[i * 2 + 1] = vmovl_u8(vget_high_u8(v.lanes[i]));
|
||||
# elif defined(USE_SSE4_2)
|
||||
lanes[i * 2 + 0] = _mm_unpacklo_epi8(v.lanes[i], zero);
|
||||
lanes[i * 2 + 1] = _mm_unpackhi_epi8(v.lanes[i], zero);
|
||||
# endif
|
||||
}
|
||||
}
|
||||
|
||||
void store(uint16_t *dst) const
|
||||
{
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
vst1q_u16(dst + i * 8, lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
_mm_storeu_si128((__m128i *)dst + i, lanes[i]);
|
||||
# endif
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using u16x16 = u16_base<2>;
|
||||
using u16x64 = u16_base<8>;
|
||||
|
||||
/* Size must be power of 2. */
|
||||
template<int Size> struct u32_base {
|
||||
# if defined(USE_NEON)
|
||||
uint32x4_t lanes[Size];
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i lanes[Size];
|
||||
# endif
|
||||
|
||||
u32_base() = default;
|
||||
|
||||
explicit u32_base(const u8_base<Size / 4> v)
|
||||
{
|
||||
for (int i = 0; i < Size / 4; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
uint16x8_t tmp_lo = vmovl_u8(vget_low_u8(v.lanes[i]));
|
||||
uint16x8_t tmp_hi = vmovl_u8(vget_high_u8(v.lanes[i]));
|
||||
lanes[i * 4 + 0] = vmovl_u16(vget_low_u16(tmp_lo));
|
||||
lanes[i * 4 + 1] = vmovl_u16(vget_high_u16(tmp_lo));
|
||||
lanes[i * 4 + 2] = vmovl_u16(vget_low_u16(tmp_hi));
|
||||
lanes[i * 4 + 3] = vmovl_u16(vget_high_u16(tmp_hi));
|
||||
# elif defined(USE_SSE4_2)
|
||||
lanes[i * 4 + 0] = _mm_cvtepu8_epi32(v.lanes[i]);
|
||||
lanes[i * 4 + 1] = _mm_cvtepu8_epi32(_mm_srli_si128(v.lanes[i], 4));
|
||||
lanes[i * 4 + 2] = _mm_cvtepu8_epi32(_mm_srli_si128(v.lanes[i], 8));
|
||||
lanes[i * 4 + 3] = _mm_cvtepu8_epi32(_mm_srli_si128(v.lanes[i], 12));
|
||||
# endif
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE: This does a signed saturation on SSE. */
|
||||
explicit operator u8_base<Size / 4>() const
|
||||
{
|
||||
u8_base<Size / 4> res;
|
||||
for (int i = 0; i < Size / 4; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
uint16x4_t n0 = vmovn_u32(lanes[i * 4 + 0]);
|
||||
uint16x4_t n1 = vmovn_u32(lanes[i * 4 + 1]);
|
||||
uint16x4_t n2 = vmovn_u32(lanes[i * 4 + 2]);
|
||||
uint16x4_t n3 = vmovn_u32(lanes[i * 4 + 3]);
|
||||
uint16x8_t q01 = vcombine_u16(n0, n1);
|
||||
uint16x8_t q23 = vcombine_u16(n2, n3);
|
||||
res.lanes[i] = vcombine_u8(vmovn_u16(q01), vmovn_u16(q23));
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i pack_16_lo = _mm_packs_epi32(lanes[i * 4 + 0], lanes[i * 4 + 1]);
|
||||
__m128i pack_16_hi = _mm_packs_epi32(lanes[i * 4 + 2], lanes[i * 4 + 3]);
|
||||
res.lanes[i] = _mm_packs_epi16(pack_16_lo, pack_16_hi);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static u32_base load_unaligned(const uint32_t *src)
|
||||
{
|
||||
u32_base result;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[i] = vld1q_u32(src + i * 4);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result.lanes[i] = _mm_loadu_si128((const __m128i *)src + i);
|
||||
# endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static u32_base load(const uint32_t *src)
|
||||
{
|
||||
assert((intptr_t(src) & (16 - 1)) == 0);
|
||||
u32_base result;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
result.lanes[i] = vld1q_u32(src + i * 4);
|
||||
# elif defined(USE_SSE4_2)
|
||||
result.lanes[i] = _mm_load_si128((const __m128i *)src + i);
|
||||
# endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void store_unaligned(uint32_t *dst) const
|
||||
{
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
vst1q_u32(dst + i * 4, lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
_mm_storeu_si128((__m128i *)dst + i, lanes[i]);
|
||||
# endif
|
||||
}
|
||||
}
|
||||
|
||||
void store(uint32_t *dst) const
|
||||
{
|
||||
assert((intptr_t(dst) & (16 - 1)) == 0);
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
vst1q_u32(dst + i * 4, lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
_mm_store_si128((__m128i *)dst + i, lanes[i]);
|
||||
# endif
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Arithmetic Operators --- */
|
||||
|
||||
/* Note: Signed comparison on SSE. */
|
||||
friend u32_base operator<(u32_base a, u32_base b)
|
||||
{
|
||||
u32_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vcltq_u32(a.lanes[i], b.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_cmplt_epi32(a.lanes[i], b.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Note: Signed comparison on SSE. */
|
||||
friend u32_base operator<(u32_base a, uint32_t b)
|
||||
{
|
||||
# if defined(USE_NEON)
|
||||
uint32x4_t tmp = vdupq_n_u32(b);
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i tmp = _mm_set1_epi32(b);
|
||||
# endif
|
||||
|
||||
u32_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vcltq_u32(a.lanes[i], tmp);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_cmplt_epi32(a.lanes[i], tmp);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
friend u32_base operator>(u32_base a, uint32_t b)
|
||||
{
|
||||
# if defined(USE_NEON)
|
||||
uint32x4_t tmp = vdupq_n_u32(b);
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i tmp = _mm_set1_epi32(b);
|
||||
# endif
|
||||
|
||||
u32_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vcgtq_u32(a.lanes[i], tmp);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_cmpgt_epi32(a.lanes[i], tmp);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
friend u32_base operator+(u32_base a, uint32_t b)
|
||||
{
|
||||
# if defined(USE_NEON)
|
||||
uint32x4_t tmp = vdupq_n_u32(b);
|
||||
# elif defined(USE_SSE4_2)
|
||||
__m128i tmp = _mm_set1_epi32(b);
|
||||
# endif
|
||||
|
||||
u32_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vaddq_u32(a.lanes[i], tmp);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_add_epi32(a.lanes[i], tmp);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
friend u32_base operator-(u32_base a, u32_base b)
|
||||
{
|
||||
u32_base res;
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
# if defined(USE_NEON)
|
||||
res.lanes[i] = vsubq_u32(a.lanes[i], b.lanes[i]);
|
||||
# elif defined(USE_SSE4_2)
|
||||
res.lanes[i] = _mm_sub_epi32(a.lanes[i], b.lanes[i]);
|
||||
# endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
using u32x16 = u32_base<4>;
|
||||
using u32x32 = u32_base<8>;
|
||||
using u32x64 = u32_base<16>;
|
||||
|
||||
} // namespace lexit::simd
|
||||
|
||||
#endif
|
||||
146
blender-5.2.0/source/blender/gpu/shader_tool/lexit/tables.hh
Normal file
146
blender-5.2.0/source/blender/gpu/shader_tool/lexit/tables.hh
Normal file
@@ -0,0 +1,146 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Clement Foucault
|
||||
*
|
||||
* SPDX-License-Identifier: MIT */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace lexit {
|
||||
|
||||
/* Associates a token type to each ASCII character. */
|
||||
alignas(128) const CharClass char_class_table[128] = {
|
||||
/* 0x00, '\0' */ CharClass::None,
|
||||
/* 0x01, SOH */ CharClass::None,
|
||||
/* 0x02, STX */ CharClass::None,
|
||||
/* 0x03, ETX */ CharClass::None,
|
||||
/* 0x04, EOT */ CharClass::None,
|
||||
/* 0x05, ENQ */ CharClass::None,
|
||||
/* 0x06, ACK */ CharClass::None,
|
||||
/* 0x07, '\a' */ CharClass::None,
|
||||
/* 0x08, '\b' */ CharClass::None,
|
||||
/* 0x09, '\t' */ CharClass::WhiteSpace,
|
||||
/* 0x0A, '\n' */ CharClass::Separator,
|
||||
/* 0x0B, '\v' */ CharClass::None,
|
||||
/* 0x0C, '\f' */ CharClass::None,
|
||||
/* 0x0D, '\r' */ CharClass::Separator,
|
||||
/* 0x0E, SO */ CharClass::None,
|
||||
/* 0x0F, SI */ CharClass::None,
|
||||
/* 0x10, DLE */ CharClass::None,
|
||||
/* 0x11, DC1 */ CharClass::None,
|
||||
/* 0x12, DC2 */ CharClass::None,
|
||||
/* 0x13, DC3 */ CharClass::None,
|
||||
/* 0x14, DC4 */ CharClass::None,
|
||||
/* 0x15, NAK */ CharClass::None,
|
||||
/* 0x16, SYN */ CharClass::None,
|
||||
/* 0x17, ETB */ CharClass::None,
|
||||
/* 0x18, CAN */ CharClass::None,
|
||||
/* 0x19, EM */ CharClass::None,
|
||||
/* 0x1A, SUB */ CharClass::None,
|
||||
/* 0x1B, ESC */ CharClass::None,
|
||||
/* 0x1C, FS */ CharClass::None,
|
||||
/* 0x1D, GS */ CharClass::None,
|
||||
/* 0x1E, RS */ CharClass::None,
|
||||
/* 0x1F, US */ CharClass::None,
|
||||
|
||||
/* 0x20, ' ' */ CharClass::WhiteSpace,
|
||||
/* 0x21, '!' */ CharClass::MultiTok,
|
||||
/* 0x22, '"' */ CharClass::Separator,
|
||||
/* 0x23, '#' */ CharClass::Separator,
|
||||
/* 0x24, '$' */ CharClass::Separator,
|
||||
/* 0x25, '%' */ CharClass::MultiTok,
|
||||
/* 0x26, '&' */ CharClass::MultiTok,
|
||||
/* 0x27, '\'' */ CharClass::Separator,
|
||||
/* 0x28, '(' */ CharClass::Separator,
|
||||
/* 0x29, ')' */ CharClass::Separator,
|
||||
/* 0x2A, '*' */ CharClass::MultiTok,
|
||||
/* 0x2B, '+' */ CharClass::MultiTok,
|
||||
/* 0x2C, ',' */ CharClass::Separator,
|
||||
/* 0x2D, '-' */ CharClass::MultiTok,
|
||||
/* 0x2E, '.' */ CharClass::MultiTok,
|
||||
/* 0x2F, '/' */ CharClass::MultiTok,
|
||||
/* 0x30, '0' */ CharClass::Numeric,
|
||||
/* 0x31, '1' */ CharClass::Numeric,
|
||||
/* 0x32, '2' */ CharClass::Numeric,
|
||||
/* 0x33, '3' */ CharClass::Numeric,
|
||||
/* 0x34, '4' */ CharClass::Numeric,
|
||||
/* 0x35, '5' */ CharClass::Numeric,
|
||||
/* 0x36, '6' */ CharClass::Numeric,
|
||||
/* 0x37, '7' */ CharClass::Numeric,
|
||||
/* 0x38, '8' */ CharClass::Numeric,
|
||||
/* 0x39, '9' */ CharClass::Numeric,
|
||||
/* 0x3A, ':' */ CharClass::Separator,
|
||||
/* 0x3B, ';' */ CharClass::Separator,
|
||||
/* 0x3C, '<' */ CharClass::MultiTok,
|
||||
/* 0x3D, '=' */ CharClass::MultiTok,
|
||||
/* 0x3E, '>' */ CharClass::MultiTok,
|
||||
/* 0x3F, '?' */ CharClass::Separator,
|
||||
|
||||
/* 0x40, '@' */ CharClass::Separator,
|
||||
/* 0x41, 'A' */ CharClass::Alpha,
|
||||
/* 0x42, 'B' */ CharClass::Alpha,
|
||||
/* 0x43, 'C' */ CharClass::Alpha,
|
||||
/* 0x44, 'D' */ CharClass::Alpha,
|
||||
/* 0x45, 'E' */ CharClass::Alpha,
|
||||
/* 0x46, 'F' */ CharClass::Alpha,
|
||||
/* 0x47, 'G' */ CharClass::Alpha,
|
||||
/* 0x48, 'H' */ CharClass::Alpha,
|
||||
/* 0x49, 'I' */ CharClass::Alpha,
|
||||
/* 0x4A, 'J' */ CharClass::Alpha,
|
||||
/* 0x4B, 'K' */ CharClass::Alpha,
|
||||
/* 0x4C, 'L' */ CharClass::Alpha,
|
||||
/* 0x4D, 'M' */ CharClass::Alpha,
|
||||
/* 0x4E, 'N' */ CharClass::Alpha,
|
||||
/* 0x4F, 'O' */ CharClass::Alpha,
|
||||
/* 0x50, 'P' */ CharClass::Alpha,
|
||||
/* 0x51, 'Q' */ CharClass::Alpha,
|
||||
/* 0x52, 'R' */ CharClass::Alpha,
|
||||
/* 0x53, 'S' */ CharClass::Alpha,
|
||||
/* 0x54, 'T' */ CharClass::Alpha,
|
||||
/* 0x55, 'U' */ CharClass::Alpha,
|
||||
/* 0x56, 'V' */ CharClass::Alpha,
|
||||
/* 0x57, 'W' */ CharClass::Alpha,
|
||||
/* 0x58, 'X' */ CharClass::Alpha,
|
||||
/* 0x59, 'Y' */ CharClass::Alpha,
|
||||
/* 0x5A, 'Z' */ CharClass::Alpha,
|
||||
/* 0x5B, '[' */ CharClass::Separator,
|
||||
/* 0x5C, '\\' */ CharClass::Separator,
|
||||
/* 0x5D, ']' */ CharClass::Separator,
|
||||
/* 0x5E, '^' */ CharClass::MultiTok,
|
||||
/* 0x5F, '_' */ CharClass::Alpha, /* Can also start identifiers. */
|
||||
|
||||
/* 0x60, '`' */ CharClass::Separator,
|
||||
/* 0x61, 'a' */ CharClass::Alpha,
|
||||
/* 0x62, 'b' */ CharClass::Alpha,
|
||||
/* 0x63, 'c' */ CharClass::Alpha,
|
||||
/* 0x64, 'd' */ CharClass::Alpha,
|
||||
/* 0x65, 'e' */ CharClass::Alpha,
|
||||
/* 0x66, 'f' */ CharClass::Alpha,
|
||||
/* 0x67, 'g' */ CharClass::Alpha,
|
||||
/* 0x68, 'h' */ CharClass::Alpha,
|
||||
/* 0x69, 'i' */ CharClass::Alpha,
|
||||
/* 0x6A, 'j' */ CharClass::Alpha,
|
||||
/* 0x6B, 'k' */ CharClass::Alpha,
|
||||
/* 0x6C, 'l' */ CharClass::Alpha,
|
||||
/* 0x6D, 'm' */ CharClass::Alpha,
|
||||
/* 0x6E, 'n' */ CharClass::Alpha,
|
||||
/* 0x6F, 'o' */ CharClass::Alpha,
|
||||
/* 0x70, 'p' */ CharClass::Alpha,
|
||||
/* 0x71, 'q' */ CharClass::Alpha,
|
||||
/* 0x72, 'r' */ CharClass::Alpha,
|
||||
/* 0x73, 's' */ CharClass::Alpha,
|
||||
/* 0x74, 't' */ CharClass::Alpha,
|
||||
/* 0x75, 'u' */ CharClass::Alpha,
|
||||
/* 0x76, 'v' */ CharClass::Alpha,
|
||||
/* 0x77, 'w' */ CharClass::Alpha,
|
||||
/* 0x78, 'x' */ CharClass::Alpha,
|
||||
/* 0x79, 'y' */ CharClass::Alpha,
|
||||
/* 0x7A, 'z' */ CharClass::Alpha,
|
||||
/* 0x7B, '{' */ CharClass::Separator,
|
||||
/* 0x7C, '|' */ CharClass::MultiTok,
|
||||
/* 0x7D, '}' */ CharClass::Separator,
|
||||
/* 0x7E, '~' */ CharClass::Separator,
|
||||
/* 0x7F, Del */ CharClass::None,
|
||||
};
|
||||
|
||||
} // namespace lexit
|
||||
161
blender-5.2.0/source/blender/gpu/shader_tool/lexit/types.hh
Normal file
161
blender-5.2.0/source/blender/gpu/shader_tool/lexit/types.hh
Normal file
@@ -0,0 +1,161 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Clement Foucault
|
||||
*
|
||||
* SPDX-License-Identifier: MIT */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace lexit {
|
||||
|
||||
/**
|
||||
* Class for each characters inside the ASCII table.
|
||||
*
|
||||
* The tokenizer identifies runs of characters with similar classes.
|
||||
* A character is grouped with its predecessor if it shares a class.
|
||||
* The Separator class is the exception which never group chars together.
|
||||
*
|
||||
* Note: The values were chosen to allow fast comparison, masking, and cast to printable TokenType.
|
||||
*/
|
||||
enum class CharClass : uint8_t {
|
||||
/* Will decay into single char token. */
|
||||
None = 0,
|
||||
/* Will decay into single char of the token. */
|
||||
Separator = (1 << 1),
|
||||
/* Will decay into the first char of the token. */
|
||||
MultiTok = (1 << 2),
|
||||
WhiteSpace = (1 << 3),
|
||||
/* Will decay into Word. Can start an identifier. */
|
||||
Alpha = 'A', /* 0b01000001 */
|
||||
/* Will decay into Number. Can continue an identifier. */
|
||||
Numeric = '1', /* 0b00110001 */
|
||||
|
||||
/* These classes will merge characters together. */
|
||||
CanMerge = Alpha | Numeric | MultiTok | WhiteSpace,
|
||||
/* Classes above this value will cast to TokenType instead of using the character. */
|
||||
ClassToTypeThreshold = Numeric - 1,
|
||||
};
|
||||
|
||||
/* Make sure to declare this enum as being a char.
|
||||
* This is allow casting to string possible. */
|
||||
enum TokenType : uint8_t {
|
||||
Invalid = 0,
|
||||
Word = TokenType(CharClass::Alpha),
|
||||
Number = TokenType(CharClass::Numeric),
|
||||
/* Use printable ascii chars to store them in string, and for easy debugging / testing. */
|
||||
NewLine = '\n',
|
||||
Space = ' ',
|
||||
Dot = '.',
|
||||
Hash = '#',
|
||||
Ampersand = '&',
|
||||
DoubleQuote = '"',
|
||||
SingleQuote = '\'',
|
||||
ParOpen = '(',
|
||||
ParClose = ')',
|
||||
BracketOpen = '{',
|
||||
BracketClose = '}',
|
||||
SquareOpen = '[',
|
||||
SquareClose = ']',
|
||||
AngleOpen = '<',
|
||||
AngleClose = '>',
|
||||
Assign = '=',
|
||||
SemiColon = ';',
|
||||
Question = '?',
|
||||
Not = '!',
|
||||
Colon = ':',
|
||||
Comma = ',',
|
||||
Star = '*',
|
||||
Plus = '+',
|
||||
Minus = '-',
|
||||
Divide = '/',
|
||||
Tilde = '~',
|
||||
Caret = '^',
|
||||
Pipe = '|',
|
||||
Percent = '%',
|
||||
Backslash = '\\',
|
||||
/* Mark end of stream. */
|
||||
EndOfFile = '\0',
|
||||
|
||||
/* --- Keywords --- */
|
||||
|
||||
LogicalAnd = 'a',
|
||||
// Word = 'A',
|
||||
Break = 'b',
|
||||
// Unused = 'B',
|
||||
Const = 'c',
|
||||
Constexpr = 'C',
|
||||
Do = 'd',
|
||||
Decrement = 'D',
|
||||
NotEqual = 'e',
|
||||
Equal = 'E',
|
||||
For = 'f',
|
||||
While = 'F',
|
||||
LogicalOr = 'g',
|
||||
GEqual = 'G',
|
||||
Switch = 'h',
|
||||
Case = 'H',
|
||||
If = 'i',
|
||||
Else = 'I',
|
||||
Elif = 'j',
|
||||
Endif = 'J',
|
||||
Ifdef = 'k',
|
||||
Ifndef = 'K',
|
||||
Inline = 'l',
|
||||
LEqual = 'L',
|
||||
Static = 'm',
|
||||
Enum = 'M',
|
||||
Namespace = 'n',
|
||||
Define = 'N',
|
||||
Union = 'o',
|
||||
Continue = 'O',
|
||||
Line = 'p',
|
||||
Increment = 'P',
|
||||
Pragma = 'q',
|
||||
DoubleHash = 'Q',
|
||||
Return = 'r',
|
||||
// Unused = 'R',
|
||||
Struct = 's',
|
||||
Class = 'S',
|
||||
Template = 't',
|
||||
This = 'T',
|
||||
Using = 'u',
|
||||
Undef = 'U',
|
||||
Private = 'v',
|
||||
Public = 'V',
|
||||
// Unused = 'w',
|
||||
// Unused = 'W',
|
||||
// Unused = 'x',
|
||||
// Unused = 'X',
|
||||
// Unused = 'y',
|
||||
TemplateOpen = 'Y',
|
||||
TemplateClose = 'z',
|
||||
Comment = 'Z',
|
||||
// Number = '0',
|
||||
// Unused = '1',
|
||||
// Unused = '2',
|
||||
// Unused = '3',
|
||||
// Unused = '4',
|
||||
// Unused = '5',
|
||||
// Unused = '6',
|
||||
// Unused = '7',
|
||||
// Unused = '8',
|
||||
// Unused = '9',
|
||||
|
||||
/* Aliases. */
|
||||
Multiply = Star,
|
||||
And = Ampersand,
|
||||
Or = Pipe,
|
||||
Xor = Caret,
|
||||
GThan = AngleClose,
|
||||
LThan = AngleOpen,
|
||||
BitwiseNot = Tilde,
|
||||
Modulo = Percent,
|
||||
|
||||
String = DoubleQuote,
|
||||
Slash = Divide,
|
||||
};
|
||||
|
||||
/* Unique identifier to a word token. */
|
||||
using TokenAtom = uint16_t;
|
||||
|
||||
} // namespace lexit
|
||||
145
blender-5.2.0/source/blender/gpu/shader_tool/lexit/vector.hh
Normal file
145
blender-5.2.0/source/blender/gpu/shader_tool/lexit/vector.hh
Normal file
@@ -0,0 +1,145 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Clement Foucault
|
||||
*
|
||||
* SPDX-License-Identifier: MIT */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#ifndef NDEBUG
|
||||
# include <span>
|
||||
#endif
|
||||
|
||||
namespace lexit {
|
||||
|
||||
template<typename T> class AlignedArrayPtr {
|
||||
private:
|
||||
T *ptr = nullptr;
|
||||
|
||||
public:
|
||||
AlignedArrayPtr() = default;
|
||||
|
||||
AlignedArrayPtr(int size)
|
||||
{
|
||||
ptr = static_cast<T *>(operator new[](sizeof(T) * size, std::align_val_t{64}));
|
||||
}
|
||||
|
||||
AlignedArrayPtr(const AlignedArrayPtr &other) = delete;
|
||||
|
||||
AlignedArrayPtr(AlignedArrayPtr &&other) : ptr(other.ptr)
|
||||
{
|
||||
other.ptr = nullptr;
|
||||
}
|
||||
|
||||
~AlignedArrayPtr()
|
||||
{
|
||||
operator delete[](ptr, std::align_val_t{64});
|
||||
}
|
||||
|
||||
AlignedArrayPtr &operator=(AlignedArrayPtr &&other)
|
||||
{
|
||||
if (this != &other) {
|
||||
operator delete[](ptr, std::align_val_t{64});
|
||||
ptr = other.ptr;
|
||||
other.ptr = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
T *get()
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
const T *get() const
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
T &operator[](int i)
|
||||
{
|
||||
return ptr[i];
|
||||
}
|
||||
const T &operator[](int i) const
|
||||
{
|
||||
return ptr[i];
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct Vector {
|
||||
private:
|
||||
std::unique_ptr<T[]> data_;
|
||||
int size_ = 0;
|
||||
int alloc_size_ = 0;
|
||||
#ifndef NDEBUG
|
||||
std::span<T> debug_view_;
|
||||
#endif
|
||||
|
||||
public:
|
||||
void reserve(int new_size)
|
||||
{
|
||||
if ((alloc_size_ >= new_size) && data_) {
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<T[]> new_ptr(new T[new_size]);
|
||||
if (data_) {
|
||||
std::memcpy(new_ptr.get(), data_.get(), alloc_size_ * sizeof(T));
|
||||
}
|
||||
data_ = std::move(new_ptr);
|
||||
alloc_size_ = new_size;
|
||||
#ifndef NDEBUG
|
||||
debug_view_ = std::span<T>(data_.get(), size_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void resize(int new_size)
|
||||
{
|
||||
reserve(new_size);
|
||||
size_ = new_size;
|
||||
#ifndef NDEBUG
|
||||
debug_view_ = std::span<T>(data_.get(), size_);
|
||||
#endif
|
||||
}
|
||||
|
||||
T *data()
|
||||
{
|
||||
return data_.get();
|
||||
}
|
||||
const T *data() const
|
||||
{
|
||||
return data_.get();
|
||||
}
|
||||
|
||||
T &operator[](int i)
|
||||
{
|
||||
return data_.get()[i];
|
||||
}
|
||||
const T &operator[](int i) const
|
||||
{
|
||||
return data_.get()[i];
|
||||
}
|
||||
|
||||
T *end()
|
||||
{
|
||||
return data_.get() + size_;
|
||||
}
|
||||
const T *end() const
|
||||
{
|
||||
return data_.get() + size_;
|
||||
}
|
||||
|
||||
int size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
void increase_size_by_unchecked(int elem_count)
|
||||
{
|
||||
assert(size_ + elem_count <= alloc_size_);
|
||||
size_ += elem_count;
|
||||
#ifndef NDEBUG
|
||||
debug_view_ = std::span<T>(data_.get(), size_);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lexit
|
||||
277
blender-5.2.0/source/blender/gpu/shader_tool/metadata.cc
Normal file
277
blender-5.2.0/source/blender/gpu/shader_tool/metadata.cc
Normal file
@@ -0,0 +1,277 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include "metadata.hh"
|
||||
|
||||
namespace blender::gpu::shader::metadata {
|
||||
|
||||
std::string ParsedResource::serialize() const
|
||||
{
|
||||
std::string res_condition_lambda;
|
||||
|
||||
if (!res_condition.empty()) {
|
||||
res_condition_lambda = ", [](blender::Span<CompilationConstant> constants) { ";
|
||||
res_condition_lambda += res_condition;
|
||||
res_condition_lambda += "}";
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
if (res_type == "legacy_info") {
|
||||
ss << "ADDITIONAL_INFO(" << var_name << ")";
|
||||
}
|
||||
else if (res_type == "resource_table") {
|
||||
if (!res_condition.empty()) {
|
||||
ss << ".additional_info_with_condition(\"" << var_type << "\"" << res_condition_lambda
|
||||
<< ")";
|
||||
}
|
||||
else {
|
||||
ss << ".additional_info(\"" << var_type << "\")";
|
||||
}
|
||||
}
|
||||
else if (res_type == "sampler") {
|
||||
ss << ".sampler(" << res_slot;
|
||||
ss << ", ImageType::" << var_type;
|
||||
ss << ", \"" << var_name << "\"";
|
||||
ss << ", Frequency::" << res_frequency;
|
||||
ss << ", GPUSamplerState::internal_sampler()";
|
||||
ss << res_condition_lambda << ")";
|
||||
}
|
||||
else if (res_type == "image") {
|
||||
ss << ".image(" << res_slot;
|
||||
ss << ", blender::gpu::TextureFormat::" << res_format;
|
||||
ss << ", Qualifier::" << res_qualifier;
|
||||
ss << ", ImageReadWriteType::" << var_type;
|
||||
ss << ", \"" << var_name << "\"";
|
||||
ss << ", Frequency::" << res_frequency;
|
||||
ss << res_condition_lambda << ")";
|
||||
}
|
||||
else if (res_type == "uniform") {
|
||||
ss << ".uniform_buf(" << res_slot;
|
||||
ss << ", \"" << var_type << "\"";
|
||||
ss << ", \"" << var_name << var_array << "\"";
|
||||
ss << ", Frequency::" << res_frequency;
|
||||
ss << res_condition_lambda << ")";
|
||||
}
|
||||
else if (res_type == "storage") {
|
||||
ss << ".storage_buf(" << res_slot;
|
||||
ss << ", Qualifier::" << res_qualifier;
|
||||
ss << ", \"" << var_type << "\"";
|
||||
ss << ", \"" << var_name << var_array << "\"";
|
||||
ss << ", Frequency::" << res_frequency;
|
||||
ss << res_condition_lambda << ")";
|
||||
}
|
||||
else if (res_type == "shared") {
|
||||
ss << "GROUP_SHARED(" << var_type << ", " << var_name << var_array << ")";
|
||||
}
|
||||
else if (res_type == "push_constant") {
|
||||
if (!var_array.empty()) {
|
||||
ss << "PUSH_CONSTANT_ARRAY(" << var_type << ", " << var_name << ", "
|
||||
<< var_array.substr(1, var_array.size() - 2) << ")";
|
||||
}
|
||||
else {
|
||||
ss << "PUSH_CONSTANT(" << var_type << ", " << var_name << ")";
|
||||
}
|
||||
}
|
||||
else if (res_type == "compilation_constant") {
|
||||
/* Needs to be defined on the shader declaration. */
|
||||
/* TODO(fclem): Add check that shader sets an existing compilation constant. */
|
||||
// ss << "COMPILATION_CONSTANT(" << var_type << ", " << var_name << ", " << res_value << ")";
|
||||
/* WORKAROUND: Avoid unused expression warning. */
|
||||
ss << ".noop()\n";
|
||||
}
|
||||
else if (res_type == "specialization_constant") {
|
||||
ss << "SPECIALIZATION_CONSTANT(" << var_type << ", " << var_name << ", " << res_value << ")";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ParsedAttribute::serialize() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (interpolation_mode == "flat") {
|
||||
ss << "FLAT(" << var_type << ", " << var_name << ")";
|
||||
}
|
||||
else if (interpolation_mode == "smooth") {
|
||||
ss << "SMOOTH(" << var_type << ", " << var_name << ")";
|
||||
}
|
||||
else if (interpolation_mode == "no_perspective") {
|
||||
ss << "NO_PERSPECTIVE(" << var_type << ", " << var_name << ")";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string StageInterface::serialize() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "GPU_SHADER_INTERFACE_INFO(" << name << "_t)\n";
|
||||
|
||||
for (const auto &res : *this) {
|
||||
ss << res.serialize() << "\n";
|
||||
}
|
||||
|
||||
ss << "GPU_SHADER_INTERFACE_END()\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ParsedFragOuput::serialize() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (!dual_source.empty()) {
|
||||
ss << "FRAGMENT_OUT_DUAL(" << slot << ", " << var_type << ", " << var_name << ", "
|
||||
<< "SRC_" << dual_source << ")";
|
||||
}
|
||||
else if (!raster_order_group.empty()) {
|
||||
ss << "FRAGMENT_OUT_ROG(" << slot << ", " << var_type << ", " << var_name << ", "
|
||||
<< raster_order_group << ")";
|
||||
}
|
||||
else {
|
||||
ss << "FRAGMENT_OUT(" << slot << ", " << var_type << ", " << var_name << ")";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ParsedFragInput::serialize() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SUBPASS_IN(" << slot << ", " << var_type << ", " << image_type << ", " << var_name << ", "
|
||||
<< raster_order_group << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string FragmentOutputs::serialize() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "GPU_SHADER_CREATE_INFO(" << name << ")\n";
|
||||
|
||||
for (const auto &res : *this) {
|
||||
ss << res.serialize() << "\n";
|
||||
}
|
||||
|
||||
ss << "GPU_SHADER_CREATE_END()\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string FragmentInputs::serialize() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "GPU_SHADER_CREATE_INFO(" << name << ")\n";
|
||||
|
||||
for (const auto &res : *this) {
|
||||
ss << res.serialize() << "\n";
|
||||
}
|
||||
|
||||
ss << "GPU_SHADER_CREATE_END()\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ParsedVertInput::serialize() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "VERTEX_IN(" << slot << ", " << var_type << ", " << var_name << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string VertexInputs::serialize() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "GPU_SHADER_CREATE_INFO(" << name << ")\n";
|
||||
|
||||
for (const auto &res : *this) {
|
||||
ss << res.serialize() << "\n";
|
||||
}
|
||||
|
||||
ss << "GPU_SHADER_CREATE_END()\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string Source::serialize(const std::string &function_name) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "static void " << function_name
|
||||
<< "(GPUSource &source, GPUFunctionDictionary *g_functions, GPUPrintFormatMap *g_formats) "
|
||||
"{\n";
|
||||
for (auto function : functions) {
|
||||
ss << " {\n";
|
||||
ss << " Vector<metadata::ArgumentFormat> args = {\n";
|
||||
for (auto arg : function.arguments) {
|
||||
ss << " "
|
||||
<< "metadata::ArgumentFormat{"
|
||||
<< "metadata::Qualifier(" << std::to_string(uint64_t(arg.qualifier)) << "LLU), "
|
||||
<< "metadata::Type(" << std::to_string(uint64_t(arg.type)) << "LLU)"
|
||||
<< "},\n";
|
||||
}
|
||||
ss << " };\n";
|
||||
ss << " source.add_function(\"" << function.name << "\", args, g_functions);\n";
|
||||
ss << " }\n";
|
||||
}
|
||||
for (auto builtin : builtins) {
|
||||
ss << " source.add_builtin(metadata::Builtin(" << std::to_string(builtin) << "LLU));\n";
|
||||
}
|
||||
for (auto dependency : dependencies) {
|
||||
ss << " source.add_dependency(\"" << dependency << "\");\n";
|
||||
}
|
||||
for (auto var : shared_variables) {
|
||||
ss << " source.add_shared_variable(Type::" << var.type << "_t, \"" << var.name << "\");\n";
|
||||
}
|
||||
for (auto format : printf_formats) {
|
||||
ss << " source.add_printf_format(uint32_t(" << std::to_string(format.hash) << "), "
|
||||
<< format.format << ", g_formats);\n";
|
||||
}
|
||||
/* Avoid warnings. */
|
||||
ss << " UNUSED_VARS(source, g_functions, g_formats);\n";
|
||||
ss << "}\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string Source::serialize_infos() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "#pragma once\n";
|
||||
ss << "\n";
|
||||
for (auto dependency : create_infos_dependencies) {
|
||||
ss << "#include \"" << dependency << "\"\n";
|
||||
}
|
||||
ss << "\n";
|
||||
for (auto define : create_infos_defines) {
|
||||
ss << define;
|
||||
}
|
||||
ss << "\n";
|
||||
for (auto vert_inputs : vertex_inputs) {
|
||||
ss << vert_inputs.serialize() << "\n";
|
||||
}
|
||||
ss << "\n";
|
||||
for (auto frag_outputs : fragment_outputs) {
|
||||
ss << frag_outputs.serialize() << "\n";
|
||||
}
|
||||
for (auto frag_inputs : fragment_inputs) {
|
||||
ss << frag_inputs.serialize() << "\n";
|
||||
}
|
||||
ss << "\n";
|
||||
for (auto iface : stage_interfaces) {
|
||||
ss << iface.serialize() << "\n";
|
||||
}
|
||||
ss << "\n";
|
||||
for (auto res_table : resource_tables) {
|
||||
ss << "GPU_SHADER_CREATE_INFO(" << res_table.name << ")\n";
|
||||
|
||||
if (res_table.empty()) {
|
||||
/* Add unused define to avoid warning about unused expression. */
|
||||
ss << "DEFINE(\"EMPTY_CREATE_INFO\")\n";
|
||||
}
|
||||
for (const auto &res : res_table) {
|
||||
ss << res.serialize() << "\n";
|
||||
}
|
||||
ss << "GPU_SHADER_CREATE_END()\n";
|
||||
}
|
||||
ss << "\n";
|
||||
for (auto declaration : create_infos_declarations) {
|
||||
ss << declaration << "\n";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
} // namespace blender::gpu::shader::metadata
|
||||
286
blender-5.2.0/source/blender/gpu/shader_tool/metadata.hh
Normal file
286
blender-5.2.0/source/blender/gpu/shader_tool/metadata.hh
Normal file
@@ -0,0 +1,286 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/* Metadata extracted from shader source file.
|
||||
* These are then converted to their GPU module equivalent. */
|
||||
/* TODO(fclem): Make GPU enums standalone and directly use them instead of using separate enums
|
||||
* and types. */
|
||||
namespace blender::gpu::shader::metadata {
|
||||
|
||||
/* Compile-time hashing function which converts string to a 64bit hash. */
|
||||
constexpr static uint64_t hash(const char *name)
|
||||
{
|
||||
uint64_t hash = 2166136261u;
|
||||
while (*name) {
|
||||
hash = hash * 16777619u;
|
||||
hash = hash ^ *name;
|
||||
++name;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static inline uint64_t hash(const std::string &name)
|
||||
{
|
||||
return hash(name.c_str());
|
||||
}
|
||||
|
||||
enum Builtin : uint64_t {
|
||||
ClipDistance = hash("gl_ClipDistance"),
|
||||
FragCoord = hash("gl_FragCoord"),
|
||||
FragStencilRef = hash("gl_FragStencilRefARB"),
|
||||
FrontFacing = hash("gl_FrontFacing"),
|
||||
GlobalInvocationID = hash("gl_GlobalInvocationID"),
|
||||
InstanceIndex = hash("gpu_InstanceIndex"),
|
||||
BaseInstance = hash("gpu_BaseInstance"),
|
||||
InstanceID = hash("gl_InstanceID"),
|
||||
LocalInvocationID = hash("gl_LocalInvocationID"),
|
||||
LocalInvocationIndex = hash("gl_LocalInvocationIndex"),
|
||||
NumWorkGroup = hash("gl_NumWorkGroup"),
|
||||
PointCoord = hash("gl_PointCoord"),
|
||||
PointSize = hash("gl_PointSize"),
|
||||
PrimitiveID = hash("gl_PrimitiveID"),
|
||||
VertexID = hash("gl_VertexID"),
|
||||
WorkGroupID = hash("gl_WorkGroupID"),
|
||||
WorkGroupSize = hash("gl_WorkGroupSize"),
|
||||
drw_debug = hash("drw_debug_"),
|
||||
printf = hash("printf"),
|
||||
assert = hash("assert"),
|
||||
runtime_generated = hash("runtime_generated"),
|
||||
};
|
||||
|
||||
enum Qualifier : uint64_t {
|
||||
in = hash("in"),
|
||||
out = hash("out"),
|
||||
inout = hash("inout"),
|
||||
};
|
||||
|
||||
enum Type : uint64_t {
|
||||
float1 = hash("float"),
|
||||
float2 = hash("float2"),
|
||||
float3 = hash("float3"),
|
||||
float4 = hash("float4"),
|
||||
float3x3 = hash("float3x3"),
|
||||
float4x4 = hash("float4x4"),
|
||||
sampler1DArray = hash("sampler1DArray"),
|
||||
sampler2DArray = hash("sampler2DArray"),
|
||||
sampler2D = hash("sampler2D"),
|
||||
sampler3D = hash("sampler3D"),
|
||||
Closure = hash("Closure"),
|
||||
};
|
||||
|
||||
struct ArgumentFormat {
|
||||
Qualifier qualifier;
|
||||
Type type;
|
||||
};
|
||||
|
||||
struct FunctionFormat {
|
||||
std::string name;
|
||||
std::vector<ArgumentFormat> arguments;
|
||||
};
|
||||
|
||||
struct PrintfFormat {
|
||||
uint32_t hash;
|
||||
std::string format;
|
||||
};
|
||||
|
||||
struct SharedVariable {
|
||||
std::string type;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct ParsedResource {
|
||||
/** Line this resource was defined. */
|
||||
size_t line;
|
||||
|
||||
std::string var_type;
|
||||
std::string var_name;
|
||||
std::string var_array;
|
||||
|
||||
std::string res_type;
|
||||
/** For images, storage, uniforms and samplers. */
|
||||
std::string res_frequency = "PASS";
|
||||
/** For images, storage, uniforms and samplers. */
|
||||
std::string res_slot;
|
||||
/** For images & storage. */
|
||||
std::string res_qualifier;
|
||||
/** For specialization & compilation constants. */
|
||||
std::string res_value;
|
||||
/** For images. */
|
||||
std::string res_format;
|
||||
/** Optional condition to enable this resource. */
|
||||
std::string res_condition;
|
||||
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
struct ResourceTable : std::vector<ParsedResource> {
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct ParsedAttribute {
|
||||
/* Line this resource was defined. */
|
||||
size_t line;
|
||||
|
||||
std::string var_type;
|
||||
std::string var_name;
|
||||
|
||||
std::string interpolation_mode;
|
||||
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
struct StageInterface : std::vector<ParsedAttribute> {
|
||||
std::string name;
|
||||
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
struct ParsedFragOuput {
|
||||
/* Line this resource was defined. */
|
||||
size_t line;
|
||||
|
||||
std::string var_type;
|
||||
std::string var_name;
|
||||
|
||||
std::string slot;
|
||||
std::string dual_source;
|
||||
std::string raster_order_group;
|
||||
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
struct FragmentOutputs : std::vector<ParsedFragOuput> {
|
||||
std::string name;
|
||||
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
struct ParsedFragInput {
|
||||
/* Line this resource was defined. */
|
||||
size_t line;
|
||||
|
||||
std::string var_type;
|
||||
std::string var_name;
|
||||
|
||||
std::string slot;
|
||||
std::string image_type;
|
||||
std::string raster_order_group;
|
||||
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
struct FragmentInputs : std::vector<ParsedFragInput> {
|
||||
std::string name;
|
||||
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
struct ParsedVertInput {
|
||||
/* Line this resource was defined. */
|
||||
size_t line;
|
||||
|
||||
std::string var_type;
|
||||
std::string var_name;
|
||||
|
||||
std::string slot;
|
||||
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
struct VertexInputs : std::vector<ParsedVertInput> {
|
||||
std::string name;
|
||||
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
struct TemplateDefinition {
|
||||
std::string identifier;
|
||||
std::string name_space;
|
||||
std::string definition;
|
||||
std::string filepath;
|
||||
size_t definition_line;
|
||||
bool is_method;
|
||||
bool is_static;
|
||||
bool is_struct;
|
||||
|
||||
bool operator==(const TemplateDefinition &other) const
|
||||
{
|
||||
return std::tie(identifier, name_space) == std::tie(other.identifier, other.name_space);
|
||||
}
|
||||
};
|
||||
|
||||
struct Symbol {
|
||||
std::string identifier;
|
||||
std::string name_space;
|
||||
size_t definition_line;
|
||||
bool is_method;
|
||||
bool is_static;
|
||||
bool is_struct;
|
||||
/* For structures only. */
|
||||
std::vector<std::pair<std::string, std::string>> members;
|
||||
|
||||
bool operator<(const Symbol &other) const
|
||||
{
|
||||
if (is_static != other.is_static) {
|
||||
return is_static > other.is_static;
|
||||
}
|
||||
if (is_method != other.is_method) {
|
||||
/* Methods are supposed to have more precedence.
|
||||
* So make them smaller than anything else. */
|
||||
return is_method > other.is_method;
|
||||
}
|
||||
if (name_space != other.name_space) {
|
||||
return name_space > other.name_space;
|
||||
}
|
||||
if (definition_line != other.definition_line) {
|
||||
return definition_line < other.definition_line;
|
||||
}
|
||||
if (identifier != other.identifier) {
|
||||
return identifier < other.identifier;
|
||||
}
|
||||
if (is_struct != other.is_struct) {
|
||||
return is_struct < other.is_struct;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct Source {
|
||||
std::vector<Builtin> builtins;
|
||||
/* Note: Could be a set, but for now the order matters. */
|
||||
std::vector<std::string> dependencies;
|
||||
std::vector<SharedVariable> shared_variables;
|
||||
std::vector<PrintfFormat> printf_formats;
|
||||
std::vector<FunctionFormat> functions;
|
||||
std::vector<std::string> create_infos;
|
||||
std::vector<std::string> create_infos_declarations;
|
||||
std::vector<std::string> create_infos_dependencies;
|
||||
std::vector<std::string> create_infos_defines;
|
||||
std::vector<ResourceTable> resource_tables;
|
||||
std::vector<StageInterface> stage_interfaces;
|
||||
std::vector<FragmentOutputs> fragment_outputs;
|
||||
std::vector<FragmentInputs> fragment_inputs;
|
||||
std::vector<VertexInputs> vertex_inputs;
|
||||
std::vector<Symbol> symbol_table;
|
||||
std::vector<TemplateDefinition> template_definitions;
|
||||
|
||||
/* Serialize Metadata for this source file. */
|
||||
std::string serialize(const std::string &function_name) const;
|
||||
/* Serialize Create Infos for this source file. */
|
||||
std::string serialize_infos() const;
|
||||
};
|
||||
|
||||
} // namespace blender::gpu::shader::metadata
|
||||
414
blender-5.2.0/source/blender/gpu/shader_tool/namespace.cc
Normal file
414
blender-5.2.0/source/blender/gpu/shader_tool/namespace.cc
Normal file
@@ -0,0 +1,414 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
static string get_prefix(Scope ns_scope)
|
||||
{
|
||||
string prefix;
|
||||
while (ns_scope.type() == ScopeType::Namespace || ns_scope.type() == ScopeType::Struct) {
|
||||
prefix = ns_scope.front().prev().full_symbol_name() + "::" + prefix;
|
||||
ns_scope = ns_scope.scope();
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
TemplateDefinition SourceProcessor::parse_template_definition(SourceProcessor::Parser &parser,
|
||||
Token template_tok,
|
||||
bool is_method,
|
||||
Scope ns_scope,
|
||||
const std::string &filepath)
|
||||
{
|
||||
Token def_start = template_tok;
|
||||
Scope template_args = def_start.next().scope();
|
||||
/* Skip arguments. */
|
||||
Token tok_type = template_args.back().next();
|
||||
|
||||
Token body_start = template_tok.find_next(BracketOpen);
|
||||
Token def_end = body_start.scope().back();
|
||||
|
||||
TemplateDefinition symbol;
|
||||
symbol.filepath = filepath;
|
||||
symbol.definition_line = tok_type.line_number();
|
||||
symbol.is_method = is_method;
|
||||
symbol.is_static = tok_type == Static;
|
||||
symbol.is_struct = tok_type == Struct || tok_type == Class;
|
||||
symbol.name_space = get_prefix(ns_scope);
|
||||
|
||||
if (symbol.is_struct) {
|
||||
Token name = body_start.prev();
|
||||
symbol.identifier = string(name.str());
|
||||
}
|
||||
else {
|
||||
Token fn_args = body_start.prev() == Const ? body_start.prev(2) : body_start.prev();
|
||||
Token fn_name = fn_args.scope().front().prev();
|
||||
symbol.identifier = string(fn_name.str());
|
||||
}
|
||||
|
||||
/* Capture end semicolon for structs. */
|
||||
def_end = (symbol.is_struct) ? def_end.next() : def_end;
|
||||
symbol.definition = parser.substr(def_start, def_end);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
void SourceProcessor::parse_namespace_symbols(SourceProcessor::Parser &parser,
|
||||
Scope ns,
|
||||
metadata::Source &metadata,
|
||||
const std::string &filepath)
|
||||
{
|
||||
ns.foreach_scope(ScopeType::Namespace, [&](const Scope &ns) {
|
||||
parse_namespace_symbols(parser, ns, metadata, filepath);
|
||||
});
|
||||
|
||||
auto process_symbol = [&](Scope ns_scope,
|
||||
Token name,
|
||||
string_view identifier,
|
||||
size_t line,
|
||||
bool is_method,
|
||||
bool is_static,
|
||||
bool is_struct,
|
||||
std::vector<std::pair<std::string, std::string>> members = {}) {
|
||||
if (name.scope() != ns_scope) {
|
||||
return;
|
||||
}
|
||||
Symbol symbol;
|
||||
symbol.name_space = get_prefix(ns_scope);
|
||||
symbol.identifier = identifier;
|
||||
symbol.definition_line = line;
|
||||
symbol.is_method = is_method;
|
||||
symbol.is_static = is_static;
|
||||
symbol.is_struct = is_struct;
|
||||
symbol.members = members;
|
||||
metadata.symbol_table.emplace_back(symbol);
|
||||
};
|
||||
|
||||
auto process_templates = [&](Scope ns_scope, Token t, bool is_method) {
|
||||
if (t.scope() != ns_scope) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (t.next() == '<') {
|
||||
if (t.next(2) == '>') {
|
||||
/* Template specialization. */
|
||||
return;
|
||||
}
|
||||
TemplateDefinition symbol = SourceProcessor::parse_template_definition(
|
||||
parser, t, is_method, ns_scope, filepath);
|
||||
metadata.template_definitions.emplace_back(symbol);
|
||||
return;
|
||||
}
|
||||
/* Line number of the instantiation should be the one of the definition.
|
||||
* But it is very hard at this point to search for the definition.
|
||||
* Instead we consider the instantiation to be at the top of the file.
|
||||
* It is unlikely we will have name collision with an instantiated template. */
|
||||
size_t line = 0;
|
||||
if (t.next() == Struct || t.next() == Class) {
|
||||
/* Struct. */
|
||||
Token name = t.next().next();
|
||||
Scope template_args = name.next().scope();
|
||||
string resolved_name = string(name.str()) +
|
||||
SourceProcessor::template_arguments_mangle(template_args);
|
||||
process_symbol(ns_scope, name, resolved_name, line, false, false, true, {});
|
||||
}
|
||||
else {
|
||||
/* Function. */
|
||||
Token end = t.find_next(SemiColon);
|
||||
Scope template_args = end.prev().scope().front().prev().scope();
|
||||
Token name = template_args.front().prev();
|
||||
string resolved_name = string(name.str()) +
|
||||
SourceProcessor::template_arguments_mangle(template_args);
|
||||
process_symbol(ns_scope, name, resolved_name, line, is_method, false, false);
|
||||
}
|
||||
};
|
||||
|
||||
ns.foreach_struct([&](Token, Scope, Token struct_name, Scope body) {
|
||||
/* Parse member. */
|
||||
std::vector<std::pair<std::string, std::string>> members;
|
||||
body.foreach_declaration([&](Scope, Token, Token type, Scope, Token name, Scope, Token) {
|
||||
/* For methods, the declaration line is the top of the struct. */
|
||||
members.emplace_back(type.str(), name.str());
|
||||
});
|
||||
process_symbol(ns,
|
||||
struct_name,
|
||||
struct_name.str(),
|
||||
struct_name.line_number(),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
members);
|
||||
/* Methods. */
|
||||
body.foreach_function([&](bool is_static, Token, Token name, Scope, bool, Scope) {
|
||||
/* For methods, the declaration line is the top of the struct. */
|
||||
process_symbol(body, name, name.str(), struct_name.line_number(), true, is_static, false);
|
||||
});
|
||||
/* Parse template instantiations. */
|
||||
body.foreach_token(Template, [&](Token t) { process_templates(body, t, true); });
|
||||
});
|
||||
|
||||
ns.foreach_function([&](bool, Token, Token name, Scope, bool, Scope) {
|
||||
process_symbol(ns, name, name.str(), name.line_number(), false, false, false);
|
||||
});
|
||||
/* Parse template instantiations. */
|
||||
ns.foreach_token(Template, [&](Token t) { process_templates(ns, t, false); });
|
||||
}
|
||||
|
||||
void SourceProcessor::parse_local_symbols(Parser &parser)
|
||||
{
|
||||
parse_namespace_symbols(parser, parser(), metadata_, filepath_);
|
||||
}
|
||||
|
||||
static void lower_namespace(string ns_prefix,
|
||||
const Scope &scope,
|
||||
SourceProcessor::Parser &parser,
|
||||
ErrorHandler &error_handler,
|
||||
const set<Symbol> &symbols_set)
|
||||
{
|
||||
string ns_name(scope.front().prev().str());
|
||||
ns_prefix += ns_name + "::";
|
||||
|
||||
bool has_nested_scope = false;
|
||||
scope.foreach_scope(ScopeType::Namespace, [&](const Scope &scope) {
|
||||
lower_namespace(ns_prefix, scope, parser, error_handler, symbols_set);
|
||||
has_nested_scope = true;
|
||||
});
|
||||
|
||||
if (has_nested_scope) {
|
||||
/* Process iteratively. */
|
||||
return;
|
||||
}
|
||||
|
||||
scope.foreach_token(Word, [&](const Token &token) {
|
||||
/* Reject method calls. */
|
||||
if (token.prev() == '.') {
|
||||
return;
|
||||
}
|
||||
Token next = token.next();
|
||||
/* Only process the end token of a namespace qualified identifier. */
|
||||
if (next == ':') {
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_pipeline_arg = false;
|
||||
if (token.scope().type() == ScopeType::FunctionArg) {
|
||||
std::string_view type = token.scope().scope().front().prev(2).str();
|
||||
is_pipeline_arg = (type == "PipelineGraphic" || type == "PipelineCompute");
|
||||
}
|
||||
|
||||
if (is_pipeline_arg) {
|
||||
/* Special case for pipelines. We need to match the function names but they are arguments. */
|
||||
}
|
||||
/* Only process function calls or types. */
|
||||
else if (next != '<' && /* Templated type or function. */
|
||||
next != '{' && /* Type definition. */
|
||||
next != '&' && /* Reference definition. */
|
||||
next != Word && /* Variable definition. */
|
||||
token.scope().type() != parser::ScopeType::TemplateArg && /* Template argument. */
|
||||
next != '(' /* Function call or reference definition. */
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_fn = (token.next() == '(') ||
|
||||
(token.next() == '<' && token.next().scope().back().next() == '(');
|
||||
/* Reject method definition. */
|
||||
if (is_fn && token.scope().type() == ScopeType::Struct) {
|
||||
return;
|
||||
}
|
||||
|
||||
string struct_name;
|
||||
if (is_fn) {
|
||||
/* If this is function call inside a struct, this could reference a method.
|
||||
* In this case we need to add the struct name during the fully qualified name lookup. */
|
||||
const Scope struct_scope = token.scope().first_scope_of_type(ScopeType::Struct);
|
||||
if (struct_scope.is_valid()) {
|
||||
struct_name = struct_scope.front().prev().full_symbol_name();
|
||||
}
|
||||
}
|
||||
|
||||
const int token_line = token.line_number();
|
||||
|
||||
for (const auto &symbol : symbols_set) {
|
||||
if (token.str() != symbol.identifier) {
|
||||
continue;
|
||||
}
|
||||
/* Only expand symbols that are visible inside this namespace. */
|
||||
if (!symbol.name_space.starts_with(ns_prefix)) {
|
||||
continue;
|
||||
}
|
||||
/* Reject symbols declared after the identifier.
|
||||
* Note that static method have their definition line at the top of the struct. */
|
||||
if (token_line < symbol.definition_line) {
|
||||
continue;
|
||||
}
|
||||
/* Symbol as it could be specified from this namespace. */
|
||||
string symbol_visible = symbol.name_space.substr(ns_prefix.size()) + symbol.identifier;
|
||||
|
||||
bool append_struct_ns = false;
|
||||
string specified_symbol = token.full_symbol_name();
|
||||
/* First try to match methods. */
|
||||
if (symbol.is_method && !struct_name.empty()) {
|
||||
if (!symbol.is_static) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_prev_ns_specifier = token.prev() == ':' && token.prev(2) == ':';
|
||||
if (!is_prev_ns_specifier) {
|
||||
/* For unspecified symbol, we append the struct namespace to try to match the method
|
||||
* visible symbol. */
|
||||
specified_symbol = struct_name + "::" + specified_symbol;
|
||||
append_struct_ns = true;
|
||||
}
|
||||
|
||||
if (specified_symbol != symbol_visible) {
|
||||
continue;
|
||||
}
|
||||
/* Matched a static method. */
|
||||
}
|
||||
else {
|
||||
/* Other symbols. */
|
||||
if (specified_symbol != symbol_visible) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Only for non-definition. */
|
||||
if (token.prev() != Word) {
|
||||
/* WORKAROUND: Since we do not have overload argument type infos, we cannot resolve
|
||||
* them like C++ does. Instead, we error on ambiguity and let the user resolve it. */
|
||||
for (const auto &overload : symbols_set) {
|
||||
/* Searching for overload in other namespaces. */
|
||||
if (overload.name_space == symbol.name_space || overload.identifier != symbol.identifier)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/* Reject symbols declared after the identifier.
|
||||
* Note that static method have their definition line at the top of the struct. */
|
||||
if (token_line < overload.definition_line) {
|
||||
continue;
|
||||
}
|
||||
/* Only expand symbols that are visible inside this namespace. */
|
||||
if (!ns_prefix.starts_with(overload.name_space)) {
|
||||
continue;
|
||||
}
|
||||
if (specified_symbol != overload.identifier) {
|
||||
continue;
|
||||
}
|
||||
error_handler.report(
|
||||
token, "Call to function is ambiguous. Specify namespace to remove ambiguity.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Append current namespace. */
|
||||
parser.insert_before(token.namespace_start(), ns_name + "::");
|
||||
if (append_struct_ns) {
|
||||
/* Append struct namespace for static methods. */
|
||||
parser.insert_before(token.namespace_start(), struct_name + "::");
|
||||
}
|
||||
/* Only match a symbol once. */
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
/* Pipeline declarations.
|
||||
* Manually handle them. They are the only use-case of variable defined in global scope. */
|
||||
scope.foreach_match("AA(A", [&](vector<Token> toks) {
|
||||
if (toks[0].scope().type() != ScopeType::Namespace || !toks[0].str().starts_with("Pipeline")) {
|
||||
return;
|
||||
}
|
||||
parser.insert_before(toks[1], ns_name + SourceProcessor::namespace_separator);
|
||||
});
|
||||
|
||||
Token namespace_tok = scope.front().prev().namespace_start().prev();
|
||||
if (namespace_tok == Namespace) {
|
||||
parser.erase(namespace_tok, scope.front());
|
||||
parser.erase(scope.back());
|
||||
}
|
||||
else {
|
||||
error_handler.report(namespace_tok, "Expected namespace token.");
|
||||
}
|
||||
}
|
||||
|
||||
/* Lower namespaces by adding namespace prefix to all the contained structs and functions. */
|
||||
void SourceProcessor::lower_namespaces(Parser &parser)
|
||||
{
|
||||
using namespace metadata;
|
||||
|
||||
/* Expand compound namespaces. Simplify lowering.
|
||||
* Example: `namespace A::B {}` > `namespace A { namespace B {} }` */
|
||||
parser().foreach_token(Namespace, [&](Token t) {
|
||||
int nesting = 0;
|
||||
Token name = t.next();
|
||||
while (name.next() == ':') {
|
||||
parser.replace(name.next(), name.next().next(), " { namespace ");
|
||||
name = name.next().next().next();
|
||||
nesting++;
|
||||
}
|
||||
Scope scope = name.next().scope();
|
||||
for (int i = 0; i < nesting; i++) {
|
||||
parser.insert_before(scope.back(), "}");
|
||||
}
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
|
||||
/* Using an ordered set ordered by namespace make homonym symbols are resolve
|
||||
* properly (closest from current namespace). */
|
||||
set<Symbol> symbols_set;
|
||||
{
|
||||
/* Deduplicate symbols. Done this way because we want to keep line definition ordering
|
||||
* inside the symbols_set. */
|
||||
unordered_set<string> unique_symbols;
|
||||
for (const auto &symbol : metadata_.symbol_table) {
|
||||
auto [_, inserted] = unique_symbols.insert(symbol.name_space + symbol.identifier);
|
||||
if (inserted) {
|
||||
symbols_set.emplace(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
/* Parse each namespace declaration.
|
||||
* Do it iteratively from the deepest namespace to the shallowest. */
|
||||
parser().foreach_scope(ScopeType::Namespace, [&](const Scope &scope) {
|
||||
lower_namespace("", scope, parser, error_handler, symbols_set);
|
||||
});
|
||||
} while (parser.apply_mutations());
|
||||
}
|
||||
|
||||
void SourceProcessor::lower_scope_resolution_operators(Parser &parser)
|
||||
{
|
||||
parser().foreach_match<true>("::", [&](const vector<Token> &tokens) {
|
||||
if (tokens[0].scope().type() == ScopeType::Attribute) {
|
||||
return;
|
||||
}
|
||||
Token prev = tokens[0].prev();
|
||||
if (prev != Word && !(prev == '>' && prev.followed_by_whitespace() == false)) {
|
||||
/* Global namespace reference. */
|
||||
parser.erase(tokens.front(), tokens.back());
|
||||
}
|
||||
else {
|
||||
/* Specific namespace reference. */
|
||||
parser.replace(tokens.front(), tokens.back(), namespace_separator);
|
||||
}
|
||||
});
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
1891
blender-5.2.0/source/blender/gpu/shader_tool/processor.cc
Normal file
1891
blender-5.2.0/source/blender/gpu/shader_tool/processor.cc
Normal file
File diff suppressed because it is too large
Load Diff
417
blender-5.2.0/source/blender/gpu/shader_tool/processor.hh
Normal file
417
blender-5.2.0/source/blender/gpu/shader_tool/processor.hh
Normal file
@@ -0,0 +1,417 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
|
||||
enum class Language {
|
||||
UNKNOWN = 0,
|
||||
/* Shared header. */
|
||||
CPP,
|
||||
/* Metal Shading Language. */
|
||||
MSL,
|
||||
/* OpenGL Shading Language. */
|
||||
GLSL,
|
||||
/* Blender Shading Language. */
|
||||
BSL,
|
||||
/* Same as GLSL but enable partial C++ feature support like template, references,
|
||||
* include system, etc ... */
|
||||
BLENDER_GLSL,
|
||||
};
|
||||
|
||||
static inline Language language_from_filename(const std::string &filename)
|
||||
{
|
||||
if (filename.find(".msl") != std::string::npos) {
|
||||
return Language::MSL;
|
||||
}
|
||||
if (filename.find(".glsl") != std::string::npos || filename.find(".bsl.hh") != std::string::npos)
|
||||
{
|
||||
return Language::GLSL;
|
||||
}
|
||||
if (filename.find(".hh") != std::string::npos) {
|
||||
return Language::CPP;
|
||||
}
|
||||
return Language::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shader source preprocessor that allow to mutate shader sources into cross API source that can be
|
||||
* interpreted by the different GPU backends. Some syntax are mutated or reported as incompatible.
|
||||
*/
|
||||
class SourceProcessor {
|
||||
public:
|
||||
using ErrorHandler = parser::ErrorHandler;
|
||||
using Parser = parser::IntermediateForm<parser::FullLexer, parser::FullParser>;
|
||||
using Scope = parser::Scope;
|
||||
using Token = parser::Token;
|
||||
using Tokens = std::vector<parser::Token>;
|
||||
|
||||
/* Cannot use `__` because of some compilers complaining about reserved symbols. */
|
||||
static constexpr const char *namespace_separator = "_";
|
||||
/* Add a prefix to all member functions so that they are not clashing with local variables. */
|
||||
static constexpr const char *method_call_prefix = "_";
|
||||
static constexpr const char *linted_struct_suffix = "_host_shared_";
|
||||
static constexpr const char *uniform_struct_suffix = "uniform_";
|
||||
|
||||
private:
|
||||
const std::string source_;
|
||||
const std::string filepath_;
|
||||
metadata::Source metadata_;
|
||||
|
||||
Language language_;
|
||||
|
||||
parser::ErrorHandler error_handler = {
|
||||
.default_filename = filepath_.substr(filepath_.find_last_of('/') + 1)};
|
||||
|
||||
void report_error(Token tok, const std::string &message)
|
||||
{
|
||||
error_handler.report(tok, message);
|
||||
}
|
||||
|
||||
void report_error(int row, int column, const std::string &line, const std::string &message)
|
||||
{
|
||||
error_handler.report(row, column, line, message);
|
||||
}
|
||||
|
||||
public:
|
||||
SourceProcessor(const std::string &source, const std::string &filepath, Language language)
|
||||
: source_(source), filepath_(filepath), language_(language)
|
||||
{
|
||||
}
|
||||
|
||||
struct Result {
|
||||
/* Resulting Intermediate Language source. */
|
||||
std::string source;
|
||||
/* Parsed metadata. */
|
||||
metadata::Source metadata;
|
||||
|
||||
std::optional<parser::ErrorHandler::Error> error;
|
||||
};
|
||||
|
||||
/* Convert to intermediate language. Also outputs metadata.
|
||||
* symbols_set is the set of namespace symbols from external files / dependencies. */
|
||||
Result convert(metadata::Source external_sources_symbols = {});
|
||||
|
||||
/* Lightweight parsing. Only Source::dependencies and Source::symbol_table are populated. */
|
||||
metadata::Source parse_include_and_symbols();
|
||||
|
||||
/* Return the input string with comments removed. */
|
||||
std::string remove_comments()
|
||||
{
|
||||
return remove_comments(source_);
|
||||
}
|
||||
|
||||
/* String hash are outputted inside GLSL and needs to fit 32 bits. */
|
||||
static uint32_t hash_string(const std::string &str)
|
||||
{
|
||||
uint64_t hash_64 = metadata::hash(str);
|
||||
uint32_t hash_32 = uint32_t(hash_64 ^ (hash_64 >> 32));
|
||||
return hash_32;
|
||||
}
|
||||
|
||||
private:
|
||||
Result convert_glsl();
|
||||
Result convert_msl();
|
||||
Result convert_bsl(metadata::Source external_sources_symbols);
|
||||
|
||||
/* --- Cleanup --- */
|
||||
|
||||
/** Remove single and multi-line comments to avoid this complexity during parsing. */
|
||||
std::string remove_comments(const std::string &str);
|
||||
/* Lower preprocessor directives containing `GPU_SHADER`.
|
||||
* Avoid processing code that is not destined to be shader code and could contain unsupported
|
||||
* syntax. */
|
||||
std::string disabled_code_mutation(const std::string &str);
|
||||
/* Remove trailing white spaces. */
|
||||
template<typename ParserT> void cleanup_whitespace(ParserT &parser);
|
||||
/* Successive mutations can introduce a lot of unneeded line directives. */
|
||||
void cleanup_line_directives(Parser &parser);
|
||||
/* Successive mutations can introduce a lot of unneeded blank lines. */
|
||||
void cleanup_empty_lines(Parser &parser);
|
||||
|
||||
/* --- Parsing --- */
|
||||
|
||||
/* Parse defines in order to output them with the create infos.
|
||||
* This allow the create infos to use shared defines values. */
|
||||
void parse_defines(Parser &parser);
|
||||
/* Populates metadata::symbol_table by scanning all namespaces.
|
||||
* Does not parse global symbols. */
|
||||
void parse_local_symbols(Parser &parser);
|
||||
/* Legacy create info parsing and removing. */
|
||||
void parse_legacy_create_info(Parser &parser);
|
||||
/* Populates metadata::dependencies by scanning include directives. */
|
||||
void parse_includes(Parser &parser);
|
||||
/* Parse special pragma. */
|
||||
void parse_pragma_runtime_generated(Parser &parser);
|
||||
/** Populate metadata::functions for runtime node-tree compilation. */
|
||||
void parse_library_functions(Parser &parser);
|
||||
/* Populate metadata::builtins by scanning source for keywords. Can trigger false positive.
|
||||
* This is mostly legacy path as most builtin should be explicitly defined inside the BSL entry
|
||||
* points. */
|
||||
void parse_builtins(const std::string &str, const std::string &filename, bool pure_glsl = false);
|
||||
|
||||
/* Legacy shared variable support. */
|
||||
std::string threadgroup_variables_parse_and_remove(const std::string &str);
|
||||
|
||||
/* --- Linting --- */
|
||||
|
||||
/* Make sure `if`, `else`, `for` statements are followed by braces. */
|
||||
void lint_unbraced_statements(Parser &parser);
|
||||
/* Lint for BSL reserved tokens. */
|
||||
void lint_reserved_tokens(Parser &parser);
|
||||
/* Lint for valid BSL attributes. */
|
||||
void lint_attributes(Parser &parser);
|
||||
/* Assume formatted source with our code style. Cannot be applied to python shaders. */
|
||||
void lint_global_scope_constants(Parser &parser);
|
||||
/* Search for constructor definition in active code. These are not supported. */
|
||||
void lint_constructors(Parser &parser);
|
||||
/* Forward declaration of types are not supported and makes no sense in a shader program where
|
||||
* there is no pointers. */
|
||||
void lint_forward_declared_structs(Parser &parser);
|
||||
|
||||
/* --- Lowering --- */
|
||||
|
||||
/* Remove `maybe_unused` attribute. */
|
||||
void lower_maybe_unused(Parser &parser);
|
||||
/* Lower parameters that have no name (invalid in GLSL). */
|
||||
void lower_namesless_parameters(Parser &parser);
|
||||
/**
|
||||
* Given our code-style, we don't need the disambiguation.
|
||||
* Example: `x.template foo<int>()` > `x.foo<int>()`
|
||||
*/
|
||||
void lower_template_dependent_names(Parser &parser);
|
||||
/* Lower template definition and instantiation by doing simple copy paste + argument
|
||||
* substitution. */
|
||||
void lower_templates(Parser &parser);
|
||||
void lower_template_calls(Parser &parser);
|
||||
void lower_template_specialization(Parser &parser);
|
||||
/* Ensures pragma once is present in headers to comply to our include semantic. */
|
||||
void lint_pragma_once(Parser &parser, const std::string &filename);
|
||||
/* Unroll loops by copy pasting content. */
|
||||
void lower_loop_unroll(Parser &parser);
|
||||
/* Convert if statements marked as static to preprocessor #if statements. */
|
||||
void lower_static_branch(Parser &parser);
|
||||
/* Lower namespaces by adding namespace prefix to all the contained structs and functions. */
|
||||
void lower_namespaces(Parser &parser);
|
||||
/**
|
||||
* Needs to run before namespace mutation so that `using` have more precedence.
|
||||
* Otherwise the following would fail.
|
||||
* \code{.cc}
|
||||
* namespace B {
|
||||
* int test(int a) {}
|
||||
* }
|
||||
*
|
||||
* namespace A {
|
||||
* int test(int a) {}
|
||||
* int func(int a) {
|
||||
* using B::test;
|
||||
* return test(a); // Should reference B::test and not A::test
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
void lower_using(Parser &parser);
|
||||
/* Example: `A::B` --> `A_B` */
|
||||
void lower_scope_resolution_operators(Parser &parser);
|
||||
/* Remove preprocessor directives unsupported by target shading languages.
|
||||
* Examples `#includes`, `#pragma once`. */
|
||||
void lower_preprocessor(Parser &parser);
|
||||
/* Support for BLI swizzle syntax.
|
||||
* Examples `a.xy()` --> `a.xy`. */
|
||||
void lower_swizzle_methods(Parser &parser);
|
||||
/* Support for binary literals.
|
||||
* Examples `0b1001` --> `0x9`. */
|
||||
void lower_binary_literals(Parser &parser);
|
||||
/* Change printf calls to "recursive" call to implementation functions.
|
||||
* This allows to emulate the variadic arguments of printf. */
|
||||
void lower_printf(Parser &parser);
|
||||
/* Turn assert into a printf. */
|
||||
void lower_assert(Parser &parser, const std::string &filename);
|
||||
/* Parse SRT and interfaces, remove their attributes and create init function for SRT structs. */
|
||||
void lower_resource_table(Parser &parser);
|
||||
/* Examples `string_t s = "a" "b"` --> `string_t s = "ab"`. */
|
||||
void lower_strings_sequences(Parser &parser);
|
||||
/* Replace string literals by their hash and store the original string in the file metadata. */
|
||||
void lower_strings(Parser &parser);
|
||||
/* `class` -> `struct` */
|
||||
void lower_classes(Parser &parser);
|
||||
/* Create default initializer (empty brace) for all classes. */
|
||||
void lower_default_constructors(Parser &parser);
|
||||
/* Make all members of a class to be referenced using `this->`. */
|
||||
void lower_implicit_member(Parser &parser);
|
||||
/* Move all method definition outside of struct definition blocks. */
|
||||
void lower_method_definitions(Parser &parser);
|
||||
/* Add padding member to empty structs. */
|
||||
void lower_empty_struct(Parser &parser);
|
||||
/* Transform `a.fn(b)` into `fn(a, b)`. */
|
||||
void lower_method_calls(Parser &parser);
|
||||
/* Transform `auto [a, b] = fn()` into `S _tmp = fn(); a = _tmp.A; b = _tmp.B;`. */
|
||||
void lower_structured_bindings(Parser &parser);
|
||||
/* Parse, convert to create infos, and erase declaration. */
|
||||
void lower_pipeline_definition(Parser &parser, const std::string &filename);
|
||||
/* Remove `[vertex|fragment|compute]` function attribute and add appropriate guards. */
|
||||
void lower_stage_function(Parser &parser);
|
||||
/* Add #ifdef directive around functions using SRT arguments.
|
||||
* Need to run after `lower_entry_points_signature`. */
|
||||
void lower_srt_arguments(Parser &parser);
|
||||
/* Add ifdefs guards around scopes using resource accessors. */
|
||||
void lower_resource_access_functions(Parser &parser);
|
||||
/* Lower enums to constants. */
|
||||
void lower_enums(Parser &parser);
|
||||
/* Merge attribute scopes. They are equivalent in the C++ standard.
|
||||
* This allow to simplify parsing later on.
|
||||
* `[[a]] [[b]]` > `[[a, b]]` */
|
||||
void lower_attribute_sequences(Parser &parser);
|
||||
/* Lint host shared structure for padding and alignment.
|
||||
* Remove the [[host_shared]] attribute. */
|
||||
void lower_host_shared_structures(Parser &parser);
|
||||
/* Remove noop keywords that makes subsequent lowering passes more complicated. */
|
||||
void lower_noop_keywords(Parser &parser);
|
||||
/* Example: `int a[] = {1,2,};` --> `int a[] = {1,2 };` */
|
||||
void lower_trailing_comma_in_list(Parser &parser);
|
||||
/* Allow easier parsing of struct member declaration.
|
||||
* Example: `int a, b;` --> `int a; int b;` */
|
||||
void lower_comma_separated_declarations(Parser &parser);
|
||||
/* Example: `return {1, 2};` --> `T tmp = T{1, 2}; return tmp;`. */
|
||||
void lower_implicit_return_types(Parser &parser);
|
||||
/* Example: `int a{1};` --> `int a = int{1};`. */
|
||||
void lower_initializer_implicit_types(Parser &parser);
|
||||
/* Example: `T a{.a=1};` --> `T a; a.a=1;`. */
|
||||
void lower_designated_initializers(Parser &parser);
|
||||
/* Support for **full** aggregate initialization.
|
||||
* They are converted to default constructor for GLSL. */
|
||||
void lower_aggregate_initializers(Parser &parser);
|
||||
/* Auto detect array length, and lower to GLSL compatible syntax.
|
||||
* TODO(fclem): GLSL 4.3 already supports initializer list. So port the old GLSL syntax to
|
||||
* initializer list instead. */
|
||||
void lower_array_initializations(Parser &parser);
|
||||
/**
|
||||
* Expand functions with default arguments to function overloads.
|
||||
* Expects formatted input and that function bodies are followed by newline.
|
||||
*/
|
||||
void lower_function_default_arguments(Parser &parser);
|
||||
/* Limited union implementation. Create getters and setters to a raw data struct. */
|
||||
void lower_unions(Parser &parser);
|
||||
/**
|
||||
* For safety reason, union members need to be declared with the union_t template.
|
||||
* This avoid raw member access which we cannot emulate. Instead this forces the use of the `()`
|
||||
* operator for accessing the members of the enum.
|
||||
*
|
||||
* Need to run before lower_unions.
|
||||
*/
|
||||
void lower_union_accessor_templates(Parser &parser);
|
||||
/**
|
||||
* For safety reason, nested resource tables need to be declared with the srt_t template.
|
||||
* This avoid chained member access which isn't well defined with the preprocessing we are doing.
|
||||
*
|
||||
* This linting phase make sure that [[resource_table]] members uses it and that no incorrect
|
||||
* usage is made. We also remove this template because it has no real meaning.
|
||||
*
|
||||
* Need to run before lower_resource_table.
|
||||
*/
|
||||
void lower_srt_accessor_templates(Parser &parser);
|
||||
/* Add `srt_access` around all member access of SRT variables.
|
||||
* Need to run before local reference mutations. */
|
||||
void lower_srt_member_access(Parser &parser);
|
||||
/* Parse entry point definitions and mutating all parameter usage to global resources. */
|
||||
void lower_entry_points(Parser &parser);
|
||||
/* Removes entry point arguments to make it compatible with the legacy code.
|
||||
* Has to run after mutation related to function arguments. */
|
||||
void lower_entry_points_signature(Parser &parser);
|
||||
/* To be run after `lower_reference_arguments()`. */
|
||||
void lower_reference_variables(Parser &parser);
|
||||
/* To be run before `argument_decorator_macro_injection()`. */
|
||||
void lower_reference_arguments(Parser &parser);
|
||||
/* Example: `out float var[2]` > `_ref(float, var)[2]` */
|
||||
void lower_argument_qualifiers(Parser &parser);
|
||||
/* Example: `textureGather(t,c,1)` > `textureGather1(t,c)` */
|
||||
void lower_gather_component(Parser &parser);
|
||||
/* Lower test expect clauses to SSBO assignments. */
|
||||
void lower_tests(Parser &parser);
|
||||
|
||||
/* --- Legacy passes for GLSL --- */
|
||||
|
||||
/* Example: `out float var[2]` > `out float _out_sta var _out_end[2]` */
|
||||
std::string argument_decorator_macro_injection(const std::string &str);
|
||||
/* Example: `= float[2](0.0, 0.0)` > `= ARRAY_T(float) ARRAY_V(0.0, 0.0)` */
|
||||
std::string array_constructor_macro_injection(const std::string &str);
|
||||
/* Used to make GLSL matrix constructor compatible with MSL in pyGPU shaders.
|
||||
* This syntax is not supported in blender's own shaders. */
|
||||
std::string matrix_constructor_mutation(const std::string &str);
|
||||
|
||||
/* --- Utilities --- */
|
||||
|
||||
/* Parse subscript scope with single integer literal and return the literal value.
|
||||
* Return the fallback value in any case of non-literal value, or failed conversion. */
|
||||
int static_array_size(const Scope &array, int fallback_value);
|
||||
|
||||
/* Process struct declaration and instantiate it in this file. */
|
||||
void process_template_struct(metadata::TemplateDefinition &template_def,
|
||||
SourceProcessor::Parser &parser);
|
||||
/* Process templated function (or class method) declaration and instantiate it in this file. */
|
||||
void process_template_function(metadata::TemplateDefinition &template_def,
|
||||
SourceProcessor::Parser &parser,
|
||||
/* If method, the end token of the template inside the struct. */
|
||||
const Token method_end);
|
||||
|
||||
void lower_pre_template(Parser &parser);
|
||||
|
||||
void lower_template_instantiation(
|
||||
Parser &parser,
|
||||
/* If method, the end token of the template inside the struct. */
|
||||
const Token method_end,
|
||||
const Token &inst_start,
|
||||
const Scope &inst_args,
|
||||
const metadata::TemplateDefinition template_def,
|
||||
const Token &symbol_name,
|
||||
const std::vector<std::string> &arg_list,
|
||||
const std::string &fn_decl,
|
||||
const bool all_template_args_in_function_signature);
|
||||
|
||||
metadata::TemplateDefinition parse_template_definition(SourceProcessor::Parser &parser,
|
||||
Token template_tok,
|
||||
bool is_method,
|
||||
Scope ns_scope,
|
||||
const std::string &filepath);
|
||||
|
||||
void parse_namespace_symbols(SourceProcessor::Parser &parser,
|
||||
Scope ns,
|
||||
metadata::Source &metadata,
|
||||
const std::string &filepath);
|
||||
|
||||
std::string template_full_specified_name(metadata::TemplateDefinition &template_def);
|
||||
|
||||
public:
|
||||
/* Check for existence of preprocessor pragma in file. */
|
||||
static bool has_pragma(Parser &parser, std::string_view pragma_str);
|
||||
|
||||
/** Remove trailing white-spaces. */
|
||||
static std::string strip_whitespace(const std::string &str);
|
||||
|
||||
/* Example: `VertOut<float, 1>` > `VertOutTfloatT1` */
|
||||
static std::string template_arguments_mangle(const Scope template_args);
|
||||
|
||||
/* Create placeholder for GLSL declarations generated by the GPU backends (VK/GL). */
|
||||
static std::string get_create_info_placeholder(const std::string &name);
|
||||
|
||||
/* Make a scope only active based on the given condition using `#if` preprocessor directives.
|
||||
* Processor contained return statements by returning 0 if scope is disabled.
|
||||
* fn_type can be invalid token if scope is not a function scope. */
|
||||
static void guarded_scope_mutation(Parser &parser,
|
||||
Scope scope,
|
||||
const std::string &condition,
|
||||
Token fn_type);
|
||||
|
||||
/* Return `#line 1 filename\n`. */
|
||||
static std::string line_directive_prefix(const std::string &filename);
|
||||
};
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
726
blender-5.2.0/source/blender/gpu/shader_tool/resource_table.cc
Normal file
726
blender-5.2.0/source/blender/gpu/shader_tool/resource_table.cc
Normal file
@@ -0,0 +1,726 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
/**
|
||||
* For safety reason, nested resource tables need to be declared with the srt_t template.
|
||||
* This avoid chained member access which isn't well defined with the preprocessing we are doing.
|
||||
*
|
||||
* This linting phase make sure that [[resource_table]] members uses it and that no incorrect
|
||||
* usage is made. We also remove this template because it has no real meaning.
|
||||
*
|
||||
* Need to run before lower_resource_table.
|
||||
*/
|
||||
void SourceProcessor::lower_srt_accessor_templates(Parser &parser)
|
||||
{
|
||||
parser().foreach_token(Struct, [&](Token tok) {
|
||||
Scope body = tok.find_next(lexit::BracketOpen).scope();
|
||||
body.foreach_declaration([&](Scope attributes,
|
||||
Token,
|
||||
Token type,
|
||||
Scope template_scope,
|
||||
Token name,
|
||||
Scope array,
|
||||
Token) {
|
||||
if (attributes[1].str() != "resource_table") {
|
||||
if (type.str() == "srt_t") {
|
||||
report_error(name,
|
||||
"The srt_t<T> template is only to be used with members declared with the "
|
||||
"[[resource_table]] attribute.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.str() != "srt_t") {
|
||||
report_error(type,
|
||||
"Members declared with the [[resource_table]] attribute must wrap their type "
|
||||
"with the srt_t<T> template.");
|
||||
}
|
||||
|
||||
if (array.is_valid()) {
|
||||
report_error(name, "[[resource_table]] members cannot be arrays.");
|
||||
}
|
||||
if (name.prev() == '&') {
|
||||
report_error(name.prev(), "[[resource_table]] members cannot be references.");
|
||||
}
|
||||
|
||||
/* Remove the template but not the wrapped type. */
|
||||
parser.erase(type);
|
||||
if (template_scope.is_valid()) {
|
||||
parser.erase(template_scope.front());
|
||||
parser.erase(template_scope.back());
|
||||
}
|
||||
});
|
||||
});
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/* Add `srt_access` around all member access of SRT variables.
|
||||
* Need to run before local reference mutations. */
|
||||
void SourceProcessor::lower_srt_member_access(Parser &parser)
|
||||
{
|
||||
const string srt_attribute = "resource_table";
|
||||
|
||||
auto memher_access_mutation = [&](Scope attribute, Token type, Token var, Scope body_scope) {
|
||||
if (attribute[2].str() != srt_attribute) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_func_prototype_decl = body_scope.is_invalid();
|
||||
const bool is_local_reference = attribute.scope().type() != ScopeType::FunctionArgs &&
|
||||
attribute.scope().type() != ScopeType::FunctionArg;
|
||||
|
||||
if (is_local_reference || is_func_prototype_decl) {
|
||||
parser.replace(attribute, "");
|
||||
}
|
||||
|
||||
/* Change references to copies to allow placeholder "*_new_()" function result to be passed
|
||||
* as argument. Once these placeholder function are removed, we can pass the value as
|
||||
* reference. */
|
||||
if (!is_local_reference && var.prev() == '&') {
|
||||
parser.erase(var.prev());
|
||||
}
|
||||
|
||||
string srt_type(type.str());
|
||||
string srt_var(var.str());
|
||||
|
||||
body_scope.foreach_match("A.A", [&](const vector<Token> toks) {
|
||||
if (toks[0].str() != srt_var || toks[0].prev() == '.') {
|
||||
return;
|
||||
}
|
||||
parser.replace(
|
||||
toks[0], toks[2], "srt_access(" + srt_type + ", " + string(toks[2].str()) + ")", true);
|
||||
});
|
||||
};
|
||||
|
||||
parser().foreach_scope(ScopeType::FunctionArgs, [&](const Scope fn_args) {
|
||||
/* Parse both function and prototypes. */
|
||||
Scope fn_body = fn_args.next().type() == ScopeType::Function ? fn_args.next() : Scope(parser);
|
||||
/* Function arguments. */
|
||||
fn_args.foreach_match("[[..]]c?A&A", [&](const vector<Token> toks) {
|
||||
memher_access_mutation(toks[0].scope(), toks[8], toks[10], fn_body);
|
||||
});
|
||||
fn_args.foreach_match("[[..]]c?AA", [&](const vector<Token> toks) {
|
||||
if (toks[1].next().str() == srt_attribute) {
|
||||
parser.erase(toks[0].scope());
|
||||
report_error(toks[9], "Shader Resource Table arguments must be references.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
parser().foreach_scope(ScopeType::Function, [&](const Scope fn_body) {
|
||||
/* Local references. */
|
||||
fn_body.foreach_match("[[..]]c?A&A", [&](const vector<Token> toks) {
|
||||
memher_access_mutation(toks[0].scope(), toks[8], toks[10], toks[10].scope());
|
||||
});
|
||||
/* Local variables. */
|
||||
fn_body.foreach_match("[[..]]c?AA", [&](const vector<Token> toks) {
|
||||
memher_access_mutation(toks[0].scope(), toks[8], toks[9], toks[9].scope());
|
||||
});
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/* Add #ifdef directive around functions using SRT arguments.
|
||||
* Need to run after `lower_entry_points_signature`. */
|
||||
void SourceProcessor::lower_srt_arguments(Parser &parser)
|
||||
{
|
||||
/* SRT arguments. */
|
||||
parser().foreach_function([&](bool, Token fn_type, Token, Scope fn_args, bool, Scope fn_body) {
|
||||
string condition;
|
||||
fn_args.foreach_match("[[..]]c?A", [&](const vector<Token> &tokens) {
|
||||
if (tokens[1].next().str() != "resource_table") {
|
||||
return;
|
||||
}
|
||||
string srt_cond;
|
||||
tokens[1].scope().foreach_attribute([&](Token attribute_name, Scope attribute_parameters) {
|
||||
if (attribute_name.str() == "condition") {
|
||||
srt_cond = "SRT_CONSTANT_" + string(attribute_parameters.str_exclusive());
|
||||
}
|
||||
});
|
||||
|
||||
condition += " && ";
|
||||
if (!srt_cond.empty()) {
|
||||
/* If condition exists, ensure the function will be available if the condition is false */
|
||||
condition += "(!(" + srt_cond + ") ||";
|
||||
}
|
||||
condition += "defined(CREATE_INFO_" + string(tokens[8].str()) + ")";
|
||||
if (!srt_cond.empty()) {
|
||||
condition += ")";
|
||||
}
|
||||
parser.replace(tokens[0].scope(), "");
|
||||
});
|
||||
|
||||
if (!condition.empty()) {
|
||||
/* Take attribute into account. */
|
||||
Token first_tok = fn_type.prev() == ']' ? fn_type.prev().scope().front() : fn_type;
|
||||
parser.insert_directive(first_tok.prev(), "#if " + condition.substr(4));
|
||||
parser.insert_directive(fn_body.back(), "#endif");
|
||||
}
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/* Add ifdefs guards around scopes using resource accessors. */
|
||||
void SourceProcessor::lower_resource_access_functions(Parser &parser)
|
||||
{
|
||||
/* Legacy access macros. */
|
||||
parser().foreach_function([&](bool, Token fn_type, Token, Scope, bool, Scope fn_body) {
|
||||
fn_body.foreach_match("A(", [&](const vector<Token> &tokens) {
|
||||
string_view func_name = tokens[0].str();
|
||||
if (func_name != "specialization_constant_get" && func_name != "shared_variable_get" &&
|
||||
func_name != "push_constant_get" && func_name != "interface_get" &&
|
||||
func_name != "resource_table_get" && func_name != "attribute_get" &&
|
||||
func_name != "buffer_get" && func_name != "sampler_get" && func_name != "image_get")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokens[1].next() != Word) {
|
||||
report_error(tokens[1].next(), "Expecting symbol name");
|
||||
return;
|
||||
}
|
||||
|
||||
string info_name(tokens[1].next().str());
|
||||
Scope scope = tokens[0].scope();
|
||||
/* We can be in expression scope. Take parent scope until we find a local scope. */
|
||||
while (scope.type() != ScopeType::Function && scope.type() != ScopeType::Local) {
|
||||
scope = scope.scope();
|
||||
}
|
||||
|
||||
string condition = "defined(CREATE_INFO_" + info_name + ")";
|
||||
|
||||
if (scope.type() == ScopeType::Function) {
|
||||
guarded_scope_mutation(parser, scope, condition, fn_type);
|
||||
}
|
||||
else {
|
||||
guarded_scope_mutation(parser, scope, condition, Token(parser));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs to run before namespace mutation so that `using` have more precedence.
|
||||
* Otherwise the following would fail.
|
||||
* \code{.cc}
|
||||
* namespace B {
|
||||
* int test(int a) {}
|
||||
* }
|
||||
*
|
||||
* namespace A {
|
||||
* int test(int a) {}
|
||||
* int func(int a) {
|
||||
* using B::test;
|
||||
* return test(a); // Should reference B::test and not A::test
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
void SourceProcessor::lower_using(Parser &parser)
|
||||
{
|
||||
parser().foreach_match("un", [&](const vector<Token> &tokens) {
|
||||
report_error(tokens[0],
|
||||
"Unsupported `using namespace`. "
|
||||
"Add individual `using` directives for each needed symbol.");
|
||||
});
|
||||
|
||||
auto process_using = [&](const Token &using_tok,
|
||||
const Token &from,
|
||||
const Token &to_start,
|
||||
const Token &to_end,
|
||||
const Token &end_tok) {
|
||||
string to = parser.substr_range_inclusive(to_start, to_end);
|
||||
string namespace_prefix = parser.substr_range_inclusive(to_start, to_end.prev().prev().prev());
|
||||
Scope scope = from.scope();
|
||||
|
||||
/* Using the keyword in global or at namespace scope. */
|
||||
if (scope.type() == ScopeType::Global) {
|
||||
report_error(using_tok, "The `using` keyword is not allowed in global scope.");
|
||||
return;
|
||||
}
|
||||
if (scope.type() == ScopeType::Namespace) {
|
||||
/* Ensure we are bringing symbols from the same namespace.
|
||||
* Otherwise we can have different shadowing outcome between shader and C++. */
|
||||
string namespace_name = scope.front().prev().full_symbol_name();
|
||||
if (namespace_name != namespace_prefix) {
|
||||
report_error(
|
||||
using_tok,
|
||||
"The `using` keyword is only allowed in namespace scope to make visible symbols "
|
||||
"from the same namespace declared in another scope, potentially from another "
|
||||
"file.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Assignments do not allow to alias functions symbols. */
|
||||
const bool use_alias = from.str() != to_end.str();
|
||||
const bool replace_fn = !use_alias;
|
||||
/** IMPORTANT: If replace_fn is true, this can replace any symbol type if there are functions
|
||||
* and types with the same name. We could support being more explicit about the type of
|
||||
* symbol to replace using an optional attribute [[gpu::using_function]]. */
|
||||
|
||||
/* Replace all occurrences of the non-namespace specified symbol. */
|
||||
scope.foreach_token(Word, [&](const Token &token) {
|
||||
/* Do not replace symbols before the using statement. */
|
||||
if (token.index_ <= to_end.index_) {
|
||||
return;
|
||||
}
|
||||
/* Reject symbols that contain the target symbol name. */
|
||||
if (token.prev() == ':') {
|
||||
return;
|
||||
}
|
||||
if (!replace_fn && token.next() == '(') {
|
||||
return;
|
||||
}
|
||||
if (token.str() != from.str()) {
|
||||
return;
|
||||
}
|
||||
parser.replace(token, to, true);
|
||||
});
|
||||
|
||||
parser.erase(using_tok, end_tok);
|
||||
};
|
||||
|
||||
parser().foreach_match("uA::A", [&](const vector<Token> &tokens) {
|
||||
Token end = tokens.back().find_next(SemiColon);
|
||||
process_using(tokens[0], end.prev(), tokens[1], end.prev(), end);
|
||||
});
|
||||
|
||||
parser().foreach_match("uA=A::A", [&](const vector<Token> &tokens) {
|
||||
Token end = tokens.back().find_next(SemiColon);
|
||||
process_using(tokens[0], tokens[1], tokens[3], end.prev(), end);
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
|
||||
/* Verify all using were processed. */
|
||||
parser().foreach_token(Using, [&](const Token &token) {
|
||||
report_error(token, "Unsupported `using` keyword usage.");
|
||||
});
|
||||
}
|
||||
|
||||
/* Parse SRT and interfaces, remove their attributes and create init function for SRT structs. */
|
||||
void SourceProcessor::lower_resource_table(Parser &parser)
|
||||
{
|
||||
enum class SrtType {
|
||||
undefined,
|
||||
none,
|
||||
resource_table,
|
||||
vertex_input,
|
||||
vertex_output,
|
||||
fragment_output,
|
||||
fragment_input,
|
||||
};
|
||||
|
||||
auto parse_resource = [&](Scope attributes, Token type, Token name, Scope array) {
|
||||
/* FIXME(fclem): This is a hotfix to support multi dimensional array.
|
||||
* Ideally, array should already contain all dimensions */
|
||||
Scope array_end = array;
|
||||
while (array_end.next().type() == ScopeType::Subscript) {
|
||||
array_end = array_end.next();
|
||||
}
|
||||
string_view array_str;
|
||||
if (array.is_valid()) {
|
||||
array_str = parser.substr(array.front(), array_end.back(), true);
|
||||
}
|
||||
|
||||
metadata::ParsedResource resource{
|
||||
type.line_number(), string(type.str()), string(name.str()), string(array_str)};
|
||||
attributes.foreach_scope(ScopeType::Attribute, [&](const Scope &attribute) {
|
||||
string_view type = attribute[0].str();
|
||||
if (type == "sampler") {
|
||||
resource.res_type = type;
|
||||
resource.res_slot = attribute[2].str();
|
||||
}
|
||||
else if (type == "image") {
|
||||
resource.res_type = type;
|
||||
resource.res_slot = attribute[2].str();
|
||||
resource.res_qualifier = attribute[4].str();
|
||||
resource.res_format = attribute[6].str();
|
||||
}
|
||||
else if (type == "uniform") {
|
||||
resource.res_type = type;
|
||||
resource.res_slot = attribute[2].str();
|
||||
}
|
||||
else if (type == "storage") {
|
||||
resource.res_type = type;
|
||||
resource.res_slot = attribute[2].str();
|
||||
resource.res_qualifier = attribute[4].str();
|
||||
}
|
||||
else if (type == "shared") {
|
||||
resource.res_type = type;
|
||||
}
|
||||
else if (type == "push_constant") {
|
||||
resource.res_type = type;
|
||||
}
|
||||
else if (type == "compilation_constant") {
|
||||
resource.res_type = type;
|
||||
}
|
||||
else if (type == "specialization_constant") {
|
||||
resource.res_type = type;
|
||||
resource.res_value = attribute[1].scope().str_exclusive();
|
||||
}
|
||||
else if (type == "condition") {
|
||||
attribute[1].scope().foreach_token(Word, [&](const Token tok) {
|
||||
resource.res_condition += "int " + string(tok.str()) + " = ";
|
||||
resource.res_condition += "ShaderCreateInfo::find_constant(constants, \"" +
|
||||
string(tok.str()) + "\"); ";
|
||||
});
|
||||
resource.res_condition += "return " + string(attribute[1].scope().str()) + ";";
|
||||
}
|
||||
else if (type == "frequency") {
|
||||
resource.res_frequency = attribute[2].str();
|
||||
}
|
||||
else if (type == "resource_table") {
|
||||
resource.res_type = type;
|
||||
}
|
||||
else if (type == "legacy_info") {
|
||||
resource.res_type = type;
|
||||
}
|
||||
else {
|
||||
report_error(attribute[0], "Invalid attribute in resource table");
|
||||
}
|
||||
});
|
||||
return resource;
|
||||
};
|
||||
|
||||
auto parse_vertex_input = [&](Scope attributes, Token type, Token name, Scope array) {
|
||||
if (array.is_valid()) {
|
||||
report_error(array[0], "Array are not supported as vertex attributes");
|
||||
}
|
||||
|
||||
metadata::ParsedVertInput vert_in{type.line_number(), string(type.str()), string(name.str())};
|
||||
|
||||
if (vert_in.var_type == "float3x3" || vert_in.var_type == "float2x2" ||
|
||||
vert_in.var_type == "float4x4" || vert_in.var_type == "float3x4")
|
||||
{
|
||||
report_error(name, "Matrices are not supported as vertex attributes");
|
||||
}
|
||||
|
||||
attributes.foreach_scope(ScopeType::Attribute, [&](const Scope &attribute) {
|
||||
string_view type = attribute[0].str();
|
||||
if (type == "attribute") {
|
||||
vert_in.slot = attribute[2].str();
|
||||
}
|
||||
else {
|
||||
report_error(attribute[0], "Invalid attribute in vertex input interface");
|
||||
}
|
||||
});
|
||||
return vert_in;
|
||||
};
|
||||
|
||||
auto parse_vertex_output =
|
||||
[&](Token struct_name, Scope attributes, Token type, Token name, Scope array) {
|
||||
if (array.is_valid()) {
|
||||
report_error(array[0], "Array are not supported in stage interface");
|
||||
}
|
||||
|
||||
Token interpolation_mode = attributes[1];
|
||||
|
||||
metadata::ParsedAttribute attr{type.line_number(),
|
||||
string(type.str()),
|
||||
string(struct_name.str()) + "_" + string(name.str()),
|
||||
string(interpolation_mode.str())};
|
||||
|
||||
if (attr.var_type == "float3x3" || attr.var_type == "float2x2" ||
|
||||
attr.var_type == "float4x4" || attr.var_type == "float3x4")
|
||||
{
|
||||
report_error(name, "Matrices are not supported in stage interface");
|
||||
}
|
||||
|
||||
if (attr.interpolation_mode != "smooth" && attr.interpolation_mode != "flat" &&
|
||||
attr.interpolation_mode != "no_perspective")
|
||||
{
|
||||
report_error(attributes[0], "Invalid attribute in shader stage interface");
|
||||
}
|
||||
return attr;
|
||||
};
|
||||
|
||||
auto parse_fragment_output =
|
||||
[&](Token struct_name, Scope attributes, Token tok_type, Token name, Scope) {
|
||||
metadata::ParsedFragOuput frag_out{tok_type.line_number(),
|
||||
string(tok_type.str()),
|
||||
string(struct_name.str()) + "_" + string(name.str())};
|
||||
|
||||
attributes.foreach_scope(ScopeType::Attribute, [&](const Scope &attribute) {
|
||||
string_view type = attribute[0].str();
|
||||
if (type == "frag_color") {
|
||||
frag_out.slot = attribute[2].str();
|
||||
}
|
||||
else if (type == "raster_order_group") {
|
||||
frag_out.raster_order_group = attribute[2].str();
|
||||
}
|
||||
else if (type == "index") {
|
||||
frag_out.dual_source = attribute[2].str();
|
||||
}
|
||||
else {
|
||||
report_error(attributes[0], "Invalid attribute in fragment output interface");
|
||||
}
|
||||
});
|
||||
return frag_out;
|
||||
};
|
||||
|
||||
auto parse_fragment_input =
|
||||
[&](Token struct_name, Scope attributes, Token tok_type, Token name, Scope) {
|
||||
metadata::ParsedFragInput frag_in{tok_type.line_number(),
|
||||
string(tok_type.str()),
|
||||
string(struct_name.str()) + "_" + string(name.str())};
|
||||
|
||||
attributes.foreach_scope(ScopeType::Attribute, [&](const Scope &attribute) {
|
||||
string_view type = attribute[0].str();
|
||||
if (type == "subpass_input") {
|
||||
frag_in.slot = attribute[2].str();
|
||||
frag_in.image_type = attribute[4].str();
|
||||
}
|
||||
else if (type == "raster_order_group") {
|
||||
frag_in.raster_order_group = attribute[2].str();
|
||||
}
|
||||
else {
|
||||
report_error(attributes[0], "Invalid attribute in fragment output interface");
|
||||
}
|
||||
});
|
||||
return frag_in;
|
||||
};
|
||||
|
||||
auto is_resource_table_attribute = [](Token attr) {
|
||||
string_view type = attr.str();
|
||||
return (type == "sampler" || type == "image" || type == "uniform" || type == "storage" ||
|
||||
type == "shared" || type == "push_constant" || type == "compilation_constant" ||
|
||||
type == "specialization_constant" || type == "legacy_info" ||
|
||||
type == "resource_table");
|
||||
};
|
||||
auto is_vertex_input_attribute = [](Token attr) {
|
||||
string_view type = attr.str();
|
||||
return (type == "attribute");
|
||||
};
|
||||
auto is_vertex_output_attribute = [](Token attr) {
|
||||
string_view type = attr.str();
|
||||
return (type == "flat" || type == "smooth" || type == "no_perspective");
|
||||
};
|
||||
auto is_fragment_output_attribute = [](Token attr) {
|
||||
string_view type = attr.str();
|
||||
return (type == "frag_color" || type == "frag_depth" || type == "frag_stencil_ref");
|
||||
};
|
||||
auto is_fragment_input_attribute = [](Token attr) {
|
||||
string_view type = attr.str();
|
||||
return (type == "subpass_input");
|
||||
};
|
||||
|
||||
parser().foreach_struct([&](Token struct_tok, Scope, Token struct_name, Scope body) {
|
||||
SrtType srt_type = SrtType::undefined;
|
||||
bool has_srt_members = false;
|
||||
|
||||
metadata::ResourceTable srt;
|
||||
metadata::VertexInputs vertex_in;
|
||||
metadata::StageInterface vertex_out;
|
||||
metadata::FragmentOutputs fragment_out;
|
||||
metadata::FragmentInputs fragment_in;
|
||||
srt.name = struct_name.str();
|
||||
vertex_in.name = struct_name.str();
|
||||
vertex_out.name = struct_name.str();
|
||||
fragment_out.name = struct_name.str();
|
||||
fragment_in.name = struct_name.str();
|
||||
|
||||
body.foreach_declaration([&](Scope attributes,
|
||||
Token const_tok,
|
||||
Token type,
|
||||
Scope /*template_scope*/, /* TODO */
|
||||
Token name,
|
||||
Scope array,
|
||||
Token decl_end) {
|
||||
SrtType decl_type = SrtType::undefined;
|
||||
if (attributes.is_invalid()) {
|
||||
decl_type = SrtType::none;
|
||||
}
|
||||
else if (is_resource_table_attribute(attributes[1])) {
|
||||
decl_type = SrtType::resource_table;
|
||||
}
|
||||
else if (is_vertex_input_attribute(attributes[1])) {
|
||||
decl_type = SrtType::vertex_input;
|
||||
}
|
||||
else if (is_vertex_output_attribute(attributes[1])) {
|
||||
decl_type = SrtType::vertex_output;
|
||||
}
|
||||
else if (is_fragment_output_attribute(attributes[1])) {
|
||||
decl_type = SrtType::fragment_output;
|
||||
}
|
||||
else if (is_fragment_input_attribute(attributes[1])) {
|
||||
decl_type = SrtType::fragment_input;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (srt_type == SrtType::undefined) {
|
||||
srt_type = decl_type;
|
||||
}
|
||||
else if (srt_type != decl_type) {
|
||||
switch (srt_type) {
|
||||
case SrtType::resource_table:
|
||||
report_error(struct_name, "Structure expected to contain resources...");
|
||||
break;
|
||||
case SrtType::vertex_input:
|
||||
report_error(struct_name, "Structure expected to contain vertex inputs...");
|
||||
break;
|
||||
case SrtType::vertex_output:
|
||||
report_error(struct_name, "Structure expected to contain vertex outputs...");
|
||||
break;
|
||||
case SrtType::fragment_output:
|
||||
report_error(struct_name, "Structure expected to contain fragment outputs...");
|
||||
break;
|
||||
case SrtType::fragment_input:
|
||||
report_error(struct_name, "Structure expected to contain fragment inputs...");
|
||||
break;
|
||||
case SrtType::none:
|
||||
report_error(struct_name, "Structure expected to contain plain data...");
|
||||
break;
|
||||
case SrtType::undefined:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (decl_type) {
|
||||
case SrtType::resource_table:
|
||||
report_error(attributes[1], "...but member declared as resource.");
|
||||
break;
|
||||
case SrtType::vertex_input:
|
||||
report_error(attributes[1], "...but member declared as vertex input.");
|
||||
break;
|
||||
case SrtType::vertex_output:
|
||||
report_error(attributes[1], "...but member declared as vertex output.");
|
||||
break;
|
||||
case SrtType::fragment_output:
|
||||
report_error(attributes[1], "...but member declared as fragment output.");
|
||||
break;
|
||||
case SrtType::fragment_input:
|
||||
report_error(attributes[1], "...but member declared as fragment input.");
|
||||
break;
|
||||
case SrtType::none:
|
||||
report_error(name, "...but member declared as plain data.");
|
||||
break;
|
||||
case SrtType::undefined:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (decl_type) {
|
||||
case SrtType::resource_table:
|
||||
srt.emplace_back(parse_resource(attributes, type, name, array));
|
||||
if (attributes[1].str() == "resource_table") {
|
||||
has_srt_members = true;
|
||||
parser.erase(attributes.scope());
|
||||
parser.erase(const_tok);
|
||||
}
|
||||
else {
|
||||
parser.erase(attributes.front().line_start(), decl_end.line_end());
|
||||
}
|
||||
break;
|
||||
case SrtType::vertex_input:
|
||||
vertex_in.emplace_back(parse_vertex_input(attributes, type, name, array));
|
||||
parser.erase(attributes.scope());
|
||||
break;
|
||||
case SrtType::vertex_output:
|
||||
vertex_out.emplace_back(parse_vertex_output(struct_name, attributes, type, name, array));
|
||||
parser.erase(attributes.scope());
|
||||
break;
|
||||
case SrtType::fragment_output:
|
||||
fragment_out.emplace_back(
|
||||
parse_fragment_output(struct_name, attributes, type, name, array));
|
||||
parser.erase(attributes.scope());
|
||||
break;
|
||||
case SrtType::fragment_input:
|
||||
fragment_in.emplace_back(
|
||||
parse_fragment_input(struct_name, attributes, type, name, array));
|
||||
parser.erase(attributes.scope());
|
||||
break;
|
||||
case SrtType::undefined:
|
||||
case SrtType::none:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
switch (srt_type) {
|
||||
case SrtType::resource_table:
|
||||
metadata_.resource_tables.emplace_back(srt);
|
||||
break;
|
||||
case SrtType::vertex_input:
|
||||
metadata_.vertex_inputs.emplace_back(vertex_in);
|
||||
break;
|
||||
case SrtType::vertex_output:
|
||||
metadata_.stage_interfaces.emplace_back(vertex_out);
|
||||
break;
|
||||
case SrtType::fragment_output:
|
||||
metadata_.fragment_outputs.emplace_back(fragment_out);
|
||||
break;
|
||||
case SrtType::fragment_input:
|
||||
metadata_.fragment_inputs.emplace_back(fragment_in);
|
||||
break;
|
||||
case SrtType::undefined:
|
||||
case SrtType::none:
|
||||
break;
|
||||
}
|
||||
|
||||
Token end_of_srt = body.back().prev();
|
||||
|
||||
if (srt_type == SrtType::resource_table) {
|
||||
/* Add static constructor.
|
||||
* These are only to avoid warnings on certain backend compilers. */
|
||||
string ctor;
|
||||
ctor += "\nstatic " + srt.name + " new_()\n";
|
||||
ctor += "{\n";
|
||||
ctor += " " + srt.name + " result;\n";
|
||||
if (has_srt_members == false) {
|
||||
ctor += " result._pad = 0;\n";
|
||||
}
|
||||
for (const auto &member : srt) {
|
||||
if (member.res_type == "resource_table") {
|
||||
ctor += " result." + member.var_name + " = " + member.var_type + "::new_();\n";
|
||||
}
|
||||
}
|
||||
ctor += " return result;\n";
|
||||
/* Avoid messing up the line count and keep empty struct empty. */
|
||||
ctor += "#line " + to_string(end_of_srt.line_number()) + "\n";
|
||||
ctor += "}\n";
|
||||
parser.insert_after(end_of_srt, ctor);
|
||||
|
||||
string access_macros;
|
||||
for (const auto &member : srt) {
|
||||
if (member.res_type == "resource_table") {
|
||||
access_macros += "#define access_" + srt.name + "_" + member.var_name + "() ";
|
||||
access_macros += member.var_type + "::new_()\n";
|
||||
}
|
||||
else {
|
||||
access_macros += "#define access_" + srt.name + "_" + member.var_name + "() ";
|
||||
access_macros += member.var_name + "\n";
|
||||
}
|
||||
}
|
||||
parser.insert_before(struct_tok, access_macros);
|
||||
parser.insert_before(struct_tok, get_create_info_placeholder(srt.name));
|
||||
|
||||
parser.insert_before(struct_tok, "\n");
|
||||
parser.insert_line_number(struct_tok.str_index_start() - 1, struct_tok.line_number());
|
||||
|
||||
/* Insert attribute so that method mutations know that this struct is an SRT. */
|
||||
parser.insert_before(struct_tok, "[[resource_table]] ");
|
||||
}
|
||||
});
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
541
blender-5.2.0/source/blender/gpu/shader_tool/scope.hh
Normal file
541
blender-5.2.0/source/blender/gpu/shader_tool/scope.hh
Normal file
@@ -0,0 +1,541 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "token.hh"
|
||||
#include "token_stream.hh"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace blender::gpu::shader::parser {
|
||||
|
||||
struct ScopeParser;
|
||||
|
||||
struct Scope {
|
||||
friend ScopeParser;
|
||||
|
||||
private:
|
||||
#ifndef NDEBUG
|
||||
/* String view for nicer debugging experience. Isn't actually used. */
|
||||
std::string_view token_view_;
|
||||
std::string_view str_view_;
|
||||
#endif
|
||||
/* Parser in which the scope resides. */
|
||||
const ParserBase *parser_;
|
||||
/* Scope index. */
|
||||
int64_t index_;
|
||||
|
||||
public:
|
||||
Scope() = delete;
|
||||
|
||||
Scope(const ParserBase &parser, int64_t index) : parser_(&parser), index_(index)
|
||||
{
|
||||
if (index < 0 || index >= parser.scope_types.size()) {
|
||||
index = parser.scope_types.size();
|
||||
return;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
IndexRange index_range = parser.scope_ranges[index];
|
||||
token_view_ = parser_->token_types_str().substr(index_range.start, index_range.size);
|
||||
str_view_ = parser_->substr((*parser_)[index_range.start], (*parser_)[index_range.last()]);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Create an invalid scope. */
|
||||
Scope(const ParserBase &parser) : Scope(parser, -1) {}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return index_ < parser_->scope_types.size();
|
||||
}
|
||||
bool is_invalid() const
|
||||
{
|
||||
return index_ >= parser_->scope_types.size();
|
||||
}
|
||||
|
||||
Token operator[](int i)
|
||||
{
|
||||
return is_invalid() ? Token(*parser_) : Token(*parser_, range().start + i);
|
||||
}
|
||||
|
||||
/* Return first token of that scope. */
|
||||
Token front() const
|
||||
{
|
||||
return is_invalid() ? Token(*parser_) : Token(*parser_, range().start);
|
||||
}
|
||||
|
||||
/* Return last token of that scope. */
|
||||
Token back() const
|
||||
{
|
||||
return is_invalid() ? Token(*parser_) : Token(*parser_, range().last());
|
||||
}
|
||||
|
||||
IndexRange range() const
|
||||
{
|
||||
return is_invalid() ? IndexRange(0, 0) : parser_->scope_ranges[index_];
|
||||
}
|
||||
|
||||
Token operator[](const int64_t index) const
|
||||
{
|
||||
return Token(*parser_, range().start + index);
|
||||
}
|
||||
|
||||
size_t token_count() const
|
||||
{
|
||||
return is_invalid() ? 0 : range().size;
|
||||
}
|
||||
|
||||
ScopeType type() const
|
||||
{
|
||||
return is_invalid() ? ScopeType::Invalid : ScopeType(parser_->scope_types[index_]);
|
||||
}
|
||||
|
||||
/* WORKAROUND: Only used for semantic tagging of scopes after parsing pass.
|
||||
* The type is only retained until the next parsing pass. */
|
||||
void set_type(ScopeType type)
|
||||
{
|
||||
const_cast<ParserBase *>(parser_)->scope_types[index_] = type;
|
||||
}
|
||||
|
||||
/* Returns the scope that contains this scope. */
|
||||
Scope scope() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return Scope(*parser_);
|
||||
}
|
||||
const size_t scope_start = this->front().str_index_start();
|
||||
Scope scope = *this;
|
||||
while ((scope = scope.prev()).is_valid()) {
|
||||
if (scope.back().str_index_last() > scope_start) {
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
/* Returns the parent node.
|
||||
* Equivalent to scope(). Should ultimately replace it. */
|
||||
Scope parent() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return Scope(*parser_);
|
||||
}
|
||||
return Scope(*parser_, parser_->scope_links[index_].parent_);
|
||||
}
|
||||
|
||||
Scope prev_neighbor() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return Scope(*parser_);
|
||||
}
|
||||
return Scope(*parser_, parser_->scope_links[index_].prev_);
|
||||
}
|
||||
|
||||
Scope next_neighbor() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return Scope(*parser_);
|
||||
}
|
||||
return Scope(*parser_, parser_->scope_links[index_].next_);
|
||||
}
|
||||
|
||||
Scope child_first() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return Scope(*parser_);
|
||||
}
|
||||
return Scope(*parser_, parser_->scope_links[index_].child_first_);
|
||||
}
|
||||
|
||||
/* Returns the previous scope before this scope. Can be either the container scope or the
|
||||
* previous scope inside the same container. */
|
||||
Scope prev() const
|
||||
{
|
||||
return is_invalid() ? Scope(*parser_) : front().prev().scope();
|
||||
}
|
||||
|
||||
/* Returns the next scope after this scope. Can be either the container scope or the next scope
|
||||
* inside the same container. */
|
||||
Scope next() const
|
||||
{
|
||||
return is_invalid() ? Scope(*parser_) : back().next().scope();
|
||||
}
|
||||
|
||||
bool contains(const Scope sub) const
|
||||
{
|
||||
Scope parent = sub.scope();
|
||||
while (parent.type() != ScopeType::Global && parent != *this) {
|
||||
parent = parent.scope();
|
||||
}
|
||||
return parent == *this;
|
||||
}
|
||||
|
||||
/* Returns true if scope contains the sub-string. */
|
||||
bool contains(const std::string &str) const
|
||||
{
|
||||
return this->str().find(str) != std::string::npos;
|
||||
}
|
||||
|
||||
std::string_view str_with_whitespace() const
|
||||
{
|
||||
if (this->is_invalid()) {
|
||||
return "";
|
||||
}
|
||||
return parser_->substr(front(), back(), true);
|
||||
}
|
||||
|
||||
std::string_view str() const
|
||||
{
|
||||
if (this->is_invalid()) {
|
||||
return "";
|
||||
}
|
||||
return parser_->substr(front(), back(), false);
|
||||
}
|
||||
|
||||
/* Return the content without the first and last token. */
|
||||
std::string_view str_exclusive() const
|
||||
{
|
||||
if (this->is_invalid() || this->token_count() <= 2) {
|
||||
return "";
|
||||
}
|
||||
return parser_->substr(front().next(), back().prev(), false);
|
||||
}
|
||||
|
||||
/* Return first occurrence of token_type inside this scope. */
|
||||
Token find_token(const char token_type) const
|
||||
{
|
||||
if (this->is_invalid()) {
|
||||
return Token(*parser_);
|
||||
}
|
||||
size_t pos = parser_->token_types_str().substr(range().start, range().size).find(token_type);
|
||||
return (pos != std::string::npos) ? Token(*parser_, range().start + pos) : Token(*parser_);
|
||||
}
|
||||
|
||||
bool contains_token(const char token_type) const
|
||||
{
|
||||
return find_token(token_type).is_valid();
|
||||
}
|
||||
|
||||
/* Return the first container scope that has the given type (including itself).
|
||||
* Returns invalid scope on failure. */
|
||||
Scope first_scope_of_type(const ScopeType type) const
|
||||
{
|
||||
Scope scope = *this;
|
||||
while (scope.type() != ScopeType::Global && scope.type() != type) {
|
||||
scope = scope.scope();
|
||||
}
|
||||
return scope.type() == type ? scope : Scope(*parser_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Small pattern matching engine.
|
||||
* - pattern is expected to a be a sequence of #TokenType stored as a string.
|
||||
* - single '?' after a token will make this token optional.
|
||||
* - double '?' will match the question mark.
|
||||
* - double '.' will skip to the end of the current matched scope.
|
||||
* - callback is called for each matches with a vector of token the size of the input pattern.
|
||||
* - control tokens ('..' and '?') and unmatched optional tokens will be set to invalid in match
|
||||
* vector.
|
||||
* IMPORTANT: 2 matches cannot overlap. The pattern matching algorithm skips the whole match
|
||||
* after a match there is no readback. This could eventually be fixed.
|
||||
*
|
||||
* If `include_preprocessor` is true, try to match any token. Otherwise ignore tokens in
|
||||
* preprocessor scopes.
|
||||
*
|
||||
* Callback should have this signature `void(const std::vector<Token>)`.
|
||||
*/
|
||||
template<bool include_preprocessor = false, typename CallbackFn>
|
||||
void foreach_match(const std::string &pattern, CallbackFn callback) const
|
||||
{
|
||||
assert(!pattern.empty());
|
||||
if (this->is_invalid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view scope_tokens = parser_->token_types_str().substr(range().start,
|
||||
range().size);
|
||||
|
||||
auto count_match = [](const std::string_view &s, const std::string_view &pattern) {
|
||||
size_t pos = 0, occurrences = 0;
|
||||
while ((pos = s.find(pattern, pos)) != std::string::npos) {
|
||||
occurrences += 1;
|
||||
pos += pattern.length();
|
||||
}
|
||||
return occurrences;
|
||||
};
|
||||
const int control_token_count = count_match(pattern, "?") * 2 + count_match(pattern, "..") * 2;
|
||||
|
||||
if (range().size < pattern.size() - control_token_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t searchable_range = scope_tokens.size() -
|
||||
(pattern.size() - 1 - control_token_count);
|
||||
|
||||
std::vector<Token> match(pattern.size(), Token(*parser_));
|
||||
|
||||
for (size_t pos = 0; pos < searchable_range; pos++) {
|
||||
size_t cursor = range().start + pos;
|
||||
|
||||
for (int i = 0; i < pattern.size(); i++) {
|
||||
bool is_last_token = i == pattern.size() - 1;
|
||||
TokenType token_type = TokenType(parser_->types_[cursor]);
|
||||
TokenType curr_search_token = TokenType(pattern[i]);
|
||||
TokenType next_search_token = TokenType(is_last_token ? '\0' : pattern[i + 1]);
|
||||
|
||||
/* Scope skipping. */
|
||||
if (!is_last_token && curr_search_token == '.' && next_search_token == '.') {
|
||||
cursor = match[i - 1].scope().back().index_;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Regular token. */
|
||||
if (curr_search_token == token_type) {
|
||||
match[i] = Token(*parser_, cursor++);
|
||||
}
|
||||
else if (curr_search_token == '?' && next_search_token != '?') {
|
||||
/* We just matched an optional token in previous iteration. Continue scanning. */
|
||||
match[i] = Token(*parser_);
|
||||
}
|
||||
else if (!is_last_token && curr_search_token != '?' && next_search_token == '?') {
|
||||
/* This was an optional token. Continue scanning. */
|
||||
match[i] = Token(*parser_);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
/* Token mismatch. Test next position. */
|
||||
break;
|
||||
}
|
||||
|
||||
if constexpr (!include_preprocessor) {
|
||||
if (match[i].scope().type() == ScopeType::Preprocessor) {
|
||||
/* Scope mismatch. Test next position. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_last_token) {
|
||||
callback(match);
|
||||
/* Avoid matching the same position if start of pattern is optional tokens. */
|
||||
pos = cursor - range().start - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will iterate over all the scopes that are direct children.
|
||||
* Callback should have this signature `void(Scope)`.
|
||||
*/
|
||||
template<typename CallbackFn> void foreach_scope(ScopeType type, CallbackFn callback) const
|
||||
{
|
||||
/* Makes no sense to iterate on global scope since it is the top level. */
|
||||
assert(type != ScopeType::Global);
|
||||
|
||||
if (this->is_invalid()) {
|
||||
return;
|
||||
}
|
||||
size_t pos = this->index_;
|
||||
while ((pos = parser_->scope_types_str.find(char(type), pos)) != std::string::npos) {
|
||||
Scope scope(*parser_, pos);
|
||||
if (scope.front().index_ > this->back().index_) {
|
||||
/* Found scope starts after this scope. End iteration. */
|
||||
break;
|
||||
}
|
||||
/* Make sure found scope is direct child of this scope. */
|
||||
Scope parent_scope = scope.scope();
|
||||
if (parent_scope.index_ == this->index_) {
|
||||
callback(scope);
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will iterate over all the attribute if this scope is an ScopeType::Attributes.
|
||||
* Callback should have this signature `void(Token attribute_name, Scope attribute_parameters)`.
|
||||
*/
|
||||
template<typename CallbackFn> void foreach_attribute(CallbackFn callback) const
|
||||
{
|
||||
assert(this->type() == ScopeType::Attributes);
|
||||
this->foreach_scope(ScopeType::Attribute, [&](Scope attr) {
|
||||
callback(attr[0], attr[1] == '(' ? attr[1].scope() : Scope(*parser_));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will iterate over all tokens of the scope (and its contained scopes).
|
||||
* Callback should have this signature `void(Token)`.
|
||||
*/
|
||||
template<typename Callback>
|
||||
void foreach_token(const TokenType token_type, Callback callback) const
|
||||
{
|
||||
IndexRange index_range = parser_->scope_ranges[index_];
|
||||
std::string_view view(parser_->token_types_str());
|
||||
|
||||
size_t offset = index_range.start;
|
||||
for (const char c : view.substr(index_range.start, index_range.size)) {
|
||||
if (token_type == TokenType(c)) {
|
||||
callback(Token(*parser_, offset));
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a callback for all the function scopes that are direct children of this scope.
|
||||
* Callback should have this signature
|
||||
* `void(bool is_static, Token type, Token name, Scope args, bool is_const, Scope body)`.
|
||||
*/
|
||||
template<typename Callback> void foreach_function(Callback callback) const
|
||||
{
|
||||
foreach_match("m?AA(..)c?{..}", [&](const std::vector<Token> matches) {
|
||||
callback(matches[0] == Static,
|
||||
matches[2],
|
||||
matches[3],
|
||||
matches[4].scope(),
|
||||
matches[8] == Const,
|
||||
matches[10].scope());
|
||||
});
|
||||
foreach_match("m?A<..>A(..)c?{..}", [&](const std::vector<Token> matches) {
|
||||
callback(matches[0] == Static,
|
||||
matches[2],
|
||||
matches[7],
|
||||
matches[8].scope(),
|
||||
matches[12] == Const,
|
||||
matches[14].scope());
|
||||
});
|
||||
foreach_match("m?AA::A(..)c?{..}", [&](const std::vector<Token> matches) {
|
||||
callback(matches[0] == Static,
|
||||
matches[2],
|
||||
matches[6],
|
||||
matches[7].scope(),
|
||||
matches[11] == Const,
|
||||
matches[13].scope());
|
||||
});
|
||||
foreach_match("m?AA<..>(..)c?{..}", [&](const std::vector<Token> matches) {
|
||||
callback(matches[0] == Static,
|
||||
matches[2],
|
||||
matches[3],
|
||||
matches[8].scope(),
|
||||
matches[12] == Const,
|
||||
matches[14].scope());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a callback for all the struct scopes that are direct children of this scope.
|
||||
* Callback should have this signature
|
||||
* `void(Token struct_tok, Scope attributes, Token name, Scope body)`.
|
||||
*/
|
||||
template<typename Callback> void foreach_struct(Callback callback) const
|
||||
{
|
||||
foreach_match("sA{..}", [&](const std::vector<Token> matches) {
|
||||
callback(matches[0], Scope(*parser_), matches[1], matches[2].scope());
|
||||
});
|
||||
foreach_match("sA<..>{..}", [&](const std::vector<Token> matches) {
|
||||
callback(matches[0], Scope(*parser_), matches[1], matches[6].scope());
|
||||
});
|
||||
foreach_match("s[[..]]A{..}", [&](const std::vector<Token> matches) {
|
||||
callback(matches[0], matches[2].scope(), matches[7], matches[8].scope());
|
||||
});
|
||||
foreach_match("s[[..]]A<..>{..}", [&](const std::vector<Token> matches) {
|
||||
callback(matches[0], matches[2].scope(), matches[7], matches[12].scope());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a callback for all the variable declarations (without assignment) that are direct children
|
||||
* Callback should have this signature
|
||||
* `void(Scope attributes,
|
||||
* Token const_tok,
|
||||
* Token type,
|
||||
* Scope template_scope,
|
||||
* Token name,
|
||||
* Scope array,
|
||||
* Token decl_end)`.
|
||||
*/
|
||||
template<typename Callback> void foreach_declaration(Callback callback) const
|
||||
{
|
||||
auto attrs = [&](const std::vector<Token> &tokens) {
|
||||
Token first = tokens[0].is_valid() ? tokens[0] : tokens[2];
|
||||
Scope attributes = first.prev().prev().scope();
|
||||
attributes = (attributes.type() == ScopeType::Attributes) ? attributes : Scope(*parser_);
|
||||
return attributes;
|
||||
};
|
||||
|
||||
auto cb = [&](Scope attributes,
|
||||
Token const_tok,
|
||||
Token type,
|
||||
Scope template_scope,
|
||||
Token name,
|
||||
Scope array,
|
||||
Token decl_end) {
|
||||
if (type.scope() != *this) {
|
||||
return;
|
||||
}
|
||||
callback(attributes, const_tok, type, template_scope, name, array, decl_end);
|
||||
};
|
||||
|
||||
Scope invalid(*parser_);
|
||||
|
||||
/* TODO(fclem): This is getting out of hand... */
|
||||
foreach_match("c?AA;", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], invalid, toks[3], invalid, toks.back());
|
||||
});
|
||||
foreach_match("c?AA[..];", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], invalid, toks[3], toks[4].scope(), toks.back());
|
||||
});
|
||||
foreach_match("c?AA[..][..];", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], invalid, toks[3], toks[4].scope(), toks.back());
|
||||
});
|
||||
foreach_match("c?A<..>A;", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], toks[3].scope(), toks[7], invalid, toks.back());
|
||||
});
|
||||
foreach_match("c?A<..>A[..];", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], toks[3].scope(), toks[7], toks[8].scope(), toks.back());
|
||||
});
|
||||
foreach_match("c?A<..>A[..][..];", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], toks[3].scope(), toks[7], toks[8].scope(), toks.back());
|
||||
});
|
||||
|
||||
foreach_match("c?A&A;", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], invalid, toks[4], invalid, toks.back());
|
||||
});
|
||||
foreach_match("c?A(&A)[..];", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], invalid, toks[5], toks[7].scope(), toks.back());
|
||||
});
|
||||
foreach_match("c?A(&A)[..][..];", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], invalid, toks[5], toks[7].scope(), toks.back());
|
||||
});
|
||||
foreach_match("c?A<..>&A;", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], toks[3].scope(), toks[8], invalid, toks.back());
|
||||
});
|
||||
foreach_match("c?A<..>(&A)[..];", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], toks[3].scope(), toks[9], toks[11].scope(), toks.back());
|
||||
});
|
||||
foreach_match("c?A<..>(&A)[..][..];", [&](const std::vector<Token> toks) {
|
||||
cb(attrs(toks), toks[0], toks[2], toks[3].scope(), toks[9], toks[11].scope(), toks.back());
|
||||
});
|
||||
}
|
||||
|
||||
bool operator==(const Scope &other) const
|
||||
{
|
||||
return this->index_ == other.index_ && this->parser_ == other.parser_;
|
||||
}
|
||||
bool operator!=(const Scope &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::gpu::shader::parser
|
||||
235
blender-5.2.0/source/blender/gpu/shader_tool/shader_tool.cc
Normal file
235
blender-5.2.0/source/blender/gpu/shader_tool/shader_tool.cc
Normal file
@@ -0,0 +1,235 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "processor.hh"
|
||||
|
||||
using namespace blender::gpu::shader;
|
||||
|
||||
static std::vector<std::string> list_files(const std::string &dir)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
for (const auto &entry : std::filesystem::directory_iterator(std::filesystem::path(dir))) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::string filename(entry.path().string());
|
||||
/* We only allow including header files or shader files. */
|
||||
if (filename.find(".hh") != std::string::npos ||
|
||||
filename.find(".msl") != std::string::npos ||
|
||||
filename.find(".glsl") != std::string::npos)
|
||||
{
|
||||
files.push_back(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
static metadata::Source scan_external_symbols(const std::vector<std::string> &file_list,
|
||||
std::vector<std::string> &visited_files,
|
||||
const std::string &file_buffer,
|
||||
const std::string &file_name)
|
||||
{
|
||||
Language language = language_from_filename(file_name);
|
||||
SourceProcessor processor(file_buffer, file_name, language);
|
||||
|
||||
metadata::Source include_data = processor.parse_include_and_symbols();
|
||||
|
||||
bool errors = false;
|
||||
|
||||
for (const auto &dep : include_data.dependencies) {
|
||||
std::string file;
|
||||
for (const auto &filename : file_list) {
|
||||
if (filename.find(dep) != std::string::npos) {
|
||||
file = filename;
|
||||
}
|
||||
}
|
||||
|
||||
if (file.empty()) {
|
||||
std::cout << "Error: Included file not found " << dep << std::endl;
|
||||
errors = true;
|
||||
}
|
||||
else if (std::find(visited_files.begin(), visited_files.end(), file) == visited_files.end()) {
|
||||
visited_files.emplace_back(file);
|
||||
|
||||
std::ifstream input_file(file);
|
||||
if (!input_file) {
|
||||
std::cerr << "Error: Could not open file " << file << std::endl;
|
||||
errors = true;
|
||||
}
|
||||
else {
|
||||
std::stringstream buffer;
|
||||
buffer << input_file.rdbuf();
|
||||
|
||||
metadata::Source source = scan_external_symbols(
|
||||
file_list, visited_files, buffer.str(), file);
|
||||
|
||||
/* Extend list. */
|
||||
include_data.symbol_table.insert(include_data.symbol_table.end(),
|
||||
source.symbol_table.begin(),
|
||||
source.symbol_table.end());
|
||||
include_data.template_definitions.insert(include_data.template_definitions.end(),
|
||||
source.template_definitions.begin(),
|
||||
source.template_definitions.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors) {
|
||||
exit(1);
|
||||
}
|
||||
return include_data;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
using namespace blender;
|
||||
if (argc < 6) {
|
||||
std::cerr << "Usage: shader_tool <data_file_from> <data_file_to> <metadata_file_to> "
|
||||
"<infos_file_to> <dep_file_to> <include_dir1> <include_dir2> ..."
|
||||
<< std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const char *input_file_name = argv[1];
|
||||
const char *output_file_name = argv[2];
|
||||
const char *metadata_file_name = argv[3];
|
||||
const char *infos_file_name = argv[4];
|
||||
const char *dep_file_name = argv[5];
|
||||
|
||||
/* Open the input file for reading */
|
||||
std::ifstream input_file(input_file_name);
|
||||
if (!input_file) {
|
||||
std::cerr << "Error: Could not open input file " << input_file_name << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* We make the required directories here rather than having the build system
|
||||
* do the work for us, as having cmake do it leads to several thousand cmake
|
||||
* instances being launched, leading to significant overhead, see pr #141404
|
||||
* for details. */
|
||||
std::filesystem::path parent_dir = std::filesystem::path(output_file_name).parent_path();
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::create_directories(parent_dir, ec)) {
|
||||
if (ec) {
|
||||
std::cerr << "Unable to create " << parent_dir << " : " << ec.message() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Open the output file for writing */
|
||||
std::ofstream output_file(output_file_name, std::ofstream::out | std::ofstream::binary);
|
||||
if (!output_file) {
|
||||
std::cerr << "Error: Could not open output file " << output_file_name << std::endl;
|
||||
input_file.close();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Open the output file for writing */
|
||||
std::ofstream metadata_file(metadata_file_name, std::ofstream::out | std::ofstream::binary);
|
||||
if (!metadata_file) {
|
||||
std::cerr << "Error: Could not open output file " << metadata_file_name << std::endl;
|
||||
input_file.close();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Open the output file for writing */
|
||||
std::ofstream infos_file(infos_file_name, std::ofstream::out | std::ofstream::binary);
|
||||
if (!infos_file) {
|
||||
std::cerr << "Error: Could not open output file " << infos_file_name << std::endl;
|
||||
input_file.close();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Open the output file for writing */
|
||||
std::ofstream dep_file(dep_file_name, std::ofstream::out | std::ofstream::binary);
|
||||
if (!dep_file) {
|
||||
std::cerr << "Error: Could not open output file " << dep_file_name << std::endl;
|
||||
input_file.close();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* List of files available for include. */
|
||||
std::vector<std::string> file_list;
|
||||
for (int i = 6; i < argc; i++) {
|
||||
auto list = list_files(std::string(argv[i]));
|
||||
/* Extend list. */
|
||||
file_list.insert(file_list.end(), list.begin(), list.end());
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << input_file.rdbuf();
|
||||
|
||||
std::string filename(input_file_name);
|
||||
const bool is_info = filename.ends_with("infos.hh") || filename.ends_with(".bsl.hh");
|
||||
|
||||
using namespace gpu::shader;
|
||||
|
||||
Language language = language_from_filename(filename);
|
||||
|
||||
if (language == Language::GLSL) {
|
||||
/* All build-time GLSL files should be considered blender-GLSL. */
|
||||
language = Language::BLENDER_GLSL;
|
||||
}
|
||||
|
||||
metadata::Source external_symbols;
|
||||
std::vector<std::string> visited_files{input_file_name};
|
||||
if (language == Language::BLENDER_GLSL) {
|
||||
external_symbols = scan_external_symbols(file_list, visited_files, buffer.str(), filename);
|
||||
}
|
||||
|
||||
/* Escape path according to the depfile syntax. */
|
||||
auto escape_path = [](std::string filepath) {
|
||||
size_t pos = 0;
|
||||
while ((pos = filepath.find(' ', pos)) != std::string::npos) {
|
||||
filepath.replace(pos, 1, "\\ ");
|
||||
pos += 2;
|
||||
}
|
||||
return filepath;
|
||||
};
|
||||
|
||||
dep_file << output_file_name << " : ";
|
||||
for (const auto &file : visited_files) {
|
||||
dep_file << escape_path(file) << " ";
|
||||
}
|
||||
dep_file << "\n";
|
||||
|
||||
SourceProcessor processor(buffer.str(), input_file_name, language);
|
||||
|
||||
auto [result, metadata, error] = processor.convert(external_symbols);
|
||||
|
||||
output_file << result;
|
||||
|
||||
/* TODO(fclem): Don't use regex for that. */
|
||||
size_t last_slash = filename.find_last_of('/');
|
||||
std::string name = (last_slash == std::string::npos) ? filename :
|
||||
filename.substr(last_slash + 1);
|
||||
std::string metadata_function_name = "metadata_" + name + "_tmp";
|
||||
std::ranges::replace(metadata_function_name, '.', '_');
|
||||
|
||||
metadata_file << metadata.serialize(metadata_function_name);
|
||||
if (is_info) {
|
||||
infos_file << metadata.serialize_infos();
|
||||
}
|
||||
|
||||
input_file.close();
|
||||
output_file.close();
|
||||
metadata_file.close();
|
||||
infos_file.close();
|
||||
dep_file.close();
|
||||
|
||||
if (error) {
|
||||
std::cerr << error.value().full_report << std::endl;
|
||||
}
|
||||
|
||||
return error.has_value() ? 1 : 0;
|
||||
}
|
||||
118
blender-5.2.0/source/blender/gpu/shader_tool/string.cc
Normal file
118
blender-5.2.0/source/blender/gpu/shader_tool/string.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
void SourceProcessor::lower_strings_sequences(Parser &parser)
|
||||
{
|
||||
do {
|
||||
parser().foreach_match("\"\"", [&](const vector<Token> &tokens) {
|
||||
string first(tokens[0].str());
|
||||
string second(tokens[1].str());
|
||||
string between = parser.substr_range_inclusive(tokens[0].str_index_last_no_whitespace() + 1,
|
||||
tokens[1].str_index_start() - 1);
|
||||
string trailing = parser.substr_range_inclusive(tokens[1].str_index_last_no_whitespace() + 1,
|
||||
tokens[1].str_index_last());
|
||||
string merged = first.substr(0, first.length() - 1) + second.substr(1) + between + trailing;
|
||||
parser.replace_try(tokens[0], tokens[1], merged);
|
||||
});
|
||||
} while (parser.apply_mutations());
|
||||
}
|
||||
|
||||
/* Turn assert into a printf. */
|
||||
void SourceProcessor::lower_assert(Parser &parser, [[maybe_unused]] const string &filename)
|
||||
{
|
||||
/* Example: `assert(i < 0)` > `if (!(i < 0)) { printf(...); }` */
|
||||
parser().foreach_match("A(..)", [&](const vector<Token> &tokens) {
|
||||
if (tokens[0].str() != "assert") {
|
||||
return;
|
||||
}
|
||||
string replacement;
|
||||
#ifdef WITH_GPU_SHADER_ASSERT
|
||||
string condition = string(tokens[1].scope().str());
|
||||
|
||||
auto escape = [](string s) {
|
||||
string result;
|
||||
for (char c : s) {
|
||||
if (c == '%') {
|
||||
result += "%%";
|
||||
}
|
||||
else if (c == '\\') {
|
||||
result += "\\\\";
|
||||
}
|
||||
else if (c == '\"') {
|
||||
result += "\\\"";
|
||||
}
|
||||
else {
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
replacement += "if (!" + condition + ") ";
|
||||
replacement += "{";
|
||||
replacement += " printf(\"";
|
||||
replacement += "Assertion failed: " + escape(condition) + ", ";
|
||||
replacement += "file " + filename + ", ";
|
||||
replacement += "line " + to_string(tokens[1].line_number()) + ", ";
|
||||
replacement += "thread (%u,%u,%u).\\n";
|
||||
replacement += "\"";
|
||||
replacement += ", GPU_THREAD.x, GPU_THREAD.y, GPU_THREAD.z); ";
|
||||
replacement += "}";
|
||||
#endif
|
||||
parser.replace(tokens[0], tokens[4], replacement);
|
||||
});
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/* Replace string literals by their hash and store the original string in the file metadata. */
|
||||
void SourceProcessor::lower_strings(Parser &parser)
|
||||
{
|
||||
parser().foreach_token(String, [&](const Token &token) {
|
||||
if (token.scope().type() == ScopeType::Preprocessor) {
|
||||
return;
|
||||
}
|
||||
uint32_t hash = hash_string(string(token.str()));
|
||||
metadata::PrintfFormat format = {hash, string(token.str())};
|
||||
metadata_.printf_formats.emplace_back(format);
|
||||
parser.replace(token, "string_t(" + to_string(hash) + "u)", true);
|
||||
});
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/* Change printf calls to "recursive" call to implementation functions.
|
||||
* This allows to emulate the variadic arguments of printf. */
|
||||
void SourceProcessor::lower_printf(Parser &parser)
|
||||
{
|
||||
parser().foreach_match("A(..)", [&](const vector<Token> &tokens) {
|
||||
if (tokens[0].str() != "printf") {
|
||||
return;
|
||||
}
|
||||
|
||||
int arg_count = 0;
|
||||
tokens[1].scope().foreach_scope(ScopeType::FunctionParam, [&](const Scope &) { arg_count++; });
|
||||
|
||||
string unrolled = "print_start(" + to_string(arg_count) + "u)";
|
||||
tokens[1].scope().foreach_scope(ScopeType::FunctionParam, [&](const Scope &attribute) {
|
||||
unrolled = "print_data(" + unrolled + ", " + string(attribute.str()) + ")";
|
||||
});
|
||||
|
||||
parser.replace(tokens.front(), tokens.back(), unrolled);
|
||||
});
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
543
blender-5.2.0/source/blender/gpu/shader_tool/struct.cc
Normal file
543
blender-5.2.0/source/blender/gpu/shader_tool/struct.cc
Normal file
@@ -0,0 +1,543 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
/* `class` -> `struct` */
|
||||
void SourceProcessor::lower_classes(Parser &parser)
|
||||
{
|
||||
parser().foreach_token(Class, [&](const Token &token) {
|
||||
if (token.prev() != Enum) {
|
||||
parser.replace(token, "struct ");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Search for constructor definition in active code. These are not supported. */
|
||||
void SourceProcessor::lint_constructors(Parser &parser)
|
||||
{
|
||||
parser().foreach_struct([&](Token, Scope, Token struct_name, Scope struct_scope) {
|
||||
struct_scope.foreach_match("A(..)", [&](const Tokens &t) {
|
||||
if (t[0].scope() != struct_scope) {
|
||||
return;
|
||||
}
|
||||
if (t[0].str() == struct_name.str()) {
|
||||
report_error(t[0], "Constructors are not supported.");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* Forward declaration of types are not supported and makes no sense in a shader program where
|
||||
* there is no pointers. */
|
||||
void SourceProcessor::lint_forward_declared_structs(Parser &parser)
|
||||
{
|
||||
parser().foreach_match("sA;", [&](const Tokens &t) {
|
||||
if (t[0].scope().type() == ScopeType::Global) {
|
||||
report_error(t[0], "Forward declaration of types are not supported.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Create default initializer (empty brace) for all classes. */
|
||||
void SourceProcessor::lower_default_constructors(Parser &parser)
|
||||
{
|
||||
unordered_set<string> builtin_types = {
|
||||
"bool32_t", "float2", "packed_float2", "float3", "packed_float3", "float4",
|
||||
"packed_float4", "float2x2", "float2x3", "float2x4", "float3x2", "float3x3",
|
||||
"float3x4", "float4x2", "float4x3", "float4x4", "float2x2", "float3x3",
|
||||
"float4x4", "int2", "int3", "packed_int3", "int4", "uint2",
|
||||
"uint3", "packed_uint3", "uint4", "bool2", "bool3", "bool4",
|
||||
};
|
||||
|
||||
parser().foreach_struct([&](Token, Scope attributes, Token name, Scope body) {
|
||||
/* Don't do host shared structures. */
|
||||
if (attributes.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int decl_count = 0;
|
||||
string decl;
|
||||
body.foreach_declaration([&](Scope, Token, Token type, Scope, Token name, Scope array, Token) {
|
||||
auto default_value = [&](const string_view type) -> string {
|
||||
if (type == "float") {
|
||||
return "0.0f";
|
||||
}
|
||||
if (type == "uint" || type == "uchar") {
|
||||
return "0u";
|
||||
}
|
||||
if (type == "int" || type == "char") {
|
||||
return "0";
|
||||
}
|
||||
if (type == "bool") {
|
||||
return "false";
|
||||
}
|
||||
if (builtin_types.contains(string(type))) {
|
||||
return string(type) + "(0)";
|
||||
}
|
||||
return string(type) + "{}";
|
||||
};
|
||||
|
||||
if (array.is_valid()) {
|
||||
int array_len = static_array_size(array, 0);
|
||||
if (array_len == 0) {
|
||||
decl += "for(int i=0;i < " + string(array.str_exclusive()) + ";i++){";
|
||||
decl += "r." + string(name.str()) + "[i]=" + default_value(type.str()) + ";";
|
||||
decl += "}";
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < array_len; i++) {
|
||||
decl += "r." + string(name.str()) + "[" + to_string(i) + "]";
|
||||
decl += "=" + default_value(type.str()) + ";";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Assigning members one by one as the foreach decl iterator can be out of order. */
|
||||
decl += "r." + string(name.str()) + "=" + default_value(type.str()) + ";";
|
||||
}
|
||||
decl_count++;
|
||||
});
|
||||
|
||||
if (decl_count == 0) {
|
||||
/* Empty struct will have a padding int. */
|
||||
decl += "r._pad=0;";
|
||||
}
|
||||
|
||||
decl = "static " + string(name.str()) + " ctor_() {" + string(name.str()) + " r;" + decl +
|
||||
"return r;}";
|
||||
|
||||
parser.insert_after(body.front().str_index_last_no_whitespace(), decl);
|
||||
});
|
||||
}
|
||||
|
||||
/* Make all members of a class to be referenced using `this->`. */
|
||||
void SourceProcessor::lower_implicit_member(Parser &parser)
|
||||
{
|
||||
parser().foreach_struct([&](Token, Scope, Token, Scope body) {
|
||||
vector<Token> members_tokens;
|
||||
vector<Token> methods_tokens;
|
||||
|
||||
auto is_class_token = [&](const vector<Token> &members, const string_view token) {
|
||||
for (const Token &member : members) {
|
||||
if (token == member.str()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto check_shadowing = [&](const Tokens &toks) {
|
||||
if (is_class_token(members_tokens, toks[1].str())) {
|
||||
report_error(toks[1], "Class member shadowing.");
|
||||
}
|
||||
};
|
||||
|
||||
body.foreach_declaration([&](Scope, Token, Token type, Scope, Token name, Scope, Token) {
|
||||
/* Do not match legacy infos in order to allow resource getter to work. */
|
||||
if (name.scope() == body && type.str() != "ShaderCreateInfo") {
|
||||
members_tokens.emplace_back(name);
|
||||
}
|
||||
});
|
||||
|
||||
body.foreach_function(
|
||||
[&](bool is_static, Token, Token fn_name, Scope fn_args, bool, Scope fn_body) {
|
||||
if (is_static) {
|
||||
return;
|
||||
}
|
||||
fn_args.foreach_match("AA", check_shadowing);
|
||||
fn_args.foreach_match("&A", check_shadowing);
|
||||
fn_body.foreach_match("AA", check_shadowing);
|
||||
fn_body.foreach_match("&A", check_shadowing);
|
||||
methods_tokens.emplace_back(fn_name);
|
||||
});
|
||||
|
||||
body.foreach_function([&](bool is_static, Token, Token, Scope, bool, Scope fn_body) {
|
||||
if (is_static) {
|
||||
return;
|
||||
}
|
||||
fn_body.foreach_token(Word, [&](Token tok) {
|
||||
if (!(tok.prev().prev() == '-' && tok.prev() == '>') && tok.prev() != Dot &&
|
||||
/* Reject namespace qualified symbols. */
|
||||
(tok.prev() != Colon || tok.prev().prev() != Colon))
|
||||
{
|
||||
bool is_method = tok.next() == '(';
|
||||
if (tok.next() == '<' && !tok.next().followed_by_whitespace()) {
|
||||
/* Might be templated method call. */
|
||||
is_method = tok.next().scope().back().next() == '(';
|
||||
}
|
||||
|
||||
if (!is_class_token(is_method ? methods_tokens : members_tokens, tok.str())) {
|
||||
return;
|
||||
}
|
||||
parser.insert_before(tok, "this->");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/* Move all method definition outside of struct definition blocks. */
|
||||
void SourceProcessor::lower_method_definitions(Parser &parser)
|
||||
{
|
||||
/* NOTE: We need to avoid the case of `a * this->b` being replaced as 2 dereferences. */
|
||||
|
||||
/* `(*this)` -> `(this_)` */
|
||||
parser().foreach_match("*T)", [&](const Tokens &t) { parser.replace(t[0], t[1], "this_"); });
|
||||
/* `return *this;` -> `return this_;` */
|
||||
parser().foreach_match("*T;", [&](const Tokens &t) { parser.replace(t[0], t[1], "this_"); });
|
||||
/* `this->` -> `this_.` */
|
||||
parser().foreach_match("T->", [&](const Tokens &t) { parser.replace(t[0], t[2], "this_."); });
|
||||
|
||||
parser.apply_mutations();
|
||||
|
||||
parser().foreach_match("sA:", [&](const Tokens &toks) {
|
||||
if (toks[2] == ':') {
|
||||
report_error(toks[2], "class inheritance is not supported");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
parser().foreach_match("cAA(..)c?{..}", [&](const Tokens &toks) {
|
||||
if (toks[0].prev() == Const) {
|
||||
report_error(toks[0],
|
||||
"function return type is marked `const` but it makes no sense for values "
|
||||
"and returning reference is not supported");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
/* Add `this` parameter and fold static keywords into function name. */
|
||||
parser().foreach_struct([&](Token struct_tok,
|
||||
Scope,
|
||||
const Token struct_name,
|
||||
const Scope struct_scope) {
|
||||
const Scope attributes = struct_tok.prev().scope();
|
||||
const bool is_resource_table = (attributes.type() == ScopeType::Subscript) &&
|
||||
(attributes.str() == "[[resource_table]]");
|
||||
|
||||
if (is_resource_table) {
|
||||
parser.replace(attributes, "");
|
||||
}
|
||||
|
||||
struct_scope.foreach_function(
|
||||
[&](bool is_static, Token fn_type, Token fn_name, Scope fn_args, bool is_const, Scope) {
|
||||
const Token static_tok = is_static ? fn_type.prev() : Token(parser);
|
||||
const Token const_tok = is_const ? fn_args.back().next() : Token(parser);
|
||||
|
||||
if (fn_name.str()[0] == '_') {
|
||||
report_error(fn_name, "function name starting with an underscore are reserved");
|
||||
}
|
||||
|
||||
if (is_static) {
|
||||
parser.replace(
|
||||
fn_name, string(struct_name.str()) + namespace_separator + string(fn_name.str()));
|
||||
/* WORKAROUND: Erase the static keyword as it conflicts with the wrapper class
|
||||
* member accesses MSL. */
|
||||
parser.erase(static_tok);
|
||||
}
|
||||
else {
|
||||
const bool has_no_args = fn_args.token_count() == 2;
|
||||
const char *suffix = (has_no_args ? "" : ", ");
|
||||
const string prefix = (is_resource_table ? "[[resource_table]] " : "");
|
||||
|
||||
/* Add a prefix to all member functions. */
|
||||
parser.insert_before(fn_name, method_call_prefix);
|
||||
|
||||
parser.erase(const_tok);
|
||||
if (is_const && !is_resource_table) {
|
||||
parser.insert_after(fn_args.front(),
|
||||
prefix + "const " + string(struct_name.str()) + " this_" +
|
||||
suffix);
|
||||
}
|
||||
else {
|
||||
parser.insert_after(fn_args.front(),
|
||||
prefix + string(struct_name.str()) + " &this_" + suffix);
|
||||
}
|
||||
|
||||
if (fn_name.str().length() > 1 &&
|
||||
(fn_name.str().find_first_not_of("xyzw") == string::npos ||
|
||||
fn_name.str().find_first_not_of("rgba") == string::npos))
|
||||
{
|
||||
report_error(fn_name, "Method name matching swizzles accessor are forbidden.");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
|
||||
/* Copy method functions outside of struct scope. */
|
||||
parser().foreach_struct([&](Token, Scope, const Token, const Scope struct_scope) {
|
||||
const Token struct_end = struct_scope.back().next();
|
||||
|
||||
int method_len = 0;
|
||||
struct_scope.foreach_function([&](bool, Token, Token, Scope, bool, Scope) { method_len++; });
|
||||
if (method_len == 0) {
|
||||
/* Avoid unnecessary preprocessor directives. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add prototypes to allow arbitrary order of definition inside a class.
|
||||
* Can be skipped if there is only one method. */
|
||||
if (method_len > 1) {
|
||||
/* First output prototypes. Not needed on metal because of wrapper class. */
|
||||
parser.insert_after(struct_end, "\n#ifndef GPU_METAL\n");
|
||||
struct_scope.foreach_function(
|
||||
[&](bool is_static, Token fn_type, Token, Scope fn_args, bool, Scope) {
|
||||
const Token fn_start = is_static ? fn_type.prev() : fn_type;
|
||||
|
||||
string proto_str = parser.substr_range_inclusive(fn_start, fn_args.back());
|
||||
proto_str = strip_whitespace(proto_str) + ";\n";
|
||||
Parser proto(proto_str, error_handler);
|
||||
|
||||
parser.insert_after(struct_end, proto.result_get());
|
||||
});
|
||||
parser.insert_after(struct_end, "#endif\n");
|
||||
}
|
||||
|
||||
struct_scope.foreach_function(
|
||||
[&](bool is_static, Token fn_type, Token, Scope, bool, Scope fn_body) {
|
||||
const Token fn_start = is_static ? fn_type.prev() : fn_type;
|
||||
|
||||
string fn_str = parser.substr_range_inclusive(fn_start, fn_body.back());
|
||||
fn_str = string(fn_start.char_number(), ' ') + fn_str + "\n";
|
||||
|
||||
parser.erase(fn_start, fn_body.back());
|
||||
parser.insert_line_number(struct_end, fn_start.line_number());
|
||||
parser.insert_after(struct_end, fn_str);
|
||||
});
|
||||
|
||||
parser.insert_line_number(struct_end, struct_end.line_number(true));
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/* Transform `a.fn(b)` into `fn(a, b)`. */
|
||||
void SourceProcessor::lower_method_calls(Parser &parser)
|
||||
{
|
||||
do {
|
||||
parser().foreach_scope(ScopeType::Function, [&](Scope scope) {
|
||||
scope.foreach_match(".A(", [&](const vector<Token> &tokens) {
|
||||
const Token dot = tokens[0];
|
||||
const Token func = tokens[1];
|
||||
const Token par_open = tokens[2];
|
||||
const Token end_of_this = dot.prev();
|
||||
Token start_of_this = end_of_this;
|
||||
while (true) {
|
||||
if (start_of_this == ')') {
|
||||
/* Function call. Take argument scope and function name. No recursion. */
|
||||
start_of_this = start_of_this.scope().front().prev();
|
||||
break;
|
||||
}
|
||||
if (start_of_this == ']') {
|
||||
/* Array subscript. Take scope and continue. */
|
||||
start_of_this = start_of_this.scope().front().prev();
|
||||
continue;
|
||||
}
|
||||
if (start_of_this == Word) {
|
||||
/* Member. */
|
||||
if (start_of_this.prev() == '.') {
|
||||
start_of_this = start_of_this.prev().prev();
|
||||
/* Continue until we find root member. */
|
||||
continue;
|
||||
}
|
||||
/* End of chain. */
|
||||
break;
|
||||
}
|
||||
report_error(start_of_this.line_number(),
|
||||
start_of_this.char_number(),
|
||||
start_of_this.line_str(),
|
||||
"lower_method_call parsing error");
|
||||
break;
|
||||
}
|
||||
string this_str = parser.substr_range_inclusive(start_of_this, end_of_this);
|
||||
string func_str = method_call_prefix + string(func.str());
|
||||
const bool has_no_arg = par_open.next() == ')';
|
||||
/* `a.fn(b)` -> `_fn(a, b)` */
|
||||
parser.replace_try(
|
||||
start_of_this, par_open, func_str + "(" + this_str + (has_no_arg ? "" : ", "));
|
||||
});
|
||||
});
|
||||
} while (parser.apply_mutations());
|
||||
}
|
||||
|
||||
void SourceProcessor::lower_structured_bindings(Parser &parser)
|
||||
{
|
||||
auto get_function_return_type = [&](string_view fn_name) {
|
||||
string return_type;
|
||||
parser().foreach_function([&](bool, Token type, Token name, Scope, bool, Scope) {
|
||||
if (name.str() != fn_name) {
|
||||
return;
|
||||
}
|
||||
return_type = type.str();
|
||||
});
|
||||
return return_type;
|
||||
};
|
||||
|
||||
auto get_argument_type = [&](Scope args, string_view arg_name) {
|
||||
string return_type;
|
||||
args.foreach_scope(ScopeType::FunctionArg, [&](Scope arg) {
|
||||
const Token name = arg.back();
|
||||
if (name == ']') {
|
||||
/* No array support for now. */
|
||||
return;
|
||||
}
|
||||
if (name.str() == arg_name) {
|
||||
Token type = name.prev() == '&' ? name.prev(2) : name.prev();
|
||||
return_type = type.str();
|
||||
}
|
||||
});
|
||||
return return_type;
|
||||
};
|
||||
|
||||
auto get_local_symbol_type = [&](Scope fn_body, Token symbol) {
|
||||
string return_type;
|
||||
string_view symbol_str = symbol.str();
|
||||
fn_body.foreach_token(Word, [&](Token tok) {
|
||||
if (tok != Word || tok.str() != symbol_str || tok.index_ >= symbol.index_) {
|
||||
return;
|
||||
}
|
||||
/* Check if it is in a visible scope. */
|
||||
Scope tok_scope = tok.scope();
|
||||
if (tok_scope != symbol.scope() && !tok_scope.contains(symbol.scope())) {
|
||||
return;
|
||||
}
|
||||
/* Check if it is a definition. */
|
||||
/* TODO(fclem): Comma declaration. */
|
||||
bool is_reference = tok.prev() == Ampersand;
|
||||
if (is_reference ? (tok.next() != '=' || tok.prev(2) != Word) : (tok.prev() != Word)) {
|
||||
return;
|
||||
}
|
||||
Token type_tok = tok.prev(is_reference ? 2 : 1);
|
||||
return_type = type_tok.str();
|
||||
});
|
||||
return return_type;
|
||||
};
|
||||
|
||||
auto get_struct_members = [&](string_view struct_name) {
|
||||
/* Search in symbol table first. */
|
||||
for (const auto &symbol : metadata_.symbol_table) {
|
||||
if (symbol.is_struct && symbol.identifier == struct_name) {
|
||||
return symbol.members;
|
||||
}
|
||||
}
|
||||
|
||||
/* Search symbols inside this file. This can help find instanciated structs. */
|
||||
vector<pair<string, string>> members;
|
||||
parser().foreach_match("sA{", [&](const Tokens &t) {
|
||||
if (t[1].str() != struct_name) {
|
||||
return;
|
||||
}
|
||||
Scope body = t.back().scope();
|
||||
body.foreach_declaration([&](Scope, Token, Token type, Scope, Token name, Scope, Token) {
|
||||
members.emplace_back(type.str(), name.str());
|
||||
});
|
||||
});
|
||||
return members;
|
||||
};
|
||||
|
||||
parser().foreach_function([&](bool, Token, Token, Scope args, bool, Scope body) {
|
||||
/* Unique index per binding to avoid to mess with scopes. */
|
||||
int index = 0;
|
||||
body.foreach_match("A[..]=", [&](const Tokens &t) {
|
||||
if (t[0].str() != "auto") {
|
||||
return;
|
||||
}
|
||||
Token symbol_name = t.back().next(1);
|
||||
/* For now only support expanding form a single function call. */
|
||||
if (symbol_name != Word) {
|
||||
report_error(symbol_name, "Expected either a function call or a single variable");
|
||||
return;
|
||||
}
|
||||
|
||||
Token after_symbol = Token::invalid(&parser);
|
||||
string struct_type;
|
||||
if (symbol_name.next(1) == '(') {
|
||||
after_symbol = symbol_name.next(1).scope().back().next();
|
||||
if (after_symbol != ';') {
|
||||
report_error(after_symbol, "Expected single function call");
|
||||
return;
|
||||
}
|
||||
struct_type = get_function_return_type(symbol_name.str());
|
||||
if (struct_type.empty()) {
|
||||
report_error(symbol_name,
|
||||
"Couldn't infer function return type. Can be caused by overload.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
after_symbol = symbol_name.next(1);
|
||||
if (after_symbol != ';') {
|
||||
report_error(after_symbol, "Expected single symbol to unpack");
|
||||
return;
|
||||
}
|
||||
struct_type = get_local_symbol_type(body, symbol_name);
|
||||
|
||||
if (struct_type.empty()) {
|
||||
struct_type = get_argument_type(args, symbol_name.str());
|
||||
}
|
||||
|
||||
if (struct_type.empty()) {
|
||||
report_error(symbol_name, "Couldn't infer local symbol type.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string struct_var = "_u" + to_string(index);
|
||||
|
||||
parser.replace(t[0], t[4], struct_type + " " + struct_var);
|
||||
|
||||
vector<pair<string, string>> struct_members = get_struct_members(struct_type);
|
||||
|
||||
if (struct_members.empty()) {
|
||||
report_error(symbol_name, "Couldn't find type to unpack.");
|
||||
return;
|
||||
}
|
||||
|
||||
Scope var_list = t[1].scope();
|
||||
string assignments = ";";
|
||||
int member_index = 0;
|
||||
var_list.foreach_token(Word, [&](Token tok) {
|
||||
if (struct_members.size() > member_index) {
|
||||
auto [member_type, member_name] = struct_members[member_index];
|
||||
assignments += member_type + " " + string(tok.str()) + "=" + struct_var + "." +
|
||||
member_name + ";";
|
||||
member_index++;
|
||||
}
|
||||
else {
|
||||
report_error(tok, "Too many parameters in structured binding");
|
||||
}
|
||||
});
|
||||
if (struct_members.size() != member_index) {
|
||||
report_error(t[4], "Missing parameters in structured binding");
|
||||
}
|
||||
/* Drop trailing semicolon. */
|
||||
assignments = assignments.substr(0, assignments.size() - 1);
|
||||
|
||||
parser.insert_after(after_symbol.prev(), assignments);
|
||||
index++;
|
||||
});
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
511
blender-5.2.0/source/blender/gpu/shader_tool/template.cc
Normal file
511
blender-5.2.0/source/blender/gpu/shader_tool/template.cc
Normal file
@@ -0,0 +1,511 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
string SourceProcessor::template_arguments_mangle(const Scope template_args)
|
||||
{
|
||||
string args_concat;
|
||||
template_args.foreach_scope(ScopeType::TemplateArg, [&](const Scope &scope) {
|
||||
string str;
|
||||
if (scope[1] == '<') {
|
||||
str = string(scope[0].str()) + template_arguments_mangle(scope[1].scope());
|
||||
}
|
||||
else {
|
||||
str = scope.str();
|
||||
}
|
||||
/* In order to support negative integer literals. Replace minus sign by underscore. */
|
||||
replace(str.begin(), str.end(), '-', '_');
|
||||
args_concat += 'T' + str;
|
||||
});
|
||||
return args_concat;
|
||||
}
|
||||
|
||||
string SourceProcessor::template_full_specified_name(metadata::TemplateDefinition &template_def)
|
||||
{
|
||||
SourceProcessor::Parser name_parser(template_def.name_space + template_def.identifier,
|
||||
error_handler);
|
||||
lower_scope_resolution_operators(name_parser);
|
||||
return name_parser.result_get();
|
||||
}
|
||||
|
||||
static void parse_template_definition_args(const Scope arg,
|
||||
vector<string> &arg_list,
|
||||
const Scope fn_args,
|
||||
bool &all_template_args_in_function_signature,
|
||||
ErrorHandler &error_handler)
|
||||
{
|
||||
const Token type = arg.front();
|
||||
const Token name = type.str() == "enum" ? type.next().next() : type.next();
|
||||
const string_view name_str = name.str();
|
||||
const string_view type_str = type.str();
|
||||
|
||||
arg_list.emplace_back(name_str);
|
||||
|
||||
if (type_str == "typename") {
|
||||
bool found = false;
|
||||
/* Search argument list for type-names. If type-name matches, the template argument is
|
||||
* present inside the function signature. */
|
||||
fn_args.foreach_match("AA", [&](const vector<Token> &tokens) {
|
||||
if (tokens[0].str() == name_str) {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
fn_args.foreach_match("A&A", [&](const vector<Token> &tokens) {
|
||||
if (tokens[0].str() == name_str) {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
all_template_args_in_function_signature &= found;
|
||||
}
|
||||
else if (type_str == "enum" || type_str == "bool") {
|
||||
/* Values cannot be resolved using type deduction. */
|
||||
all_template_args_in_function_signature = false;
|
||||
}
|
||||
else if (type_str == "int" || type_str == "uint" || type_str == "char" || type_str == "uchar" ||
|
||||
type_str == "short" || type_str == "ushort")
|
||||
{
|
||||
/* Values cannot be resolved using type deduction. */
|
||||
all_template_args_in_function_signature = false;
|
||||
}
|
||||
else {
|
||||
error_handler.report(type, "Invalid template argument type");
|
||||
}
|
||||
}
|
||||
|
||||
void SourceProcessor::lower_template_instantiation(
|
||||
SourceProcessor::Parser &parser,
|
||||
/* If method, the end token of the template inside the struct. */
|
||||
const Token method_end,
|
||||
const Token &inst_start,
|
||||
const Scope &inst_args,
|
||||
const metadata::TemplateDefinition template_def,
|
||||
const Token &symbol_name,
|
||||
const vector<string> &arg_list,
|
||||
const string &fn_decl,
|
||||
const bool all_template_args_in_function_signature)
|
||||
{
|
||||
/* Note that we do not use the full path as file identifier. So all names are unique. */
|
||||
string instance_filename = filepath_.substr(filepath_.find_last_of('/') + 1);
|
||||
string template_filename = template_def.filepath.substr(template_def.filepath.find_last_of('/') +
|
||||
1);
|
||||
/* Avoid adding noise in the source file if instance is inside the same file as declaration. */
|
||||
if (instance_filename == template_filename) {
|
||||
instance_filename = "";
|
||||
template_filename = "";
|
||||
}
|
||||
|
||||
/* Parse template values. */
|
||||
vector<pair<string, string>> arg_name_value_pairs;
|
||||
{
|
||||
int arg_count = 0;
|
||||
inst_args.foreach_scope(ScopeType::TemplateArg, [&](const Scope &arg) {
|
||||
if (arg_count < arg_list.size()) {
|
||||
arg_name_value_pairs.emplace_back(arg_list[arg_count], arg.str());
|
||||
}
|
||||
arg_count++;
|
||||
});
|
||||
if (arg_count != arg_list.size()) {
|
||||
report_error(inst_args.front(), "Invalid amount of argument in template instantiation.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Specialize template content. */
|
||||
string instance_content;
|
||||
{
|
||||
SourceProcessor::Parser instance_parser(fn_decl, error_handler);
|
||||
|
||||
/* Inject namespace around definition for symbols namespaces resolution. */
|
||||
if (template_def.name_space.empty()) {
|
||||
instance_parser.insert_before(instance_parser.front(), "\n");
|
||||
instance_parser.insert_after(instance_parser.back(), "\n");
|
||||
}
|
||||
else {
|
||||
/* Remove suffix "::". */
|
||||
string ns_name(template_def.name_space.substr(0, template_def.name_space.size() - 2));
|
||||
|
||||
if (template_def.is_method) {
|
||||
size_t split = ns_name.rfind("::");
|
||||
string struct_name;
|
||||
|
||||
if (split == string::npos) {
|
||||
struct_name = ns_name;
|
||||
ns_name = "";
|
||||
}
|
||||
else {
|
||||
struct_name = ns_name.substr(split + 2);
|
||||
ns_name = ns_name.substr(0, split);
|
||||
}
|
||||
if (!ns_name.empty()) {
|
||||
instance_parser.insert_before(instance_parser.front(), "namespace " + ns_name + " {");
|
||||
}
|
||||
instance_parser.insert_before(instance_parser.front(), "struct " + struct_name + " {\n");
|
||||
instance_parser.insert_after(instance_parser.back(), "\n};");
|
||||
if (!ns_name.empty()) {
|
||||
instance_parser.insert_after(instance_parser.back(), "}");
|
||||
}
|
||||
instance_parser.insert_after(instance_parser.back(), "\n");
|
||||
}
|
||||
else {
|
||||
instance_parser.insert_before(instance_parser.front(), "namespace " + ns_name + " {\n");
|
||||
instance_parser.insert_after(instance_parser.back(), "\n}\n");
|
||||
}
|
||||
}
|
||||
/* Insert line directive. Important for symbol namespace resolution and error logging.
|
||||
* Not using insert_line_number because it uses insert_after. */
|
||||
string line_str = "\n#line " + std::to_string(template_def.definition_line);
|
||||
if (!template_filename.empty()) {
|
||||
line_str += " \"" + template_filename + '\"';
|
||||
}
|
||||
instance_parser.insert_before(instance_parser.front(), line_str + "\n");
|
||||
|
||||
instance_parser().foreach_token(Word, [&](const Token &word) {
|
||||
string_view token_str = word.str();
|
||||
|
||||
/* Replace each parameter appearance inside the instance. */
|
||||
for (const auto &arg_name_value : arg_name_value_pairs) {
|
||||
if (token_str == arg_name_value.first) {
|
||||
instance_parser.replace(word, arg_name_value.second, true);
|
||||
}
|
||||
}
|
||||
/* Append template args after unspecified struct typename references.
|
||||
* `A func(A<T> b) {}` > `A<T> func(A<T> b) {}`. */
|
||||
if (template_def.is_struct && word.next() != AngleOpen && token_str == symbol_name.str()) {
|
||||
instance_parser.insert_after(word.str_index_last_no_whitespace(),
|
||||
SourceProcessor::template_arguments_mangle(inst_args));
|
||||
}
|
||||
});
|
||||
|
||||
/* Position of symbol name declaration. */
|
||||
size_t symbol_name_pos = instance_parser.str().find(" " + string(symbol_name.str()));
|
||||
|
||||
/* Append template args after function name if needed. This is required by BSL specification.
|
||||
* `void func() {}` > `void func<a, 1>() {}`. */
|
||||
if (!template_def.is_struct && !all_template_args_in_function_signature) {
|
||||
instance_parser.insert_after(symbol_name_pos + symbol_name.str().size(),
|
||||
SourceProcessor::template_arguments_mangle(inst_args));
|
||||
}
|
||||
/* Append namespace to symbol name because the appended mangled arguments (above) make
|
||||
* namespace resolution impossible. Methods do not need it because they are instanciated inside
|
||||
* their struct. They will get the correct namespace prefix (if they are static) later on. */
|
||||
if (!template_def.is_method) {
|
||||
instance_parser.insert_after(symbol_name_pos, string(template_def.name_space));
|
||||
}
|
||||
|
||||
instance_parser.apply_mutations();
|
||||
|
||||
lower_pre_template(instance_parser);
|
||||
|
||||
/* Paste template content in place of instantiation. */
|
||||
instance_content = instance_parser.result_get();
|
||||
/* Remove added first line from the injected namespace. */
|
||||
instance_content = instance_content.substr(instance_content.find_first_of('\n') + 1);
|
||||
if (template_def.is_method) {
|
||||
/* Remove added last line from the injected struct + namespace. */
|
||||
instance_content = instance_content.substr(
|
||||
0, instance_content.find_last_of('\n', instance_content.size() - 2) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const Token inst_end = inst_start.find_next(SemiColon);
|
||||
/* Method are put back in their classes. */
|
||||
const Token insert_at = template_def.is_method ? method_end : inst_end;
|
||||
|
||||
/* Insert instantiation content. Instance line directived was already added. */
|
||||
parser.insert_after(insert_at, instance_content);
|
||||
parser.insert_line_number(insert_at, insert_at.line_number(true), instance_filename);
|
||||
}
|
||||
|
||||
void SourceProcessor::lower_template_dependent_names(Parser &parser)
|
||||
{
|
||||
parser().foreach_match("tA<..>", [&](const Tokens &toks) {
|
||||
if (toks[0].prev() == '.' || (toks[0].prev().prev() == '-' && toks[0].prev() == '>')) {
|
||||
parser.erase(toks[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SourceProcessor::lower_pre_template(Parser &parser)
|
||||
{
|
||||
/* Lower noop attributes after linting them. */
|
||||
lower_maybe_unused(parser);
|
||||
/* Lint and remove C++ accessor templates before lowering template. */
|
||||
lower_srt_accessor_templates(parser);
|
||||
lower_union_accessor_templates(parser);
|
||||
/* Lower namespaces. */
|
||||
lower_using(parser);
|
||||
lower_namespaces(parser);
|
||||
lower_scope_resolution_operators(parser);
|
||||
|
||||
lower_template_calls(parser);
|
||||
lower_template_specialization(parser);
|
||||
}
|
||||
|
||||
/* Mangle template parameter into the symbol name. */
|
||||
void SourceProcessor::lower_template_calls(Parser &parser)
|
||||
{
|
||||
/* Process templated function calls first to avoid matching them later. */
|
||||
parser().foreach_match("A<..>(..)", [&](const vector<Token> &tokens) {
|
||||
const Scope template_args = tokens[1].scope();
|
||||
template_args.foreach_match("A<..>", [&parser](const vector<Token> &tokens) {
|
||||
parser.replace(tokens[1].scope(), template_arguments_mangle(tokens[1].scope()), true);
|
||||
});
|
||||
});
|
||||
/* Likewise, process templated struct method definitions. */
|
||||
parser().foreach_match("A<..>A<", [&](const vector<Token> &tokens) {
|
||||
parser.replace(tokens[1].scope(), template_arguments_mangle(tokens[1].scope()), true);
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
void SourceProcessor::lower_template_specialization(Parser &parser)
|
||||
{
|
||||
auto process_specialization = [&](const Token specialization_start, const Scope template_args) {
|
||||
parser.erase(specialization_start, specialization_start.next().next());
|
||||
parser.replace(template_args, template_arguments_mangle(template_args), true);
|
||||
};
|
||||
|
||||
parser().foreach_match("t<>AA<", [&](const vector<Token> &tokens) {
|
||||
process_specialization(tokens[0], tokens[5].scope());
|
||||
});
|
||||
|
||||
parser().foreach_match("t<>sA<..>", [&](const vector<Token> &tokens) {
|
||||
process_specialization(tokens[0], tokens[5].scope());
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
void SourceProcessor::process_template_struct(metadata::TemplateDefinition &template_def,
|
||||
SourceProcessor::Parser &parser)
|
||||
{
|
||||
struct DefinitionParser {
|
||||
SourceProcessor::Parser def_parser;
|
||||
const Scope template_scope = def_parser[1].scope();
|
||||
const Token struct_start = template_scope.back().next();
|
||||
const Token struct_name = struct_start.next();
|
||||
const Scope struct_body = struct_name.next().scope();
|
||||
const Token struct_end = struct_body.back().next();
|
||||
const string struct_decl = def_parser.substr_range_inclusive(struct_start, struct_end);
|
||||
bool all_template_args_in_function_signature = false;
|
||||
vector<string> arg_list;
|
||||
|
||||
/* Parse template declaration. */
|
||||
DefinitionParser(metadata::TemplateDefinition &template_def, ErrorHandler report_error)
|
||||
: def_parser(template_def.definition, report_error)
|
||||
{
|
||||
template_scope.foreach_scope(ScopeType::TemplateArg, [&](Scope arg) {
|
||||
parse_template_definition_args(arg,
|
||||
arg_list,
|
||||
Scope(def_parser),
|
||||
all_template_args_in_function_signature,
|
||||
report_error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* Only parse if there is an instantiation. */
|
||||
unique_ptr<DefinitionParser> def_parser;
|
||||
/* Since we already lowered the namespaces in main parser, we need to search for the namespace
|
||||
* resolved symbol name. */
|
||||
const string full_specified_name = template_full_specified_name(template_def);
|
||||
/* Replace instantiations. */
|
||||
parser().foreach_match("tsA<", [&](const vector<Token> &tokens) {
|
||||
if (full_specified_name != tokens[2].str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!def_parser) {
|
||||
def_parser = make_unique<DefinitionParser>(template_def, error_handler);
|
||||
}
|
||||
|
||||
lower_template_instantiation(parser,
|
||||
Token::invalid(&parser),
|
||||
tokens[0],
|
||||
tokens[3].scope(),
|
||||
template_def,
|
||||
def_parser->struct_name,
|
||||
def_parser->arg_list,
|
||||
def_parser->struct_decl,
|
||||
def_parser->all_template_args_in_function_signature);
|
||||
});
|
||||
}
|
||||
|
||||
void SourceProcessor::process_template_function(
|
||||
metadata::TemplateDefinition &template_def,
|
||||
SourceProcessor::Parser &parser,
|
||||
/* If method, the end token of the template inside the struct. */
|
||||
const Token method_end)
|
||||
{
|
||||
struct DefinitionParser {
|
||||
SourceProcessor::Parser def_parser;
|
||||
const Scope template_scope = def_parser[1].scope();
|
||||
const Token fn_start = template_scope.back().next();
|
||||
/* Skip attributes */
|
||||
const Token after_attr = fn_start == SquareOpen ? fn_start.scope().back().next() : fn_start;
|
||||
const Scope fn_args = after_attr.find_next(ParOpen).scope();
|
||||
const Token fn_name = fn_args.front().prev();
|
||||
const Token fn_end = fn_args.back().find_next(BracketOpen).scope().back();
|
||||
const string fn_decl = def_parser.substr_range_inclusive(fn_start, fn_end);
|
||||
|
||||
bool all_template_args_in_function_signature = true;
|
||||
vector<string> arg_list;
|
||||
|
||||
/* Parse template declaration. */
|
||||
DefinitionParser(metadata::TemplateDefinition &template_def, ErrorHandler report_error)
|
||||
: def_parser(template_def.definition, report_error)
|
||||
{
|
||||
assert(fn_start.is_valid() && fn_name.is_valid() && fn_args.is_valid() &&
|
||||
template_scope.is_valid() && fn_end.is_valid());
|
||||
|
||||
template_scope.foreach_scope(ScopeType::TemplateArg, [&](Scope arg) {
|
||||
parse_template_definition_args(
|
||||
arg, arg_list, fn_args, all_template_args_in_function_signature, report_error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* Only parse if there is an instantiation. */
|
||||
unique_ptr<DefinitionParser> def_parser;
|
||||
/* Since we already lowered the namespaces in main parser, we need to search for the namespace
|
||||
* resolved symbol name. */
|
||||
const string full_specified_name = template_full_specified_name(template_def);
|
||||
/* Replace instantiations. */
|
||||
parser().foreach_match("tAA<", [&](const vector<Token> &tokens) {
|
||||
if (full_specified_name != tokens[2].str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!def_parser) {
|
||||
def_parser = make_unique<DefinitionParser>(template_def, error_handler);
|
||||
}
|
||||
|
||||
lower_template_instantiation(parser,
|
||||
method_end,
|
||||
tokens[0],
|
||||
tokens[3].scope(),
|
||||
template_def,
|
||||
def_parser->fn_name,
|
||||
def_parser->arg_list,
|
||||
def_parser->fn_decl,
|
||||
def_parser->all_template_args_in_function_signature);
|
||||
});
|
||||
}
|
||||
|
||||
void SourceProcessor::lower_templates(Parser &parser)
|
||||
{
|
||||
/* Lint missing template arguments in instantiation and specialization.
|
||||
* This is required by the BSL spec in order to simplify implementation. */
|
||||
auto lint_explicit = [&](const Token symbol_name) {
|
||||
if (symbol_name.next().scope().type() != parser::ScopeType::Template) {
|
||||
report_error(
|
||||
symbol_name,
|
||||
"Template instantiation and specialization require explicit template arguments");
|
||||
}
|
||||
};
|
||||
parser().foreach_match("t<>AA", [&](const vector<Token> &toks) { lint_explicit(toks[4]); });
|
||||
parser().foreach_match("t<>A<..>A", [&](const vector<Token> &toks) { lint_explicit(toks[8]); });
|
||||
parser().foreach_match("tAA", [&](const vector<Token> &toks) { lint_explicit(toks[2]); });
|
||||
parser().foreach_match("tA<..>A", [&](const vector<Token> &toks) { lint_explicit(toks[6]); });
|
||||
|
||||
/* Delete templated struct and function definitions (not methods! see later).
|
||||
* They were already parsed by `SourceProcessor::parse_namespace_symbols`. */
|
||||
bool error = false;
|
||||
parser().foreach_match("t<..>", [&](const vector<Token> &toks) {
|
||||
/* Only process global templates, not methods. */
|
||||
if (toks[0].scope() != parser()) {
|
||||
return;
|
||||
}
|
||||
/* Default arguments are not supported. */
|
||||
toks[1].scope().foreach_token(Assign, [&](Token tok) {
|
||||
report_error(tok, "Default arguments are not supported inside template declaration");
|
||||
error = true;
|
||||
});
|
||||
|
||||
Token end = toks[0].find_next(BracketOpen).scope().back();
|
||||
if (toks[4].next() == Struct) {
|
||||
/* Capture end semicolon. */
|
||||
end = end.next();
|
||||
}
|
||||
/* This can fail as it might try to erase templated method inside templated struct. */
|
||||
parser.erase_try(toks[0], end);
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
|
||||
/* Deduplicate symbols (can happen because the main file is parsed twice). */
|
||||
unordered_map<string, TemplateDefinition> unique_symbols;
|
||||
for (const auto &symbol : metadata_.template_definitions) {
|
||||
unique_symbols.try_emplace(symbol.name_space + symbol.identifier, symbol);
|
||||
}
|
||||
|
||||
/* Process struct first, so methods can instantiate inside them. */
|
||||
for (auto [_, template_def] : unique_symbols) {
|
||||
if (template_def.is_struct) {
|
||||
process_template_struct(template_def, parser);
|
||||
}
|
||||
}
|
||||
parser.apply_mutations();
|
||||
|
||||
/* Then process methods. We can only process methods if their struct exists in the same file.
|
||||
* This holds true for instanciated struct templates. */
|
||||
parser().foreach_struct([&](Token, Scope, Token, Scope body) {
|
||||
body.foreach_match("t<..>", [&](const vector<Token> &toks) {
|
||||
/* Since this can be an instanciated struct, we need to make sure to instanciate its own
|
||||
* methods. Hence the need to parse the definition. */
|
||||
TemplateDefinition template_def = parse_template_definition(
|
||||
parser, toks[0], true, toks[0].scope(), filepath_);
|
||||
/* Insertion point of the instantiation. */
|
||||
Token method_end = toks[0].find_next(BracketOpen).scope().back();
|
||||
|
||||
process_template_function(template_def, parser, method_end);
|
||||
/* Delete definitions. */
|
||||
parser.erase(toks[0], method_end);
|
||||
});
|
||||
});
|
||||
|
||||
/* For each template definition, process all instantiation. */
|
||||
for (auto [_, template_def] : unique_symbols) {
|
||||
if (!template_def.is_struct && !template_def.is_method) {
|
||||
process_template_function(template_def, parser, Token::invalid(&parser));
|
||||
}
|
||||
}
|
||||
parser.apply_mutations();
|
||||
|
||||
/* Remove template instantiation afterward. */
|
||||
parser().foreach_token(Template, [&](const Token &tok) {
|
||||
if (tok.next() == '<') {
|
||||
report_error(tok, "Invalid template definition");
|
||||
}
|
||||
else {
|
||||
parser.erase(tok, tok.find_next(SemiColon));
|
||||
}
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
|
||||
/* Process calls to templated types or functions. */
|
||||
parser().foreach_match("A<..>", [&](const vector<Token> &tokens) {
|
||||
parser.replace(tokens[1].scope(), template_arguments_mangle(tokens[1].scope()), true);
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
35
blender-5.2.0/source/blender/gpu/shader_tool/time_it.hh
Normal file
35
blender-5.2.0/source/blender/gpu/shader_tool/time_it.hh
Normal file
@@ -0,0 +1,35 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tools
|
||||
*
|
||||
* Lite duplicates of blender utility types and other utility functions.
|
||||
* They are duplicated to avoid pulling half of blender as a dependency.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace blender::gpu::shader::parser {
|
||||
|
||||
struct TimeIt {
|
||||
using Duration = std::chrono::microseconds;
|
||||
|
||||
Duration &time;
|
||||
std::chrono::high_resolution_clock::time_point start;
|
||||
|
||||
TimeIt(Duration &time) : time(time)
|
||||
{
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
~TimeIt()
|
||||
{
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::gpu::shader::parser
|
||||
190
blender-5.2.0/source/blender/gpu/shader_tool/token.hh
Normal file
190
blender-5.2.0/source/blender/gpu/shader_tool/token.hh
Normal file
@@ -0,0 +1,190 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "token_stream.hh"
|
||||
|
||||
namespace blender::gpu::shader::parser {
|
||||
|
||||
struct Scope;
|
||||
|
||||
/**
|
||||
* Semantic token adding access to ParserBase data.
|
||||
* It is also safer than lexit::Token as invalid token will not result invalid behavior.
|
||||
*/
|
||||
struct Token : lexit::Token {
|
||||
Token(const lexit::Token &tok) : lexit::Token(tok) {}
|
||||
|
||||
Token(const ParserBase &data, int64_t index) : lexit::Token(&data, index) {}
|
||||
/* Create an invalid token. */
|
||||
Token(const ParserBase &data) : lexit::Token(&data, -1) {}
|
||||
|
||||
Token prev(int i = 1) const
|
||||
{
|
||||
return static_cast<const lexit::Token *>(this)->prev(i);
|
||||
}
|
||||
Token next(int i = 1) const
|
||||
{
|
||||
return static_cast<const lexit::Token *>(this)->next(i);
|
||||
}
|
||||
|
||||
Token find_next(TokenType type) const
|
||||
{
|
||||
Token tok = this->next();
|
||||
while (tok.is_valid() && tok != type) {
|
||||
tok = tok.next();
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
/* Return start of namespace identifier if the token is part of one. */
|
||||
Token namespace_start() const
|
||||
{
|
||||
if (*this != Word) {
|
||||
return *this;
|
||||
}
|
||||
/* Scan back identifier that could contain namespaces. */
|
||||
Token tok = *this;
|
||||
while (tok.is_valid()) {
|
||||
if (tok.prev() == ':' && tok.prev(2) == ':') {
|
||||
tok = tok.prev(3);
|
||||
}
|
||||
else {
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
/* For a word, return the name containing the prefix namespaces if present. */
|
||||
std::string full_symbol_name() const
|
||||
{
|
||||
size_t start = namespace_start().str_index_start();
|
||||
size_t end = str_index_last_no_whitespace();
|
||||
return std::string(buf_->str_.substr(start, end - start + 1));
|
||||
}
|
||||
|
||||
/* Returns the scope that contains this token. */
|
||||
Scope scope() const;
|
||||
|
||||
size_t str_index_start() const
|
||||
{
|
||||
return buf_->offsets_[index_];
|
||||
}
|
||||
|
||||
size_t str_index_last() const
|
||||
{
|
||||
return buf_->offsets_[index_ + 1] - 1;
|
||||
}
|
||||
|
||||
size_t str_index_last_no_whitespace() const
|
||||
{
|
||||
return buf_->offsets_end_[index_] - 1;
|
||||
}
|
||||
|
||||
/* Index of the first character of the line this token is. */
|
||||
size_t line_start() const
|
||||
{
|
||||
size_t pos = buf_->str_.rfind('\n', str_index_start());
|
||||
return (pos == std::string::npos) ? 0 : (pos + 1);
|
||||
}
|
||||
|
||||
/* Index of the last character of the line this token is, excluding `\n`. */
|
||||
size_t line_end() const
|
||||
{
|
||||
size_t pos = buf_->str_.find('\n', str_index_start());
|
||||
return (pos == std::string::npos) ? (buf_->str_.size() - 1) : (pos - 1);
|
||||
}
|
||||
|
||||
std::string_view str_with_whitespace() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return "";
|
||||
}
|
||||
return static_cast<const lexit::Token *>(this)->str_with_whitespace();
|
||||
}
|
||||
|
||||
std::string_view str() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return "";
|
||||
}
|
||||
return static_cast<const lexit::Token *>(this)->str();
|
||||
}
|
||||
|
||||
/* Return the line number this token is found at. Take into account the #line directives.
|
||||
* If `at_end` is true, return the line number after this token. */
|
||||
size_t line_number(bool at_end = false) const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return 0;
|
||||
}
|
||||
int index = at_end ? str_index_last() : str_index_last_no_whitespace();
|
||||
int line_num = parser::line_number(buf_->str_, index);
|
||||
/* Add the last char (not counted by line_number). */
|
||||
return line_num + int(at_end && buf_->str_[index] == '\n');
|
||||
}
|
||||
|
||||
/* Return the offset to the start of the line. */
|
||||
size_t char_number() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return 0;
|
||||
}
|
||||
return parser::char_number(buf_->str_, str_index_start());
|
||||
}
|
||||
|
||||
/* Return the name of the file containing the token. */
|
||||
std::string filename() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return "";
|
||||
}
|
||||
return parser::filename(buf_->str_, str_index_start());
|
||||
}
|
||||
|
||||
/* Return the line the token is at. */
|
||||
std::string line_str() const
|
||||
{
|
||||
return parser::line_str(buf_->str_, str_index_start());
|
||||
}
|
||||
|
||||
TokenType type() const
|
||||
{
|
||||
if (is_invalid()) {
|
||||
return Invalid;
|
||||
}
|
||||
return TokenType(buf_->types_[index_]);
|
||||
}
|
||||
|
||||
/* Return the attribute scope before this token if it exists. */
|
||||
Scope attribute_before() const;
|
||||
/* Return the attribute scope after this token if it exists. */
|
||||
Scope attribute_after() const;
|
||||
|
||||
bool operator==(TokenType type) const
|
||||
{
|
||||
return this->type() == type;
|
||||
}
|
||||
bool operator!=(TokenType type) const
|
||||
{
|
||||
return !(*this == type);
|
||||
}
|
||||
bool operator==(char type) const
|
||||
{
|
||||
return *this == TokenType(type);
|
||||
}
|
||||
bool operator!=(char type) const
|
||||
{
|
||||
return *this != TokenType(type);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::gpu::shader::parser
|
||||
147
blender-5.2.0/source/blender/gpu/shader_tool/token_stream.hh
Normal file
147
blender-5.2.0/source/blender/gpu/shader_tool/token_stream.hh
Normal file
@@ -0,0 +1,147 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "enums.hh"
|
||||
#include "utils.hh"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace blender::gpu::shader::parser {
|
||||
|
||||
struct Token;
|
||||
struct Scope;
|
||||
|
||||
/**
|
||||
* Turns string into token.
|
||||
*/
|
||||
struct LexerBase : lexit::TokenBuffer {
|
||||
static const std::array<CharClass, 128> bsl_char_class_table;
|
||||
static const std::array<CharClass, 128> default_char_class_table;
|
||||
|
||||
/** Compact visualization of token_types. */
|
||||
std::string_view token_types_str() const
|
||||
{
|
||||
return std::string_view((const char *)types_.get(), size_);
|
||||
}
|
||||
|
||||
/* Change words into keyword (ex: `if`, `struct`, `template`). */
|
||||
void identify_keywords();
|
||||
/* Change angle bracket tokens into template tokens if they match template condition. */
|
||||
void identify_template_tokens();
|
||||
/* Undo the changes from identify_template_tokens. */
|
||||
void reset_template_tokens();
|
||||
};
|
||||
|
||||
/**
|
||||
* Only support rough tokenization.
|
||||
*/
|
||||
struct SimpleLexer {
|
||||
static void lexical_analysis(LexerBase &lex, std::string_view input)
|
||||
{
|
||||
lex.process(input, LexerBase::bsl_char_class_table.data());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Identify BSL keywords, and correctly identify float literals.
|
||||
*/
|
||||
struct FullLexer {
|
||||
static void lexical_analysis(LexerBase &lex, std::string_view input)
|
||||
{
|
||||
lex.process(input, LexerBase::bsl_char_class_table.data());
|
||||
lex.merge_complex_literals();
|
||||
lex.identify_keywords();
|
||||
}
|
||||
};
|
||||
|
||||
struct ParserBase;
|
||||
|
||||
struct ScopeLinks {
|
||||
/* All in scope indices. */
|
||||
int parent_ = -1;
|
||||
int prev_ = -1;
|
||||
int next_ = -1;
|
||||
int child_first_ = -1;
|
||||
int child_last_ = -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create semantic scopes from token stream.
|
||||
* Also creates mapping table from token to scope to have bi-directional mapping.
|
||||
*/
|
||||
struct ParserBase : LexerBase {
|
||||
|
||||
/** Compact visualization of scope_types. */
|
||||
std::string_view scope_types_str;
|
||||
|
||||
/* --- Structure of Array style data for scopes. --- */
|
||||
|
||||
/** Range of token per scope. */
|
||||
std::vector<ScopeType> scope_types;
|
||||
/** Range of token per scope. */
|
||||
std::vector<IndexRange> scope_ranges;
|
||||
/** Index of adjacent scopes. */
|
||||
std::vector<ScopeLinks> scope_links;
|
||||
/** Index of bottom most scope per token. */
|
||||
std::vector<int> token_scope;
|
||||
|
||||
/* Return the i'th token. */
|
||||
Token operator[](int i) const;
|
||||
|
||||
void build_scope_tree(ErrorHandler &err_handler);
|
||||
void build_token_to_scope_map();
|
||||
|
||||
private:
|
||||
void update_string_view();
|
||||
};
|
||||
|
||||
;
|
||||
|
||||
/* Don't do anything. No access to scopes is allowed. */
|
||||
struct NullParser {
|
||||
static void semantic_analysis(ParserBase &parser, ErrorHandler & /*err_handler*/)
|
||||
{
|
||||
parser.scope_types = {};
|
||||
parser.scope_ranges = {};
|
||||
}
|
||||
};
|
||||
|
||||
/* Do not parse. Creates a single global scope containing all tokens. */
|
||||
struct DummyParser {
|
||||
static void semantic_analysis(ParserBase &parser, ErrorHandler & /*err_handler*/)
|
||||
{
|
||||
parser.scope_types = {ScopeType::Global};
|
||||
parser.scope_ranges = {IndexRange(0, parser.size())};
|
||||
parser.build_token_to_scope_map();
|
||||
}
|
||||
};
|
||||
|
||||
struct FullParser {
|
||||
static void semantic_analysis(ParserBase &parser, ErrorHandler &err_handler)
|
||||
{
|
||||
parser.build_scope_tree(err_handler);
|
||||
parser.build_token_to_scope_map();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename LexerFn, typename ParserFn> struct Parser : ParserBase {
|
||||
void lexical_analysis(std::string_view input)
|
||||
{
|
||||
LexerFn::lexical_analysis(*this, input);
|
||||
}
|
||||
|
||||
void semantic_analysis(ErrorHandler &err_handler)
|
||||
{
|
||||
ParserFn::semantic_analysis(*this, err_handler);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::gpu::shader::parser
|
||||
461
blender-5.2.0/source/blender/gpu/shader_tool/union.cc
Normal file
461
blender-5.2.0/source/blender/gpu/shader_tool/union.cc
Normal file
@@ -0,0 +1,461 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tool
|
||||
*/
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "intermediate.hh"
|
||||
#include "metadata.hh"
|
||||
#include "processor.hh"
|
||||
|
||||
namespace blender::gpu::shader {
|
||||
using namespace std;
|
||||
using namespace shader::parser;
|
||||
using namespace metadata;
|
||||
|
||||
void SourceProcessor::lower_unions(Parser &parser)
|
||||
{
|
||||
struct Member {
|
||||
string type, name;
|
||||
size_t offset, size;
|
||||
bool is_enum;
|
||||
|
||||
/* Return true for builtin trivial types (e.g. uint, float3). */
|
||||
bool is_trivial() const
|
||||
{
|
||||
return type.empty();
|
||||
}
|
||||
};
|
||||
|
||||
auto is_struct_size_known = [&](Scope attributes) -> bool {
|
||||
if (attributes.is_invalid()) {
|
||||
return false;
|
||||
}
|
||||
bool is_shared = false;
|
||||
attributes.foreach_attribute([&](Token attr, Scope) {
|
||||
if (attr.str() == "host_shared") {
|
||||
is_shared = true;
|
||||
}
|
||||
});
|
||||
if (!is_shared) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/* Description of each union types. */
|
||||
unordered_map<string, vector<Member>> union_members;
|
||||
|
||||
/* First, lower anonymous unions into separate struct. */
|
||||
parser().foreach_struct([&](Token struct_tok, Scope attrs, Token struct_name, Scope body) {
|
||||
int union_index = 0;
|
||||
body.foreach_match("o{..};", [&](const Tokens &t) {
|
||||
Scope union_body = t[1].scope();
|
||||
|
||||
string union_name = "union" + to_string(union_index);
|
||||
string union_type = string(struct_name.str()) + "_" + union_name;
|
||||
|
||||
/* Parse members of the union for later use. */
|
||||
vector<Member> members;
|
||||
union_body.foreach_declaration(
|
||||
[&](Scope, Token, Token type, Scope, Token name, Scope array, Token) {
|
||||
if (array.is_valid()) {
|
||||
report_error(name, "Arrays are not supported inside unions.");
|
||||
}
|
||||
members.emplace_back(
|
||||
Member{string(type.str()), string(name.str()), 0, 0, type.prev() == Enum});
|
||||
});
|
||||
|
||||
if (members.empty()) {
|
||||
report_error(t[0], "Empty union");
|
||||
return;
|
||||
}
|
||||
union_members.emplace(union_type, members);
|
||||
|
||||
string union_member = union_type + " " + union_name + ";";
|
||||
if (attrs.contains("host_shared")) {
|
||||
union_member = "struct " + union_member;
|
||||
}
|
||||
parser.insert_before(t.front(), union_member);
|
||||
parser.erase(t.front(), t.back());
|
||||
|
||||
string type_decl = "struct [[host_shared]] " + union_type + " {\n";
|
||||
/* Temporary storage (use first member, still valid since all members should have the same
|
||||
* size). The real storage can only be set once we know the size of the union, which we can
|
||||
* only know after lowering them as outside types. */
|
||||
type_decl += " " + members.front().type + " " + members.front().name + ";\n";
|
||||
type_decl += "};\n";
|
||||
|
||||
parser.insert_line_number(struct_tok.str_index_start() - 1, t[0].line_number());
|
||||
parser.insert_before(struct_tok, type_decl);
|
||||
parser.insert_line_number(struct_tok.str_index_start() - 1, struct_tok.line_number());
|
||||
|
||||
union_index++;
|
||||
});
|
||||
});
|
||||
parser.apply_mutations();
|
||||
|
||||
/* Map structure name to structure members. */
|
||||
unordered_map<string, vector<Member>> struct_members = {
|
||||
{"float", {{"", "", 0, 4}}},
|
||||
{"float2", {{"", "", 0, 8}}},
|
||||
{"float4", {{"", "", 0, 16}}},
|
||||
{"bool32_t", {{"", "", 0, 4}}},
|
||||
{"int", {{"", "", 0, 4}}},
|
||||
{"int2", {{"", "", 0, 8}}},
|
||||
{"int4", {{"", "", 0, 16}}},
|
||||
{"uint", {{"", "", 0, 4}}},
|
||||
{"uint2", {{"", "", 0, 8}}},
|
||||
{"uint4", {{"", "", 0, 16}}},
|
||||
{"string_t", {{"", "", 0, 4}}},
|
||||
{"packed_float3", {{"", "", 0, 12}}},
|
||||
{"packed_int3", {{"", "", 0, 12}}},
|
||||
{"packed_uint3", {{"", "", 0, 12}}},
|
||||
{"float2x4", {{"float4", "[0]", 0, 16}, {"float4", "[1]", 16, 16}}},
|
||||
{"float3x4",
|
||||
{{"float4", "[0]", 0, 16}, {"float4", "[1]", 16, 16}, {"float4", "[2]", 32, 16}}},
|
||||
{"float4x4",
|
||||
{{"float4", "[0]", 0, 16},
|
||||
{"float4", "[1]", 16, 16},
|
||||
{"float4", "[2]", 32, 16},
|
||||
{"float4", "[3]", 48, 16}}},
|
||||
};
|
||||
|
||||
auto type_size_get = [&](Token type) -> size_t {
|
||||
auto value = struct_members.find(string(type.str()));
|
||||
if (value == struct_members.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int total_size = 0;
|
||||
for (const Member &member : value->second) {
|
||||
total_size += member.size;
|
||||
}
|
||||
return total_size;
|
||||
};
|
||||
|
||||
/* Then populate struct members. */
|
||||
parser().foreach_struct([&](Token, Scope attributes, Token struct_name, Scope body) {
|
||||
if (!is_struct_size_known(attributes)) {
|
||||
return;
|
||||
}
|
||||
vector<Member> members;
|
||||
size_t offset = 0;
|
||||
body.foreach_declaration([&](Scope, Token, Token type, Scope, Token name, Scope array, Token) {
|
||||
size_t size = 4;
|
||||
|
||||
size_t array_size = 0;
|
||||
if (array.is_valid()) {
|
||||
/* Assume size to be zero by default. It will create invalid size error later on. */
|
||||
array_size = static_array_size(array, 0);
|
||||
}
|
||||
else {
|
||||
array_size = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < array_size; i++) {
|
||||
string name_str(name.str());
|
||||
if (array.is_valid()) {
|
||||
name_str += "[" + to_string(i) + "]";
|
||||
}
|
||||
if (type.prev() != Enum) {
|
||||
size = type_size_get(type);
|
||||
if (size != 0) {
|
||||
members.emplace_back(Member{string(type.str()), "." + name_str, offset, size});
|
||||
}
|
||||
}
|
||||
else {
|
||||
members.emplace_back(Member{string(type.str()), "." + name_str, offset, size, true});
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
});
|
||||
|
||||
struct_members.emplace(struct_name.str(), members);
|
||||
});
|
||||
|
||||
/* Replace placeholder struct with a generic one. */
|
||||
auto replace_placeholder_member = [&](Scope body) {
|
||||
/* Replace placeholder struct with float members. */
|
||||
size_t size = type_size_get(body.front().next());
|
||||
if (size == 0) {
|
||||
report_error(body.front().next(),
|
||||
"Can't infer size of member. Type must be defined in this file and have "
|
||||
"the [[host_shared]] attribute.");
|
||||
}
|
||||
for (int i = 0; i < size; i += 16) {
|
||||
size_t member_size = size - i;
|
||||
const char *data_type = "float4";
|
||||
if (member_size == 4) {
|
||||
data_type = "float";
|
||||
}
|
||||
else if (member_size == 8) {
|
||||
data_type = "float2";
|
||||
}
|
||||
else if (member_size == 12) {
|
||||
data_type = "float3";
|
||||
}
|
||||
parser.insert_after(body.front().str_index_last_no_whitespace(),
|
||||
"\n " + string(data_type) + " data" + to_string(i / 16) + ";");
|
||||
}
|
||||
parser.erase(body.front().next(), body.back().prev());
|
||||
};
|
||||
|
||||
auto member_from_float =
|
||||
[&](const Member &union_member, const Member &struct_member, const string &access) {
|
||||
/* Account for trivial types. */
|
||||
const string &type = struct_member.is_trivial() ? union_member.type : struct_member.type;
|
||||
bool is_enum = struct_member.is_trivial() ? union_member.is_enum : struct_member.is_enum;
|
||||
|
||||
if (is_enum) {
|
||||
return struct_member.type + "(floatBitsToUint(" + access + "))";
|
||||
}
|
||||
if (type.starts_with("uint")) {
|
||||
return "floatBitsToUint(" + access + ")";
|
||||
}
|
||||
if (type.starts_with("int")) {
|
||||
return "floatBitsToInt(" + access + ")";
|
||||
}
|
||||
if (type == "bool") {
|
||||
return "floatBitsToInt(" + access + ") != 0";
|
||||
}
|
||||
return access;
|
||||
};
|
||||
|
||||
auto member_to_float =
|
||||
[&](const Member &union_member, const Member &struct_member, const string &access) {
|
||||
/* Account for trivial types. */
|
||||
const string &type = struct_member.is_trivial() ? union_member.type : struct_member.type;
|
||||
bool is_enum = struct_member.is_trivial() ? union_member.is_enum : struct_member.is_enum;
|
||||
|
||||
if (is_enum) {
|
||||
return "uintBitsToFloat(uint(" + access + "))";
|
||||
}
|
||||
if (type.starts_with("uint")) {
|
||||
return "uintBitsToFloat(" + access + ")";
|
||||
}
|
||||
if (type.starts_with("int")) {
|
||||
return "intBitsToFloat(" + access + ")";
|
||||
}
|
||||
if (type == "bool") {
|
||||
return "intBitsToFloat(int(" + access + "))";
|
||||
}
|
||||
return access;
|
||||
};
|
||||
|
||||
auto union_data_access = [&](const Member &struct_member, size_t union_size) {
|
||||
const size_t offset = struct_member.offset;
|
||||
string access = ".data" + to_string(offset / 16);
|
||||
|
||||
if (struct_member.size == 12) {
|
||||
access += ".xyz";
|
||||
}
|
||||
else if (struct_member.size == 8) {
|
||||
access += ((offset % 16) == 0) ? ".xy" : ".zw";
|
||||
}
|
||||
else if (struct_member.size == 4) {
|
||||
switch (offset % 16) {
|
||||
case 0:
|
||||
/* Special case if last member is a scalar. */
|
||||
access += ((union_size - offset) == 4) ? "" : ".x";
|
||||
break;
|
||||
case 4:
|
||||
access += ".y";
|
||||
break;
|
||||
case 8:
|
||||
access += ".z";
|
||||
break;
|
||||
case 12:
|
||||
access += ".w";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return access;
|
||||
};
|
||||
|
||||
auto member_data_access = [&](const Member &struct_member) -> string {
|
||||
return struct_member.is_trivial() ? string() : struct_member.name;
|
||||
};
|
||||
|
||||
auto create_getter = [&](/* Tokens of the union declaration inside the struct. */
|
||||
const Token &union_type_tok,
|
||||
const Token &union_var_tok,
|
||||
/* Union member we are creating the accessor for. */
|
||||
const Member &union_member,
|
||||
/* Definition of the type of the accessed member. */
|
||||
const vector<Member> &struct_members) -> string {
|
||||
const size_t union_size = type_size_get(union_type_tok);
|
||||
if (union_size == 0) {
|
||||
report_error(union_type_tok,
|
||||
"Can't infer size of member. Type must be defined in this file and have "
|
||||
"the [[host_shared]] attribute.");
|
||||
return "";
|
||||
}
|
||||
const Member &last_member = struct_members.back();
|
||||
if (last_member.offset + last_member.size != union_size) {
|
||||
report_error(union_type_tok, "union has members of different sizes");
|
||||
return "";
|
||||
}
|
||||
|
||||
string fn_body = "{\n";
|
||||
/* Declare return variable of the same type as the accessed member. */
|
||||
fn_body += " " + union_member.type + " val;\n";
|
||||
for (const auto &member : struct_members) {
|
||||
string to_var = "val" + member_data_access(member);
|
||||
string access = "this_." + string(union_var_tok.str()) +
|
||||
union_data_access(member, union_size);
|
||||
fn_body += " " + to_var + " = " + member_from_float(union_member, member, access) + ";\n";
|
||||
}
|
||||
fn_body += " return val;\n";
|
||||
fn_body += "}\n";
|
||||
|
||||
return "\n" + union_member.type + " " + union_member.name + "() const " + fn_body;
|
||||
};
|
||||
|
||||
auto create_setter = [&](/* Tokens of the union declaration inside the struct. */
|
||||
const Token &union_type_tok,
|
||||
const Token &union_var_tok,
|
||||
/* Union member we are creating the accessor for. */
|
||||
const Member &union_member,
|
||||
/* Definition of the type of the accessed member. */
|
||||
const vector<Member> &struct_members) -> string {
|
||||
const size_t union_size = type_size_get(union_type_tok);
|
||||
if (union_size == 0) {
|
||||
report_error(union_type_tok,
|
||||
"Can't infer size of member. Type must be defined in this file and have "
|
||||
"the [[host_shared]] attribute.");
|
||||
return "";
|
||||
}
|
||||
const Member &last_member = struct_members.back();
|
||||
if (last_member.offset + last_member.size != union_size) {
|
||||
report_error(union_type_tok, "union has members of different sizes");
|
||||
return "";
|
||||
}
|
||||
|
||||
string fn_body = "{\n";
|
||||
for (const auto &member : struct_members) {
|
||||
string to_var = "this->" + string(union_var_tok.str()) +
|
||||
union_data_access(member, union_size);
|
||||
string access = "value" + member_data_access(member);
|
||||
fn_body += " " + to_var + " = " + member_to_float(union_member, member, access) + ";\n";
|
||||
}
|
||||
fn_body += "}\n";
|
||||
|
||||
return "\nvoid " + union_member.name + "_set_(" + union_member.type + " value) " + fn_body;
|
||||
};
|
||||
|
||||
auto flatten_members = [&](Token type, vector<Member> &members) {
|
||||
vector<Member> dst;
|
||||
dst.reserve(members.size());
|
||||
bool expanded = false;
|
||||
for (const auto &member : members) {
|
||||
if (member.is_trivial() || member.is_enum) {
|
||||
dst.emplace_back(member);
|
||||
continue;
|
||||
}
|
||||
if (!struct_members.contains(member.type)) {
|
||||
report_error(
|
||||
type,
|
||||
"Unknown type encountered while unwrapping union. Contained types must be defined "
|
||||
"in this file and decorated with [[host_shared]] attribute.");
|
||||
continue;
|
||||
}
|
||||
|
||||
vector<Member> nested_structure = struct_members.find(member.type)->second;
|
||||
for (Member nested_member : nested_structure) {
|
||||
if (nested_member.is_trivial() || nested_member.is_enum) {
|
||||
dst.emplace_back(member);
|
||||
}
|
||||
else {
|
||||
expanded = true;
|
||||
nested_member.name = member.name + nested_member.name;
|
||||
nested_member.offset = member.offset + nested_member.offset;
|
||||
dst.emplace_back(nested_member);
|
||||
}
|
||||
}
|
||||
}
|
||||
members = dst;
|
||||
return expanded;
|
||||
};
|
||||
|
||||
parser().foreach_struct([&](Token, Scope, Token struct_name, Scope body) {
|
||||
if (union_members.contains(string(struct_name.str()))) {
|
||||
replace_placeholder_member(body);
|
||||
return;
|
||||
}
|
||||
|
||||
body.foreach_declaration([&](Scope, Token, Token type, Scope, Token name, Scope, Token) {
|
||||
if (!union_members.contains(string(type.str()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const vector<Member> &members = union_members.find(string(type.str()))->second;
|
||||
for (const auto &member : members) {
|
||||
if (!struct_members.contains(member.type)) {
|
||||
report_error(
|
||||
type,
|
||||
"Unknown union member type. Type must be defined in this file and decorated "
|
||||
"with [[host_shared]] attribute.");
|
||||
return;
|
||||
}
|
||||
vector<Member> structure = struct_members.find(member.type)->second;
|
||||
/* Flatten references to other structures, recursively. */
|
||||
while (flatten_members(type, structure)) {
|
||||
}
|
||||
|
||||
parser.insert_after(body.back().prev(), create_getter(type, name, member, structure));
|
||||
parser.insert_after(body.back().prev(), create_setter(type, name, member, structure));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* Replace assignment pattern.
|
||||
* Example: `a.b() = c;` > `a.b_set_(c);`
|
||||
* This pattern is currently only allowed for `union_t`. */
|
||||
parser().foreach_match("A()=", [&](const Tokens &t) {
|
||||
parser.insert_before(t[1], "_set_");
|
||||
parser.erase(t[2], t[3]);
|
||||
parser.insert_after(t[3].scope().back(), ")");
|
||||
});
|
||||
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
/**
|
||||
* For safety reason, union members need to be declared with the union_t template.
|
||||
* This avoid raw member access which we cannot emulate. Instead this forces the use of the `()`
|
||||
* operator for accessing the members of the enum.
|
||||
*
|
||||
* Need to run before lower_unions.
|
||||
*/
|
||||
void SourceProcessor::lower_union_accessor_templates(Parser &parser)
|
||||
{
|
||||
parser().foreach_struct([&](Token, Scope, Token, Scope body) {
|
||||
body.foreach_match("o{..};", [&](const Tokens &t) {
|
||||
t[1].scope().foreach_declaration(
|
||||
[&](Scope, Token, Token type, Scope template_scope, Token name, Scope, Token) {
|
||||
if (type.str() != "union_t") {
|
||||
report_error(
|
||||
name,
|
||||
"All union members must have their type wrapped using the union_t<T> template.");
|
||||
parser.erase(type, type.find_next(SemiColon));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove the template but not the wrapped type. */
|
||||
parser.erase(type);
|
||||
if (template_scope.is_valid()) {
|
||||
parser.erase(template_scope.front());
|
||||
parser.erase(template_scope.back());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
parser.apply_mutations();
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::shader
|
||||
131
blender-5.2.0/source/blender/gpu/shader_tool/utils.hh
Normal file
131
blender-5.2.0/source/blender/gpu/shader_tool/utils.hh
Normal file
@@ -0,0 +1,131 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup shader_tools
|
||||
*
|
||||
* Lite duplicates of blender utility types and other utility functions.
|
||||
* They are duplicated to avoid pulling half of blender as a dependency.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace blender::gpu::shader::parser {
|
||||
|
||||
struct Token;
|
||||
|
||||
struct ErrorHandler {
|
||||
struct Error {
|
||||
/* Contain only the message passed to the report function. */
|
||||
std::string message;
|
||||
/* Contains filename, token location, source line and cursor. */
|
||||
std::string full_report;
|
||||
};
|
||||
|
||||
std::string default_filename;
|
||||
std::optional<Error> err;
|
||||
|
||||
void report(Token tok, std::string_view message);
|
||||
void report(int row, int column, std::string line, std::string_view message);
|
||||
|
||||
void reset()
|
||||
{
|
||||
err.reset();
|
||||
}
|
||||
};
|
||||
|
||||
/** Poor man's IndexRange. */
|
||||
struct IndexRange {
|
||||
int64_t start;
|
||||
int64_t size;
|
||||
|
||||
IndexRange(int64_t start, int64_t size) : start(start), size(size) {}
|
||||
|
||||
bool overlaps(IndexRange other) const
|
||||
{
|
||||
if (start == other.start && size == other.size) {
|
||||
return true;
|
||||
}
|
||||
return ((start < other.start) && (other.start < (start + size))) ||
|
||||
((other.start < start) && (start < (other.start + other.size)));
|
||||
}
|
||||
|
||||
int64_t last() const
|
||||
{
|
||||
return start + size - 1;
|
||||
}
|
||||
};
|
||||
|
||||
/** Poor man's MutableSpan. */
|
||||
template<typename T> struct MutableSpan {
|
||||
T *data_;
|
||||
uint64_t size_;
|
||||
|
||||
T &operator[](const int64_t index)
|
||||
{
|
||||
return data_[index];
|
||||
}
|
||||
const T &operator[](const int64_t index) const
|
||||
{
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
T *data()
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
uint64_t size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
T back() const
|
||||
{
|
||||
return (*this)[size_ - 1];
|
||||
}
|
||||
|
||||
T *begin()
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
T *end()
|
||||
{
|
||||
return data_ + size_;
|
||||
}
|
||||
|
||||
const T *begin() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
const T *end() const
|
||||
{
|
||||
return data_ + size_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set span size to a smaller size, this invokes undefined behavior when n is negative or bigger
|
||||
* than the current span.
|
||||
*/
|
||||
void shrink(int64_t new_size)
|
||||
{
|
||||
size_ = new_size;
|
||||
}
|
||||
};
|
||||
|
||||
/** Return the line number this token is found at. Take into account the #line directives. */
|
||||
size_t line_number(const std::string_view &str, size_t pos);
|
||||
/** Return the offset to the start of the line. */
|
||||
size_t char_number(const std::string_view &str, size_t pos);
|
||||
/** Return the filename at this position. Take into account the #line directives. */
|
||||
std::string filename(const std::string_view &str, size_t pos);
|
||||
/** Returns a string of the line containing the character at the given position. */
|
||||
std::string line_str(const std::string_view &str, size_t pos);
|
||||
|
||||
} // namespace blender::gpu::shader::parser
|
||||
Reference in New Issue
Block a user