FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

MLIRの勉強 (4. 自作言語のMLIR変換とダンプ)

MLIRについて勉強している。

独自言語を作成し、その中間表現をMLIRを使って表現してみることに挑戦する。

ここで想定する流れは以下の通りだ。

  1. 独自言語(MYSV : System Verilogオリジナル改造バージョン) を読み込んで、独自形式でASTを作成する
  2. MYSVのASTをMLIRのツリーに変換する
  3. MLIRのツリーをダンプする

次は、最適化の実装をするためにいくつか言語拡張をしていく。

Toy言語の場合は、Transpose(Transpose(A))の場合にはTransposeしない、という最適化を例に紹介がされている。

https://mlir.llvm.org/docs/Tutorials/Toy/Ch-3/

ただし自分の作っているMYSVはそういう構文を持っていないため、違う最適化の余地を実験してみたい。例えばこんな感じだ。

assign A = 0;
assign B = A;

この場合、最適化を行って、

assign B = 0;

という結論を導き出してくれれば嬉しい。こういうのはできないものだろうか。

このためには、まず自分のMYSVを拡張する必要がある。拡張するポイントは以下のようになるだろう。

  • assign文のオペランドが変数を受け取るようにする(現在は定数のみ)

このためにASTの拡張を行おう。まず、式Exprが定数も変数も受け取れるようにする。

+++ b/mlir/examples/mysv/AST.cpp
@@ -26,6 +26,7 @@ class ASTDumper {
   void dump(AssignExprAST *assignExpr);
   void dump(ExprAST *expr);
   void dump(NumberExprAST *num);
+  void dump(VarExprAST *name);

Parserは、Assign文の=の次が何であるかに従ってParseの切り替えを行う。

--- a/mlir/examples/mysv/include/Parser.h
+++ b/mlir/examples/mysv/include/Parser.h
@@ -66,6 +66,40 @@ private:
     return result;
   }
 
+  /// identifierexpr
+  ///   ::= identifier
+  std::unique_ptr<ExprAST> parseIdentifierExpr() {
+    std::string name(lexer.getId());
+
+    auto loc = lexer.getLastLocation();
+    lexer.getNextToken(); // eat identifier.
+
+    return std::make_unique<VarExprAST>(std::move(loc), name);
+  }
+
+  /// primary
+  ///   ::= identifierexpr
+  ///   ::= numberexpr
+  std::unique_ptr<ExprAST> parsePrimary() {
+    switch (lexer.getCurToken()) {
+      default:
+        llvm::errs() << "unknown token '" << lexer.getCurToken()
+                     << "' when expecting an expression\n";
+        return nullptr;
+      case tok_identifier:
+        return parseIdentifierExpr();
+      case tok_number:
+        return parseNumberExpr();
+    }
+  }
+
+  std::unique_ptr<ExprAST> parseExpr() {
+    auto lhs = parsePrimary();
+    if (!lhs)
+      return nullptr;
+    return lhs;
+  }
+
   /// Parse a variable declaration, it starts with a `var` keyword followed by
   /// and identifier and an optional type (shape specification) before the
   /// initializer.
@@ -84,7 +118,7 @@ private:
 
     lexer.consume(Token('='));
 
-    auto expr = parseNumberExpr();
+    auto expr = parseExpr();
 
     lexer.consume(Token(';'));
     return std::make_unique<AssignExprAST>(std::move(loc), std::move(id), std::move(expr));

VarExprASTクラスを新たに作成する。ここではclassofメソッドを追加することでLLVMTypeSwitchを使えることができるようになる。

+/// Expression class for variable literals.
+class VarExprAST : public ExprAST {
+  std::string name;
+
+ public:
+  VarExprAST(Location loc, llvm::StringRef name)
+      : ExprAST(Expr_Var, std::move(loc)), name(name) {}
+
+  llvm::StringRef getName() { return name; }
+
+  /// LLVM style RTTI
+  static bool classof(const ExprAST *c) { return c->getKind() == Expr_Var; }
+};
+

ExprAST用のmlirGenを新たに用意する。ASTの形式に従ってVarExprASTをベースにMLIRを生成するか、NumberExprASTをベースにMLIRを生成するか切り分ける。

