#pragma once
#include <list>
#include <string>
#include <vector>
#include "parser/symbol_table.h"

class ASTPrinter;
class CodeGenerator;
class ParserState;

namespace AST
{
class Visitor;
class Node;
class NodeList;
class TypeSpecifier;
class Statement;
class Declaration;
class Expression;
class StreamDeclaration;
class FilterDeclaration;
class FilterWorkBlock;
class IntegerLiteralExpression;
class IdentifierExpression;
class BinaryExpression;
class AssignmentExpression;
class PeekExpression;
class PopExpression;
class PushStatement;
class VariableDeclaration;
class ExpressionStatement;

using LexicalScope = SymbolTable<std::string, AST::Node>;
using ExpressionList = std::vector<Expression*>;

struct SourceLocation
{
  const char* filename;
  int first_line;
  int first_column;
  int last_line;
  int last_column;
};

class StringList
{
public:
  using ListType = std::vector<std::string>;

  StringList() = default;
  ~StringList() = default;

  const std::string& operator[](size_t index) const { return m_values[index]; }
  ListType::const_iterator begin() const { return m_values.begin(); }
  ListType::const_iterator end() const { return m_values.end(); }

  std::string& operator[](size_t index) { return m_values[index]; }
  ListType::iterator begin() { return m_values.begin(); }
  ListType::iterator end() { return m_values.end(); }

  void AddString(const char* str) { m_values.emplace_back(str); }
  void AddString(const std::string& str) { m_values.push_back(str); }

private:
  ListType m_values;
};

class Node
{
public:
  virtual ~Node() = default;
  virtual void Dump(ASTPrinter* printer) const = 0;
  virtual bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) = 0;
  virtual bool Accept(Visitor* visitor) = 0;
};

class NodeList : public Node
{
public:
  using ListType = std::vector<Node*>;

  NodeList() = default;
  ~NodeList() final = default;

  const Node* operator[](size_t index) const { return m_nodes[index]; }
  ListType::const_iterator begin() const { return m_nodes.begin(); }
  ListType::const_iterator end() const { return m_nodes.end(); }

  Node*& operator[](size_t index) { return m_nodes[index]; }
  ListType::iterator begin() { return m_nodes.begin(); }
  ListType::iterator end() { return m_nodes.end(); }

  const ListType& GetNodeList() const { return m_nodes; }
  const size_t GetNumChildren() const { return m_nodes.size(); }
  const Node* GetChild(size_t index) const;
  Node* GetChild(size_t index);

  bool HasChildren() const;
  const Node* GetFirst() const;
  Node* GetFirst();

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  // If node is null, does nothing.
  // If node is a NodeList, adds all the children from it (recursively).
  void AddNode(Node* node);

  // Prepends node/nodelist to this nodelist.
  void PrependNode(Node* node);

private:
  ListType m_nodes;
};

class TypeSpecifier : public Node
{
public:
  enum class TypeId
  {
    Error,
    Void,
    Boolean,
    Bit,
    Int,
    Float,
    APInt,
    Array,
    Struct
  };

public:
  TypeSpecifier(TypeId tid, const std::string& name, TypeSpecifier* base_type, unsigned num_bits);
  virtual ~TypeSpecifier() = default;

  TypeId GetTypeId() const { return m_type_id; }
  const std::string& GetName() const { return m_name; }
  const TypeSpecifier* GetBaseType() const { return m_base_type; }
  unsigned GetNumBits() const { return m_num_bits; }

  // Helper methods
  bool IsPrimitiveType() const { return (m_type_id >= TypeId::Void && m_type_id <= TypeId::APInt); }
  bool IsErrorType() const { return (m_type_id == TypeId::Error); }
  bool IsArrayType() const { return (m_type_id == TypeId::Array); }
  bool IsStructType() const { return (m_type_id == TypeId::Struct); }
  bool HasBooleanBase() const
  {
    return (m_type_id == TypeId::Boolean || (m_base_type && m_base_type->HasBooleanBase()));
  }
  bool HasBitBase() const { return (m_type_id == TypeId::Bit || (m_base_type && m_base_type->HasBitBase())); }
  bool HasIntBase() const { return (m_type_id == TypeId::Int || (m_base_type && m_base_type->HasIntBase())); }
  bool HasFloatBase() const { return (m_type_id == TypeId::Float || (m_base_type && m_base_type->HasFloatBase())); }
  bool IsVoid() const { return (m_type_id == TypeId::Void); }
  bool IsBoolean() const { return (m_type_id == TypeId::Boolean); }
  bool IsBit() const { return (m_type_id == TypeId::Bit); }
  bool IsInt() const { return (m_type_id == TypeId::Int); }
  bool IsFloat() const { return (m_type_id == TypeId::Float); }
  bool IsAPInt() const { return (m_type_id == TypeId::APInt); }
  bool IsIntOrAPInt() const { return (IsInt() || IsAPInt()); }

  // Validity -> error
  bool IsValid() const { return !IsErrorType(); }

  // Clones this type
  virtual TypeSpecifier* Clone() const;

  // Inherited methods
  virtual void Dump(ASTPrinter* printer) const override;
  virtual bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  virtual bool Accept(Visitor* visitor) override;

  // equality operators
  virtual bool operator==(const TypeSpecifier& rhs) const;
  bool operator!=(const TypeSpecifier& rhs) const;

protected:
  TypeId m_type_id;
  std::string m_name;
  TypeSpecifier* m_base_type;
  unsigned m_num_bits;
};

class ArrayTypeSpecifier : public TypeSpecifier
{
public:
  ArrayTypeSpecifier(const std::string& name, TypeSpecifier* base_type, Expression* dimensions);
  virtual ~ArrayTypeSpecifier() = default;

  Expression* GetArrayDimensions() const { return m_array_dimensions; }

  // Inherited methods
  virtual void Dump(ASTPrinter* printer) const override;
  virtual bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  virtual bool Accept(Visitor* visitor) override;

  // Clones this type
  virtual TypeSpecifier* Clone() const override;

  virtual bool operator==(const TypeSpecifier& rhs) const;

private:
  Expression* m_array_dimensions;
};

class Declaration : public Node
{
public:
  Declaration(const SourceLocation& sloc, TypeSpecifier* type, const std::string& name, bool constant);
  virtual ~Declaration() = default;

  const SourceLocation& GetSourceLocation() const { return m_sloc; }
  const TypeSpecifier* GetType() const { return m_type; }
  const std::string& GetName() const { return m_name; }
  bool IsConstant() const { return m_constant; }

protected:
  SourceLocation m_sloc;
  TypeSpecifier* m_type;
  std::string m_name;
  bool m_constant;
};

class Statement : public Node
{
public:
  Statement(const SourceLocation& sloc);
  virtual ~Statement() = default;

  const SourceLocation& GetSourceLocation() const { return m_sloc; }

protected:
  SourceLocation m_sloc;
};

class Expression : public Node
{
public:
  Expression(const SourceLocation& sloc);
  virtual ~Expression() = default;

  const SourceLocation& GetSourceLocation() const { return m_sloc; }

  virtual bool IsConstant() const;
  virtual bool GetConstantBool() const;
  virtual int GetConstantInt() const;
  virtual float GetConstantFloat() const;
  const TypeSpecifier* GetType() const;

protected:
  SourceLocation m_sloc;
  const TypeSpecifier* m_type = nullptr;
};

class ParameterDeclaration : public Declaration
{
public:
  ParameterDeclaration(const SourceLocation& sloc, TypeSpecifier* type_specifier, const std::string& name);
  ~ParameterDeclaration() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;
};
using ParameterDeclarationList = std::vector<ParameterDeclaration*>;

class StreamDeclaration : public Node
{
public:
  StreamDeclaration(const SourceLocation& sloc, TypeSpecifier* input_type_specifier,
                    TypeSpecifier* output_type_specifier, const char* name, ParameterDeclarationList* params);
  ~StreamDeclaration() = default;

  const TypeSpecifier* GetInputType() const { return m_input_type; }
  const TypeSpecifier* GetOutputType() const { return m_output_type; }
  ParameterDeclarationList* GetParameters() const { return m_parameters; }

  const SourceLocation& GetSourceLocation() const { return m_sloc; }
  const std::string& GetName() const { return m_name; }

protected:
  SourceLocation m_sloc;

  TypeSpecifier* m_input_type = nullptr;
  TypeSpecifier* m_output_type = nullptr;

  std::string m_name;

  ParameterDeclarationList* m_parameters;
};

class PipelineDeclaration : public StreamDeclaration
{
public:
  PipelineDeclaration(const SourceLocation& sloc, TypeSpecifier* input_type_specifier,
                      TypeSpecifier* output_type_specifier, const char* name, ParameterDeclarationList* params,
                      NodeList* statements);
  ~PipelineDeclaration() override;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  NodeList* GetStatements() const { return m_statements; }

private:
  NodeList* m_statements;
};

class SplitJoinDeclaration : public StreamDeclaration
{
public:
  SplitJoinDeclaration(const SourceLocation& sloc, TypeSpecifier* input_type_specifier,
                       TypeSpecifier* output_type_specifier, const char* name, ParameterDeclarationList* params,
                       NodeList* statements);
  ~SplitJoinDeclaration() override;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  NodeList* GetStatements() const { return m_statements; }

private:
  NodeList* m_statements;
};

class FilterDeclaration : public StreamDeclaration
{
public:
  FilterDeclaration(const SourceLocation& sloc, TypeSpecifier* input_type_specifier,
                    TypeSpecifier* output_type_specifier, const char* name, ParameterDeclarationList* params,
                    NodeList* vars, FilterWorkBlock* init, FilterWorkBlock* prework, FilterWorkBlock* work,
                    bool stateful);
  FilterDeclaration(TypeSpecifier* input_type_specifier, TypeSpecifier* output_type_specifier, const char* name,
                    ParameterDeclarationList* params, bool stateful, int peek_rate, int pop_rate, int push_rate);
  ~FilterDeclaration() = default;

  // TODO: Const here, but this is a larger change (e.g. visitor impact)
  FilterWorkBlock* GetInitBlock() const { return m_init; }
  FilterWorkBlock* GetPreworkBlock() const { return m_prework; }
  FilterWorkBlock* GetWorkBlock() const { return m_work; }
  ParameterDeclarationList* GetParameters() const { return m_parameters; }
  NodeList* GetStateVariables() const { return m_vars; }
  bool IsStateful() const { return m_stateful; }
  bool IsStateless() const { return !m_stateful; }

  bool HasInitBlock() const { return (m_init != nullptr); }
  bool HasPreworkBlock() const { return (m_prework != nullptr); }
  bool HasWorkBlock() const { return (m_work != nullptr); }
  bool HasStateVariables() const { return (m_vars != nullptr); }
  bool IsBuiltin() const { return m_builtin; }

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  // Test for a builtin filter
  static FilterDeclaration* GetBuiltinFilter(ParserState* state, const std::string& name, NodeList* type_params,
                                             NodeList* params);
  static bool IsBuiltinFilter(const std::string& name);

private:
  NodeList* m_vars;
  FilterWorkBlock* m_init;
  FilterWorkBlock* m_prework;
  FilterWorkBlock* m_work;
  bool m_stateful;
  bool m_builtin;
};

struct FilterWorkParts
{
  NodeList* vars = nullptr;
  FilterWorkBlock* init = nullptr;
  FilterWorkBlock* prework = nullptr;
  FilterWorkBlock* work = nullptr;
};

class FilterWorkBlock final
{
public:
  FilterWorkBlock(const SourceLocation& sloc) : m_sloc(sloc) {}
  ~FilterWorkBlock() = default;

