| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "exprs/slot-ref.h" |
| |
| #include <limits> |
| #include <sstream> |
| |
| #include "codegen/codegen-anyval.h" |
| #include "codegen/llvm-codegen.h" |
| #include "exprs/scalar-expr-evaluator.h" |
| #include "gen-cpp/Exprs_types.h" |
| #include "llvm/IR/BasicBlock.h" |
| #include "runtime/collection-value.h" |
| #include "runtime/decimal-value.h" |
| #include "runtime/fragment-state.h" |
| #include "runtime/multi-precision.h" |
| #include "runtime/runtime-state.h" |
| #include "runtime/string-value.inline.h" |
| #include "runtime/timestamp-value.h" |
| #include "runtime/tuple-row.h" |
| |
| #include "common/names.h" |
| |
| using namespace impala_udf; |
| |
| namespace impala { |
| |
| const char* SlotRef::LLVM_CLASS_NAME = "class.impala::SlotRef"; |
| |
| SlotRef::SlotRef(const TExprNode& node) |
| : ScalarExpr(node), |
| slot_offset_(-1), // invalid |
| null_indicator_offset_(0, 0), |
| slot_id_(node.slot_ref.slot_id) { |
| // slot_/null_indicator_offset_ are set in Init() |
| } |
| |
| SlotRef::SlotRef(const SlotDescriptor* desc) |
| : ScalarExpr(desc->type(), false), |
| slot_offset_(-1), |
| null_indicator_offset_(0, 0), |
| slot_id_(desc->id()) { |
| // slot_/null_indicator_offset_ are set in Init() |
| } |
| |
| SlotRef::SlotRef(const SlotDescriptor* desc, const ColumnType& type) |
| : ScalarExpr(type, false), |
| slot_offset_(-1), |
| null_indicator_offset_(0, 0), |
| slot_id_(desc->id()) { |
| // slot_/null_indicator_offset_ are set in Init() |
| } |
| |
| SlotRef::SlotRef(const ColumnType& type, int offset, const bool nullable /* = false */) |
| : ScalarExpr(type, false), |
| tuple_idx_(0), |
| slot_offset_(offset), |
| null_indicator_offset_(0, nullable ? offset : -1), |
| slot_id_(-1) {} |
| |
| SlotRef* SlotRef::TypeSafeCreate(const SlotDescriptor* desc) { |
| if (desc->type().type == TYPE_NULL) { |
| // ScalarExprEvaluator requires a non-null type for the expr. It returns nullptr for |
| // null values of all types. So replacing TYPE_NULL to an arbitrary type is ok. |
| // Here we use TYPE_BOOLEAN for consistency with other places. |
| return new SlotRef(desc, ColumnType(TYPE_BOOLEAN)); |
| } |
| return new SlotRef(desc); |
| } |
| |
| Status SlotRef::Init( |
| const RowDescriptor& row_desc, bool is_entry_point, FragmentState* state) { |
| DCHECK(type_.IsStructType() || children_.size() == 0); |
| RETURN_IF_ERROR(ScalarExpr::Init(row_desc, is_entry_point, state)); |
| if (slot_id_ != -1) { |
| slot_desc_ = state->desc_tbl().GetSlotDescriptor(slot_id_); |
| if (slot_desc_ == NULL) { |
| // TODO: create macro MAKE_ERROR() that returns a stream |
| stringstream error; |
| error << "couldn't resolve slot descriptor " << slot_id_; |
| LOG(INFO) << error.str(); |
| return Status(error.str()); |
| } |
| if (slot_desc_->parent()->isTupleOfStructSlot()) { |
| tuple_idx_ = row_desc.GetTupleIdx(slot_desc_->parent()->getMasterTuple()->id()); |
| } else { |
| tuple_idx_ = row_desc.GetTupleIdx(slot_desc_->parent()->id()); |
| } |
| if (tuple_idx_ == RowDescriptor::INVALID_IDX) { |
| TupleDescriptor* d = |
| state->desc_tbl().GetTupleDescriptor(slot_desc_->parent()->id()); |
| string error = Substitute("invalid tuple_idx: $0\nparent=$1\nrow=$2", |
| slot_desc_->DebugString(), d->DebugString(), row_desc.DebugString()); |
| LOG(INFO) << error; |
| return Status(error); |
| } |
| DCHECK(tuple_idx_ != RowDescriptor::INVALID_IDX); |
| tuple_is_nullable_ = slot_desc_->parent()->isTupleOfStructSlot() ? |
| false : row_desc.TupleIsNullable(tuple_idx_); |
| slot_offset_ = slot_desc_->tuple_offset(); |
| null_indicator_offset_ = slot_desc_->null_indicator_offset(); |
| } |
| return Status::OK(); |
| } |
| |
| int SlotRef::GetSlotIds(vector<SlotId>* slot_ids) const { |
| if (slot_ids != nullptr) slot_ids->push_back(slot_id_); |
| return 1; |
| } |
| |
| string SlotRef::DebugString() const { |
| stringstream out; |
| out << "SlotRef(slot_id=" << slot_id_ |
| << " tuple_idx=" << tuple_idx_ << " slot_offset=" << slot_offset_ |
| << " tuple_is_nullable=" << tuple_is_nullable_ |
| << " null_indicator=" << null_indicator_offset_ |
| << ScalarExpr::DebugString() << ")"; |
| return out.str(); |
| } |
| |
| void SlotRef::AssignFnCtxIdx(int* next_fn_ctx_idx) { |
| if (!type_.IsStructType()) { |
| ScalarExpr::AssignFnCtxIdx(next_fn_ctx_idx); |
| return; |
| } |
| fn_ctx_idx_start_ = *next_fn_ctx_idx; |
| fn_ctx_idx_ = 0; |
| fn_ctx_idx_end_ = 1; |
| for (ScalarExpr* child : children()) child->AssignFnCtxIdx(next_fn_ctx_idx); |
| } |
| |
| // There are four possible cases we may generate: |
| // 1. Tuple is non-nullable and slot is non-nullable |
| // 2. Tuple is non-nullable and slot is nullable |
| // 3. Tuple is nullable and slot is non-nullable (when the aggregate output is the |
| // "nullable" side of an outer join). |
| // 4. Tuple is nullable and slot is nullable |
| // |
| // Resulting IR for a bigint slotref: |
| // (Note: some of the GEPs that look like no-ops are because certain offsets are 0 |
| // in this slot descriptor.) |
| // |
| // define { i8, i64 } @GetSlotRef.22( |
| // %"class.impala::ScalarExprEvaluator"* %eval, %"class.impala::TupleRow"* %row) #51 { |
| // entry: |
| // %cast_row_ptr = bitcast %"class.impala::TupleRow"* %row to i8** |
| // %tuple_ptr_addr = getelementptr inbounds i8*, i8** %cast_row_ptr, i32 0 |
| // %tuple_ptr = load i8*, i8** %tuple_ptr_addr |
| // br label %check_tuple_null |
| // |
| // check_tuple_null: ; preds = %entry |
| // %tuple_is_null = icmp eq i8* %tuple_ptr, null |
| // br i1 %tuple_is_null, label %null_block, label %check_slot_null |
| // |
| // check_slot_null: ; preds = %check_tuple_null |
| // %null_byte_ptr = getelementptr inbounds i8, i8* %tuple_ptr, i32 8 |
| // %null_byte = load i8, i8* %null_byte_ptr |
| // %null_mask = and i8 %null_byte, 1 |
| // %is_null = icmp ne i8 %null_mask, 0 |
| // br i1 %is_null, label %null_block, label %read_slot |
| // |
| // read_slot: ; preds = %check_slot_null |
| // %slot_addr = getelementptr inbounds i8, i8* %tuple_ptr, i32 0 |
| // %val_ptr = bitcast i8* %slot_addr to i64* |
| // %val = load i64, i64* %val_ptr |
| // br label %produce_value |
| // |
| // null_block: ; preds = %check_slot_null, %check_tuple_null |
| // br label %produce_value |
| // |
| // produce_value: ; preds = %read_slot, %null_block |
| // %is_null_phi = phi i8 [ 1, %null_block ], [ 0, %read_slot ] |
| // %val_phi = phi i64 [ 0, %null_block ], [ %val, %read_slot ] |
| // %result = insertvalue { i8, i64 } zeroinitializer, i64 %val_phi, 1 |
| // %result1 = insertvalue { i8, i64 } %result, i8 %is_null_phi, 0 |
| // ret { i8, i64 } %result1 |
| // } |
| // |
| // Produced for the following query: |
| // select t1.int_col, t2.bigint_col |
| // from functional.alltypestiny t1 left outer join functional.alltypestiny t2 |
| // on t1.int_col = t2.bigint_col |
| // order by bigint_col; |
| // |
| // TODO: We could generate a typed struct (and not a char*) for Tuple for llvm. We know |
| // the types from the TupleDesc. It will likely make this code simpler to reason about. |
| Status SlotRef::GetCodegendComputeFnImpl(LlvmCodeGen* codegen, llvm::Function** fn) { |
| // SlotRefs are based on the slot_id and tuple_idx. Combine them to make a |
| // query-wide unique id. We also need to combine whether the tuple is nullable. For |
| // example, in an outer join the scan node could have the same tuple id and slot id |
| // as the join node. When the slot is being used in the scan-node, the tuple is |
| // non-nullable. Used in the join node (and above in the plan tree), it is nullable. |
| // TODO: can we do something better. |
| constexpr int64_t TUPLE_NULLABLE_MASK = numeric_limits<int64_t>::min(); |
| int64_t unique_slot_id = slot_id_ | ((int64_t)tuple_idx_) << 32; |
| DCHECK_EQ(unique_slot_id & TUPLE_NULLABLE_MASK, 0); |
| if (tuple_is_nullable_) unique_slot_id |= TUPLE_NULLABLE_MASK; |
| llvm::Function* ir_compute_fn_ = codegen->GetRegisteredExprFn(unique_slot_id); |
| if (ir_compute_fn_ != NULL) { |
| *fn = ir_compute_fn_; |
| return Status::OK(); |
| } |
| |
| llvm::Value* args[2]; |
| *fn = CreateIrFunctionPrototype("GetSlotRef", codegen, &args); |
| llvm::Value* eval_ptr = args[0]; |
| llvm::Value* row_ptr = args[1]; |
| |
| LlvmBuilder builder(codegen->context()); |
| CodegenAnyVal result_value = CodegenValue(codegen, &builder, *fn, eval_ptr, row_ptr); |
| builder.CreateRet(result_value.GetLoweredValue()); |
| |
| *fn = codegen->FinalizeFunction(*fn); |
| if (UNLIKELY(*fn == NULL)) return Status(TErrorCode::IR_VERIFY_FAILED, "SlotRef"); |
| codegen->RegisterExprFn(unique_slot_id, *fn); |
| return Status::OK(); |
| } |
| |
| // Generates null checking code: null checking may be generated for the tuple and for the |
| // slot based on 'tuple_is_nullable' and 'slot_is_nullable'. If any one of the checks |
| // returns null, control is transferred to 'next_block_if_null', otherwise to |
| // 'next_block_if_not_null. |
| void SlotRef::CodegenNullChecking(LlvmCodeGen* codegen, LlvmBuilder* builder, |
| llvm::Function* fn, llvm::BasicBlock* next_block_if_null, |
| llvm::BasicBlock* next_block_if_not_null, llvm::Value* tuple_ptr) { |
| bool slot_is_nullable = null_indicator_offset_.bit_mask != 0; |
| llvm::BasicBlock* const check_tuple_null_block = tuple_is_nullable_ ? |
| llvm::BasicBlock::Create(codegen->context(), "check_tuple_null", |
| fn, /* insert before */ next_block_if_not_null) |
| : nullptr; |
| llvm::BasicBlock* const check_slot_null_block = slot_is_nullable ? |
| llvm::BasicBlock::Create(codegen->context(), "check_slot_null", |
| fn, /* insert before */ next_block_if_not_null) |
| : nullptr; |
| |
| // Check if tuple* is null only if the tuple is nullable |
| if (tuple_is_nullable_) { |
| builder->CreateBr(check_tuple_null_block); |
| builder->SetInsertPoint(check_tuple_null_block); |
| llvm::Value* tuple_is_null = builder->CreateIsNull(tuple_ptr, "tuple_is_null"); |
| // Check slot is null only if the null indicator bit is set |
| if (slot_is_nullable) { |
| builder->CreateCondBr(tuple_is_null, next_block_if_null, check_slot_null_block); |
| } else { |
| builder->CreateCondBr(tuple_is_null, next_block_if_null, next_block_if_not_null); |
| } |
| } else { |
| if (slot_is_nullable) { |
| builder->CreateBr(check_slot_null_block); |
| } else { |
| builder->CreateBr(next_block_if_not_null); |
| } |
| } |
| |
| // Branch for tuple* != NULL. Need to check if null-indicator is set |
| if (slot_is_nullable) { |
| builder->SetInsertPoint(check_slot_null_block); |
| llvm::Value* is_slot_null = SlotDescriptor::CodegenIsNull( |
| codegen, builder, null_indicator_offset_, tuple_ptr); |
| builder->CreateCondBr(is_slot_null, next_block_if_null, next_block_if_not_null); |
| } |
| } |
| |
| // Codegens reading the members of a StringVal or a CollectionVal from the slot pointed to |
| // by 'val_ptr'. Returns the resulting values in '*ptr' and '*len'. |
| void CodegenReadingStringOrCollectionVal(LlvmCodeGen* codegen, LlvmBuilder* builder, |
| const ColumnType& type, llvm::Value* val_ptr, llvm::Value** ptr, llvm::Value** len) { |
| if (type.IsVarLenStringType()) { |
| llvm::Function* str_ptr_fn = codegen->GetFunction( |
| IRFunction::STRING_VALUE_PTR, false); |
| llvm::Function* str_len_fn = codegen->GetFunction( |
| IRFunction::STRING_VALUE_LEN, false); |
| |
| *ptr = builder->CreateCall(str_ptr_fn, |
| llvm::ArrayRef<llvm::Value*>({val_ptr}), "ptr"); |
| *len = builder->CreateCall(str_len_fn, |
| llvm::ArrayRef<llvm::Value*>({val_ptr}), "len"); |
| } else if (type.IsCollectionType()) { |
| llvm::Value* ptr_ptr = builder->CreateStructGEP(nullptr, val_ptr, 0, "ptr_ptr"); |
| *ptr = builder->CreateLoad(ptr_ptr, "ptr"); |
| llvm::Value* len_ptr = builder->CreateStructGEP(nullptr, val_ptr, 1, "len_ptr"); |
| *len = builder->CreateLoad(len_ptr, "len"); |
| } else { |
| DCHECK(type.type == TYPE_CHAR || type.type == TYPE_FIXED_UDA_INTERMEDIATE); |
| // ptr and len are the slot and its fixed length. |
| *ptr = builder->CreateBitCast(val_ptr, codegen->ptr_type()); |
| *len = codegen->GetI32Constant(type.len); |
| } |
| } |
| |
| void CodegenReadingTimestamp(LlvmCodeGen* codegen, LlvmBuilder* builder, |
| llvm::Value* val_ptr, llvm::Value** time_of_day, llvm::Value** date) { |
| llvm::Value* time_of_day_ptr = |
| builder->CreateStructGEP(nullptr, val_ptr, 0, "time_of_day_ptr"); |
| // Cast boost::posix_time::time_duration to i64 |
| llvm::Value* time_of_day_cast = |
| builder->CreateBitCast(time_of_day_ptr, codegen->i64_ptr_type()); |
| *time_of_day = builder->CreateLoad(time_of_day_cast, "time_of_day"); |
| llvm::Value* date_ptr = builder->CreateStructGEP(nullptr, val_ptr, 1, "date_ptr"); |
| // Cast boost::gregorian::date to i32 |
| llvm::Value* date_cast = |
| builder->CreateBitCast(date_ptr, codegen->i32_ptr_type()); |
| *date = builder->CreateLoad(date_cast, "date"); |
| } |
| |
| CodegenAnyValReadWriteInfo SlotRef::CodegenReadSlot(LlvmCodeGen* codegen, |
| LlvmBuilder* builder, |
| llvm::Value* eval_ptr, llvm::Value* row_ptr, llvm::BasicBlock* entry_block, |
| llvm::BasicBlock* null_block, llvm::BasicBlock* read_slot_block, |
| llvm::Value* tuple_ptr, llvm::Value* slot_offset) { |
| builder->SetInsertPoint(read_slot_block); |
| llvm::Value* slot_ptr = builder->CreateInBoundsGEP(tuple_ptr, slot_offset, "slot_addr"); |
| |
| // This is not used for structs because the child expressions have their own slot |
| // pointers and we only read through those, not through the struct slot pointer. |
| llvm::Value* val_ptr = type_.IsStructType() ? nullptr : builder->CreateBitCast(slot_ptr, |
| codegen->GetSlotPtrType(type_), "val_ptr"); |
| |
| // For structs the code that reads the value consists of multiple basic blocks, so the |
| // block that should branch to 'produce_value_block' is not 'read_slot_block'. This |
| // variable is set to the appropriate block. |
| llvm::BasicBlock* non_null_incoming_block = read_slot_block; |
| |
| CodegenAnyValReadWriteInfo res(codegen, builder, type_); |
| |
| // Depending on the type, create phi nodes for each value needed to populate the return |
| // *Val. The optimizer does a better job when there is a phi node for each value, rather |
| // than having read_slot_block generate an AnyVal and having a single phi node over |
| // that. |
| if (type_.IsStructType()) { |
| llvm::Function* fn = builder->GetInsertBlock()->getParent(); |
| std::size_t num_children = children_.size(); |
| for (std::size_t i = 0; i < num_children; ++i) { |
| ScalarExpr* child_expr = children_[i]; |
| DCHECK(child_expr != nullptr); |
| SlotRef* child_slot_ref = dynamic_cast<SlotRef*>(child_expr); |
| DCHECK(child_slot_ref != nullptr); |
| |
| llvm::Function* const get_child_eval_fn = |
| codegen->GetFunction(IRFunction::GET_CHILD_EVALUATOR, false); |
| |
| llvm::BasicBlock* child_entry_block = llvm::BasicBlock::Create(codegen->context(), |
| "child_entry", fn); |
| builder->SetInsertPoint(child_entry_block); |
| llvm::Value* child_eval = builder->CreateCall(get_child_eval_fn, |
| {eval_ptr, codegen->GetI32Constant(i)}, "child_eval"); |
| CodegenAnyValReadWriteInfo codegen_anyval_info = |
| child_slot_ref->CreateCodegenAnyValReadWriteInfo(codegen, builder, fn, |
| child_eval, row_ptr, child_entry_block); |
| res.children().push_back(codegen_anyval_info); |
| } |
| } else if (type_.IsStringType() || type_.type == TYPE_FIXED_UDA_INTERMEDIATE |
| || type_.IsCollectionType()) { |
| llvm::Value* ptr; |
| llvm::Value* len; |
| CodegenReadingStringOrCollectionVal(codegen, builder, type_, val_ptr, |
| &ptr, &len); |
| DCHECK(ptr != nullptr); |
| DCHECK(len != nullptr); |
| res.SetPtrAndLen(ptr, len); |
| } else if (type_.type == TYPE_TIMESTAMP) { |
| llvm::Value* time_of_day; |
| llvm::Value* date; |
| CodegenReadingTimestamp(codegen, builder, val_ptr, &time_of_day, &date); |
| DCHECK(time_of_day != nullptr); |
| DCHECK(date != nullptr); |
| res.SetTimeAndDate(time_of_day, date); |
| } else { |
| res.SetSimpleVal(builder->CreateLoad(val_ptr, "val")); |
| } |
| |
| res.SetFnCtxIdx(fn_ctx_idx_); |
| res.SetEval(eval_ptr); |
| res.SetBlocks(entry_block, null_block, non_null_incoming_block); |
| return res; |
| } |
| |
| /// Generates code to read the value corresponding to this 'SlotRef' into a *Val. Returns |
| /// a 'CodegenAnyVal' containing the read value. No return statement is generated in the |
| /// code, so this function can be called recursively for structs without putting function |
| /// calls in the generated code. |
| /// |
| /// The generated code can be conceptually divided into the following parts: |
| /// 1. Find and load the tuple address (the entry block) |
| /// 2. Null checking (blocks 'check_tuple_null', 'check_slot_null' and 'null_block') |
| /// 3. Reading the actual non-null value from its slot (the 'read_slot' block) |
| /// 4. Produce a final *Val value, whether it is null or not (the 'produce_value' block) |
| /// |
| /// Number 1. is straightforward. |
| /// |
| /// Null checking may involve checking the tuple, checking the null indicators for the |
| /// slot, both or none, depending on what is nullable. If any null check returns true, |
| /// control branches to 'null_block'. This basic block will be used in PHI nodes as an |
| /// incoming branch indicating that the *Val is null. |
| /// |
| /// If all null checks return false, control is transferred to the 'read_slot' block. Here |
| /// we actually read the value from the slot. This may involve several loads yielding |
| /// different parts of the value, for example the pointer and the length of a StringValue. |
| /// If the value is a struct, we recurse to / read the struct members - this produces |
| /// additional basic blocks. |
| /// |
| /// The final block, 'produce_value' is used to create the final *Val. This block unites |
| /// the null and the non-null paths. It has PHI nodes for each value part (ptr, len etc.) |
| /// from 'null_block' and 'read_slot' (or one of its descendants in case of structs). If |
| /// control reaches here from 'null_block', the *Val is set to null, otherwise to the |
| /// value read from the slot. |
| CodegenAnyVal SlotRef::CodegenValue(LlvmCodeGen* codegen, LlvmBuilder* builder, |
| llvm::Function* fn, llvm::Value* eval_ptr, llvm::Value* row_ptr, |
| llvm::BasicBlock* entry_block) { |
| CodegenAnyValReadWriteInfo read_write_info = CreateCodegenAnyValReadWriteInfo(codegen, |
| builder, fn, eval_ptr, row_ptr, entry_block); |
| return CodegenAnyVal::CreateFromReadWriteInfo(read_write_info); |
| } |
| |
| CodegenAnyValReadWriteInfo SlotRef::CreateCodegenAnyValReadWriteInfo( |
| LlvmCodeGen* codegen, LlvmBuilder* builder, llvm::Function* fn, |
| llvm::Value* eval_ptr, llvm::Value* row_ptr, llvm::BasicBlock* entry_block) { |
| llvm::IRBuilderBase::InsertPoint ip = builder->saveIP(); |
| |
| llvm::Value* tuple_offset = codegen->GetI32Constant(tuple_idx_); |
| llvm::Value* slot_offset = codegen->GetI32Constant(slot_offset_); |
| |
| llvm::LLVMContext& context = codegen->context(); |
| |
| // Create the necessary basic blocks. |
| if (entry_block == nullptr) { |
| entry_block = llvm::BasicBlock::Create(context, "entry", fn); |
| } |
| |
| llvm::BasicBlock* read_slot_block = llvm::BasicBlock::Create(context, "read_slot", fn); |
| |
| // We use this block to collect all code paths that lead to the result being null. It |
| // does nothing but branches unconditionally to 'produce_value_block' and the PHI nodes |
| // there can add this block as an incoming branch for the null case; it is simpler and |
| // more readable than having many predeccesor blocks for the null case in |
| // 'produce_value_block'. |
| llvm::BasicBlock* null_block = llvm::BasicBlock::Create(context, "null_block", fn); |
| |
| /// Start generating instructions. |
| //### Part 1: find the tuple address. |
| builder->SetInsertPoint(entry_block); |
| // Get the tuple offset addr from the row |
| llvm::Value* cast_row_ptr = builder->CreateBitCast( |
| row_ptr, codegen->ptr_ptr_type(), "cast_row_ptr"); |
| llvm::Value* tuple_ptr_addr = |
| builder->CreateInBoundsGEP(cast_row_ptr, tuple_offset, "tuple_ptr_addr"); |
| // Load the tuple* |
| llvm::Value* tuple_ptr = builder->CreateLoad(tuple_ptr_addr, "tuple_ptr"); |
| |
| //### Part 2: null checking |
| CodegenNullChecking(codegen, builder, fn, null_block, read_slot_block, tuple_ptr); |
| |
| //### Part 3: read non-null value. |
| CodegenAnyValReadWriteInfo res = CodegenReadSlot(codegen, builder, eval_ptr, |
| row_ptr, entry_block, null_block, read_slot_block, tuple_ptr, slot_offset); |
| |
| builder->restoreIP(ip); |
| return res; |
| } |
| |
| #define SLOT_REF_GET_FUNCTION(type_lit, type_val, type_c) \ |
| type_val SlotRef::Get##type_val##Interpreted( \ |
| ScalarExprEvaluator* eval, const TupleRow* row) const { \ |
| DCHECK_EQ(type_.type, type_lit); \ |
| Tuple* t = row->GetTuple(tuple_idx_); \ |
| if (t == NULL || t->IsNull(null_indicator_offset_)) return type_val::null(); \ |
| return type_val(*reinterpret_cast<type_c*>(t->GetSlot(slot_offset_))); \ |
| } |
| |
| SLOT_REF_GET_FUNCTION(TYPE_BOOLEAN, BooleanVal, bool); |
| SLOT_REF_GET_FUNCTION(TYPE_TINYINT, TinyIntVal, int8_t); |
| SLOT_REF_GET_FUNCTION(TYPE_SMALLINT, SmallIntVal, int16_t); |
| SLOT_REF_GET_FUNCTION(TYPE_INT, IntVal, int32_t); |
| SLOT_REF_GET_FUNCTION(TYPE_BIGINT, BigIntVal, int64_t); |
| SLOT_REF_GET_FUNCTION(TYPE_FLOAT, FloatVal, float); |
| SLOT_REF_GET_FUNCTION(TYPE_DOUBLE, DoubleVal, double); |
| |
| StringVal SlotRef::GetStringValInterpreted( |
| ScalarExprEvaluator* eval, const TupleRow* row) const { |
| DCHECK(type_.IsStringType() || type_.type == TYPE_FIXED_UDA_INTERMEDIATE); |
| Tuple* t = row->GetTuple(tuple_idx_); |
| if (t == NULL || t->IsNull(null_indicator_offset_)) return StringVal::null(); |
| StringVal result; |
| if (type_.type == TYPE_CHAR || type_.type == TYPE_FIXED_UDA_INTERMEDIATE) { |
| result.ptr = reinterpret_cast<uint8_t*>(t->GetSlot(slot_offset_)); |
| result.len = type_.len; |
| } else { |
| StringValue* sv = reinterpret_cast<StringValue*>(t->GetSlot(slot_offset_)); |
| sv->ToStringVal(&result); |
| } |
| return result; |
| } |
| |
| TimestampVal SlotRef::GetTimestampValInterpreted( |
| ScalarExprEvaluator* eval, const TupleRow* row) const { |
| DCHECK_EQ(type_.type, TYPE_TIMESTAMP); |
| Tuple* t = row->GetTuple(tuple_idx_); |
| if (t == NULL || t->IsNull(null_indicator_offset_)) return TimestampVal::null(); |
| TimestampValue* tv = reinterpret_cast<TimestampValue*>(t->GetSlot(slot_offset_)); |
| TimestampVal result; |
| tv->ToTimestampVal(&result); |
| return result; |
| } |
| |
| DecimalVal SlotRef::GetDecimalValInterpreted( |
| ScalarExprEvaluator* eval, const TupleRow* row) const { |
| DCHECK_EQ(type_.type, TYPE_DECIMAL); |
| Tuple* t = row->GetTuple(tuple_idx_); |
| if (t == NULL || t->IsNull(null_indicator_offset_)) return DecimalVal::null(); |
| switch (type_.GetByteSize()) { |
| case 4: |
| return DecimalVal(*reinterpret_cast<int32_t*>(t->GetSlot(slot_offset_))); |
| case 8: |
| return DecimalVal(*reinterpret_cast<int64_t*>(t->GetSlot(slot_offset_))); |
| case 16: |
| // Avoid an unaligned load by using memcpy |
| __int128_t val; |
| memcpy(&val, t->GetSlot(slot_offset_), sizeof(val)); |
| return DecimalVal(val); |
| default: |
| DCHECK(false); |
| return DecimalVal::null(); |
| } |
| } |
| |
| DateVal SlotRef::GetDateValInterpreted( |
| ScalarExprEvaluator* eval, const TupleRow* row) const { |
| DCHECK_EQ(type_.type, TYPE_DATE); |
| Tuple* t = row->GetTuple(tuple_idx_); |
| if (t == nullptr || t->IsNull(null_indicator_offset_)) return DateVal::null(); |
| const DateValue dv(*reinterpret_cast<int32_t*>(t->GetSlot(slot_offset_))); |
| return dv.ToDateVal(); |
| } |
| |
| CollectionVal SlotRef::GetCollectionValInterpreted( |
| ScalarExprEvaluator* eval, const TupleRow* row) const { |
| DCHECK(type_.IsCollectionType()); |
| Tuple* t = row->GetTuple(tuple_idx_); |
| if (t == nullptr || t->IsNull(null_indicator_offset_)) return CollectionVal::null(); |
| CollectionValue* coll_value = |
| reinterpret_cast<CollectionValue*>(t->GetSlot(slot_offset_)); |
| return CollectionVal(coll_value->ptr, coll_value->num_tuples); |
| } |
| |
| StructVal SlotRef::GetStructValInterpreted( |
| ScalarExprEvaluator* eval, const TupleRow* row) const { |
| DCHECK(type_.IsStructType() && children_.size() > 0); |
| DCHECK_EQ(children_.size(), eval->GetChildEvaluators().size()); |
| Tuple* t = row->GetTuple(tuple_idx_); |
| if (t == nullptr || t->IsNull(null_indicator_offset_)) return StructVal::null(); |
| |
| FunctionContext* fn_ctx = eval->fn_context(fn_ctx_idx_); |
| DCHECK(fn_ctx != nullptr); |
| StructVal struct_val(fn_ctx, children_.size()); |
| vector<ScalarExprEvaluator*>& child_evaluators = eval->GetChildEvaluators(); |
| for (int i = 0; i < child_evaluators.size(); ++i) { |
| ScalarExpr* child_expr = children_[i]; |
| ScalarExprEvaluator* child_eval = child_evaluators[i]; |
| DCHECK(child_eval != nullptr); |
| void* child_val = child_eval->GetValue(*child_expr, row); |
| struct_val.addChild(child_val, i); |
| } |
| return struct_val; |
| } |
| |
| const TupleDescriptor* SlotRef::GetCollectionTupleDesc() const { |
| return slot_desc_->children_tuple_descriptor(); |
| } |
| |
| } // namespace impala |