Юрий Ефимочев, Компилируемые в реальном времени dsl для...

Post on 16-Apr-2017

660 Views

Category:

Software

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Компилируемые в реальном времени DSL для С++

Юрий Ефимочев

О себе

Архитектор в LogicNowСпециализация: высоконагруженные отказоустойчивые системы на C++

Бэкап-решение

Что такое DSL?

Domain-specific language - это язык программирования с ограниченными возможностями, ориентированный на конкретную предметную область.

Плюсы DSL

➢ управление сложностью➢ скорость разработки➢ комуникация с экспертами➢ альтернативные парадигмы➢ динамическое выполнение

Минусы DSL

➢ порог вхождения

➢ эволюция в язык общего назначения

➢ разработка и поддержка

Классификация DSL

➢ Внешние

➢ Внутренние

Внутренний DSLGiven(

Node("a")

(Node("b")

(Node("c"))

(Node("d")))

(Node("e")

(Node("f"))

(Node("g"))));

When().Filter("%f%");

Expect(

Node("a")

(Node("e")

(Node("f"))));

a

b

c d

e

f g

a

e

f

Классификация DSL

➢ Интерпретируемые

➢ Компилируемые

Архитектура DSL

ПрограмманаDSL

Семантическаямодель

Целевойкод

Опционально

Архитектура бэкап-решения

Cloudstorage

Support

Integration

Customers

Development

Sales

Finances

BackupclientBackupclientBackupclientBackupclient

~ 100 000 клиентов

~ 500 параметров у каждого

Примеры запросов

Count(true)

Sum(UsedStorage)

Count(LastSession.Timestamp < 2.days().ago())

Count(LastSession.Status == SessionStatus::Failed)

Average(LastSession.Duration)

Архитектура решения

Expression AST Result

Data

Синтаксический анализ

Flex & Bison

Antlr

boost::Spirit

Bison: грамматика

%start Start;

AtomicExpression:IntegerNumber { $$.Value = m_builder.CreateInteger($1.Name); } |Identifier { $$.Value = m_builder.CreateVariable($1.Name) };

AddSubExpression:AtomicExpression |AddSubExpression Plus AddSubExpression { m_builder.CreateAddNode(...); } |AddSubExpression Minus AddSubExpression { m_builder.CreateSubNode(...); };

Start:AddSubExpression { result.AstTree.reset($1.Value); };

AST

Count(time > 2.days().ago())Count

time ago

days

2

>

x.ago() = now - x

AST

Count(time > 2.days().ago())Count

time -

days

2

>

x.ago() = now - x

x.days() = x * 86400 now

AST

Count(time > 2.days().ago())Count

time -

*

86400

>

x.ago() = now - x

x.days() = x * 86400 now

2

Реализация объектов AST

1 class IntegerNode : public IAstNode 2 { 3 public: 4 IntegerNode(int const value) : 5 m_value(value) 6 { 7 } 8 9 private:10 virtual Variant Evaluate(IDataProvider& /*dataProvider*/) const11 {12 return Variant(m_value);13 }1415 int const m_value;16 };

Реализация объектов AST

1 class VariableNode : public IAstNode 2 { 3 public: 4 VariableNode(std::string const& name) : 5 m_name(name) 6 { 7 } 8 9 private:10 virtual Variant Evaluate(IDataProvider& dataProvider) const11 {12 return Variant(dataProvider.GetVariableValue(m_name));13 }1415 private:16 std::string const m_name;17 };

Реализация объектов AST

1 class AddNode : public IAstNode 2 { 3 public: 4 AddNode(IAstNodePtr left, IAstNodePtr right) : 5 m_left(std::move(left)), 6 m_right(std::move(right)) 7 { 8 } 910 private:11 virtual Variant Evaluate(IDataProvider& dataProvider) const12 {13 return m_left->Compile(dataProvider) + m_right->Compile(dataProvider);14 }1516 private:17 IAstNodePtr m_left;18 IAstNodePtr m_right;19 };

