Skip to content
This repository was archived by the owner on May 9, 2024. It is now read-only.

Add support for bitwise operations. #564

Merged
merged 1 commit into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions omniscidb/Analyzer/Analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,14 @@ const hdk::ir::Type* analyze_type_info(hdk::ir::OpType op,
}
}
result_type = common_type;
} else if (hdk::ir::isBitwise(op)) {
if (!left_type->isInteger() || !right_type->isInteger()) {
throw std::runtime_error("non-integer operands in bitwise operation.");
}
common_type = common_numeric_type(left_type, right_type);
*new_left_type = common_type->withNullable(left_type->nullable());
*new_right_type = common_type->withNullable(right_type->nullable());
result_type = common_type;
} else {
throw std::runtime_error("invalid binary operator type.");
}
Expand Down Expand Up @@ -646,6 +654,9 @@ hdk::ir::ExprPtr normalizeOperExpr(const hdk::ir::OpType optype,
left_expr = left_expr->decompress();
right_expr = right_expr->decompress();
}
} else if (hdk::ir::isBitwise(optype)) {
left_expr = left_expr->cast(new_left_type);
right_expr = right_expr->cast(new_right_type);
} else if (!hdk::ir::isComparison(optype)) {
left_expr = left_expr->decompress();
right_expr = right_expr->decompress();
Expand Down
5 changes: 5 additions & 0 deletions omniscidb/IR/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ class UOper : public Expr {
OpType opType() const { return op_type_; }

bool isNot() const { return op_type_ == OpType::kNot; }
bool isBwNot() const { return op_type_ == OpType::kBwNot; }
bool isUMinus() const { return op_type_ == OpType::kUMinus; }
bool isIsNull() const { return op_type_ == OpType::kIsNull; }
bool isCast() const { return op_type_ == OpType::kCast; }
Expand Down Expand Up @@ -342,6 +343,9 @@ class BinOper : public Expr {
bool isGe() const { return op_type_ == OpType::kGe; }
bool isAnd() const { return op_type_ == OpType::kAnd; }
bool isOr() const { return op_type_ == OpType::kOr; }
bool isBwAnd() const { return op_type_ == OpType::kBwAnd; }
bool isBwOr() const { return op_type_ == OpType::kBwOr; }
bool isBwXor() const { return op_type_ == OpType::kBwXor; }
bool isMinus() const { return op_type_ == OpType::kMinus; }
bool isPlus() const { return op_type_ == OpType::kPlus; }
bool isMul() const { return op_type_ == OpType::kMul; }
Expand All @@ -353,6 +357,7 @@ class BinOper : public Expr {
bool isComparison() const { return hdk::ir::isComparison(op_type_); }
bool isLogic() const { return hdk::ir::isLogic(op_type_); }
bool isArithmetic() const { return hdk::ir::isArithmetic(op_type_); }
bool isBitwise() const { return hdk::ir::isBitwise(op_type_); }

Qualifier qualifier() const { return qualifier_; }
const Expr* leftOperand() const { return left_operand_.get(); }
Expand Down
19 changes: 18 additions & 1 deletion omniscidb/IR/OpType.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ enum class OpType {
kAnd,
kOr,
kNot,
kBwAnd,
kBwOr,
kBwXor,
kBwNot,
kMinus,
kPlus,
kMul,
Expand Down Expand Up @@ -60,12 +64,17 @@ inline OpType commuteComparison(OpType op) {
}
inline bool isUnary(OpType op) {
return op == OpType::kNot || op == OpType::kUMinus || op == OpType::kIsNull ||
op == OpType::kCast;
op == OpType::kCast || op == OpType::kBwNot;
}
inline bool isEquivalence(OpType op) {
return op == OpType::kEq || op == OpType::kBwEq;
}

inline bool isBitwise(OpType op) {
return op == OpType::kBwAnd || op == OpType::kBwOr || op == OpType::kBwXor ||
op == OpType::kBwNot;
}

enum class Qualifier { kOne, kAny, kAll };

enum class AggType {
Expand Down Expand Up @@ -126,6 +135,14 @@ inline std::string toString(hdk::ir::OpType op) {
return "OR";
case hdk::ir::OpType::kNot:
return "NOT";
case hdk::ir::OpType::kBwAnd:
return "BW_AND";
case hdk::ir::OpType::kBwOr:
return "BW_OR";
case hdk::ir::OpType::kBwXor:
return "BW_XOR";
case hdk::ir::OpType::kBwNot:
return "BW_NOT";
case hdk::ir::OpType::kMinus:
return "MINUS";
case hdk::ir::OpType::kPlus:
Expand Down
78 changes: 78 additions & 0 deletions omniscidb/QueryBuilder/QueryBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,84 @@ BuilderExpr BuilderExpr::logicalOr(const BuilderExpr& rhs) const {
}
}

BuilderExpr BuilderExpr::bwAnd(const BuilderExpr& rhs) const {
try {
auto bin_oper = Analyzer::normalizeOperExpr(
OpType::kBwAnd, Qualifier::kOne, expr_, rhs.expr(), nullptr);
return {builder_, bin_oper, "", true};
} catch (std::runtime_error& e) {
throw InvalidQueryError() << "Cannot apply BW_AND operation for operand types "
<< expr_->type()->toString() << " and "
<< rhs.expr()->type()->toString();
}
}

BuilderExpr BuilderExpr::bwAnd(int val) const {
return bwAnd(builder_->cst(val, builder_->ctx_.int32(false)));
}

BuilderExpr BuilderExpr::bwAnd(int64_t val) const {
return bwAnd(builder_->cst(val, builder_->ctx_.int64(false)));
}

BuilderExpr BuilderExpr::bwOr(const BuilderExpr& rhs) const {
try {
auto bin_oper = Analyzer::normalizeOperExpr(
OpType::kBwOr, Qualifier::kOne, expr_, rhs.expr(), nullptr);
return {builder_, bin_oper, "", true};
} catch (std::runtime_error& e) {
throw InvalidQueryError() << "Cannot apply BW_OR operation for operand types "
<< expr_->type()->toString() << " and "
<< rhs.expr()->type()->toString();
}
}

BuilderExpr BuilderExpr::bwOr(int val) const {
return bwOr(builder_->cst(val, builder_->ctx_.int32(false)));
}

BuilderExpr BuilderExpr::bwOr(int64_t val) const {
return bwOr(builder_->cst(val, builder_->ctx_.int64(false)));
}

BuilderExpr BuilderExpr::bwXor(const BuilderExpr& rhs) const {
try {
auto bin_oper = Analyzer::normalizeOperExpr(
OpType::kBwXor, Qualifier::kOne, expr_, rhs.expr(), nullptr);
return {builder_, bin_oper, "", true};
} catch (std::runtime_error& e) {
throw InvalidQueryError() << "Cannot apply BW_XOR operation for operand types "
<< expr_->type()->toString() << " and "
<< rhs.expr()->type()->toString();
}
}

BuilderExpr BuilderExpr::bwXor(int val) const {
return bwXor(builder_->cst(val, builder_->ctx_.int32(false)));
}

BuilderExpr BuilderExpr::bwXor(int64_t val) const {
return bwXor(builder_->cst(val, builder_->ctx_.int64(false)));
}

BuilderExpr BuilderExpr::bwNot() const {
if (!expr_->type()->isInteger()) {
throw InvalidQueryError("Only integer expressions are allowed for BW_NOT operation.");
}

if (expr_->is<Constant>()) {
auto cst_expr = expr_->as<Constant>();
if (cst_expr->isNull()) {
return *this;
}
return builder_->cst(~cst_expr->intVal(), cst_expr->type());
}

auto uoper =
makeExpr<UOper>(expr_->type(), expr_->containsAgg(), OpType::kBwNot, expr_);
return {builder_, uoper, "", true};
}

BuilderExpr BuilderExpr::eq(const BuilderExpr& rhs) const {
try {
auto bin_oper = Analyzer::normalizeOperExpr(
Expand Down
14 changes: 14 additions & 0 deletions omniscidb/QueryBuilder/QueryBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,20 @@ class BuilderExpr {
BuilderExpr logicalAnd(const BuilderExpr& rhs) const;
BuilderExpr logicalOr(const BuilderExpr& rhs) const;

BuilderExpr bwAnd(const BuilderExpr& rhs) const;
BuilderExpr bwAnd(int val) const;
BuilderExpr bwAnd(int64_t val) const;

BuilderExpr bwOr(const BuilderExpr& rhs) const;
BuilderExpr bwOr(int val) const;
BuilderExpr bwOr(int64_t val) const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use explicit bit width everywhere? I'd expect either int32_t with int64_t or int with long long; the latter should be incorrect in our case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int and int64_t overloads match all other similar functions. We can use int32_t and int64_t instead, but it should be done globally then, not only for bitwise operations.


BuilderExpr bwXor(const BuilderExpr& rhs) const;
BuilderExpr bwXor(int val) const;
BuilderExpr bwXor(int64_t val) const;

BuilderExpr bwNot() const;

BuilderExpr eq(const BuilderExpr& rhs) const;
BuilderExpr eq(int val) const;
BuilderExpr eq(int64_t val) const;
Expand Down
64 changes: 64 additions & 0 deletions omniscidb/QueryEngine/ArithmeticIR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,70 @@ llvm::Value* CodeGenerator::codegenArith(const hdk::ir::BinOper* bin_oper,
return nullptr;
}

llvm::Value* CodeGenerator::codegenBitwise(const hdk::ir::UOper* uoper,
const CompilationOptions& co) {
CHECK(uoper->opType() == hdk::ir::OpType::kBwNot);
auto op = uoper->operand();
auto op_type = op->type();
CHECK(op_type->isInteger());
auto op_lv = codegen(op, true, co).front();
const auto int_typename = numeric_or_time_interval_type_name(op->type(), op_type);
const auto null_check_suffix = get_null_check_suffix(op->type(), op_type);
if (null_check_suffix.empty()) {
return cgen_state_->ir_builder_.CreateNot(op_lv);
} else {
return cgen_state_->emitCall(
"bw_not_" + int_typename + null_check_suffix,
{op_lv, cgen_state_->llInt(inline_int_null_value(op_type))});
}
}

llvm::Value* CodeGenerator::codegenBitwise(const hdk::ir::BinOper* bin_oper,
const CompilationOptions& co) {
const auto lhs = bin_oper->leftOperand();
const auto rhs = bin_oper->rightOperand();
const auto& lhs_type = lhs->type();
const auto& rhs_type = rhs->type();
CHECK(lhs_type->isInteger());
CHECK(rhs_type->isInteger());
CHECK_EQ(lhs_type->size(), rhs_type->size());
auto lhs_lv = codegen(lhs, true, co).front();
auto rhs_lv = codegen(rhs, true, co).front();
const auto int_typename = numeric_or_time_interval_type_name(lhs_type, rhs_type);
const auto null_check_suffix = get_null_check_suffix(lhs_type, rhs_type);

if (null_check_suffix.empty()) {
switch (bin_oper->opType()) {
case hdk::ir::OpType::kBwAnd:
return cgen_state_->ir_builder_.CreateAnd(lhs_lv, rhs_lv);
case hdk::ir::OpType::kBwOr:
return cgen_state_->ir_builder_.CreateOr(lhs_lv, rhs_lv);
case hdk::ir::OpType::kBwXor:
return cgen_state_->ir_builder_.CreateXor(lhs_lv, rhs_lv);
default:
CHECK(false);
}
}

std::string fn_name;
switch (bin_oper->opType()) {
case hdk::ir::OpType::kBwAnd:
fn_name = "bw_and_";
break;
case hdk::ir::OpType::kBwOr:
fn_name = "bw_or_";
break;
case hdk::ir::OpType::kBwXor:
fn_name = "bw_xor_";
break;
default:
CHECK(false);
}
return cgen_state_->emitCall(
fn_name + int_typename + null_check_suffix,
{lhs_lv, rhs_lv, cgen_state_->llInt(inline_int_null_value(lhs_type))});
}

// Handle integer or integer-like (decimal, time, date) operand types.
llvm::Value* CodeGenerator::codegenIntArith(const hdk::ir::BinOper* bin_oper,
llvm::Value* lhs_lv,
Expand Down
12 changes: 12 additions & 0 deletions omniscidb/QueryEngine/CalciteDeserializerUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ inline hdk::ir::OpType to_sql_op(const std::string& op_str) {
if (op_str == std::string("NOT")) {
return hdk::ir::OpType::kNot;
}
if (op_str == std::string("BW_AND") || op_str == std::string("BIT_AND")) {
return hdk::ir::OpType::kBwAnd;
}
if (op_str == std::string("BW_OR") || op_str == std::string("BIT_OR")) {
return hdk::ir::OpType::kBwOr;
}
if (op_str == std::string("BW_XOR") || op_str == std::string("BIT_XOR")) {
return hdk::ir::OpType::kBwXor;
}
if (op_str == std::string("BW_NOT") || op_str == std::string("BIT_NOT")) {
return hdk::ir::OpType::kBwNot;
}
if (op_str == std::string("IS NULL")) {
return hdk::ir::OpType::kIsNull;
}
Expand Down
3 changes: 3 additions & 0 deletions omniscidb/QueryEngine/CodeGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ class CodeGenerator {

llvm::Value* codegenArith(const hdk::ir::BinOper*, const CompilationOptions&);

llvm::Value* codegenBitwise(const hdk::ir::UOper*, const CompilationOptions&);
llvm::Value* codegenBitwise(const hdk::ir::BinOper*, const CompilationOptions&);

llvm::Value* codegenUMinus(const hdk::ir::UOper*, const CompilationOptions&);

llvm::Value* codegenCmp(const hdk::ir::BinOper*, const CompilationOptions&);
Expand Down
5 changes: 5 additions & 0 deletions omniscidb/QueryEngine/IRCodegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ llvm::Value* CodeGenerator::codegen(const hdk::ir::BinOper* bin_oper,
if (bin_oper->isArithmetic()) {
return codegenArith(bin_oper, co);
}
if (bin_oper->isBitwise()) {
return codegenBitwise(bin_oper, co);
}
if (bin_oper->isComparison()) {
return codegenCmp(bin_oper, co);
}
Expand Down Expand Up @@ -188,6 +191,8 @@ llvm::Value* CodeGenerator::codegen(const hdk::ir::UOper* u_oper,
}
case hdk::ir::OpType::kUnnest:
return codegenUnnest(u_oper, co);
case hdk::ir::OpType::kBwNot:
return codegenBitwise(u_oper, co);
default:
UNREACHABLE();
}
Expand Down
26 changes: 26 additions & 0 deletions omniscidb/QueryEngine/RuntimeFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@

// arithmetic operator implementations

#define DEF_UNARY_NULLABLE(type, null_type, opname, opsym) \
extern "C" RUNTIME_EXPORT ALWAYS_INLINE type opname##_##type##_nullable( \
const type op, const null_type null_val) { \
if (op != null_val) { \
return opsym op; \
} \
return null_val; \
}

#define DEF_ARITH_NULLABLE(type, null_type, opname, opsym) \
extern "C" RUNTIME_EXPORT ALWAYS_INLINE type opname##_##type##_nullable( \
const type lhs, const type rhs, const null_type null_val) { \
Expand Down Expand Up @@ -187,6 +196,23 @@ DEF_ARITH_NULLABLE_RHS(int64_t, int64_t, mod, %)
DEF_SAFE_INF_DIV_NULLABLE(float, float, safe_inf_div)
DEF_SAFE_INF_DIV_NULLABLE(double, double, safe_inf_div)

#define DEF_ALL_NULLABLE_BW_OPS(type, null_type) \
DEF_ARITH_NULLABLE(type, null_type, bw_and, &) \
DEF_ARITH_NULLABLE_LHS(type, null_type, bw_and, &) \
DEF_ARITH_NULLABLE_RHS(type, null_type, bw_and, &) \
DEF_ARITH_NULLABLE(type, null_type, bw_or, |) \
DEF_ARITH_NULLABLE_LHS(type, null_type, bw_or, |) \
DEF_ARITH_NULLABLE_RHS(type, null_type, bw_or, |) \
DEF_ARITH_NULLABLE(type, null_type, bw_xor, ^) \
DEF_ARITH_NULLABLE_LHS(type, null_type, bw_xor, ^) \
DEF_ARITH_NULLABLE_RHS(type, null_type, bw_xor, ^) \
DEF_UNARY_NULLABLE(type, null_type, bw_not, ~)

DEF_ALL_NULLABLE_BW_OPS(int8_t, int64_t)
DEF_ALL_NULLABLE_BW_OPS(int16_t, int64_t)
DEF_ALL_NULLABLE_BW_OPS(int32_t, int64_t)
DEF_ALL_NULLABLE_BW_OPS(int64_t, int64_t)

#undef DEF_BINARY_NULLABLE_ALL_OPS
#undef DEF_SAFE_DIV_NULLABLE
#undef DEF_CMP_NULLABLE_RHS
Expand Down
Loading