  void Dump(ASTPrinter* printer) const;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table);
  bool Accept(Visitor* visitor);

  Expression* GetPeekRateExpression() const { return m_peek_rate_expr; }
  Expression* GetPopRateExpression() const { return m_pop_rate_expr; }
  Expression* GetPushRateExpression() const { return m_push_rate_expr; }
  const NodeList* GetStatements() const { return m_stmts; }
  NodeList* GetStatements() { return m_stmts; }

  void SetPopRateExpression(Expression* expr) { m_pop_rate_expr = expr; }
  void SetPushRateExpression(Expression* expr) { m_push_rate_expr = expr; }
  void SetStatements(NodeList* stmts) { m_stmts = stmts; }

private:
  SourceLocation m_sloc;
  Expression* m_peek_rate_expr = nullptr;
  Expression* m_pop_rate_expr = nullptr;
  Expression* m_push_rate_expr = nullptr;
  NodeList* m_stmts = nullptr;
};

// Function references map names -> types
class FunctionDeclaration : public Declaration
{
public:
  FunctionDeclaration(const std::string& name, TypeSpecifier* return_type,
                      const std::vector<TypeSpecifier*>& param_types);
  // FunctionDeclaration(const SourceLocation& sloc, const std::string& name, TypeSpecifier* return_type, NodeList*
  // params, NodeList* body);
  ~FunctionDeclaration() = default;

  TypeSpecifier* GetReturnType() const { return m_return_type; }
  const std::vector<TypeSpecifier*>& GetParameterTypes() const { return m_param_types; }

  // Turns println(int) into println___int.
  const std::string& GetSymbolName() const { return m_symbol_name; }

  // Adds streamit_ prefix to builtin symbols.
  std::string GetExecutableSymbolName() const;

  // Builtin function?
  bool IsBuiltin() const { return (m_body == nullptr); }

  void Dump(ASTPrinter* printer) const;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table);
  bool Accept(Visitor* visitor);

private:
  std::string m_symbol_name;
  TypeSpecifier* m_return_type;
  std::vector<TypeSpecifier*> m_param_types;
  NodeList* m_params;
  NodeList* m_body;
};

class IntegerLiteralExpression : public Expression
{
public:
  IntegerLiteralExpression(const SourceLocation& sloc, int value) : Expression(sloc), m_value(value) {}
  ~IntegerLiteralExpression() = default;

  bool IsConstant() const override { return true; }
  int GetConstantInt() const override { return m_value; }

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  int GetValue() const { return m_value; }

private:
  int m_value;
};

class BooleanLiteralExpression : public Expression
{
public:
  BooleanLiteralExpression(const SourceLocation& sloc, bool value) : Expression(sloc), m_value(value) {}
  ~BooleanLiteralExpression() = default;

  bool IsConstant() const override { return true; }
  bool GetConstantBool() const override { return m_value; }

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  bool GetValue() const { return m_value; }

private:
  bool m_value;
};

class FloatLiteralExpression : public Expression
{
public:
  FloatLiteralExpression(const SourceLocation& sloc, float value) : Expression(sloc), m_value(value) {}
  ~FloatLiteralExpression() = default;

  bool IsConstant() const override { return true; }
  float GetConstantFloat() const override { return m_value; }

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  float GetValue() const { return m_value; }

private:
  float m_value;
};

class IdentifierExpression : public Expression
{
public:
  IdentifierExpression(const SourceLocation& sloc, const char* identifier);
  ~IdentifierExpression() = default;

  Declaration* GetReferencedDeclaration() const { return m_declaration; }

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

private:
  std::string m_identifier;
  Declaration* m_declaration = nullptr;
};

class IndexExpression : public Expression
{
public:
  IndexExpression(const SourceLocation& sloc, Expression* array_expr, Expression* index_expr);
  ~IndexExpression() = default;

  Expression* GetArrayExpression() const;
  Expression* GetIndexExpression() const;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

private:
  Expression* m_array_expression;
  Expression* m_index_expression;
};

class UnaryExpression : public Expression
{
public:
  enum Operator : unsigned int
  {
    PreIncrement,
    PreDecrement,
    PostIncrement,
    PostDecrement,
    Positive,
    Negative,
    LogicalNot,
    BitwiseNot
  };

  UnaryExpression(const SourceLocation& sloc, Operator op, Expression* rhs);
  ~UnaryExpression() = default;

  bool IsConstant() const override;
  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Operator GetOperator() const;
  Expression* GetRHSExpression() const;

private:
  Expression* m_rhs;
  Operator m_op;
};

class BinaryExpression : public Expression
{
public:
  enum Operator : unsigned int
  {
    Add,
    Subtract,
    Multiply,
    Divide,
    Modulo,
    BitwiseAnd,
    BitwiseOr,
    BitwiseXor,
    LeftShift,
    RightShift
  };

  BinaryExpression(const SourceLocation& sloc, Expression* lhs, Operator op, Expression* rhs);
  ~BinaryExpression() = default;

  bool IsConstant() const override;
  int GetConstantInt() const override;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Expression* GetLHSExpression() const;
  Expression* GetRHSExpression() const;
  Operator GetOperator() const;

private:
  Expression* m_lhs;
  Expression* m_rhs;
  Operator m_op;
};

class RelationalExpression : public Expression
{
public:
  enum Operator : unsigned int
  {
    Less,
    LessEqual,
    Greater,
    GreaterEqual,
    Equal,
    NotEqual
  };

  RelationalExpression(const SourceLocation& sloc, Expression* lhs, Operator op, Expression* rhs);
  ~RelationalExpression() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Expression* GetLHSExpression() const;
  Expression* GetRHSExpression() const;
  const TypeSpecifier* GetIntermediateType() const;
  Operator GetOperator() const;

private:
  Expression* m_lhs;
  Expression* m_rhs;
  TypeSpecifier* m_intermediate_type;
  Operator m_op;
};

class LogicalExpression : public Expression
{
public:
  enum Operator : unsigned int
  {
    And,
    Or
  };

  LogicalExpression(const SourceLocation& sloc, Expression* lhs, Operator op, Expression* rhs);
  ~LogicalExpression() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Expression* GetLHSExpression() const;
  Expression* GetRHSExpression() const;
  Operator GetOperator() const;

private:
  Expression* m_lhs;
  Expression* m_rhs;
  Operator m_op;
};

class CommaExpression : public Expression
{
public:
  CommaExpression(const SourceLocation& sloc, Expression* lhs, Expression* rhs);
  ~CommaExpression() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Expression* GetLHSExpression() const;
  Expression* GetRHSExpression() const;

private:
  Expression* m_lhs;
  Expression* m_rhs;
};

class AssignmentExpression : public Expression
{
public:
  enum Operator : unsigned int
  {
    Assign,
    Add,
    Subtract,
    Multiply,
    Divide,
    Modulo,
    BitwiseAnd,
    BitwiseOr,
    BitwiseXor,
    LeftShift,
    RightShift
  };

  AssignmentExpression(const SourceLocation& sloc, Expression* lhs, Operator op, Expression* rhs);
  ~AssignmentExpression() = default;

  Expression* GetLValueExpression() const { return m_lhs; }
  Expression* GetInnerExpression() const { return m_rhs; }
  Operator GetOperator() const { return m_op; }

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

private:
  Expression* m_lhs;
  Expression* m_rhs;
  Operator m_op;
};

class PeekExpression : public Expression
{
public:
  PeekExpression(const SourceLocation& sloc, Expression* expr);
  ~PeekExpression() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Expression* GetIndexExpression() const;

private:
  Expression* m_expr;
};

class PopExpression : public Expression
{
public:
  PopExpression(const SourceLocation& sloc);
  ~PopExpression() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;
};

class CallExpression : public Expression
{
public:
  CallExpression(const SourceLocation& sloc, const char* function_name, NodeList* args);
  ~CallExpression() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  const std::string& GetFunctionName() const { return m_function_name; }
  const NodeList* GetArgList() const { return m_args; }
  bool HasArgs() const { return (m_args != nullptr); }

  const FunctionDeclaration* GetFunctionReference() const { return m_function_ref; }

private:
  std::string m_function_name;
  NodeList* m_args;

  const FunctionDeclaration* m_function_ref = nullptr;
};

class CastExpression : public Expression
{
public:
  CastExpression(const SourceLocation& sloc, TypeSpecifier* to_type, Expression* expr);
  ~CastExpression() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  TypeSpecifier* GetToType() const { return m_to_type; }
  Expression* GetExpression() const { return m_expr; }

private:
  TypeSpecifier* m_to_type;
  Expression* m_expr;
};

class PushStatement : public Statement
{
public:
  PushStatement(const SourceLocation& sloc, Expression* expr);
  ~PushStatement() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Expression* GetValueExpression() const;

private:
  Expression* m_expr;
};

class AddStatement : public Statement
{
public:
  AddStatement(const SourceLocation& sloc, const char* filter_name, NodeList* type_parameters, NodeList* parameters);
  ~AddStatement();

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  const std::string& GetStreamName() const { return m_stream_name; }
  StreamDeclaration* GetStreamDeclaration() const { return m_stream_declaration; }
  NodeList* GetStreamParameters() const { return m_stream_parameters; }

private:
  std::string m_stream_name;
  NodeList* m_type_parameters;
  NodeList* m_stream_parameters;
  StreamDeclaration* m_stream_declaration = nullptr;
};

class SplitStatement : public Statement
{
public:
  enum Type : unsigned int
  {
    RoundRobin,
    Duplicate
  };

  SplitStatement(const SourceLocation& sloc, Type type, NodeList* distribution);
  ~SplitStatement() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Type GetType() const { return m_type; }
  NodeList* GetDistribution() const { return m_distribution; }

private:
  Type m_type;
  NodeList* m_distribution;
};

class JoinStatement : public Statement
{
public:
  enum Type : unsigned int
  {
    RoundRobin
  };

  JoinStatement(const SourceLocation& sloc, Type type, NodeList* distribution);
  ~JoinStatement() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Type GetType() const { return m_type; }
  NodeList* GetDistribution() const { return m_distribution; }

private:
  Type m_type;
  NodeList* m_distribution;
};

class InitializerListExpression : public Expression
{
public:
  InitializerListExpression(const SourceLocation& sloc);
  ~InitializerListExpression() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;
  bool IsConstant() const override;

  void AddExpression(Expression* expr);
  const std::vector<Expression*>& GetExpressionList() const;
  size_t GetListSize() const;

private:
  std::vector<Expression*> m_expressions;
};

struct InitDeclarator
{
  SourceLocation sloc;
  const char* name;
  Expression* initializer;
};
using InitDeclaratorList = std::vector<InitDeclarator>;

class VariableDeclaration final : public Declaration
{
public:
  VariableDeclaration(const SourceLocation& sloc, TypeSpecifier* type_specifier, const char* name,
                      Expression* initializer);
  ~VariableDeclaration() override final = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  bool HasInitializer() const { return (m_initializer != nullptr); }
  Expression* GetInitializer() const { return m_initializer; }
  void RemoveInitializer() { m_initializer = nullptr; }

  static Node* CreateDeclarations(TypeSpecifier* type_specifier, const InitDeclaratorList* declarator_list);

private:
  Expression* m_initializer;
};

class ExpressionStatement : public Statement
{
public:
  ExpressionStatement(const SourceLocation& sloc, Expression* expr);
  ~ExpressionStatement() = default;

  Expression* GetInnerExpression() const;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

private:
  Expression* m_expr;
};

class IfStatement : public Statement
{
public:
  IfStatement(const SourceLocation& sloc, Expression* expr, Node* then_stmts, Node* else_stmts);
  ~IfStatement() = default;

  Expression* GetInnerExpression() const;
  Node* GetThenStatements() const;
  Node* GetElseStatements() const;
  bool HasElseStatements() const;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

private:
  Expression* m_expr;
  Node* m_then;
  Node* m_else;
};

