/* * Copyright (C) 2014 Patrick Mours * SPDX-License-Identifier: BSD-3-Clause */ #include "effect_lexer.hpp" #include "effect_parser.hpp" #include "effect_codegen.hpp" #include #define RESHADEFX_SHORT_CIRCUIT 0 reshadefx::parser::parser() { } reshadefx::parser::~parser() { } void reshadefx::parser::error(const location &location, unsigned int code, const std::string &message) { _errors += location.source; _errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": error"; _errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": "; _errors += message; _errors += '\n'; } void reshadefx::parser::warning(const location &location, unsigned int code, const std::string &message) { _errors += location.source; _errors += '(' + std::to_string(location.line) + ", " + std::to_string(location.column) + ')' + ": warning"; _errors += (code == 0) ? ": " : " X" + std::to_string(code) + ": "; _errors += message; _errors += '\n'; } void reshadefx::parser::backup() { _token_backup = _token_next; _lexer_backup_offset = _lexer->input_offset(); } void reshadefx::parser::restore() { _lexer->reset_to_offset(_lexer_backup_offset); _token_next = _token_backup; // Copy instead of move here, since restore may be called twice (from 'accept_type_class' and then again from 'parse_expression_unary') } void reshadefx::parser::consume() { _token = std::move(_token_next); _token_next = _lexer->lex(); } void reshadefx::parser::consume_until(tokenid tokid) { while (!accept(tokid) && !peek(tokenid::end_of_file)) { consume(); } } bool reshadefx::parser::accept(tokenid tokid) { if (peek(tokid)) { consume(); return true; } return false; } bool reshadefx::parser::expect(tokenid tokid) { if (!accept(tokid)) { error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected '" + token::id_to_name(tokid) + '\''); return false; } return true; } bool reshadefx::parser::accept_symbol(std::string &identifier, scoped_symbol &symbol) { // Starting an identifier with '::' restricts the symbol search to the global namespace level const bool exclusive = accept(tokenid::colon_colon); if (exclusive ? !expect(tokenid::identifier) : !accept(tokenid::identifier)) { // No token should come through here, since all possible prefix expressions should have been handled above, so this is an error in the syntax if (!exclusive) error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + '\''); return false; } identifier = std::move(_token.literal_as_string); // Can concatenate multiple '::' to force symbol search for a specific namespace level while (accept(tokenid::colon_colon)) { if (!expect(tokenid::identifier)) return false; identifier += "::" + std::move(_token.literal_as_string); } // Figure out which scope to start searching in struct scope scope = { "::", 0, 0 }; if (!exclusive) scope = current_scope(); // Lookup name in the symbol table symbol = find_symbol(identifier, scope, exclusive); return true; } bool reshadefx::parser::accept_type_class(type &type) { type.rows = type.cols = 0; if (peek(tokenid::identifier) || peek(tokenid::colon_colon)) { type.base = type::t_struct; backup(); // Need to restore if this identifier does not turn out to be a structure std::string identifier; scoped_symbol symbol; if (accept_symbol(identifier, symbol)) { if (symbol.id && symbol.op == symbol_type::structure) { type.definition = symbol.id; return true; } } restore(); return false; } if (accept(tokenid::vector)) { type.base = type::t_float; // Default to float4 unless a type is specified (see below) type.rows = 4, type.cols = 1; if (accept('<')) { if (!accept_type_class(type)) // This overwrites the base type again return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected vector element type"), false; else if (!type.is_scalar()) return error(_token.location, 3122, "vector element type must be a scalar type"), false; if (!expect(',') || !expect(tokenid::int_literal)) return false; else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) return error(_token.location, 3052, "vector dimension must be between 1 and 4"), false; type.rows = static_cast(_token.literal_as_int); if (!expect('>')) return false; } return true; } if (accept(tokenid::matrix)) { type.base = type::t_float; // Default to float4x4 unless a type is specified (see below) type.rows = 4, type.cols = 4; if (accept('<')) { if (!accept_type_class(type)) // This overwrites the base type again return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected matrix element type"), false; else if (!type.is_scalar()) return error(_token.location, 3123, "matrix element type must be a scalar type"), false; if (!expect(',') || !expect(tokenid::int_literal)) return false; else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false; type.rows = static_cast(_token.literal_as_int); if (!expect(',') || !expect(tokenid::int_literal)) return false; else if (_token.literal_as_int < 1 || _token.literal_as_int > 4) return error(_token.location, 3053, "matrix dimensions must be between 1 and 4"), false; type.cols = static_cast(_token.literal_as_int); if (!expect('>')) return false; } return true; } if (accept(tokenid::sampler1d) || accept(tokenid::sampler2d) || accept(tokenid::sampler3d)) { const unsigned int texture_dimension = static_cast(_token.id) - static_cast(tokenid::sampler1d); if (accept('<')) { if (!accept_type_class(type)) return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected sampler element type"), false; if (type.is_object()) return error(_token.location, 3124, "object element type cannot be an object type"), false; if (!type.is_numeric() || type.is_matrix()) return error(_token.location, 3521, "sampler element type must fit in four 32-bit quantities"), false; if (type.is_integral() && type.is_signed()) type.base = static_cast(type::t_sampler1d_int + texture_dimension); else if (type.is_integral() && type.is_unsigned()) type.base = static_cast(type::t_sampler1d_uint + texture_dimension); else type.base = static_cast(type::t_sampler1d_float + texture_dimension); if (!expect('>')) return false; } else { type.base = static_cast(type::t_sampler1d_float + texture_dimension); type.rows = 4; type.cols = 1; } return true; } if (accept(tokenid::storage1d) || accept(tokenid::storage2d) || accept(tokenid::storage3d)) { const unsigned int texture_dimension = static_cast(_token.id) - static_cast(tokenid::storage1d); if (accept('<')) { if (!accept_type_class(type)) return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected storage element type"), false; if (type.is_object()) return error(_token.location, 3124, "object element type cannot be an object type"), false; if (!type.is_numeric() || type.is_matrix()) return error(_token.location, 3521, "storage element type must fit in four 32-bit quantities"), false; if (type.is_integral() && type.is_signed()) type.base = static_cast(type::t_storage1d_int + texture_dimension); else if (type.is_integral() && type.is_unsigned()) type.base = static_cast(type::t_storage1d_uint + texture_dimension); else type.base = static_cast(type::t_storage1d_float + texture_dimension); if (!expect('>')) return false; } else { type.base = static_cast(type::t_storage1d_float + texture_dimension); type.rows = 4; type.cols = 1; } return true; } switch (_token_next.id) { case tokenid::void_: type.base = type::t_void; break; case tokenid::bool_: case tokenid::bool2: case tokenid::bool3: case tokenid::bool4: type.base = type::t_bool; type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::bool_)); type.cols = 1; break; case tokenid::bool2x2: case tokenid::bool2x3: case tokenid::bool2x4: case tokenid::bool3x2: case tokenid::bool3x3: case tokenid::bool3x4: case tokenid::bool4x2: case tokenid::bool4x3: case tokenid::bool4x4: type.base = type::t_bool; type.rows = 2 + (static_cast(_token_next.id) - static_cast(tokenid::bool2x2)) / 3; type.cols = 2 + (static_cast(_token_next.id) - static_cast(tokenid::bool2x2)) % 3; break; case tokenid::int_: case tokenid::int2: case tokenid::int3: case tokenid::int4: type.base = type::t_int; type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::int_)); type.cols = 1; break; case tokenid::int2x2: case tokenid::int2x3: case tokenid::int2x4: case tokenid::int3x2: case tokenid::int3x3: case tokenid::int3x4: case tokenid::int4x2: case tokenid::int4x3: case tokenid::int4x4: type.base = type::t_int; type.rows = 2 + (static_cast(_token_next.id) - static_cast(tokenid::int2x2)) / 3; type.cols = 2 + (static_cast(_token_next.id) - static_cast(tokenid::int2x2)) % 3; break; case tokenid::min16int: case tokenid::min16int2: case tokenid::min16int3: case tokenid::min16int4: type.base = type::t_min16int; type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::min16int)); type.cols = 1; break; case tokenid::uint_: case tokenid::uint2: case tokenid::uint3: case tokenid::uint4: type.base = type::t_uint; type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::uint_)); type.cols = 1; break; case tokenid::uint2x2: case tokenid::uint2x3: case tokenid::uint2x4: case tokenid::uint3x2: case tokenid::uint3x3: case tokenid::uint3x4: case tokenid::uint4x2: case tokenid::uint4x3: case tokenid::uint4x4: type.base = type::t_uint; type.rows = 2 + (static_cast(_token_next.id) - static_cast(tokenid::uint2x2)) / 3; type.cols = 2 + (static_cast(_token_next.id) - static_cast(tokenid::uint2x2)) % 3; break; case tokenid::min16uint: case tokenid::min16uint2: case tokenid::min16uint3: case tokenid::min16uint4: type.base = type::t_min16uint; type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::min16uint)); type.cols = 1; break; case tokenid::float_: case tokenid::float2: case tokenid::float3: case tokenid::float4: type.base = type::t_float; type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::float_)); type.cols = 1; break; case tokenid::float2x2: case tokenid::float2x3: case tokenid::float2x4: case tokenid::float3x2: case tokenid::float3x3: case tokenid::float3x4: case tokenid::float4x2: case tokenid::float4x3: case tokenid::float4x4: type.base = type::t_float; type.rows = 2 + (static_cast(_token_next.id) - static_cast(tokenid::float2x2)) / 3; type.cols = 2 + (static_cast(_token_next.id) - static_cast(tokenid::float2x2)) % 3; break; case tokenid::min16float: case tokenid::min16float2: case tokenid::min16float3: case tokenid::min16float4: type.base = type::t_min16float; type.rows = 1 + (static_cast(_token_next.id) - static_cast(tokenid::min16float)); type.cols = 1; break; case tokenid::string_: type.base = type::t_string; break; case tokenid::texture1d: type.base = type::t_texture1d; break; case tokenid::texture2d: type.base = type::t_texture2d; break; case tokenid::texture3d: type.base = type::t_texture3d; break; default: return false; } consume(); return true; } bool reshadefx::parser::accept_type_qualifiers(type &type) { unsigned int qualifiers = 0; // Storage if (accept(tokenid::extern_)) qualifiers |= type::q_extern; if (accept(tokenid::static_)) qualifiers |= type::q_static; if (accept(tokenid::uniform_)) qualifiers |= type::q_uniform; if (accept(tokenid::volatile_)) qualifiers |= type::q_volatile; if (accept(tokenid::precise)) qualifiers |= type::q_precise; if (accept(tokenid::groupshared)) qualifiers |= type::q_groupshared; if (accept(tokenid::in)) qualifiers |= type::q_in; if (accept(tokenid::out)) qualifiers |= type::q_out; if (accept(tokenid::inout)) qualifiers |= type::q_inout; // Modifiers if (accept(tokenid::const_)) qualifiers |= type::q_const; // Interpolation if (accept(tokenid::linear)) qualifiers |= type::q_linear; if (accept(tokenid::noperspective)) qualifiers |= type::q_noperspective; if (accept(tokenid::centroid)) qualifiers |= type::q_centroid; if (accept(tokenid::nointerpolation)) qualifiers |= type::q_nointerpolation; if (qualifiers == 0) return false; if ((type.qualifiers & qualifiers) == qualifiers) warning(_token.location, 3048, "duplicate usages specified"); type.qualifiers |= qualifiers; // Continue parsing potential additional qualifiers until no more are found accept_type_qualifiers(type); return true; } bool reshadefx::parser::accept_unary_op() { switch (_token_next.id) { case tokenid::exclaim: // !x (logical not) case tokenid::plus: // +x case tokenid::minus: // -x (negate) case tokenid::tilde: // ~x (bitwise not) case tokenid::plus_plus: // ++x case tokenid::minus_minus: // --x break; default: return false; } consume(); return true; } bool reshadefx::parser::accept_postfix_op() { switch (_token_next.id) { case tokenid::plus_plus: // ++x case tokenid::minus_minus: // --x break; default: return false; } consume(); return true; } bool reshadefx::parser::peek_multary_op(unsigned int &precedence) const { // Precedence values taken from https://cppreference.com/w/cpp/language/operator_precedence switch (_token_next.id) { case tokenid::question: precedence = 1; break; // x ? a : b case tokenid::pipe_pipe: precedence = 2; break; // a || b (logical or) case tokenid::ampersand_ampersand: precedence = 3; break; // a && b (logical and) case tokenid::pipe: precedence = 4; break; // a | b (bitwise or) case tokenid::caret: precedence = 5; break; // a ^ b (bitwise xor) case tokenid::ampersand: precedence = 6; break; // a & b (bitwise and) case tokenid::equal_equal: precedence = 7; break; // a == b (equal) case tokenid::exclaim_equal: precedence = 7; break; // a != b (not equal) case tokenid::less: precedence = 8; break; // a < b case tokenid::greater: precedence = 8; break; // a > b case tokenid::less_equal: precedence = 8; break; // a <= b case tokenid::greater_equal: precedence = 8; break; // a >= b case tokenid::less_less: precedence = 9; break; // a << b (left shift) case tokenid::greater_greater: precedence = 9; break; // a >> b (right shift) case tokenid::plus: precedence = 10; break; // a + b (add) case tokenid::minus: precedence = 10; break; // a - b (subtract) case tokenid::star: precedence = 11; break; // a * b (multiply) case tokenid::slash: precedence = 11; break; // a / b (divide) case tokenid::percent: precedence = 11; break; // a % b (modulo) default: return false; } // Do not consume token yet since the expression may be skipped due to precedence return true; } bool reshadefx::parser::accept_assignment_op() { switch (_token_next.id) { case tokenid::equal: // a = b case tokenid::percent_equal: // a %= b case tokenid::ampersand_equal: // a &= b case tokenid::star_equal: // a *= b case tokenid::plus_equal: // a += b case tokenid::minus_equal: // a -= b case tokenid::slash_equal: // a /= b case tokenid::less_less_equal: // a <<= b case tokenid::greater_greater_equal: // a >>= b case tokenid::caret_equal: // a ^= b case tokenid::pipe_equal: // a |= b break; default: return false; } consume(); return true; } bool reshadefx::parser::parse_expression(expression &exp) { // Parse first expression if (!parse_expression_assignment(exp)) return false; // Continue parsing if an expression sequence is next (in the form "a, b, c, ...") while (accept(',')) // Overwrite 'exp' since conveniently the last expression in the sequence is the result if (!parse_expression_assignment(exp)) return false; return true; } bool reshadefx::parser::parse_expression_unary(expression &exp) { auto location = _token_next.location; // Check if a prefix operator exists if (accept_unary_op()) { // Remember the operator token before parsing the expression that follows it const tokenid op = _token.id; // Parse the actual expression if (!parse_expression_unary(exp)) return false; // Unary operators are only valid on basic types if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix()) return error(exp.location, 3022, "scalar, vector, or matrix expected"), false; // Special handling for the "++" and "--" operators if (op == tokenid::plus_plus || op == tokenid::minus_minus) { if (exp.type.has(type::q_const) || !exp.is_lvalue) return error(location, 3025, "l-value specifies const object"), false; // Create a constant one in the type of the expression constant one = {}; for (unsigned int i = 0; i < exp.type.components(); ++i) if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u; const auto value = _codegen->emit_load(exp); const auto result = _codegen->emit_binary_op(location, op, exp.type, value, _codegen->emit_constant(exp.type, one)); // The "++" and "--" operands modify the source variable, so store result back into it _codegen->emit_store(exp, result); } else if (op != tokenid::plus) // Ignore "+" operator since it does not actually do anything { // The "~" bitwise operator is only valid on integral types if (op == tokenid::tilde && !exp.type.is_integral()) return error(exp.location, 3082, "int or unsigned int type required"), false; // The logical not operator expects a boolean type as input, so perform cast if necessary if (op == tokenid::exclaim && !exp.type.is_boolean()) exp.add_cast_operation({ type::t_bool, exp.type.rows, exp.type.cols }); // Note: The result will be boolean as well // Constant expressions can be evaluated at compile time if (!exp.evaluate_constant_expression(op)) { const auto value = _codegen->emit_load(exp); const auto result = _codegen->emit_unary_op(location, op, exp.type, value); exp.reset_to_rvalue(location, result, exp.type); } } } else if (accept('(')) { // Note: This backup may get overridden in 'accept_type_class', but should point to the same token still backup(); // Check if this is a C-style cast expression if (type cast_type; accept_type_class(cast_type)) { if (peek('(')) { // This is not a C-style cast but a constructor call, so need to roll-back and parse that instead restore(); } else if (expect(')')) { // Parse the expression behind cast operator if (!parse_expression_unary(exp)) return false; // Check if the types already match, in which case there is nothing to do if (exp.type == cast_type) return true; // Check if a cast between these types is valid if (!type::rank(exp.type, cast_type)) return error(location, 3017, "cannot convert these types (from " + exp.type.description() + " to " + cast_type.description() + ')'), false; exp.add_cast_operation(cast_type); return true; } else { // Type name was not followed by a closing parenthesis return false; } } // Parse expression between the parentheses if (!parse_expression(exp) || !expect(')')) return false; } else if (accept('{')) { bool is_constant = true; std::vector elements; type composite_type = { type::t_void, 1, 1 }; while (!peek('}')) { // There should be a comma between arguments if (!elements.empty() && !expect(',')) return consume_until('}'), false; // Initializer lists might contain a comma at the end, so break out of the loop if nothing follows afterwards if (peek('}')) break; expression &element = elements.emplace_back(); // Parse the argument expression if (!parse_expression_assignment(element)) return consume_until('}'), false; if (element.type.is_array()) return error(element.location, 3119, "arrays cannot be multi-dimensional"), consume_until('}'), false; if (composite_type.base != type::t_void && element.type.definition != composite_type.definition) return error(element.location, 3017, "cannot convert these types (from " + element.type.description() + " to " + composite_type.description() + ')'), false; is_constant &= element.is_constant; // Result is only constant if all arguments are constant composite_type = type::merge(composite_type, element.type); } // Constant arrays can be constructed at compile time if (is_constant) { constant res = {}; for (expression &element : elements) { element.add_cast_operation(composite_type); res.array_data.push_back(element.constant); } composite_type.array_length = static_cast(elements.size()); exp.reset_to_rvalue_constant(location, std::move(res), composite_type); } else { // Resolve all access chains for (expression &element : elements) { element.add_cast_operation(composite_type); element.reset_to_rvalue(element.location, _codegen->emit_load(element), composite_type); } composite_type.array_length = static_cast(elements.size()); const auto result = _codegen->emit_construct(location, composite_type, elements); exp.reset_to_rvalue(location, result, composite_type); } return expect('}'); } else if (accept(tokenid::true_literal)) { exp.reset_to_rvalue_constant(location, true); } else if (accept(tokenid::false_literal)) { exp.reset_to_rvalue_constant(location, false); } else if (accept(tokenid::int_literal)) { exp.reset_to_rvalue_constant(location, _token.literal_as_int); } else if (accept(tokenid::uint_literal)) { exp.reset_to_rvalue_constant(location, _token.literal_as_uint); } else if (accept(tokenid::float_literal)) { exp.reset_to_rvalue_constant(location, _token.literal_as_float); } else if (accept(tokenid::double_literal)) { // Convert double literal to float literal for now warning(location, 5000, "double literal truncated to float literal"); exp.reset_to_rvalue_constant(location, static_cast(_token.literal_as_double)); } else if (accept(tokenid::string_literal)) { std::string value = std::move(_token.literal_as_string); // Multiple string literals in sequence are concatenated into a single string literal while (accept(tokenid::string_literal)) value += _token.literal_as_string; exp.reset_to_rvalue_constant(location, std::move(value)); } else if (type type; accept_type_class(type)) // Check if this is a constructor call expression { if (!expect('(')) return false; if (!type.is_numeric()) return error(location, 3037, "constructors only defined for numeric base types"), false; // Empty constructors do not exist if (accept(')')) return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false; // Parse entire argument expression list bool is_constant = true; unsigned int num_components = 0; std::vector arguments; while (!peek(')')) { // There should be a comma between arguments if (!arguments.empty() && !expect(',')) return false; expression &argument = arguments.emplace_back(); // Parse the argument expression if (!parse_expression_assignment(argument)) return false; // Constructors are only defined for numeric base types if (!argument.type.is_numeric()) return error(argument.location, 3017, "cannot convert non-numeric types"), false; is_constant &= argument.is_constant; // Result is only constant if all arguments are constant num_components += argument.type.components(); } // The list should be terminated with a parenthesis if (!expect(')')) return false; // The total number of argument elements needs to match the number of elements in the result type if (num_components != type.components()) return error(location, 3014, "incorrect number of arguments to numeric-type constructor"), false; assert(num_components > 0 && num_components <= 16 && !type.is_array()); if (is_constant) // Constants can be converted at compile time { constant res = {}; unsigned int i = 0; for (expression &argument : arguments) { argument.add_cast_operation({ type.base, argument.type.rows, argument.type.cols }); for (unsigned int k = 0; k < argument.type.components(); ++k) res.as_uint[i++] = argument.constant.as_uint[k]; } exp.reset_to_rvalue_constant(location, std::move(res), type); } else if (arguments.size() > 1) { // Flatten all arguments to a list of scalars for (auto it = arguments.begin(); it != arguments.end();) { // Argument is a scalar already, so only need to cast it if (it->type.is_scalar()) { expression &argument = *it++; auto scalar_type = argument.type; scalar_type.base = type.base; argument.add_cast_operation(scalar_type); argument.reset_to_rvalue(argument.location, _codegen->emit_load(argument), scalar_type); } else { const expression argument = *it; it = arguments.erase(it); // Convert to a scalar value and re-enter the loop in the next iteration (in case a cast is necessary too) for (unsigned int i = argument.type.components(); i > 0; --i) { expression scalar = argument; scalar.add_constant_index_access(i - 1); it = arguments.insert(it, scalar); } } } const auto result = _codegen->emit_construct(location, type, arguments); exp.reset_to_rvalue(location, result, type); } else // A constructor call with a single argument is identical to a cast { assert(!arguments.empty()); // Reset expression to only argument and add cast to expression access chain exp = std::move(arguments[0]); exp.add_cast_operation(type); } } // At this point only identifiers are left to check and resolve else { std::string identifier; scoped_symbol symbol; if (!accept_symbol(identifier, symbol)) return false; // Check if this is a function call or variable reference if (accept('(')) { // Can only call symbols that are functions, but do not abort yet if no symbol was found since the identifier may reference an intrinsic if (symbol.id && symbol.op != symbol_type::function) return error(location, 3005, "identifier '" + identifier + "' represents a variable, not a function"), false; // Parse entire argument expression list std::vector arguments; while (!peek(')')) { // There should be a comma between arguments if (!arguments.empty() && !expect(',')) return false; expression &argument = arguments.emplace_back(); // Parse the argument expression if (!parse_expression_assignment(argument)) return false; } // The list should be terminated with a parenthesis if (!expect(')')) return false; // Function calls can only be made from within functions if (!_codegen->is_in_function()) return error(location, 3005, "invalid function call outside of a function"), false; // Try to resolve the call by searching through both function symbols and intrinsics bool undeclared = !symbol.id, ambiguous = false; if (!resolve_function_call(identifier, arguments, symbol.scope, symbol, ambiguous)) { if (undeclared) error(location, 3004, "undeclared identifier or no matching intrinsic overload for '" + identifier + '\''); else if (ambiguous) error(location, 3067, "ambiguous function call to '" + identifier + '\''); else error(location, 3013, "no matching function overload for '" + identifier + '\''); return false; } assert(symbol.function != nullptr); std::vector parameters(arguments.size()); // We need to allocate some temporary variables to pass in and load results from pointer parameters for (size_t i = 0; i < arguments.size(); ++i) { const auto ¶m_type = symbol.function->parameter_list[i].type; if (param_type.has(type::q_out) && (!arguments[i].is_lvalue || (arguments[i].type.has(type::q_const) && !arguments[i].type.is_object()))) return error(arguments[i].location, 3025, "l-value specifies const object for an 'out' parameter"), false; if (arguments[i].type.components() > param_type.components()) warning(arguments[i].location, 3206, "implicit truncation of vector type"); if (symbol.op == symbol_type::function || param_type.has(type::q_out)) { if (param_type.is_object() || param_type.has(type::q_groupshared) /* Special case for atomic intrinsics */) { if (arguments[i].type != param_type) return error(location, 3004, "no matching intrinsic overload for '" + identifier + '\''), false; assert(arguments[i].is_lvalue); // Do not shadow object or pointer parameters to function calls size_t chain_index = 0; const auto access_chain = _codegen->emit_access_chain(arguments[i], chain_index); parameters[i].reset_to_lvalue(arguments[i].location, access_chain, param_type); assert(chain_index == arguments[i].chain.size()); // This is referencing a l-value, but want to avoid copying below parameters[i].is_lvalue = false; } else { // All user-defined functions actually accept pointers as arguments, same applies to intrinsics with 'out' parameters const auto temp_variable = _codegen->define_variable(arguments[i].location, param_type); parameters[i].reset_to_lvalue(arguments[i].location, temp_variable, param_type); } } else { expression arg = arguments[i]; arg.add_cast_operation(param_type); parameters[i].reset_to_rvalue(arg.location, _codegen->emit_load(arg), param_type); // Keep track of whether the parameter is a constant for code generation (this makes the expression invalid for all other uses) parameters[i].is_constant = arg.is_constant; } } // Copy in parameters from the argument access chains to parameter variables for (size_t i = 0; i < arguments.size(); ++i) { // Only do this for pointer parameters as discovered above if (parameters[i].is_lvalue && parameters[i].type.has(type::q_in) && !parameters[i].type.is_object()) { expression arg = arguments[i]; arg.add_cast_operation(parameters[i].type); _codegen->emit_store(parameters[i], _codegen->emit_load(arg)); } } // Check if the call resolving found an intrinsic or function and invoke the corresponding code const auto result = symbol.op == symbol_type::function ? _codegen->emit_call(location, symbol.id, symbol.type, parameters) : _codegen->emit_call_intrinsic(location, symbol.id, symbol.type, parameters); exp.reset_to_rvalue(location, result, symbol.type); // Copy out parameters from parameter variables back to the argument access chains for (size_t i = 0; i < arguments.size(); ++i) { // Only do this for pointer parameters as discovered above if (parameters[i].is_lvalue && parameters[i].type.has(type::q_out) && !parameters[i].type.is_object()) { expression arg = parameters[i]; arg.add_cast_operation(arguments[i].type); _codegen->emit_store(arguments[i], _codegen->emit_load(arg)); } } if (_current_function != nullptr) { // Calling a function makes the caller inherit all sampler and storage object references from the callee _current_function->referenced_samplers.insert(symbol.function->referenced_samplers.begin(), symbol.function->referenced_samplers.end()); _current_function->referenced_storages.insert(symbol.function->referenced_storages.begin(), symbol.function->referenced_storages.end()); } } else if (symbol.op == symbol_type::invalid) { // Show error if no symbol matching the identifier was found return error(location, 3004, "undeclared identifier '" + identifier + '\''), false; } else if (symbol.op == symbol_type::variable) { assert(symbol.id != 0); // Simply return the pointer to the variable, dereferencing is done on site where necessary exp.reset_to_lvalue(location, symbol.id, symbol.type); if (_current_function != nullptr && symbol.scope.level == symbol.scope.namespace_level && symbol.id != 0xFFFFFFFF) // Ignore invalid symbols that were added during error recovery { // Keep track of any global sampler or storage objects referenced in the current function if (symbol.type.is_sampler()) _current_function->referenced_samplers.insert(symbol.id); if (symbol.type.is_storage()) _current_function->referenced_storages.insert(symbol.id); } } else if (symbol.op == symbol_type::constant) { // Constants are loaded into the access chain exp.reset_to_rvalue_constant(location, symbol.constant, symbol.type); } else { // Can only reference variables and constants by name, functions need to be called return error(location, 3005, "identifier '" + identifier + "' represents a function, not a variable"), false; } } while (!peek(tokenid::end_of_file)) { location = _token_next.location; // Check if a postfix operator exists if (accept_postfix_op()) { // Unary operators are only valid on basic types if (!exp.type.is_scalar() && !exp.type.is_vector() && !exp.type.is_matrix()) return error(exp.location, 3022, "scalar, vector, or matrix expected"), false; if (exp.type.has(type::q_const) || !exp.is_lvalue) return error(exp.location, 3025, "l-value specifies const object"), false; // Create a constant one in the type of the expression constant one = {}; for (unsigned int i = 0; i < exp.type.components(); ++i) if (exp.type.is_floating_point()) one.as_float[i] = 1.0f; else one.as_uint[i] = 1u; const auto value = _codegen->emit_load(exp, true); const auto result = _codegen->emit_binary_op(location, _token.id, exp.type, value, _codegen->emit_constant(exp.type, one)); // The "++" and "--" operands modify the source variable, so store result back into it _codegen->emit_store(exp, result); // All postfix operators return a r-value rather than a l-value to the variable exp.reset_to_rvalue(location, value, exp.type); } else if (accept('.')) { if (!expect(tokenid::identifier)) return false; location = std::move(_token.location); const auto subscript = std::move(_token.literal_as_string); if (accept('(')) // Methods (function calls on types) are not supported right now { if (!exp.type.is_struct() || exp.type.is_array()) error(location, 3087, "object does not have methods"); else error(location, 3088, "structures do not have methods"); return false; } else if (exp.type.is_array()) // Arrays do not have subscripts { error(location, 3018, "invalid subscript on array"); return false; } else if (exp.type.is_vector()) { const size_t length = subscript.size(); if (length > 4) return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false; bool is_const = false; signed char offsets[4] = { -1, -1, -1, -1 }; enum { xyzw, rgba, stpq } set[4]; for (size_t i = 0; i < length; ++i) { switch (subscript[i]) { case 'x': offsets[i] = 0, set[i] = xyzw; break; case 'y': offsets[i] = 1, set[i] = xyzw; break; case 'z': offsets[i] = 2, set[i] = xyzw; break; case 'w': offsets[i] = 3, set[i] = xyzw; break; case 'r': offsets[i] = 0, set[i] = rgba; break; case 'g': offsets[i] = 1, set[i] = rgba; break; case 'b': offsets[i] = 2, set[i] = rgba; break; case 'a': offsets[i] = 3, set[i] = rgba; break; case 's': offsets[i] = 0, set[i] = stpq; break; case 't': offsets[i] = 1, set[i] = stpq; break; case 'p': offsets[i] = 2, set[i] = stpq; break; case 'q': offsets[i] = 3, set[i] = stpq; break; default: return error(location, 3018, "invalid subscript '" + subscript + '\''), false; } if (i > 0 && (set[i] != set[i - 1])) return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false; if (static_cast(offsets[i]) >= exp.type.rows) return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false; // The result is not modifiable if a swizzle appears multiple times for (size_t k = 0; k < i; ++k) if (offsets[k] == offsets[i]) { is_const = true; break; } } // Add swizzle to current access chain exp.add_swizzle_access(offsets, static_cast(length)); if (is_const) exp.type.qualifiers |= type::q_const; } else if (exp.type.is_matrix()) { const size_t length = subscript.size(); if (length < 3) return error(location, 3018, "invalid subscript '" + subscript + '\''), false; bool is_const = false; signed char offsets[4] = { -1, -1, -1, -1 }; const unsigned int set = subscript[1] == 'm'; const int coefficient = !set; for (size_t i = 0, j = 0; i < length; i += 3 + set, ++j) { if (subscript[i] != '_' || subscript[i + set + 1] < '0' + coefficient || subscript[i + set + 1] > '3' + coefficient || subscript[i + set + 2] < '0' + coefficient || subscript[i + set + 2] > '3' + coefficient) return error(location, 3018, "invalid subscript '" + subscript + '\''), false; if (set && subscript[i + 1] != 'm') return error(location, 3018, "invalid subscript '" + subscript + "', mixed swizzle sets"), false; const unsigned int row = static_cast((subscript[i + set + 1] - '0') - coefficient); const unsigned int col = static_cast((subscript[i + set + 2] - '0') - coefficient); if ((row >= exp.type.rows || col >= exp.type.cols) || j > 3) return error(location, 3018, "invalid subscript '" + subscript + "', swizzle out of range"), false; offsets[j] = static_cast(row * 4 + col); // The result is not modifiable if a swizzle appears multiple times for (size_t k = 0; k < j; ++k) if (offsets[k] == offsets[j]) { is_const = true; break; } } // Add swizzle to current access chain exp.add_swizzle_access(offsets, static_cast(length / (3 + set))); if (is_const) exp.type.qualifiers |= type::q_const; } else if (exp.type.is_struct()) { const auto &member_list = _codegen->get_struct(exp.type.definition).member_list; // Find member with matching name is structure definition uint32_t member_index = 0; for (const struct_member_info &member : member_list) { if (member.name == subscript) break; ++member_index; } if (member_index >= member_list.size()) return error(location, 3018, "invalid subscript '" + subscript + '\''), false; // Add field index to current access chain exp.add_member_access(member_index, member_list[member_index].type); } else if (exp.type.is_scalar()) { const size_t length = subscript.size(); if (length > 4) return error(location, 3018, "invalid subscript '" + subscript + "', swizzle too long"), false; for (size_t i = 0; i < length; ++i) if ((subscript[i] != 'x' && subscript[i] != 'r' && subscript[i] != 's') || i > 3) return error(location, 3018, "invalid subscript '" + subscript + '\''), false; // Promote scalar to vector type using cast auto target_type = exp.type; target_type.rows = static_cast(length); exp.add_cast_operation(target_type); if (length > 1) exp.type.qualifiers |= type::q_const; } else { error(location, 3018, "invalid subscript '" + subscript + '\''); return false; } } else if (accept('[')) { if (!exp.type.is_array() && !exp.type.is_vector() && !exp.type.is_matrix()) return error(_token.location, 3121, "array, matrix, vector, or indexable object type expected in index expression"), false; // Parse index expression expression index; if (!parse_expression(index) || !expect(']')) return false; else if (!index.type.is_scalar() || !index.type.is_integral()) return error(index.location, 3120, "invalid type for index - index must be an integer scalar"), false; // Add index expression to current access chain if (index.is_constant) { // Check array bounds if known if (exp.type.array_length > 0 && index.constant.as_uint[0] >= static_cast(exp.type.array_length)) return error(index.location, 3504, "array index out of bounds"), false; exp.add_constant_index_access(index.constant.as_uint[0]); } else { if (exp.is_constant) { // To handle a dynamic index into a constant means we need to create a local variable first or else any of the indexing instructions do not work const auto temp_variable = _codegen->define_variable(location, exp.type, std::string(), false, _codegen->emit_constant(exp.type, exp.constant)); exp.reset_to_lvalue(exp.location, temp_variable, exp.type); } exp.add_dynamic_index_access(_codegen->emit_load(index)); } } else { break; } } return true; } bool reshadefx::parser::parse_expression_multary(expression &lhs, unsigned int left_precedence) { // Parse left hand side of the expression if (!parse_expression_unary(lhs)) return false; // Check if an operator exists so that this is a binary or ternary expression unsigned int right_precedence; while (peek_multary_op(right_precedence)) { // Only process this operator if it has a lower precedence than the current operation, otherwise leave it for later and abort if (right_precedence <= left_precedence) break; // Finally consume the operator token consume(); const tokenid op = _token.id; // Check if this is a binary or ternary operation if (op != tokenid::question) { #if RESHADEFX_SHORT_CIRCUIT codegen::id lhs_block = 0; codegen::id rhs_block = 0; codegen::id merge_block = 0; // Switch block to a new one before parsing right-hand side value in case it needs to be skipped during short-circuiting if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) { lhs_block = _codegen->set_block(0); rhs_block = _codegen->create_block(); merge_block = _codegen->create_block(); _codegen->enter_block(rhs_block); } #endif // Parse the right hand side of the binary operation expression rhs; if (!parse_expression_multary(rhs, right_precedence)) return false; // Deduce the result base type based on implicit conversion rules type type = type::merge(lhs.type, rhs.type); bool is_bool_result = false; // Do some error checking depending on the operator if (op == tokenid::equal_equal || op == tokenid::exclaim_equal) { // Equality checks return a boolean value is_bool_result = true; // Cannot check equality between incompatible types if (lhs.type.is_array() || rhs.type.is_array() || lhs.type.definition != rhs.type.definition) return error(rhs.location, 3020, "type mismatch"), false; } else if (op == tokenid::ampersand || op == tokenid::pipe || op == tokenid::caret) { if (type.is_boolean()) type.base = type::t_int; // Cannot perform bitwise operations on non-integral types if (!lhs.type.is_integral()) return error(lhs.location, 3082, "int or unsigned int type required"), false; if (!rhs.type.is_integral()) return error(rhs.location, 3082, "int or unsigned int type required"), false; } else { if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) type.base = type::t_bool; else if (op == tokenid::less || op == tokenid::less_equal || op == tokenid::greater || op == tokenid::greater_equal) is_bool_result = true; // Logical operations return a boolean value else if (type.is_boolean()) type.base = type::t_int; // Arithmetic with boolean values treats the operands as integers // Cannot perform arithmetic operations on non-basic types if (!lhs.type.is_scalar() && !lhs.type.is_vector() && !lhs.type.is_matrix()) return error(lhs.location, 3022, "scalar, vector, or matrix expected"), false; if (!rhs.type.is_scalar() && !rhs.type.is_vector() && !rhs.type.is_matrix()) return error(rhs.location, 3022, "scalar, vector, or matrix expected"), false; } // Perform implicit type conversion if (lhs.type.components() > type.components()) warning(lhs.location, 3206, "implicit truncation of vector type"); if (rhs.type.components() > type.components()) warning(rhs.location, 3206, "implicit truncation of vector type"); lhs.add_cast_operation(type); rhs.add_cast_operation(type); #if RESHADEFX_SHORT_CIRCUIT // Reset block to left-hand side since the load of the left-hand side value has to happen in there if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) _codegen->set_block(lhs_block); #endif // Constant expressions can be evaluated at compile time if (rhs.is_constant && lhs.evaluate_constant_expression(op, rhs.constant)) continue; const auto lhs_value = _codegen->emit_load(lhs); #if RESHADEFX_SHORT_CIRCUIT // Short circuit for logical && and || operators if (op == tokenid::ampersand_ampersand || op == tokenid::pipe_pipe) { // Emit "if ( lhs) result = rhs" for && expression codegen::id condition_value = lhs_value; // Emit "if (!lhs) result = rhs" for || expression if (op == tokenid::pipe_pipe) condition_value = _codegen->emit_unary_op(lhs.location, tokenid::exclaim, type, lhs_value); _codegen->leave_block_and_branch_conditional(condition_value, rhs_block, merge_block); _codegen->set_block(rhs_block); // Only load value of right hand side expression after entering the second block const auto rhs_value = _codegen->emit_load(rhs); _codegen->leave_block_and_branch(merge_block); _codegen->enter_block(merge_block); const auto result_value = _codegen->emit_phi(lhs.location, condition_value, lhs_block, rhs_value, rhs_block, lhs_value, lhs_block, type); lhs.reset_to_rvalue(lhs.location, result_value, type); continue; } #endif const auto rhs_value = _codegen->emit_load(rhs); // Certain operations return a boolean type instead of the type of the input expressions if (is_bool_result) type = { type::t_bool, type.rows, type.cols }; const auto result_value = _codegen->emit_binary_op(lhs.location, op, type, lhs.type, lhs_value, rhs_value); lhs.reset_to_rvalue(lhs.location, result_value, type); } else { // A conditional expression needs a scalar or vector type condition if (!lhs.type.is_scalar() && !lhs.type.is_vector()) return error(lhs.location, 3022, "boolean or vector expression expected"), false; #if RESHADEFX_SHORT_CIRCUIT // Switch block to a new one before parsing first part in case it needs to be skipped during short-circuiting const codegen::id merge_block = _codegen->create_block(); const codegen::id condition_block = _codegen->set_block(0); codegen::id true_block = _codegen->create_block(); codegen::id false_block = _codegen->create_block(); _codegen->enter_block(true_block); #endif // Parse the first part of the right hand side of the ternary operation expression true_exp; if (!parse_expression(true_exp)) return false; if (!expect(':')) return false; #if RESHADEFX_SHORT_CIRCUIT // Switch block to a new one before parsing second part in case it needs to be skipped during short-circuiting _codegen->set_block(0); _codegen->enter_block(false_block); #endif // Parse the second part of the right hand side of the ternary operation expression false_exp; if (!parse_expression_assignment(false_exp)) return false; // Check that the condition dimension matches that of at least one side if (lhs.type.rows != true_exp.type.rows && lhs.type.cols != true_exp.type.cols) return error(lhs.location, 3020, "dimension of conditional does not match value"), false; // Check that the two value expressions can be converted between each other if (true_exp.type.array_length != false_exp.type.array_length || true_exp.type.definition != false_exp.type.definition) return error(false_exp.location, 3020, "type mismatch between conditional values"), false; // Deduce the result base type based on implicit conversion rules const type type = type::merge(true_exp.type, false_exp.type); if (true_exp.type.components() > type.components()) warning(true_exp.location, 3206, "implicit truncation of vector type"); if (false_exp.type.components() > type.components()) warning(false_exp.location, 3206, "implicit truncation of vector type"); #if RESHADEFX_SHORT_CIRCUIT // Reset block to left-hand side since the load of the condition value has to happen in there _codegen->set_block(condition_block); #else // The conditional operator instruction expects the condition to be a boolean type lhs.add_cast_operation({ type::t_bool, type.rows, 1 }); #endif true_exp.add_cast_operation(type); false_exp.add_cast_operation(type); // Load condition value from expression const auto condition_value = _codegen->emit_load(lhs); #if RESHADEFX_SHORT_CIRCUIT _codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block); _codegen->set_block(true_block); // Only load true expression value after entering the first block const auto true_value = _codegen->emit_load(true_exp); true_block = _codegen->leave_block_and_branch(merge_block); _codegen->set_block(false_block); // Only load false expression value after entering the second block const auto false_value = _codegen->emit_load(false_exp); false_block = _codegen->leave_block_and_branch(merge_block); _codegen->enter_block(merge_block); const auto result_value = _codegen->emit_phi(lhs.location, condition_value, condition_block, true_value, true_block, false_value, false_block, type); #else const auto true_value = _codegen->emit_load(true_exp); const auto false_value = _codegen->emit_load(false_exp); const auto result_value = _codegen->emit_ternary_op(lhs.location, op, type, condition_value, true_value, false_value); #endif lhs.reset_to_rvalue(lhs.location, result_value, type); } } return true; } bool reshadefx::parser::parse_expression_assignment(expression &lhs) { // Parse left hand side of the expression if (!parse_expression_multary(lhs)) return false; // Check if an operator exists so that this is an assignment if (accept_assignment_op()) { // Remember the operator token before parsing the expression that follows it const tokenid op = _token.id; // Parse right hand side of the assignment expression // This may be another assignment expression to support chains like "a = b = c = 0;" expression rhs; if (!parse_expression_assignment(rhs)) return false; // Check if the assignment is valid if (lhs.type.has(type::q_const) || !lhs.is_lvalue) return error(lhs.location, 3025, "l-value specifies const object"), false; if (!type::rank(lhs.type, rhs.type)) return error(rhs.location, 3020, "cannot convert these types (from " + rhs.type.description() + " to " + lhs.type.description() + ')'), false; // Cannot perform bitwise operations on non-integral types if (!lhs.type.is_integral() && (op == tokenid::ampersand_equal || op == tokenid::pipe_equal || op == tokenid::caret_equal)) return error(lhs.location, 3082, "int or unsigned int type required"), false; // Perform implicit type conversion of right hand side value if (rhs.type.components() > lhs.type.components()) warning(rhs.location, 3206, "implicit truncation of vector type"); rhs.add_cast_operation(lhs.type); auto result = _codegen->emit_load(rhs); // Check if this is an assignment with an additional arithmetic instruction if (op != tokenid::equal) { // Load value for modification const auto value = _codegen->emit_load(lhs); // Handle arithmetic assignment operation result = _codegen->emit_binary_op(lhs.location, op, lhs.type, value, result); } // Write result back to variable _codegen->emit_store(lhs, result); // Return the result value since you can write assignments within expressions lhs.reset_to_rvalue(lhs.location, result, lhs.type); } return true; }