From 87468dee50d445279db554545239a85aea1b8d53 Mon Sep 17 00:00:00 2001 From: Pindikura Ravindra Date: Mon, 11 Jun 2018 14:01:31 +0530 Subject: [PATCH] GDV-46: [C++] Add unit tests for bitmap/time fns - Added definitions for other integer types (int8, int16) - Added definitions for unsigned types - Added a test for arithmetic ops on all int types - The functions should be inlined in the pre-compiled library, but not in the unit tests. Added a compiler flag to control this. --- cpp/src/gandiva/codegen/function_registry.cc | 18 +++- cpp/src/gandiva/codegen/function_registry.h | 2 +- cpp/src/gandiva/codegen/llvm_types.cc | 8 ++ cpp/src/gandiva/codegen/llvm_types.h | 4 + cpp/src/gandiva/integ/projector_test.cc | 92 +++++++++++++++++++ cpp/src/gandiva/integ/test_util.h | 6 ++ cpp/src/gandiva/precompiled/CMakeLists.txt | 2 + cpp/src/gandiva/precompiled/arithmetic_ops.cc | 22 +++-- cpp/src/gandiva/precompiled/bitmap.cc | 6 +- cpp/src/gandiva/precompiled/bitmap_test.cc | 60 ++++++++++++ cpp/src/gandiva/precompiled/sample.cc | 1 + cpp/src/gandiva/precompiled/time.cc | 12 +-- cpp/src/gandiva/precompiled/time_test.cc | 37 ++++++++ cpp/src/gandiva/precompiled/types.h | 31 ++++++- 14 files changed, 279 insertions(+), 22 deletions(-) create mode 100644 cpp/src/gandiva/precompiled/bitmap_test.cc create mode 100644 cpp/src/gandiva/precompiled/time_test.cc diff --git a/cpp/src/gandiva/codegen/function_registry.cc b/cpp/src/gandiva/codegen/function_registry.cc index ce7c77063c7f8..35f97b66f4802 100644 --- a/cpp/src/gandiva/codegen/function_registry.cc +++ b/cpp/src/gandiva/codegen/function_registry.cc @@ -18,8 +18,14 @@ namespace gandiva { using std::vector; +using arrow::int8; +using arrow::int16; using arrow::int32; using arrow::int64; +using arrow::uint8; +using arrow::uint16; +using arrow::uint32; +using arrow::uint64; using arrow::float32; using arrow::float64; using arrow::boolean; @@ -107,8 +113,14 @@ using arrow::date64; // Iterate the inner macro over all numeric types #define NUMERIC_TYPES(INNER, NAME) \ - INNER(NAME, int32), \ - INNER(NAME, int64), \ + INNER(NAME, int8), \ + INNER(NAME, int16), \ + INNER(NAME, int32), \ + INNER(NAME, int64), \ + INNER(NAME, uint8), \ + INNER(NAME, uint16), \ + INNER(NAME, uint32), \ + INNER(NAME, uint64), \ INNER(NAME, float32), \ INNER(NAME, float64) @@ -121,7 +133,7 @@ using arrow::date64; #define DATE_TYPES(INNER, NAME) \ INNER(NAME, date64), \ INNER(NAME, time64), \ - INNER(NAME, timestamp64) + INNER(NAME, timestamp) // list of registered native functions. NativeFunction FunctionRegistry::pc_registry_[] = { diff --git a/cpp/src/gandiva/codegen/function_registry.h b/cpp/src/gandiva/codegen/function_registry.h index 3169e6619050a..6c90e0286c751 100644 --- a/cpp/src/gandiva/codegen/function_registry.h +++ b/cpp/src/gandiva/codegen/function_registry.h @@ -44,7 +44,7 @@ class FunctionRegistry { return arrow::time64(arrow::TimeUnit::MICRO); } - static DataTypePtr timestamp64() { + static DataTypePtr timestamp() { return arrow::timestamp(arrow::TimeUnit::MILLI); } diff --git a/cpp/src/gandiva/codegen/llvm_types.cc b/cpp/src/gandiva/codegen/llvm_types.cc index cffa82e905223..a9d66375d14e1 100644 --- a/cpp/src/gandiva/codegen/llvm_types.cc +++ b/cpp/src/gandiva/codegen/llvm_types.cc @@ -16,13 +16,21 @@ namespace gandiva { +// LLVM doesn't distinguish between signed and unsigned types. + LLVMTypes::LLVMTypes(llvm::LLVMContext &context) : context_(context) { arrow_id_to_llvm_type_map_ = { {arrow::Type::type::BOOL, i1_type()}, + {arrow::Type::type::INT8, i8_type()}, + {arrow::Type::type::INT16, i16_type()}, {arrow::Type::type::INT32, i32_type()}, {arrow::Type::type::INT64, i64_type()}, + {arrow::Type::type::UINT8, i8_type()}, + {arrow::Type::type::UINT16, i16_type()}, + {arrow::Type::type::UINT32, i32_type()}, + {arrow::Type::type::UINT64, i64_type()}, {arrow::Type::type::FLOAT, float_type()}, {arrow::Type::type::DOUBLE, double_type()}, {arrow::Type::type::DATE64, i64_type()}, diff --git a/cpp/src/gandiva/codegen/llvm_types.h b/cpp/src/gandiva/codegen/llvm_types.h index 62a551c9c1f80..bb21088c7cdb4 100644 --- a/cpp/src/gandiva/codegen/llvm_types.h +++ b/cpp/src/gandiva/codegen/llvm_types.h @@ -35,6 +35,10 @@ class LLVMTypes { return llvm::Type::getInt8Ty(context_); } + llvm::Type *i16_type() { + return llvm::Type::getInt16Ty(context_); + } + llvm::Type *i32_type() { return llvm::Type::getInt32Ty(context_); } diff --git a/cpp/src/gandiva/integ/projector_test.cc b/cpp/src/gandiva/integ/projector_test.cc index a72bd09ee6363..c95e0cab88550 100644 --- a/cpp/src/gandiva/integ/projector_test.cc +++ b/cpp/src/gandiva/integ/projector_test.cc @@ -72,6 +72,98 @@ TEST_F(TestProjector, TestIntSumSub) { EXPECT_ARROW_ARRAY_EQUALS(exp_sub, outputs.at(1)); } +template +static void TestArithmeticOpsForType(arrow::MemoryPool *pool) { + auto atype = arrow::TypeTraits::type_singleton(); + + // schema for input fields + auto field0 = field("f0", atype); + auto field1 = field("f1", atype); + auto schema = arrow::schema({field0, field1}); + + // output fields + auto field_sum = field("add", atype); + auto field_sub = field("subtract", atype); + auto field_mul = field("multiply", atype); + auto field_div = field("divide", atype); + auto field_eq = field("equal", arrow::boolean()); + auto field_lt = field("less_than", arrow::boolean()); + + // Build expression + auto sum_expr = TreeExprBuilder::MakeExpression("add", {field0, field1}, field_sum); + auto sub_expr = TreeExprBuilder::MakeExpression("subtract", {field0, field1}, + field_sub); + auto mul_expr = TreeExprBuilder::MakeExpression("multiply", {field0, field1}, + field_mul); + auto div_expr = TreeExprBuilder::MakeExpression("divide", {field0, field1}, field_div); + auto eq_expr = TreeExprBuilder::MakeExpression("equal", {field0, field1}, field_eq); + auto lt_expr = TreeExprBuilder::MakeExpression("less_than", {field0, field1}, field_lt); + + std::shared_ptr projector; + Status status = + Projector::Make(schema, {sum_expr, sub_expr, mul_expr, div_expr, eq_expr, lt_expr}, + pool, &projector); + EXPECT_TRUE(status.ok()); + + // Create a row-batch with some sample data + int num_records = 4; + std::vector input0 = {1, 2, 53, 84}; + std::vector input1 = {10, 15, 23, 84}; + std::vector validity = {true, true, true, true}; + + auto array0 = MakeArrowArray(input0, validity); + auto array1 = MakeArrowArray(input1, validity); + + // expected output + std::vector sum; + std::vector sub; + std::vector mul; + std::vector div; + std::vector eq; + std::vector lt; + for (int i = 0; i < num_records; i++) { + sum.push_back(input0[i] + input1[i]); + sub.push_back(input0[i] - input1[i]); + mul.push_back(input0[i] * input1[i]); + div.push_back(input0[i] / input1[i]); + eq.push_back(input0[i] == input1[i]); + lt.push_back(input0[i] < input1[i]); + } + auto exp_sum = MakeArrowArray(sum, validity); + auto exp_sub = MakeArrowArray(sub, validity); + auto exp_mul = MakeArrowArray(mul, validity); + auto exp_div = MakeArrowArray(div, validity); + auto exp_eq = MakeArrowArray(eq, validity); + auto exp_lt = MakeArrowArray(lt, validity); + + // prepare input record batch + auto in_batch = arrow::RecordBatch::Make(schema, num_records, {array0, array1}); + + // Evaluate expression + arrow::ArrayVector outputs; + status = projector->Evaluate(*in_batch, &outputs); + EXPECT_TRUE(status.ok()); + + // Validate results + EXPECT_ARROW_ARRAY_EQUALS(exp_sum, outputs.at(0)); + EXPECT_ARROW_ARRAY_EQUALS(exp_sub, outputs.at(1)); + EXPECT_ARROW_ARRAY_EQUALS(exp_mul, outputs.at(2)); + EXPECT_ARROW_ARRAY_EQUALS(exp_div, outputs.at(3)); + EXPECT_ARROW_ARRAY_EQUALS(exp_eq, outputs.at(4)); + EXPECT_ARROW_ARRAY_EQUALS(exp_lt, outputs.at(5)); +} + +TEST_F(TestProjector, TestAllIntTypes) { + TestArithmeticOpsForType(pool_); + TestArithmeticOpsForType(pool_); + TestArithmeticOpsForType(pool_); + TestArithmeticOpsForType(pool_); + TestArithmeticOpsForType(pool_); + TestArithmeticOpsForType(pool_); + TestArithmeticOpsForType(pool_); + TestArithmeticOpsForType(pool_); +} + TEST_F(TestProjector, TestFloatLessThan) { // schema for input fields auto field0 = field("f0", float32()); diff --git a/cpp/src/gandiva/integ/test_util.h b/cpp/src/gandiva/integ/test_util.h index f260b385ba4f6..d507aa932a407 100644 --- a/cpp/src/gandiva/integ/test_util.h +++ b/cpp/src/gandiva/integ/test_util.h @@ -35,8 +35,14 @@ static ArrayPtr MakeArrowArray(std::vector values, return out; } #define MakeArrowArrayBool MakeArrowArray +#define MakeArrowArrayInt8 MakeArrowArray +#define MakeArrowArrayInt16 MakeArrowArray #define MakeArrowArrayInt32 MakeArrowArray #define MakeArrowArrayInt64 MakeArrowArray +#define MakeArrowArrayUint8 MakeArrowArray +#define MakeArrowArrayUint16 MakeArrowArray +#define MakeArrowArrayUint32 MakeArrowArray +#define MakeArrowArrayUint64 MakeArrowArray #define MakeArrowArrayFloat32 MakeArrowArray #define MakeArrowArrayFloat64 MakeArrowArray diff --git a/cpp/src/gandiva/precompiled/CMakeLists.txt b/cpp/src/gandiva/precompiled/CMakeLists.txt index fab31ca76a16b..d8794dd5e57dd 100644 --- a/cpp/src/gandiva/precompiled/CMakeLists.txt +++ b/cpp/src/gandiva/precompiled/CMakeLists.txt @@ -48,4 +48,6 @@ add_custom_command( add_custom_target(precompiled ALL DEPENDS ${GANDIVA_BC_OUTPUT_PATH}) # testing +add_precompiled_unit_test(bitmap_test.cc bitmap.cc) +add_precompiled_unit_test(time_test.cc time.cc) add_precompiled_unit_test(sample_test.cc sample.cc) diff --git a/cpp/src/gandiva/precompiled/arithmetic_ops.cc b/cpp/src/gandiva/precompiled/arithmetic_ops.cc index 63a1ce848533b..8c1e80e01c87d 100644 --- a/cpp/src/gandiva/precompiled/arithmetic_ops.cc +++ b/cpp/src/gandiva/precompiled/arithmetic_ops.cc @@ -18,9 +18,15 @@ extern "C" { // Expand inner macro for all numeric types. #define NUMERIC_TYPES(INNER, NAME, OP) \ - INNER(NAME, int32, OP) \ - INNER(NAME, int64, OP) \ - INNER(NAME, float32, OP) \ + INNER(NAME, int8, OP) \ + INNER(NAME, int16, OP) \ + INNER(NAME, int32, OP) \ + INNER(NAME, int64, OP) \ + INNER(NAME, uint8, OP) \ + INNER(NAME, uint16, OP) \ + INNER(NAME, uint32, OP) \ + INNER(NAME, uint64, OP) \ + INNER(NAME, float32, OP)\ INNER(NAME, float64, OP) #define NUMERIC_AND_BOOL_TYPES(INNER, NAME, OP) \ @@ -28,14 +34,14 @@ extern "C" { INNER(NAME, boolean, OP) #define BINARY_GENERIC_OP(NAME, IN_TYPE1, IN_TYPE2, OUT_TYPE, OP) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ OUT_TYPE NAME##_##IN_TYPE1##_##IN_TYPE2(IN_TYPE1 left, IN_TYPE2 right) { \ return left OP right; \ } // Symmetric binary fns : left, right params and return type are same. #define BINARY_SYMMETRIC(NAME, TYPE, OP) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ TYPE NAME##_##TYPE##_##TYPE(TYPE left, TYPE right) { \ return left OP right; \ } @@ -51,7 +57,7 @@ BINARY_GENERIC_OP(mod, int64, int64, int64, %) // Relational binary fns : left, right params are same, return is bool. #define BINARY_RELATIONAL(NAME, TYPE, OP) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ bool NAME##_##TYPE##_##TYPE(TYPE left, TYPE right) { \ return left OP right; \ } @@ -65,7 +71,7 @@ NUMERIC_TYPES(BINARY_RELATIONAL, greater_than_or_equal_to, >=) // cast fns : takes one param type, returns another type. #define CAST_UNARY(NAME, IN_TYPE, OUT_TYPE) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ OUT_TYPE NAME##_##IN_TYPE(IN_TYPE in) { \ return (OUT_TYPE)in; \ } @@ -79,7 +85,7 @@ CAST_UNARY(castFLOAT8, float32, float64) // simple nullable functions, result value = fn(input validity) #define VALIDITY_OP(NAME, TYPE, OP) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ bool NAME##_##TYPE(TYPE in, boolean is_valid) { \ return OP is_valid; \ } diff --git a/cpp/src/gandiva/precompiled/bitmap.cc b/cpp/src/gandiva/precompiled/bitmap.cc index 6304b25ca66cc..a8e6978ed2225 100644 --- a/cpp/src/gandiva/precompiled/bitmap.cc +++ b/cpp/src/gandiva/precompiled/bitmap.cc @@ -24,14 +24,14 @@ extern "C" { #define POS_TO_BYTE_INDEX(p) (p / 8) #define POS_TO_BIT_INDEX(p) (p % 8) -__attribute__((always_inline)) +FORCE_INLINE bool bitMapGetBit(const unsigned char *bmap, int position) { int byteIdx = POS_TO_BYTE_INDEX(position); int bitIdx = POS_TO_BIT_INDEX(position); return ((bmap[byteIdx] & (1 << bitIdx)) > 0); } -__attribute__((always_inline)) +FORCE_INLINE void bitMapSetBit(unsigned char *bmap, int position, bool value) { int byteIdx = POS_TO_BYTE_INDEX(position); int bitIdx = POS_TO_BIT_INDEX(position); @@ -43,7 +43,7 @@ void bitMapSetBit(unsigned char *bmap, int position, bool value) { } // Clear the bit if value = false. Does nothing if value = true. -__attribute__((always_inline)) +FORCE_INLINE void bitMapClearBitIfFalse(unsigned char *bmap, int position, bool value) { if (!value) { int byteIdx = POS_TO_BYTE_INDEX(position); diff --git a/cpp/src/gandiva/precompiled/bitmap_test.cc b/cpp/src/gandiva/precompiled/bitmap_test.cc new file mode 100644 index 0000000000000..c64e39695f3f7 --- /dev/null +++ b/cpp/src/gandiva/precompiled/bitmap_test.cc @@ -0,0 +1,60 @@ +// Copyright (C) 2017-2018 Dremio Corporation +// +// Licensed 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 +#include "precompiled/types.h" + +namespace gandiva { + +TEST(TestBitMap, TestSimple) { + static const int kNumBytes = 16; + uint8_t bit_map[kNumBytes]; + memset(bit_map, 0, kNumBytes); + + EXPECT_EQ(bitMapGetBit(bit_map, 100), false); + + // set 100th bit and verify + bitMapSetBit(bit_map, 100, true); + EXPECT_EQ(bitMapGetBit(bit_map, 100), true); + + // clear 100th bit and verify + bitMapSetBit(bit_map, 100, false); + EXPECT_EQ(bitMapGetBit(bit_map, 100), false); +} + +TEST(TestBitMap, TestClearIfFalse) { + static const int kNumBytes = 32; + uint8_t bit_map[kNumBytes]; + memset(bit_map, 0, kNumBytes); + + bitMapSetBit(bit_map, 24, true); + + // bit should remain unchanged. + bitMapClearBitIfFalse(bit_map, 24, true); + EXPECT_EQ(bitMapGetBit(bit_map, 24), true); + + // bit should be cleared. + bitMapClearBitIfFalse(bit_map, 24, false); + EXPECT_EQ(bitMapGetBit(bit_map, 24), false); + + // this function should have no impact if the bit is already clear. + bitMapClearBitIfFalse(bit_map, 24, true); + EXPECT_EQ(bitMapGetBit(bit_map, 24), false); + + bitMapClearBitIfFalse(bit_map, 24, false); + EXPECT_EQ(bitMapGetBit(bit_map, 24), false); +} + + +} // namespace gandiva diff --git a/cpp/src/gandiva/precompiled/sample.cc b/cpp/src/gandiva/precompiled/sample.cc index dec9ab120967f..7a780367bbef2 100644 --- a/cpp/src/gandiva/precompiled/sample.cc +++ b/cpp/src/gandiva/precompiled/sample.cc @@ -19,6 +19,7 @@ extern "C" { // Dummy function to test NULL_INTERNAL (most valid ones need varchar). // If input is valid and a multiple of 2, return half the value. else, null. +FORCE_INLINE int half_or_null_int32(int32 val, bool in_valid, bool *out_valid) { if (in_valid && (val % 2 == 0)) { // output is valid. diff --git a/cpp/src/gandiva/precompiled/time.cc b/cpp/src/gandiva/precompiled/time.cc index c7d3a1f6812a1..d6dc15d59181c 100644 --- a/cpp/src/gandiva/precompiled/time.cc +++ b/cpp/src/gandiva/precompiled/time.cc @@ -24,12 +24,12 @@ extern "C" { #define DATE_TYPES(INNER) \ INNER(date) \ INNER(time64) \ - INNER(timestamp64) + INNER(timestamp) // Extract year. #define EXTRACT_YEAR(TYPE) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ int64 extractYear##_##TYPE(TYPE millis) { \ time_t tsec = (time_t) MILLIS_TO_SEC(millis); \ struct tm tm; \ @@ -40,7 +40,7 @@ extern "C" { DATE_TYPES(EXTRACT_YEAR) #define EXTRACT_MONTH(TYPE) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ int64 extractMonth##_##TYPE(TYPE millis) { \ time_t tsec = (time_t) MILLIS_TO_SEC(millis); \ struct tm tm; \ @@ -51,7 +51,7 @@ DATE_TYPES(EXTRACT_YEAR) DATE_TYPES(EXTRACT_MONTH) #define EXTRACT_DAY(TYPE) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ int64 extractDay##_##TYPE(TYPE millis) { \ time_t tsec = (time_t) MILLIS_TO_SEC(millis); \ struct tm tm; \ @@ -62,7 +62,7 @@ DATE_TYPES(EXTRACT_MONTH) DATE_TYPES(EXTRACT_DAY) #define EXTRACT_HOUR(TYPE) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ int64 extractHour##_##TYPE(TYPE millis) { \ time_t tsec = (time_t) MILLIS_TO_SEC(millis); \ struct tm tm; \ @@ -73,7 +73,7 @@ DATE_TYPES(EXTRACT_DAY) DATE_TYPES(EXTRACT_HOUR) #define EXTRACT_MINUTE(TYPE) \ - __attribute__((always_inline)) \ + FORCE_INLINE \ int64 extractMinute##_##TYPE(TYPE millis) { \ time_t tsec = (time_t) MILLIS_TO_SEC(millis); \ struct tm tm; \ diff --git a/cpp/src/gandiva/precompiled/time_test.cc b/cpp/src/gandiva/precompiled/time_test.cc new file mode 100644 index 0000000000000..0fce9d2657a57 --- /dev/null +++ b/cpp/src/gandiva/precompiled/time_test.cc @@ -0,0 +1,37 @@ +// Copyright (C) 2017-2018 Dremio Corporation +// +// Licensed 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 +#include +#include "precompiled/types.h" + +namespace gandiva { + +timestamp StringToTimestamp(const char *buf) { + struct tm tm; + strptime(buf, "%Y-%m-%d %H:%M:%S", &tm); + return timegm(&tm) * 1000; // to millis +} + +TEST(TestTime, TestExtractTimestamp) { + timestamp ts = StringToTimestamp("1970-05-02 10:20:33"); + + EXPECT_EQ(extractYear_timestamp(ts), 1970); + EXPECT_EQ(extractMonth_timestamp(ts), 5); + EXPECT_EQ(extractDay_timestamp(ts), 2); + EXPECT_EQ(extractHour_timestamp(ts), 10); + EXPECT_EQ(extractMinute_timestamp(ts), 20); +} + +} // namespace gandiva diff --git a/cpp/src/gandiva/precompiled/types.h b/cpp/src/gandiva/precompiled/types.h index f371ae6cf0fa0..418cda06dbfef 100644 --- a/cpp/src/gandiva/precompiled/types.h +++ b/cpp/src/gandiva/precompiled/types.h @@ -19,12 +19,41 @@ // Use the same names as in arrow data types. Makes it easy to write pre-processor macros. using boolean = bool; +using int8 = int8_t; +using int16 = int16_t; using int32 = int32_t; using int64 = int64_t; +using uint8 = uint8_t; +using uint16 = uint16_t; +using uint32 = uint32_t; +using uint64 = uint64_t; using float32 = float; using float64 = double; using date = int64_t; using time64 = int64_t; -using timestamp64 = int64_t; +using timestamp = int64_t; + +#ifdef GANDIVA_UNIT_TEST +// unit tests may be compiled without O2, so inlining may not happen. +#define FORCE_INLINE +#else +#define FORCE_INLINE __attribute__((always_inline)) +#endif + +// Declarations : used in testing + +extern "C" { + +bool bitMapGetBit(const unsigned char *bmap, int position); +void bitMapSetBit(unsigned char *bmap, int position, bool value); +void bitMapClearBitIfFalse(unsigned char *bmap, int position, bool value); + +int64 extractYear_timestamp(timestamp millis); +int64 extractMonth_timestamp(timestamp millis); +int64 extractDay_timestamp(timestamp millis); +int64 extractHour_timestamp(timestamp millis); +int64 extractMinute_timestamp(timestamp millis); + +} // extern "C" #endif //PRECOMPILED_TYPES_H