2018-04-25 15:19:22 +00:00
|
|
|
#include <Interpreters/ExpressionJIT.h>
|
|
|
|
|
|
|
|
#if USE_EMBEDDED_COMPILER
|
|
|
|
|
2018-05-06 10:42:35 +00:00
|
|
|
#include <optional>
|
|
|
|
|
2018-04-25 11:55:54 +00:00
|
|
|
#include <Columns/ColumnConst.h>
|
2018-04-27 21:30:38 +00:00
|
|
|
#include <Columns/ColumnNullable.h>
|
2018-04-24 19:42:06 +00:00
|
|
|
#include <Columns/ColumnVector.h>
|
|
|
|
#include <Common/typeid_cast.h>
|
2018-05-07 06:23:18 +00:00
|
|
|
#include <Common/ProfileEvents.h>
|
2018-04-24 19:42:06 +00:00
|
|
|
#include <DataTypes/DataTypeNullable.h>
|
|
|
|
#include <DataTypes/DataTypesNumber.h>
|
2018-04-27 21:30:38 +00:00
|
|
|
#include <DataTypes/Native.h>
|
2018-04-29 01:00:26 +00:00
|
|
|
#include <Functions/IFunction.h>
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
|
2018-05-06 09:29:57 +00:00
|
|
|
#pragma GCC diagnostic push
|
2018-05-06 09:37:16 +00:00
|
|
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
|
|
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
|
2018-05-06 09:29:57 +00:00
|
|
|
|
2018-05-06 11:16:38 +00:00
|
|
|
#include <llvm/Config/llvm-config.h>
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
#include <llvm/IR/BasicBlock.h>
|
|
|
|
#include <llvm/IR/DataLayout.h>
|
|
|
|
#include <llvm/IR/DerivedTypes.h>
|
|
|
|
#include <llvm/IR/Function.h>
|
|
|
|
#include <llvm/IR/IRBuilder.h>
|
|
|
|
#include <llvm/IR/LLVMContext.h>
|
|
|
|
#include <llvm/IR/Mangler.h>
|
|
|
|
#include <llvm/IR/Module.h>
|
|
|
|
#include <llvm/IR/Type.h>
|
|
|
|
#include <llvm/ExecutionEngine/ExecutionEngine.h>
|
|
|
|
#include <llvm/ExecutionEngine/JITSymbol.h>
|
|
|
|
#include <llvm/ExecutionEngine/SectionMemoryManager.h>
|
|
|
|
#include <llvm/ExecutionEngine/Orc/CompileUtils.h>
|
|
|
|
#include <llvm/ExecutionEngine/Orc/IRCompileLayer.h>
|
|
|
|
#include <llvm/ExecutionEngine/Orc/NullResolver.h>
|
|
|
|
#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
|
|
|
|
#include <llvm/Target/TargetMachine.h>
|
2018-04-27 21:30:38 +00:00
|
|
|
#include <llvm/Support/TargetSelect.h>
|
2018-04-24 14:12:45 +00:00
|
|
|
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
|
2018-05-06 09:29:57 +00:00
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
|
2018-05-07 08:59:24 +00:00
|
|
|
#if !LLVM_HAS_RTTI
|
|
|
|
|
2018-05-07 00:00:12 +00:00
|
|
|
/** HACK
|
|
|
|
* Allow to link with LLVM that was compiled without RTTI.
|
|
|
|
* This is the default option when you build LLVM from sources.
|
|
|
|
* We define fake symbols for RTTI to help linker.
|
|
|
|
* This assumes that enabling/disabling RTTI doesn't change memory layout of objects
|
|
|
|
* in any significant way and it doesn't affect the code that isn't actually using RTTI.
|
|
|
|
* Proper solution: recompile LLVM with enabled RTTI.
|
|
|
|
*/
|
|
|
|
extern "C"
|
|
|
|
{
|
|
|
|
|
|
|
|
__attribute__((__weak__)) int _ZTIN4llvm13ErrorInfoBaseE = 0;
|
|
|
|
__attribute__((__weak__)) int _ZTIN4llvm12MemoryBufferE = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-05-07 08:59:24 +00:00
|
|
|
#endif
|
|
|
|
|
2018-05-07 00:00:12 +00:00
|
|
|
|
2018-05-07 06:23:18 +00:00
|
|
|
namespace ProfileEvents
|
|
|
|
{
|
|
|
|
extern const Event CompileFunction;
|
|
|
|
}
|
|
|
|
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
|
Implement JIT compilation, without a loop for now.
It actually seems to work, so long as you only have one row that is. E.g.
> select something(cast(number + 6 as Float64), cast(number + 2 as Float64)) from system.numbers limit 1';
8
with this IR:
define void @"something(CAST(plus(number, 6), 'Float64'), CAST(plus(number, 2), 'Float64'))"(void**, i8*, double*) {
entry:
%3 = load void*, void** %0
%4 = bitcast void* %3 to double*
%5 = load double, double* %4
%6 = getelementptr void*, void** %0, i32 1
%7 = load void*, void** %6
%8 = bitcast void* %7 to double*
%9 = load double, double* %8
%10 = fadd double %5, %9
store double %10, double* %2
ret void
}
2018-04-23 23:52:54 +00:00
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int LOGICAL_ERROR;
|
2018-05-07 06:49:56 +00:00
|
|
|
extern const int CANNOT_COMPILE_CODE;
|
Implement JIT compilation, without a loop for now.
It actually seems to work, so long as you only have one row that is. E.g.
> select something(cast(number + 6 as Float64), cast(number + 2 as Float64)) from system.numbers limit 1';
8
with this IR:
define void @"something(CAST(plus(number, 6), 'Float64'), CAST(plus(number, 2), 'Float64'))"(void**, i8*, double*) {
entry:
%3 = load void*, void** %0
%4 = bitcast void* %3 to double*
%5 = load double, double* %4
%6 = getelementptr void*, void** %0, i32 1
%7 = load void*, void** %6
%8 = bitcast void* %7 to double*
%9 = load double, double* %8
%10 = fadd double %5, %9
store double %10, double* %2
ret void
}
2018-04-23 23:52:54 +00:00
|
|
|
}
|
|
|
|
|
2018-04-27 15:44:38 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct ColumnData
|
|
|
|
{
|
2018-04-27 21:30:38 +00:00
|
|
|
const char * data = nullptr;
|
|
|
|
const char * null = nullptr;
|
2018-04-27 15:44:38 +00:00
|
|
|
size_t stride;
|
|
|
|
};
|
2018-04-27 21:30:38 +00:00
|
|
|
|
2018-04-29 18:03:58 +00:00
|
|
|
struct ColumnDataPlaceholder
|
2018-04-27 21:30:38 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
llvm::Value * data_init; /// first row
|
2018-04-27 21:30:38 +00:00
|
|
|
llvm::Value * null_init;
|
|
|
|
llvm::Value * stride;
|
2018-04-29 01:00:26 +00:00
|
|
|
llvm::PHINode * data; /// current row
|
|
|
|
llvm::PHINode * null;
|
2018-04-27 21:30:38 +00:00
|
|
|
};
|
2018-04-27 15:44:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static ColumnData getColumnData(const IColumn * column)
|
|
|
|
{
|
2018-04-27 21:30:38 +00:00
|
|
|
ColumnData result;
|
|
|
|
const bool is_const = column->isColumnConst();
|
|
|
|
if (is_const)
|
|
|
|
column = &reinterpret_cast<const ColumnConst *>(column)->getDataColumn();
|
|
|
|
if (auto * nullable = typeid_cast<const ColumnNullable *>(column))
|
|
|
|
{
|
|
|
|
result.null = nullable->getNullMapColumn().getRawData().data;
|
|
|
|
column = &nullable->getNestedColumn();
|
|
|
|
}
|
|
|
|
result.data = column->getRawData().data;
|
|
|
|
result.stride = is_const ? 0 : column->sizeOfValueIfFixed();
|
|
|
|
return result;
|
2018-04-27 15:44:38 +00:00
|
|
|
}
|
|
|
|
|
2018-04-29 01:00:26 +00:00
|
|
|
static void applyFunction(IFunctionBase & function, Field & value)
|
|
|
|
{
|
|
|
|
const auto & type = function.getArgumentTypes().at(0);
|
|
|
|
Block block = {{ type->createColumnConst(1, value), type, "x" }, { nullptr, function.getReturnType(), "y" }};
|
2018-05-01 19:52:33 +00:00
|
|
|
function.execute(block, {0}, 1, 1);
|
2018-04-29 01:00:26 +00:00
|
|
|
block.safeGetByPosition(1).column->get(0, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct LLVMContext
|
2018-04-24 19:42:06 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
llvm::LLVMContext context;
|
2018-05-06 11:16:38 +00:00
|
|
|
#if LLVM_VERSION_MAJOR >= 7
|
|
|
|
llvm::orc::ExecutionSession execution_session;
|
|
|
|
std::unique_ptr<llvm::Module> module;
|
|
|
|
#else
|
2018-04-29 01:00:26 +00:00
|
|
|
std::shared_ptr<llvm::Module> module;
|
2018-05-06 11:16:38 +00:00
|
|
|
#endif
|
2018-04-29 01:00:26 +00:00
|
|
|
std::unique_ptr<llvm::TargetMachine> machine;
|
|
|
|
llvm::orc::RTDyldObjectLinkingLayer objectLayer;
|
|
|
|
llvm::orc::IRCompileLayer<decltype(objectLayer), llvm::orc::SimpleCompiler> compileLayer;
|
|
|
|
llvm::DataLayout layout;
|
|
|
|
llvm::IRBuilder<> builder;
|
2018-05-06 10:42:35 +00:00
|
|
|
std::unordered_map<std::string, void *> symbols;
|
2018-04-29 01:00:26 +00:00
|
|
|
|
|
|
|
LLVMContext()
|
2018-05-06 11:16:38 +00:00
|
|
|
#if LLVM_VERSION_MAJOR >= 7
|
|
|
|
: module(std::make_unique<llvm::Module>("jit", context))
|
|
|
|
#else
|
2018-04-29 01:00:26 +00:00
|
|
|
: module(std::make_shared<llvm::Module>("jit", context))
|
2018-05-06 11:16:38 +00:00
|
|
|
#endif
|
2018-04-29 01:00:26 +00:00
|
|
|
, machine(llvm::EngineBuilder().selectTarget())
|
2018-05-06 11:16:38 +00:00
|
|
|
#if LLVM_VERSION_MAJOR >= 7
|
|
|
|
, objectLayer(execution_session, [](llvm::orc::VModuleKey)
|
|
|
|
{
|
|
|
|
return llvm::orc::RTDyldObjectLinkingLayer::Resources
|
|
|
|
{
|
|
|
|
std::make_shared<llvm::SectionMemoryManager>(),
|
|
|
|
std::make_shared<llvm::orc::NullResolver>()
|
|
|
|
};
|
|
|
|
})
|
|
|
|
#else
|
2018-04-29 01:00:26 +00:00
|
|
|
, objectLayer([]() { return std::make_shared<llvm::SectionMemoryManager>(); })
|
2018-05-06 11:16:38 +00:00
|
|
|
#endif
|
2018-04-29 01:00:26 +00:00
|
|
|
, compileLayer(objectLayer, llvm::orc::SimpleCompiler(*machine))
|
|
|
|
, layout(machine->createDataLayout())
|
|
|
|
, builder(context)
|
2018-04-25 15:16:48 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
module->setDataLayout(layout);
|
|
|
|
module->setTargetTriple(machine->getTargetTriple().getTriple());
|
|
|
|
}
|
|
|
|
|
|
|
|
void finalize()
|
|
|
|
{
|
|
|
|
if (!module->size())
|
|
|
|
return;
|
|
|
|
llvm::PassManagerBuilder builder;
|
|
|
|
llvm::legacy::FunctionPassManager fpm(module.get());
|
|
|
|
builder.OptLevel = 3;
|
|
|
|
builder.SLPVectorize = true;
|
|
|
|
builder.LoopVectorize = true;
|
|
|
|
builder.RerollLoops = true;
|
|
|
|
builder.VerifyInput = true;
|
|
|
|
builder.VerifyOutput = true;
|
|
|
|
builder.populateFunctionPassManager(fpm);
|
|
|
|
for (auto & function : *module)
|
|
|
|
fpm.run(function);
|
2018-05-06 11:16:38 +00:00
|
|
|
|
|
|
|
/// name, mangled name
|
|
|
|
std::vector<std::pair<std::string, std::string>> function_names;
|
|
|
|
function_names.reserve(module->size());
|
2018-05-04 21:38:17 +00:00
|
|
|
for (const auto & function : *module)
|
|
|
|
{
|
2018-05-06 11:16:38 +00:00
|
|
|
std::string mangled_name;
|
|
|
|
llvm::raw_string_ostream mangled_name_stream(mangled_name);
|
|
|
|
llvm::Mangler::getNameWithPrefix(mangled_name_stream, function.getName(), layout);
|
2018-05-07 06:23:18 +00:00
|
|
|
mangled_name_stream.flush();
|
2018-05-06 11:16:38 +00:00
|
|
|
function_names.emplace_back(function.getName(), mangled_name);
|
2018-05-04 21:38:17 +00:00
|
|
|
}
|
2018-05-06 11:16:38 +00:00
|
|
|
|
|
|
|
#if LLVM_VERSION_MAJOR >= 7
|
|
|
|
llvm::orc::VModuleKey module_key = execution_session.allocateVModule();
|
2018-05-07 06:49:56 +00:00
|
|
|
if (compileLayer.addModule(module_key, std::move(module)))
|
|
|
|
throw Exception("Cannot add module to compile layer", ErrorCodes::CANNOT_COMPILE_CODE);
|
2018-05-06 11:16:38 +00:00
|
|
|
#else
|
2018-05-07 06:49:56 +00:00
|
|
|
if (compileLayer.addModule(module, std::make_shared<llvm::orc::NullResolver>()))
|
|
|
|
throw Exception("Cannot add module to compile layer", ErrorCodes::CANNOT_COMPILE_CODE);
|
2018-05-06 11:16:38 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
for (const auto & names : function_names)
|
2018-05-07 06:23:18 +00:00
|
|
|
{
|
|
|
|
if (auto symbol = compileLayer.findSymbol(names.second, false))
|
|
|
|
{
|
|
|
|
if (auto address_or_error = symbol.getAddress())
|
|
|
|
symbols[names.first] = reinterpret_cast<void *>(*address_or_error);
|
2018-05-07 06:49:56 +00:00
|
|
|
else
|
|
|
|
throw Exception("Cannot get an address of compiled symbol from a module", ErrorCodes::CANNOT_COMPILE_CODE);
|
2018-05-07 06:23:18 +00:00
|
|
|
}
|
2018-05-07 06:49:56 +00:00
|
|
|
else
|
|
|
|
throw Exception("Cannot find compiled symbol in a module", ErrorCodes::CANNOT_COMPILE_CODE);
|
2018-05-07 06:23:18 +00:00
|
|
|
}
|
2018-04-25 15:16:48 +00:00
|
|
|
}
|
2018-04-24 19:42:06 +00:00
|
|
|
};
|
|
|
|
|
2018-05-03 10:22:41 +00:00
|
|
|
class LLVMPreparedFunction : public PreparedFunctionImpl
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
std::string name;
|
|
|
|
std::shared_ptr<LLVMContext> context;
|
2018-05-06 10:42:35 +00:00
|
|
|
void * function;
|
2018-04-29 01:00:26 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
LLVMPreparedFunction(std::string name_, std::shared_ptr<LLVMContext> context)
|
2018-05-04 21:38:17 +00:00
|
|
|
: name(std::move(name_)), context(context), function(context->symbols.at(name))
|
|
|
|
{}
|
2018-04-29 01:00:26 +00:00
|
|
|
|
|
|
|
String getName() const override { return name; }
|
|
|
|
|
2018-05-03 10:22:41 +00:00
|
|
|
bool useDefaultImplementationForNulls() const override { return false; }
|
|
|
|
|
|
|
|
bool useDefaultImplementationForConstants() const override { return true; }
|
|
|
|
|
|
|
|
void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t block_size) override
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
auto col_res = block.getByPosition(result).type->createColumn()->cloneResized(block_size);
|
|
|
|
if (block_size)
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
std::vector<ColumnData> columns(arguments.size() + 1);
|
2018-05-06 10:42:35 +00:00
|
|
|
for (size_t i = 0; i < arguments.size(); ++i)
|
2018-04-29 01:00:26 +00:00
|
|
|
{
|
|
|
|
auto * column = block.getByPosition(arguments[i]).column.get();
|
|
|
|
if (!column)
|
2018-05-06 10:42:35 +00:00
|
|
|
throw Exception("Column " + block.getByPosition(arguments[i]).name + " is missing", ErrorCodes::LOGICAL_ERROR);
|
2018-04-29 01:00:26 +00:00
|
|
|
columns[i] = getColumnData(column);
|
|
|
|
}
|
|
|
|
columns[arguments.size()] = getColumnData(col_res.get());
|
|
|
|
reinterpret_cast<void (*) (size_t, ColumnData *)>(function)(block_size, columns.data());
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
}
|
2018-04-29 01:00:26 +00:00
|
|
|
block.getByPosition(result).column = std::move(col_res);
|
|
|
|
};
|
|
|
|
};
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
|
2018-04-29 18:03:58 +00:00
|
|
|
static void compileFunction(std::shared_ptr<LLVMContext> & context, const IFunctionBase & f)
|
|
|
|
{
|
2018-05-07 06:23:18 +00:00
|
|
|
ProfileEvents::increment(ProfileEvents::CompileFunction);
|
|
|
|
|
2018-04-29 18:03:58 +00:00
|
|
|
auto & arg_types = f.getArgumentTypes();
|
|
|
|
auto & b = context->builder;
|
|
|
|
auto * size_type = b.getIntNTy(sizeof(size_t) * 8);
|
|
|
|
auto * data_type = llvm::StructType::get(b.getInt8PtrTy(), b.getInt8PtrTy(), size_type);
|
2018-05-03 10:22:41 +00:00
|
|
|
auto * func_type = llvm::FunctionType::get(b.getVoidTy(), { size_type, data_type->getPointerTo() }, /*isVarArg=*/false);
|
2018-04-29 18:03:58 +00:00
|
|
|
auto * func = llvm::Function::Create(func_type, llvm::Function::ExternalLinkage, f.getName(), context->module.get());
|
|
|
|
auto args = func->args().begin();
|
|
|
|
llvm::Value * counter_arg = &*args++;
|
|
|
|
llvm::Value * columns_arg = &*args++;
|
|
|
|
|
|
|
|
auto * entry = llvm::BasicBlock::Create(b.getContext(), "entry", func);
|
|
|
|
b.SetInsertPoint(entry);
|
|
|
|
std::vector<ColumnDataPlaceholder> columns(arg_types.size() + 1);
|
2018-05-06 10:42:35 +00:00
|
|
|
for (size_t i = 0; i <= arg_types.size(); ++i)
|
2018-04-29 18:03:58 +00:00
|
|
|
{
|
|
|
|
auto & type = i == arg_types.size() ? f.getReturnType() : arg_types[i];
|
2018-05-03 10:22:41 +00:00
|
|
|
auto * data = b.CreateLoad(b.CreateConstInBoundsGEP1_32(data_type, columns_arg, i));
|
|
|
|
columns[i].data_init = b.CreatePointerCast(b.CreateExtractValue(data, {0}), toNativeType(b, removeNullable(type))->getPointerTo());
|
|
|
|
columns[i].null_init = type->isNullable() ? b.CreateExtractValue(data, {1}) : nullptr;
|
|
|
|
columns[i].stride = b.CreateExtractValue(data, {2});
|
2018-04-29 18:03:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// assume nonzero initial value in `counter_arg`
|
|
|
|
auto * loop = llvm::BasicBlock::Create(b.getContext(), "loop", func);
|
|
|
|
b.CreateBr(loop);
|
|
|
|
b.SetInsertPoint(loop);
|
|
|
|
auto * counter_phi = b.CreatePHI(counter_arg->getType(), 2);
|
|
|
|
counter_phi->addIncoming(counter_arg, entry);
|
|
|
|
for (auto & col : columns)
|
|
|
|
{
|
|
|
|
col.data = b.CreatePHI(col.data_init->getType(), 2);
|
|
|
|
col.data->addIncoming(col.data_init, entry);
|
|
|
|
if (col.null_init)
|
|
|
|
{
|
|
|
|
col.null = b.CreatePHI(col.null_init->getType(), 2);
|
|
|
|
col.null->addIncoming(col.null_init, entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ValuePlaceholders arguments(arg_types.size());
|
2018-05-06 10:42:35 +00:00
|
|
|
for (size_t i = 0; i < arguments.size(); ++i)
|
2018-04-29 18:03:58 +00:00
|
|
|
{
|
|
|
|
arguments[i] = [&b, &col = columns[i], &type = arg_types[i]]() -> llvm::Value *
|
|
|
|
{
|
|
|
|
auto * value = b.CreateLoad(col.data);
|
|
|
|
if (!col.null)
|
|
|
|
return value;
|
|
|
|
auto * is_null = b.CreateICmpNE(b.CreateLoad(col.null), b.getInt8(0));
|
|
|
|
auto * nullable = llvm::Constant::getNullValue(toNativeType(b, type));
|
|
|
|
return b.CreateInsertValue(b.CreateInsertValue(nullable, value, {0}), is_null, {1});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
auto * result = f.compile(b, std::move(arguments));
|
|
|
|
if (columns.back().null)
|
|
|
|
{
|
|
|
|
b.CreateStore(b.CreateExtractValue(result, {0}), columns.back().data);
|
|
|
|
b.CreateStore(b.CreateSelect(b.CreateExtractValue(result, {1}), b.getInt8(1), b.getInt8(0)), columns.back().null);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
b.CreateStore(result, columns.back().data);
|
|
|
|
}
|
|
|
|
auto * cur_block = b.GetInsertBlock();
|
|
|
|
for (auto & col : columns)
|
|
|
|
{
|
2018-05-07 13:03:26 +00:00
|
|
|
/// stride is either 0 or size of native type; output column is never constant; neither is at least one input
|
|
|
|
auto * is_const = &col == &columns.back() || columns.size() <= 2 ? b.getFalse() : b.CreateICmpEQ(col.stride, llvm::ConstantInt::get(size_type, 0));
|
2018-05-03 10:22:41 +00:00
|
|
|
col.data->addIncoming(b.CreateSelect(is_const, col.data, b.CreateConstInBoundsGEP1_32(nullptr, col.data, 1)), cur_block);
|
2018-04-29 18:03:58 +00:00
|
|
|
if (col.null)
|
2018-05-03 10:22:41 +00:00
|
|
|
col.null->addIncoming(b.CreateSelect(is_const, col.null, b.CreateConstInBoundsGEP1_32(nullptr, col.null, 1)), cur_block);
|
2018-04-29 18:03:58 +00:00
|
|
|
}
|
|
|
|
counter_phi->addIncoming(b.CreateSub(counter_phi, llvm::ConstantInt::get(size_type, 1)), cur_block);
|
|
|
|
|
|
|
|
auto * end = llvm::BasicBlock::Create(b.getContext(), "end", func);
|
|
|
|
b.CreateCondBr(b.CreateICmpNE(counter_phi, llvm::ConstantInt::get(size_type, 1)), loop, end);
|
|
|
|
b.SetInsertPoint(end);
|
|
|
|
b.CreateRetVoid();
|
|
|
|
}
|
|
|
|
|
|
|
|
static llvm::Constant * getNativeValue(llvm::Type * type, const IColumn & column, size_t i)
|
|
|
|
{
|
|
|
|
if (!type)
|
|
|
|
return nullptr;
|
|
|
|
if (auto * constant = typeid_cast<const ColumnConst *>(&column))
|
|
|
|
return getNativeValue(type, constant->getDataColumn(), 0);
|
|
|
|
if (auto * nullable = typeid_cast<const ColumnNullable *>(&column))
|
|
|
|
{
|
|
|
|
auto * value = getNativeValue(type->getContainedType(0), nullable->getNestedColumn(), i);
|
|
|
|
auto * is_null = llvm::ConstantInt::get(type->getContainedType(1), nullable->isNullAt(i));
|
|
|
|
return value ? llvm::ConstantStruct::get(static_cast<llvm::StructType *>(type), value, is_null) : nullptr;
|
|
|
|
}
|
|
|
|
if (type->isFloatTy())
|
|
|
|
return llvm::ConstantFP::get(type, static_cast<const ColumnVector<Float32> &>(column).getElement(i));
|
|
|
|
if (type->isDoubleTy())
|
|
|
|
return llvm::ConstantFP::get(type, static_cast<const ColumnVector<Float64> &>(column).getElement(i));
|
|
|
|
if (type->isIntegerTy())
|
|
|
|
return llvm::ConstantInt::get(type, column.getUInt(i));
|
|
|
|
/// TODO: if (type->isVectorTy())
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as IFunctionBase::compile, but also for constants and input columns.
|
|
|
|
using CompilableExpression = std::function<llvm::Value * (llvm::IRBuilderBase &, const ValuePlaceholders &)>;
|
|
|
|
|
|
|
|
static CompilableExpression subexpression(ColumnPtr c, DataTypePtr type)
|
|
|
|
{
|
|
|
|
return [=](llvm::IRBuilderBase & b, const ValuePlaceholders &) { return getNativeValue(toNativeType(b, type), *c, 0); };
|
|
|
|
}
|
|
|
|
|
|
|
|
static CompilableExpression subexpression(size_t i)
|
|
|
|
{
|
|
|
|
return [=](llvm::IRBuilderBase &, const ValuePlaceholders & inputs) { return inputs[i](); };
|
|
|
|
}
|
|
|
|
|
|
|
|
static CompilableExpression subexpression(const IFunctionBase & f, std::vector<CompilableExpression> args)
|
|
|
|
{
|
|
|
|
return [&, args = std::move(args)](llvm::IRBuilderBase & builder, const ValuePlaceholders & inputs)
|
|
|
|
{
|
|
|
|
ValuePlaceholders input;
|
|
|
|
for (const auto & arg : args)
|
|
|
|
input.push_back([&]() { return arg(builder, inputs); });
|
|
|
|
auto * result = f.compile(builder, input);
|
|
|
|
if (result->getType() != toNativeType(builder, f.getReturnType()))
|
2018-05-06 10:42:35 +00:00
|
|
|
throw Exception("Function " + f.getName() + " generated an llvm::Value of invalid type", ErrorCodes::LOGICAL_ERROR);
|
2018-04-29 18:03:58 +00:00
|
|
|
return result;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-04-29 01:00:26 +00:00
|
|
|
class LLVMFunction : public IFunctionBase
|
|
|
|
{
|
2018-04-29 18:03:58 +00:00
|
|
|
std::string name;
|
2018-04-29 01:00:26 +00:00
|
|
|
Names arg_names;
|
|
|
|
DataTypes arg_types;
|
|
|
|
std::shared_ptr<LLVMContext> context;
|
2018-04-29 18:03:58 +00:00
|
|
|
std::vector<FunctionBasePtr> originals;
|
|
|
|
std::unordered_map<StringRef, CompilableExpression> subexpressions;
|
2018-04-29 01:00:26 +00:00
|
|
|
|
|
|
|
public:
|
2018-04-29 18:03:58 +00:00
|
|
|
LLVMFunction(const ExpressionActions::Actions & actions, std::shared_ptr<LLVMContext> context, const Block & sample_block)
|
|
|
|
: name(actions.back().result_name), context(context)
|
2018-04-27 21:30:38 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
for (const auto & c : sample_block)
|
2018-04-29 18:03:58 +00:00
|
|
|
/// TODO: implement `getNativeValue` for all types & replace the check with `c.column && toNativeType(...)`
|
|
|
|
if (c.column && getNativeValue(toNativeType(context->builder, c.type), *c.column, 0))
|
|
|
|
subexpressions[c.name] = subexpression(c.column, c.type);
|
2018-04-29 01:00:26 +00:00
|
|
|
for (const auto & action : actions)
|
|
|
|
{
|
|
|
|
const auto & names = action.argument_names;
|
|
|
|
const auto & types = action.function->getArgumentTypes();
|
2018-04-29 18:03:58 +00:00
|
|
|
std::vector<CompilableExpression> args;
|
2018-05-06 10:42:35 +00:00
|
|
|
for (size_t i = 0; i < names.size(); ++i)
|
2018-04-29 01:00:26 +00:00
|
|
|
{
|
2018-04-29 18:03:58 +00:00
|
|
|
auto inserted = subexpressions.emplace(names[i], subexpression(arg_names.size()));
|
|
|
|
if (inserted.second)
|
|
|
|
{
|
|
|
|
arg_names.push_back(names[i]);
|
|
|
|
arg_types.push_back(types[i]);
|
|
|
|
}
|
|
|
|
args.push_back(inserted.first->second);
|
2018-04-29 01:00:26 +00:00
|
|
|
}
|
2018-04-29 18:03:58 +00:00
|
|
|
subexpressions[action.result_name] = subexpression(*action.function, std::move(args));
|
|
|
|
originals.push_back(action.function);
|
2018-04-29 01:00:26 +00:00
|
|
|
}
|
2018-04-29 18:03:58 +00:00
|
|
|
compileFunction(context, *this);
|
|
|
|
}
|
2018-04-29 01:00:26 +00:00
|
|
|
|
2018-04-29 18:03:58 +00:00
|
|
|
bool isCompilable() const override { return true; }
|
2018-04-29 01:00:26 +00:00
|
|
|
|
2018-04-29 18:03:58 +00:00
|
|
|
llvm::Value * compile(llvm::IRBuilderBase & builder, ValuePlaceholders values) const override { return subexpressions.at(name)(builder, values); }
|
2018-04-24 13:21:42 +00:00
|
|
|
|
2018-04-29 18:03:58 +00:00
|
|
|
String getName() const override { return name; }
|
2018-04-29 01:00:26 +00:00
|
|
|
|
|
|
|
const Names & getArgumentNames() const { return arg_names; }
|
|
|
|
|
|
|
|
const DataTypes & getArgumentTypes() const override { return arg_types; }
|
|
|
|
|
2018-04-29 18:03:58 +00:00
|
|
|
const DataTypePtr & getReturnType() const override { return originals.back()->getReturnType(); }
|
2018-04-29 01:00:26 +00:00
|
|
|
|
2018-04-29 18:03:58 +00:00
|
|
|
PreparedFunctionPtr prepare(const Block &) const override { return std::make_shared<LLVMPreparedFunction>(name, context); }
|
2018-04-29 01:00:26 +00:00
|
|
|
|
|
|
|
bool isDeterministic() override
|
Implement JIT compilation, without a loop for now.
It actually seems to work, so long as you only have one row that is. E.g.
> select something(cast(number + 6 as Float64), cast(number + 2 as Float64)) from system.numbers limit 1';
8
with this IR:
define void @"something(CAST(plus(number, 6), 'Float64'), CAST(plus(number, 2), 'Float64'))"(void**, i8*, double*) {
entry:
%3 = load void*, void** %0
%4 = bitcast void* %3 to double*
%5 = load double, double* %4
%6 = getelementptr void*, void** %0, i32 1
%7 = load void*, void** %6
%8 = bitcast void* %7 to double*
%9 = load double, double* %8
%10 = fadd double %5, %9
store double %10, double* %2
ret void
}
2018-04-23 23:52:54 +00:00
|
|
|
{
|
2018-04-29 18:03:58 +00:00
|
|
|
for (const auto & f : originals)
|
|
|
|
if (!f->isDeterministic())
|
2018-04-29 01:00:26 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
2018-04-27 21:30:38 +00:00
|
|
|
}
|
2018-04-29 01:00:26 +00:00
|
|
|
|
|
|
|
bool isDeterministicInScopeOfQuery() override
|
2018-04-27 15:44:38 +00:00
|
|
|
{
|
2018-04-29 18:03:58 +00:00
|
|
|
for (const auto & f : originals)
|
|
|
|
if (!f->isDeterministicInScopeOfQuery())
|
2018-04-29 01:00:26 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
2018-04-24 13:21:42 +00:00
|
|
|
}
|
|
|
|
|
2018-04-29 01:00:26 +00:00
|
|
|
bool isSuitableForConstantFolding() const override
|
2018-04-27 15:44:38 +00:00
|
|
|
{
|
2018-04-29 18:03:58 +00:00
|
|
|
for (const auto & f : originals)
|
|
|
|
if (!f->isSuitableForConstantFolding())
|
2018-04-29 01:00:26 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
2018-04-27 15:44:38 +00:00
|
|
|
}
|
Implement JIT compilation, without a loop for now.
It actually seems to work, so long as you only have one row that is. E.g.
> select something(cast(number + 6 as Float64), cast(number + 2 as Float64)) from system.numbers limit 1';
8
with this IR:
define void @"something(CAST(plus(number, 6), 'Float64'), CAST(plus(number, 2), 'Float64'))"(void**, i8*, double*) {
entry:
%3 = load void*, void** %0
%4 = bitcast void* %3 to double*
%5 = load double, double* %4
%6 = getelementptr void*, void** %0, i32 1
%7 = load void*, void** %6
%8 = bitcast void* %7 to double*
%9 = load double, double* %8
%10 = fadd double %5, %9
store double %10, double* %2
ret void
}
2018-04-23 23:52:54 +00:00
|
|
|
|
2018-04-29 01:00:26 +00:00
|
|
|
bool isInjective(const Block & sample_block) override
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
{
|
2018-04-29 18:03:58 +00:00
|
|
|
for (const auto & f : originals)
|
|
|
|
if (!f->isInjective(sample_block))
|
2018-04-29 01:00:26 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
2018-04-27 21:30:38 +00:00
|
|
|
}
|
2018-04-29 01:00:26 +00:00
|
|
|
|
|
|
|
bool hasInformationAboutMonotonicity() const override
|
2018-04-27 21:30:38 +00:00
|
|
|
{
|
2018-04-29 18:03:58 +00:00
|
|
|
for (const auto & f : originals)
|
|
|
|
if (!f->hasInformationAboutMonotonicity())
|
2018-04-29 01:00:26 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
}
|
Implement JIT compilation, without a loop for now.
It actually seems to work, so long as you only have one row that is. E.g.
> select something(cast(number + 6 as Float64), cast(number + 2 as Float64)) from system.numbers limit 1';
8
with this IR:
define void @"something(CAST(plus(number, 6), 'Float64'), CAST(plus(number, 2), 'Float64'))"(void**, i8*, double*) {
entry:
%3 = load void*, void** %0
%4 = bitcast void* %3 to double*
%5 = load double, double* %4
%6 = getelementptr void*, void** %0, i32 1
%7 = load void*, void** %6
%8 = bitcast void* %7 to double*
%9 = load double, double* %8
%10 = fadd double %5, %9
store double %10, double* %2
ret void
}
2018-04-23 23:52:54 +00:00
|
|
|
|
2018-04-29 01:00:26 +00:00
|
|
|
Monotonicity getMonotonicityForRange(const IDataType & type, const Field & left, const Field & right) const override
|
2018-04-27 15:44:38 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
const IDataType * type_ = &type;
|
|
|
|
Field left_ = left;
|
|
|
|
Field right_ = right;
|
|
|
|
Monotonicity result(true, true, true);
|
|
|
|
/// monotonicity is only defined for unary functions, so the chain must describe a sequence of nested calls
|
2018-05-06 10:42:35 +00:00
|
|
|
for (size_t i = 0; i < originals.size(); ++i)
|
2018-04-28 15:11:23 +00:00
|
|
|
{
|
2018-04-29 18:03:58 +00:00
|
|
|
Monotonicity m = originals[i]->getMonotonicityForRange(*type_, left_, right_);
|
2018-04-29 01:00:26 +00:00
|
|
|
if (!m.is_monotonic)
|
|
|
|
return m;
|
|
|
|
result.is_positive ^= !m.is_positive;
|
|
|
|
result.is_always_monotonic &= m.is_always_monotonic;
|
2018-04-29 18:03:58 +00:00
|
|
|
if (i + 1 < originals.size())
|
2018-04-29 01:00:26 +00:00
|
|
|
{
|
|
|
|
if (left_ != Field())
|
2018-04-29 18:03:58 +00:00
|
|
|
applyFunction(*originals[i], left_);
|
2018-04-29 01:00:26 +00:00
|
|
|
if (right_ != Field())
|
2018-04-29 18:03:58 +00:00
|
|
|
applyFunction(*originals[i], right_);
|
2018-04-29 01:00:26 +00:00
|
|
|
if (!m.is_positive)
|
|
|
|
std::swap(left_, right_);
|
2018-04-29 18:03:58 +00:00
|
|
|
type_ = originals[i]->getReturnType().get();
|
2018-04-29 01:00:26 +00:00
|
|
|
}
|
2018-04-28 15:11:23 +00:00
|
|
|
}
|
2018-04-29 01:00:26 +00:00
|
|
|
return result;
|
2018-04-27 15:44:38 +00:00
|
|
|
}
|
2018-04-29 01:00:26 +00:00
|
|
|
};
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
|
2018-04-29 01:00:26 +00:00
|
|
|
static bool isCompilable(llvm::IRBuilderBase & builder, const IFunctionBase& function)
|
2018-04-25 11:55:54 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
if (!toNativeType(builder, function.getReturnType()))
|
|
|
|
return false;
|
|
|
|
for (const auto & type : function.getArgumentTypes())
|
|
|
|
if (!toNativeType(builder, type))
|
|
|
|
return false;
|
|
|
|
return function.isCompilable();
|
2018-04-25 11:55:54 +00:00
|
|
|
}
|
|
|
|
|
2018-04-29 01:00:26 +00:00
|
|
|
void compileFunctions(ExpressionActions::Actions & actions, const Names & output_columns, const Block & sample_block)
|
2018-04-25 11:55:54 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
auto context = std::make_shared<LLVMContext>();
|
|
|
|
/// an empty optional is a poisoned value prohibiting the column's producer from being removed
|
|
|
|
/// (which it could be, if it was inlined into every dependent function).
|
|
|
|
std::unordered_map<std::string, std::unordered_set<std::optional<size_t>>> current_dependents;
|
|
|
|
for (const auto & name : output_columns)
|
|
|
|
current_dependents[name].emplace();
|
|
|
|
/// a snapshot of each compilable function's dependents at the time of its execution.
|
|
|
|
std::vector<std::unordered_set<std::optional<size_t>>> dependents(actions.size());
|
|
|
|
for (size_t i = actions.size(); i--;)
|
|
|
|
{
|
|
|
|
switch (actions[i].type)
|
|
|
|
{
|
|
|
|
case ExpressionAction::REMOVE_COLUMN:
|
|
|
|
current_dependents.erase(actions[i].source_name);
|
|
|
|
/// poison every other column used after this point so that inlining chains do not cross it.
|
|
|
|
for (auto & dep : current_dependents)
|
|
|
|
dep.second.emplace();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ExpressionAction::PROJECT:
|
|
|
|
current_dependents.clear();
|
|
|
|
for (const auto & proj : actions[i].projection)
|
|
|
|
current_dependents[proj.first].emplace();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ExpressionAction::ADD_COLUMN:
|
|
|
|
case ExpressionAction::COPY_COLUMN:
|
|
|
|
case ExpressionAction::ARRAY_JOIN:
|
|
|
|
case ExpressionAction::JOIN:
|
|
|
|
{
|
|
|
|
Names columns = actions[i].getNeededColumns();
|
|
|
|
for (const auto & column : columns)
|
|
|
|
current_dependents[column].emplace();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ExpressionAction::APPLY_FUNCTION:
|
|
|
|
{
|
|
|
|
dependents[i] = current_dependents[actions[i].result_name];
|
|
|
|
const bool compilable = isCompilable(context->builder, *actions[i].function);
|
|
|
|
for (const auto & name : actions[i].argument_names)
|
|
|
|
{
|
|
|
|
if (compilable)
|
|
|
|
current_dependents[name].emplace(i);
|
|
|
|
else
|
|
|
|
current_dependents[name].emplace();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<ExpressionActions::Actions> fused(actions.size());
|
2018-05-06 10:42:35 +00:00
|
|
|
for (size_t i = 0; i < actions.size(); ++i)
|
2018-04-25 11:55:54 +00:00
|
|
|
{
|
2018-04-29 01:00:26 +00:00
|
|
|
if (actions[i].type != ExpressionAction::APPLY_FUNCTION || !isCompilable(context->builder, *actions[i].function))
|
|
|
|
continue;
|
2018-05-07 06:23:18 +00:00
|
|
|
|
2018-04-29 22:23:27 +00:00
|
|
|
fused[i].push_back(actions[i]);
|
2018-04-29 01:00:26 +00:00
|
|
|
if (dependents[i].find({}) != dependents[i].end())
|
2018-04-25 11:55:54 +00:00
|
|
|
{
|
2018-05-03 13:34:42 +00:00
|
|
|
/// the result of compiling one function in isolation is pretty much the same as its `execute` method.
|
|
|
|
if (fused[i].size() == 1)
|
|
|
|
continue;
|
2018-04-29 01:00:26 +00:00
|
|
|
auto fn = std::make_shared<LLVMFunction>(std::move(fused[i]), context, sample_block);
|
|
|
|
actions[i].function = fn;
|
|
|
|
actions[i].argument_names = fn->getArgumentNames();
|
|
|
|
continue;
|
2018-04-25 11:55:54 +00:00
|
|
|
}
|
2018-05-07 06:23:18 +00:00
|
|
|
|
2018-04-29 01:00:26 +00:00
|
|
|
/// TODO: determine whether it's profitable to inline the function if there's more than one dependent.
|
|
|
|
for (const auto & dep : dependents[i])
|
2018-04-29 22:23:27 +00:00
|
|
|
fused[*dep].insert(fused[*dep].end(), fused[i].begin(), fused[i].end());
|
2018-04-25 11:55:54 +00:00
|
|
|
}
|
2018-05-07 06:23:18 +00:00
|
|
|
|
2018-04-29 01:00:26 +00:00
|
|
|
context->finalize();
|
2018-04-25 11:55:54 +00:00
|
|
|
}
|
|
|
|
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
2018-04-27 15:44:38 +00:00
|
|
|
struct LLVMTargetInitializer
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
{
|
2018-04-27 15:44:38 +00:00
|
|
|
LLVMTargetInitializer()
|
|
|
|
{
|
|
|
|
llvm::InitializeNativeTarget();
|
|
|
|
llvm::InitializeNativeTargetAsmPrinter();
|
|
|
|
}
|
|
|
|
} llvmInitializer;
|
Add a JIT interface for row-wise default-nullable functions.
Not actually implemented, though. It does print out some jit-compiled stuff,
but that's about it. For example, this query:
select number from system.numbers where something(cast(number as Float64)) == 4
results in this on server's stderr:
define double @"something(CAST(number, 'Float64'))"(void**, i8*, void*) {
"something(CAST(number, 'Float64'))":
ret double 1.234500e+04
}
(and an exception, because that's what the non-jitted method does.)
As one may notice, this function neither reads the input (first argument;
tuple of arrays) nor writes the output (third argument; array), instead
returning some general nonsense.
In addition, `#if USE_EMBEDDED_COMPILER` doesn't work for some reason,
including LLVM headers requires -Wno-unused-parameter, this probably only
works on LLVM 5.0 due to rampant API instability, and I'm definitely
no expert on CMake. In short, there's still a long way to go.
2018-04-23 22:29:39 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 15:19:22 +00:00
|
|
|
#endif
|