class ForStatement : public Statement
{
public:
  ForStatement(const SourceLocation& sloc, Node* init, Expression* cond, Expression* loop, Node* inner);
  ~ForStatement() = default;

  Node* GetInitStatements() const;
  Expression* GetConditionExpression() const;
  Expression* GetLoopExpression() const;
  Node* GetInnerStatements() const;
  bool HasInitStatements() const;
  bool HasConditionExpression() const;
  bool HasLoopExpression() const;
  bool HasInnerStatements() const;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

private:
  Node* m_init;
  Expression* m_cond;
  Expression* m_loop;
  Node* m_inner;
};

class BreakStatement : public Statement
{
public:
  BreakStatement(const SourceLocation& sloc);
  ~BreakStatement() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;
};

class ContinueStatement : public Statement
{
public:
  ContinueStatement(const SourceLocation& sloc);
  ~ContinueStatement() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;
};

class ReturnStatement : public Statement
{
public:
  ReturnStatement(const SourceLocation& sloc, Expression* expr = nullptr);
  ~ReturnStatement() = default;

  void Dump(ASTPrinter* printer) const override;
  bool SemanticAnalysis(ParserState* state, LexicalScope* symbol_table) override;
  bool Accept(Visitor* visitor) override;

  Expression* GetInnerExpression() const;
  bool HasReturnValue() const;

private:
  Expression* m_expr;
};
}

void SetPeekRateExpression(Expression* expr) { m_peek_rate_expr = expr; }


#pragma once
#include <sstream>

class ASTPrinter
{
public:
  ASTPrinter() = default;
  ~ASTPrinter() = default;

private:
  std::stringstream m_ss;
  int m_indent = 0;
};

  std::string ToString() const { return m_ss.str(); }

  void BeginBlock(const char* fmt, ...);
  void Write(const char* fmt, ...);


#pragma once
#include <string>
#include <unordered_map>
#include "common/string_helpers.h"

template <typename NameType, typename ValueType>
class SymbolTable
{
public:
  using MapType = std::unordered_map<NameType, ValueType*>;

  SymbolTable(SymbolTable* parent = nullptr) : m_parent(parent)
  {
    m_id_counter = m_parent ? m_parent->m_id_counter : 1;
  }
  ~SymbolTable() = default;

  typename MapType::const_iterator begin() const { return m_map.begin(); }
  typename MapType::const_iterator end() const { return m_map.end(); }

  SymbolTable* GetParentScope() const { return m_parent; }

  bool HasName(const NameType& name) const
  {
    if (m_parent && m_parent->HasName(name))
      return true;

    return m_map.find(name) != m_map.end();
  }

  bool AddName(const NameType& name, ValueType* node)
  {
    if (HasNameInLocalScope(name))
      return false;

    m_map.emplace(name, node);
    return true;
  }

  ValueType* GetName(const NameType& name) const
  {
    auto* current_table = this;
    while (current_table)
    {
      auto it = current_table->m_map.find(name);
      if (it != current_table->m_map.end())
        return it->second;

      current_table = current_table->m_parent;
    }

    return nullptr;
  }

  unsigned int GenerateNameId() { return m_id_counter++; }

  std::string GenerateName(const char* prefix) { return StringFromFormat("%s_%u", prefix, m_id_counter++); }

private:
  SymbolTable* m_parent;
  MapType m_map;
  unsigned int m_id_counter;
};

bool HasNameInLocalScope(const NameType& name) const { return m_map.find(name) != m_map.end(); }


#pragma once
#include <cstdarg>
#include <memory>
#include <stack>
#include <unordered_map>
#include <vector>

class ParserState;

namespace llvm
{
class Constant;
class ExecutionEngine;
class Function;
class GlobalVariable;
class Module;
}

namespace AST
{
class StreamDeclaration;
class FilterDeclaration;
class PipelineDeclaration;
class SplitJoinDeclaration;
}

namespace Frontend
{
class WrappedLLVMContext;
}

namespace StreamGraph
{
class FilterParameters;
class FilterPermutation;
class Node;
}

namespace StreamGraph
{
class BuilderState;

class Builder
{
public:
  Builder(Frontend::WrappedLLVMContext* context, ParserState* state);
  ~Builder();

  std::unique_ptr<BuilderState> GenerateGraph();

private:
  bool GenerateCode();
  bool GenerateGlobals();
  bool GenerateStreamGraphFunctions();
  bool GenerateStreamFunctionPrototype(AST::StreamDeclaration* decl);
  bool GenerateStreamFunction(AST::StreamDeclaration* decl);
  bool GenerateMain();
  bool CreateExecutionEngine();

  Frontend::WrappedLLVMContext* m_context;
  ParserState* m_parser_state;
  std::unique_ptr<llvm::Module> m_module;
  std::unordered_map<const AST::StreamDeclaration*, llvm::Function*> m_function_map;
  llvm::ExecutionEngine* m_execution_engine = nullptr;

  std::unique_ptr<BuilderState> m_builder_state;
};

// Methods called by generated code.
class BuilderState
{
public:
  BuilderState(Frontend::WrappedLLVMContext* context, ParserState* state);
  ~BuilderState() = default;

  Node* GetStartNode() const { return m_start_node; }
  const std::vector<FilterPermutation*>& GetFilterPermutations() { return m_filter_permutations; }
  Node* GetProgramInputNode() const { return m_program_input_node; }
  Node* GetProgramOutputNode() const { return m_program_output_node; }

  void AddFilter(const AST::FilterDeclaration* decl, int peek_rate, int pop_rate, int push_rate, va_list ap);
  void BeginPipeline(const AST::PipelineDeclaration* decl);
  void EndPipeline();
  void BeginSplitJoin(const AST::SplitJoinDeclaration* decl);
  void EndSplitJoin();
  void SplitJoinSplit(int mode, const std::vector<int>& distribution);
  void SplitJoinJoin(const std::vector<int>& distribution);

  std::string GenerateName(const std::string& prefix);
  void Error(const char* fmt, ...);

private:
  bool HasTopNode() const;
  Node* GetTopNode();

  void ExtractParameters(FilterParameters* out_params, const AST::StreamDeclaration* stream_decl, va_list ap);

  Frontend::WrappedLLVMContext* m_context;
  ParserState* m_parser_state;
  bool m_error_state = false;

  // Graph building.
  unsigned int m_name_id = 1;
  std::stack<Node*> m_node_stack;
  Node* m_start_node = nullptr;
  Node* m_program_input_node = nullptr;
  Node* m_program_output_node = nullptr;

  // Filter permutations
  std::vector<FilterPermutation*> m_filter_permutations;
};

} // namespace Frontend


// Copyright 2007, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Author: wan@google.com (Zhanyong Wan)

// Google Test - The Google C++ Testing Framework
//
// This file implements a universal value printer that can print a
// value of any type T:
//
//   void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr);
//
// It uses the << operator when possible, and prints the bytes in the
// object otherwise.  A user can override its behavior for a class
// type Foo by defining either operator<<(::std::ostream&, const Foo&)
// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that
// defines Foo.

#include "gtest/gtest-printers.h"
#include <ctype.h>
#include <stdio.h>
#include <cwchar>
#include <ostream>  // NOLINT
#include <string>
#include "gtest/internal/gtest-port.h"

namespace testing {

namespace {

using ::std::ostream;

// Prints a segment of bytes in the given object.
GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start,
                                size_t count, ostream* os) {
  char text[5] = "";
  for (size_t i = 0; i != count; i++) {
    const size_t j = start + i;
    if (i != 0) {
      // Organizes the bytes into groups of 2 for easy parsing by
      // human.
      if ((j % 2) == 0)
        *os << ' ';
      else
        *os << '-';
    }
    GTEST_SNPRINTF_(text, sizeof(text), "%02X", obj_bytes[j]);
    *os << text;
  }
}

// Prints the bytes in the given value to the given ostream.
void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count,
                              ostream* os) {
  // Tells the user how big the object is.
  *os << count << "-byte object <";

  const size_t kThreshold = 132;

  // If the object size is bigger than kThreshold, we'll have to omit
  // some details by printing only the first and the last kChunkSize
  // bytes.
  // TODO(wan): let the user control the threshold using a flag.
  if (count < kThreshold) {
    PrintByteSegmentInObjectTo(obj_bytes, 0, count, os);
  } else {
    PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os);
    *os << " ... ";
    // Rounds up to 2-byte boundary.
    const size_t resume_pos = (count - kChunkSize + 1)/2*2;
    PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os);
  }
  *os << ">";
}

}  // namespace

namespace internal2 {

// Delegates to PrintBytesInObjectToImpl() to print the bytes in the
// given object.  The delegation simplifies the implementation, which
// uses the << operator and thus is easier done outside of the
// ::testing::internal namespace, which contains a << operator that
// sometimes conflicts with the one in STL.
void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count,
                          ostream* os) {
  PrintBytesInObjectToImpl(obj_bytes, count, os);
}

}  // namespace internal2

namespace internal {

// Depending on the value of a char (or wchar_t), we print it in one
// of three formats:
//   - as is if it's a printable ASCII (e.g. 'a', '2', ' '),
//   - as a hexidecimal escape sequence (e.g. '\x7F'), or
//   - as a special escape sequence (e.g. '\r', '\n').
enum CharFormat {
  kAsIs,
  kHexEscape,
  kSpecialEscape
};

// Returns true if c is a printable ASCII character.  We test the
// value of c directly instead of calling isprint(), which is buggy on
// Windows Mobile.
inline bool IsPrintableAscii(wchar_t c) {
  return 0x20 <= c && c <= 0x7E;
}

// Prints a wide or narrow char c as a character literal without the
// quotes, escaping it when necessary; returns how c was formatted.
// The template argument UnsignedChar is the unsigned version of Char,
// which is the type of c.
template <typename UnsignedChar, typename Char>
static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) {
  switch (static_cast<wchar_t>(c)) {
    case L'\0':
      *os << "\\0";
      break;
    case L'\'':
      *os << "\\'";
      break;
    case L'\\':
      *os << "\\\\";
      break;
    case L'\a':
      *os << "\\a";
      break;
    case L'\b':
      *os << "\\b";
      break;
    case L'\f':
      *os << "\\f";
      break;
    case L'\n':
      *os << "\\n";
      break;
    case L'\r':
      *os << "\\r";
      break;
    case L'\t':
      *os << "\\t";
      break;
    case L'\v':
      *os << "\\v";
      break;
    default:
      if (IsPrintableAscii(c)) {
        *os << static_cast<char>(c);
        return kAsIs;
      } else {
        *os << "\\x" + String::FormatHexInt(static_cast<UnsignedChar>(c));
        return kHexEscape;
      }
  }
  return kSpecialEscape;
}

// Prints a wchar_t c as if it's part of a string literal, escaping it when
// necessary; returns how c was formatted.
static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) {
  switch (c) {
    case L'\'':
      *os << "'";
      return kAsIs;
    case L'"':
      *os << "\\\"";
      return kSpecialEscape;
    default:
      return PrintAsCharLiteralTo<wchar_t>(c, os);
  }
}

// Prints a char c as if it's part of a string literal, escaping it when
// necessary; returns how c was formatted.
static CharFormat PrintAsStringLiteralTo(char c, ostream* os) {
  return PrintAsStringLiteralTo(
      static_cast<wchar_t>(static_cast<unsigned char>(c)), os);
}

