// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #ifndef RUNTIME_LLVM_CODEGEN_CODEGEN_DART_H_ #define RUNTIME_LLVM_CODEGEN_CODEGEN_DART_H_ #include #include #include #include "llvm/ADT/StringRef.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Module.h" #include "llvm/IR/ValueSymbolTable.h" #include "llvm/Support/Error.h" #include "llvm/Support/WithColor.h" // Ensure to use our own zone. #include "custom_zone.h" #include "vm/compiler/backend/sexpression.h" // This file introduces several representations that exist between // S-Expressions and llvm IR. To explain how each part is intended // to be roughly translated using these I'll provide a mapping // showing how each part is translated. // At a high level two translations are very seamless: // // (function foo ...) -> DartFunction -> llvm::Function* // (block B ...) -> DartBlock -> (sort of) llvm::BasicBlock* // // Internally within a function constants are given names and are then used in // blocks. Each block additionally defines more names. Each of these names // needs to be mapped to a DartValue (more on that later) so a context to help // keep track of these name to DartValue mappings called a // DartBasicBlockBuilder exists. It is only used for to aid in the // (block B ...) -> DartBlock translation which is in turn used in the // (function foo ...) -> DartFunction translation. DartFunctions and DartBlocks // can be seen as validated representations of their S-Expression forms. // Within a DartBlock we have DartInstructions. Each DartInstruction either // has some effect (like calling a function or updating a value) or defines // a new name mapping it to a DartValue later. DartValues are referenced as // arguments to DartInstructions and map to llvm::Value*s. DartInstructions // have no analogue which they're directly translated to in code but they // do closely correspond mentally to llvm::Instruction*. The challenge is that // a DartInstruction might map to a near arbitrary number of llvm Instructions // and that mapping is very dependent on context. So instead of mapping them // to objects DartInstructions just know how to add their llvm Instructions to // an llvm::BasicBlock. These instructions might reference a verity of context // and so BasicBlockBuilder is passed in to provide them with this context. // Each DartInstruction is expected to already hold each DartValue that it // needs as previously supplied by the DartBasicBlockBuilder on construction. // An additional issue arises when translating DartInstructions into llvm IR. // Many require introducing control flow when lowered to the level of llvm IR // but this requires using multiple llvm::BasicBlock for a single DartBlock. // Luckily each DartInstruction that is not a terminator is expected to make it // possible for all non-exceptional control flow paths to wind up at a single // basic block. Since BasicBlockBuilder keeps track of a "current" basic block // via llvm::IRBuilder we simply ensure that after generating many blocks we // end each instruction which requires new basic blocks on this final basic // block that all generated blocks flow into. A picture better explains this: // Say you have a DartBlock B_1 it would then be mapped to llvm blocks that // look like this: // // B_1 // / \ // / \ // B_1_left B_1_right // \ / // \ / // B_1_0 // // And then we go on adding to B_1_0 as if it were the end of B_1 from before. // Each DartValue knows how to map itself to an llvm::Value. In llvm a Value // is really just anything you can give a name to in the IR but a DartValue // is intended to be more specific, it's a compile time representation of a // object, be it an integer, a tagged SMI, a pointer to a Dart Object, etc... // its intended to correspond to some actual object that we're computing on. // Class FunctionBuilder provides functionality for building // and LLVM function object including the ability to add a // named basic block, find an existing block, get the current // context/module and additionally retrieve llvm Values valid // in the current function. class FunctionBuilder { public: FunctionBuilder(llvm::LLVMContext& ctx, llvm::Module& mod, llvm::Function& func) : ctx_(ctx), mod_(mod), func_(func) {} llvm::LLVMContext& Context() { return ctx_; } llvm::Module& Module() { return mod_; } llvm::Value* GetSymbolValue(llvm::StringRef name) { auto* symtab = func_.getValueSymbolTable(); return symtab->lookup(name); } llvm::BasicBlock* AddBasicBlock() { return llvm::BasicBlock::Create(ctx_, "", &func_); } llvm::BasicBlock* AddBasicBlock(llvm::StringRef name) { auto* bb = llvm::BasicBlock::Create(ctx_, name, &func_); basic_blocks_[name] = bb; return bb; } llvm::BasicBlock* GetBasicBlock(llvm::StringRef name) const { auto iter = basic_blocks_.find(name); if (iter != basic_blocks_.end()) return iter->getValue(); return nullptr; } private: llvm::LLVMContext& ctx_; llvm::Module& mod_; llvm::Function& func_; llvm::StringMap basic_blocks_; }; class BasicBlockBuilder; // TODO(jakehehrlich): Make this architecture dependent // Class DartThreadObject is used as a high level object for generating // code that reads fields from the thread object. class DartThreadObject { public: explicit DartThreadObject(BasicBlockBuilder& bb_builder) : bb_builder_(bb_builder) {} // StackLimit returns an llvm::Value representing the stack limit // of the thread object. llvm::Value* StackLimit() const; private: static constexpr intptr_t kThreadStackLimitOffset = 72; BasicBlockBuilder& bb_builder_; // GetOffset returns an llvm::Value* representing a pointer to a particular // field of the thread object. It adds the specified offset to the thread // pointer, and then casts to the specified type. llvm::Value* GetOffset(llvm::Type* type, intptr_t offset) const; }; class DartValue; // A class for keeping track of the basic block state and SSA values. // This is similar to IRBuilder but also keeps track of the argument stack and // basic block names. class BasicBlockBuilder { public: BasicBlockBuilder(llvm::BasicBlock* bb, FunctionBuilder& fb) : fb_(fb), top_(bb), builder_(bb), thread_object_(*this) {} llvm::LLVMContext& Context() { return fb_.Context(); } llvm::Module& Module() { return fb_.Module(); } llvm::IRBuilder<>& Builder() { return builder_; } const DartThreadObject& ThreadObject() { return thread_object_; } llvm::BasicBlock* AddBasicBlock() { return fb_.AddBasicBlock(); } llvm::BasicBlock* GetBasicBlock(llvm::StringRef Name) const { return fb_.GetBasicBlock(Name); } llvm::Value* GetSymbolValue(llvm::StringRef name) const { return fb_.GetBasicBlock(name); } llvm::Value* GetValue(const DartValue* v); void PushArgument(llvm::Value* v) { stack_.push_back(v); } llvm::Value* PopArgument() { llvm::Value* out = stack_.back(); stack_.pop_back(); return out; } private: FunctionBuilder& fb_; llvm::BasicBlock* top_; llvm::SmallVector stack_; llvm::IRBuilder<> builder_; llvm::DenseMap values_; DartThreadObject thread_object_; }; // Class DartValue represents an SSA value from the Dart SSA // such that it can be converted into an llvm::Value*. class DartValue { public: virtual ~DartValue() {} virtual llvm::Value* Make(BasicBlockBuilder& bb_builder) const = 0; virtual llvm::Type* GetType(BasicBlockBuilder& bb_builder) const = 0; }; // Class DartBasicBlockBuilder provides helpful context for going // from an S-Expression basic block to a DartBlock. It just lets // one lookup DartValue's by name at this time. class DartBasicBlockBuilder { public: void AddDef(llvm::StringRef name, const DartValue* v) { defs_[name] = v; } const DartValue* GetDef(llvm::StringRef name) const { auto iter = defs_.find(name); if (iter != defs_.end()) return iter->getValue(); return nullptr; } private: llvm::StringMap defs_; }; // A DartConstant is a DartValue for a constant. class DartConstant : public DartValue { public: std::string str; enum class Type { String }; Type type; llvm::Value* Make(BasicBlockBuilder& bb_builder) const override; llvm::Type* GetType(BasicBlockBuilder& bb_builder) const override; }; // Class DartInstruction represents a step within a DartBasicBlock. // CheckStackOverflow or PushArgument are instructions. They contain // DartValues as arguments typically. SSA definitions are also // DartInstructions which assign DartValues to names in the function's // context. class DartInstruction { public: virtual ~DartInstruction(); virtual void Build(BasicBlockBuilder& bb_builder) const = 0; }; // Class InstCheckStackOverflow is a DartInstruction that represents // an instance of a CheckStackOverflow instruction. class InstCheckStackOverflow : public DartInstruction { public: InstCheckStackOverflow() {} ~InstCheckStackOverflow() override {} void Build(BasicBlockBuilder& bb_builder) const override; static llvm::Expected> Construct( dart::SExpList* inst, DartBasicBlockBuilder& bb_builder); }; // Class InstPushArgument is a DartInstruction that represents // and instance of a PushArgument Instruction. class InstPushArgument : public DartInstruction { public: explicit InstPushArgument(const DartValue* arg) : arg_(arg) {} ~InstPushArgument() override {} void Build(BasicBlockBuilder& bb_builder) const override; static llvm::Expected> Construct( dart::SExpList* inst, DartBasicBlockBuilder& bb_builder); private: const DartValue* arg_; }; // Class InstStaticCall is a DartInstruction that represents a // StaticCall instruction. class InstStaticCall : public DartInstruction { public: InstStaticCall(const DartValue* func, size_t args_len) : function_(func), args_len_(args_len) {} ~InstStaticCall() override {} void Build(BasicBlockBuilder& bb_builder) const override; static llvm::Expected> Construct( dart::SExpList* inst, DartBasicBlockBuilder& bb_builder); private: const DartValue* function_; size_t args_len_; }; // Class InstReturn is a DartInstruction that represents a Return // instruction. class InstReturn : public DartInstruction { public: InstReturn() {} ~InstReturn() override {} void Build(BasicBlockBuilder& bb_builder) const override; static llvm::Expected> Construct( dart::SExpList* inst, DartBasicBlockBuilder& bb_builder); }; // Class DartBlock represents a validated basic block as parsed from // a (block ...) S-Expression. struct DartBlock { std::string name; std::vector> instructions; }; // Class DartFunction represents a validated function parsed from a // (function ...) S-Expression. struct DartFunction { std::string name; DartBlock* normal_entry; llvm::StringMap constants; llvm::StringMap blocks; }; // MakeFunction takes an S-Expression and an environment of externally // defined DartValues and produces a DartFunction corresponding to the // S-Expression if everything is valid. If something about the syntax // of the S-Expression is invalid then the llvm::Expected will hold an // error explaining the issue. llvm::Expected MakeFunction( dart::Zone* zone, dart::SExpression* sexpr, const llvm::StringMap& env); #endif // RUNTIME_LLVM_CODEGEN_CODEGEN_DART_H_