Итоги: первая версия

Плюсы:

➢ простота использования➢ скорость разработки➢ простота реализации

Минусы:

➢ производительность

AST

Count(time > 2.days().ago())Count

time -

*

86400

>

now

2

Архитектура LLVM

Что такое LLVM IR?LLVM IR(Intermediate representation) - апаратно независимый низкоуровневый язык прогрммирования.

LLVM IR

i1, … ,i32, … , i1942652 Целочисленные типы

half, float, double Типы с плавающей точкой

[40 x i32] Массивы

<4 x i32> Векторы (SIMD)

{ i32, i32, i32 } Структуры

i32*, [4 x i32]* Указатили

➢ функции

➢ типы данных(статическая строгая типизация)

➢ платформонезависимый

➢ SSA(static single assignment) нотация

LLVM IR 1 bool MoveNext(); 2 int GetValue(); 3 4 int main() 5 { 6 int sum = 0; 7 8 while (MoveNext()) 9 {10 sum += GetValue();11 }1213 return sum;14 }

1 declare i1 @move_next() 2 declare i64 @get_value() 3 4 define i64 @main() 5 { 6 entry: 7 br label %loop_condition 8 9 loop_condition: 10 %1 = phi i64 [ 0, %entry ], [ %4, %loop_body ] 11 %2 = call i1 @move_next() 12 br i1 %2, label %loop_body, label %loop_exit 13 14 loop_body: 15 %3 = call i64 @get_value() 16 %4 = add i64 %3, %1 17 br label %loop_condition 18 19 loop_exit: 20 ret i64 %1 21 }

Архитектура решения

Expression AST LLVM IR AST

Native code

Data descriptor

Data provider

Result

Data

1 class Expression 2 { 3 public: 4 Expression(std::string const& expression); 5 6 std::int64_t Evaluate(IDataProvider& dataProvider); 7 8 private: 9 typedef std::int64_t(*CompiledExpression)();1011 LLVMContext m_llvmContext;12 ExecutionEnginePtr m_executionEngine;13 CompiledExpression m_expression;14 ExecutionContext m_executionContext;15 };

Реализация

1 std::int64_t Expression::Evaluate(IDataProvider& dataProvider) 2 { 3 m_executionContext.Reset(dataProvider); 4 5 std::int64_t const result = m_expression(); 6 7 m_executionContext.ThrowIfError(); 8 9 return result;10 }

Реализация

1 Expression::Expression(std::string const& expression) 2 { 3 IAstNodePtr astTree = BuildAst(expression); 4 5 IRBuilder builder(m_llvmContext); 6 Module* module = new Module("module", m_llvmContext); 7 8 m_executionEngine.reset(ExecutionEngine::createJIT(module)); 910 FunctionType* mainType = FunctionType::get(builder.getInt64Ty(), Arguments(), false);11 Function* main = Function::Create(mainType, Function::ExternalLinkage, "main", module);12 BasicBlock* mainEntry = BasicBlock::Create(m_llvmContext, "entry", main);1314 builder.SetInsertPoint(mainEntry);1516 CompilationContext compilationContext(builder, *m_executionEngine, m_executionContext, module);17 Value* returnValue = astTree->Compile(compilationContext);18 builder.CreateRet(returnValue);1920 m_expression = reinterpret_cast<CompiledExpression>(m_executionEngine->getPointerToFunction(main));21 }

Реализация

Реализация: константы 1 class IntegerNode : public IAstNode 2 { 3 public: 4 IntegerNode(int const value) : 5 m_value(value) 6 { 7 } 8 9 private:10 virtual Value* Compile(IDataProvider& context) const11 {12 return context.GetBuilder().getInt64(m_value);13 }1415 int const m_value;16 };