// Prints a wide or narrow character c and its code.  '\0' is printed
// as "'\\0'", other unprintable characters are also properly escaped
// using the standard C++ escape sequence.  The template argument
// UnsignedChar is the unsigned version of Char, which is the type of c.
template <typename UnsignedChar, typename Char>
void PrintCharAndCodeTo(Char c, ostream* os) {
  // First, print c as a literal in the most readable form we can find.
  *os << ((sizeof(c) > 1) ? "L'" : "'");
  const CharFormat format = PrintAsCharLiteralTo<UnsignedChar>(c, os);
  *os << "'";

  // To aid user debugging, we also print c's code in decimal, unless
  // it's 0 (in which case c was printed as '\\0', making the code
  // obvious).
  if (c == 0)
    return;
  *os << " (" << static_cast<int>(c);

  // For more convenience, we print c's code again in hexidecimal,
  // unless c was already printed in the form '\x##' or the code is in
  // [1, 9].
  if (format == kHexEscape || (1 <= c && c <= 9)) {
    // Do nothing.
  } else {
    *os << ", 0x" << String::FormatHexInt(static_cast<UnsignedChar>(c));
  }
  *os << ")";
}

void PrintTo(unsigned char c, ::std::ostream* os) {
  PrintCharAndCodeTo<unsigned char>(c, os);
}
void PrintTo(signed char c, ::std::ostream* os) {
  PrintCharAndCodeTo<unsigned char>(c, os);
}

// Prints a wchar_t as a symbol if it is printable or as its internal
// code otherwise and also as its code.  L'\0' is printed as "L'\\0'".
void PrintTo(wchar_t wc, ostream* os) {
  PrintCharAndCodeTo<wchar_t>(wc, os);
}

// Prints the given array of characters to the ostream.  CharType must be either
// char or wchar_t.
// The array starts at begin, the length is len, it may include '\0' characters
// and may not be NUL-terminated.
template <typename CharType>
GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
static void PrintCharsAsStringTo(
    const CharType* begin, size_t len, ostream* os) {
  const char* const kQuoteBegin = sizeof(CharType) == 1 ? "\"" : "L\"";
  *os << kQuoteBegin;
  bool is_previous_hex = false;
  for (size_t index = 0; index < len; ++index) {
    const CharType cur = begin[index];
    if (is_previous_hex && IsXDigit(cur)) {
      // Previous character is of '\x..' form and this character can be
      // interpreted as another hexadecimal digit in its number. Break string to
      // disambiguate.
      *os << "\" " << kQuoteBegin;
    }
    is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape;
  }
  *os << "\"";
}

// Prints a (const) char/wchar_t array of 'len' elements, starting at address
// 'begin'.  CharType must be either char or wchar_t.
template <typename CharType>
GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
static void UniversalPrintCharArray(
    const CharType* begin, size_t len, ostream* os) {
  // The code
  //   const char kFoo[] = "foo";
  // generates an array of 4, not 3, elements, with the last one being '\0'.
  //
  // Therefore when printing a char array, we don't print the last element if
  // it's '\0', such that the output matches the string literal as it's
  // written in the source code.
  if (len > 0 && begin[len - 1] == '\0') {
    PrintCharsAsStringTo(begin, len - 1, os);
    return;
  }

  // If, however, the last element in the array is not '\0', e.g.
  //    const char kFoo[] = { 'f', 'o', 'o' };
  // we must print the entire array.  We also print a message to indicate
  // that the array is not NUL-terminated.
  PrintCharsAsStringTo(begin, len, os);
  *os << " (no terminating NUL)";
}

// Prints a (const) char array of 'len' elements, starting at address 'begin'.
void UniversalPrintArray(const char* begin, size_t len, ostream* os) {
  UniversalPrintCharArray(begin, len, os);
}

// Prints a (const) wchar_t array of 'len' elements, starting at address
// 'begin'.
void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) {
  UniversalPrintCharArray(begin, len, os);
}

// Prints the given C string to the ostream.
void PrintTo(const char* s, ostream* os) {
  if (s == NULL) {
    *os << "NULL";
  } else {
    *os << ImplicitCast_<const void*>(s) << " pointing to ";
    PrintCharsAsStringTo(s, strlen(s), os);
  }
}

// MSVC compiler can be configured to define whar_t as a typedef
// of unsigned short. Defining an overload for const wchar_t* in that case
// would cause pointers to unsigned shorts be printed as wide strings,
// possibly accessing more memory than intended and causing invalid
// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when
// wchar_t is implemented as a native type.
#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED)
// Prints the given wide C string to the ostream.
void PrintTo(const wchar_t* s, ostream* os) {
  if (s == NULL) {
    *os << "NULL";
  } else {
    *os << ImplicitCast_<const void*>(s) << " pointing to ";
    PrintCharsAsStringTo(s, std::wcslen(s), os);
  }
}
#endif  // wchar_t is native

// Prints a ::string object.
#if GTEST_HAS_GLOBAL_STRING
void PrintStringTo(const ::string& s, ostream* os) {
  PrintCharsAsStringTo(s.data(), s.size(), os);
}
#endif  // GTEST_HAS_GLOBAL_STRING

void PrintStringTo(const ::std::string& s, ostream* os) {
  PrintCharsAsStringTo(s.data(), s.size(), os);
}

// Prints a ::wstring object.
#if GTEST_HAS_GLOBAL_WSTRING
void PrintWideStringTo(const ::wstring& s, ostream* os) {
  PrintCharsAsStringTo(s.data(), s.size(), os);
}
#endif  // GTEST_HAS_GLOBAL_WSTRING

#if GTEST_HAS_STD_WSTRING
void PrintWideStringTo(const ::std::wstring& s, ostream* os) {
  PrintCharsAsStringTo(s.data(), s.size(), os);
}
#endif  // GTEST_HAS_STD_WSTRING

}  // namespace internal

}  // namespace testing


// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Author: mheule@google.com (Markus Heule)
//
// The Google C++ Testing Framework (Google Test)

#include "gtest/gtest-test-part.h"

// Indicates that this translation unit is part of Google Test's
// implementation.  It must come before gtest-internal-inl.h is
// included, or there will be a compiler error.  This trick exists to
// prevent the accidental inclusion of gtest-internal-inl.h in the
// user's code.
#define GTEST_IMPLEMENTATION_ 1
#include "src/gtest-internal-inl.h"
#undef GTEST_IMPLEMENTATION_

namespace testing {

using internal::GetUnitTestImpl;

// Gets the summary of the failure message by omitting the stack trace
// in it.
std::string TestPartResult::ExtractSummary(const char* message) {

  return stack_trace == NULL ? message :
      std::string(message, stack_trace);
}

// Prints a TestPartResult object.
std::ostream& operator<<(std::ostream& os, const TestPartResult& result) {
  return os
      << result.file_name() << ":" << result.line_number() << ": "
      << (result.type() == TestPartResult::kSuccess ? "Success" :
          result.type() == TestPartResult::kFatalFailure ? "Fatal failure" :
          "Non-fatal failure") << ":\n"
      << result.message() << std::endl;
}

// Appends a TestPartResult to the array.
void TestPartResultArray::Append(const TestPartResult& result) {
  array_.push_back(result);
}

// Returns the TestPartResult at the given index (0-based).
const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const {
  if (index < 0 || index >= size()) {
    printf("\nInvalid index (%d) into TestPartResultArray.\n", index);
    internal::posix::Abort();
  }

  return array_[index];
}

// Returns the number of TestPartResult objects in the array.
int TestPartResultArray::size() const {
  return static_cast<int>(array_.size());
}

namespace internal {

HasNewFatalFailureHelper::HasNewFatalFailureHelper()
    : has_new_fatal_failure_(false),
      original_reporter_(GetUnitTestImpl()->
                         GetTestPartResultReporterForCurrentThread()) {
  GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this);
}

HasNewFatalFailureHelper::~HasNewFatalFailureHelper() {
  GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(
      original_reporter_);
}

void HasNewFatalFailureHelper::ReportTestPartResult(
    const TestPartResult& result) {
  if (result.fatally_failed())
    has_new_fatal_failure_ = true;
  original_reporter_->ReportTestPartResult(result);
}

}  // namespace internal

}  // namespace testing

const char* const stack_trace = strstr(message, internal::kStackTraceMarker);


#include "hlstarget/test_bench_generator.h"
#include <cassert>
#include <iomanip>
#include <sstream>
#include <vector>
#include "common/log.h"
#include "common/string_helpers.h"
#include "common/types.h"
#include "frontend/function_builder.h"
#include "frontend/state_variables_builder.h"
#include "frontend/wrapped_llvm_context.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/IR/Argument.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/TargetSelect.h"
#include "parser/ast.h"
#include "streamgraph/streamgraph.h"
Log_SetChannel(HLSTarget::TestBenchGenerator);

// TODO: Move this elsewhere
#if defined(_WIN32) || defined(__CYGWIN__)
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __attribute__((visibility("default")))
#endif

constexpr size_t DATA_BUFFER_SIZE = 1024 * 1024;
EXPORT byte test_bench_gen_input_buffer[DATA_BUFFER_SIZE];
EXPORT u32 test_bench_gen_input_buffer_pos;
EXPORT byte test_bench_gen_output_buffer[DATA_BUFFER_SIZE];

namespace HLSTarget
{
TestBenchGenerator::TestBenchGenerator(Frontend::WrappedLLVMContext* context, StreamGraph::StreamGraph* streamgraph,
                                       const std::string& module_name, const std::string& out_dir)
  : m_context(context), m_stream_graph(streamgraph), m_module_name(module_name), m_output_directory(out_dir)
{
}

TestBenchGenerator::~TestBenchGenerator()
{
  delete m_module;
}

bool TestBenchGenerator::GenerateTestBenches()
{
  CreateModule();

  if (!GenerateFilterFunctions())
    return false;

  if (!CreateExecutionEngine())
    return false;

  ExecuteFilters();

  if (!GenerateTestBenchCCode())
    return false;

  return true;
}

void TestBenchGenerator::CreateModule()
{
  m_module = m_context->CreateModule(m_module_name.c_str());
  Log_InfoPrintf("Module name is '%s'", m_module_name.c_str());

  // Create data buffers in the module.
  // When the JIT compiles this code, it will resolve the symbols defined above.
  // This is how we pass the data from the JIT back to us.
  llvm::Type* buffer_type = llvm::ArrayType::get(m_context->GetByteType(), DATA_BUFFER_SIZE);
  assert(buffer_type && "buffer type is a valid type");
  m_input_buffer_global = new llvm::GlobalVariable(*m_module, buffer_type, false, llvm::GlobalValue::ExternalLinkage,
                                                   nullptr, "test_bench_gen_input_buffer");
  m_input_buffer_pos_global =
    new llvm::GlobalVariable(*m_module, m_context->GetIntType(), false, llvm::GlobalVariable::ExternalLinkage, nullptr,
                             "test_bench_gen_input_buffer_pos");
  m_output_buffer_global = new llvm::GlobalVariable(*m_module, buffer_type, false, llvm::GlobalValue::ExternalLinkage,
                                                    nullptr, "test_bench_gen_output_buffer");
  m_output_buffer_pos_global =
    new llvm::GlobalVariable(*m_module, m_context->GetIntType(), false, llvm::GlobalVariable::ExternalLinkage, nullptr,
                             "test_bench_gen_output_buffer_pos");
}

bool TestBenchGenerator::GenerateFilterFunctions()
{
  for (const StreamGraph::FilterPermutation* filter_perm : m_stream_graph->GetFilterPermutationList())
  {
    llvm::Function* func = GenerateFilterFunction(filter_perm);
    if (!func)
      return false;

    m_filter_functions.emplace(filter_perm, func);
  }

  Log_InfoPrintf("Generated %u filter functions", unsigned(m_filter_functions.size()));

  // m_context->DumpModule(m_module);
  return true;
}

namespace
{
class FragmentBuilder : public Frontend::FunctionBuilder::TargetFragmentBuilder
{
public:
  FragmentBuilder(Frontend::WrappedLLVMContext* context, llvm::Type* input_type, llvm::GlobalVariable* input_buffer_var,
                  llvm::GlobalVariable* input_buffer_pos_var, llvm::Type* output_type,
                  llvm::GlobalVariable* output_buffer_var, llvm::GlobalVariable* output_buffer_pos_var)
    : m_context(context), m_input_type(input_type), m_input_buffer_var(input_buffer_var),
      m_input_buffer_pos_var(input_buffer_pos_var), m_output_type(output_type), m_output_buffer_var(output_buffer_var),
      m_output_buffer_pos_var(output_buffer_pos_var)
  {
  }

