| // Copyright (c) 2018, 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. |
| #include "llvm_codegen.h" |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <iostream> |
| |
| #include <llvm/IR/BasicBlock.h> |
| #include <llvm/IR/Constants.h> |
| #include <llvm/IR/Function.h> |
| #include <llvm/IR/GlobalVariable.h> |
| #include <llvm/IR/InlineAsm.h> |
| #include <llvm/IR/IRBuilder.h> |
| #include <llvm/IR/LegacyPassManager.h> |
| #include <llvm/IR/MDBuilder.h> |
| #include <llvm/IR/Module.h> |
| #include <llvm/IR/Type.h> |
| #include "llvm/Pass.h" |
| #include <llvm/Transforms/InstCombine/InstCombine.h> |
| #include <llvm/Transforms/IPO.h> |
| #include <llvm/Transforms/IPO/PassManagerBuilder.h> |
| #include <llvm/Transforms/Utils.h> |
| #include <llvm/Transforms/Vectorize.h> |
| #include <llvm/Transforms/Scalar.h> |
| #include <llvm/Transforms/Scalar/GVN.h> |
| |
| #include "dispatch_table.h" |
| #include "il_deserializer.h" |
| |
| namespace dart_llvm { |
| |
| namespace { |
| std::string MangleRuntimeName(const char* entry) { |
| return std::string("_ZN4dart") + std::to_string(strlen(entry) + 4) + |
| std::string("DRT_") + std::string(entry) + |
| std::string("ENS_15NativeArgumentsE"); |
| } |
| } // namespace |
| |
| #define DECLARE_RUNTIME_ENTRY_TAG_TO_NAME_ENTRY(entry) \ |
| {RuntimeEntryTag::k##entry, MangleRuntimeName(#entry)}, |
| |
| const std::unordered_map<CodegenModule::RuntimeEntryTag, std::string> |
| CodegenModule::kRuntimeEntryTagToName = { |
| LLVM_RUNTIME_ENTRY_LIST(DECLARE_RUNTIME_ENTRY_TAG_TO_NAME_ENTRY) |
| {RuntimeEntryTag::kNumEntries, "NumEntries"}}; |
| |
| #undef DECLARE_RUNTIME_ENTRY_TAG_TO_NAME_ENTRY |
| |
| // TODO(sarkin): Int64Ty needs to be replaced with IntTy in some places. |
| CodegenModule::CodegenModule(llvm::Module& module, const DartProgram* program) |
| : DartVMConstants(*program), |
| module_(module), |
| llvm_context_(module.getContext()), |
| program_(program) { |
| dispatch_table_ = |
| std::make_unique<DispatchTable>(*this, program->method_table()); |
| |
| // Use non-integral pointers for GC-able address space |
| // which inhibits incorrect optimizations. |
| module_.setDataLayout("S128-ni:1"); |
| |
| // TODO(sarkin): Set correct target. |
| #ifdef TARGET_X64 |
| module_.setTargetTriple("x86_64-unknown-linux-gnu"); |
| #endif |
| |
| #ifdef TARGET_WASM |
| module_.setTargetTriple("wasm32-unknown-unknown"); |
| #endif |
| |
| // Used when converting LLVM's Int1Ty to Dart bools by adding |
| // the Int1 to the False object's offset in the constant pool. |
| assert(program->true_offset() == program->false_offset() + 1); |
| |
| Int1Ty = llvm::IntegerType::getInt1Ty(llvm_context_); |
| Int8Ty = llvm::IntegerType::getInt8Ty(llvm_context_); |
| Int16Ty = llvm::IntegerType::getInt16Ty(llvm_context_); |
| Int32Ty = llvm::IntegerType::getInt32Ty(llvm_context_); |
| Int64Ty = llvm::IntegerType::getInt64Ty(llvm_context_); |
| |
| // TODO(sarkin): Platform dependent. |
| IntTy = Int64Ty; |
| |
| FloatTy = llvm::Type::getFloatTy(llvm_context_); |
| DoubleTy = llvm::Type::getDoubleTy(llvm_context_); |
| VoidTy = llvm::Type::getVoidTy(llvm_context_); |
| |
| Int32x4Ty = llvm::VectorType::get(Int32Ty, 4); |
| |
| Float32x4Ty = llvm::VectorType::get(FloatTy, 4); |
| Float64x2Ty = llvm::VectorType::get(DoubleTy, 2); |
| |
| ObjectPtrTy = llvm::PointerType::get(Int8Ty, kGCAddressSpace); |
| PtrToPtrTy = llvm::PointerType::get(ObjectPtrTy, kGCAddressSpace); |
| NonGCObjectPtrTy = |
| llvm::PointerType::getInt8PtrTy(llvm_context_, kNonGCAddressSpace); |
| NonGCPtrToPtrTy = llvm::PointerType::get(ObjectPtrTy, kNonGCAddressSpace); |
| |
| ThreadObjectTy = NonGCObjectPtrTy; |
| FunctionIDTy = Int64Ty; |
| StackArrayTy = NonGCPtrToPtrTy; |
| |
| ConstantPoolTy = llvm::ArrayType::get(ObjectPtrTy, program->num_constants()); |
| constant_pool_ = new llvm::GlobalVariable( |
| module_, ConstantPoolTy, false, llvm::GlobalVariable::ExternalLinkage, |
| nullptr, kGlobalConstantPoolName); |
| |
| FunctionPoolTy = |
| llvm::ArrayType::get(NonGCObjectPtrTy, program->NumFunctions()); |
| function_pool_ = new llvm::GlobalVariable( |
| module_, FunctionPoolTy, false, llvm::GlobalVariable::ExternalLinkage, |
| nullptr, kGlobalFunctionPoolName); |
| |
| DartFunctionPoolTy = |
| llvm::ArrayType::get(ObjectPtrTy, program->num_dart_functions()); |
| dart_function_pool_ = new llvm::GlobalVariable( |
| module_, DartFunctionPoolTy, false, llvm::GlobalVariable::ExternalLinkage, |
| nullptr, kGlobalDartFunctionPoolName); |
| |
| { |
| auto target_code_ty = ObjectPtrTy; |
| auto arg_descriptor_ty = ObjectPtrTy; |
| auto num_args_ty = Int64Ty; |
| LLVMToDartTrampolineTy = llvm::FunctionType::get( |
| ObjectPtrTy, |
| {ThreadObjectTy, target_code_ty, arg_descriptor_ty, num_args_ty, |
| StackArrayTy}, |
| false); |
| llvm_to_dart_trampoline_ = new llvm::GlobalVariable( |
| module_, NonGCObjectPtrTy, false, llvm::GlobalVariable::ExternalLinkage, |
| nullptr, kGlobalLLVMToDartTrampolineName); |
| } |
| |
| FixedParamTrampolineTy = llvm::FunctionType::get( |
| ObjectPtrTy, {FunctionIDTy, ThreadObjectTy, StackArrayTy}, false); |
| |
| StaticTrampolinesTy = |
| llvm::ArrayType::get(NonGCObjectPtrTy, program->NumFunctions()); |
| static_trampolines_ = new llvm::GlobalVariable( |
| module_, StaticTrampolinesTy, false, |
| llvm::GlobalVariable::InternalLinkage, nullptr, "_StaticTrampolines"); |
| |
| NativeArgumentsTy = llvm::StructType::create( |
| llvm_context_, |
| {ThreadObjectTy, /* argc */ Int64Ty, StackArrayTy, StackArrayTy}, |
| "NativeArguments"); |
| // Non GC-able because always allocated on the stack. |
| NativeArgumentsPtrTy = |
| llvm::PointerType::get(NativeArgumentsTy, kNonGCAddressSpace); |
| |
| RuntimeFunctionTy = llvm::FunctionType::get(/* Return type */ VoidTy, |
| {NativeArgumentsPtrTy}, false); |
| |
| RuntimeFunctionsTy = |
| llvm::ArrayType::get(NonGCObjectPtrTy, NumRuntimeFunctions()); |
| runtime_functions_ = new llvm::GlobalVariable( |
| module_, RuntimeFunctionsTy, false, llvm::GlobalVariable::InternalLinkage, |
| nullptr, "_RuntimeFunctions"); |
| |
| DispatchTableEntryTy = llvm::StructType::create( |
| llvm_context_, {Int64Ty, NonGCObjectPtrTy}, "DispatchTableEntry"); |
| DispatchTableTy = |
| llvm::PointerType::get(DispatchTableEntryTy, kNonGCAddressSpace); |
| |
| // Used temporarily when generating instance calls then replaced with the "real" |
| // dispatch table. |
| temp_dispatch_table_ = new llvm::GlobalVariable( |
| GetModule(), DispatchTableTy, false, llvm::GlobalVariable::ExternalLinkage, |
| nullptr, "_TempDispatchTable"); |
| } |
| |
| const llvm::DataLayout& CodegenModule::GetDataLayout() const { |
| return module_.getDataLayout(); |
| } |
| |
| llvm::Function* CodegenModule::GetFunctionByID(size_t id) const { |
| auto it = function_by_id_.find(id); |
| assert(it != function_by_id_.end()); |
| return it->second; |
| } |
| |
| const DartFunction* CodegenModule::GetDartFunctionByID(size_t id) const { |
| return program_->FunctionAt(id); |
| } |
| |
| size_t CodegenModule::GetFunctionIDByPatchPoint( |
| const std::string& patch_point) const { |
| assert(!patch_point.empty() && patch_point[0] == '1'); |
| return program_->GetFunctionIDForPatchPoint(patch_point); |
| } |
| |
| llvm::Function* CodegenModule::GetFunctionByPatchPoint( |
| const std::string& patch_point) const { |
| assert(!patch_point.empty() && patch_point[0] == '1'); |
| return GetFunctionByID(GetFunctionIDByPatchPoint(patch_point)); |
| } |
| |
| size_t CodegenModule::GetDartFunctionIndexByPatchPoint( |
| const std::string& patch_point) const { |
| assert(!patch_point.empty() && patch_point[0] == '0'); |
| return program_->GetFunctionIDForPatchPoint(patch_point); |
| } |
| |
| llvm::Function* CodegenModule::GetOrCreateHandleOptionalParamsTrampoline( |
| size_t function_id, |
| const Selector& selector) { |
| auto function = program_->FunctionAt(function_id); |
| std::string trampoline_name = selector.selector(); |
| |
| auto it = optional_param_trampoline_cache_.find( |
| make_pair(function_id, trampoline_name)); |
| if (it != optional_param_trampoline_cache_.end()) { |
| return it->second; |
| } |
| |
| const auto argc = selector.argc() + CodegenFunction::kDartParamOffset; |
| |
| std::vector<llvm::Type*> params(argc, ObjectPtrTy); |
| params[0] = ThreadObjectTy; |
| auto trampoline_ty = llvm::FunctionType::get(ObjectPtrTy, params, false); |
| auto trampoline = llvm::Function::Create( |
| trampoline_ty, llvm::GlobalVariable::InternalLinkage, trampoline_name, |
| &module_); |
| auto target_fn = GetDartFunctionByID(function_id); |
| // Set GC strategy only if the target function can trigger GC, because |
| // the trampoline only forwards the arguments and does nothing that can |
| // trigger GC by itself. |
| if (target_fn->can_trigger_gc()) { |
| trampoline->setGC(kGCStrategyName); |
| } |
| |
| CodegenHelper helper(*this, trampoline); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| |
| const auto num_params = |
| function->num_params() + CodegenFunction::kDartParamOffset; |
| const auto num_fixed_params = |
| function->NumFixedParameters() + CodegenFunction::kDartParamOffset; |
| const auto num_positional_args = |
| selector.NumPositionalArgs() + CodegenFunction::kDartParamOffset; |
| |
| std::vector<llvm::Value*> forward_args(num_params); |
| for (size_t i = 0; i < num_positional_args; ++i) { |
| forward_args[i] = helper.GetParam(i); |
| } |
| |
| if (!function->HasNamedParameters()) { |
| for (size_t i = num_positional_args; i < num_params; ++i) { |
| auto const_id = function->DefaultParamValueAt(i - num_fixed_params); |
| forward_args[i] = helper.EmitLoadConstant(const_id); |
| } |
| } else { |
| const auto& named_args = selector.named_args(); |
| auto get_named_arg_index = [&](const std::string& name) -> intptr_t { |
| auto it = std::find(named_args.begin(), named_args.end(), name); |
| return (it == named_args.end()) |
| ? -1 |
| : it - named_args.begin() + num_fixed_params; |
| }; |
| for (size_t i = 0; i < function->NumOptionalParameters(); ++i) { |
| auto named_arg_i = get_named_arg_index(function->NamedParameterAt(i)); |
| auto forward_arg_i = i + num_fixed_params; |
| if (named_arg_i < 0) { |
| auto const_id = function->DefaultParamValueAt(i); |
| forward_args[forward_arg_i] = helper.EmitLoadConstant(const_id); |
| } else { |
| forward_args[forward_arg_i] = helper.GetParam(named_arg_i); |
| } |
| } |
| } |
| auto call = builder.CreateCall(GetFunctionByID(function_id), {forward_args}); |
| call->setTailCallKind(llvm::CallInst::TailCallKind::TCK_Tail); |
| |
| builder.CreateRet(call); |
| |
| optional_param_trampoline_cache_.emplace( |
| std::make_pair(function_id, trampoline_name), trampoline); |
| |
| return trampoline; |
| } |
| |
| llvm::Function* CodegenModule::GetOrCreateDartCallTrampoline( |
| size_t function_id, |
| const Selector& selector) { |
| std::string trampoline_name = selector.selector(); |
| auto it = |
| dart_call_trampoline_cache_.find(make_pair(function_id, trampoline_name)); |
| if (it != dart_call_trampoline_cache_.end()) { |
| return it->second; |
| } |
| |
| auto argc = selector.argc() + CodegenFunction::kDartParamOffset; |
| std::vector<llvm::Type*> params(argc, ObjectPtrTy); |
| params[0] = ThreadObjectTy; |
| auto trampoline_ty = llvm::FunctionType::get(ObjectPtrTy, params, false); |
| auto trampoline = llvm::Function::Create( |
| trampoline_ty, llvm::GlobalVariable::InternalLinkage, trampoline_name, |
| &module_); |
| trampoline->setGC(kGCStrategyName); |
| |
| CodegenHelper helper(*this, trampoline); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| |
| assert(selector.arg_descriptor_id() >= 0); |
| auto arg_descriptor = helper.EmitLoadConstant(selector.arg_descriptor_id()); |
| auto arg_array = helper.EmitStackArray(selector.argc()); |
| for (size_t i = 0; i < selector.argc(); ++i) { |
| helper.EmitStoreInArray( |
| arg_array, i, helper.GetParam(i + CodegenFunction::kDartParamOffset)); |
| } |
| auto arg_array_ptr = helper.EmitStackArrayToPtr(arg_array); |
| auto code_object = helper.EmitLoadFieldFromGlobal( |
| dart_function_pool_, function_id, ObjectPtrTy, true); |
| |
| std::vector<llvm::Value*> trampoline_args{ |
| helper.GetThread(), code_object, arg_descriptor, |
| GetConstantInt(selector.argc()), arg_array_ptr}; |
| auto callee_ty = helper.GetNonGCPointer(LLVMToDartTrampolineTy); |
| llvm::Value* callee = builder.CreateLoad(llvm_to_dart_trampoline_); |
| callee = builder.CreateBitCast(callee, callee_ty); |
| builder.CreateRet(builder.CreateCall(callee, trampoline_args)); |
| |
| dart_call_trampoline_cache_.emplace( |
| std::make_pair(function_id, trampoline_name), trampoline); |
| |
| return trampoline; |
| } |
| |
| llvm::Function* CodegenModule::GetOrCreateDynamicTrampoline( |
| const DartFunctionDeclaration* target, |
| const Selector& selector) { |
| if (target->is_llvm()) { |
| return GetOrCreateHandleOptionalParamsTrampoline(target->id(), selector); |
| } |
| return GetOrCreateDartCallTrampoline(target->id(), selector); |
| } |
| |
| llvm::Function* CodegenModule::GetOrCreateClassAllocationStub(size_t cid) { |
| auto it = class_allocation_stub_cache_.find(cid); |
| if (it != class_allocation_stub_cache_.end()) { |
| return it->second; |
| } |
| |
| auto allocation_info = GetClassAllocationInfo(cid); |
| assert(allocation_info != nullptr); |
| |
| // (Thread object, Class object, Type arguments) |
| const int kThreadObjectOffset = 0; |
| const int kClassObjectOffset = 1; |
| const int kTypeArgumentsOffset = 2; |
| std::vector<llvm::Type*> params{ThreadObjectTy, ObjectPtrTy, ObjectPtrTy}; |
| auto stub_ty = llvm::FunctionType::get(ObjectPtrTy, params, false); |
| auto stub_name = "_LLVMAllocationStub_" + std::to_string(cid); |
| auto stub = llvm::Function::Create( |
| stub_ty, llvm::GlobalVariable::InternalLinkage, stub_name, &module_); |
| stub->setGC(kGCStrategyName); |
| |
| CodegenHelper helper(*this, stub); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| |
| auto class_object = helper.GetParam(kClassObjectOffset); |
| auto type_arguments = helper.GetParam(kTypeArgumentsOffset); |
| |
| auto slow_path = helper.CreateBasicBlock("slow_path"); |
| if (allocation_info->has_fast_path()) { |
| auto fast_path = helper.CreateBasicBlock("fast_path"); |
| builder.CreateBr(fast_path); |
| builder.SetInsertPoint(fast_path); |
| |
| auto thread_object = helper.GetParam(kThreadObjectOffset); |
| auto fast_path_cont = helper.CreateBasicBlock("fast_path_cont"); |
| auto top = |
| helper.EmitLoadFieldRaw(thread_object, kThreadTopOffset, Int64Ty); |
| auto size = GetConstantInt(allocation_info->instance_size()); |
| auto obj_end = builder.CreateAdd(top, size); |
| { |
| auto end = |
| helper.EmitLoadFieldRaw(thread_object, kThreadEndOffset, Int64Ty); |
| auto cmp = builder.CreateICmpSGE(obj_end, end); |
| helper.EmitBrSlowFast(cmp, slow_path, fast_path_cont); |
| } |
| builder.SetInsertPoint(fast_path_cont); |
| |
| helper.EmitStoreFieldRaw(thread_object, kThreadTopOffset, obj_end); |
| |
| auto new_obj = helper.EmitNewObjectAndTag(top); |
| // 64-bit operation is intended to zero out higher bits. |
| helper.EmitStoreField(new_obj, kInstanceTagsOffset, |
| GetConstantInt(allocation_info->tags())); |
| |
| { |
| auto null = helper.EmitNull(); |
| llvm::Value* initial = GetConstantInt(kInstanceNextFieldOffset); |
| |
| auto loop_head = helper.CreateBasicBlock("init_head"); |
| auto loop_body = helper.CreateBasicBlock("init_body"); |
| auto loop_done = helper.CreateBasicBlock("init_done"); |
| |
| auto entry = builder.GetInsertBlock(); |
| builder.CreateBr(loop_head); |
| |
| builder.SetInsertPoint(loop_head); |
| auto counter = builder.CreatePHI(initial->getType(), 2); |
| counter->addIncoming(initial, entry); |
| |
| auto cmp = builder.CreateICmpSGE(counter, size); |
| builder.CreateCondBr(cmp, loop_done, loop_body); |
| |
| builder.SetInsertPoint(loop_body); |
| helper.EmitStoreField(new_obj, counter, null); |
| auto next_counter = builder.CreateAdd(counter, GetConstantInt(kWordSize)); |
| counter->addIncoming(next_counter, loop_body); |
| builder.CreateBr(loop_head); |
| |
| builder.SetInsertPoint(loop_done); |
| } |
| if (allocation_info->is_parameterized()) { |
| helper.EmitStoreField(new_obj, |
| allocation_info->type_arguments_field_offset(), |
| type_arguments); |
| } |
| builder.CreateRet(new_obj); |
| } else { |
| builder.CreateBr(slow_path); |
| } |
| |
| builder.SetInsertPoint(slow_path); |
| |
| // (Class object, Type arguments) |
| auto object = builder.CreateAlloca(ObjectPtrTy); |
| auto native_args = |
| helper.EmitNativeArguments(object, {class_object, type_arguments}); |
| helper.EmitCallToRuntimeTrampoline(RuntimeEntryTag::kAllocateObject, |
| native_args); |
| |
| builder.CreateRet(builder.CreateLoad(object)); |
| |
| class_allocation_stub_cache_.emplace(cid, stub); |
| return stub; |
| } |
| |
| // Statepoint IDs are used when traversing the stack to identify the statepoint. |
| struct AddStatepointIDsToCallSites : public llvm::FunctionPass { |
| static char ID; // Pass identification, replacement for typeid. |
| CodegenModule& cgm; |
| int64_t id = 0; |
| |
| explicit AddStatepointIDsToCallSites(CodegenModule& cgm) |
| : FunctionPass(ID), cgm(cgm) {} |
| |
| void addStatepoints(llvm::BasicBlock& bb, llvm::Function* func) { |
| for (llvm::Instruction& instruction : bb) { |
| if (auto call = llvm::dyn_cast<llvm::CallInst>(&instruction)) { |
| llvm::Function* fn = call->getCalledFunction(); |
| if (fn && fn == cgm.new_object()) { |
| continue; |
| } |
| cgm.AddStatepoint(func); |
| auto attr = llvm::Attribute::get(cgm.GetLLVMContext(), "statepoint-id", |
| std::to_string(id)); |
| id++; |
| call->addAttribute(llvm::AttributeList::FunctionIndex, attr); |
| } |
| } |
| } |
| |
| bool runOnFunction(llvm::Function& func) override { |
| for (llvm::BasicBlock& bb : func) { |
| addStatepoints(bb, &func); |
| } |
| return false; |
| } |
| }; |
| |
| char AddStatepointIDsToCallSites::ID = 0; |
| |
| // The dart_new_object intrinsic is required when creating new GC tracked |
| // pointers, e.g. in the fast path of allocation, because it's illegal |
| // to have IntToPtr to the GC tracked address space, and the |
| // RewriteStatepointsForGC pass will complain if it encounters such |
| // casts. The intrinsic "hides" the address space cast and is lowered |
| // only after the RewriteStatepointsForGC pass. |
| struct RewriteGCIntrinsics : public llvm::FunctionPass { |
| static char ID; // Pass identification, replacement for typeid. |
| CodegenModule& cgm_; |
| |
| RewriteGCIntrinsics(CodegenModule& cgm) : FunctionPass(ID), cgm_(cgm) {} |
| |
| bool tryRewrite(llvm::BasicBlock& bb) { |
| for (llvm::Instruction& instruction : bb) { |
| if (llvm::CallInst* call = llvm::dyn_cast<llvm::CallInst>(&instruction)) { |
| llvm::Function* fn = call->getCalledFunction(); |
| if (fn && fn == cgm_.new_object()) { |
| llvm::IRBuilder<> b(&instruction); |
| auto pointer = call->getArgOperand(0); |
| pointer = b.CreateAddrSpaceCast(pointer, cgm_.ObjectPtrTy); |
| call->replaceAllUsesWith(pointer); |
| call->eraseFromParent(); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool runOnFunction(llvm::Function& func) override { |
| if (cgm_.new_object() != nullptr) { |
| for (llvm::BasicBlock& bb : func) { |
| while (tryRewrite(bb)) { |
| } |
| } |
| } |
| return false; |
| } |
| }; |
| |
| char RewriteGCIntrinsics::ID = 1; |
| |
| void CodegenModule::OptimizeModule() { |
| // TODO(sarkin): |
| auto run_standard_opt = [&](int opt_level, int size_level) { |
| llvm::legacy::PassManager mpm; |
| llvm::legacy::FunctionPassManager fpm(&module_); |
| llvm::PassManagerBuilder builder; |
| builder.OptLevel = opt_level; |
| builder.SizeLevel = size_level; |
| |
| builder.populateFunctionPassManager(fpm); |
| builder.populateModulePassManager(mpm); |
| |
| for (auto& f : module_) { |
| fpm.run(f); |
| } |
| mpm.run(module_); |
| }; |
| |
| auto optimize_before = [&]() { |
| { |
| llvm::legacy::PassManager mpm; |
| mpm.add(llvm::createFunctionInliningPass()); |
| mpm.add(llvm::createGlobalDCEPass()); |
| |
| llvm::legacy::FunctionPassManager fpm(&module_); |
| fpm.add(llvm::createPromoteMemoryToRegisterPass()); |
| |
| fpm.add(llvm::createCFGSimplificationPass()); |
| fpm.add(llvm::createConstantPropagationPass()); |
| fpm.add(llvm::createInstructionCombiningPass()); |
| fpm.add(llvm::createTailCallEliminationPass()); |
| fpm.add(llvm::createSpeculativeExecutionPass()); |
| fpm.add(llvm::createAggressiveDCEPass()); |
| |
| mpm.run(module_); |
| |
| for (auto& f : module_) { |
| fpm.run(f); |
| } |
| } |
| }; |
| |
| optimize_before(); |
| |
| { |
| #ifdef TARGET_X64 |
| llvm::legacy::FunctionPassManager fpm(&module_); |
| fpm.add(new AddStatepointIDsToCallSites(*this)); |
| for (auto& f : module_) { |
| fpm.run(f); |
| } |
| llvm::legacy::PassManager mpm; |
| mpm.add(llvm::createRewriteStatepointsForGCLegacyPass()); |
| mpm.run(module_); |
| #endif |
| } |
| |
| { |
| llvm::legacy::FunctionPassManager fpm(&module_); |
| fpm.add(new RewriteGCIntrinsics(*this)); |
| for (auto& f : module_) { |
| fpm.run(f); |
| } |
| } |
| |
| #if 0 |
| auto optimize_after = [&]() { |
| { |
| llvm::legacy::PassManager mpm; |
| mpm.add(llvm::createFunctionInliningPass()); |
| mpm.add(llvm::createGlobalDCEPass()); |
| |
| llvm::legacy::FunctionPassManager fpm(&module_); |
| fpm.add(llvm::createPromoteMemoryToRegisterPass()); |
| fpm.add(llvm::createCFGSimplificationPass()); |
| fpm.add(llvm::createConstantPropagationPass()); |
| fpm.add(llvm::createLICMPass()); |
| fpm.add(llvm::createGVNPass()); |
| fpm.add(llvm::createEarlyCSEPass()); |
| fpm.add(llvm::createInstructionCombiningPass()); |
| fpm.add(llvm::createTailCallEliminationPass()); |
| fpm.add(llvm::createSpeculativeExecutionPass()); |
| fpm.add(llvm::createAggressiveDCEPass()); |
| |
| mpm.run(module_); |
| |
| for (auto& f : module_) { |
| fpm.run(f); |
| } |
| } |
| }; |
| optimize_after(); |
| #endif |
| |
| // TODO(sarkin): Sometimes O3 does a worse job than optimize_after. |
| run_standard_opt(3, 1); |
| } |
| |
| void CodegenModule::GenerateProgram() { |
| assert(!is_generated_); |
| is_generated_ = true; |
| |
| assert(program_ != nullptr); |
| // Order matters. |
| InitializeGlobals(); |
| GenerateRuntimeFunctionDeclarations(); |
| GenerateSpecialFunctions(); |
| GenerateFunctions(); |
| |
| OptimizeModule(); |
| |
| // Done after optimization, since Statepoint IDs should be |
| // assigned after inlining. |
| CreateStatepointIDToFunctionTable(); |
| } |
| |
| // Signature: Object* (Thread, Object* args...) |
| llvm::FunctionType* CodegenModule::GetStaticFunctionType(size_t num_params) { |
| std::vector<llvm::Type*> params; |
| params.push_back(ThreadObjectTy); |
| for (size_t i = 0; i < num_params; ++i) { |
| params.push_back(ObjectPtrTy); |
| } |
| auto return_ty = ObjectPtrTy; |
| return llvm::FunctionType::get(return_ty, params, false); |
| } |
| |
| llvm::Constant* CodegenModule::GetConstantInt(int64_t val, |
| llvm::IntegerType* ty) const { |
| if (ty == nullptr) { |
| ty = IntTy; |
| } |
| return llvm::ConstantInt::get(ty, val); |
| } |
| |
| void CodegenModule::AddStatepoint(llvm::Function* func) { |
| statepoint_id_to_func_.push_back(func); |
| } |
| |
| void CodegenModule::CreateStatepointIDToFunctionTable() { |
| auto size = statepoint_id_to_func_.size(); |
| auto table_ty = llvm::ArrayType::get(NonGCObjectPtrTy, size); |
| std::vector<llvm::Constant*> entries(size); |
| for (size_t i = 0; i < size; ++i) { |
| entries[i] = llvm::ConstantExpr::getBitCast(statepoint_id_to_func_[i], |
| NonGCObjectPtrTy); |
| } |
| auto initializer = llvm::ConstantArray::get(table_ty, entries); |
| auto table = new llvm::GlobalVariable( |
| GetModule(), table_ty, false, llvm::GlobalVariable::ExternalLinkage, |
| initializer, "_LLVMStatepointIDToFunction"); |
| (void)table; |
| } |
| |
| void CodegenModule::GenerateRuntimeFunctionDeclarations() { |
| std::vector<llvm::Constant*> runtime_functions(NumRuntimeFunctions()); |
| |
| for (size_t i = 0; i < NumRuntimeFunctions(); ++i) { |
| auto it = kRuntimeEntryTagToName.find(static_cast<RuntimeEntryTag>(i)); |
| assert(it != kRuntimeEntryTagToName.end()); |
| auto func = llvm::Function::Create(RuntimeFunctionTy, |
| llvm::GlobalVariable::ExternalLinkage, |
| it->second, &module_); |
| // Pass NativeArguments by value |
| func->addParamAttr(0, llvm::Attribute::AttrKind::ByVal); |
| |
| runtime_functions[i] = |
| llvm::ConstantExpr::getBitCast(func, NonGCObjectPtrTy); |
| } |
| runtime_functions_->setInitializer( |
| llvm::ConstantArray::get(RuntimeFunctionsTy, runtime_functions)); |
| } |
| |
| void CodegenModule::GenerateSpecialFunctions() { |
| // None of these are required for WASM, but generate some of them |
| // to bypass linker errors. |
| #ifndef TARGET_WASM |
| GenerateDartToLLVMTrampoline(); |
| GenerateReadStackPointer(); |
| #endif |
| GenerateFixedParamsTrampolines(); |
| GenerateLLVMToRuntimeTrampoline(); |
| GenerateCreateArrayStub(); |
| GenerateWriteBarrier(); |
| #ifndef TARGET_WASM |
| GenerateGetStackMaps(); |
| #endif |
| } |
| |
| void CodegenModule::GenerateDartToLLVMTrampoline() { |
| llvm::Function* trampoline = llvm::Function::Create( |
| FixedParamTrampolineTy, llvm::GlobalVariable::ExternalLinkage, |
| kDartToLLVMTrampolineName, &module_); |
| |
| trampoline->setGC(kGCStrategyName); |
| |
| CodegenHelper helper(*this, trampoline); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| |
| llvm::Value* func_id = helper.GetParam(0); |
| llvm::Value* thr = helper.GetParam(1);; |
| llvm::Value* args = helper.GetParam(2); |
| |
| auto callee_ty = helper.GetNonGCPointer(FixedParamTrampolineTy); |
| auto callee = helper.EmitLoadFieldFromGlobal(static_trampolines_, func_id, |
| callee_ty, true); |
| |
| builder.CreateRet(builder.CreateCall(callee, {func_id, thr, args})); |
| } |
| |
| void CodegenModule::GenerateLLVMToRuntimeTrampoline() { |
| const int kFunctionIDOffset = 2; |
| const int kNativeArgumentsOffset = 3; |
| auto trampoline_ty = |
| llvm::FunctionType::get(VoidTy, |
| {ThreadObjectTy, /* exit_frame offset */ Int64Ty, |
| FunctionIDTy, NativeArgumentsPtrTy}, |
| false); |
| auto trampoline = llvm::Function::Create( |
| trampoline_ty, llvm::GlobalVariable::InternalLinkage, |
| "_LLVMToRuntimeTrampoline", &module_); |
| // Explicitly set calling convention, as the function is internal and |
| // the calling convention might be optimized to fastcc. |
| trampoline->setCallingConv(llvm::CallingConv::C); |
| // No inling, as inling can break the exit frame setup. |
| trampoline->addAttribute(llvm::AttributeList::FunctionIndex, |
| llvm::Attribute::AttrKind::NoInline); |
| // Do not need to set GC strategy for this function, as it's the "exit frame" |
| // and is never visited during GC. |
| |
| CodegenHelper helper(*this, trampoline); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| |
| // Setup top exit frame. |
| #ifndef TARGET_WASM |
| auto sp = builder.CreateCall(read_sp()); |
| helper.EmitStoreFieldRaw(helper.GetThread(), kThreadTopExitFrameInfoOffset, |
| sp); |
| |
| llvm::Value* func_id = helper.GetParam(kFunctionIDOffset); |
| llvm::Value* native_args = helper.GetParam(kNativeArgumentsOffset); |
| auto callee_ty = helper.GetNonGCPointer(RuntimeFunctionTy); |
| auto callee = helper.EmitLoadFieldFromGlobal(runtime_functions_, func_id, |
| callee_ty, true); |
| auto call = builder.CreateCall(callee, {native_args}); |
| // Pass NativeArguments by value. |
| call->addParamAttr(0, llvm::Attribute::ByVal); |
| #endif |
| builder.CreateRetVoid(); |
| |
| llvm_to_runtime_trampoline_ = trampoline; |
| } |
| |
| void CodegenModule::GenerateReadStackPointer() { |
| // TODO(sarkin): Currently only works for x64. For other architectures, |
| // need to change the name of the register. |
| #ifdef TARGET_X64 |
| auto& func = read_sp_; |
| auto func_ty = llvm::FunctionType::get(IntTy, false); |
| func = llvm::Function::Create(func_ty, llvm::GlobalVariable::InternalLinkage, |
| "", &module_); |
| func->addAttribute(llvm::AttributeList::FunctionIndex, |
| llvm::Attribute::AttrKind::AlwaysInline); |
| |
| CodegenHelper helper(*this, func); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| |
| llvm::MDBuilder md_builder(GetLLVMContext()); |
| auto rsp_node = |
| llvm::MDNode::get(GetLLVMContext(), {md_builder.createString("rsp")}); |
| auto rsp_meta = llvm::MetadataAsValue::get(GetLLVMContext(), rsp_node); |
| auto sp = builder.CreateIntrinsic(llvm::Intrinsic::read_register, {IntTy}, |
| {rsp_meta}); |
| builder.CreateRet(sp); |
| #endif |
| } |
| |
| void CodegenModule::GenerateFixedParamsTrampolines() { |
| std::vector<llvm::Constant*> trampolines(program_->NumFunctions()); |
| for (size_t i = 0; i < program_->NumFunctions(); ++i) { |
| auto t = |
| GetOrCreateFixedParamTrampoline(program_->FunctionAt(i)->num_params()); |
| trampolines[i] = llvm::ConstantExpr::getBitCast(t, NonGCObjectPtrTy); |
| } |
| static_trampolines_->setInitializer( |
| llvm::ConstantArray::get(StaticTrampolinesTy, trampolines)); |
| } |
| |
| void CodegenModule::GenerateCreateArrayStub() { |
| // (Thread object, Length, Element Type) |
| const int kThreadObjectOffset = 0; |
| const int kLengthOffset = 1; |
| const int kElementTypeOffset = 2; |
| std::vector<llvm::Type*> params{ThreadObjectTy, ObjectPtrTy, ObjectPtrTy}; |
| auto stub_ty = llvm::FunctionType::get(ObjectPtrTy, params, false); |
| auto stub_name = "_LLVMCreateArrayStub"; |
| auto& stub = create_array_stub_; |
| stub = llvm::Function::Create( |
| stub_ty, llvm::GlobalVariable::InternalLinkage, stub_name, &module_); |
| stub->setGC(kGCStrategyName); |
| |
| CodegenHelper helper(*this, stub); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| |
| auto length = helper.GetParam(kLengthOffset); |
| auto element_type = helper.GetParam(kElementTypeOffset); |
| |
| auto slow_path = helper.CreateBasicBlock("slow_path"); |
| auto max_len_check = helper.CreateBasicBlock("max_len_check"); |
| auto bump_pointer_carry_check = |
| helper.CreateBasicBlock("bump_pointer_carry_check"); |
| auto fit_in_new_space_check = |
| helper.CreateBasicBlock("fit_in_new_space_check"); |
| auto fast_path = helper.CreateBasicBlock("fast_path"); |
| |
| auto is_not_smi = helper.EmitIsNotSmi(length); |
| helper.EmitBrSlowFast(is_not_smi, slow_path, max_len_check); |
| |
| builder.SetInsertPoint(max_len_check); |
| llvm::Value* int_length = helper.EmitSmiToInt(length); |
| { |
| // Smi tag the constant |
| auto bound = GetConstantInt(kArrayMaxNewSpaceElements << 1); |
| auto cmp = builder.CreateICmpSGT(int_length, bound); |
| helper.EmitBrSlowFast(cmp, slow_path, bump_pointer_carry_check); |
| } |
| |
| auto allocation_size = int_length; |
| builder.SetInsertPoint(bump_pointer_carry_check); |
| { |
| const intptr_t fixed_size_plus_alignment_padding = |
| kRawArraySize + kObjectAlignment - 1; |
| // Platform dependent. Shift by log2(kWordSize) - 1 |
| // since length is Smi tagged. |
| auto log2 = [](auto n) { |
| assert(n); |
| int l = 0; |
| while (!(n & 1)) { |
| l++; |
| n >>= 1; |
| } |
| return l; |
| }; |
| allocation_size = builder.CreateShl(allocation_size, log2(kWordSize) - 1); |
| allocation_size = builder.CreateAdd( |
| allocation_size, GetConstantInt(fixed_size_plus_alignment_padding)); |
| allocation_size = builder.CreateAnd(allocation_size, -kObjectAlignment); |
| } |
| auto thread_object = helper.GetParam(kThreadObjectOffset); |
| auto top = |
| helper.EmitLoadFieldRaw(thread_object, kThreadTopOffset, Int64Ty); |
| llvm::Value* obj_end = nullptr; |
| { |
| auto uadd = builder.CreateIntrinsic(llvm::Intrinsic::ID::uadd_with_overflow, |
| {Int64Ty}, {top, allocation_size}); |
| obj_end = builder.CreateExtractValue(uadd, {0}); |
| auto cmp = builder.CreateExtractValue(uadd, {1}); |
| helper.EmitBrSlowFast(cmp, slow_path, fit_in_new_space_check); |
| } |
| |
| builder.SetInsertPoint(fit_in_new_space_check); |
| { |
| auto end = |
| helper.EmitLoadFieldRaw(thread_object, kThreadEndOffset, Int64Ty); |
| auto cmp = builder.CreateICmpSGE(obj_end, end); |
| helper.EmitBrSlowFast(cmp, slow_path, fast_path); |
| } |
| |
| builder.SetInsertPoint(fast_path); |
| { |
| helper.EmitStoreFieldRaw(thread_object, kThreadTopOffset, obj_end); |
| |
| llvm::Value* tags = nullptr; |
| { |
| auto overflow_bb = helper.CreateBasicBlock("allocation_size_overflow"); |
| auto no_overflow_bb = |
| helper.CreateBasicBlock("allocation_size_no_overflow"); |
| auto fast_path_cont = helper.CreateBasicBlock("fast_path"); |
| |
| auto cmp = builder.CreateICmpSGT(allocation_size, |
| GetConstantInt(kRawObjectMaxSizeTag)); |
| builder.CreateCondBr(cmp, overflow_bb, no_overflow_bb); |
| |
| builder.SetInsertPoint(overflow_bb); |
| auto otag = GetConstantInt(0); |
| builder.CreateBr(fast_path_cont); |
| |
| builder.SetInsertPoint(no_overflow_bb); |
| auto notag = builder.CreateShl( |
| allocation_size, kRawObjectSizeTagPos - kObjectAlignmentLog2); |
| builder.CreateBr(fast_path_cont); |
| |
| builder.SetInsertPoint(fast_path_cont); |
| auto merge_tag = builder.CreatePHI(Int64Ty, 2); |
| merge_tag->addIncoming(otag, overflow_bb); |
| merge_tag->addIncoming(notag, no_overflow_bb); |
| |
| tags = builder.CreateOr(merge_tag, kRawArrayTags); |
| } |
| |
| auto new_obj = helper.EmitNewObjectAndTag(top); |
| helper.EmitStoreField(new_obj, kArrayTagsOffset, tags); |
| helper.EmitStoreField(new_obj, kArrayTypeArgumentsOffset, element_type); |
| helper.EmitStoreField(new_obj, kArrayLengthOffset, length); |
| { |
| auto null = helper.EmitNull(); |
| llvm::Value* initial = GetConstantInt(kRawArraySize); |
| |
| auto loop_head = helper.CreateBasicBlock("init_head"); |
| auto loop_body = helper.CreateBasicBlock("init_body"); |
| auto loop_done = helper.CreateBasicBlock("init_done"); |
| |
| auto entry = builder.GetInsertBlock(); |
| builder.CreateBr(loop_head); |
| |
| builder.SetInsertPoint(loop_head); |
| auto counter = builder.CreatePHI(initial->getType(), 2); |
| counter->addIncoming(initial, entry); |
| |
| auto cmp = builder.CreateICmpSGE(counter, allocation_size); |
| builder.CreateCondBr(cmp, loop_done, loop_body); |
| |
| builder.SetInsertPoint(loop_body); |
| helper.EmitStoreField(new_obj, counter, null); |
| auto next_counter = |
| builder.CreateAdd(counter, GetConstantInt(kWordSize)); |
| counter->addIncoming(next_counter, loop_body); |
| builder.CreateBr(loop_head); |
| |
| builder.SetInsertPoint(loop_done); |
| } |
| |
| builder.CreateRet(new_obj); |
| } |
| |
| builder.SetInsertPoint(slow_path); |
| // (Length, Element Type) |
| auto array = builder.CreateAlloca(ObjectPtrTy); |
| auto native_args = helper.EmitNativeArguments(array, {length, element_type}); |
| helper.EmitCallToRuntimeTrampoline(RuntimeEntryTag::kAllocateArray, |
| native_args); |
| |
| builder.CreateRet(builder.CreateLoad(array)); |
| } |
| |
| void CodegenModule::GenerateWriteBarrier() { |
| // (Thread object, Object, Value) |
| const int kObjectOffset = 1; |
| const int kValueOffset = 2; |
| std::vector<llvm::Type*> params{ThreadObjectTy, ObjectPtrTy, ObjectPtrTy}; |
| auto barrier_ty = llvm::FunctionType::get(VoidTy, params, false); |
| auto barrier_name = "_LLVMWriteBarrier"; |
| auto& wb = write_barrier_; |
| wb = llvm::Function::Create(barrier_ty, llvm::GlobalVariable::InternalLinkage, |
| barrier_name, &module_); |
| wb->setGC(kGCStrategyName); |
| wb->addAttribute(llvm::AttributeList::FunctionIndex, |
| llvm::Attribute::AttrKind::NoInline); |
| |
| CodegenHelper helper(*this, wb); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| |
| #ifdef TARGET_WASM |
| // TODO(sarkin): Hack: |
| // For WASM we don't need a write barrier, but we still generate the |
| // definition to bypass linker errors. |
| builder.CreateRetVoid(); |
| return; |
| #endif |
| |
| auto thread = helper.GetThread(); |
| auto object = helper.GetParam(kObjectOffset); |
| auto value = helper.GetParam(kValueOffset); |
| auto untagged_object = helper.EmitUntagObject(object); |
| auto untagged_value = helper.EmitUntagObject(value); |
| |
| // Assume concurrent marking. |
| auto is_val_new = helper.EmitIsNewObject(value); |
| auto add_to_mark_stack = helper.CreateBasicBlock("add_to_mark_stack"); |
| auto val_is_new = helper.CreateBasicBlock("value_is_new"); |
| helper.EmitBrFastSlow(is_val_new, val_is_new, add_to_mark_stack); |
| |
| builder.SetInsertPoint(val_is_new); |
| { |
| auto tags = |
| helper.EmitFieldPointer(untagged_object, kObjectTagsOffset, Int32Ty); |
| // TODO(sarkin): Might be possible to use weaker ordering. |
| builder.CreateAtomicRMW( |
| llvm::AtomicRMWInst::BinOp::And, tags, |
| GetConstantInt(~(1 << kRawObjectOldAndNotRememberedBit), Int32Ty), |
| llvm::AtomicOrdering::SequentiallyConsistent); |
| } |
| |
| // Load the StoreBuffer block out of the thread. Then load top_ out of the |
| // StoreBufferBlock and add the address to the pointers_. |
| auto store_buffer_block = helper.EmitLoadFieldRaw( |
| thread, kThreadStoreBufferBlockOffset, NonGCObjectPtrTy); |
| auto store_buffer_top = helper.EmitLoadFieldRaw( |
| store_buffer_block, kStoreBufferBlockTopOffset, Int32Ty); |
| |
| { |
| auto offset = builder.CreateZExt(store_buffer_top, Int64Ty); |
| offset = builder.CreateShl(offset, kWordSizeLog2); |
| offset = builder.CreateAdd(offset, |
| GetConstantInt(kStoreBufferBlockPointersOffset)); |
| helper.EmitStoreFieldRaw(store_buffer_block, offset, object); |
| } |
| |
| auto store_buffer_overflow = helper.CreateBasicBlock("store_buffer_overflow"); |
| auto store_buffer_no_overflow = |
| helper.CreateBasicBlock("store_buffer_no_overflow"); |
| |
| { |
| auto inc_top = |
| builder.CreateAdd(store_buffer_top, GetConstantInt(1, Int32Ty)); |
| inc_top = builder.CreateTrunc(inc_top, Int32Ty); |
| helper.EmitStoreFieldRaw(store_buffer_block, kStoreBufferBlockTopOffset, |
| inc_top); |
| auto cmp = builder.CreateICmpEQ( |
| inc_top, GetConstantInt(kStoreBufferBlockSize, Int32Ty)); |
| helper.EmitBrSlowFast(cmp, store_buffer_overflow, store_buffer_no_overflow); |
| } |
| |
| builder.SetInsertPoint(store_buffer_no_overflow); |
| builder.CreateRetVoid(); |
| |
| builder.SetInsertPoint(store_buffer_overflow); |
| // Call to runtime. |
| { |
| auto func_ty = llvm::FunctionType::get(VoidTy, {NonGCObjectPtrTy}, false); |
| auto func = llvm::Function::Create( |
| func_ty, llvm::GlobalVariable::LinkageTypes::ExternalLinkage, |
| "StoreBufferBlockProcess", &module_); |
| builder.CreateCall(func, {thread}); |
| } |
| builder.CreateRetVoid(); |
| |
| auto already_marked = helper.CreateBasicBlock("already_marked"); |
| auto try_mark = helper.CreateBasicBlock("try_mark"); |
| auto mark_success = helper.CreateBasicBlock("mark_success"); |
| |
| builder.SetInsertPoint(already_marked); |
| builder.CreateRetVoid(); |
| |
| builder.SetInsertPoint(add_to_mark_stack); |
| auto old_tags = helper.EmitLoadField(value, kObjectTagsOffset, Int32Ty); |
| { |
| auto cmp = helper.EmitTestBit(old_tags, kRawObjectOldAndNotMarkedBit); |
| builder.CreateCondBr(cmp, already_marked, try_mark); |
| } |
| |
| builder.SetInsertPoint(try_mark); |
| { |
| auto tags = |
| builder.CreateAnd(old_tags, ~(1 << kRawObjectOldAndNotMarkedBit)); |
| auto tags_ptr = |
| helper.EmitFieldPointer(untagged_value, kObjectTagsOffset, Int32Ty); |
| // TODO(sarkin): Might be possible to use weaker ordering. |
| auto xchg = builder.CreateAtomicCmpXchg( |
| tags_ptr, old_tags, tags, llvm::AtomicOrdering::SequentiallyConsistent, |
| llvm::AtomicOrdering::SequentiallyConsistent); |
| auto success = builder.CreateExtractValue(xchg, {1}); |
| builder.CreateCondBr(success, mark_success, add_to_mark_stack); |
| } |
| |
| builder.SetInsertPoint(mark_success); |
| // Load the MarkingStack block out of the thread. Then load top_ out of the |
| // MarkingStackBlock and add the address to the pointers_. |
| auto marking_stack_block = helper.EmitLoadFieldRaw( |
| thread, kThreadMarkingStackBlockOffset, NonGCObjectPtrTy); |
| auto marking_stack_top = helper.EmitLoadFieldRaw( |
| marking_stack_block, kMarkingStackBlockTopOffset, Int32Ty); |
| |
| { |
| auto offset = builder.CreateZExt(marking_stack_top, Int64Ty); |
| offset = builder.CreateShl(offset, kWordSizeLog2); |
| offset = builder.CreateAdd( |
| offset, GetConstantInt(kMarkingStackBlockPointersOffset)); |
| helper.EmitStoreFieldRaw(marking_stack_block, offset, value); |
| } |
| |
| auto marking_stack_overflow = |
| helper.CreateBasicBlock("marking_stack_overflow"); |
| auto marking_stack_no_overflow = |
| helper.CreateBasicBlock("marking_stack_no_overflow"); |
| |
| { |
| auto inc_top = |
| builder.CreateAdd(marking_stack_top, GetConstantInt(1, Int32Ty)); |
| inc_top = builder.CreateTrunc(inc_top, Int32Ty); |
| helper.EmitStoreFieldRaw(marking_stack_block, kMarkingStackBlockTopOffset, |
| inc_top); |
| auto cmp = builder.CreateICmpEQ( |
| inc_top, GetConstantInt(kMarkingStackBlockSize, Int32Ty)); |
| helper.EmitBrSlowFast(cmp, marking_stack_overflow, |
| marking_stack_no_overflow); |
| } |
| |
| builder.SetInsertPoint(marking_stack_no_overflow); |
| builder.CreateRetVoid(); |
| |
| builder.SetInsertPoint(marking_stack_overflow); |
| // Call to runtime. |
| { |
| auto func_ty = llvm::FunctionType::get(VoidTy, {NonGCObjectPtrTy}, false); |
| auto func = llvm::Function::Create( |
| func_ty, llvm::GlobalVariable::LinkageTypes::ExternalLinkage, |
| "MarkingStackBlockProcess", &module_); |
| builder.CreateCall(func, {thread}); |
| } |
| builder.CreateRetVoid(); |
| } |
| |
| void CodegenModule::GenerateGetStackMaps() { |
| // Make the __LLVM_StackMaps section external. |
| auto stack_maps = new llvm::GlobalVariable( |
| GetModule(), Int8Ty, false, llvm::GlobalVariable::ExternalLinkage, |
| nullptr, "__LLVM_StackMaps"); |
| |
| auto func_ty = llvm::FunctionType::get(NonGCObjectPtrTy, {}, false); |
| auto func = |
| llvm::Function::Create(func_ty, llvm::GlobalVariable::ExternalLinkage, |
| "_LLVMGetStackMaps", &module_); |
| |
| CodegenHelper helper(*this, func); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| builder.CreateRet(stack_maps); |
| } |
| |
| llvm::Function* CodegenModule::GetOrCreateFixedParamTrampoline( |
| size_t num_params) { |
| auto it = fixed_params_trampolines_.find(num_params); |
| if (it != fixed_params_trampolines_.end()) { |
| return it->second; |
| } |
| auto trampoline = llvm::Function::Create( |
| FixedParamTrampolineTy, llvm::GlobalVariable::InternalLinkage, |
| "_FixedParamTrampoline$" + std::to_string(num_params), &module_); |
| |
| trampoline->setGC(kGCStrategyName); |
| |
| CodegenHelper helper(*this, trampoline); |
| auto bb = helper.CreateBasicBlock("entry"); |
| auto& builder = helper.builder(); |
| builder.SetInsertPoint(bb); |
| |
| llvm::Value* func_id = helper.GetParam(0); |
| llvm::Value* thr = helper.GetParam(1); |
| llvm::Value* args = helper.GetParam(2); |
| |
| std::vector<llvm::Value*> extracted_args{thr}; |
| for (size_t i = 0; i < num_params; ++i) { |
| // ======= |
| // | first | || |
| // |-------| || |
| // | | || |
| // |-------| \/ |
| // | | |
| // |-------| |
| // | | |
| // |-------| |
| // | last | |
| // ------- <--- args |
| std::vector<llvm::Value*> idx{GetConstantInt(num_params - i)}; |
| auto argi = builder.CreateLoad(builder.CreateGEP(ObjectPtrTy, args, idx)); |
| extracted_args.push_back(argi); |
| } |
| |
| auto callee_ty = helper.GetNonGCPointer(GetStaticFunctionType(num_params)); |
| auto callee = |
| helper.EmitLoadFieldFromGlobal(function_pool_, func_id, callee_ty, true); |
| builder.CreateRet(builder.CreateCall(callee, extracted_args)); |
| |
| fixed_params_trampolines_.emplace(num_params, trampoline); |
| return trampoline; |
| } |
| |
| void CodegenModule::GenerateFunctions() { |
| // Declarations |
| std::vector<llvm::Constant*> function_pool_entries(program_->NumFunctions()); |
| for (size_t i = 0; i < program_->NumFunctions(); ++i) { |
| auto func = program_->FunctionAt(i); |
| |
| auto linkage = llvm::GlobalVariable::InternalLinkage; |
| #ifdef TARGET_WASM |
| // TODO(sarkin): Hack: |
| // We call the functions directly on WASM. |
| // Side-note: pretty much anything with ifdef Target_WASM is a hack :) |
| linkage = llvm::GlobalVariable::ExternalLinkage; |
| #endif |
| |
| auto llvm_func = |
| llvm::Function::Create(GetStaticFunctionType(func->num_params()), |
| linkage, "LLVM_" + func->name(), &module_); |
| if (func->can_trigger_gc()) { |
| llvm_func->setGC(kGCStrategyName); |
| } |
| |
| function_pool_entries[func->id()] = |
| llvm::ConstantExpr::getBitCast(llvm_func, NonGCObjectPtrTy); |
| |
| function_by_id_.emplace(func->id(), llvm_func); |
| } |
| |
| function_pool_->setInitializer( |
| llvm::ConstantArray::get(FunctionPoolTy, function_pool_entries)); |
| |
| std::vector<CodegenFunction> cgfs; |
| for (size_t i = 0; i < program_->NumFunctions(); ++i) { |
| auto func = program_->FunctionAt(i); |
| |
| auto llvm_func = GetFunctionByID(func->id()); |
| cgfs.emplace_back(*this, dispatch_table_.get(), func, llvm_func); |
| cgfs.back().GenerateCode(); |
| } |
| |
| // TODO(sarkin): Should probably refactor this out. |
| dispatch_table_->BuildTable(); |
| auto real_dispatch_table = llvm::ConstantExpr::getBitCast( |
| dispatch_table_->dispatch_table(), |
| llvm::PointerType::get(DispatchTableTy, kNonGCAddressSpace)); |
| temp_dispatch_table_->replaceAllUsesWith(real_dispatch_table); |
| |
| for (auto& cgf : cgfs) { |
| cgf.PatchDynamicCalls(); |
| } |
| } |
| |
| void CodegenModule::InitializeGlobals() { |
| { |
| std::vector<llvm::Constant*> init( |
| program_->num_constants(), llvm::ConstantPointerNull::get(ObjectPtrTy)); |
| constant_pool_->setInitializer( |
| llvm::ConstantArray::get(ConstantPoolTy, init)); |
| } |
| |
| { |
| std::vector<llvm::Constant*> init( |
| program_->num_dart_functions(), |
| llvm::ConstantPointerNull::get(ObjectPtrTy)); |
| dart_function_pool_->setInitializer( |
| llvm::ConstantArray::get(DartFunctionPoolTy, init)); |
| } |
| |
| { |
| llvm_to_dart_trampoline_->setInitializer( |
| llvm::ConstantPointerNull::get(NonGCObjectPtrTy)); |
| } |
| } |
| |
| const std::string CodegenModule::kGCStrategyName = "statepoint-example"; |
| |
| const std::string CodegenModule::kGlobalConstantPoolName = |
| "_GlobalConstantPool"; |
| const std::string CodegenModule::kGlobalFunctionPoolName = |
| "_GlobalFunctionPool"; |
| const std::string CodegenModule::kGlobalDartFunctionPoolName = |
| "_GlobalDartFunctionPool"; |
| const std::string CodegenModule::kGlobalLLVMToDartTrampolineName = |
| "_GlobalLLVMToDartTrampoline"; |
| const std::string CodegenModule::kDartToLLVMTrampolineName = |
| "_DartToLLVMTrampoline"; |
| |
| InstructionInputExtractor::InstructionInputExtractor(const ILInstruction* instr) |
| : instr_(instr) {} |
| |
| std::string InstructionInputExtractor::NextInput() { |
| return instr_->InputAt(idx_++); |
| } |
| |
| intptr_t InstructionInputExtractor::NextInputAsIntPtr() { |
| return stoll(NextInput()); |
| } |
| |
| int64_t InstructionInputExtractor::NextInputAsInt64() { |
| return stoll(NextInput()); |
| } |
| |
| template <typename T> |
| T InstructionInputExtractor::NextInputAsEnum() { |
| return static_cast<T>(NextInputAsIntPtr()); |
| } |
| |
| size_t InstructionInputExtractor::NumInputs() const { |
| return instr_->NumInputs(); |
| } |
| |
| size_t InstructionInputExtractor::NumInputsLeft() const { |
| return NumInputs() - idx_; |
| } |
| |
| CodegenHelper::CodegenHelper(CodegenModule& cgm, llvm::Function* function) |
| : CodegenTypeCache(cgm), |
| DartVMConstants(cgm), |
| cgm_(cgm), |
| function_(function), |
| builder_(std::make_unique<llvm::IRBuilder<>>(cgm_.GetLLVMContext())) {} |
| |
| llvm::Constant* CodegenHelper::GetConstantInt(int64_t val, |
| llvm::IntegerType* ty) const { |
| if (ty == nullptr) { |
| ty = IntTy; |
| } |
| return llvm::ConstantInt::get(ty, val); |
| } |
| |
| void CodegenHelper::SetInvariantLoad(llvm::LoadInst* val) { |
| val->setMetadata(llvm::LLVMContext::MD_invariant_load, |
| llvm::MDNode::get(cgm_.GetLLVMContext(), llvm::None)); |
| } |
| |
| llvm::PointerType* CodegenHelper::GetNonGCPointer(llvm::Type* ty) { |
| return llvm::PointerType::get(ty, CodegenModule::kNonGCAddressSpace); |
| } |
| |
| llvm::Value* CodegenHelper::EmitLoadFieldFromGlobal(llvm::Value* global, |
| size_t offset, |
| llvm::Type* type, |
| bool invariant) { |
| return EmitLoadFieldFromGlobal(global, GetConstantInt(offset), type, |
| invariant); |
| } |
| |
| llvm::Value* CodegenHelper::EmitLoadFieldFromGlobal(llvm::Value* global, |
| llvm::Value* offset, |
| llvm::Type* type, |
| bool invariant) { |
| auto field_ptr_ty = GetNonGCPointer(type); |
| |
| auto field_ptr = builder_->CreateGEP(global, {GetConstantInt(0), offset}); |
| field_ptr = builder_->CreatePointerCast(field_ptr, field_ptr_ty); |
| auto load = builder_->CreateLoad(field_ptr); |
| if (invariant) { |
| SetInvariantLoad(load); |
| } |
| return load; |
| } |
| |
| llvm::Value* CodegenHelper::EmitBool(llvm::Value* val) { |
| assert(val != nullptr); |
| assert(val->getType() == Int1Ty); |
| |
| val = builder_->CreateZExt(val, Int64Ty); |
| auto offset = builder_->CreateAdd(val, GetConstantInt(cgm_.FalseOffset())); |
| return EmitLoadFieldFromGlobal(cgm_.constant_pool(), offset, ObjectPtrTy, true); |
| } |
| |
| llvm::Value* CodegenHelper::EmitFalse() { |
| return EmitBool(GetConstantInt(0, Int1Ty)); |
| } |
| |
| llvm::Value* CodegenHelper::EmitTrue() { |
| return EmitBool(GetConstantInt(1, Int1Ty)); |
| } |
| |
| llvm::Value* CodegenHelper::EmitNull() { |
| auto offset = GetConstantInt(cgm_.NullOffset()); |
| return EmitLoadFieldFromGlobal(cgm_.constant_pool(), offset, ObjectPtrTy, true); |
| } |
| |
| llvm::Value* CodegenHelper::EmitTestBit(llvm::Value* val, int bit_num) { |
| assert(bit_num < 30); |
| auto bit = builder_->CreateAnd(val, (1 << bit_num)); |
| auto ty = val->getType(); |
| assert(ty->isIntegerTy()); |
| return builder_->CreateICmpNE( |
| bit, GetConstantInt(0, llvm::cast<llvm::IntegerType>(ty))); |
| } |
| |
| llvm::Value* CodegenHelper::EmitIsNewObject(llvm::Value* val) { |
| val = EmitSmiToInt(val); |
| return EmitTestBit(val, kNewObjectBitPosition); |
| } |
| |
| void CodegenHelper::EmitBrSlowFast(llvm::Value* cond, |
| llvm::BasicBlock* slow_path, |
| llvm::BasicBlock* fast_path) { |
| llvm::MDBuilder md_builder(cgm_.GetLLVMContext()); |
| auto assume_fast = md_builder.createBranchWeights(1, 100); |
| builder_->CreateCondBr(cond, slow_path, fast_path, assume_fast); |
| } |
| |
| void CodegenHelper::EmitBrFastSlow(llvm::Value* cond, |
| llvm::BasicBlock* fast_path, |
| llvm::BasicBlock* slow_path) { |
| llvm::MDBuilder md_builder(cgm_.GetLLVMContext()); |
| auto assume_fast = md_builder.createBranchWeights(100, 1); |
| builder_->CreateCondBr(cond, fast_path, slow_path, assume_fast); |
| } |
| |
| llvm::Value* CodegenHelper::EmitNegateBool(llvm::Value* val) { |
| auto cmp = builder_->CreateICmpEQ(val, EmitFalse()); |
| return EmitBool(cmp); |
| } |
| |
| llvm::Value* CodegenHelper::EmitSmiToInt(llvm::Value* val) { |
| assert(val->getType() == ObjectPtrTy); |
| val = builder_->CreateAddrSpaceCast(val, NonGCObjectPtrTy); |
| return builder_->CreatePtrToInt(val, Int64Ty); |
| } |
| |
| llvm::Value* CodegenHelper::EmitIsAnyNotSmi(llvm::Value* a, llvm::Value* b) { |
| assert(a->getType() == ObjectPtrTy); |
| assert(b->getType() == ObjectPtrTy); |
| |
| a = EmitSmiToInt(a); |
| b = EmitSmiToInt(b); |
| |
| auto ab_or = builder_->CreateOr(a, b); |
| return builder_->CreateTrunc(ab_or, Int1Ty); |
| } |
| |
| llvm::Value* CodegenHelper::EmitIsNotSmi(llvm::Value* val) { |
| assert(val->getType() == ObjectPtrTy); |
| // Checking for smi should be safe to do even in the presence |
| // of a moving GC / compiler optimizations. |
| val = EmitSmiToInt(val); |
| return builder_->CreateTrunc(val, Int1Ty); |
| } |
| |
| llvm::Value* CodegenHelper::EmitSmiTag(llvm::Value* val) { |
| assert(val->getType() == Int64Ty); |
| val = builder_->CreateAdd(val, val); |
| return EmitNewObject(val); |
| } |
| |
| llvm::Value* CodegenHelper::EmitUntagSmi(llvm::Value* val) { |
| val = EmitSmiToInt(val); |
| return builder_->CreateAShr(val, 1); |
| } |
| |
| llvm::Value* CodegenHelper::EmitUntagObject(llvm::Value* obj) { |
| return EmitFieldPointer(obj, -1, Int8Ty); |
| } |
| |
| llvm::Value* CodegenHelper::EmitTagObject(llvm::Value* obj) { |
| return EmitFieldPointer(obj, 1, Int8Ty); |
| } |
| |
| llvm::Value* CodegenHelper::EmitFieldPointer(llvm::Value* obj, |
| intptr_t offset, |
| llvm::Type* ty) { |
| return EmitFieldPointer(obj, GetConstantInt(offset), ty); |
| } |
| |
| llvm::Value* CodegenHelper::EmitFieldPointer(llvm::Value* obj, |
| llvm::Value* offset, |
| llvm::Type* ty) { |
| auto object_ty = llvm::cast<llvm::PointerType>(obj->getType()); |
| auto field_ptr_ty = llvm::PointerType::get(ty, object_ty->getAddressSpace()); |
| auto field_ptr = builder_->CreateGEP(obj, offset); |
| return builder_->CreatePointerCast(field_ptr, field_ptr_ty); |
| } |
| |
| llvm::Function* CodegenModule::GetOrCreateNewObject() { |
| if (new_object_ == nullptr) { |
| auto ty = llvm::FunctionType::get(ObjectPtrTy, {NonGCObjectPtrTy}, false); |
| new_object_ = llvm::Function::Create(ty, llvm::Function::ExternalLinkage, "__to_ptr", &module_); |
| new_object_->addFnAttr("gc-leaf-function"); |
| } |
| return new_object_; |
| } |
| |
| llvm::Value* CodegenHelper::EmitNewObject(llvm::Value* ptr) { |
| if (ptr->getType() != NonGCObjectPtrTy) { |
| assert(ptr->getType() == Int64Ty); |
| ptr = builder_->CreateIntToPtr(ptr, NonGCObjectPtrTy); |
| } |
| |
| // TODO(sarkin): Currently the intrinsic takes an Int64Ty, which doesn't |
| // work for 32-bit architectures. Add different intrinsics for different |
| // pointer widths in LLVM, something like the uadd_with_overflow intrinsic |
| // does. |
| #ifdef TARGET_X64 |
| return builder_->CreateCall(cgm_.GetOrCreateNewObject(), {ptr}); |
| #endif |
| return ptr; |
| } |
| |
| llvm::Value* CodegenHelper::EmitNewObjectAndTag(llvm::Value* ptr) { |
| assert(ptr->getType() == IntTy); |
| // Tag. |
| ptr = builder_->CreateAdd(ptr, GetConstantInt(1)); |
| return EmitNewObject(ptr); |
| } |
| |
| llvm::Value* CodegenHelper::EmitBoxAllocation(size_t cid) { |
| auto slow_path = CreateBasicBlock("box_slow_path"); |
| auto fast_path = CreateBasicBlock("box_fast_path"); |
| auto merge_path = CreateBasicBlock("box_merge_path"); |
| |
| auto allocation_info = cgm_.GetClassAllocationInfo(cid); |
| auto thread_object = GetThread(); |
| auto top = EmitLoadFieldRaw(thread_object, kThreadTopOffset, Int64Ty); |
| auto size = GetConstantInt(allocation_info->instance_size()); |
| auto obj_end = builder_->CreateAdd(top, size); |
| { |
| auto end = EmitLoadFieldRaw(thread_object, kThreadEndOffset, Int64Ty); |
| auto cmp = builder_->CreateICmpSGE(obj_end, end); |
| EmitBrSlowFast(cmp, slow_path, fast_path); |
| } |
| |
| builder_->SetInsertPoint(fast_path); |
| EmitStoreFieldRaw(thread_object, kThreadTopOffset, obj_end); |
| auto new_obj = EmitNewObjectAndTag(top); |
| // 64-bit operation is intended to zero out higher bits. |
| EmitStoreField(new_obj, kInstanceTagsOffset, |
| GetConstantInt(allocation_info->tags())); |
| builder_->CreateBr(merge_path); |
| |
| builder_->SetInsertPoint(slow_path); |
| auto stub = cgm_.GetOrCreateClassAllocationStub(cid); |
| assert(allocation_info->class_object() >= 0); |
| auto cls = EmitLoadConstant(allocation_info->class_object()); |
| auto slow_new_obj = |
| builder_->CreateCall(stub, {GetThread(), cls, EmitNull()}); |
| builder_->CreateBr(merge_path); |
| |
| builder_->SetInsertPoint(merge_path); |
| auto merge_obj = builder_->CreatePHI(ObjectPtrTy, 2); |
| merge_obj->addIncoming(new_obj, fast_path); |
| merge_obj->addIncoming(slow_new_obj, slow_path); |
| |
| return merge_obj; |
| } |
| |
| llvm::Value* CodegenHelper::EmitStackArray(intptr_t size) { |
| auto arg_array_ty = llvm::ArrayType::get(ObjectPtrTy, size); |
| return builder_->CreateAlloca(arg_array_ty); |
| } |
| |
| void CodegenHelper::EmitStoreInArray(llvm::Value* arr, |
| intptr_t i, |
| llvm::Value* val) { |
| auto argi = builder_->CreateGEP(arr, {GetConstantInt(0), GetConstantInt(i)}); |
| builder_->CreateStore(val, argi); |
| } |
| |
| llvm::Value* CodegenHelper::EmitStackArrayToPtr(llvm::Value* arr) { |
| return builder_->CreateBitCast(arr, StackArrayTy); |
| } |
| |
| llvm::Value* CodegenHelper::EmitNativeArguments( |
| llvm::Value* ret_val, |
| std::vector<llvm::Value*> args) { |
| llvm::Value* args_array = nullptr; |
| if (!args.empty()) { |
| args_array = EmitStackArray(args.size()); |
| for (size_t i = 0; i < args.size(); ++i) { |
| EmitStoreInArray(args_array, i, args[i]); |
| } |
| args_array = EmitStackArrayToPtr(args_array); |
| } |
| size_t argc_tags = args.size(); |
| assert(ArgcTagBits::kReverseArgOrderBit < 64); |
| argc_tags |= (1ll << ArgcTagBits::kReverseArgOrderBit); |
| return EmitNativeArguments(argc_tags, args_array, ret_val); |
| } |
| |
| llvm::Value* CodegenHelper::EmitNativeArguments(intptr_t argc_tag, |
| llvm::Value* argv, |
| llvm::Value* ret_val) { |
| auto native_args = builder_->CreateAlloca(NativeArgumentsTy); |
| |
| auto get_field = [&](int index) { |
| std::vector<llvm::Value*> idx{GetConstantInt(/* GEP specific */ 0, Int32Ty), |
| GetConstantInt(index, Int32Ty)}; |
| return builder_->CreateGEP(NativeArgumentsTy, native_args, idx); |
| }; |
| |
| auto thread_field = get_field(0); |
| builder_->CreateStore(GetThread(), thread_field); |
| auto argc_field = get_field(1); |
| builder_->CreateStore(GetConstantInt(argc_tag), argc_field); |
| if (argv != nullptr) { |
| auto argv_field = get_field(2); |
| builder_->CreateStore(argv, argv_field); |
| } |
| if (ret_val != nullptr) { |
| auto ret_val_field = get_field(3); |
| builder_->CreateStore(ret_val, ret_val_field); |
| } |
| |
| return native_args; |
| } |
| |
| void CodegenHelper::EmitCallToRuntimeTrampoline( |
| CodegenModule::RuntimeEntryTag tag, |
| llvm::Value* native_args) { |
| auto call = builder_->CreateCall( |
| cgm_.llvm_to_runtime_trampoline(), |
| {GetThread(), GetConstantInt(kThreadTopExitFrameInfoOffset), |
| GetConstantInt(tag), native_args}); |
| call->setCallingConv(llvm::CallingConv::C); |
| } |
| |
| llvm::Value* CodegenHelper::EmitLoadConstant(intptr_t const_id) { |
| std::vector<llvm::Value*> idx{GetConstantInt(/* GEP specific */ 0), |
| GetConstantInt(const_id)}; |
| |
| // Invariant if loading False, True or Null. |
| bool invariant = (const_id <= 2); |
| return EmitLoadFieldFromGlobal(cgm_.constant_pool(), const_id, ObjectPtrTy, |
| invariant); |
| } |
| |
| llvm::Value* CodegenHelper::EmitLoadField(llvm::Value* object, |
| intptr_t offset, |
| llvm::Type* type) { |
| return EmitLoadFieldRaw(EmitUntagObject(object), offset, type); |
| } |
| |
| void CodegenHelper::EmitStoreField(llvm::Value* object, |
| intptr_t offset, |
| llvm::Value* val) { |
| EmitStoreFieldRaw(EmitUntagObject(object), offset, val); |
| } |
| |
| llvm::Value* CodegenHelper::EmitLoadField(llvm::Value* object, |
| llvm::Value* offset, |
| llvm::Type* type) { |
| return EmitLoadFieldRaw(EmitUntagObject(object), offset, type); |
| } |
| |
| void CodegenHelper::EmitStoreField(llvm::Value* object, |
| llvm::Value* offset, |
| llvm::Value* val) { |
| EmitStoreFieldRaw(EmitUntagObject(object), offset, val); |
| } |
| |
| llvm::Value* CodegenHelper::EmitLoadFieldRaw(llvm::Value* object, |
| intptr_t offset, |
| llvm::Type* type) { |
| return EmitLoadFieldRaw(object, GetConstantInt(offset), type); |
| } |
| |
| void CodegenHelper::EmitStoreFieldRaw(llvm::Value* object, |
| intptr_t offset, |
| llvm::Value* val) { |
| EmitStoreFieldRaw(object, GetConstantInt(offset), val); |
| } |
| |
| llvm::Value* CodegenHelper::EmitLoadFieldRaw(llvm::Value* object, |
| llvm::Value* offset, |
| llvm::Type* type) { |
| auto field_ptr = EmitFieldPointer(object, offset, type); |
| return builder_->CreateLoad(field_ptr); |
| } |
| |
| void CodegenHelper::EmitStoreFieldRaw(llvm::Value* object, |
| llvm::Value* offset, |
| llvm::Value* val) { |
| auto field_ptr = EmitFieldPointer(object, offset, val->getType()); |
| builder_->CreateStore(val, field_ptr); |
| } |
| |
| void CodegenHelper::EmitStoreInObject(llvm::Value* object, |
| intptr_t offset, |
| llvm::Value* val, |
| bool can_value_be_smi) { |
| EmitStoreInObject(object, GetConstantInt(offset), val, can_value_be_smi); |
| } |
| |
| void CodegenHelper::EmitStoreInObject(llvm::Value* object, |
| llvm::Value* offset, |
| llvm::Value* val, |
| bool can_value_be_smi) { |
| EmitStoreField(object, offset, val); |
| |
| auto exit_block = CreateBasicBlock("store_in_object_exit"); |
| if (can_value_be_smi) { |
| auto val_is_not_smi = CreateBasicBlock("val_is_not_smi"); |
| auto cmp = EmitIsNotSmi(val); |
| builder_->CreateCondBr(cmp, val_is_not_smi, exit_block); |
| |
| builder_->SetInsertPoint(val_is_not_smi); |
| } |
| |
| auto object_tags = EmitLoadField(object, kObjectTagsOffset, Int8Ty); |
| object_tags = |
| builder_->CreateAShr(object_tags, kRawObjectBarrierOverlapShift); |
| auto wb_mask = |
| EmitLoadFieldRaw(GetThread(), kThreadWriteBarrierMaskOffset, Int8Ty); |
| object_tags = builder_->CreateAnd(object_tags, wb_mask); |
| auto val_tags = EmitLoadField(val, kObjectTagsOffset, Int8Ty); |
| |
| auto wb_block = CreateBasicBlock("wb"); |
| { |
| auto and_tags = builder_->CreateAnd(object_tags, val_tags); |
| auto cmp = builder_->CreateICmpEQ(and_tags, GetConstantInt(0, Int8Ty)); |
| builder_->CreateCondBr(cmp, exit_block, wb_block); |
| } |
| |
| builder_->SetInsertPoint(wb_block); |
| builder_->CreateCall(cgm_.write_barrier(), {GetThread(), object, val}); |
| builder_->CreateBr(exit_block); |
| |
| builder_->SetInsertPoint(exit_block); |
| } |
| |
| llvm::Value* CodegenHelper::GetThread() const { |
| // Thread is always the first parameter. |
| return GetParam(CodegenFunction::kThreadOffset); |
| } |
| |
| llvm::Value* CodegenHelper::GetParam(size_t i) const { |
| assert(function_ != nullptr); |
| assert(i < function_->arg_size()); |
| |
| return function_->arg_begin() + i; |
| } |
| |
| llvm::BasicBlock* CodegenHelper::CreateBasicBlock(intptr_t id) { |
| return CreateBasicBlock(std::to_string(id)); |
| } |
| |
| llvm::BasicBlock* CodegenHelper::CreateBasicBlock(std::string id) { |
| return llvm::BasicBlock::Create(cgm_.GetLLVMContext(), id, function_); |
| } |
| |
| CodegenFunction::CodegenFunction(CodegenModule& cgm, |
| DispatchTable* dispatch_table, |
| const DartFunction* func, |
| llvm::Function* llvm_func) |
| : CodegenTypeCache(cgm), |
| DartVMConstants(cgm), |
| cgm_(cgm), |
| dispatch_table_(dispatch_table), |
| func_(func), |
| helper_(cgm, llvm_func), |
| builder_(helper_.builder()) {} |
| |
| llvm::Constant* CodegenFunction::GetConstantInt(int64_t val, |
| llvm::IntegerType* ty) const { |
| return helper_.GetConstantInt(val, ty); |
| } |
| |
| void CodegenFunction::GenerateCode() { |
| assert(!is_generated_); |
| is_generated_ = true; |
| |
| for (size_t i = 0; i < func_->NumBasicBlocks(); ++i) { |
| auto bb = func_->BasicBlockAt(i); |
| blocks_.emplace(bb->id(), helper_.CreateBasicBlock(bb->id())); |
| } |
| |
| // Each Dart basic block might in turn be converted into many |
| // LLVM basic blocks. The LLVM basic blocks corresponding |
| // to one Dart basic block form a graph with a single entry |
| // and a single exit. The unique entry is the basic block |
| // created above (stored in blocks_), while the unique exits |
| // are stored in final_blocks_. |
| for (size_t i = 0; i < func_->NumBasicBlocks(); ++i) { |
| auto bb = func_->BasicBlockAt(i); |
| GenerateBasicBlock(bb, GetBlock(bb->id())); |
| final_blocks_[bb->id()] = builder_.GetInsertBlock(); |
| } |
| |
| // Assume there's one function entry block and jump into |
| // it from the entry block. Furthermore, assume the first |
| // block is the entry and the second is the only function |
| // entry block, which *should* be the case since blocks |
| // are in reverse postorder. |
| assert(func_->NumBasicBlocks() >= 2); |
| const int kEntryBlock = func_->BasicBlockAt(0)->id(); |
| const int kFunctionEntryBlock = func_->BasicBlockAt(1)->id(); |
| builder_.SetInsertPoint(GetBlock(kEntryBlock)); |
| builder_.CreateBr(GetBlock(kFunctionEntryBlock)); |
| |
| ProcessPhiIncomingValues(); |
| } |
| |
| void CodegenFunction::PatchDynamicCalls() { |
| assert(is_generated_ && !is_patched_); |
| is_patched_ = true; |
| for (const auto& pp : dynamic_calls_to_patch_) { |
| auto new_offset = GetConstantInt(dispatch_table_->GetOffset(pp.first)); |
| pp.second->replaceAllUsesWith(new_offset); |
| } |
| } |
| |
| void CodegenFunction::GenerateBasicBlock(const DartBasicBlock* bb, |
| llvm::BasicBlock* llvm_bb) { |
| builder_.SetInsertPoint(llvm_bb); |
| |
| for (size_t i = 0; i < bb->NumInstructions(); ++i) { |
| EmitInstruction(bb->InstructionAt(i)); |
| } |
| } |
| |
| llvm::Value* CodegenFunction::EmitGraphEntry(InstructionInputExtractor I) { |
| assert(false); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitFunctionEntry(InstructionInputExtractor I) { |
| assert(false); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitTargetEntry(InstructionInputExtractor I) { |
| assert(false); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitJoinEntry(InstructionInputExtractor I) { |
| assert(false); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitConstant(InstructionInputExtractor I) { |
| auto is_smi = I.NextInputAsIntPtr(); |
| if (is_smi) { |
| // Should already be tagged. |
| auto val = I.NextInputAsIntPtr(); |
| assert(!(val & 1)); |
| return helper_.EmitNewObject(GetConstantInt(val)); |
| } |
| auto const_id = I.NextInputAsIntPtr(); |
| return helper_.EmitLoadConstant(const_id); |
| } |
| |
| llvm::Value* CodegenFunction::EmitReturn(InstructionInputExtractor I) { |
| auto ret_val = I.NextInput(); |
| builder_.CreateRet(GetValue(ret_val)); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitGoto(InstructionInputExtractor I) { |
| auto dest = I.NextInputAsIntPtr(); |
| builder_.CreateBr(GetBlock(dest)); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitParameter(InstructionInputExtractor I) { |
| size_t param_idx = I.NextInputAsIntPtr(); |
| assert(param_idx >= 0 && param_idx < func_->num_params()); |
| return helper_.GetParam(param_idx + kDartParamOffset); |
| } |
| |
| llvm::Value* CodegenFunction::EmitPhi(InstructionInputExtractor I) { |
| auto representation = I.NextInputAsEnum<Representation>(); |
| auto phi_ty = GetTypeFromRepresentation(representation); |
| // entry = (value_id, block_id) |
| const int kEntrySize = 2; |
| size_t num_income_values = I.NumInputsLeft(); |
| assert(num_income_values % kEntrySize == 0); |
| num_income_values /= kEntrySize; |
| assert(num_income_values > 1); |
| // For processing later as needed values might not be emitted yet. |
| phi_instructions_.push_back(I.instr()); |
| return builder_.CreatePHI(phi_ty, num_income_values); |
| } |
| |
| const char* RepresentationName(Representation rep) { |
| switch (rep) { |
| #define DEFINE_CASE(Name) case Representation::Name: return #Name; |
| REPRESENTATIONS_LIST(DEFINE_CASE) |
| #undef DEFINE_CASE |
| default: |
| return "<?>"; |
| } |
| } |
| |
| llvm::Value* CodegenFunction::EmitUnboxedConstant(InstructionInputExtractor I) { |
| auto representation = I.NextInputAsEnum<Representation>(); |
| if (representation == Representation::kUnboxedInt32 || |
| representation == Representation::kUnboxedInt64) { |
| auto val = I.NextInputAsInt64(); |
| return GetConstantInt(val); |
| } else { |
| std::cerr << "Unsupported representation: " << RepresentationName(representation) << std::endl; |
| // TODO(sarkin): Other representations |
| assert(false); |
| } |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitIntegerArithmetic(TokenKind op, |
| llvm::Value* left, |
| llvm::Value* right) { |
| assert(left->getType() == right->getType()); |
| assert(left->getType() == Int32Ty || left->getType() == Int64Ty); |
| |
| static const std::unordered_map<TokenKind, llvm::Instruction::BinaryOps> |
| kDartOpToLLVM{{TokenKind::kADD, llvm::Instruction::BinaryOps::Add}, |
| {TokenKind::kSUB, llvm::Instruction::BinaryOps::Sub}, |
| {TokenKind::kMUL, llvm::Instruction::BinaryOps::Mul}, |
| {TokenKind::kBIT_AND, llvm::Instruction::BinaryOps::And}, |
| {TokenKind::kBIT_OR, llvm::Instruction::BinaryOps::Or}, |
| {TokenKind::kBIT_XOR, llvm::Instruction::BinaryOps::Xor}, |
| {TokenKind::kMOD, |
| llvm::Instruction::BinaryOps::SRem}, // TODO(sarkin): mod |
| {TokenKind::kTRUNCDIV, |
| llvm::Instruction::BinaryOps::SDiv}}; // TODO(sarkin): mod |
| auto llvm_op = kDartOpToLLVM.find(op); |
| assert(llvm_op != kDartOpToLLVM.end()); |
| |
| return builder_.CreateBinOp(llvm_op->second, left, right); |
| } |
| |
| llvm::Value* CodegenFunction::EmitBinaryInt64Op(InstructionInputExtractor I) { |
| auto op = I.NextInputAsEnum<TokenKind>(); |
| auto left = GetValueOrConstant(I.NextInput()); |
| auto right = GetValueOrConstant(I.NextInput()); |
| |
| assert(left->getType() == Int64Ty); |
| assert(right->getType() == Int64Ty); |
| |
| return EmitIntegerArithmetic(op, left, right); |
| } |
| |
| llvm::Value* CodegenFunction::EmitComparisonOpInt64(TokenKind op, |
| llvm::Value* left, |
| llvm::Value* right) { |
| if (left->getType()->isPointerTy()) { |
| left = helper_.EmitSmiToInt(left); |
| } |
| if (right->getType()->isPointerTy()) { |
| right = helper_.EmitSmiToInt(right); |
| } |
| |
| static const std::unordered_map<TokenKind, llvm::CmpInst::Predicate> |
| kDartOpToLLVM{{TokenKind::kLT, llvm::CmpInst::Predicate::ICMP_SLT}, |
| {TokenKind::kGT, llvm::CmpInst::Predicate::ICMP_SGT}, |
| {TokenKind::kGTE, llvm::CmpInst::Predicate::ICMP_SGE}, |
| {TokenKind::kLTE, llvm::CmpInst::Predicate::ICMP_SLE}, |
| {TokenKind::kEQ, llvm::CmpInst::Predicate::ICMP_EQ}, |
| {TokenKind::kNE, llvm::CmpInst::Predicate::ICMP_NE}}; |
| |
| auto llvm_op = kDartOpToLLVM.find(op); |
| assert(llvm_op != kDartOpToLLVM.end()); |
| return builder_.CreateICmp(llvm_op->second, left, right); |
| } |
| |
| llvm::Value* CodegenFunction::EmitComparisonOpDouble(TokenKind op, |
| llvm::Value* left, |
| llvm::Value* right) { |
| // Using ordered comparisons, i.e. having NaN as either operand results in false |
| assert(left->getType()->isDoubleTy()); |
| assert(right->getType()->isDoubleTy()); |
| |
| static const std::unordered_map<TokenKind, llvm::CmpInst::Predicate> |
| kDartOpToLLVM{{TokenKind::kLT, llvm::CmpInst::Predicate::FCMP_OLT}, |
| {TokenKind::kGT, llvm::CmpInst::Predicate::FCMP_OGT}, |
| {TokenKind::kGTE, llvm::CmpInst::Predicate::FCMP_OGE}, |
| {TokenKind::kLTE, llvm::CmpInst::Predicate::FCMP_OLE}, |
| {TokenKind::kEQ, llvm::CmpInst::Predicate::FCMP_OEQ}, |
| {TokenKind::kNE, llvm::CmpInst::Predicate::FCMP_ONE}}; |
| |
| auto llvm_op = kDartOpToLLVM.find(op); |
| assert(llvm_op != kDartOpToLLVM.end()); |
| return builder_.CreateICmp(llvm_op->second, left, right); |
| } |
| |
| llvm::Value* CodegenFunction::EmitRelationalOp(InstructionInputExtractor I) { |
| auto op = I.NextInputAsEnum<TokenKind>(); |
| auto op_type = I.NextInputAsEnum<RelationalOpCid>(); |
| auto left = GetValueOrConstant(I.NextInput()); |
| auto right = GetValueOrConstant(I.NextInput()); |
| llvm::Value* result = nullptr; |
| switch (op_type) { |
| case RelationalOpCid::kInt64: |
| result = EmitComparisonOpInt64(op, left, right); |
| break; |
| case RelationalOpCid::kDouble: |
| result = EmitComparisonOpDouble(op, left, right); |
| break; |
| default: |
| assert(false); |
| } |
| if (!I.instr()->IsComparisonInBranch()) { |
| result = helper_.EmitBool(result); |
| } |
| return result; |
| } |
| |
| llvm::Value* CodegenFunction::EmitEqualityCompare(InstructionInputExtractor I) { |
| return EmitRelationalOp(I); |
| } |
| |
| llvm::Value* CodegenFunction::EmitStrictCompare(InstructionInputExtractor I) { |
| // TODO(sarkin): Num checks. |
| auto op = I.NextInputAsEnum<TokenKind>(); |
| bool needs_num_check = I.NextInputAsInt64(); |
| auto left = GetValue(I.NextInput()); |
| auto right = GetValue(I.NextInput()); |
| assert(!needs_num_check); |
| |
| auto cmp = (op == kNE_STRICT) ? builder_.CreateICmpNE(left, right) |
| : builder_.CreateICmpEQ(left, right); |
| if (!I.instr()->IsComparisonInBranch()) { |
| return helper_.EmitBool(cmp); |
| } |
| return cmp; |
| } |
| |
| llvm::Value* CodegenFunction::EmitBranch(InstructionInputExtractor I) { |
| // The -1 comes from the ComparisonInstr, which has a ssa_tmp_index -1 and |
| // is processed right before the branch instruction |
| auto cmp = GetValue("v-1"); |
| auto true_block = GetBlock(I.NextInputAsIntPtr()); |
| auto false_block = GetBlock(I.NextInputAsIntPtr()); |
| builder_.CreateCondBr(cmp, true_block, false_block); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitBoxInt64(InstructionInputExtractor I) { |
| auto val_fits_smi = I.NextInputAsIntPtr(); |
| auto val = GetValue(I.NextInput()); |
| if (val_fits_smi) { |
| return helper_.EmitSmiTag(val); |
| } |
| |
| auto fast_path = helper_.CreateBasicBlock("box64_fast_path"); |
| auto slow_path = helper_.CreateBasicBlock("box64_slow_path"); |
| auto merge_path = helper_.CreateBasicBlock("box64_merge_path"); |
| |
| auto try_tag = builder_.CreateIntrinsic( |
| llvm::Intrinsic::ID::sadd_with_overflow, {Int64Ty}, {val, val}); |
| auto tagged = builder_.CreateExtractValue(try_tag, {0}); |
| auto of = builder_.CreateExtractValue(try_tag, {1}); |
| helper_.EmitBrSlowFast(of, slow_path, fast_path); |
| |
| builder_.SetInsertPoint(fast_path); |
| tagged = helper_.EmitNewObject(tagged); |
| builder_.CreateBr(merge_path); |
| |
| builder_.SetInsertPoint(slow_path); |
| auto mint = helper_.EmitBoxAllocation(kMintCid); |
| auto mint_block = builder_.GetInsertBlock(); |
| helper_.EmitStoreField(mint, kMintValueOffset, val); |
| builder_.CreateBr(merge_path); |
| |
| builder_.SetInsertPoint(merge_path); |
| auto boxed = builder_.CreatePHI(ObjectPtrTy, 2); |
| boxed->addIncoming(tagged, fast_path); |
| boxed->addIncoming(mint, mint_block); |
| |
| return boxed; |
| } |
| |
| llvm::Value* CodegenFunction::EmitUnboxInt64(InstructionInputExtractor I) { |
| bool is_smi = I.NextInputAsIntPtr(); |
| auto val = GetValue(I.NextInput()); |
| if (is_smi) { |
| return helper_.EmitUntagSmi(val); |
| } |
| |
| auto smi_bb = helper_.CreateBasicBlock("unbox_smi"); |
| auto mint_bb = helper_.CreateBasicBlock("unbox_mint"); |
| auto merge_bb = helper_.CreateBasicBlock("unbox_merge"); |
| |
| auto cmp = helper_.EmitIsNotSmi(val); |
| helper_.EmitBrSlowFast(cmp, mint_bb, smi_bb); |
| |
| builder_.SetInsertPoint(smi_bb); |
| auto unboxed_smi = helper_.EmitUntagSmi(val); |
| builder_.CreateBr(merge_bb); |
| |
| builder_.SetInsertPoint(mint_bb); |
| auto unboxed_mint = helper_.EmitLoadField(val, kMintValueOffset, Int64Ty); |
| builder_.CreateBr(merge_bb); |
| |
| builder_.SetInsertPoint(merge_bb); |
| auto unboxed_val = builder_.CreatePHI(Int64Ty, 2); |
| unboxed_val->addIncoming(unboxed_smi, smi_bb); |
| unboxed_val->addIncoming(unboxed_mint, mint_bb); |
| |
| return unboxed_val; |
| } |
| |
| llvm::Value* CodegenFunction::EmitCheckStackOverflow( |
| InstructionInputExtractor I) { |
| // TODO(sarkin): Implement the full semantics of CheckStackOverflow. |
| auto error_bb = helper_.CreateBasicBlock("stack_error"); |
| auto cont_bb = helper_.CreateBasicBlock("stack_cont"); |
| |
| auto sp = builder_.CreateCall(cgm_.read_sp()); |
| auto stack_limit = helper_.EmitLoadFieldRaw(helper_.GetThread(), |
| kThreadStackLimitOffset, Int64Ty); |
| auto cmp = builder_.CreateICmpULT(sp, stack_limit); |
| helper_.EmitBrSlowFast(cmp, error_bb, cont_bb); |
| |
| builder_.SetInsertPoint(error_bb); |
| // TODO(sarkin): |
| EmitNullError(); |
| |
| builder_.SetInsertPoint(cont_bb); |
| |
| return nullptr; |
| } |
| |
| void CodegenFunction::EmitNullError() { |
| auto native_args = helper_.EmitNativeArguments(nullptr, {}); |
| helper_.EmitCallToRuntimeTrampoline( |
| CodegenModule::RuntimeEntryTag::kNullError, native_args); |
| builder_.CreateUnreachable(); |
| } |
| |
| llvm::Value* CodegenFunction::EmitCheckNull(InstructionInputExtractor I) { |
| auto val = GetValue(I.NextInput()); |
| auto error_bb = helper_.CreateBasicBlock("null_error"); |
| auto cont_bb = helper_.CreateBasicBlock("null_cont"); |
| |
| auto null = helper_.EmitNull(); |
| |
| auto cmp = builder_.CreateICmpEQ(val, null); |
| helper_.EmitBrSlowFast(cmp, error_bb, cont_bb); |
| |
| builder_.SetInsertPoint(error_bb); |
| EmitNullError(); |
| |
| builder_.SetInsertPoint(cont_bb); |
| |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitPushArgument(InstructionInputExtractor I) { |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitLLVMStaticCall( |
| size_t llvm_function_id, |
| const Selector& selector, |
| const std::vector<std::string>& args) { |
| std::vector<llvm::Value*> vargs{helper_.GetThread()}; |
| std::transform(args.begin(), args.end(), std::back_inserter(vargs), |
| [&](auto arg) { return GetValue(arg); }); |
| auto fn = cgm_.GetOrCreateHandleOptionalParamsTrampoline(llvm_function_id, |
| selector); |
| return builder_.CreateCall(fn, vargs); |
| } |
| |
| llvm::Value* CodegenFunction::EmitDartStaticCall( |
| intptr_t dart_function_id, |
| const Selector& selector, |
| const std::vector<std::string>& args) { |
| std::vector<llvm::Value*> vargs{helper_.GetThread()}; |
| std::transform(args.begin(), args.end(), std::back_inserter(vargs), |
| [&](auto arg) { return GetValue(arg); }); |
| auto fn = cgm_.GetOrCreateDartCallTrampoline(dart_function_id, selector); |
| return builder_.CreateCall(fn, vargs); |
| } |
| |
| llvm::Value* CodegenFunction::EmitSpecialParameter( |
| InstructionInputExtractor I) { |
| // TODO(sarkin): return null for now. |
| return helper_.EmitNull(); |
| } |
| |
| namespace { |
| void ReadCall(InstructionInputExtractor I, |
| intptr_t& arg_descriptor_id, |
| std::vector<std::string>& named_args, |
| std::vector<std::string>& args) { |
| arg_descriptor_id = I.NextInputAsIntPtr(); |
| size_t num_named = I.NextInputAsIntPtr(); |
| named_args.reserve(num_named); |
| for (size_t i = 0; i < num_named; ++i) { |
| named_args.push_back(I.NextInput()); |
| } |
| auto argc = I.NumInputsLeft(); |
| args.reserve(argc); |
| for (size_t i = 0; i < argc; ++i) { |
| args.push_back(I.NextInput()); |
| } |
| } |
| } // namespace |
| |
| llvm::Value* CodegenFunction::EmitStaticCall(InstructionInputExtractor I) { |
| bool is_llvm_compiled = I.NextInputAsInt64(); |
| auto patch_point = I.NextInput(); |
| intptr_t arg_descriptor_id; |
| std::vector<std::string> named_args; |
| std::vector<std::string> args; |
| ReadCall(I, arg_descriptor_id, named_args, args); |
| |
| Selector selector("", args.size(), std::move(named_args), arg_descriptor_id); |
| if (!is_llvm_compiled) { |
| auto dart_function_id = cgm_.GetDartFunctionIndexByPatchPoint(patch_point); |
| return EmitDartStaticCall(dart_function_id, selector, args); |
| } |
| auto llvm_function_id = cgm_.GetFunctionIDByPatchPoint(patch_point); |
| return EmitLLVMStaticCall(llvm_function_id, selector, args); |
| } |
| |
| llvm::Value* CodegenFunction::EmitInstanceCall(InstructionInputExtractor I) { |
| // TODO(sarkin): |
| std::string name = I.NextInput(); |
| bool check_smi = I.NextInputAsInt64(); |
| intptr_t arg_descriptor_id; |
| std::vector<std::string> named_args; |
| std::vector<std::string> args; |
| ReadCall(I, arg_descriptor_id, named_args, args); |
| |
| // TODO(sarkin): How to handle dyn: if they're not in the invocation cache? |
| if (name.substr(0, 4) == "dyn:") { |
| name = name.substr(4); |
| } |
| std::replace(name.begin(), name.end(), '@', '$'); |
| |
| Selector selector(name, args.size(), std::move(named_args), |
| arg_descriptor_id); |
| auto selector_id = dispatch_table_->AddSelector(selector); |
| |
| // We don't know the offset yet, because the dispatch table is still |
| // being constructed. Return a dummy value and store it to be replaced |
| // later with the real offset. |
| // Note: Can't use a constant here, because they're uniqued across the |
| // whole module. Create an alloca that will be optimised-out later. |
| auto dummy_offset = builder_.CreateLoad(builder_.CreateAlloca(Int64Ty)); |
| dynamic_calls_to_patch_.emplace_back(selector.selector(), dummy_offset); |
| |
| std::vector<llvm::Value*> vargs{helper_.GetThread()}; |
| std::transform(args.begin(), args.end(), std::back_inserter(vargs), |
| [&](auto arg) { return GetValue(arg); }); |
| |
| const int kReceiverIndex = 1; |
| auto receiver = vargs[kReceiverIndex]; |
| auto receiver_cid = EmitCid(receiver, check_smi); |
| auto entry_offset = builder_.CreateAdd(dummy_offset, receiver_cid); |
| |
| auto layout = cgm_.GetDataLayout().getStructLayout(DispatchTableEntryTy); |
| auto struct_size = GetConstantInt(layout->getSizeInBytes()); |
| // Check that selector-id is at offset 0 in the struct. |
| assert(layout->getElementOffset(0) == 0); |
| auto selector_id_offset = builder_.CreateMul(entry_offset, struct_size); |
| auto target_offset = builder_.CreateAdd( |
| selector_id_offset, GetConstantInt(layout->getElementOffset(1))); |
| |
| auto get_element = [&](auto offset, auto type) { |
| auto addr = builder_.CreatePtrToInt(cgm_.GetLLVMDispatchTable(), Int64Ty); |
| addr = builder_.CreateAdd(addr, offset); |
| auto elty = helper_.GetNonGCPointer(type); |
| auto elp = builder_.CreateIntToPtr(addr, elty); |
| return builder_.CreateLoad(elp); |
| }; |
| |
| auto fpty = helper_.GetNonGCPointer(cgm_.GetStaticFunctionType(args.size())); |
| auto target = get_element(target_offset, fpty); |
| auto nsm_block = helper_.CreateBasicBlock("instance_call_nsm"); |
| auto smi_safe = helper_.CreateBasicBlock("smi_safe"); |
| auto selector_safe = helper_.CreateBasicBlock("selector_safe"); |
| if (check_smi) { |
| auto cmp = helper_.EmitIsNotSmi(receiver); |
| helper_.EmitBrFastSlow(cmp, smi_safe, nsm_block); |
| } else { |
| builder_.CreateBr(smi_safe); |
| } |
| builder_.SetInsertPoint(smi_safe); |
| { |
| auto table_sid = get_element(selector_id_offset, Int64Ty); |
| auto cmp = builder_.CreateICmpEQ(GetConstantInt(selector_id), table_sid); |
| helper_.EmitBrFastSlow(cmp, selector_safe, nsm_block); |
| } |
| |
| builder_.SetInsertPoint(nsm_block); |
| // TODO(sarkin): No such method. |
| builder_.CreateUnreachable(); |
| |
| builder_.SetInsertPoint(selector_safe); |
| |
| return builder_.CreateCall(target, vargs); |
| } |
| |
| llvm::Value* CodegenFunction::EmitLoadIndexedUnsafe( |
| InstructionInputExtractor I) { |
| return EmitParameter(I); |
| } |
| |
| llvm::Value* CodegenFunction::EmitPolymorphicInstanceCall( |
| InstructionInputExtractor I) { |
| return EmitInstanceCall(I); |
| } |
| |
| llvm::Value* CodegenFunction::EmitLoadClassId(InstructionInputExtractor I) { |
| bool check_smi = I.NextInputAsInt64(); |
| auto val = GetValue(I.NextInput()); |
| return helper_.EmitSmiTag(EmitCid(val, check_smi)); |
| } |
| |
| llvm::Value* CodegenFunction::EmitLoadStaticField(InstructionInputExtractor I) { |
| auto val = GetValue(I.NextInput()); |
| return helper_.EmitLoadField(val, kFieldStaticValueOffset, ObjectPtrTy); |
| } |
| |
| void CodegenFunction::EmitNonBoolTypeError(llvm::Value* val) { |
| auto native_args = helper_.EmitNativeArguments(nullptr, {val}); |
| helper_.EmitCallToRuntimeTrampoline( |
| CodegenModule::RuntimeEntryTag::kNonBoolTypeError, native_args); |
| } |
| |
| llvm::Value* CodegenFunction::EmitAssertBoolean(InstructionInputExtractor I) { |
| auto val = GetValue(I.NextInput()); |
| auto done_bb = helper_.CreateBasicBlock("done"); |
| auto not_false_bb = helper_.CreateBasicBlock("not_false"); |
| auto not_bool_bb = helper_.CreateBasicBlock("not_bool"); |
| |
| auto false_obj = helper_.EmitBool(GetConstantInt(0, Int1Ty)); |
| |
| auto cmp = builder_.CreateICmpEQ(val, false_obj); |
| builder_.CreateCondBr(cmp, done_bb, not_false_bb); |
| |
| builder_.SetInsertPoint(not_false_bb); |
| auto true_obj = helper_.EmitBool(GetConstantInt(1, Int1Ty)); |
| cmp = builder_.CreateICmpEQ(val, true_obj); |
| helper_.EmitBrFastSlow(cmp, done_bb, not_bool_bb); |
| |
| builder_.SetInsertPoint(not_bool_bb); |
| EmitNonBoolTypeError(val); |
| builder_.CreateBr(done_bb); |
| |
| builder_.SetInsertPoint(done_bb); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitCheckedSmiComparison( |
| InstructionInputExtractor I) { |
| auto op = I.NextInputAsEnum<TokenKind>(); |
| auto selector = I.NextInput(); |
| bool is_negated = I.NextInputAsIntPtr(); |
| bool is_left_smi = I.NextInputAsIntPtr(); |
| bool is_right_smi = I.NextInputAsIntPtr(); |
| auto left = GetValue(I.NextInput()); |
| auto right = GetValue(I.NextInput()); |
| auto is_static_call = I.NextInputAsIntPtr(); |
| |
| llvm::Value* cmp = nullptr; |
| if (left == right) { |
| cmp = helper_.EmitIsNotSmi(left); |
| } else if (is_left_smi) { |
| cmp = helper_.EmitIsNotSmi(right); |
| } else if (is_right_smi) { |
| cmp = helper_.EmitIsNotSmi(left); |
| } else { |
| cmp = helper_.EmitIsAnyNotSmi(left, right); |
| } |
| auto slow_path = helper_.CreateBasicBlock("checked_smi_slow_path"); |
| auto fast_path = helper_.CreateBasicBlock("checked_smi_fast_path"); |
| auto merge_paths = helper_.CreateBasicBlock("checked_smi_merge_paths"); |
| helper_.EmitBrSlowFast(cmp, slow_path, fast_path); |
| |
| builder_.SetInsertPoint(slow_path); |
| auto object_cmp = !is_static_call ? EmitInstanceCall(I) : EmitStaticCall(I); |
| auto cmp_against = helper_.EmitBool(GetConstantInt(!is_negated, Int1Ty)); |
| auto slow_cmp = builder_.CreateICmpEQ(object_cmp, cmp_against); |
| builder_.CreateBr(merge_paths); |
| |
| builder_.SetInsertPoint(fast_path); |
| auto fast_cmp = EmitComparisonOpInt64(op, left, right); |
| builder_.CreateBr(merge_paths); |
| |
| builder_.SetInsertPoint(merge_paths); |
| auto merge_cmp = builder_.CreatePHI(Int1Ty, 2); |
| merge_cmp->addIncoming(slow_cmp, slow_path); |
| merge_cmp->addIncoming(fast_cmp, fast_path); |
| |
| if (!I.instr()->IsComparisonInBranch()) { |
| return helper_.EmitBool(merge_cmp); |
| } |
| return merge_cmp; |
| } |
| |
| llvm::Value* CodegenFunction::EmitCheckedSmiOp( |
| InstructionInputExtractor I) { |
| auto op = I.NextInputAsEnum<TokenKind>(); |
| auto selector = I.NextInput(); |
| bool is_left_smi = I.NextInputAsIntPtr(); |
| bool is_right_smi = I.NextInputAsIntPtr(); |
| auto left = GetValue(I.NextInput()); |
| auto right = GetValue(I.NextInput()); |
| auto is_static_call = I.NextInputAsIntPtr(); |
| |
| llvm::Value* cmp = nullptr; |
| if (left == right) { |
| cmp = helper_.EmitIsNotSmi(left); |
| } else if (is_left_smi) { |
| cmp = helper_.EmitIsNotSmi(right); |
| } else if (is_right_smi) { |
| cmp = helper_.EmitIsNotSmi(left); |
| } else { |
| cmp = helper_.EmitIsAnyNotSmi(left, right); |
| } |
| auto slow_path = helper_.CreateBasicBlock("checked_smi_slow_path"); |
| auto fast_path = helper_.CreateBasicBlock("checked_smi_fast_path"); |
| auto merge_paths = helper_.CreateBasicBlock("checked_smi_merge_paths"); |
| helper_.EmitBrSlowFast(cmp, slow_path, fast_path); |
| |
| builder_.SetInsertPoint(slow_path); |
| auto slow_val = !is_static_call ? EmitInstanceCall(I) : EmitStaticCall(I); |
| builder_.CreateBr(merge_paths); |
| |
| builder_.SetInsertPoint(fast_path); |
| left = helper_.EmitSmiToInt(left); |
| right = helper_.EmitSmiToInt(right); |
| auto fast_val = EmitIntegerArithmetic(op, left, right); |
| fast_val = helper_.EmitNewObject(fast_val); |
| builder_.CreateBr(merge_paths); |
| |
| builder_.SetInsertPoint(merge_paths); |
| auto merge_val = builder_.CreatePHI(ObjectPtrTy, 2); |
| merge_val->addIncoming(slow_val, slow_path); |
| merge_val->addIncoming(fast_val, fast_path); |
| |
| return merge_val; |
| } |
| |
| llvm::Value* CodegenFunction::EmitAssertAssignable( |
| InstructionInputExtractor I) { |
| // TODO(sarkin): |
| return GetConstantInt(-1); |
| } |
| |
| llvm::Value* CodegenFunction::EmitAllocateObject(InstructionInputExtractor I) { |
| auto cid = I.NextInputAsIntPtr(); |
| auto cls = helper_.EmitLoadConstant(I.NextInputAsIntPtr()); |
| auto type_arguments = helper_.EmitNull(); |
| if (I.NumInputsLeft()) { |
| type_arguments = GetValue(I.NextInput()); |
| } |
| auto allocation_stub = cgm_.GetOrCreateClassAllocationStub(cid); |
| return builder_.CreateCall(allocation_stub, |
| {helper_.GetThread(), cls, type_arguments}); |
| } |
| |
| llvm::Value* CodegenFunction::EmitCreateArray(InstructionInputExtractor I) { |
| auto n = GetValue(I.NextInput()); |
| auto element_type = GetValue(I.NextInput()); |
| |
| return builder_.CreateCall(cgm_.create_array_stub(), |
| {helper_.GetThread(), n, element_type}); |
| } |
| |
| llvm::Value* CodegenFunction::EmitLoadField(InstructionInputExtractor I) { |
| bool is_unboxed_load = I.NextInputAsIntPtr(); |
| bool is_potential_unboxed = I.NextInputAsIntPtr(); |
| auto representation = I.NextInputAsEnum<Representation>(); |
| auto offset_in_bytes = I.NextInputAsIntPtr(); |
| auto instance = GetValue(I.NextInput()); |
| auto field_id = I.NextInputAsIntPtr(); |
| |
| if (is_unboxed_load) { |
| assert(representation != kTagged); |
| intptr_t value_offset = 0; |
| switch (representation) { |
| case kUnboxedDouble: |
| value_offset = kDoubleValueOffset; |
| break; |
| case kUnboxedFloat32x4: |
| value_offset = kFloat32x4ValueOffset; |
| break; |
| case kUnboxedFloat64x2: |
| value_offset = kFloat64x2ValueOffset; |
| break; |
| default: |
| assert(false); |
| } |
| auto ty = GetTypeFromRepresentation(representation); |
| auto field = helper_.EmitLoadField(instance, offset_in_bytes, ObjectPtrTy); |
| return helper_.EmitLoadField(field, value_offset, ty); |
| } |
| |
| auto load_pointer = helper_.CreateBasicBlock("load_pointer"); |
| |
| if (is_potential_unboxed) { |
| auto check_double = helper_.CreateBasicBlock("check_double"); |
| auto check_float32x4 = helper_.CreateBasicBlock("check_float32x4"); |
| auto check_float64x2 = helper_.CreateBasicBlock("check_float64x2"); |
| |
| auto load_double = helper_.CreateBasicBlock("load_double"); |
| auto load_float32x4 = helper_.CreateBasicBlock("load_float32x4"); |
| auto load_float64x2 = helper_.CreateBasicBlock("load_float64x2"); |
| |
| auto merge = helper_.CreateBasicBlock("merge_loads"); |
| |
| auto field = helper_.EmitLoadConstant(field_id); |
| |
| auto is_nullable = |
| helper_.EmitLoadField(field, kFieldIsNullableOffset, Int16Ty); |
| { |
| auto cmp = |
| builder_.CreateICmpEQ(is_nullable, GetConstantInt(kNullCid, Int16Ty)); |
| builder_.CreateCondBr(cmp, load_pointer, check_double); |
| } |
| |
| auto pointer_field = |
| helper_.EmitLoadField(instance, offset_in_bytes, ObjectPtrTy); |
| |
| builder_.SetInsertPoint(check_double); |
| auto guarded_cid = |
| helper_.EmitLoadField(field, kFieldGuardedCidOffset, Int16Ty); |
| { |
| auto cmp = builder_.CreateICmpEQ(guarded_cid, |
| GetConstantInt(kDoubleCid, Int16Ty)); |
| builder_.CreateCondBr(cmp, load_double, check_float32x4); |
| } |
| |
| builder_.SetInsertPoint(check_float32x4); |
| { |
| auto cmp = builder_.CreateICmpEQ(guarded_cid, |
| GetConstantInt(kFloat32x4Cid, Int16Ty)); |
| builder_.CreateCondBr(cmp, load_float32x4, check_float64x2); |
| } |
| |
| builder_.SetInsertPoint(check_float64x2); |
| { |
| auto cmp = builder_.CreateICmpEQ(guarded_cid, |
| GetConstantInt(kFloat64x2Cid, Int16Ty)); |
| builder_.CreateCondBr(cmp, load_float64x2, load_pointer); |
| } |
| |
| builder_.SetInsertPoint(load_pointer); |
| builder_.CreateBr(merge); |
| |
| builder_.SetInsertPoint(load_double); |
| auto double_field = helper_.EmitBoxAllocation(kDoubleCid); |
| load_double = builder_.GetInsertBlock(); |
| { |
| auto val = helper_.EmitLoadField(load_pointer, kDoubleValueOffset, |
| DoubleTy); |
| helper_.EmitStoreField(double_field, kDoubleValueOffset, val); |
| } |
| builder_.CreateBr(merge); |
| |
| builder_.SetInsertPoint(load_float32x4); |
| auto float32x4_field = helper_.EmitBoxAllocation(kFloat32x4Cid); |
| load_float32x4 = builder_.GetInsertBlock(); |
| { |
| auto val = helper_.EmitLoadField(load_pointer, kFloat32x4ValueOffset, |
| Float32x4Ty); |
| helper_.EmitStoreField(float32x4_field, kFloat32x4ValueOffset, val); |
| } |
| builder_.CreateBr(merge); |
| |
| builder_.SetInsertPoint(load_float64x2); |
| auto float64x2_field = helper_.EmitBoxAllocation(kFloat64x2Cid); |
| load_float64x2 = builder_.GetInsertBlock(); |
| { |
| auto val = helper_.EmitLoadField(load_pointer, kFloat64x2ValueOffset, |
| Float64x2Ty); |
| helper_.EmitStoreField(float64x2_field, kFloat64x2ValueOffset, val); |
| } |
| builder_.CreateBr(merge); |
| |
| builder_.SetInsertPoint(merge); |
| auto merge_field = builder_.CreatePHI(ObjectPtrTy, 4); |
| merge_field->addIncoming(pointer_field, load_pointer); |
| merge_field->addIncoming(double_field, load_double); |
| merge_field->addIncoming(float32x4_field, load_float32x4); |
| merge_field->addIncoming(float64x2_field, load_float64x2); |
| |
| return merge_field; |
| } |
| |
| builder_.CreateBr(load_pointer); |
| |
| builder_.SetInsertPoint(load_pointer); |
| return helper_.EmitLoadField(instance, offset_in_bytes, ObjectPtrTy); |
| } |
| |
| llvm::Value* CodegenFunction::EmitStoreInstanceField(InstructionInputExtractor I) { |
| bool is_unboxed_store = I.NextInputAsIntPtr(); |
| bool is_initialization = I.NextInputAsIntPtr(); |
| bool is_potential_unboxed = I.NextInputAsIntPtr(); |
| bool should_emit_store_barrier = I.NextInputAsIntPtr(); |
| auto instance = GetValue(I.NextInput()); |
| auto value = GetValue(I.NextInput()); |
| auto unboxed_field_cid = I.NextInputAsIntPtr(); |
| auto offset_in_bytes = I.NextInputAsIntPtr(); |
| bool can_value_be_smi = I.NextInputAsIntPtr(); |
| auto field_id = I.NextInputAsIntPtr(); |
| |
| if (is_unboxed_store) { |
| llvm::Value* field = nullptr; |
| if (is_initialization) { |
| field = helper_.EmitBoxAllocation(unboxed_field_cid); |
| helper_.EmitStoreInObject(instance, offset_in_bytes, field, false); |
| } else { |
| field = helper_.EmitLoadField(instance, offset_in_bytes, ObjectPtrTy); |
| } |
| |
| auto val_offset = GetValueOffsetForCid(unboxed_field_cid); |
| // TODO(sarkin): Might need to cast value to ty here, |
| // e.g. if it's of type ObjectPtrTy. |
| // auto ty = GetUnboxedTypeForCid(unboxed_field_cid); |
| helper_.EmitStoreField(field, val_offset, value); |
| return nullptr; |
| } |
| |
| auto store_pointer_entry = helper_.CreateBasicBlock("store_pointer"); |
| llvm::BasicBlock* store_pointer_exit = nullptr; |
| { |
| auto cur_bb = builder_.GetInsertBlock(); |
| |
| builder_.SetInsertPoint(store_pointer_entry); |
| if (should_emit_store_barrier) { |
| helper_.EmitStoreInObject(instance, offset_in_bytes, value, |
| can_value_be_smi); |
| } else { |
| assert(value->getType() == ObjectPtrTy); |
| helper_.EmitStoreField(instance, offset_in_bytes, value); |
| } |
| store_pointer_exit = builder_.GetInsertBlock(); |
| |
| builder_.SetInsertPoint(cur_bb); |
| } |
| |
| if (is_potential_unboxed) { |
| auto check_unboxing_candidate = helper_.CreateBasicBlock("check_unboxing_candidate"); |
| auto check_double = helper_.CreateBasicBlock("check_double"); |
| auto check_float32x4 = helper_.CreateBasicBlock("check_float32x4"); |
| auto check_float64x2 = helper_.CreateBasicBlock("check_float64x2"); |
| |
| auto store_double = helper_.CreateBasicBlock("store_double"); |
| auto store_float32x4 = helper_.CreateBasicBlock("store_float32x4"); |
| auto store_float64x2 = helper_.CreateBasicBlock("store_float64x2"); |
| |
| auto merge = helper_.CreateBasicBlock("merge_stores"); |
| |
| auto field = helper_.EmitLoadConstant(field_id); |
| |
| auto is_nullable = |
| helper_.EmitLoadField(field, kFieldIsNullableOffset, Int16Ty); |
| { |
| auto cmp = |
| builder_.CreateICmpEQ(is_nullable, GetConstantInt(kNullCid, Int16Ty)); |
| builder_.CreateCondBr(cmp, store_pointer_entry, check_unboxing_candidate); |
| } |
| |
| auto kind_bits = helper_.EmitLoadField(field, kFieldKindBitsOffset, Int8Ty); |
| { |
| auto cmp = helper_.EmitTestBit(kind_bits, kFieldUnboxingCandidateBit); |
| builder_.CreateCondBr(cmp, check_unboxing_candidate, store_pointer_entry); |
| } |
| |
| auto box_field = |
| helper_.EmitLoadField(instance, offset_in_bytes, ObjectPtrTy); |
| |
| builder_.SetInsertPoint(check_double); |
| auto guarded_cid = |
| helper_.EmitLoadField(field, kFieldGuardedCidOffset, Int16Ty); |
| { |
| auto cmp = builder_.CreateICmpEQ(guarded_cid, |
| GetConstantInt(kDoubleCid, Int16Ty)); |
| builder_.CreateCondBr(cmp, store_double, check_float32x4); |
| } |
| |
| builder_.SetInsertPoint(check_float32x4); |
| { |
| auto cmp = builder_.CreateICmpEQ(guarded_cid, |
| GetConstantInt(kFloat32x4Cid, Int16Ty)); |
| builder_.CreateCondBr(cmp, store_float32x4, check_float64x2); |
| } |
| |
| builder_.SetInsertPoint(check_float64x2); |
| { |
| auto cmp = builder_.CreateICmpEQ(guarded_cid, |
| GetConstantInt(kFloat64x2Cid, Int16Ty)); |
| builder_.CreateCondBr(cmp, store_float64x2, store_pointer_entry); |
| } |
| |
| builder_.SetInsertPoint(store_pointer_exit); |
| builder_.CreateBr(merge); |
| |
| auto null = helper_.EmitNull(); |
| auto ensure_is_box = [&](auto box, auto cid) { |
| auto is_null = helper_.CreateBasicBlock("ensure_box_is_null"); |
| auto is_not_null = helper_.CreateBasicBlock("ensure_box_is_not_null"); |
| auto merge = helper_.CreateBasicBlock("ensure_box_merge"); |
| auto cmp = builder_.CreateICmpEQ(box, null); |
| builder_.CreateCondBr(cmp, is_null, is_not_null); |
| |
| builder_.SetInsertPoint(is_null); |
| auto new_box = helper_.EmitBoxAllocation(cid); |
| helper_.EmitStoreInObject(instance, offset_in_bytes, new_box, false); |
| is_not_null = builder_.GetInsertBlock(); |
| builder_.CreateBr(merge); |
| |
| builder_.SetInsertPoint(is_not_null); |
| builder_.CreateBr(merge); |
| |
| builder_.SetInsertPoint(merge); |
| auto merge_box = builder_.CreatePHI(ObjectPtrTy, 2); |
| merge_box->addIncoming(box, is_not_null); |
| merge_box->addIncoming(new_box, is_null); |
| |
| return merge_box; |
| }; |
| |
| builder_.SetInsertPoint(store_double); |
| { |
| box_field = ensure_is_box(box_field, kDoubleCid); |
| auto val = helper_.EmitLoadField(value, kDoubleValueOffset, DoubleTy); |
| helper_.EmitStoreField(box_field, kDoubleValueOffset, val); |
| } |
| builder_.CreateBr(merge); |
| |
| builder_.SetInsertPoint(store_float32x4); |
| { |
| box_field = ensure_is_box(box_field, kFloat32x4Cid); |
| auto val = |
| helper_.EmitLoadField(value, kFloat32x4ValueOffset, Float32x4Ty); |
| helper_.EmitStoreField(box_field, kFloat32x4ValueOffset, val); |
| } |
| builder_.CreateBr(merge); |
| |
| builder_.SetInsertPoint(store_float64x2); |
| { |
| box_field = ensure_is_box(box_field, kFloat64x2Cid); |
| auto val = |
| helper_.EmitLoadField(value, kFloat64x2ValueOffset, Float64x2Ty); |
| helper_.EmitStoreField(box_field, kFloat64x2ValueOffset, val); |
| } |
| builder_.CreateBr(merge); |
| |
| builder_.SetInsertPoint(merge); |
| |
| return nullptr; |
| } |
| |
| builder_.CreateBr(store_pointer_entry); |
| |
| builder_.SetInsertPoint(store_pointer_exit); |
| |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitLoadIndexed(InstructionInputExtractor I) { |
| bool is_external = I.NextInputAsIntPtr(); |
| auto representation = I.NextInputAsEnum<Representation>(); |
| auto index_scale = I.NextInputAsIntPtr(); |
| auto class_id = I.NextInputAsIntPtr(); |
| intptr_t data_offset = 0; |
| if (!is_external) { |
| data_offset = I.NextInputAsIntPtr(); |
| } |
| auto array = GetValue(I.NextInput()); |
| auto index = GetValueOrConstant(I.NextInput()); |
| // Check for Smi |
| if (index->getType()->isPointerTy()) { |
| if (index_scale == 1) { |
| index = helper_.EmitUntagSmi(index); |
| } else { |
| // The smi index is either untagged (element size == 1), or it is left smi |
| // tagged (for all element sizes > 1). |
| index = helper_.EmitSmiToInt(index); |
| } |
| } |
| |
| // Note that index is expected smi-tagged, (i.e, times 2) for all arrays with |
| // index scale factor > 1. E.g., for Uint8Array and OneByteString the index is |
| // expected to be untagged before accessing. |
| if (!(index_scale & 1)) { |
| index_scale >>= 1; |
| } |
| auto offset = builder_.CreateMul(index, GetConstantInt(index_scale)); |
| offset = builder_.CreateAdd(offset, GetConstantInt(data_offset)); |
| |
| auto ty = GetTypeFromRepresentation(representation); |
| if (representation == kUnboxedDouble) { |
| assert(class_id == kTypedDataFloat32ArrayCid || |
| class_id == kTypedDataFloat64ArrayCid); |
| if (class_id == kTypedDataFloat32ArrayCid) { |
| ty = FloatTy; |
| } |
| } |
| |
| auto get_element = [&](auto ty) { |
| if (is_external) { |
| return helper_.EmitLoadFieldRaw(array, offset, ty); |
| } |
| return helper_.EmitLoadField(array, offset, ty); |
| }; |
| |
| if (representation != kTagged) { |
| llvm::Value* el = get_element(ty); |
| if (ty->isIntegerTy()) { |
| el = builder_.CreateSExt(el, Int64Ty); |
| } else if (ty->isFloatTy()) { |
| el = builder_.CreateFPExt(el, DoubleTy); |
| } |
| |
| return el; |
| } |
| |
| if (class_id == kTypedDataInt8ArrayCid) { |
| auto el = get_element(Int8Ty); |
| el = builder_.CreateSExt(el, Int64Ty); |
| return helper_.EmitSmiTag(el); |
| } |
| |
| if (class_id == kTypedDataUint8ArrayCid || |
| class_id == kTypedDataUint8ClampedArrayCid || |
| class_id == kExternalTypedDataUint8ArrayCid || |
| class_id == kExternalTypedDataUint8ClampedArrayCid || |
| class_id == kOneByteStringCid || class_id == kExternalOneByteStringCid) { |
| auto el = get_element(Int8Ty); |
| el = builder_.CreateZExt(el, Int64Ty); |
| return helper_.EmitSmiTag(el); |
| } |
| |
| if (class_id == kTypedDataInt16ArrayCid) { |
| auto el = get_element(Int16Ty); |
| el = builder_.CreateSExt(el, Int64Ty); |
| return helper_.EmitSmiTag(el); |
| } |
| |
| if (class_id == kTypedDataUint16ArrayCid || class_id == kTwoByteStringCid || |
| class_id == kExternalTwoByteStringCid) { |
| auto el = get_element(Int16Ty); |
| el = builder_.CreateZExt(el, Int64Ty); |
| return helper_.EmitSmiTag(el); |
| } |
| |
| assert(class_id == kArrayCid || class_id == kImmutableArrayCid); |
| return get_element(ObjectPtrTy); |
| } |
| |
| llvm::Value* CodegenFunction::EmitStoreIndexed(InstructionInputExtractor I) { |
| // TODO(sarkin): Duplicated code with LoadIndexed. |
| bool should_emit_store_barrier = I.NextInputAsIntPtr(); |
| bool is_external = I.NextInputAsIntPtr(); |
| auto index_scale = I.NextInputAsIntPtr(); |
| auto class_id = I.NextInputAsIntPtr(); |
| intptr_t data_offset = 0; |
| if (!is_external) { |
| data_offset = I.NextInputAsIntPtr(); |
| } |
| bool can_value_be_smi = I.NextInputAsIntPtr(); |
| auto array = GetValue(I.NextInput()); |
| auto index = GetValueOrConstant(I.NextInput()); |
| auto value = GetValue(I.NextInput()); |
| bool is_constant = I.NextInputAsIntPtr(); |
| llvm::Value* const_val = |
| (is_constant) ? GetValueOrConstant(I.NextInput()) : nullptr; |
| |
| // Check for Smi |
| if (index->getType()->isPointerTy()) { |
| if (index_scale == 1) { |
| index = helper_.EmitUntagSmi(index); |
| } else { |
| // The smi index is either untagged (element size == 1), or it is left smi |
| // tagged (for all element sizes > 1). |
| index = helper_.EmitSmiToInt(index); |
| } |
| } |
| |
| // Note that index is expected smi-tagged, (i.e, times 2) for all arrays with |
| // index scale factor > 1. E.g., for Uint8Array and OneByteString the index is |
| // expected to be untagged before accessing. |
| if (!(index_scale & 1)) { |
| index_scale >>= 1; |
| } |
| auto offset = builder_.CreateMul(index, GetConstantInt(index_scale)); |
| offset = builder_.CreateAdd(offset, GetConstantInt(data_offset)); |
| |
| if (class_id == kArrayCid) { |
| if (should_emit_store_barrier) { |
| helper_.EmitStoreInObject(array, offset, value, can_value_be_smi); |
| } else { |
| assert(value->getType() == ObjectPtrTy); |
| helper_.EmitStoreField(array, offset, value); |
| } |
| return nullptr; |
| } |
| |
| auto put_element = [&](auto val) { |
| if (is_external) { |
| return helper_.EmitStoreFieldRaw(array, offset, val); |
| } |
| return helper_.EmitStoreField(array, offset, val); |
| }; |
| |
| if (class_id == kTypedDataInt8ArrayCid || |
| class_id == kTypedDataUint8ArrayCid || |
| class_id == kExternalTypedDataUint8ArrayCid || |
| class_id == kOneByteStringCid) { |
| value = helper_.EmitUntagSmi(value); |
| auto final_val = |
| builder_.CreateTrunc(((is_constant) ? const_val : value), Int8Ty); |
| put_element(final_val); |
| return nullptr; |
| } |
| |
| if (class_id == kTypedDataUint8ClampedArrayCid || |
| class_id == kExternalTypedDataUint8ClampedArrayCid) { |
| } |
| |
| if (class_id == kTypedDataInt16ArrayCid || |
| class_id == kTypedDataUint16ArrayCid) { |
| value = helper_.EmitUntagSmi(value); |
| auto final_val = builder_.CreateTrunc(value, Int16Ty); |
| put_element(final_val); |
| return nullptr; |
| } |
| |
| if (class_id == kTypedDataInt32ArrayCid || |
| class_id == kTypedDataUint32ArrayCid) { |
| auto final_val = builder_.CreateTrunc(value, Int32Ty); |
| put_element(final_val); |
| return nullptr; |
| } |
| |
| if (class_id == kTypedDataInt64ArrayCid || |
| class_id == kTypedDataUint64ArrayCid) { |
| put_element(value); |
| return nullptr; |
| } |
| |
| // TODO(sarkin): |
| if (class_id == kTypedDataFloat32ArrayCid) { |
| // auto final_val = builder_. |
| } |
| assert(false); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitOneByteStringFromCharCode( |
| InstructionInputExtractor I) { |
| auto char_code = GetValue(I.NextInput()); |
| |
| // char_code is Smi. |
| char_code = helper_.EmitSmiToInt(char_code); |
| |
| // TODO(sarkin): Can the symbol table be moved by the GC? |
| // If so, then should change to ObjectPtrTy. |
| auto pre_symbols_table = helper_.EmitLoadFieldRaw( |
| helper_.GetThread(), kThreadPredefinedSymbolsAddressOffset, |
| NonGCObjectPtrTy); |
| auto offset_in_table = builder_.CreateShl(char_code, kWordSizeLog2 - 1); |
| offset_in_table = builder_.CreateAdd( |
| offset_in_table, |
| GetConstantInt(kWordSize * kSymbolNullCharCodeSymbolOffset)); |
| auto symbol = |
| helper_.EmitLoadFieldRaw(pre_symbols_table, offset_in_table, ObjectPtrTy); |
| return symbol; |
| } |
| |
| void CodegenFunction::EmitRangeError(llvm::Value* index, llvm::Value* length) { |
| auto native_args = helper_.EmitNativeArguments(nullptr, {length, index}); |
| helper_.EmitCallToRuntimeTrampoline( |
| CodegenModule::RuntimeEntryTag::kRangeError, native_args); |
| builder_.CreateUnreachable(); |
| } |
| |
| llvm::Value* CodegenFunction::EmitGenericCheckBound( |
| InstructionInputExtractor I) { |
| // TODO(sarkin): |
| bool is_known_smi = I.NextInputAsIntPtr(); |
| auto index = GetValue(I.NextInput()); |
| auto length = GetValue(I.NextInput()); |
| |
| auto error_bb = helper_.CreateBasicBlock("bound_error"); |
| auto smi_check_bb = helper_.CreateBasicBlock("bound_smi_check"); |
| auto cont_bb = helper_.CreateBasicBlock("bound_cont"); |
| |
| if (!is_known_smi) { |
| auto is_not_smi = helper_.EmitIsNotSmi(index); |
| helper_.EmitBrSlowFast(is_not_smi, error_bb, smi_check_bb); |
| } else { |
| builder_.CreateBr(smi_check_bb); |
| } |
| |
| builder_.SetInsertPoint(smi_check_bb); |
| auto cmp = builder_.CreateICmpULT(index, length); |
| helper_.EmitBrFastSlow(cmp, cont_bb, error_bb); |
| |
| builder_.SetInsertPoint(error_bb); |
| EmitRangeError(index, length); |
| |
| builder_.SetInsertPoint(cont_bb); |
| |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitStoreStaticField( |
| InstructionInputExtractor I) { |
| bool needs_write_barrier = I.NextInputAsIntPtr(); |
| bool can_value_be_smi = I.NextInputAsIntPtr(); |
| auto field_id = I.NextInputAsIntPtr(); |
| auto value = GetValue(I.NextInput()); |
| |
| auto field = helper_.EmitLoadConstant(field_id); |
| if (needs_write_barrier) { |
| helper_.EmitStoreInObject(field, kFieldStaticValueOffset, value, |
| can_value_be_smi); |
| } else { |
| helper_.EmitStoreField(field, kFieldStaticValueOffset, value); |
| } |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitBooleanNegate(InstructionInputExtractor I) { |
| auto val = GetValue(I.NextInput()); |
| return helper_.EmitNegateBool(val); |
| } |
| |
| llvm::Value* CodegenFunction::EmitIfThenElse(InstructionInputExtractor I) { |
| auto true_val = I.NextInputAsIntPtr(); |
| auto false_val = I.NextInputAsIntPtr(); |
| |
| auto cmp = GetValue("v-1"); |
| |
| auto merge_val = builder_.CreateSelect(cmp, GetConstantInt(true_val), |
| GetConstantInt(false_val)); |
| return helper_.EmitSmiTag(merge_val); |
| } |
| |
| llvm::Value* CodegenFunction::EmitBoxUint32(InstructionInputExtractor I) { |
| auto val = GetValue(I.NextInput()); |
| |
| assert(val->getType()->isIntegerTy()); |
| if (val->getType() != IntTy) { |
| val = builder_.CreateZExt(val, IntTy); |
| } |
| |
| return helper_.EmitSmiTag(val); |
| } |
| |
| llvm::Value* CodegenFunction::EmitBinaryUint32Op(InstructionInputExtractor I) { |
| auto op = I.NextInputAsEnum<TokenKind>(); |
| auto left = GetValue(I.NextInput()); |
| auto right = GetValue(I.NextInput()); |
| |
| // assert(left->getType() == Int32Ty); |
| // assert(right->getType() == Int32Ty); |
| // TODO(sarkin): Check that op is not division / remainder. |
| left = builder_.CreateTrunc(left, Int32Ty); |
| right = builder_.CreateTrunc(right, Int32Ty); |
| return EmitIntegerArithmetic(op, left, right); |
| } |
| |
| llvm::Value* CodegenFunction::EmitUnboxUint32(InstructionInputExtractor I) { |
| auto val_cid = I.NextInputAsIntPtr(); |
| auto val = GetValue(I.NextInput()); |
| |
| // TODO(sarkin): Only Smis and Mints for now. |
| // assert(val_cid == kSmiCid || val_cid == kMintCid); |
| |
| if (val_cid == kSmiCid) { |
| return helper_.EmitUntagSmi(val); |
| } else if (val_cid == kMintCid) { |
| return helper_.EmitLoadField(val, kMintValueOffset, Int64Ty); |
| } else { |
| // TODO(sarkin): |
| return helper_.EmitUntagSmi(val); |
| } |
| |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitUnboxedIntConverter(InstructionInputExtractor I) { |
| // TODO(sarkin): Not all conversions implemented. |
| auto from = I.NextInputAsEnum<Representation>(); |
| auto to = I.NextInputAsEnum<Representation>(); |
| auto val = GetValue(I.NextInput()); |
| |
| if (from == kUnboxedUint32 && to == kUnboxedInt64) { |
| return builder_.CreateZExt(val, Int64Ty); |
| } else if (to == kUnboxedUint32 && from == kUnboxedInt64) { |
| return builder_.CreateTrunc(val, Int32Ty); |
| } |
| assert(false); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitShiftInt64Op(InstructionInputExtractor I) { |
| // TODO(sarkin): |
| auto op = I.NextInputAsEnum<TokenKind>(); |
| auto left = GetValue(I.NextInput()); |
| auto right = GetValue(I.NextInput()); |
| |
| if (op == kSHL) { |
| return builder_.CreateShl(left, right); |
| } |
| return builder_.CreateAShr(left, right); |
| } |
| |
| llvm::Value* CodegenFunction::EmitCheckSmi(InstructionInputExtractor I) { |
| // TODO(sarkin): |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitBinarySmiOp(InstructionInputExtractor I) { |
| // TODO(sarkin): |
| auto op = I.NextInputAsEnum<TokenKind>(); |
| auto left = GetValue(I.NextInput()); |
| auto right = GetValue(I.NextInput()); |
| |
| left = helper_.EmitSmiToInt(left); |
| right = helper_.EmitSmiToInt(right); |
| if (op == kBIT_AND) { |
| return helper_.EmitNewObject(builder_.CreateAnd(left, right)); |
| } |
| assert(false); |
| return nullptr; |
| } |
| |
| llvm::Value* CodegenFunction::EmitCid(llvm::Value* val, bool check_smi) { |
| auto get_cid = [&](auto val) { |
| // Offset in bytes |
| const int kCidOffset = 2; |
| auto cid = helper_.EmitLoadField(val, kCidOffset, Int16Ty); |
| return builder_.CreateZExt(cid, IntTy); |
| }; |
| |
| if (!check_smi) { |
| return get_cid(val); |
| } |
| |
| auto smi_bb = helper_.CreateBasicBlock("smi"); |
| auto not_smi_bb = helper_.CreateBasicBlock("not_smi"); |
| auto join_bb = helper_.CreateBasicBlock("join"); |
| |
| builder_.CreateCondBr(helper_.EmitIsNotSmi(val), not_smi_bb, smi_bb); |
| |
| builder_.SetInsertPoint(smi_bb); |
| auto cid_smi = GetConstantInt(kSmiCid); |
| builder_.CreateBr(join_bb); |
| |
| builder_.SetInsertPoint(not_smi_bb); |
| auto cid_not_smi = get_cid(val); |
| builder_.CreateBr(join_bb); |
| |
| builder_.SetInsertPoint(join_bb); |
| auto cid = builder_.CreatePHI(IntTy, 2); |
| cid->addIncoming(cid_smi, smi_bb); |
| cid->addIncoming(cid_not_smi, not_smi_bb); |
| |
| return cid; |
| } |
| |
| void CodegenFunction::EmitInstruction(const ILInstruction* instr) { |
| llvm::Value* produced_value = nullptr; |
| switch (instr->tag()) { |
| #define CASE(Name) case ILInstruction::Tag::k##Name: |
| #define EMIT(Name) \ |
| produced_value = Emit##Name(instr); \ |
| break; |
| #define CASE_EMIT(Name, ...) CASE(Name) EMIT(Name) |
| FOR_EACH_SUPPORTED_INSTRUCTION(CASE_EMIT) |
| default: |
| assert(false); |
| #undef CASE |
| #undef EMIT |
| #undef CASE_EMIT |
| } |
| if (instr->is_value()) { |
| assert(produced_value != nullptr); |
| AddValue(instr->value_id(), produced_value); |
| return; |
| } |
| } |
| |
| void CodegenFunction::AddValue(std::string value_id, llvm::Value* value) { |
| assert(value_id == "v-1" || values_.find(value_id) == values_.end()); |
| values_[value_id] = value; |
| } |
| |
| llvm::Type* CodegenFunction::GetTypeFromRepresentation( |
| Representation representation) const { |
| // TODO(sarkin): Handle all representations. |
| switch (representation) { |
| case kTagged: |
| return ObjectPtrTy; |
| case kUnboxedInt32: |
| case kUnboxedUint32: |
| return Int32Ty; |
| case kUnboxedInt64: |
| return Int64Ty; |
| case kUnboxedDouble: |
| return DoubleTy; |
| case kUnboxedFloat32x4: |
| return Float32x4Ty; |
| case kUnboxedFloat64x2: |
| return Float64x2Ty; |
| case kUnboxedInt32x4: |
| return Int32x4Ty; |
| |
| default: |
| assert(false); |
| } |
| return nullptr; |
| } |
| |
| llvm::Type* CodegenFunction::GetUnboxedTypeForCid(int cid) const { |
| if (cid == kSmiCid || cid == kMintCid) { |
| return Int64Ty; |
| } |
| if (cid == kDoubleCid) { |
| return DoubleTy; |
| } |
| if (cid == kFloat32x4Cid) { |
| return Float32x4Ty; |
| } |
| if (cid == kFloat64x2Cid) { |
| return Float64x2Ty; |
| } |
| assert(false); |
| return nullptr; |
| } |
| |
| intptr_t CodegenFunction::GetValueOffsetForCid(int cid) const { |
| if (cid == kMintCid) { |
| return kMintValueOffset; |
| } |
| if (cid == kDoubleCid) { |
| return kDoubleValueOffset; |
| } |
| if (cid == kFloat32x4Cid) { |
| return kFloat32x4ValueOffset; |
| } |
| if (cid == kFloat64x2Cid) { |
| return kFloat64x2ValueOffset; |
| } |
| assert(false); |
| return -1; |
| } |
| |
| llvm::Value* CodegenFunction::GetValue(const std::string& value_id) const { |
| auto it = values_.find(value_id); |
| assert(it != values_.end()); |
| return it->second; |
| } |
| |
| llvm::Value* CodegenFunction::GetValueOrConstant( |
| const std::string& value_id) const { |
| assert(!value_id.empty()); |
| if (ILInstruction::IsValue(value_id)) { |
| return GetValue(value_id); |
| } |
| return GetConstantInt(std::stoll(value_id)); |
| } |
| |
| llvm::BasicBlock* CodegenFunction::GetBlock(intptr_t block_id) const { |
| auto it = blocks_.find(block_id); |
| assert(it != blocks_.end()); |
| return it->second; |
| } |
| |
| llvm::BasicBlock* CodegenFunction::GetFinalBlock(intptr_t block_id) const { |
| auto it = final_blocks_.find(block_id); |
| assert(it != final_blocks_.end()); |
| return it->second; |
| } |
| |
| void CodegenFunction::ProcessPhiIncomingValues() { |
| for (auto instr : phi_instructions_) { |
| llvm::PHINode* phi_node = |
| llvm::cast<llvm::PHINode>(GetValue(instr->value_id())); |
| const int kEntrySize = 2; |
| assert(instr->NumInputs() > 1 + kEntrySize /* 1 for the representation */); |
| assert((instr->NumInputs() - 1) % kEntrySize == 0); |
| size_t num_income_values = instr->NumInputs() / kEntrySize; |
| for (size_t i = 0; i < num_income_values; ++i) { |
| auto value = GetValue(instr->InputAt(i * 2 + 1)); |
| auto block = GetFinalBlock(std::stoull(instr->InputAt((i + 1) * 2))); |
| phi_node->addIncoming(value, block); |
| } |
| } |
| } |
| |
| } // namespace dart_llvm |