blob: 0b77612842b7863d68209ef15bd41503fb0948f4 [file]
// 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 "runtime/tuple.h"
#include <vector>
#include "llvm/IR/Function.h"
#include "codegen/codegen-anyval.h"
#include "codegen/llvm-codegen.h"
#include "exprs/scalar-expr-evaluator.h"
#include "exprs/scalar-expr.h"
#include "gutil/strings/substitute.h"
#include "runtime/collection-value.h"
#include "runtime/descriptors.h"
#include "runtime/mem-pool.h"
#include "runtime/mem-tracker.h"
#include "runtime/raw-value.h"
#include "runtime/runtime-state.h"
#include "runtime/string-value.h"
#include "runtime/tuple-row.h"
#include "util/debug-util.h"
#include "util/runtime-profile-counters.h"
#include "util/ubsan.h"
#include "common/names.h"
namespace impala {
const char* Tuple::LLVM_CLASS_NAME = "class.impala::Tuple";
const char* Tuple::CodegenTypes::LLVM_CLASS_NAME = "struct.impala::Tuple::CodegenTypes";
const char* SlotOffsets::LLVM_CLASS_NAME = "struct.impala::SlotOffsets";
const char* Tuple::MATERIALIZE_EXPRS_SYMBOL = "MaterializeExprsILb0ELb0";
const char* Tuple::MATERIALIZE_EXPRS_NULL_POOL_SYMBOL = "MaterializeExprsILb0ELb1";
Tuple* const Tuple::POISON = reinterpret_cast<Tuple*>(42L);
int64_t Tuple::TotalByteSize(const TupleDescriptor& desc, bool assume_smallify) const {
int64_t result = desc.byte_size();
if (!desc.HasVarlenSlots()) return result;
result += VarlenByteSize(desc, assume_smallify);
return result;
}
int64_t Tuple::VarlenByteSize(const TupleDescriptor& desc, bool assume_smallify) const {
int64_t result = 0;
vector<SlotDescriptor*>::const_iterator slot = desc.string_slots().begin();
for (; slot != desc.string_slots().end(); ++slot) {
DCHECK((*slot)->type().IsVarLenStringType());
if (IsNull((*slot)->null_indicator_offset())) continue;
const StringValue* string_val = GetStringSlot((*slot)->tuple_offset());
result += string_val->ExternalLen(assume_smallify);
}
slot = desc.collection_slots().begin();
for (; slot != desc.collection_slots().end(); ++slot) {
DCHECK((*slot)->type().IsCollectionType());
if (IsNull((*slot)->null_indicator_offset())) continue;
const CollectionValue* coll_value = GetCollectionSlot((*slot)->tuple_offset());
uint8_t* coll_data = coll_value->ptr;
const TupleDescriptor& item_desc = *(*slot)->children_tuple_descriptor();
for (int i = 0; i < coll_value->num_tuples; ++i) {
Tuple* tuple = reinterpret_cast<Tuple*>(coll_data);
result += tuple->TotalByteSize(item_desc, assume_smallify);
coll_data += item_desc.byte_size();
}
}
return result;
}
Tuple* Tuple::DeepCopy(const TupleDescriptor& desc, MemPool* pool) const {
Tuple* result = reinterpret_cast<Tuple*>(pool->Allocate(desc.byte_size()));
DeepCopy(result, desc, pool);
return result;
}
// TODO: the logic is very similar to the other DeepCopy implementation aside from how
// memory is allocated - can we templatise it somehow to avoid redundancy without runtime
// overhead.
void Tuple::DeepCopy(Tuple* dst, const TupleDescriptor& desc, MemPool* pool) const {
if (desc.HasVarlenSlots()) {
memcpy(dst, this, desc.byte_size());
dst->DeepCopyVarlenData(desc, pool);
} else {
memcpy(dst, this, desc.byte_size());
}
}
void Tuple::DeepCopyVarlenData(const TupleDescriptor& desc, MemPool* pool) {
// allocate then copy all non-null string and collection slots
for (vector<SlotDescriptor*>::const_iterator slot = desc.string_slots().begin();
slot != desc.string_slots().end(); ++slot) {
DCHECK((*slot)->type().IsVarLenStringType());
if (IsNull((*slot)->null_indicator_offset())) continue;
StringValue* string_v = GetStringSlot((*slot)->tuple_offset());
// It is safe to smallify at this point as DeepCopyVarlenData is called on the new
// tuple which can be modified.
if (string_v->Smallify()) continue;
char* string_copy = reinterpret_cast<char*>(pool->Allocate(string_v->Len()));
Ubsan::MemCpy(string_copy, string_v->Ptr(), string_v->Len());
string_v->SetPtr(string_copy);
}
for (vector<SlotDescriptor*>::const_iterator slot = desc.collection_slots().begin();
slot != desc.collection_slots().end(); ++slot) {
DCHECK((*slot)->type().IsCollectionType());
if (IsNull((*slot)->null_indicator_offset())) continue;
CollectionValue* cv = GetCollectionSlot((*slot)->tuple_offset());
const TupleDescriptor* item_desc = (*slot)->children_tuple_descriptor();
int coll_byte_size = cv->num_tuples * item_desc->byte_size();
uint8_t* coll_data = reinterpret_cast<uint8_t*>(pool->Allocate(coll_byte_size));
memcpy(coll_data, cv->ptr, coll_byte_size);
cv->ptr = coll_data;
if (!item_desc->HasVarlenSlots()) continue;
for (int i = 0; i < cv->num_tuples; ++i) {
int item_offset = i * item_desc->byte_size();
Tuple* dst_item = reinterpret_cast<Tuple*>(coll_data + item_offset);
dst_item->DeepCopyVarlenData(*item_desc, pool);
}
}
}
void Tuple::DeepCopy(const TupleDescriptor& desc, char** data, int* offset,
bool convert_ptrs) const {
Tuple* dst = reinterpret_cast<Tuple*>(*data);
memcpy(dst, this, desc.byte_size());
*data += desc.byte_size();
*offset += desc.byte_size();
if (desc.HasVarlenSlots()) {
dst->DeepCopyVarlenData(desc, data, offset, convert_ptrs);
}
}
void Tuple::DeepCopyVarlenData(const TupleDescriptor& desc, char** data, int* offset,
bool convert_ptrs) {
vector<SlotDescriptor*>::const_iterator slot = desc.string_slots().begin();
for (; slot != desc.string_slots().end(); ++slot) {
DCHECK((*slot)->type().IsVarLenStringType());
if (IsNull((*slot)->null_indicator_offset())) continue;
StringValue* string_v = GetStringSlot((*slot)->tuple_offset());
// It is safe to smallify at this point as DeepCopyVarlenData is called on the new
// tuple which can be modified.
if (string_v->Smallify()) continue;
unsigned int len = string_v->Len();
Ubsan::MemCpy(*data, string_v->Ptr(), len);
string_v->SetPtr(convert_ptrs ? reinterpret_cast<char*>(*offset) : *data);
*data += len;
*offset += len;
}
slot = desc.collection_slots().begin();
for (; slot != desc.collection_slots().end(); ++slot) {
DCHECK((*slot)->type().IsCollectionType());
if (IsNull((*slot)->null_indicator_offset())) continue;
CollectionValue* coll_value = GetCollectionSlot((*slot)->tuple_offset());
const TupleDescriptor& item_desc = *(*slot)->children_tuple_descriptor();
int coll_byte_size = coll_value->num_tuples * item_desc.byte_size();
memcpy(*data, coll_value->ptr, coll_byte_size);
uint8_t* coll_data = reinterpret_cast<uint8_t*>(*data);
coll_value->ptr = convert_ptrs ? reinterpret_cast<uint8_t*>(*offset) : coll_data;
*data += coll_byte_size;
*offset += coll_byte_size;
// Copy per-tuple varlen data if necessary.
if (!item_desc.HasVarlenSlots()) continue;
for (int i = 0; i < coll_value->num_tuples; ++i) {
Tuple* dst_item = reinterpret_cast<Tuple*>(coll_data);
dst_item->DeepCopyVarlenData(item_desc, data, offset, convert_ptrs);
coll_data += item_desc.byte_size();
}
}
}
void Tuple::ConvertOffsetsToPointers(const TupleDescriptor& desc, uint8_t* tuple_data) {
vector<SlotDescriptor*>::const_iterator slot = desc.string_slots().begin();
for (; slot != desc.string_slots().end(); ++slot) {
DCHECK((*slot)->type().IsVarLenStringType());
if (IsNull((*slot)->null_indicator_offset())) continue;
StringValue* string_val = GetStringSlot((*slot)->tuple_offset());
if (string_val->IsSmall()) continue;
int offset = reinterpret_cast<intptr_t>(string_val->Ptr());
string_val->SetPtr(reinterpret_cast<char*>(tuple_data + offset));
}
slot = desc.collection_slots().begin();
for (; slot != desc.collection_slots().end(); ++slot) {
DCHECK((*slot)->type().IsCollectionType());
if (IsNull((*slot)->null_indicator_offset())) continue;
CollectionValue* coll_value = GetCollectionSlot((*slot)->tuple_offset());
int offset = reinterpret_cast<intptr_t>(coll_value->ptr);
coll_value->ptr = tuple_data + offset;
uint8_t* coll_data = coll_value->ptr;
const TupleDescriptor& item_desc = *(*slot)->children_tuple_descriptor();
for (int i = 0; i < coll_value->num_tuples; ++i) {
reinterpret_cast<Tuple*>(coll_data)->ConvertOffsetsToPointers(
item_desc, tuple_data);
coll_data += item_desc.byte_size();
}
}
}
void Tuple::SetNullIndicators(NullIndicatorOffset offset, int64_t num_tuples,
int64_t tuple_stride, uint8_t* tuple_mem) {
// 'offset' is passed by value instead of const reference so that the compiler knows
// that it doesn't alias 'tuple_mem' and keep the offset values in registers in the
// below loop.
for (int64_t i = 0; i < num_tuples; ++i) {
reinterpret_cast<Tuple*>(tuple_mem)->SetNull(offset);
tuple_mem += tuple_stride;
}
}
// Note: 'noexcept' is added because otherwise in LLVM code this function is called with
// the 'invoke' instruction, not the 'call' instruction. This is bad because we can't
// replace functions called with 'invoke' with a codegen'd function.
//
// The 'invoke' instruction is for functions that can throw exceptions. It would be chosen
// here - if we didn't add 'noexcept' - because std::vector<...> (the actual type of
// 'non_null_string_values' and 'non_null_collection_values') can throw if it fails to
// allocate memory when it tries to grow. If a noexcept function throws, std::terminate()
// is called (so no Undefined Behaviour).
//
// Currently the hand-crafted function we replace this function with doesn't use the
// vectors (COLLECT_VAR_LEN_VALS is always false), so no exception will be thrown in
// codegen code.
//
// In interpreted mode, if we crash because of std::terminate(), it is the same as what we
// did before changing the type of these parameters to std::vector<...>: vector resizing
// was done in the caller, but there was no catch block for it. This will also be the case
// if the hand-crafted function in codegen code starts using the vectors.
template <bool COLLECT_VAR_LEN_VALS, bool NO_POOL>
void Tuple::MaterializeExprs(TupleRow* row, const TupleDescriptor& desc,
ScalarExprEvaluator* const* evals, MemPool* pool,
CodegenTypes::StringValuePtrVecType* non_null_string_values,
CodegenTypes::CollValuePtrAndSizeVecType* non_null_collection_values,
int* total_varlen_lengths,
int* num_non_null_string_values, int* num_non_null_collection_values) noexcept {
if constexpr (COLLECT_VAR_LEN_VALS) {
DCHECK(non_null_string_values != nullptr);
DCHECK(non_null_collection_values != nullptr);
DCHECK(total_varlen_lengths != nullptr);
DCHECK(num_non_null_string_values != nullptr);
DCHECK(num_non_null_collection_values != nullptr);
}
ClearNullBits(desc);
// Evaluate the materialize_expr_evals and place the results in the tuple.
for (int i = 0; i < desc.slots().size(); ++i) {
SlotDescriptor* slot_desc = desc.slots()[i];
// The FE ensures we don't get any TYPE_NULL expressions by picking an arbitrary type
// when necessary, but does not do this for slot descs.
// TODO: revisit this logic in the FE
DCHECK(slot_desc->type().type == TYPE_NULL ||
slot_desc->type() == evals[i]->root().type());
void* src = evals[i]->GetValue(row);
const size_t old_num_string_values = COLLECT_VAR_LEN_VALS ?
non_null_string_values->size() : 0;
const size_t old_num_collection_values = COLLECT_VAR_LEN_VALS ?
non_null_collection_values->size() : 0;
RawValue::Write<COLLECT_VAR_LEN_VALS>(src, this, slot_desc, pool,
non_null_string_values, non_null_collection_values);
if constexpr (COLLECT_VAR_LEN_VALS) {
const size_t new_num_string_values = non_null_string_values->size();
for (size_t i = old_num_string_values; i < new_num_string_values; i++) {
const StringValue* string_val = (*non_null_string_values)[i];
DCHECK(!string_val->IsSmall());
*total_varlen_lengths += string_val->Len();
}
(*num_non_null_string_values) += new_num_string_values - old_num_string_values;
const size_t new_num_collection_values = non_null_collection_values->size();
for (size_t i = old_num_collection_values; i < new_num_collection_values; i++) {
const pair<CollectionValue*, int64_t>& collection_val_pair =
(*non_null_collection_values)[i];
*total_varlen_lengths += collection_val_pair.second;
}
(*num_non_null_collection_values) +=
new_num_collection_values - old_num_collection_values;
}
}
}
void Tuple::SetStructToNull(const SlotDescriptor* const slot_desc) {
DCHECK(slot_desc != nullptr && slot_desc->type().IsStructType());
DCHECK(slot_desc->children_tuple_descriptor() != nullptr);
SetNull(slot_desc->null_indicator_offset());
for (SlotDescriptor* child_slot : slot_desc->children_tuple_descriptor()->slots()) {
SetNull(child_slot->null_indicator_offset());
if (child_slot->type().IsStructType()) SetStructToNull(child_slot);
}
}
char* Tuple::AllocateStrings(const char* err_ctx, RuntimeState* state,
int64_t bytes, MemPool* pool, Status* status) noexcept {
char* buf = reinterpret_cast<char*>(pool->TryAllocateUnaligned(bytes));
if (UNLIKELY(buf == nullptr)) {
string details = Substitute("$0 failed to allocate $1 bytes for strings.",
err_ctx, bytes);
*status = pool->mem_tracker()->MemLimitExceeded(state, details, bytes);
return nullptr;
}
return buf;
}
// Codegens an unrolled version of MaterializeExprs(). Uses codegen'd exprs and slot
// writes. If 'pool' is non-NULL, string data is copied into it.
//
// Example IR for materializing a string column with non-NULL 'pool', produced by the
// following query:
// select l_comment
// from tpch.lineitem
// order by l_comment
// limit 10;
//
//
// define void @MaterializeExprs(%"class.impala::Tuple"* %opaque_tuple,
// %"class.impala::TupleRow"* %row, %"class.impala::TupleDescriptor"* %desc,
// %"class.impala::ScalarExprEvaluator"** %slot_materialize_expr_evals,
// %"class.impala::MemPool"* %pool,
// %"class.std::vector.1154"* %non_null_string_values,
// %"class.std::vector.1159"* %non_null_collection_values, i32* %total_varlen_lengths,
// i32* %num_non_null_string_values, i32* %num_non_null_collection_values) #50 {
// entry:
// %tuple = bitcast %"class.impala::Tuple"* %opaque_tuple
// to <{ %"struct.impala::StringValue", i8 }>*
// %int8_ptr = bitcast <{ %"struct.impala::StringValue", i8 }>* %tuple to i8*
// %null_bytes_ptr = getelementptr inbounds i8, i8* %int8_ptr, i32 12
// call void @llvm.memset.p0i8.i64(i8* %null_bytes_ptr, i8 0, i64 1, i32 0, i1 false)
// %0 = getelementptr %"class.impala::ScalarExprEvaluator"*,
// %"class.impala::ScalarExprEvaluator"** %slot_materialize_expr_evals, i32 0
// %expr_eval = load %"class.impala::ScalarExprEvaluator"*,
// %"class.impala::ScalarExprEvaluator"** %0
// %src = call { i64, i8* } @GetSlotRef.4(
// %"class.impala::ScalarExprEvaluator"* %expr_eval,
// %"class.impala::TupleRow"* %row)
// ; -- generated by CodegenAnyVal::ToReadWriteInfo() and
// ; -- SlotDescriptor::CodegenWriteToSlot()
// br label %entry1
//
// entry1: ; preds = %entry
// %1 = extractvalue { i64, i8* } %src, 0
// %is_null = trunc i64 %1 to i1
// br i1 %is_null, label %null, label %non_null
//
// non_null: ; preds = %entry1
// %2 = extractvalue { i64, i8* } %src, 0
// %3 = ashr i64 %2, 32
// %4 = trunc i64 %3 to i32
// %src2 = extractvalue { i64, i8* } %src, 1
// %slot = getelementptr inbounds <{ %"struct.impala::StringValue", i8 }>,
// <{ %"struct.impala::StringValue", i8 }>* %tuple, i32 0, i32 0
// %5 = insertvalue %"struct.impala::StringValue" zeroinitializer, i32 %4, 1
// %6 = sext i32 %4 to i64
// %new_coll_val_ptr = call i8* @_ZN6impala7MemPool8AllocateILb0EEEPhli(
// %"class.impala::MemPool"* %pool, i64 %6, i32 8)
// call void @llvm.memcpy.p0i8.p0i8.i32(i8* %new_coll_val_ptr, i8* %src2, i32 %4, i32 0,
// i1 false)
// %7 = insertvalue %"struct.impala::StringValue" %5, i8* %new_coll_val_ptr, 0
// store %"struct.impala::StringValue" %7, %"struct.impala::StringValue"* %slot
// br label %end_write
//
// null: ; preds = %entry1
// %8 = bitcast <{ %"struct.impala::StringValue", i8 }>* %tuple to i8*
// %null_byte_ptr = getelementptr inbounds i8, i8* %8, i32 12
// %null_byte = load i8, i8* %null_byte_ptr
// %null_bit_set = or i8 %null_byte, 1
// store i8 %null_bit_set, i8* %null_byte_ptr
// br label %end_write
//
// end_write: ; preds = %null, %non_null
// ; -- end CodegenAnyVal::ToReadWriteInfo() and SlotDescriptor::CodegenWriteToSlot()
// ret void
// }
Status Tuple::CodegenMaterializeExprs(LlvmCodeGen* codegen, bool collect_varlen_vals,
const TupleDescriptor& desc, const vector<ScalarExpr*>& slot_materialize_exprs,
bool use_mem_pool, llvm::Function** fn) {
// Only support 'collect_varlen_vals' == false for now.
// TODO IMPALA-12068: implement it for 'collect_varlen_vals' == true too.
if (collect_varlen_vals) {
return Status("CodegenMaterializeExprs() collect_varlen_vals == true NYI");
}
// VARIANT slot writes are not codegen'd yet; fall back to the interpreted path
// (Tuple::MaterializeExprs -> RawValue::WriteVariant). Without this, codegen would
// reach GetLlvmStruct/GetSlotType for TYPE_VARIANT and DCHECK.
for (const SlotDescriptor* slot : desc.slots()) {
if (slot->type().IsVariantType()) {
return Status::Expected("CodegenMaterializeExprs() VARIANT type NYI");
}
}
llvm::LLVMContext& context = codegen->context();
// Codegen each compute function from slot_materialize_exprs
llvm::Function* materialize_expr_fns[slot_materialize_exprs.size()];
for (int i = 0; i < slot_materialize_exprs.size(); ++i) {
Status status = slot_materialize_exprs[i]->GetCodegendComputeFn(
codegen, false, &materialize_expr_fns[i]);
if (!status.ok()) {
return Status::Expected(Substitute("Could not codegen CodegenMaterializeExprs: $0",
status.GetDetail()));
}
}
// Construct function signature (this must exactly match the actual signature since it's
// used in xcompiled IR). With 'pool':
// void MaterializeExprs(Tuple* opaque_tuple, TupleRow* row,
// const TupleDescriptor& desc, ScalarExprEvaluator* const* evals, MemPool* pool,
// CodegenTypes::StringValuePtrVecType* non_null_string_values,
// CodegenTypes::CollValuePtrAndSizeVecType* non_null_collection_values,
// int* total_varlen_lengths, int* num_non_null_string_values,
// int* num_non_null_collection_values);
llvm::PointerType* opaque_tuple_type = codegen->GetStructPtrType<Tuple>();
llvm::PointerType* row_type = codegen->GetStructPtrType<TupleRow>();
llvm::PointerType* desc_type = codegen->GetStructPtrType<TupleDescriptor>();
llvm::PointerType* expr_evals_type =
codegen->GetStructPtrPtrType<ScalarExprEvaluator>();
llvm::PointerType* pool_type = codegen->GetStructPtrType<MemPool>();
llvm::Type* string_values_type =
CodegenTypes::getStringValuePtrVecType(codegen)->getPointerTo();
llvm::Type* coll_values_and_sizes_type =
CodegenTypes::getCollValuePtrAndSizeVecType(codegen)->getPointerTo();
llvm::PointerType* int_ptr_type = codegen->i32_ptr_type();
LlvmCodeGen::FnPrototype prototype(codegen, "MaterializeExprs", codegen->void_type());
prototype.AddArgument("opaque_tuple", opaque_tuple_type);
prototype.AddArgument("row", row_type);
prototype.AddArgument("desc", desc_type);
prototype.AddArgument("slot_materialize_expr_evals", expr_evals_type);
prototype.AddArgument("pool", pool_type);
prototype.AddArgument("non_null_string_values", string_values_type);
prototype.AddArgument("non_null_collection_values", coll_values_and_sizes_type);
prototype.AddArgument("total_varlen_lengths", int_ptr_type);
prototype.AddArgument("num_non_null_string_values", int_ptr_type);
prototype.AddArgument("num_non_null_collection_values", int_ptr_type);
LlvmBuilder builder(context);
llvm::Value* args[10];
*fn = prototype.GeneratePrototype(&builder, args);
llvm::Value* opaque_tuple_arg = args[0];
llvm::Value* row_arg = args[1];
// llvm::Value* desc_arg = args[2]; // unused
llvm::Value* expr_evals_arg = args[3];
llvm::Value* pool_arg = args[4];
// The followings arguments are unused as 'collect_varlen_vals' is false.
// llvm::Value* non_null_string_values_arg = args[5]; // unused
// llvm::Value* non_null_collection_values_arg = args[6]; // unused
// llvm::Value* total_varlen_lengths_arg = args[7]; // unused
// llvm::Value* num_non_null_string_values_arg = args[8]; // unused
// llvm::Value* num_non_null_collection_values_arg = args[9]; // unused
// Cast the opaque Tuple* argument to the generated struct type
llvm::Type* tuple_struct_type = desc.GetLlvmStruct(codegen);
DCHECK(tuple_struct_type != nullptr);
llvm::PointerType* tuple_type = codegen->GetPtrType(tuple_struct_type);
llvm::Value* tuple = builder.CreateBitCast(opaque_tuple_arg, tuple_type, "tuple");
// Clear tuple's null bytes
codegen->CodegenClearNullBits(&builder, tuple, desc);
// Evaluate the slot_materialize_exprs and place the results in the tuple.
for (int i = 0; i < desc.slots().size(); ++i) {
SlotDescriptor* slot_desc = desc.slots()[i];
DCHECK(slot_desc->type().type == TYPE_NULL
|| slot_desc->type() == slot_materialize_exprs[i]->type());
// Call materialize_expr_fns[i](slot_materialize_exprs[i], row)
llvm::Value* expr_eval =
codegen->CodegenArrayAt(&builder, expr_evals_arg, i, "expr_eval");
llvm::Value* expr_args[] = {expr_eval, row_arg};
CodegenAnyVal src = CodegenAnyVal::CreateCallWrapped(codegen, &builder,
slot_materialize_exprs[i]->type(), materialize_expr_fns[i], expr_args, "src");
// Write expr result 'src' to slot
CodegenAnyValReadWriteInfo read_write_info = src.ToReadWriteInfo();
slot_desc->CodegenWriteToSlot(
read_write_info, tuple, use_mem_pool ? pool_arg : nullptr);
}
builder.CreateRetVoid();
// TODO: if pool != NULL, OptimizeFunctionWithExprs() is inlining the Allocate()
// call. Investigate if this is a good thing.
*fn = codegen->FinalizeFunction(*fn);
if (*fn == nullptr) {
return Status("Tuple::CodegenMaterializeTuple(): failed to finalize function");
}
return Status::OK();
}
Status Tuple::CodegenCopyStrings(
LlvmCodeGen* codegen, const TupleDescriptor& desc, llvm::Function** copy_strings_fn) {
llvm::PointerType* opaque_tuple_type = codegen->GetStructPtrType<Tuple>();
llvm::PointerType* runtime_state_type = codegen->GetStructPtrType<RuntimeState>();
llvm::StructType* slot_offsets_type = codegen->GetStructType<SlotOffsets>();
llvm::PointerType* pool_type = codegen->GetStructPtrType<MemPool>();
llvm::PointerType* status_type = codegen->GetStructPtrType<Status>();
LlvmCodeGen::FnPrototype prototype(
codegen, "CopyStringsWrapper", codegen->bool_type());
prototype.AddArgument("opaque_tuple", opaque_tuple_type);
prototype.AddArgument("err_ctx", codegen->ptr_type());
prototype.AddArgument("state", runtime_state_type);
prototype.AddArgument("slot_offsets", codegen->GetPtrType(slot_offsets_type));
prototype.AddArgument("num_string_slots", codegen->i32_type());
prototype.AddArgument("pool", pool_type);
prototype.AddArgument("status", status_type);
LlvmBuilder builder(codegen->context());
llvm::Value* args[7];
*copy_strings_fn = prototype.GeneratePrototype(&builder, args);
llvm::Value* opaque_tuple_arg = args[0];
llvm::Value* err_ctx_arg = args[1];
llvm::Value* state_arg = args[2];
// slot_offsets and num_string_slots are replaced with constants so args are unused.
llvm::Value* pool_arg = args[5];
llvm::Value* status_arg = args[6];
llvm::Function* cross_compiled_fn =
codegen->GetFunction(IRFunction::TUPLE_COPY_STRINGS, false);
DCHECK(cross_compiled_fn != nullptr);
// Convert the offsets of the string slots into a constant IR array 'slot_offsets'.
vector<llvm::Constant*> slot_offset_ir_constants;
for (SlotDescriptor* slot_desc : desc.string_slots()) {
SlotOffsets offsets = {slot_desc->null_indicator_offset(), slot_desc->tuple_offset()};
slot_offset_ir_constants.push_back(offsets.ToIR(codegen));
}
llvm::Constant* constant_slot_offsets = codegen->ConstantsToGVArrayPtr(
slot_offsets_type, slot_offset_ir_constants, "slot_offsets");
llvm::Constant* num_string_slots = codegen->GetI32Constant(desc.string_slots().size());
// Get SlotOffsets* pointer to the first element of the constant array.
llvm::Value* constant_slot_offsets_first_element_ptr =
builder.CreateConstGEP2_64(constant_slot_offsets, 0, 0);
llvm::Value* result_val = builder.CreateCall(cross_compiled_fn,
{opaque_tuple_arg, err_ctx_arg, state_arg, constant_slot_offsets_first_element_ptr,
num_string_slots, pool_arg, status_arg});
builder.CreateRet(result_val);
*copy_strings_fn = codegen->FinalizeFunction(*copy_strings_fn);
if (*copy_strings_fn == nullptr) {
return Status("Tuple::CodegenCopyStrings(): failed to finalize function");
}
return Status::OK();
}
Status Tuple::CodegenTryDeepCopy(LlvmCodeGen* codegen, const TupleDescriptor* tuple_desc,
llvm::Function** fn) {
llvm::Type* this_ptr_type = codegen->GetStructPtrType<Tuple>();
llvm::Type* tuple_descriptor_type = codegen->GetStructPtrType<TupleDescriptor>();
LlvmCodeGen::FnPrototype prototype(codegen, "TryDeepCopy", codegen->bool_type());
prototype.AddArgument(LlvmCodeGen::NamedVariable("this", this_ptr_type));
prototype.AddArgument(LlvmCodeGen::NamedVariable("data",
codegen->GetPtrType(codegen->ptr_type())));
prototype.AddArgument(LlvmCodeGen::NamedVariable("data_end", codegen->ptr_type()));
prototype.AddArgument(LlvmCodeGen::NamedVariable("offset", codegen->i32_ptr_type()));
prototype.AddArgument(LlvmCodeGen::NamedVariable("desc", tuple_descriptor_type));
llvm::LLVMContext& context = codegen->context();
LlvmBuilder builder(context);
llvm::Value* args[5];
*fn = prototype.GeneratePrototype(&builder, args);
llvm::Value* this_ptr = args[0];
llvm::Value* data = args[1];
llvm::Value* data_end = args[2];
llvm::Value* offset = args[3];
llvm::Value* desc = args[4];
// Save current value of data and offset, so it can be reset if running out of memory
llvm::Value* data_start = builder.CreateLoad(data, "data_start");
llvm::Value* offset_start = builder.CreateLoad(offset, "offset_start");
llvm::Value* dst_tuple = builder.CreateBitCast(data_start, this_ptr_type);
llvm::BasicBlock* return_block = llvm::BasicBlock::Create(context, "return", *fn);
// copy fixed length part of tuple
llvm::Constant* desc_byte_size = codegen->GetI32Constant(tuple_desc->byte_size());
llvm::Value* fixed_size_copy_result = codegen->CodegenCallFunction(&builder,
IRFunction::TUPLE_TRY_DEEP_COPY_FIXED_SIZE,
{this_ptr, data, data_end, offset, desc_byte_size}, "fixed_size_copy_result");
// return if didn't succeed, meaning copied data would exceed "data_end"
llvm::BasicBlock* continue_block = llvm::BasicBlock::Create(context, "continue", *fn);
builder.CreateCondBr(fixed_size_copy_result, continue_block, return_block);
builder.SetInsertPoint(continue_block);
// copy strings
for (SlotDescriptor* slot : tuple_desc->string_slots()) {
DCHECK(slot->type().IsVarLenStringType());
// call TryDeepCopyStringSlot
llvm::Value* null_indicator_offset =
slot->null_indicator_offset().ToIRPacked(codegen);
llvm::Constant* tuple_offset = codegen->GetI32Constant(slot->tuple_offset());
llvm::Value* string_copy_result = codegen->CodegenCallFunction(&builder,
IRFunction::TUPLE_TRY_DEEP_COPY_STRING_SLOT,
{dst_tuple, data, data_end, offset, null_indicator_offset, tuple_offset},
"string_copy_result");
// return if didn't succeed
continue_block = llvm::BasicBlock::Create(context, "continue", *fn);
builder.CreateCondBr(string_copy_result, continue_block, return_block);
builder.SetInsertPoint(continue_block);
}
// TODO: consider proper codegen for collection slots
if (tuple_desc->collection_slots().size() > 0) {
// call interpreted function to copy collection slots
llvm::Value* collections_copy_result = codegen->CodegenCallFunction(&builder,
IRFunction::TUPLE_TRY_DEEP_COPY_COLLECTIONS,
{dst_tuple, data, data_end, offset, desc}, "collections_copy_result");
// return if didn't succeed
continue_block = llvm::BasicBlock::Create(context, "continue", *fn);
builder.CreateCondBr(collections_copy_result, continue_block, return_block);
builder.SetInsertPoint(continue_block);
}
// copy succeded: return true
builder.CreateRet(codegen->true_value());
// return block: reset data and offset to starting value, and return false
builder.SetInsertPoint(return_block);
builder.CreateStore(data_start, data);
builder.CreateStore(offset_start, offset);
builder.CreateRet(codegen->false_value());
*fn = codegen->FinalizeFunction(*fn);
if (*fn == nullptr) {
return Status("Codegen'd Tuple::TryDeepCopy() function failed verification, see log");
}
return Status::OK();
}
llvm::Constant* SlotOffsets::ToIR(LlvmCodeGen* codegen) const {
return llvm::ConstantStruct::get(
codegen->GetStructType<SlotOffsets>(),
{null_indicator_offset.ToIR(codegen),
codegen->GetI32Constant(tuple_offset)});
}
llvm::Type* Tuple::CodegenTypes::getStringValuePtrVecType(LlvmCodeGen* codegen) {
llvm::StructType* codegenTypes = codegen->GetStructType<Tuple::CodegenTypes>();
return codegenTypes->getElementType(0);
}
llvm::Type* Tuple::CodegenTypes::getCollValuePtrAndSizeVecType(LlvmCodeGen* codegen) {
llvm::StructType* codegenTypes = codegen->GetStructType<Tuple::CodegenTypes>();
return codegenTypes->getElementType(1);
}
template void Tuple::MaterializeExprs<false, false>(TupleRow*, const TupleDescriptor&,
ScalarExprEvaluator* const*, MemPool*, Tuple::CodegenTypes::StringValuePtrVecType*,
Tuple::CodegenTypes::CollValuePtrAndSizeVecType*, int*, int*, int*);
template void Tuple::MaterializeExprs<false, true>(TupleRow*, const TupleDescriptor&,
ScalarExprEvaluator* const*, MemPool*, Tuple::CodegenTypes::StringValuePtrVecType*,
Tuple::CodegenTypes::CollValuePtrAndSizeVecType*, int*, int*, int*);
template void Tuple::MaterializeExprs<true, false>(TupleRow*, const TupleDescriptor&,
ScalarExprEvaluator* const*, MemPool*, Tuple::CodegenTypes::StringValuePtrVecType*,
Tuple::CodegenTypes::CollValuePtrAndSizeVecType*, int*, int*, int*);
template void Tuple::MaterializeExprs<true, true>(TupleRow*, const TupleDescriptor&,
ScalarExprEvaluator* const*, MemPool*, Tuple::CodegenTypes::StringValuePtrVecType*,
Tuple::CodegenTypes::CollValuePtrAndSizeVecType*, int*, int*, int*);
}