| #pragma once | |
| #include <torch/csrc/jit/ir.h> | |
| #include <torch/csrc/jit/irparser.h> | |
| #include "test/cpp/jit/test_base.h" | |
| #include <sstream> | |
| #include <string> | |
| namespace torch { | |
| namespace jit { | |
| /** \brief Parse IR from \p S, print the parsed graph and verify that the output | |
| * string matches the original string. | |
| * | |
| * The function is sensitive to value naming and whitespace, so it should be | |
| * used with care. Nevertheless, it helps to keep tests more compact. | |
| */ | |
| static void checkRoundtrip(const std::string& s) { | |
| auto graph = std::make_shared<Graph>(); | |
| script::parseIR(s, &*graph); | |
| std::ostringstream ss; | |
| ss << *graph; | |
| std::string parsed = ss.str(); | |
| // Skip whitespace in the beginning of the input string. | |
| int i = 0; | |
| for (char c : s) { | |
| if (!isspace(c)) { | |
| break; | |
| } | |
| i++; | |
| } | |
| std::string original = s.substr(i, s.size()); | |
| if (original != parsed) { | |
| std::cerr << "Input:" << std::endl << original << std::endl; | |
| std::cerr << "Parsed:" << std::endl << parsed << std::endl; | |
| } | |
| AT_ASSERT(original == parsed); | |
| } | |
| void testIRParser(std::ostream& out = std::cout) { | |
| { | |
| auto graph = std::make_shared<Graph>(); | |
| script::parseIR( | |
| R"IR( | |
| graph(%0 : Tensor, %1 : Tensor): | |
| %2 : Tensor = foo::add(%0, %1) | |
| %res, %3 = foo::mul(%0, %2) | |
| %x, %y = foo::combine(%res, %2, %3) | |
| return (%x, %y, %res))IR", | |
| &*graph); | |
| AT_ASSERT(graph->inputs().size() == 2); | |
| AT_ASSERT(graph->outputs().size() == 3); | |
| Value* x = graph->outputs()[0]; | |
| Value* y = graph->outputs()[1]; | |
| Value* res = graph->outputs()[2]; | |
| Value* t0 = graph->inputs()[0]; | |
| Value* t1 = graph->inputs()[1]; | |
| AT_ASSERT(x->node() == y->node()); | |
| Node* comb = x->node(); | |
| Value* t2 = comb->inputs()[1]; | |
| Value* t3 = comb->inputs()[2]; | |
| AT_ASSERT(comb->kind().toQualString() == std::string("foo::combine")); | |
| AT_ASSERT(comb->outputs() == std::vector<Value*>({x, y})); | |
| AT_ASSERT(comb->inputs() == std::vector<Value*>({res, t2, t3})); | |
| Node* mul = res->node(); | |
| AT_ASSERT(mul->kind().toQualString() == std::string("foo::mul")); | |
| AT_ASSERT(mul->inputs() == std::vector<Value*>({t0, t2})); | |
| AT_ASSERT(mul->outputs() == std::vector<Value*>({res, t3})); | |
| Node* add = t2->node(); | |
| AT_ASSERT(add->kind().toQualString() == std::string("foo::add")); | |
| AT_ASSERT(add->inputs() == std::vector<Value*>({t0, t1})); | |
| AT_ASSERT(add->outputs() == std::vector<Value*>({t2})); | |
| } | |
| { | |
| checkRoundtrip(R"IR( | |
| graph(): | |
| %0 : Tensor = a::a() | |
| block0(): | |
| %1 : Tensor = b::b() | |
| block0(): | |
| %2 : Tensor = c::c() | |
| -> () | |
| -> () | |
| %3 : Tensor = d::d() | |
| return (%3) | |
| )IR"); | |
| } | |
| { | |
| checkRoundtrip(R"IR( | |
| graph(%0 : Tensor, | |
| %1 : Tensor, | |
| %2 : Tensor): | |
| %3 : int = prim::Constant[value=1]() | |
| %4 : Tensor = aten::add(%0, %1, %3) | |
| %5 : Tensor = prim::If(%2) | |
| block0(): | |
| %6 : int = prim::Constant[value=1]() | |
| %7 : Tensor = aten::add(%1, %3, %6) | |
| %8 : int = prim::Constant[value=1]() | |
| %9 : Tensor = aten::add(%7, %3, %8) | |
| -> (%9) | |
| %10 : int = prim::Constant[value=1]() | |
| %11 : Tensor = aten::add(%5, %3, %10) | |
| return (%11) | |
| )IR"); | |
| } | |
| { | |
| auto graph = std::make_shared<Graph>(); | |
| script::parseIR( | |
| R"IR( | |
| graph(%a): | |
| return (%a))IR", | |
| &*graph); | |
| graph->inputs()[0]->type()->expect<TensorType>(); | |
| } | |
| { | |
| // Check that parser corectly handles values reusing the same name. | |
| auto graph = std::make_shared<Graph>(); | |
| script::parseIR( | |
| R"IR( | |
| graph(%x): | |
| %x = a::a(%x) | |
| %x = b::b(%x) | |
| return (%x))IR", | |
| &*graph); | |
| Value* x0 = graph->inputs()[0]; | |
| Value* x2 = graph->outputs()[0]; | |
| Node* b = x2->node(); | |
| Value* x1 = b->inputs()[0]; | |
| Node* a = x1->node(); | |
| AT_ASSERT(a->inputs() == std::vector<Value*>({x0})); | |
| AT_ASSERT(a->outputs() == std::vector<Value*>({x1})); | |
| AT_ASSERT(b->inputs() == std::vector<Value*>({x1})); | |
| AT_ASSERT(b->outputs() == std::vector<Value*>({x2})); | |
| } | |
| { | |
| // Check that parser handles attributes and types. | |
| checkRoundtrip( | |
| R"IR( | |
| graph(%0 : Tensor, | |
| %1 : Tensor, | |
| %2 : Tensor): | |
| %3 : int, %4 : Tensor = qqq::qqq[i_asdf=2, f_asdf=3.14, s_asdf="hello", ss_asdf=["hello world", "bye bye"]](%0) | |
| %5 : int, %6 : Tensor = ppp::ppp[i_asdf=2, f_asdf=3.14, s_asdf="\"\"\"\"\nhe\"llo", q=[3, 2, 4]](%0) | |
| %7 : float = vvv::vvv[s_asdf="hello"](%0) | |
| %8 : string = z::z() | |
| return (%7) | |
| )IR"); | |
| } | |
| { | |
| checkRoundtrip( | |
| R"IR( | |
| graph(%0 : Tensor, | |
| %1 : Tensor, | |
| %2 : Tensor): | |
| %3 : int? = prim::Constant() | |
| return (%3) | |
| )IR"); | |
| } | |
| { | |
| checkRoundtrip( | |
| R"IR( | |
| graph(%0 : Tensor, | |
| %1 : Tensor, | |
| %2 : Tensor): | |
| %3 : Float(*, *, *) = prim::Constant() | |
| return (%3) | |
| )IR"); | |
| } | |
| { | |
| checkRoundtrip( | |
| R"IR( | |
| graph(%0 : Tensor, | |
| %1 : Tensor, | |
| %2 : Tensor): | |
| %3 : Long() = prim::Constant() | |
| return (%3) | |
| )IR"); | |
| } | |
| { | |
| checkRoundtrip( | |
| R"IR( | |
| graph(%0 : Tensor, | |
| %1 : Tensor, | |
| %2 : Tensor): | |
| %3 : Double(4, 4, 5) = prim::Constant() | |
| return (%3) | |
| )IR"); | |
| } | |
| { | |
| bool error_thrown = false; | |
| try { | |
| checkRoundtrip( | |
| R"IR( | |
| graph(%0 : Tensor, | |
| %1 : Tensor, | |
| %2 : Tensor): | |
| %3 : Double(4!, 4, 5) = prim::Constant() | |
| return (%3) | |
| )IR"); | |
| } catch (const std::exception& error) { | |
| error_thrown = true; | |
| } | |
| AT_ASSERT(error_thrown); | |
| } | |
| } | |
| } // namespace jit | |
| } // namespace torch |