From 47415185d04dfedc3a8d29e4c1bc2719c9e88cba Mon Sep 17 00:00:00 2001 From: VMois Date: Sat, 12 Aug 2023 01:02:00 +0200 Subject: [PATCH] WIP, add custom MLIR type and step op --- README.md | 9 +- example_workflow.snakefile | 16 +++ include/Parsers/SnakemakeParser.hpp | 110 +++++++++++----- include/PolyFlow/PolyFlowDialect.h | 5 - include/PolyFlow/PolyFlowDialect.td | 1 + include/PolyFlow/PolyFlowOps.h | 3 + include/PolyFlow/PolyFlowOps.td | 29 ++++- include/PolyFlow/PolyFlowTypes.h | 9 ++ include/PolyFlow/PolyFlowTypes.td | 21 +++ lib/Parsers/SnakemakeParser.cpp | 190 +++++++++++++++++++--------- lib/PolyFlow/PolyFlowDialect.cpp | 11 ++ polyflow-opt/polyflow-opt.cpp | 41 +++--- 12 files changed, 323 insertions(+), 122 deletions(-) create mode 100644 example_workflow.snakefile create mode 100644 include/PolyFlow/PolyFlowTypes.h create mode 100644 include/PolyFlow/PolyFlowTypes.td diff --git a/README.md b/README.md index c527421..2705551 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,9 @@ While working on [REANA](https://github.com/reanahub/reana) project at CERN, I n Please make sure to build LLVM and MLIR projects first according to [the instruction](https://mlir.llvm.org/getting_started/). For this particular project those commands were used to build LLVM and MLIR project (provided as an example): ```sh -cmake -G Ninja ../llvm \ +$ mkdir build && cd build + +$ cmake -G Ninja ../llvm \ -DLLVM_ENABLE_PROJECTS=mlir \ -DLLVM_BUILD_EXAMPLES=ON \ -DLLVM_TARGETS_TO_BUILD="host" \ @@ -23,7 +25,8 @@ cmake -G Ninja ../llvm \ -DMLIR_INCLUDE_INTEGRATION_TESTS=ON \ -DLLVM_INSTALL_UTILS=ON \ -DLLVM_INCLUDE_TOOLS=ON -cmake --build . --target check-mlir + +$ cmake --build . --target check-mlir ``` To build PolyFlow, run the following commands: @@ -48,4 +51,4 @@ cmake --build . --target mlir-doc # Credits -- [MLIR Hello World repo](https://github.com/Lewuathe/mlir-hello) \ No newline at end of file +- [MLIR Hello World repo](https://github.com/Lewuathe/mlir-hello) diff --git a/example_workflow.snakefile b/example_workflow.snakefile new file mode 100644 index 0000000..18c3767 --- /dev/null +++ b/example_workflow.snakefile @@ -0,0 +1,16 @@ +rule task1: + input: + "inputs/input1.txt", + "inputs/input2.txt" + output: + "./output_task1.txt" + shell: + "cat input1.txt input2.txt > outputs/task1.txt | echo Done >> outputs/task1.txt" + +rule task2: + input: + "outputs/task1.txt" + output: + "outputs/task2.txt" + shell: + "cat outputs/task1.txt >> outputs/task2.txt" diff --git a/include/Parsers/SnakemakeParser.hpp b/include/Parsers/SnakemakeParser.hpp index b99ca84..2f15580 100644 --- a/include/Parsers/SnakemakeParser.hpp +++ b/include/Parsers/SnakemakeParser.hpp @@ -4,6 +4,16 @@ #include #include #include +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/MLIRContext.h" + +#include "PolyFlow/PolyFlowDialect.h" +#include "PolyFlow/PolyFlowTypes.h" +#include "PolyFlow/PolyFlowOps.h" + + +namespace snakemake { enum TokenType { RULE, @@ -26,9 +36,9 @@ struct Token { size_t column; }; -class SnakemakeLexer { +class Lexer { public: - SnakemakeLexer(std::istream& input) : input_(input) {} + Lexer(std::istream& input) : input_(input) {} Token nextToken(); bool eof() const { return input_.eof(); } @@ -49,45 +59,63 @@ class SnakemakeLexer { Token readIdentifierOrKeyword(); }; -class TreeNode { +class BaseAST { public: - std::string name; - std::vector> children; + enum ASTKind { + Rule, + }; - TreeNode(const std::string &name) : name(name) {} + BaseAST(ASTKind kind, Token location) + : kind(kind), location(std::move(location)) {} + virtual ~BaseAST() = default; - void add_child(const std::shared_ptr &child) { - children.push_back(child); - } + ASTKind getKind() const { return kind; } - void print(int indent = 0) { - for (int i = 0; i < indent; ++i) { - std::cout << " "; - } - std::cout << name << std::endl; - for (const auto &child : children) { - child->print(indent + 1); - } - } + const Token &loc() { return location; } + +private: + const ASTKind kind; + Token location; +}; + +class RuleAST : public BaseAST { + public: + std::string name; + std::vector inputs; + std::vector outputs; + std::string command; + + RuleAST(Token loc, + std::string name, + std::vector inputs, + std::vector outputs, + std::string command) + : BaseAST(Rule, std::move(loc)), name(std::move(name)), inputs(std::move(inputs)), outputs(std::move(outputs)), command(std::move(command)) {} + + static bool classof(const BaseAST *c) { return c->getKind() == Rule; } }; -class SnakemakeParser { +class ModuleAST { + public: + std::vector> rules; + ModuleAST(std::vector> rules) + : rules(std::move(rules)) {} +}; + +void dumpAST(ModuleAST &); + +class Parser { public: - SnakemakeParser(std::istream& input): lexer_(input) {} + Parser(std::istream& input): lexer_(input) {} - void parse() { + std::unique_ptr parseModule() { advance(); - snakemake(); - } - - void print_tree() { - root->print(); + return snakemake(); } private: - SnakemakeLexer lexer_; + Lexer lexer_; Token lookahead; - std::shared_ptr root = std::make_shared("snakemake"); void advance(); @@ -95,11 +123,25 @@ class SnakemakeParser { void match(const TokenType tokenType); - std::shared_ptr snakemake(); - std::shared_ptr rule(); + std::unique_ptr snakemake(); + std::unique_ptr rule(); - std::shared_ptr input_rule(); - std::shared_ptr output_rule(); - std::shared_ptr shell_rule(); - std::shared_ptr parameter_list(); + std::vector input_rule(); + std::vector output_rule(); + std::string shell_rule(); + std::vector parameter_list(); }; + +class MLIRGen { + public: + MLIRGen(mlir::MLIRContext &context) : builder(&context) {} + mlir::ModuleOp mlirGen(ModuleAST &); + + private: + mlir::ModuleOp theModule; + mlir::OpBuilder builder; + + polyflow::StepOp mlirGen(RuleAST &); +}; + +} // namespace snakemake diff --git a/include/PolyFlow/PolyFlowDialect.h b/include/PolyFlow/PolyFlowDialect.h index bf7162b..ff91f4b 100644 --- a/include/PolyFlow/PolyFlowDialect.h +++ b/include/PolyFlow/PolyFlowDialect.h @@ -2,11 +2,6 @@ #define POLYFLOW_POLYFLOWDIALECT_H #include "mlir/IR/Dialect.h" -#include "mlir/IR/BuiltinOps.h" -#include "mlir/IR/BuiltinDialect.h" -#include "mlir/IR/BuiltinTypes.h" -#include "mlir/IR/Dialect.h" -#include "mlir/Interfaces/SideEffectInterfaces.h" #include "PolyFlow/PolyFlowOpsDialect.h.inc" diff --git a/include/PolyFlow/PolyFlowDialect.td b/include/PolyFlow/PolyFlowDialect.td index 0b7ed57..0562820 100644 --- a/include/PolyFlow/PolyFlowDialect.td +++ b/include/PolyFlow/PolyFlowDialect.td @@ -15,6 +15,7 @@ def PolyFlow_Dialect : Dialect { }]; let cppNamespace = "::polyflow"; let hasConstantMaterializer = 0; + let useDefaultTypePrinterParser = 1; } //===----------------------------------------------------------------------===// diff --git a/include/PolyFlow/PolyFlowOps.h b/include/PolyFlow/PolyFlowOps.h index c60e3a1..a6863a8 100644 --- a/include/PolyFlow/PolyFlowOps.h +++ b/include/PolyFlow/PolyFlowOps.h @@ -4,8 +4,11 @@ #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Dialect.h" #include "mlir/IR/OpDefinition.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" +#include "PolyFlow/PolyFlowTypes.h" + #define GET_OP_CLASSES #include "PolyFlow/PolyFlowOps.h.inc" diff --git a/include/PolyFlow/PolyFlowOps.td b/include/PolyFlow/PolyFlowOps.td index 784bcbb..b3cb652 100644 --- a/include/PolyFlow/PolyFlowOps.td +++ b/include/PolyFlow/PolyFlowOps.td @@ -2,16 +2,33 @@ #define POLYFLOW_OPS include "PolyFlowDialect.td" +include "PolyFlowTypes.td" +include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/Interfaces/SideEffectInterfaces.td" -def ConstantOp : PolyFlow_Op<"constant", [Pure]> { - let summary = "constant"; - let description = "simple constant operation"; - let arguments = (ins F64Attr:$value); - let results = (outs F64:$result); +def FileLocation : PolyFlow_Op<"FileLocation", [Pure]> { + let summary = "An input value to a file."; + let description = "A single step in the workflow execution."; - let assemblyFormat = "attr-dict `(` $value `)` `:` type($result)"; + let arguments = (ins StrAttr:$value); + + let results = (outs PolyFlow_StringType:$result); + let assemblyFormat = "attr-dict `:` type($result)"; +} + +def StepOp : PolyFlow_Op<"Step", []> { + let summary = "A single step in the workflow execution."; + let description = "A single step in the workflow execution."; + + let arguments = (ins + StrAttr:$name, + Variadic:$inputs + ); + + let regions = (region AnyRegion:$body); + + let assemblyFormat = "$name `(` $inputs `)` attr-dict `(` type($inputs) `)` `:` $body"; } #endif // POLYFLOW_OPS diff --git a/include/PolyFlow/PolyFlowTypes.h b/include/PolyFlow/PolyFlowTypes.h new file mode 100644 index 0000000..9e8b851 --- /dev/null +++ b/include/PolyFlow/PolyFlowTypes.h @@ -0,0 +1,9 @@ +#ifndef POLYFLOW_POLYFLOWTYPES_H +#define POLYFLOW_POLYFLOWTYPES_H + +#include "mlir/IR/BuiltinTypes.h" + +#define GET_TYPEDEF_CLASSES +#include "PolyFlow/PolyFlowOpsTypes.h.inc" + +#endif // POLYFLOW_POLYFLOWTYPES_H \ No newline at end of file diff --git a/include/PolyFlow/PolyFlowTypes.td b/include/PolyFlow/PolyFlowTypes.td new file mode 100644 index 0000000..5303f76 --- /dev/null +++ b/include/PolyFlow/PolyFlowTypes.td @@ -0,0 +1,21 @@ +#ifndef STANDALONE_TYPES +#define STANDALONE_TYPES + +include "mlir/IR/AttrTypeBase.td" +include "PolyFlowDialect.td" + + +class PolyFlow_Type traits = []> + : TypeDef { + let mnemonic = typeMnemonic; +} + + +def PolyFlow_StringType : PolyFlow_Type<"Polyflow_Str", "polyflow_str"> { + let summary = "A string"; + let description = "Custom type in standalone dialect"; + //let parameters = (ins StringRefParameter<"the custom value">:$value); + //let assemblyFormat = "`<` $value `>`"; +} + +#endif diff --git a/lib/Parsers/SnakemakeParser.cpp b/lib/Parsers/SnakemakeParser.cpp index 3584d8d..7d6cd72 100644 --- a/lib/Parsers/SnakemakeParser.cpp +++ b/lib/Parsers/SnakemakeParser.cpp @@ -1,14 +1,8 @@ -#include -#include -#include -#include -#include -#include - #include "Parsers/SnakemakeParser.hpp" +namespace snakemake { -Token SnakemakeLexer::nextToken() { +Token Lexer::nextToken() { skipWhitespace(); if (input_.eof()) { @@ -42,26 +36,26 @@ Token SnakemakeLexer::nextToken() { } }; -bool SnakemakeLexer::isWhitespace(char c) const { +bool Lexer::isWhitespace(char c) const { return c == ' '; }; -void SnakemakeLexer::skipWhitespace() { +void Lexer::skipWhitespace() { while (isWhitespace(input_.peek())) { input_.get(); column_++; } }; -bool SnakemakeLexer::isIdentifierStart(char c) const { +bool Lexer::isIdentifierStart(char c) const { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_'); }; -bool SnakemakeLexer::isIdentifierChar(char c) const { +bool Lexer::isIdentifierChar(char c) const { return isIdentifierStart(c) || (c >= '0' && c <= '9'); }; -Token SnakemakeLexer::readString() { +Token Lexer::readString() { std::string value; input_.get(); // consume the opening quote @@ -73,14 +67,14 @@ Token SnakemakeLexer::readString() { return Token{STRING, value, line_, column_ - value.length()}; }; -Token SnakemakeLexer::readNewline() { +Token Lexer::readNewline() { input_.get(); // consume the newline line_++; column_ = 0; return Token{NEWLINE, "NEW_LINE", line_, column_}; }; -Token SnakemakeLexer::readIdentifierOrKeyword() { +Token Lexer::readIdentifierOrKeyword() { std::string value; while (isIdentifierChar(input_.peek())) { value += input_.get(); @@ -101,15 +95,15 @@ Token SnakemakeLexer::readIdentifierOrKeyword() { }; -void SnakemakeParser::advance() { +void Parser::advance() { lookahead = lexer_.nextToken(); } -void SnakemakeParser::throw_unexpected_token() { +void Parser::throw_unexpected_token() { throw std::runtime_error("Unexpected token: " + lookahead.value + " on line " + std::to_string(lookahead.line) + ", column " + std::to_string(lookahead.column) + "\n"); } -void SnakemakeParser::match(const TokenType tokenType) { +void Parser::match(const TokenType tokenType) { if (lookahead.type == tokenType) { advance(); } else { @@ -117,93 +111,87 @@ void SnakemakeParser::match(const TokenType tokenType) { } } -std::shared_ptr SnakemakeParser::snakemake() { - if (lookahead.type == END_OF_FILE) return nullptr; +std::unique_ptr Parser::snakemake() { + std::vector> rules; - std::shared_ptr node; - if (lookahead.type == RULE) { - node = rule(); - } else if (lookahead.type == NEWLINE) { - match(NEWLINE); - } else { - throw_unexpected_token(); - } - - if (node) { - root->add_child(node); + while (lookahead.type != END_OF_FILE) { + if (lookahead.type == RULE) { + rules.push_back(rule()); + } else if (lookahead.type == NEWLINE) { + match(NEWLINE); + } else { + throw_unexpected_token(); + } } - snakemake(); - return root; + return std::make_unique(std::move(rules)); } -std::shared_ptr SnakemakeParser::rule() { +std::unique_ptr Parser::rule() { match(RULE); - std::shared_ptr rule_node = std::make_shared("rule"); + std::string ruleName = "unknown"; + Token location = lookahead; if (lookahead.type == IDENTIFIER) { - rule_node->add_child(std::make_shared(lookahead.value)); + ruleName = lookahead.value; advance(); } match(COLON); match(NEWLINE); + std::vector inputs; + std::vector outputs; + std::string command; + while (lookahead.type == INPUT || lookahead.type == OUTPUT || lookahead.type == SHELL) { if (lookahead.type == INPUT) { - rule_node->add_child(input_rule()); + inputs = input_rule(); } else if (lookahead.type == OUTPUT) { - rule_node->add_child(output_rule()); + outputs = output_rule(); } else if (lookahead.type == SHELL) { - rule_node->add_child(shell_rule()); + command = shell_rule(); } } - return rule_node; + return std::make_unique(location, ruleName, std::move(inputs), + std::move(outputs), std::move(command)); } -std::shared_ptr SnakemakeParser::input_rule() { +std::vector Parser::input_rule() { match(INPUT); match(COLON); match(NEWLINE); - std::shared_ptr input_node = std::make_shared("input"); - input_node->add_child(parameter_list()); - - return input_node; + return parameter_list(); } -std::shared_ptr SnakemakeParser::output_rule() { +std::vector Parser::output_rule() { match(OUTPUT); match(COLON); match(NEWLINE); - std::shared_ptr output_node = std::make_shared("output"); - output_node->add_child(parameter_list()); - - return output_node; + return parameter_list(); } -std::shared_ptr SnakemakeParser::shell_rule() { +std::string Parser::shell_rule() { match(SHELL); match(COLON); match(NEWLINE); - std::shared_ptr shell_node = std::make_shared("shell"); + std::string command = lookahead.value; - shell_node->add_child(std::make_shared(lookahead.value)); match(STRING); - match(NEWLINE); - return shell_node; + return command; } -std::shared_ptr SnakemakeParser::parameter_list() { - std::shared_ptr list_node = std::make_shared("parameter_list"); +std::vector Parser::parameter_list() { + std::vector parameters; while (lookahead.type == STRING) { - list_node->add_child(std::make_shared(lookahead.value)); + parameters.push_back(lookahead.value); match(STRING); if (lookahead.type == COMMA) { @@ -211,5 +199,89 @@ std::shared_ptr SnakemakeParser::parameter_list() { } match(NEWLINE); } - return list_node; + return parameters; +}; + +void dumpAST(ModuleAST &module) { + for (auto &rule : module.rules) { + std::cout << "rule " << rule->name << ":\n"; + std::cout << " input:\n"; + for (auto &input : rule->inputs) { + std::cout << " " << input << "\n"; + } + std::cout << " output:\n"; + for (auto &output : rule->outputs) { + std::cout << " " << output << "\n"; + } + std::cout << " shell:\n"; + std::cout << " " << rule->command << "\n"; + } +}; + + +mlir::ModuleOp snakemake::MLIRGen::mlirGen(ModuleAST &moduleAST) { + theModule = mlir::ModuleOp::create(builder.getUnknownLoc()); + builder.setInsertionPointToEnd(theModule.getBody()); + + for (auto &rule : moduleAST.rules) { + mlirGen(*rule); + } + return theModule; +}; + +polyflow::StepOp snakemake::MLIRGen::mlirGen(RuleAST &rule) { + auto name = llvm::StringRef(rule.name); + + // auto command = llvm::StringRef(rule.command); + auto inputs = rule.inputs; + // auto outputs = rule.outputs; + + std::vector inputValues; + + for (auto i = 0; i < inputs.size(); i++) { + mlir::StringAttr strAttr = mlir::StringAttr::get(builder.getContext(), inputs[i]); + auto fileLocationStr = llvm::StringRef(inputs[i]); + polyflow::FileLocation inputOp = builder.create(builder.getUnknownLoc(), polyflow::Polyflow_StrType::get(builder.getContext()), strAttr); + //polyflow::FileLocation inputOp = builder.create(builder.getUnknownLoc(), mlir::IntegerType::get(builder.getContext(), 69), strAttr); + inputValues.push_back(inputOp.getResult()); + } + + mlir::ValueRange inputRange = mlir::ValueRange(inputValues); + polyflow::StepOp step = builder.create( + builder.getUnknownLoc(), + name, + inputRange + ); + // mlir::Region& region = step.getBody(); + // mlir::Block *body = new mlir::Block(); + + // mlir::OpBuilder::InsertionGuard guard(builder); + // builder.setInsertionPointToStart(body); + + // region.push_back(body); + + //mlir::ArrayAttr inputsAttr = mlir::ArrayAttr::get(builder.getContext(), stringAttrs); + //llvm::ScopedHashTable symbolTable; + //mlir::OpBuilder::InsertPoint savedInsertPoint = builder.saveInsertionPoint(); + // Create the inputs variable using the OpBuilder + // mlir::Value inputsVar = builder.create(builder.getUnknownLoc(), + // /*result type=*/inputsAttr.getType(), + // /*value=*/inputsAttr); + //builder.setInsertionPointToEnd(theModule.getBody()); + + //mlir::Type result = builder.getTupleType(mlir::ArrayRef()); + //builder.create(builder.getUnknownLoc(), inputsAttr); + //theModule->setAttr(llvm::StringRef(rule.name + "inputs"), inputsAttr); + //theModule->insertRegionEntryArgument(0, inputsVar); + //builder.restoreInsertionPoint(savedInsertPoint); + + + // print regions + // mlir::Region region = step.getBody(); + // mlir::Block *body = new mlir::Block(); + // body->push_back(new mlir::Block()); + // region.push_back(body); + //return step; +}; + } diff --git a/lib/PolyFlow/PolyFlowDialect.cpp b/lib/PolyFlow/PolyFlowDialect.cpp index d321fe6..dfa2abb 100644 --- a/lib/PolyFlow/PolyFlowDialect.cpp +++ b/lib/PolyFlow/PolyFlowDialect.cpp @@ -1,7 +1,10 @@ #include "mlir/IR/Builders.h" #include "mlir/IR/OpImplementation.h" +#include "mlir/IR/DialectImplementation.h" +#include "llvm/ADT/TypeSwitch.h" #include "PolyFlow/PolyFlowDialect.h" +#include "PolyFlow/PolyFlowTypes.h" #include "PolyFlow/PolyFlowOps.h" using namespace mlir; @@ -13,9 +16,17 @@ using namespace polyflow; #include "PolyFlow/PolyFlowOpsDialect.cpp.inc" +#define GET_TYPEDEF_CLASSES +#include "PolyFlow/PolyFlowOpsTypes.cpp.inc" + + void PolyFlowDialect::initialize() { addOperations< #define GET_OP_LIST #include "PolyFlow/PolyFlowOps.cpp.inc" >(); + addTypes< +#define GET_TYPEDEF_LIST +#include "PolyFlow/PolyFlowOpsTypes.cpp.inc" + >(); } diff --git a/polyflow-opt/polyflow-opt.cpp b/polyflow-opt/polyflow-opt.cpp index 6bd16b7..8048ac8 100644 --- a/polyflow-opt/polyflow-opt.cpp +++ b/polyflow-opt/polyflow-opt.cpp @@ -8,36 +8,47 @@ #include "mlir/InitAllPasses.h" #include "llvm/Support/raw_ostream.h" -#include "PolyFlow/PolyFlowOps.h" -#include "PolyFlow/PolyFlowDialect.h" #include "Parsers/SnakemakeParser.hpp" int main() { - std::string filename = "example_workflow.snakefile"; + std::string filename = "../example_workflow.snakefile"; std::ifstream file(filename); if (!file) { throw std::runtime_error("Cannot open file: " + filename); } - SnakemakeParser parser(file); - parser.parse(); - parser.print_tree(); + snakemake::Parser parser(file); + std::unique_ptr snakemakeModule = parser.parseModule(); + //snakemake::dumpAST(*snakemakeModule); mlir::MLIRContext context; - context.getOrLoadDialect(); - mlir::OpBuilder builder(&context); - mlir::ModuleOp module = mlir::ModuleOp::create(builder.getUnknownLoc()); + mlir::ModuleOp m = snakemake::MLIRGen(context).mlirGen(*snakemakeModule); + m->dump(); + + //mlir::OpBuilder builder(&context); + //mlir::ModuleOp module = mlir::ModuleOp::create(builder.getUnknownLoc()); + + // mlir::OpBuilder opBuilder(module.getBodyRegion()); + + // std::vector strings = {"string1", "string2", "string3"}; - mlir::OpBuilder opBuilder(module.getBodyRegion()); + // std::vector stringAttrs; + // for (int i = 0; i < 3; i++) { + // mlir::Attribute strAttr = mlir::StringAttr::get(&context, strings[i]); + // stringAttrs.push_back(strAttr); + // } - // Create the ConstantOp with a value of 42.0 - auto dataType = opBuilder.getF64Type(); - auto dataAttribute = mlir::FloatAttr::get(dataType, 42.0); - polyflow::ConstantOp constantOp = opBuilder.create(opBuilder.getUnknownLoc(), dataType, dataAttribute); + // mlir::ArrayAttr inputs = mlir::ArrayAttr::get(&context, stringAttrs); - module.print(llvm::outs()); + // polyflow::StepOp stepOp = opBuilder.create( + // opBuilder.getUnknownLoc(), + // llvm::StringRef("task1"), + // inputs, + // inputs, + // llvm::StringRef("echo hello world") + // ); return 0; }