  void CreateTemporaries(llvm::IRBuilder<>& builder)
  {
    // TODO: Fix this type
    if (!m_input_type->isVoidTy())
      m_input_temp = builder.CreateAlloca(m_input_type, nullptr, "bitcast_input_temp");
    if (!m_output_type->isVoidTy())
      m_output_temp = builder.CreateAlloca(m_output_type, nullptr, "bitcast_output_temp");
  }

  u32 GetSizeOfInputElement() const
  {
    // Round up to nearest byte size.
    return (m_input_type->getPrimitiveSizeInBits() + 7) / 8;
  }

  u32 GetSizeOfOutputElement() const
  {
    // Round up to nearest byte size.
    return (m_output_type->getPrimitiveSizeInBits() + 7) / 8;
  }

  llvm::Value* BuildPop(llvm::IRBuilder<>& builder) override final
  {
    // pop() is the same data as peek(0)
    llvm::Value* peek_val = BuildPeek(builder, builder.getInt32(0));
    // new_pos <- input_buffer_pos + sizeof(elem)
    llvm::Value* new_pos = builder.CreateAdd(builder.CreateLoad(m_input_buffer_pos_var, "input_buffer_pos"),
                                             builder.getInt32(GetSizeOfInputElement()));
    // input_buffer_pos <- new_pos
    builder.CreateStore(new_pos, m_input_buffer_pos_var);
    return peek_val;
  }

  llvm::Value* BuildPeek(llvm::IRBuilder<>& builder, llvm::Value* idx_value) override final
  {
    llvm::Type* byte_ptr_type = llvm::PointerType::getUnqual(m_context->GetByteType());
    // input_buffer_pos <- GLOBAL_input_buffer_pos
    llvm::Value* input_buffer_pos = builder.CreateLoad(m_input_buffer_pos_var, "input_buffer_pos");
    // input_buffer_ptr <- &GLOBAL_input_buffer[input_buffer_pos]
    llvm::Value* input_buffer_ptr =
      builder.CreateInBoundsGEP(m_input_buffer_var, {builder.getInt32(0), input_buffer_pos}, "input_buffer_ptr");
    // bitcast_temp_ptr <- (i8*)&bitcast_temp
    llvm::Value* raw_ptr = builder.CreateBitCast(m_input_temp, byte_ptr_type, "bitcast_temp_ptr");
    // memcpy(bitcast_temp_ptr, input_buffer_ptr, sizeof(elem))
    builder.CreateMemCpy(raw_ptr, input_buffer_ptr, GetSizeOfInputElement(), GetSizeOfInputElement());
    // peek_val <- bitcast_temp
    return builder.CreateLoad(m_input_temp, "peek_val");
  }

  bool BuildPush(llvm::IRBuilder<>& builder, llvm::Value* value) override final
  {
    llvm::Type* byte_ptr_type = llvm::PointerType::getUnqual(m_context->GetByteType());
    // input_buffer_pos <- GLOBAL_input_buffer_pos
    llvm::Value* input_buffer_pos = builder.CreateLoad(m_output_buffer_pos_var, "input_buffer_pos");
    // input_buffer_ptr <- &GLOBAL_input_buffer[input_buffer_pos]
    llvm::Value* input_buffer_ptr =
      builder.CreateInBoundsGEP(m_output_buffer_var, {builder.getInt32(0), input_buffer_pos}, "input_buffer_ptr");
    // bitcast_temp <- value
    builder.CreateStore(value, m_output_temp);
    // bitcast_temp_ptr <- (i8*)&value
    llvm::Value* raw_ptr = builder.CreateBitCast(m_output_temp, byte_ptr_type, "value_raw_ptr");
    // memcpy(bitcast_temp_ptr, input_buffer_ptr, sizeof(elem))
    builder.CreateMemCpy(input_buffer_ptr, raw_ptr, GetSizeOfOutputElement(), GetSizeOfOutputElement());
    // new_pos <- input_buffer_pos + sizeof(elem)
    llvm::Value* new_pos = builder.CreateAdd(builder.CreateLoad(m_output_buffer_pos_var, "input_buffer_pos"),
                                             builder.getInt32(GetSizeOfOutputElement()));
    // input_buffer_pos <- new_pos
    builder.CreateStore(new_pos, m_output_buffer_pos_var);
    return true;
  }

private:
  Frontend::WrappedLLVMContext* m_context;
  llvm::AllocaInst* m_input_temp = nullptr;
  llvm::AllocaInst* m_output_temp = nullptr;
  llvm::Type* m_input_type;
  llvm::GlobalVariable* m_input_buffer_var;
  llvm::GlobalVariable* m_input_buffer_pos_var;
  llvm::Type* m_output_type;
  llvm::GlobalVariable* m_output_buffer_var;
  llvm::GlobalVariable* m_output_buffer_pos_var;
};
}

llvm::Function* TestBenchGenerator::GenerateFilterFunction(const StreamGraph::FilterPermutation* filter_perm)
{
  std::string function_name = StringFromFormat("%s_work", filter_perm->GetName().c_str());
  llvm::FunctionType* func_type = llvm::FunctionType::get(m_context->GetVoidType(), {}, false);
  llvm::Constant* func_cons = m_module->getOrInsertFunction(function_name, func_type);
  llvm::Function* func = llvm::dyn_cast<llvm::Function>(func_cons);
  if (!func)
  {
    Log_ErrorPrintf("Could not create function '%s'", function_name.c_str());
    return nullptr;
  }

  FragmentBuilder fragment_builder(m_context, filter_perm->GetInputType(), m_input_buffer_global,
                                   m_input_buffer_pos_global, filter_perm->GetOutputType(), m_output_buffer_global,
                                   m_output_buffer_pos_global);
  Frontend::FunctionBuilder function_builder(m_context, m_module, &fragment_builder, func);
  fragment_builder.CreateTemporaries(function_builder.GetCurrentIRBuilder());

  // Add constants for the filter parameters
  for (const auto& it : filter_perm->GetFilterParameters())
    function_builder.AddVariable(it.decl, it.value);

  // Visit the state variable declarations, generating LLVM variables for them
  if (filter_perm->GetFilterDeclaration()->HasStateVariables())
  {
    Frontend::WrappedLLVMContext::VariableMap vm;
    for (const auto& it : filter_perm->GetFilterParameters())
      vm.emplace(it.decl, it.value);
    m_context->PushVariableMap(&vm);

    Frontend::StateVariablesBuilder gvb(m_context, m_module, filter_perm->GetName());
    if (!filter_perm->GetFilterDeclaration()->GetStateVariables()->Accept(&gvb))
    {
      m_context->PopVariableMap();
      return nullptr;
    }

    m_context->PopVariableMap();

    for (const auto& it : gvb.GetVariableMap())
      function_builder.AddVariable(it.first, it.second);
  }

  // Generate the actual function.
  Log_DevPrintf("Generating work function '%s' for filter '%s'", function_name.c_str(), filter_perm->GetName().c_str());
  assert(filter_perm->GetFilterDeclaration()->HasWorkBlock());
  if (!filter_perm->GetFilterDeclaration()->GetWorkBlock()->Accept(&function_builder))
    return nullptr;

  // TODO: Need to insert the return. This should be fixed.
  function_builder.GetCurrentIRBuilder().CreateRetVoid();
  return func;
}

bool TestBenchGenerator::CreateExecutionEngine()
{
  llvm::InitializeNativeTarget();
  llvm::InitializeNativeTargetAsmPrinter();
  llvm::InitializeNativeTargetAsmParser();

  std::string error_msg;
  m_execution_engine = llvm::EngineBuilder(std::unique_ptr<llvm::Module>(m_module)).setErrorStr(&error_msg).create();

  if (!m_execution_engine)
  {
    Log_ErrorPrintf("Failed to create LLVM execution engine: %s", error_msg.c_str());
    return false;
  }

  m_module = nullptr;
  m_execution_engine->finalizeObject();
  return true;
}

namespace
{

class FilterExecutorVisitor : public StreamGraph::Visitor
{
public:
  FilterExecutorVisitor(Frontend::WrappedLLVMContext* context, StreamGraph::StreamGraph* streamgraph,
                        llvm::ExecutionEngine* execution_engine, TestBenchGenerator::FilterDataMap& filter_input_data,
                        TestBenchGenerator::FilterDataMap& filter_output_data)
    : m_context(context), m_streamgraph(streamgraph), m_execution_engine(execution_engine),
      m_filter_input_data(filter_input_data), m_filter_output_data(filter_output_data)
  {
  }