Реализация: сложение 1 class AddNode : public IAstNode 2 { 3 public: 4 AddNode(IAstNodePtr left, IAstNodePtr right) : 5 m_left(std::move(left)), 6 m_right(std::move(right)) 7 { 8 } 910 private:11 virtual Value* Compile(ICompilationContext& context) const12 {13 Value* left = m_left->Compile(context);14 Value* right = m_right->Compile(context);15 return context.GetBuilder().CreateAdd(left, right);16 }1718 private:19 IAstNodePtr m_left;20 IAstNodePtr m_right;21 };

Реализация: переменные 1 class VariableNode : public IAstNode 2 { 3 public: 4 VariableNode(std::string const& name) : 5 m_name(name) 6 { 7 } 8 9 private: 10 virtual Value* Compile(ICompilationContext& context) const 11 { 12 return context.GetHelper().GetVariable(m_name); 13 } 14 15 private: 16 std::string const m_name; 17 };

Реализация: переменные 1 extern "C" std::int64_t GetVariableValue(std::int64_t const context, char const* const variableName) 2 { 3 ExecutionContext& executionContext = *reinterpret_cast<ExecutionContext*>(context); 4 std::int64_t const result = executionContext.GetVariableValue(variableName); 5 return result; 6 } 7 8 Value* LlvmHelper::GetVariable(std::string const& name) 9 {10 Value*& variable = m_variables[name];1112 if (variable == nullptr)13 {14 Function* llvmFunction = Export("get_value", &GetVariableValue);15 variable = Call(llvmFunction, &m_executionContext, name);16 }1718 return variable;19 }

Реализация: переменные 1 template<typename ReturnType, typename... ArgumentTypes> 2 Function* LlvmHelper::Export(std::string const& name, ReturnType (*nativeFunction)(ArgumentTypes...)) 3 { 4 std::vector<Type*> llvmArgumentTypes = { (GetLlvmType<ArgumentTypes>())... }; 5 Type* llvmReturnType = GetLlvmType<ReturnType>(); 6 7 Function* function = Function::Create(functionType, Function::ExternalLinkage, name, m_module); 8 function->setCallingConv(llvm::CallingConv::C); 910 m_executionEngine.addGlobalMapping(function, (void*)nativeFunction);1112 return function;13 }1415 template<typename... ArgumentTypes>16 Value* LlvmHelper::Call(Function* llvmFunction, Arguments... arguments)17 {18 std::vector<Value*> llvmArguments = { (ToLlvmValue(arguments))... };19 return m_builder.CreateCall(llvmFunction, llvmArguments);20 }

Реализация: отрицание

1 Value* NotNode::Compile(ICompilationContext& context) const 2 { 3 CompiledNode child = m_value->Compile(context); 4 5 IRBuilder& builder = context.GetBuilder(); 6 7 Value* one = builder.getInt64(1); 8 Value* zero = builder.getInt64(0); 9 Value* notEqualZero = builder.CreateICmpNE(zero, child.Value); 10 notEqualZero = builder.CreateZExt(notEqualZero, builder.getInt64Ty()); 11 12 Value* value = builder.CreateXor(one, nonEqualZero); 13 value = builder.CreateZExt(value, builder.getInt64Ty()); 14 15 return value; 16 }

not x == 1 xor (x != 0)

ПроизводительностьCount(x < 50 && x > 22 || x == 77), size = 1000000

Interpreted 1734 мс

LLVM 31 мс

Native 21 мс

Кодогенерация

LLVM

Google V8

Lua

ПроизводительностьCount(x < 50 && x > 22 || x == 77), size = 1000000

Interpreted 1734 мс

V8 402 мс

Lua 309 мс

LLVM 31 мс

Native 21 мс

Итоги: вторая версия

Плюсы:

➢ простота использования➢ скорость разработки➢ производительность

Итоги: вторая версия

Минусы:

➢ размер бинарного файла (~ 15 Mb)➢ сложность реализации➢ нативный код

top related