blob: b5f758f1fa2a71d57d1b9e0d4dca9006ff011e7f [file] [log] [blame]
// 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 "runtime/collection-value.h"
#include "runtime/decimal-value.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 Prepare()
}
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 Prepare()
}
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 Prepare()
}
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) {}
Status SlotRef::Init(
const RowDescriptor& row_desc, bool is_entry_point, RuntimeState* state) {
DCHECK_EQ(children_.size(), 0);
RETURN_IF_ERROR(ScalarExpr::Init(row_desc, is_entry_point, state));
if (slot_id_ != -1) {
const SlotDescriptor* 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());
}
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_ = 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();
}
// 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(i8** %context, %"class.impala::TupleRow"* %row) {
// entry:
// %cast_row_ptr = bitcast %"class.impala::TupleRow"* %row to i8**
// %tuple_addr = getelementptr i8** %cast_row_ptr, i32 0
// %tuple_ptr = load i8** %tuple_addr
// br label %check_slot_null
//
// check_slot_null: ; preds = %entry
// %null_byte_ptr = getelementptr i8* %tuple_ptr, i32 0
// %null_byte = load i8* %null_ptr
// %null_byte_set = and i8 %null_byte, 2
// %is_null = icmp ne i8 %null_byte_set, 0
// br i1 %is_null, label %ret, label %get_slot
//
// get_slot: ; preds = %check_slot_null
// %slot_addr = getelementptr i8* %tuple_ptr, i32 8
// %val_ptr = bitcast i8* %slot_addr to i64*
// %val = load i64* %val_ptr
// br label %ret
//
// ret: ; preds = %get_slot, %check_slot_null
// %is_null_phi = phi i8 [ 1, %check_slot_null ], [ 0, %get_slot ]
// %val_phi = phi i64 [ 0, %check_slot_null ], [ %val, %get_slot ]
// %result = insertvalue { i8, i64 } zeroinitializer, i8 %is_null_phi, 0
// %result1 = insertvalue { i8, i64 } %result, i64 %val_phi, 1
// ret { i8, i64 } %result1
// }
//
// 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) {
DCHECK_EQ(GetNumChildren(), 0);
// 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::LLVMContext& context = codegen->context();
llvm::Value* args[2];
*fn = CreateIrFunctionPrototype("GetSlotRef", codegen, &args);
llvm::Value* row_ptr = args[1];
llvm::Value* tuple_offset = codegen->GetI32Constant(tuple_idx_);
llvm::Value* slot_offset = codegen->GetI32Constant(slot_offset_);
llvm::Value* zero = codegen->GetI8Constant(0);
llvm::Value* one = codegen->GetI8Constant(1);
llvm::BasicBlock* entry_block = llvm::BasicBlock::Create(context, "entry", *fn);
bool slot_is_nullable = null_indicator_offset_.bit_mask != 0;
llvm::BasicBlock* check_slot_null_indicator_block = NULL;
if (slot_is_nullable) {
check_slot_null_indicator_block =
llvm::BasicBlock::Create(context, "check_slot_null", *fn);
}
llvm::BasicBlock* get_slot_block = llvm::BasicBlock::Create(context, "get_slot", *fn);
llvm::BasicBlock* ret_block = llvm::BasicBlock::Create(context, "ret", *fn);
LlvmBuilder builder(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_addr =
builder.CreateInBoundsGEP(cast_row_ptr, tuple_offset, "tuple_addr");
// Load the tuple*
llvm::Value* tuple_ptr = builder.CreateLoad(tuple_addr, "tuple_ptr");
// Check if tuple* is null only if the tuple is nullable
if (tuple_is_nullable_) {
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, ret_block, check_slot_null_indicator_block);
} else {
builder.CreateCondBr(tuple_is_null, ret_block, get_slot_block);
}
} else {
if (slot_is_nullable) {
builder.CreateBr(check_slot_null_indicator_block);
} else {
builder.CreateBr(get_slot_block);
}
}
// Branch for tuple* != NULL. Need to check if null-indicator is set
if (slot_is_nullable) {
builder.SetInsertPoint(check_slot_null_indicator_block);
llvm::Value* is_slot_null = SlotDescriptor::CodegenIsNull(
codegen, &builder, null_indicator_offset_, tuple_ptr);
builder.CreateCondBr(is_slot_null, ret_block, get_slot_block);
}
// Branch for slot != NULL
builder.SetInsertPoint(get_slot_block);
llvm::Value* slot_ptr = builder.CreateInBoundsGEP(tuple_ptr, slot_offset, "slot_addr");
llvm::Value* val_ptr = builder.CreateBitCast(slot_ptr,
codegen->GetSlotPtrType(type_), "val_ptr");
// Depending on the type, load the values we need
llvm::Value* val = NULL;
llvm::Value* ptr = NULL;
llvm::Value* len = NULL;
llvm::Value* time_of_day = NULL;
llvm::Value* date = NULL;
if (type_.IsVarLenStringType() || type_.IsCollectionType()) {
llvm::Value* ptr_ptr = builder.CreateStructGEP(NULL, val_ptr, 0, "ptr_ptr");
ptr = builder.CreateLoad(ptr_ptr, "ptr");
llvm::Value* len_ptr = builder.CreateStructGEP(NULL, val_ptr, 1, "len_ptr");
len = builder.CreateLoad(len_ptr, "len");
} else if (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);
} else if (type_.type == TYPE_TIMESTAMP) {
llvm::Value* time_of_day_ptr =
builder.CreateStructGEP(NULL, 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(NULL, 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");
} else {
// val_ptr is a native type
val = builder.CreateLoad(val_ptr, "val");
}
builder.CreateBr(ret_block);
// Return block
builder.SetInsertPoint(ret_block);
llvm::PHINode* is_null_phi =
builder.CreatePHI(codegen->i8_type(), 2, "is_null_phi");
if (tuple_is_nullable_) is_null_phi->addIncoming(one, entry_block);
if (check_slot_null_indicator_block != NULL) {
is_null_phi->addIncoming(one, check_slot_null_indicator_block);
}
is_null_phi->addIncoming(zero, get_slot_block);
// 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 get_slot_block generate an AnyVal and having a single phi node over that.
// TODO: revisit this code, can possibly be simplified
if (type_.IsStringType() || type_.type == TYPE_FIXED_UDA_INTERMEDIATE
|| type_.IsCollectionType()) {
DCHECK(ptr != NULL);
DCHECK(len != NULL);
llvm::PHINode* ptr_phi = builder.CreatePHI(ptr->getType(), 2, "ptr_phi");
llvm::Value* null = llvm::Constant::getNullValue(ptr->getType());
if (tuple_is_nullable_) {
ptr_phi->addIncoming(null, entry_block);
}
if (check_slot_null_indicator_block != NULL) {
ptr_phi->addIncoming(null, check_slot_null_indicator_block);
}
ptr_phi->addIncoming(ptr, get_slot_block);
llvm::PHINode* len_phi = builder.CreatePHI(len->getType(), 2, "len_phi");
null = llvm::ConstantInt::get(len->getType(), 0);
if (tuple_is_nullable_) {
len_phi->addIncoming(null, entry_block);
}
if (check_slot_null_indicator_block != NULL) {
len_phi->addIncoming(null, check_slot_null_indicator_block);
}
len_phi->addIncoming(len, get_slot_block);
CodegenAnyVal result =
CodegenAnyVal::GetNonNullVal(codegen, &builder, type(), "result");
result.SetIsNull(is_null_phi);
result.SetPtr(ptr_phi);
result.SetLen(len_phi);
builder.CreateRet(result.GetLoweredValue());
} else if (type_.type == TYPE_TIMESTAMP) {
DCHECK(time_of_day != NULL);
DCHECK(date != NULL);
llvm::PHINode* time_of_day_phi =
builder.CreatePHI(time_of_day->getType(), 2, "time_of_day_phi");
llvm::Value* null = llvm::ConstantInt::get(time_of_day->getType(), 0);
if (tuple_is_nullable_) {
time_of_day_phi->addIncoming(null, entry_block);
}
if (check_slot_null_indicator_block != NULL) {
time_of_day_phi->addIncoming(null, check_slot_null_indicator_block);
}
time_of_day_phi->addIncoming(time_of_day, get_slot_block);
llvm::PHINode* date_phi = builder.CreatePHI(date->getType(), 2, "date_phi");
null = llvm::ConstantInt::get(date->getType(), 0);
if (tuple_is_nullable_) {
date_phi->addIncoming(null, entry_block);
}
if (check_slot_null_indicator_block != NULL) {
date_phi->addIncoming(null, check_slot_null_indicator_block);
}
date_phi->addIncoming(date, get_slot_block);
CodegenAnyVal result =
CodegenAnyVal::GetNonNullVal(codegen, &builder, type(), "result");
result.SetIsNull(is_null_phi);
result.SetTimeOfDay(time_of_day_phi);
result.SetDate(date_phi);
builder.CreateRet(result.GetLoweredValue());
} else {
DCHECK(val != NULL);
llvm::PHINode* val_phi = builder.CreatePHI(val->getType(), 2, "val_phi");
llvm::Value* null = llvm::Constant::getNullValue(val->getType());
if (tuple_is_nullable_) {
val_phi->addIncoming(null, entry_block);
}
if (check_slot_null_indicator_block != NULL) {
val_phi->addIncoming(null, check_slot_null_indicator_block);
}
val_phi->addIncoming(val, get_slot_block);
CodegenAnyVal result =
CodegenAnyVal::GetNonNullVal(codegen, &builder, type(), "result");
result.SetIsNull(is_null_phi);
result.SetVal(val_phi);
builder.CreateRet(result.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();
}
#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:
return DecimalVal(*reinterpret_cast<int128_t*>(t->GetSlot(slot_offset_)));
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);
}
} // namespace impala