  virtual bool Visit(StreamGraph::Filter* node) override;
  virtual bool Visit(StreamGraph::Pipeline* node) override;
  virtual bool Visit(StreamGraph::SplitJoin* node) override;
  virtual bool Visit(StreamGraph::Split* node) override;
  virtual bool Visit(StreamGraph::Join* node) override;

private:
  Frontend::WrappedLLVMContext* m_context;
  StreamGraph::StreamGraph* m_streamgraph;
  llvm::ExecutionEngine* m_execution_engine;
  TestBenchGenerator::FilterDataMap& m_filter_input_data;
  TestBenchGenerator::FilterDataMap& m_filter_output_data;
};

bool FilterExecutorVisitor::Visit(StreamGraph::Filter* node)
{
  const StreamGraph::FilterPermutation* filter_perm = node->GetFilterPermutation();

  bool skip_execution = false;
  if (filter_perm->IsBuiltin())
  {
    if (m_streamgraph->GetProgramInputNode() == node)
    {
      u32 size_per_elem = (filter_perm->GetOutputType()->getPrimitiveSizeInBits() + 7) / 8;
      u32 input_data_count = node->GetNetPush();
      assert(size_per_elem > 0 && input_data_count > 0);

      u32 counter = 1;
      byte* ptr = test_bench_gen_output_buffer;
      for (u32 i = 0; i < input_data_count; i++)
      {
        std::memcpy(ptr, &counter, size_per_elem);
        ptr += size_per_elem;
        counter++;
      }

      test_bench_gen_output_buffer_pos = input_data_count * size_per_elem;
      Log_DevPrintf("Generated %u byts of input for %s", test_bench_gen_output_buffer_pos, node->GetName().c_str());
      skip_execution = true;
    }
    else if (m_streamgraph->GetProgramOutputNode() == node)
    {
      // Do nothing for output nodes.
      return true;
    }
  }

  if (!skip_execution)
  {
    // Copy the last filter execution's output to this execution's input.
    test_bench_gen_input_buffer_pos = 0;
    if (!filter_perm->GetInputType()->isVoidTy())
    {
      u32 input_data_size = test_bench_gen_output_buffer_pos;
      if (input_data_size > 0)
      {
        std::copy_n(test_bench_gen_output_buffer, input_data_size, test_bench_gen_input_buffer);
        std::fill_n(test_bench_gen_input_buffer + input_data_size,
                    sizeof(test_bench_gen_input_buffer) - input_data_size, 0);
        Log_DevPrintf("Using %u bytes from previous filter", test_bench_gen_output_buffer_pos);
        auto& data_vec = m_filter_input_data[filter_perm];
        data_vec.resize(input_data_size);
        std::copy_n(test_bench_gen_input_buffer, input_data_size, data_vec.begin());
      }
    }

    // Ensure remaining data is zeroed out.
    std::fill_n(test_bench_gen_output_buffer, sizeof(test_bench_gen_output_buffer), 0);
    test_bench_gen_output_buffer_pos = 0;

    // Execute filter
    std::string function_name = StringFromFormat("%s_work", filter_perm->GetName().c_str());
    llvm::Function* filter_func = m_execution_engine->FindFunctionNamed(function_name);
    assert(filter_func && "filter function exists in execution engine");
    Log_DevPrintf("Executing filter '%s' %u times", function_name.c_str(), node->GetMultiplicity());
    for (u32 i = 0; i < node->GetMultiplicity(); i++)
      m_execution_engine->runFunction(filter_func, {});
    Log_DevPrintf("Exec result %u %u", test_bench_gen_input_buffer_pos, test_bench_gen_output_buffer_pos);
  }

  // Do we have output data?
  if (!filter_perm->GetOutputType()->isVoidTy())
  {
    std::string hexdump = HexDumpString(test_bench_gen_output_buffer, test_bench_gen_output_buffer_pos);
    Log_DevPrintf("Output data\n%s", hexdump.c_str());

    auto& data_vec = m_filter_output_data[filter_perm];
    data_vec.resize(test_bench_gen_output_buffer_pos);
    std::copy_n(test_bench_gen_output_buffer, test_bench_gen_output_buffer_pos, data_vec.begin());
  }

  return true;
}

bool FilterExecutorVisitor::Visit(StreamGraph::Pipeline* node)
{
  for (StreamGraph::Node* child : node->GetChildren())
  {
    if (!child->Accept(this))
      return false;
  }

  return true;
}

bool FilterExecutorVisitor::Visit(StreamGraph::SplitJoin* node)
{
  // TODO
  return true;
}

bool FilterExecutorVisitor::Visit(StreamGraph::Split* node)
{
  // TODO
  return true;
}

bool FilterExecutorVisitor::Visit(StreamGraph::Join* node)
{
  // TODO
  return true;
}

} // namespace

void TestBenchGenerator::ExecuteFilters()
{
  // Zero out buffers so we don't get any previous/undefined data.
  std::fill_n(test_bench_gen_input_buffer, sizeof(test_bench_gen_input_buffer), 0);
  std::fill_n(test_bench_gen_output_buffer, sizeof(test_bench_gen_output_buffer), 0);
  test_bench_gen_input_buffer_pos = 0;
  test_bench_gen_output_buffer_pos = 0;

  FilterExecutorVisitor visitor(m_context, m_stream_graph, m_execution_engine, m_filter_input_data,
                                m_filter_output_data);
  m_stream_graph->GetRootNode()->Accept(&visitor);
}

static std::string GetTestBenchTypeName(const llvm::Type* ty)
{
  if (ty->isIntegerTy())
  {
    const llvm::IntegerType* int_ty = llvm::cast<const llvm::IntegerType>(ty);
    if (int_ty->getBitWidth() == 1)
      return "uint1";
    else if (int_ty->getBitWidth() == 8)
      return "uint8_t";
    else if (int_ty->getBitWidth() == 16)
      return "uint16_t";
    else if (int_ty->getBitWidth() == 32)
      return "uint32_t";
    else
      return StringFromFormat("uint%u", int_ty->getBitWidth());
  }
  else if (ty->isFloatTy())
  {
    return "float";
  }
  else if (ty->isDoubleTy())
  {
    return "double";
  }
  else if (ty->isVoidTy())
  {
    return "void";
  }
  else
  {
    assert(0 && "unknown type");
    return "";
  }
}

static u32 GetTestBenchTypeSize(const llvm::Type* ty)
{
  // Round up to nearest byte.
  if (ty->isVoidTy())
    return 0;

  return (ty->getPrimitiveSizeInBits() + 7) / 8;
}

static void WriteTestBenchFor(const StreamGraph::FilterPermutation* filter_perm,
                              const TestBenchGenerator::FilterDataVector* input_data,
                              const TestBenchGenerator::FilterDataVector* output_data, llvm::raw_ostream& os)
{
  // TODO: Fix this for types...
  const std::string in_type = GetTestBenchTypeName(filter_perm->GetInputType());
  u32 in_element_size = GetTestBenchTypeSize(filter_perm->GetInputType());
  u32 input_count = input_data ? (input_data->size() / in_element_size) : 0;
  u32 inputs_per_iteration = 0;
  const std::string out_type = GetTestBenchTypeName(filter_perm->GetOutputType());
  u32 out_element_size = GetTestBenchTypeSize(filter_perm->GetOutputType());
  u32 output_count = output_data ? (output_data->size() / out_element_size) : 0;
  u32 outputs_per_iteration = 0;

  // Declaration of top function
  if (!filter_perm->IsCombinational())
  {
    os << "void filter_" << filter_perm->GetName() << "(";
    if (input_data)
    {
      os << in_type << "*";
      inputs_per_iteration = filter_perm->GetPopRate();
      if (output_data)
      {
        os << ", " << out_type << "*";
        outputs_per_iteration = filter_perm->GetPushRate();
      }
    }
    else if (output_data)
    {
      os << out_type << "*";
      outputs_per_iteration = filter_perm->GetPushRate();
    }
    os << ");\n";
  }
  else
  {
    if (output_data)
    {
      os << out_type << " ";
      outputs_per_iteration = filter_perm->GetPushRate();
    }
    else
    {
      os << out_type << "void ";
    }
    os << "filter_" << filter_perm->GetName() << "(";
    if (input_data)
    {
      os << in_type;
      inputs_per_iteration = filter_perm->GetPopRate();
    }
    os << ");\n";
  }

  // Definition of test function
  os << "int test_filter_" << filter_perm->GetName() << "() {\n";

  // Sometimes it doesn't compile in C99 mode...
  os << "  int i;\n";

  auto WriteData = [&](const char* array_name, const llvm::Type* element_type, const std::string& element_type_name,
                       u32 element_size, u32 element_count, const TestBenchGenerator::FilterDataVector* data) {
    const byte* current_in_ptr = data->data();
    os << "  " << element_type_name << " " << array_name << "[" << element_count << "] = {\n";
    for (u32 i = 0; i < element_count; i++)
    {
      // This will only work on little-endian..
      u32 value = 0;
      std::memcpy(&value, current_in_ptr, element_size);
      current_in_ptr += element_size;
      os << "    " << llvm::format_hex(value, 10, true);
      if (element_type->isIntegerTy())
        os << " & " << llvm::cast<const llvm::IntegerType>(element_type)->getBitMask();
      os << ",\n";
    }
    os << "  };\n";
  };

  // Write input data
  if (input_data)
    WriteData("INPUT_DATA", filter_perm->GetInputType(), in_type, in_element_size, input_count, input_data);

  // Write expected output data
  if (output_data)
  {
    WriteData("REFERENCE_OUTPUT_DATA", filter_perm->GetOutputType(), out_type, out_element_size, output_count,
              output_data);
    os << "  " << out_type << " OUTPUT_DATA[" << output_count << "];\n";
  }

  u32 input_iterations = inputs_per_iteration ? input_count / inputs_per_iteration : 0;
  u32 output_iterations = outputs_per_iteration ? output_count / outputs_per_iteration : 0;
  u32 num_iterations = input_data ? input_iterations : output_iterations;
  if (input_data && output_data && input_iterations != output_iterations)
  {
    Log_ErrorPrintf("Iteration mismatch for '%s' (%u vs %u)", filter_perm->GetName().c_str(), input_iterations,
                    output_iterations);
  }

  // Print some info to the C file to improve readability
  os << "\n"
     << "  /* " << input_count << " inputs, " << inputs_per_iteration << " inputs per iteration */\n"
     << "  /* " << output_count << " outputs, " << outputs_per_iteration << " outputs per iteration */\n"
     << "  /* " << num_iterations << " iterations. */\n"
     << "\n";

  // Main execution loop
  if (input_data)
    os << "  " << in_type << "* current_in_ptr = INPUT_DATA;\n";
  if (output_data)
    os << "  " << out_type << "* current_out_ptr = OUTPUT_DATA;\n";

  os << "  for (i = 0; i < " << num_iterations << "; i++) {\n";
  if (!filter_perm->IsCombinational())
  {
    os << "    filter_" << filter_perm->GetName() << "(";
    if (input_data)
    {
      os << "current_in_ptr";
      if (output_data)
        os << ", current_out_ptr";
    }
    else if (output_data)
    {
      os << "current_out_ptr";
    }
    os << ");\n";
  }
  else
  {
    os << "    ";
    if (output_data)
      os << "*current_out_ptr = ";
    os << "filter_" << filter_perm->GetName() << "(";
    if (input_data)
      os << "*current_in_ptr";
    os << ");\n";
  }

  if (input_data)
    os << "    current_in_ptr += " << inputs_per_iteration << ";\n";
  if (output_data)
    os << "    current_out_ptr += " << outputs_per_iteration << ";\n";
  os << "  }\n";

  // Compare loop
  os << "  int num_errors = 0;\n";
  if (output_data)
  {
    os << "  for (i = 0; i < " << output_count << "; i++) {\n";
    os << "    if (OUTPUT_DATA[i] != REFERENCE_OUTPUT_DATA[i]) {\n";
    os << "      printf(\"Output mismatch for filter " << filter_perm->GetName() << " at output %d: %d vs %d\\n\",\n";
    os << "             i, OUTPUT_DATA[i], REFERENCE_OUTPUT_DATA[i]); \n";
    os << "      num_errors++;\n";
    os << "    }\n";
    os << "  }\n";
  }
  os << "  return num_errors;\n";
  os << "}\n";
  os << "\n";
}

bool TestBenchGenerator::GenerateTestBenchCCode()
{
  std::string c_filename = StringFromFormat("%s/_autogen_hls/filters_tb.c", m_output_directory.c_str());
  Log_InfoPrintf("Writing test bench C code to %s...", c_filename.c_str());

  std::error_code ec;
  llvm::raw_fd_ostream os(c_filename, ec, llvm::sys::fs::F_None);
  if (ec || os.has_error())
    return false;

  // Write includes
  os << "#include <stdint.h>\n";
  os << "#include <stdio.h>\n";
  os << "#include <string.h>\n";
  os << "#include <ap_cint.h>\n";
  os << "\n";

  // Write test functions
  std::vector<std::string> function_names;
  for (const StreamGraph::FilterPermutation* filter_perm : m_stream_graph->GetFilterPermutationList())
  {
    const TestBenchGenerator::FilterDataVector* input_data = nullptr;
    const TestBenchGenerator::FilterDataVector* output_data = nullptr;
    auto iter = m_filter_input_data.find(filter_perm);
    if (iter != m_filter_input_data.end())
      input_data = &iter->second;
    iter = m_filter_output_data.find(filter_perm);
    if (iter != m_filter_output_data.end())
      output_data = &iter->second;

    if (!filter_perm->GetInputType()->isVoidTy() && !input_data)
    {
      Log_ErrorPrintf("Missing input data for '%s'", filter_perm->GetName().c_str());
      continue;
    }
    if (!filter_perm->GetOutputType()->isVoidTy() && !output_data)
    {
      Log_ErrorPrintf("Missing output data for '%s'", filter_perm->GetName().c_str());
      continue;
    }

    WriteTestBenchFor(filter_perm, input_data, output_data, os);
    function_names.push_back(filter_perm->GetName());
  }

  // Write main function
  os << "int main(int argc, char* argv[]) {\n";
  os << "  if (argc < 2) {\n";
  os << "    printf(\"No module specified.\\n\");\n";
  os << "    return 0;\n";
  os << "  }\n";
  os << "\n";
  os << "  const char* test_name = argv[1];\n";
  os << "\n";

  for (const auto& it : function_names)
  {
    os << "  if (!strcmp(test_name, \"filter_" << it << "\"))\n";
    os << "    return test_filter_" << it << "();\n";
  }

  os << "\n";
  os << "  printf(\"Unknown test name %s\\n\", test_name);\n";
  os << "  return 0;\n";
  os << "}\n";
  return true;
}

} // namespace CPUTarget