+  /// Dispatch codegen for the right expression subclass using RTTI.
+  mlir::Value mlirGen(ExprAST &expr) {
+    switch (expr.getKind()) {
+      case mysv::ExprAST::Expr_Var:
+        return mlirGen(cast<VarExprAST>(expr));
+      case mysv::ExprAST::Expr_Num:
+        return mlirGen(cast<NumberExprAST>(expr));
+      default:
+        emitError(loc(expr.loc()))
+            << "MLIR codegen encountered an unhandled expr kind '"
+            << Twine(expr.getKind()) << "'";
+        return nullptr;
+    }
+  }
+
 
   /// Emit a literal/constant array. It will be emitted as a flattened array of
   /// data in an Attribute attached to a `mysv.constant` operation.
@@ -121,6 +136,19 @@ class MLIRGenImpl {
     return builder.create<ConstantOp>(loc(lit.loc()), elementType, lit.getValue());
   }
 
+
+  /// This is a reference to a variable in an expression. The variable is
+  /// expected to have been declared and so should have a value in the symbol
+  /// table, otherwise emit an error and return nullptr.
+  mlir::Value mlirGen(VarExprAST &expr) {
+    if (auto variable = symbolTable.lookup(expr.getName()))
+      return variable;
+
+    emitError(loc(expr.loc()), "error: unknown variable '")
+        << expr.getName() << "'";
+    return nullptr;
+  }
+

まずはASTでダンプしてみる。

 $ ./bin/mysv --emit=ast ../mlir/examples/mysv/test/assign_var.sv
  Module:
    assign A @../mlir/examples/mysv/test/assign_var.sv:1:1
      0 @../mlir/examples/mysv/test/assign_var.sv:1:12
    assign Hoge @../mlir/examples/mysv/test/assign_var.sv:2:1
      A @../mlir/examples/mysv/test/assign_var.sv:2:15

つぎはMLIRでダンプしてみる。

./bin/mysv --emit=mlir ../mlir/examples/mysv/test/assign_var.sv
loc("../mlir/examples/mysv/test/assign_var.sv":2:15): error: error: unknown variable 'A'
module {
  %0 = "mysv.constant"() {value = 3 : si64} : () -> i64
}

あれ?1文しか出力されないし、変数Aが無かったことになっている。これはなんだろう。

MLIRの勉強 (3. 自作言語のMLIR変換とダンプ)

MLIRについて勉強している。

独自言語を作成し、その中間表現をMLIRを使って表現してみることに挑戦する。

ここで想定する流れは以下の通りだ。

  1. 独自言語(MYSV : System Verilogオリジナル改造バージョン) を読み込んで、独自形式でASTを作成する
  2. MYSVのASTをMLIRのツリーに変換する
  3. MLIRのツリーをダンプする

次はMLIRに変換してダンプできるようになろう。

MLIRの変換およびMLIRのダンプには、MLIRGen.cppおよびMLIRGen.hというファイルを作成する。

MLIRGen.hのファイル自体は一番トップレベルのmlirGen(mlir::MLIRContext, ModuleAST)を宣言する。最もトップレベルでの変換を行うための関数だ。ここから階層的にMLIRに変換する関数を実装していく。

namespace mysv {
   class ModuleAST;

   /// Emit IR for the given Mysv moduleAST, returns a newly created MLIR module
   /// or nullptr on failure.
   mlir::OwningOpRef<mlir::ModuleOp> mlirGen(mlir::MLIRContext &context,
                                             ModuleAST &moduleAST);
 } // namespace mysv

ModuleASTの変換から始めていこう。

// The public API for codegen.
 mlir::OwningOpRef<mlir::ModuleOp> mlirGen(mlir::MLIRContext &context,
                                           ModuleAST &moduleAST) {
   return MLIRGenImpl(context).mlirGen(moduleAST);
 }

