* first pass table type

* indexed table with comments

* clang-tidy

* build stuff

* sanity fixes

* fixes for clang4

* add tests for size

* build fixes

* Add port_platform-only library

* remove port_platform exceptions

* merge new port_platform lib

* Add a bitset abstraction

* set(bool)

* bitset integration

* fixup test

* all or nothing

* comments, clangfmt

* add bitset tests

* add bitset tests

* merge stuff

* clang-tidy

* add c++ attribute detection

* use macro

* fmt

* typo

* exclude test from windows
pull/26782/head
Craig Tiller 3 years ago committed by GitHub
parent 48ce79f7e5
commit a989e0bfb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      BUILD
  2. 37
      CMakeLists.txt
  3. 13
      build_autogenerated.yaml
  4. 386
      src/core/lib/gprpp/table.h
  5. 15
      test/core/gprpp/BUILD
  6. 151
      test/core/gprpp/table_test.cc
  7. 24
      tools/run_tests/generated/tests.json

11
BUILD

@ -800,6 +800,17 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "table",
external_deps = ["absl/utility"],
language = "c++",
public_hdrs = ["src/core/lib/gprpp/table.h"],
deps = [
"bitset",
"gpr_platform",
],
)
grpc_cc_library(
name = "bitset",
language = "c++",

@ -960,6 +960,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx streaming_throughput_test)
endif()
add_dependencies(buildtests_cxx string_ref_test)
add_dependencies(buildtests_cxx table_test)
add_dependencies(buildtests_cxx test_cpp_client_credentials_test)
add_dependencies(buildtests_cxx test_cpp_server_credentials_test)
add_dependencies(buildtests_cxx test_cpp_util_slice_test)
@ -14351,6 +14352,42 @@ target_link_libraries(string_ref_test
)
endif()
if(gRPC_BUILD_TESTS)
add_executable(table_test
test/core/gprpp/table_test.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(table_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(table_test
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
absl::optional
absl::utility
)
endif()
if(gRPC_BUILD_TESTS)

@ -6513,6 +6513,19 @@ targets:
- grpc++
- grpc_test_util
uses_polling: false
- name: table_test
gtest: true
build: test
language: c++
headers:
- src/core/lib/gprpp/bitset.h
- src/core/lib/gprpp/table.h
src:
- test/core/gprpp/table_test.cc
deps:
- absl/types:optional
- absl/utility:utility
uses_polling: false
- name: test_cpp_client_credentials_test
gtest: true
build: test

@ -0,0 +1,386 @@
// Copyright 2021 gRPC authors.
//
// 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.
#ifndef GRPC_CORE_LIB_GPRPP_TABLE_H
#define GRPC_CORE_LIB_GPRPP_TABLE_H
#include <grpc/impl/codegen/port_platform.h>
#include <utility>
#include "absl/utility/utility.h"
#include "src/core/lib/gprpp/bitset.h"
namespace grpc_core {
// Meta-programming detail types to aid in building up a Table
namespace table_detail {
// A tuple-like type that contains manually constructed elements.
template <typename... Ts>
struct Elements;
template <typename T, typename... Ts>
struct Elements<T, Ts...> : Elements<Ts...> {
union U {
U() {}
~U() {}
GPR_NO_UNIQUE_ADDRESS T x;
};
U u;
};
template <>
struct Elements<> {};
// Element accessor for Elements<>
// Provides a static method f that returns a pointer to the value of element I
// for Elements<Ts...>
template <size_t I, typename... Ts>
struct GetElem;
template <typename T, typename... Ts>
struct GetElem<0, T, Ts...> {
static T* f(Elements<T, Ts...>* e) { return &e->u.x; }
static const T* f(const Elements<T, Ts...>* e) { return &e->u.x; }
};
template <size_t I, typename T, typename... Ts>
struct GetElem<I, T, Ts...> {
static auto f(Elements<T, Ts...>* e)
-> decltype(GetElem<I - 1, Ts...>::f(e)) {
return GetElem<I - 1, Ts...>::f(e);
}
static auto f(const Elements<T, Ts...>* e)
-> decltype(GetElem<I - 1, Ts...>::f(e)) {
return GetElem<I - 1, Ts...>::f(e);
}
};
// CountIncludedStruct is the backing for the CountIncluded function below.
// Sets a member constant N to the number of times Needle is in Haystack.
template <typename Needle, typename... Haystack>
struct CountIncludedStruct;
template <typename Needle, typename Straw, typename... RestOfHaystack>
struct CountIncludedStruct<Needle, Straw, RestOfHaystack...> {
static constexpr size_t N =
static_cast<size_t>(std::is_same<Needle, Straw>::value) +
CountIncludedStruct<Needle, RestOfHaystack...>::N;
};
template <typename Needle>
struct CountIncludedStruct<Needle> {
static constexpr size_t N = 0;
};
// Returns the number of times Needle is in Haystack.
template <typename Needle, typename... Haystack>
constexpr size_t CountIncluded() {
return CountIncludedStruct<Needle, Haystack...>::N;
}
// IndexOfStruct is the backing for IndexOf below.
// Set a member constant N to the index of Needle in Haystack.
// Ignored should be void always, and is used for enable_if_t.
template <typename Ignored, typename Needle, typename... Haystack>
struct IndexOfStruct;
template <typename Needle, typename Straw, typename... RestOfHaystack>
struct IndexOfStruct<absl::enable_if_t<std::is_same<Needle, Straw>::value>,
Needle, Straw, RestOfHaystack...> {
// The first element is the one we're looking for. Done.
static constexpr size_t N = 0;
};
template <typename Needle, typename Straw, typename... RestOfHaystack>
struct IndexOfStruct<absl::enable_if_t<!std::is_same<Needle, Straw>::value>,
Needle, Straw, RestOfHaystack...> {
// The first element is not the one we're looking for, recurse looking at the
// tail, and sum the number of recursions.
static constexpr size_t N =
1 + IndexOfStruct<void, Needle, RestOfHaystack...>::N;
};
// Return the index of Needle in Haystack.
// Guarded by CountIncluded to ensure that the return type is unambiguous.
// If you got here from a compiler error using Table, it's likely that you've
// used the type-based accessor/mutators, but the type you're using is repeated
// more than once in the Table type arguments. Consider either using the indexed
// accessor/mutator variants, or eliminating the ambiguity in type resolution.
template <typename Needle, typename... Haystack>
constexpr absl::enable_if_t<CountIncluded<Needle, Haystack...>() == 1, size_t>
IndexOf() {
return IndexOfStruct<void, Needle, Haystack...>::N;
}
// TypeIndexStruct is the backing for TypeIndex below.
// Sets member type Type to the type at index I in Ts.
// Implemented as a simple type recursion.
template <size_t I, typename... Ts>
struct TypeIndexStruct;
template <typename T, typename... Ts>
struct TypeIndexStruct<0, T, Ts...> {
using Type = T;
};
template <size_t I, typename T, typename... Ts>
struct TypeIndexStruct<I, T, Ts...> : TypeIndexStruct<I - 1, Ts...> {};
// TypeIndex is the type at index I in Ts.
template <size_t I, typename... Ts>
using TypeIndex = typename TypeIndexStruct<I, Ts...>::Type;
// Helper to call the destructor of p if p is non-null.
template <typename T>
void DestructIfNotNull(T* p) {
if (p) p->~T();
}
// Helper function... just ignore the initializer list passed into it.
// Allows doing 'statements' via parameter pack expansion in C++11 - given
// template <typename... Ts>:
// do_these_things({(foo<Ts>(), 1)});
// will execute foo<T>() for each T in Ts.
// In this example we also leverage the comma operator to make the resultant
// type of each statement be a consistant int so that C++ type deduction works
// as we'd like (note that in the expression (a, 1) in C++, the 'result' of the
// expression is the value after the right-most ',' -- in this case 1, with a
// executed as a side effect.
template <typename T>
void do_these_things(std::initializer_list<T>) {}
} // namespace table_detail
// A Table<Ts> is much like a tuple<optional<Ts>...> - a set of values that are
// optionally present. Table efficiently packs the presence bits for size, and
// provides a slightly more convenient interface.
template <typename... Ts>
class Table {
// Helper - TypeIndex<I> is the type at index I in Ts
template <size_t I>
using TypeIndex = table_detail::TypeIndex<I, Ts...>;
public:
// Construct a table with no values set.
Table() = default;
// Destruct - forwards to the Destruct member with an integer sequence so we
// can destruct field-wise.
~Table() { Destruct(absl::make_index_sequence<sizeof...(Ts)>()); }
// Copy another table
Table(const Table& rhs) {
// Since we know all fields are clear initially, pass false for or_clear.
Copy<false>(absl::make_index_sequence<sizeof...(Ts)>(), rhs);
}
// Copy another table
Table& operator=(const Table& rhs) {
// Since we may not be all clear, pass true for or_clear to have Copy()
// clear newly emptied fields.
Copy<true>(absl::make_index_sequence<sizeof...(Ts)>(), rhs);
return *this;
}
// Move from another table
Table(Table&& rhs) noexcept {
// Since we know all fields are clear initially, pass false for or_clear.
Move<false>(absl::make_index_sequence<sizeof...(Ts)>(),
std::forward<Table>(rhs));
}
// Move from another table
Table& operator=(Table&& rhs) noexcept {
// Since we may not be all clear, pass true for or_clear to have Move()
// clear newly emptied fields.
Move<true>(absl::make_index_sequence<sizeof...(Ts)>(),
std::forward<Table>(rhs));
return *this;
}
// Check if this table has a value for type T.
// Only available if there exists only one T in Ts.
template <typename T>
bool has() const {
return has<index_of<T>()>();
}
// Check if this table has index I.
template <size_t I>
absl::enable_if_t < I<sizeof...(Ts), bool> has() const {
return present_bits_.is_set(I);
}
// Return the value for type T, or nullptr if it is un-set.
// Only available if there exists only one T in Ts.
template <typename T>
T* get() {
return get<index_of<T>()>();
}
// Return the value for type T, or nullptr if it is un-set.
// Only available if there exists only one T in Ts.
template <typename T>
const T* get() const {
return get<index_of<T>()>();
}
// Return the value for index I, or nullptr if it is un-set.
template <size_t I>
TypeIndex<I>* get() {
if (has<I>()) return element_ptr<I>();
return nullptr;
}
// Return the value for index I, or nullptr if it is un-set.
template <size_t I>
const TypeIndex<I>* get() const {
if (has<I>()) return element_ptr<I>();
return nullptr;
}
// Return the value for type T, default constructing it if it is un-set.
template <typename T>
T* get_or_create() {
return get_or_create<index_of<T>()>();
}
// Return the value for index I, default constructing it if it is un-set.
template <size_t I>
TypeIndex<I>* get_or_create() {
auto* p = element_ptr<I>();
if (!set_present<I>(true)) {
new (p) TypeIndex<I>();
}
return element_ptr<I>();
}
// Set the value for type T - using Args as construction arguments.
template <typename T, typename... Args>
T* set(Args&&... args) {
return set<index_of<T>()>(std::forward<Args>(args)...);
}
// Set the value for index I - using Args as construction arguments.
template <size_t I, typename... Args>
TypeIndex<I>* set(Args&&... args) {
auto* p = element_ptr<I>();
if (set_present<I>(true)) {
*p = TypeIndex<I>(std::forward<Args>(args)...);
} else {
new (p) TypeIndex<I>(std::forward<Args>(args)...);
}
return p;
}
// Clear the value for type T, leaving it un-set.
template <typename T>
void clear() {
clear<index_of<T>()>();
}
// Clear the value for index I, leaving it un-set.
template <size_t I>
void clear() {
if (set_present<I>(false)) {
using T = TypeIndex<I>;
element_ptr<I>()->~T();
}
}
private:
// Bit field for which elements of the table are set (true) or un-set (false,
// the default) -- one bit for each type in Ts.
using PresentBits = BitSet<sizeof...(Ts)>;
// The tuple-like backing structure for Table.
using Elements = table_detail::Elements<Ts...>;
// Extractor from Elements
template <size_t I>
using GetElem = table_detail::GetElem<I, Ts...>;
// Given a T, return the unambiguous index of it within Ts.
template <typename T>
static constexpr size_t index_of() {
return table_detail::IndexOf<T, Ts...>();
}
// Given an index, return a point to the (maybe uninitialized!) data value at
// index I.
template <size_t I>
TypeIndex<I>* element_ptr() {
return GetElem<I>::f(&elements_);
}
// Given an index, return a point to the (maybe uninitialized!) data value at
// index I.
template <size_t I>
const TypeIndex<I>* element_ptr() const {
return GetElem<I>::f(&elements_);
}
// Set the present bit to value (if true - value is present/set, if false,
// value is un-set). Returns the old value so that calling code can note
// transition edges.
template <size_t I>
bool set_present(bool value) {
bool out = present_bits_.is_set(I);
present_bits_.set(I, value);
return out;
}
// Set the value of index I to the value held in rhs index I if it is set.
// If it is unset, if or_clear is true, then clear our value, otherwise do
// nothing.
template <bool or_clear, size_t I>
void CopyIf(const Table& rhs) {
if (auto* p = rhs.get<I>()) {
set<I>(*p);
} else if (or_clear) {
clear<I>();
}
}
// Set the value of index I to the value moved from rhs index I if it was set.
// If it is unset, if or_clear is true, then clear our value, otherwise do
// nothing.
template <bool or_clear, size_t I>
void MoveIf(Table&& rhs) {
if (auto* p = rhs.get<I>()) {
set<I>(std::move(*p));
} else if (or_clear) {
clear<I>();
}
}
// For each field (element I=0, 1, ...) if that field is present, call its
// destructor.
template <size_t... I>
void Destruct(absl::index_sequence<I...>) {
table_detail::do_these_things(
{(table_detail::DestructIfNotNull(get<I>()), 1)...});
}
// For each field (element I=0, 1, ...) copy that field into this table -
// or_clear as per CopyIf().
template <bool or_clear, size_t... I>
void Copy(absl::index_sequence<I...>, const Table& rhs) {
table_detail::do_these_things({(CopyIf<or_clear, I>(rhs), 1)...});
}
// For each field (element I=0, 1, ...) move that field into this table -
// or_clear as per MoveIf().
template <bool or_clear, size_t... I>
void Move(absl::index_sequence<I...>, Table&& rhs) {
table_detail::do_these_things(
{(MoveIf<or_clear, I>(std::forward<Table>(rhs)), 1)...});
}
// Bit field indicating which elements are set.
GPR_NO_UNIQUE_ADDRESS PresentBits present_bits_;
// The memory to store the elements themselves.
GPR_NO_UNIQUE_ADDRESS Elements elements_;
};
} // namespace grpc_core
#endif // GRPC_CORE_LIB_GPRPP_TABLE_H

@ -125,6 +125,21 @@ grpc_cc_test(
],
)
grpc_cc_test(
name = "table_test",
srcs = ["table_test.cc"],
external_deps = [
"gtest",
"absl/types:optional",
],
language = "C++",
uses_polling = False,
deps = [
"//:table",
"//test/core/util:grpc_suppressions",
],
)
grpc_cc_test(
name = "host_port_test",
srcs = ["host_port_test.cc"],

@ -0,0 +1,151 @@
// Copyright 2021 gRPC authors.
//
// 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 "src/core/lib/gprpp/table.h"
#include <gtest/gtest.h>
#include <string>
#include <tuple>
#include "absl/types/optional.h"
namespace grpc_core {
namespace testing {
TEST(Table, NoOp) {
Table<int, double, std::string> t;
EXPECT_EQ(t.get<int>(), nullptr);
EXPECT_EQ(t.get<double>(), nullptr);
EXPECT_EQ(t.get<std::string>(), nullptr);
EXPECT_EQ(t.get<0>(), nullptr);
EXPECT_EQ(t.get<1>(), nullptr);
EXPECT_EQ(t.get<2>(), nullptr);
}
TEST(Table, SetTheThings) {
Table<int, double, std::string> t;
t.set<int>(3);
t.set<double>(2.9);
t.set<std::string>("Hello world!");
EXPECT_EQ(*t.get<int>(), 3);
EXPECT_EQ(*t.get<double>(), 2.9);
EXPECT_EQ(*t.get<std::string>(), "Hello world!");
EXPECT_EQ(*t.get<0>(), 3);
EXPECT_EQ(*t.get<1>(), 2.9);
EXPECT_EQ(*t.get<2>(), "Hello world!");
}
TEST(Table, GetDefault) {
Table<int, double, std::string> t;
EXPECT_EQ(*t.get_or_create<std::string>(), "");
EXPECT_EQ(*t.get_or_create<double>(), 0.0);
EXPECT_EQ(*t.get_or_create<int>(), 0);
}
TEST(Table, GetDefaultIndexed) {
Table<int, double, std::string> t;
EXPECT_EQ(*t.get_or_create<2>(), "");
EXPECT_EQ(*t.get_or_create<1>(), 0.0);
EXPECT_EQ(*t.get_or_create<0>(), 0);
}
TEST(Table, Copy) {
Table<int, std::string> t;
t.set<std::string>("abcdefghijklmnopqrstuvwxyz");
EXPECT_EQ(*t.get<std::string>(), "abcdefghijklmnopqrstuvwxyz");
EXPECT_EQ(t.get<int>(), nullptr);
Table<int, std::string> u(t);
EXPECT_EQ(*u.get<std::string>(), "abcdefghijklmnopqrstuvwxyz");
EXPECT_EQ(*t.get<std::string>(), "abcdefghijklmnopqrstuvwxyz");
EXPECT_EQ(t.get<int>(), nullptr);
EXPECT_EQ(u.get<int>(), nullptr);
u.set<std::string>("hello");
EXPECT_EQ(*u.get<1>(), "hello");
EXPECT_EQ(*t.get<1>(), "abcdefghijklmnopqrstuvwxyz");
t = u;
EXPECT_EQ(*u.get<std::string>(), "hello");
EXPECT_EQ(*t.get<std::string>(), "hello");
}
TEST(Table, Move) {
Table<int, std::string> t;
t.set<std::string>("abcdefghijklmnopqrstuvwxyz");
EXPECT_EQ(*t.get<std::string>(), "abcdefghijklmnopqrstuvwxyz");
EXPECT_EQ(t.get<int>(), nullptr);
Table<int, std::string> u(std::move(t));
EXPECT_NE(t.get<std::string>(), nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(*u.get<std::string>(), "abcdefghijklmnopqrstuvwxyz");
EXPECT_EQ(t.get<int>(), nullptr);
EXPECT_EQ(u.get<int>(), nullptr);
u.set<std::string>("hello");
EXPECT_EQ(*u.get<1>(), "hello");
t = std::move(u);
EXPECT_NE(u.get<std::string>(), nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(*t.get<std::string>(), "hello");
}
TEST(Table, SameTypes) {
Table<std::string, std::string, std::string> t;
// The following lines should not compile:
// t.get<std::string>();
// t.has<4>();
// t.get<4>();
// t.clear<4>();
EXPECT_EQ(t.get<0>(), nullptr);
EXPECT_EQ(t.get<1>(), nullptr);
EXPECT_EQ(t.get<2>(), nullptr);
t.set<1>("Hello!");
EXPECT_EQ(t.get<0>(), nullptr);
EXPECT_EQ(*t.get<1>(), "Hello!");
EXPECT_EQ(t.get<2>(), nullptr);
}
#if !defined(_MSC_VER)
// Test suite proving this is memory efficient compared to
// tuple<optional<Ts>...>
// TODO(ctiller): determine why this test doesn't compile under MSVC.
// For now whether it passes or not in that one environment is probably
// immaterial.
template <typename T>
struct TableSizeTest : public ::testing::Test {};
using SizeTests = ::testing::Types<
std::tuple<char>, std::tuple<char, char>, std::tuple<char, char, char>,
std::tuple<int>, std::tuple<std::string>,
std::tuple<int, int, int, int, int, int, int, int, int, int>>;
TYPED_TEST_SUITE(TableSizeTest, SizeTests);
template <typename... Ts>
int sizeof_tuple_of_optionals(std::tuple<Ts...>*) {
return sizeof(std::tuple<absl::optional<Ts>...>);
}
template <typename... Ts>
int sizeof_table(std::tuple<Ts...>*) {
return sizeof(Table<Ts...>);
}
TYPED_TEST(TableSizeTest, SmallerThanTupleOfOptionals) {
EXPECT_GE(sizeof_tuple_of_optionals(static_cast<TypeParam*>(nullptr)),
sizeof_table(static_cast<TypeParam*>(nullptr)));
}
#endif
} // namespace testing
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

@ -6315,6 +6315,30 @@
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "table_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,

Loading…
Cancel
Save