#include "parser/ast_printer.h"
#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <string>
#ifdef DEBUG
#include <iostream>
#endif

static std::string StringFromFormat(const char* fmt, va_list ap)
{
  va_list tmp_ap;
  va_copy(tmp_ap, ap);
#ifdef WIN32
  int length = _vscprintf(fmt, tmp_ap) + 1;
#else
  int length = vsnprintf(0, 0, fmt, tmp_ap) + 1;
#endif
  assert(length > 0);

  if (length == 0)
    return std::string();

  size_t memlength = static_cast<size_t>(length);
  char* temp = new char[memlength];

#ifdef WIN32
  _vsnprintf_s(temp, memlength, _TRUNCATE, fmt, ap);
#else
  vsnprintf(temp, memlength, fmt, ap);
#endif

  va_end(tmp_ap);

  std::string ret(temp);
  delete[] temp;
  return ret;
}

static std::string IndentString(int indent)
{
  std::stringstream ss;
  for (int i = 0; i < indent; i++)
    ss << "  ";
  return ss.str();
}

void ASTPrinter::BeginBlock(const char* fmt, ...)
{

  va_start(ap, fmt);
  m_ss << IndentString(m_indent) << StringFromFormat(fmt, ap) << ": {" << std::endl;
  va_end(ap);
  m_indent++;
#ifdef DEBUG
  std::cout << m_ss.str() << std::endl;
#endif
}

void ASTPrinter::Write(const char* fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  m_ss << IndentString(m_indent) << StringFromFormat(fmt, ap);
  va_end(ap);
}

void ASTPrinter::WriteLine(const char* fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  m_ss << IndentString(m_indent) << StringFromFormat(fmt, ap) << std::endl;
  va_end(ap);
}

void ASTPrinter::EndBlock()
{
  assert(m_indent > 0);
  m_indent--;
  m_ss << IndentString(m_indent) << "}" << std::endl;
}


#include "streamgraph/streamgraph_function_builder.h"
#include <cassert>
#include "frontend/expression_builder.h"
#include "frontend/statement_builder.h"
#include "frontend/wrapped_llvm_context.h"
#include "llvm/IR/Module.h"
#include "parser/ast.h"

namespace StreamGraph
{
// Dummy interface for push/pop/peek
struct StreamGraphTargetFragmentBuilder : public Frontend::FunctionBuilder::TargetFragmentBuilder
{
  StreamGraphTargetFragmentBuilder() = default;
  ~StreamGraphTargetFragmentBuilder() = default;

  llvm::Value* BuildPop(llvm::IRBuilder<>& builder) override final { return nullptr; }
  llvm::Value* BuildPeek(llvm::IRBuilder<>& builder, llvm::Value* idx_value) override final { return nullptr; }

};

StreamGraphFunctionBuilder::StreamGraphFunctionBuilder(Frontend::WrappedLLVMContext* ctx, llvm::Module* mod,
                                                       llvm::Function* func)
  : Frontend::FunctionBuilder(ctx, mod, new StreamGraphTargetFragmentBuilder(), func)
{
}

StreamGraphFunctionBuilder::~StreamGraphFunctionBuilder()
{
  delete m_target_builder;
  m_target_builder = nullptr;
}

static llvm::Value* GetRateValue(Frontend::FunctionBuilder* fb, AST::Expression* expr)
{
  // Use 0 when rate is unspecified.
  if (!expr)
    return llvm::ConstantInt::get(fb->GetContext()->GetIntType(), static_cast<uint64_t>(0));

  Frontend::ExpressionBuilder eb(fb);
  if (!expr->Accept(&eb) || !eb.IsValid())
    return nullptr;

  return eb.GetResultValue();
}

static void AddStreamParameterValues(Frontend::FunctionBuilder* fb, AST::StreamDeclaration* stream_decl,
                                     std::vector<llvm::Value*>& call_params)
{
  for (AST::ParameterDeclaration* param_decl : *stream_decl->GetParameters())
    call_params.push_back(fb->GetVariable(param_decl));
}

bool StreamGraphFunctionBuilder::Visit(AST::FilterDeclaration* node)
{
  llvm::Value* peek_rate_val = GetRateValue(this, node->GetWorkBlock()->GetPeekRateExpression());
  llvm::Value* pop_rate_val = GetRateValue(this, node->GetWorkBlock()->GetPopRateExpression());
  llvm::Value* push_rate_val = GetRateValue(this, node->GetWorkBlock()->GetPushRateExpression());
  if (!peek_rate_val || !pop_rate_val || !push_rate_val)
    return false;

  // We're a filter, simply call AddFilter
  llvm::Function* call_func = GetModule()->getFunction("StreamGraphBuilder_AddFilter");
  std::vector<llvm::Value*> call_params = {m_context->CreateHostPointerValue(node), peek_rate_val, pop_rate_val,
                                           push_rate_val};
  AddStreamParameterValues(this, node, call_params);
  GetCurrentIRBuilder().CreateCall(call_func, call_params);
  GetCurrentIRBuilder().CreateRetVoid();
  return true;
}

bool StreamGraphFunctionBuilder::Visit(AST::SplitJoinDeclaration* node)
{
  // We're a splitjoin
  llvm::Function* call_func = GetModule()->getFunction("StreamGraphBuilder_BeginSplitJoin");
  GetCurrentIRBuilder().CreateCall(call_func, {m_context->CreateHostPointerValue(node)});

  // Generate statements
  bool result = node->GetStatements()->Accept(this);

  // End of splitjoin
  call_func = GetModule()->getFunction("StreamGraphBuilder_EndSplitJoin");
  GetCurrentIRBuilder().CreateCall(call_func);
  GetCurrentIRBuilder().CreateRetVoid();
  return result;
}

bool StreamGraphFunctionBuilder::Visit(AST::PipelineDeclaration* node)
{
  // We're a pipeline
  llvm::Function* call_func = GetModule()->getFunction("StreamGraphBuilder_BeginPipeline");
  GetCurrentIRBuilder().CreateCall(call_func, {m_context->CreateHostPointerValue(node)});

  // Generate statements
  bool result = node->GetStatements()->Accept(this);

  // End of splitjoin
  call_func = GetModule()->getFunction("StreamGraphBuilder_EndPipeline");
  GetCurrentIRBuilder().CreateCall(call_func);
  GetCurrentIRBuilder().CreateRetVoid();
  return result;
}
}

bool BuildPush(llvm::IRBuilder<>& builder, llvm::Value* value) override final { return false; }


// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include <circt/Dialect/HW/HWOps.h>
#include <circt/Dialect/Kanagawa/KanagawaOps.h>
#include <circt/Dialect/Pipeline/PipelineOps.h>
#include <circt/Dialect/Seq/SeqTypes.h>
#include <circt/Support/LLVM.h>
#include <mlir/IR/Value.h>

// Redirect assertions to a function that will throw an exception on release builds
#include "ship_assert.h"

// Describes how a HW module port
// will be used in an ESI channel
enum class EsiPortSemantics
{
    NonEsi,
    Valid,
    Ready,
    ReadEnable,
    Empty,
    Payload
};

// Describes which channel in a bundle
// a particular port will be a port of
enum class EsiChannelSemantics
{
    NonEsi,

    // The channel which sends information from generated HW to the outside world
    FromGeneratedHw,

    // The channel which receives information from the outside world
    ToGeneratedHw
};

// Name of a channel within a bundle
enum class EsiChannelName
{
    Undefined,
    Arguments,
    Results
};

void LoadDialects(mlir::MLIRContext& context);
mlir::ModuleOp CreateMlirModuleAndDesign(const mlir::Location& loc, const std::string& designName);
circt::kanagawa::DesignOp GetDesignOp(mlir::ModuleOp mlirModule);

circt::hw::StructType GetInspectableStructType();
mlir::Location LocationToCirctLocation(const Location& locationIn);
mlir::Location CallStackToCirctLocation(const Program& program, const size_t callStackIndex,
                                        const mlir::Location leafLocation);
mlir::Location FileAndLineNumberToCirctLocation(const Program& program, const FileAndLineNumber& faln);
mlir::Location OperationToCirctLocation(const Operation& op, const Program& program);
mlir::Location GetUnknownLocation();
mlir::Location RegDescToLocation(const RegisterDescription& regDesc);

std::string FixupStringCirct(const std::string& src);

mlir::Value GetPathOp(circt::OpBuilder& opb, const ObjectPath& srcPath, const ObjectPath& dstPath,
                      const std::string& finalTypeName, const std::string& circtDesignName);

mlir::APInt LiteralToApInt(const Literal& l);
mlir::Value LiteralToValue(const Literal& l, circt::OpBuilder& opb, const mlir::Location& location);

mlir::StringAttr StringToStringAttr(const std::string& str);

size_t GetMlirTypeWidth(const mlir::Type& type);

size_t GetMlirValueWidth(const mlir::Value& v);

mlir::IntegerType GetIntegerType(const size_t width,
                                 mlir::IntegerType::SignednessSemantics signedness = mlir::IntegerType::Signless);

mlir::IntegerType GetI1Type();

circt::hw::ArrayType GetPackedArrayType(const mlir::Type& elementType, const size_t elementCount);

circt::hw::ArrayType GetPackedArrayTypeParameterizedSize(const mlir::Type& elementType, const std::string& paramName);

circt::seq::ClockType GetClockType();

mlir::Type ToMlirType(const Type* typeIn, bool signedness = false);

mlir::Value GetTypedZeros(circt::OpBuilder& opb, const mlir::Location& location, const mlir::Type& typeIn);

mlir::Value PopCount(circt::OpBuilder& opb, const mlir::Location location, const mlir::ValueRange values,
                     const mlir::Value outputWhenEmpty);

mlir::Value ReadContainerPort(circt::OpBuilder& opb, const mlir::Location location, const mlir::Value containerPath,
                              const mlir::StringAttr portSymbol, const mlir::Type type, const mlir::Type dstType = {});

void WriteContainerPort(circt::OpBuilder& opb, const mlir::Location location, const mlir::Value containerPath,
                        const mlir::StringAttr portSymbol, const mlir::Type type, const mlir::Value valueToWrite);

void AssertTwoState(mlir::ModuleOp& moduleOp);

void DumpMlirOperation(mlir::Operation* const op);

void AttachNameHintToValue(const mlir::Value value, const AccessedRegister accessedRegister, const Program& program);

mlir::Value MuxTree(const mlir::Value selectIndex, const std::vector<mlir::Value>& choices, circt::OpBuilder& opb,
                    const mlir::Location& location);

using SourceOperandToMlirValueCb =
    std::function<mlir::Value(const Operation& op, const size_t srcOperandIndex, const size_t desiredWidth)>;

using StoreMlirValueInDestOperandCb =
    std::function<void(const Operation& op, const size_t dstOperandIndex, const mlir::Value& value)>;

void ConvertOpToCirct(const Operation& op, circt::OpBuilder& opb, const Program& program,
                      const SourceOperandToMlirValueCb& srcToValue, const StoreMlirValueInDestOperandCb& storeDst);

mlir::Value AdjustValueWidth(const mlir::Value& srcValue, const size_t desiredWidth, const bool signExtend,
                             circt::OpBuilder& opb, const mlir::Location& location);

