/*
 * Copyright 2023 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkTypes.h"
#include "include/private/SkSLIRNode.h"
#include "include/private/SkSLModifiers.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/analysis/SkSLProgramVisitor.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLFunctionCall.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLVariable.h"
#include "src/sksl/ir/SkSLVariableReference.h"

namespace SkSL {

bool Analysis::IsDynamicallyUniformExpression(const Expression& expr) {
    class IsDynamicallyUniformExpressionVisitor : public ProgramVisitor {
    public:
        bool visitExpression(const Expression& expr) override {
            switch (expr.kind()) {
                case Expression::Kind::kBinary:
                case Expression::Kind::kConstructorArray:
                case Expression::Kind::kConstructorArrayCast:
                case Expression::Kind::kConstructorCompound:
                case Expression::Kind::kConstructorCompoundCast:
                case Expression::Kind::kConstructorDiagonalMatrix:
                case Expression::Kind::kConstructorMatrixResize:
                case Expression::Kind::kConstructorScalarCast:
                case Expression::Kind::kConstructorSplat:
                case Expression::Kind::kConstructorStruct:
                case Expression::Kind::kFieldAccess:
                case Expression::Kind::kIndex:
                case Expression::Kind::kPostfix:
                case Expression::Kind::kPrefix:
                case Expression::Kind::kSwizzle:
                case Expression::Kind::kTernary:
                    // These expressions might be dynamically uniform, if they are composed entirely
                    // of constants and uniforms.
                    break;

                case Expression::Kind::kVariableReference: {
                    // Verify that variable references are const or uniform.
                    const Variable* var = expr.as<VariableReference>().variable();
                    if (!var || !(var->modifiers().fFlags & (Modifiers::Flag::kConst_Flag |
                                                             Modifiers::Flag::kUniform_Flag))) {
                        fIsDynamicallyUniform = false;
                        return true;
                    }
                    break;
                }
                case Expression::Kind::kFunctionCall: {
                    // Verify that function calls are pure.
                    const FunctionDeclaration& decl = expr.as<FunctionCall>().function();
                    if (!(decl.modifiers().fFlags & Modifiers::Flag::kPure_Flag)) {
                        fIsDynamicallyUniform = false;
                        return true;
                    }
                    break;
                }
                case Expression::Kind::kLiteral:
                    // Literals are compile-time constants.
                    return false;

                default:
                    // This expression isn't dynamically uniform.
                    fIsDynamicallyUniform = false;
                    return true;
            }
            return INHERITED::visitExpression(expr);
        }

        bool fIsDynamicallyUniform = true;
        using INHERITED = ProgramVisitor;
    };

    IsDynamicallyUniformExpressionVisitor visitor;
    visitor.visitExpression(expr);
    return visitor.fIsDynamicallyUniform;
}

}  // namespace SkSL