ModuleASTは、Assign文の集合で構成されるので、内部のAssignExprASTを繰り返しMLIRに変換する処理で構成されている。

   /// Public API: convert the AST for a Mysv module (source file) to an MLIR
   /// Module operation.
   mlir::ModuleOp mlirGen(ModuleAST &moduleAST) {
     // Create a scope in the symbol table to hold variable declarations.
     ScopedHashTableScope<llvm::StringRef, mlir::Value> varScope(symbolTable);

     // We create an empty MLIR module and codegen functions one at a time and
     // add them to the module.
     theModule = mlir::ModuleOp::create(builder.getUnknownLoc());

     for (AssignExprAST &a : moduleAST)
       mlirGen(a);

このsymbolTableというのはクラス内で宣言されているメンバ変数で、変数の名前とMLIRのmlir::Valueをマップするためのテーブルらしい。

   /// The symbol table maps a variable name to a value in the current scope.
   /// Entering a function creates a new scope, and the function arguments are
   /// added to the mapping. When the processing of a function is terminated, the
   /// scope is destroyed and the mappings created in this scope are dropped.
   llvm::ScopedHashTable<StringRef, mlir::Value> symbolTable;

AssignExprASTの変換により、変換用の固定値(数値)のMLIRツリーを作り上げていく。

   /// Emit a new function and add it to the MLIR module.
   mlir::Value mlirGen(AssignExprAST &assignAST) {
     // Create a scope in the symbol table to hold variable declarations.
     ScopedHashTableScope<llvm::StringRef, mlir::Value> varScope(symbolTable);

     // Create an MLIR function for the given prototype.
     builder.setInsertionPointToEnd(theModule.getBody());

     auto *init = assignAST.getInitVal();
     if (!init) {
       emitError(loc(assignAST.loc()),
                 "missing initializer in variable declaration");
       return nullptr;
     }

     auto assign = mlirGen(*init);
     if (!assign)
       return nullptr;

これにより、とりあえずMLIRを生成してダンプすることができるようになった。

$ ./bin/mysv --emit=mlir ../mlir/examples/mysv/test/assign.sv

module {
  %0 = "mysv.constant"() {value = 0 : si64} : () -> i64
  %1 = "mysv.constant"() {value = 2 : si64} : () -> i64
}

MLIRの勉強 (2. チュートリアルをベースとした自作言語作成)

MLIRについて勉強している。

独自言語を作成し、その中間表現をMLIRを使って表現してみることに挑戦する。

ここで想定する流れは以下の通りだ。

  1. 独自言語(MYSV : System Verilogオリジナル改造バージョン) を読み込んで、独自形式でASTを作成する
  2. MYSVのASTをMLIRのツリーに変換する
  3. MLIRのツリーをダンプする

という流れでMLIRを体験したいと思う。対象とするサンプルプログラムは、とりあえず以下の通り。

assign A = 0;
assign Hoge = 2;

Toyのサンプルプログラムを見ながら構築する。まずはこれからASTを作る。

int dumpAST() {
   auto moduleAST = parseInputFile(inputFilename);
   if (!moduleAST)
     return 1;

   dump(*moduleAST);
   return 0;
 }

parseInputFile()は、入力ファイルを読み込んで、それからASTを作成する。最終的にASTはmoduleASTとして返される。

この時点ではまだMLIRは登場していなくて、単純に独自形式でASTが作られただけである。

  • mysv.cpp
/// Returns a MYSV AST resulting from parsing the file or a nullptr on error.                                                                                                                                                                                                                                                                                                                                                                                             
 std::unique_ptr<mysv::ModuleAST> parseInputFile(llvm::StringRef filename) {
   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> fileOrErr =
       llvm::MemoryBuffer::getFileOrSTDIN(filename);
   if (std::error_code ec = fileOrErr.getError()) {
     llvm::errs() << "Could not open input file: " << ec.message() << "\n";
     return nullptr;
   }
   auto buffer = fileOrErr.get()->getBuffer();
   LexerBuffer lexer(buffer.begin(), buffer.end(), std::string(filename));
   Parser parser(lexer);
   return parser.parseModule();
 }

ASTのフォーマットは以下のようになる。これは独自に作成した。

  • AST.h
/// Expression class for assignment.                                                                                                                                                                                                                                                                                                                                                                                                                                      
 class AssignExprAST : public ExprAST {
   std::string name;
   std::unique_ptr<NumberExprAST> initVal;

  public:
   AssignExprAST(Location loc, llvm::StringRef name, std::unique_ptr<NumberExprAST> initVal)
       : ExprAST(Expr_Assign, std::move(loc)),
         name(name),
         initVal(std::move(initVal)) {}

   llvm::StringRef getName() { return name; }
   NumberExprAST *getInitVal() { return initVal.get(); }

   /// LLVM style RTTI                                                                                                                                                                                                                                                                                                                                                                                                                                                     
   static bool classof(const ExprAST *c) { return c->getKind() == Expr_Assign; }
 };

 /// This class represents a list of functions to be processed together                                                                                                                                                                                                                                                                                                                                                                                                    
 class ModuleAST {
   std::vector<AssignExprAST> assigns;

  public:
   ModuleAST(std::vector<AssignExprAST> assigns)
       : assigns(std::move(assigns)) {}

   auto begin() { return assigns.begin(); }
   auto end() { return assigns.end(); }
 };

ASTを作成するに当たり、LexerとParserが必要なのだが、Toyのサンプルプログラムを見る限りは独自に作成している。

  • Lexer.h
 /// The Lexer is an abstract base class providing all the facilities that the                                                                                                                                                                                                                                                                                                                                                                                             
 /// Parser expects. It goes through the stream one token at a time and keeps                                                                                                                                                                                                                                                                                                                                                                                              
 /// track of the location in the file for debugging purposes.                                                                                                                                                                                                                                                                                                                                                                                                             
 /// It relies on a subclass to provide a `readNextLine()` method. The subclass                                                                                                                                                                                                                                                                                                                                                                                            
 /// can proceed by reading the next line from the standard input or from a                                                                                                                                                                                                                                                                                                                                                                                                
 /// memory mapped file.                                                                                                                                                                                                                                                                                                                                                                                                                                                   
 class Lexer {
 public:
   /// Create a lexer for the given filename. The filename is kept only for                                                                                                                                                                                                                                                                                                                                                                                                
   /// debugging purposes (attaching a location to a Token).                                                                                                                                                                                                                                                                                                                                                                                                               
   Lexer(std::string filename)
       : lastLocation(
             {std::make_shared<std::string>(std::move(filename)), 0, 0}) {}
   virtual ~Lexer() = default;

   /// Look at the current token in the stream.                                                                                                                                                                                                                                                                                                                                                                                                                            
   Token getCurToken() { return curTok; }

   /// Move to the next token in the stream and return it.                                                                                                                                                                                                                                                                                                                                                                                                                 
   Token getNextToken() { return curTok = getTok(); }

   /// Move to the next token in the stream, asserting on the current token                                                                                                                                                                                                                                                                                                                                                                                                
   /// matching the expectation.                                                                                                                                                                                                                                                                                                                                                                                                                                           
   void consume(Token tok) {
     assert(tok == curTok && "consume Token mismatch expectation");
     getNextToken();
   }

   /// Return the current identifier (prereq: getCurToken() == tok_identifier)                                                                                                                                                                                                                                                                                                                                                                                             
   llvm::StringRef getId() {
     assert(curTok == tok_identifier);
  • Parser.h
/// This is a simple recursive parser for the MYSV language. It produces a well                                                                                                                                                                                                                                                                                                                                                                                           
/// formed AST from a stream of Token supplied by the Lexer. No semantic checks                                                                                                                                                                                                                                                                                                                                                                                           
/// or symbol resolution is performed. For example, variables are referenced by                                                                                                                                                                                                                                                                                                                                                                                           
/// string and the code could reference an undeclared variable and the parsing                                                                                                                                                                                                                                                                                                                                                                                            
/// succeeds.                                                                                                                                                                                                                                                                                                                                                                                                                                                             
class Parser {
public:
  /// Create a Parser for the supplied lexer.                                                                                                                                                                                                                                                                                                                                                                                                                             
  Parser(Lexer &lexer) : lexer(lexer) {}

  /// Parse a full Module. A module is a list of function definitions.                                                                                                                                                                                                                                                                                                                                                                                                    
  std::unique_ptr<ModuleAST> parseModule() {
    lexer.getNextToken();   // prime the lexer                                                                                                                                                                                                                                                                                                                                                                                                                            
     // Parse functions one at a time and accumulate in this vector.                                                                                                                                                                                                                                                                                                                                                                                                       
     std::vector<AssignExprAST> functions;
     while (auto f = parseAssign()) {
       functions.push_back(std::move(*f));
       if (lexer.getCurToken() == tok_eof)
         break;
     }

     // If we didn't reach EOF, there was an error during parsing                                                                                                                                                                                                                                                                                                                                                                                                          
     if (lexer.getCurToken() != tok_eof)
       return parseError<ModuleAST>("nothing", "at end of module");

     return std::make_unique<ModuleAST>(std::move(functions));
   }

 private:
   Lexer &lexer;

   /// Parse a literal number.                                                                                                                                                                                                                                                                                                                                                                                                                                             
   /// numberexpr ::= number                                                                                                                                                                                                                                                                                                                                                                                                                                               
   std::unique_ptr<NumberExprAST> parseNumberExpr() {
     auto loc = lexer.getLastLocation();
     auto result = std::make_unique<NumberExprAST>(std::move(loc), lexer.getValue());
     lexer.consume(tok_number);
     return result;
   }

   /// Parse a variable declaration, it starts with a `var` keyword followed by                                                                                                                                                                                                                                                                                                                                                                                            
   /// and identifier and an optional type (shape specification) before the                                                                                                                                                                                                                                                                                                                                                                                                
   /// initializer.                                                                                                                                                                                                                                                                                                                                                                                                                                                        
   /// decl ::= assign identifier = expr                                                                                                                                                                                                                                                                                                                                                                                                                                   
   std::unique_ptr<AssignExprAST> parseAssign() {
     if (lexer.getCurToken() != tok_assign) {
       return parseError<AssignExprAST>("assign", "to begin assign");
     }

とりあえずココまでで、独自形式のASTは生成できるようになった。

$ ./bin/mysv --emit=ast ../mlir/examples/mysv/test/assign.sv

  Module:
    assign A @../mlir/examples/mysv/test/assign.sv:1:1
      0 @../mlir/examples/mysv/test/assign.sv:1:12
    assign Hoge @../mlir/examples/mysv/test/assign.sv:2:1
      2 @../mlir/examples/mysv/test/assign.sv:2:15

次はMLIRに変換してダンプできるようになろう。

2022年のRISC-V新仕様まとめ

RISC-V Internationalがアナウンスした2022年に仕様策定に入った新仕様、いくつか情報が出てきている。

riscv.org

  • E-Trace for RISC-V: CPUのためのデバッグ用分岐トレース。なんかこれの大昔のバージョンを読んだことがあったな。

github.com

  • RISC-V specification for SBI :

github.com

github.com

  • RISC-V Zmmul Multiply Only : 乗算器のみの命令仕様。

MLIRについての勉強 (チュートリアルをベースとした自作言語作成2)

MLIRはMulti Level Intermediate Representationの略で、LLVM IRよりも更にメタ化したような中間言語だ。LLVM IRでは吸収しきれないような各言語で定義される中間表現を、許容するために開発された。

これを使ってみたいので、まずはToyに似たような言語を使って自分でMLIRを生成できるようになりたい。いくつか試行をしている。

MLIRのブロックを作らなければならないのだけれども、これがどうにもうまく行かない。ビルド時にエラーで落ちてしまう。 (手元にx86マシンが無くて試すことができていない)

Undefined symbols for architecture arm64:
  "mlir::mysv::ConstantOp::verifyInvariantsImpl()", referenced from:
      mlir::OpTrait::OpInvariants<mlir::mysv::ConstantOp>::verifyTrait(mlir::Operation*) in Dialect.cpp.o
  "mlir::mysv::ConstantOp::build(mlir::OpBuilder&, mlir::OperationState&, mlir::Type, long long)", referenced from:
      mlir::mysv::ConstantOp mlir::OpBuilder::create<mlir::mysv::ConstantOp, mlir::Type&, unsigned long long>(mlir::Location, mlir::Type&, unsigned long long&&) in MLIRGen.cpp.o
  "mlir::mysv::MYSVDialect::materializeConstant(mlir::OpBuilder&, mlir::Attribute, mlir::Type, mlir::Location)", referenced from:
      vtable for mlir::mysv::MYSVDialect in Dialect.cpp.o
  "mlir::detail::TypeIDResolver<mlir::mysv::ConstantOp, void>::id", referenced from:
      mlir::detail::TypeIDResolver<mlir::mysv::ConstantOp, void>::resolveTypeID() in MLIRGen.cpp.o
      mlir::detail::TypeIDResolver<mlir::mysv::ConstantOp, void>::resolveTypeID() in Dialect.cpp.o
ld: symbol(s) not found for architecture arm64

うーん、MLIRのリファレンスマニュアルを見てもよくわからない。

Alpha EV8の分岐予測機に関する論文を読む(1. 概要)

Alpha EV8の分岐予測機に関する論文を読み始めた。

ieeexplore.ieee.org

Alpha EV8の分岐予測のポイント:

  • グローバルヒストリ分岐予測の機構を用いる。
  • ハイブリッドスキューブランチ予測器2Bc-gskewを用いて予測を行う。
  • 圧縮された分岐履歴とパス履歴を組み合わせた分岐予測器のベクトルインデックスを再定義する。
  • 異なる予測と、ヒステリシスなテーブルサイズを用いる
    • 分岐テーブルとヒステリシステーブルは異なるパイプラインステージでアクセスされ、したがって物理的に異なるテーブルとして実装される。
  • 可変長履歴を用いる。
    • EV8内の4つの論理的なテーブルを用いる。
  • 8つの命令と後続の命令、16個の分岐命令がシングルポートメモリを使って、コンフリクトが発生せずに分岐予測できることを保証する。

以降の構成:

  • Section2. Alpha EV8の命令フェッチパイプラインの紹介。
  • Section3. グローバル履歴分岐予測機がローカル分岐予測機よりも優れている理由
  • Section4. 2Bc-gskew分岐予測器の紹介。
  • Section5. 分岐予測機を参照する際のインデックスに使用する情報の紹介。
  • Section6. サイクルあたりのコンフリクトが発生しないバンクインタリーブされた分岐予測器
  • Section7. 分岐予測テーブルのためのインデックス関数のハードウェア的な制限
  • Section8. EV8分岐予測器のステップバイステップの性能評価

MLIRについての勉強 (チュートリアルをベースとした自作言語作成)

MLIRはMulti Level Intermediate Representationの略で、LLVM IRよりも更にメタ化したような中間言語だ。LLVM IRでは吸収しきれないような各言語で定義される中間表現を、許容するために開発された。

これを使ってみたいので、まずはToyに似たような言語を使って自分でMLIRを生成できるようになりたい。いくつか試行をしている。

MYSVというVerilogに等価な言語を作って、まずはそれをParseしてDumpできるようにする。これまMLIRを生成する前段階だ。

 assign A = 0;
 assign Hoge = 2;

前回まででとりあえず簡単なVerilogをParseできるようになったので、今度はこれをMLIRにビルドしていく、というわけだ。

まずはMLIRを出力できるモードを作る。

  switch (emitAction) {
  case Action::DumpAST:
    dumpAST();
    return 0;
  case Action::DumpMLIR:
    dumpMLIR(*moduleAST);
    return 0;
  default:
    llvm::errs() << "No action specified (parsing only?), use -emit=<action>\n";
  }

基本的な考え方としては、前回作った自作のASTツリーをMLIRにコンバートしていく。MLIRGen.cppを実装していく。

moduleASTの中に入っているAssignExprASTに分解していく。mlirGen(AssignExprAST)に分解していく。

   /// Public API: convert the AST for a Mysv module (source file) to an MLIR                                                                                                                                                        
   /// Module operation.                                                                                                                                                                                                             
   mlir::ModuleOp mlirGen(ModuleAST &moduleAST) {
     // We create an empty MLIR module and codegen functions one at a time and                                                                                                                                                       
     // add them to the module.                                                                                                                                                                                                      
     theModule = mlir::ModuleOp::create(builder.getUnknownLoc());

     for (AssignExprAST &a : moduleAST)
       mlirGen(a);
     // ...
     return theModule;
   }

AssignExprASTに対するMLIRの構築がこのようになる。引き続きMLIRブロックを作っていく。

   /// Emit a new function and add it to the MLIR module.                                                                                                                                                                            
   mlir::Value mlirGen(AssignExprAST &assignAST) {
     auto *init = vardecl.getInitVal();
     if (!init) {
       emitError(loc(vardecl.loc()),
                 "missing initializer in variable declaration");
       return nullptr;
     }

     mlir::Value value = mlirGen(*init);
     if (!value)
       return nullptr;

     // Register the value in the symbol table.                                                                                                                                                                                      
     if (failed(declare(vardecl.getName(), value)))
       return nullptr;

     return value;
   }