void SetLoweringOperations(mlir::ModuleOp& moduleOp);

std::string GetSVTypeString(mlir::Type type, const std::string& arrayDims);

mlir::StringAttr GetFullyQualifiedStringAttr(const ObjectPath& containerPath, const std::string& fieldName);

circt::hw::InnerSymAttr GetFullyQualifiedInnerSymAttr(const ObjectPath& containerPath, const std::string& fieldName);

std::string GetMemoryContainerName(const std::string& registerName);

void addPipelineSrcs(mlir::ModuleOp module);

// Compute a single value by applying a binary operator to reduce an array of values
// If the input array of values is empty, then return a default value
template <typename OpType>
mlir::Value Reduce(const llvm::SmallVector<mlir::Value>& inputs, const mlir::IntegerAttr& defaultValue,
                   circt::OpBuilder& opb, const mlir::Location& location, const bool TwoState = true)
{
    if (inputs.empty())
    {
        return opb.create<circt::hw::ConstantOp>(location, defaultValue);
    }
    else
    {
        return opb.create<OpType>(location, inputs, TwoState);
    }
}

// Used to construct a single mlir::Value out of a sparse set of slices
// unspecified bits are set to 0
class SparseConcat
{

  private:
    circt::OpBuilder& _opb;
    mlir::Location _location;
    size_t _width;

    // Offset and value
    using QueuedValue = std::pair<size_t, mlir::Value>;

    std::list<QueuedValue> _values;
};

// Groups assignments (where AssignOp) could be used
// into an AlwaysCombOp with a batch of BPAssignOp
class BatchAssignments
{
  public:
    void Append(const mlir::Location& location, const mlir::Value& dstValue, const mlir::Value& srcValue);

    void AppendVerbatimDst(const mlir::Location& location, const std::string& dstString, const mlir::Value& srcValue);

    void Flush(circt::OpBuilder& opb, const mlir::Location& location);

    bool Empty() const;

  private:
    struct AssignRecord
    {
        mlir::Location _location;
        mlir::Value _dstValue;
        mlir::Value _srcValue;
    };

    struct VerbatimAssignRecord
    {
        mlir::Location _location;
        std::string _dstString;
        mlir::Value _srcValue;
    };

    std::vector<AssignRecord> _assignments;
    std::vector<VerbatimAssignRecord> _verbatimAssignments;
};

// Each call to Accumulate writes a subset of bits in an output port
// Flush() inserts sv.assign operations to write an entire output port
class AccumulateOutputPortUpdates
{
  public:
    void Accumulate(const size_t outputRegisterIndex, const mlir::Value& valueToWrite, const size_t offset,
                    const size_t width);

    void Flush(circt::OpBuilder& opb, const mlir::Location& location, const size_t outputRegisterIndex,
               const mlir::Value portSsaValue, BatchAssignments& batchAssignments);

  private:
    struct AccumulateRecord
    {
        size_t _offset;
        size_t _width;
        mlir::Value _value;
    };

    // List of updates to apply
    using Value = std::list<AccumulateRecord>;

    std::map<size_t, Value> _updates;
};

// Used to build verbatim ops with substitutions for mlir::Value
class VerbatimWriter
{
  public:
    VerbatimWriter(circt::OpBuilder& opb, const mlir::Location& location);

    ~VerbatimWriter();

    template <typename T, typename std::enable_if<std::is_same<T, mlir::Value>::value>::type* = nullptr>
    VerbatimWriter& operator<<(const T& v)
    {
        _str << "{{" << _substitutions.size() << "}}";

        _substitutions.push_back(v);

        return *this;
    }

    template <typename T, typename std::enable_if<!std::is_same<T, mlir::Value>::value>::type* = nullptr>
    VerbatimWriter& operator<<(const T& v)
    {
        _str << v;

        return *this;
    }

    mlir::Value GetExpr(mlir::Type type);

  private:
    circt::OpBuilder& _opb;

    mlir::Location _location;

    mlir::SmallVector<mlir::Value> _substitutions;

    std::ostringstream _str;
};

// This is a RAII class for a ifdef section for CIRCT
class DisableDynamicAssertsAndTranslateOffCirct
{
  public:
    DisableDynamicAssertsAndTranslateOffCirct(circt::OpBuilder& opb, mlir::Location opLocation);

    ~DisableDynamicAssertsAndTranslateOffCirct();

  private:
    circt::OpBuilder& _opb;
    mlir::Location _location;
};

class ModuleDeclarationHelper
{
  public:
    ModuleDeclarationHelper(RedirectableSourceWriter& writer, const std::string& name, const mlir::Location& location,
                            const std::string& circtDesignName, mlir::ModuleOp* const mlirModule);

    ~ModuleDeclarationHelper();

    mlir::Block* GetBodyBlock();

    void BeginEsiBundle(const std::string& name);

    void EndEsiBundle();

    void AddPort(const std::string& name, const circt::hw::ModulePort::Direction direction, const size_t width,
                 const EsiPortSemantics portSemantics = EsiPortSemantics::NonEsi,
                 const EsiChannelSemantics channelSemantics = EsiChannelSemantics::NonEsi,
                 const EsiChannelName channelName = EsiChannelName::Undefined, const std::string fieldName = "");

    void AddPort(const std::string& name, const circt::hw::ModulePort::Direction direction, const size_t outerWidth,
                 const size_t innerWidth);

    void AddPortOptionalArray(const std::string& name, const circt::hw::ModulePort::Direction direction,
                              const size_t outerWidth, const size_t innerWidth);

    void AddPort(const std::string& name, const circt::hw::ModulePort::Direction direction, const mlir::Type type,
                 const Type* origType = nullptr, const EsiPortSemantics portSemantics = EsiPortSemantics::NonEsi,
                 const EsiChannelSemantics channelSemantics = EsiChannelSemantics::NonEsi,
                 const EsiChannelName channelName = EsiChannelName::Undefined, const std::string fieldName = "");

    void FinishPorts();

    std::string AssignPort(const std::string& portName);

    std::string AssignPortOptional(const std::string& portName);

    void AddTypedefs(const std::string& typeScopeName);

    mlir::Type GetInspectableTypeAlias();

    mlir::ModuleOp MlirModule();

    circt::kanagawa::ContainerOp Container();

    circt::OpBuilder& OpBuilder();

    void Finish();

    void EmitEsiWrapper(const std::string& circtDesignName);

    void FlushVerbatimStrings();

    using VerbatimCallback = std::function<void(VerbatimWriter& writer)>;

    void AddVerbatimOp(const mlir::Location& location, const VerbatimCallback& callback);

    std::string Name() const;

    mlir::Value GetPort(circt::OpBuilder& opb, const ObjectPath& srcPath, const ObjectPath& dstPath,
                        const mlir::StringAttr portSymbol, const circt::kanagawa::Direction portDirection,
                        const mlir::Type portType, const std::string& finalTypeName,
                        const std::string& circtDesignName);

    void WritePort(circt::OpBuilder& opb, const ObjectPath& srcPath, const ObjectPath& dstPath,
                   const mlir::StringAttr portSymbol, const std::string& finalTypeName,
                   const std::string& circtDesignName, mlir::Value value);

    mlir::Value ReadPort(circt::OpBuilder& opb, const ObjectPath& srcPath, const ObjectPath& dstPath,
                         const mlir::StringAttr portSymbol, const std::string& finalTypeName,
                         const std::string& circtDesignName, const mlir::Type type);

  private:
    mlir::Type GetTypeAlias(const std::string& name, const mlir::Type& referencedType);

  private:
    void AssertStructsMatch(const mlir::Type& circtTypeAlias, const std::string& otherStructName);

    std::string _name;
    mlir::Location _location;
    std::string _circtDesignName;
    circt::OpBuilder _opb;
    RedirectableSourceWriter& _originalWriter;

    struct PortInfo
    {
        circt::hw::PortInfo _hwPortInfo;
        EsiPortSemantics _esiPortSemantics;
        EsiChannelSemantics _esiChannelSemantics;
        EsiChannelName _esiChannelName;
        std::string _fieldName;
        std::optional<std::string> _bundleName;
        const Type* _origType;
    };

    llvm::SmallVector<PortInfo> _ports;
    mlir::ModuleOp _mlirModule;
    circt::kanagawa::ContainerOp _container;
    StringSourceWriter _verbatimBuffer;

    std::optional<std::string> _bundleName;
    size_t _bundleStartPortIndex;

    // Maps bundle name to range of relevant ports
    std::map<std::string, std::pair<size_t, size_t>> _bundleNameToPortRange;

    std::map<std::string, size_t> _portNameToIndex;
    std::map<std::string, mlir::Value> _outputValues;

    // Maps port name to mlir operation which represents the port
    std::map<std::string, mlir::Value> _inputPortOps;
    std::map<std::string, mlir::Value> _outputPortOps;

    circt::hw::TypeScopeOp _typeScopeOp;

    bool _finished;

    bool _exportVerilog;
};

// RAII class for {Begin, End}EsiBundle
class PushPopEsiBundle
{
  public:
    PushPopEsiBundle(ModuleDeclarationHelper& helper, const std::optional<std::string>& name) : _helper(helper)
    {
        if (name)
        {
            _pushedName = true;

            helper.BeginEsiBundle(*name);
        }
        else
        {
            _pushedName = false;
        }
    }

    ~PushPopEsiBundle()
    {
        if (_pushedName)
        {
            _helper.EndEsiBundle();
        }
    }

  private:
    ModuleDeclarationHelper& _helper;
    bool _pushedName;
};

// Helper to create a triggered region
// and feed signals into it
class TriggeredOpHelper
{
  public:
    TriggeredOpHelper(circt::OpBuilder& opb, circt::pipeline::ScheduledPipelineOp& scheduledPipelineOp,
                      const SourceOperandToMlirValueCb& sourceOperandToMlirValue, const Program& program,
                      const mlir::Value enableSignal);

    ~TriggeredOpHelper();

    void AddOp(const Operation& op);

    void DoneAddingOps(const circt::hw::EventControl triggerCondition, const mlir::Value clock);

    mlir::Value GetEnableSignal();

    mlir::Value GetSourceOperand(const Operation& op, const size_t operandIndex);

    mlir::Value GetSourceOperand(const Operation& op, const size_t operandIndex, const size_t desiredWidth);

  private:
    size_t AddSignal(const mlir::Value& value);

    circt::OpBuilder& _opb;
    circt::pipeline::ScheduledPipelineOp& _scheduledPipelineOp;
    SourceOperandToMlirValueCb _sourceOperandToMlirValue;
    const Program& _program;
    mlir::SmallVector<mlir::Value> _signals;
    circt::hw::TriggeredOp _triggeredOp;
    circt::OpBuilder::InsertPoint _insertionPoint;
    bool _doneAddingOps;
    std::vector<const Operation*> _ops;
    std::map<const Operation*, size_t> _opToStartIndex;
};

// Top-level container of MLIR
class MlirModule
{
  public:
    MlirModule(const std::string& circtDesignName);

    std::string Generate();

    mlir::ModuleOp& Module();

    void VerifyRoundTrip();

  private:
    mlir::ModuleOp _mlirModule;
};

// Wrapper around LatencyOp that handles a latency of 0
class LatencyOpHelper
{
  public:
    LatencyOpHelper(const mlir::Location location, circt::OpBuilder& opb, const llvm::SmallVector<mlir::Value>& values,
                    const size_t latency);

    mlir::Value GetResult(const size_t index);

  private:
    llvm::SmallVector<mlir::Value> _results;
};

  public:
    SparseConcat(circt::OpBuilder& opb, const mlir::Location& location, const size_t width);
    void Insert(const size_t offset, const mlir::Value value);