Message ID | 20240511090130.248174-3-kmatsui@gcc.gnu.org |
---|---|
State | New |
Headers | show |
Series | [v26,01/13] libstdc++: Optimize std::is_const compilation performance | expand |
On 11/05/24 02:01 -0700, Ken Matsui wrote: >This patch optimizes the compilation performance of std::is_pointer >by dispatching to the new __is_pointer built-in trait. > >libstdc++-v3/ChangeLog: > > * include/bits/cpp_type_traits.h (__is_pointer): Use > __is_pointer built-in trait. Optimize its implementation. > * include/std/type_traits (is_pointer): Likewise. > (is_pointer_v): Likewise. > >Co-authored-by: Jonathan Wakely <jwakely@redhat.com> >Signed-off-by: Ken Matsui <kmatsui@gcc.gnu.org> >--- > libstdc++-v3/include/bits/cpp_type_traits.h | 31 ++++++++++++++- > libstdc++-v3/include/std/type_traits | 44 +++++++++++++++++---- > 2 files changed, 66 insertions(+), 9 deletions(-) > >diff --git a/libstdc++-v3/include/bits/cpp_type_traits.h b/libstdc++-v3/include/bits/cpp_type_traits.h >index 59f1a1875eb..210a9ea00da 100644 >--- a/libstdc++-v3/include/bits/cpp_type_traits.h >+++ b/libstdc++-v3/include/bits/cpp_type_traits.h >@@ -363,6 +363,13 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > // > // Pointer types > // >+#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) >+ template<typename _Tp, bool _IsPtr = __is_pointer(_Tp)> >+ struct __is_pointer : __truth_type<_IsPtr> I was worried that reusing __is_pointer this way would cause a problem with Clang, because it has an __is_pointer built-in and the code above causes a warning: isp.cc:2:12: warning: keyword '__is_pointer' will be made available as an identifier for the remainder of the translation unit [-Wkeyword-compat] 2 | struct __is_pointer | ^ I thought this warning meant it was only available as an identifier. But in fact it becomes available as both an identifier and as the built-in. This is what I tested: template<typename T, bool IsPtr = __is_pointer(T)> struct __is_pointer { enum { value = IsPtr }; }; static_assert( __is_pointer<int*>::value, "" ); template<typename T> struct is_pointer { static constexpr bool value = __is_pointer(T); }; static_assert( is_pointer<int*>::value, "" ); So the is_pointer template can still use the built-in even though the name '__is_pointer' has been declared as an identifier. So Clang matches GCC and it works fine. Good! >+ { >+ enum { __value = _IsPtr }; >+ }; >+#else > template<typename _Tp> > struct __is_pointer > { >@@ -377,6 +384,28 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > typedef __true_type __type; > }; > >+ template<typename _Tp> >+ struct __is_pointer<_Tp* const> None of the other traits in bits/cpp_type_traits.h is true for cv-qualified types, so these partial specializations make __is_pointer the odd one out. Are they necessary? >+ { >+ enum { __value = 1 }; >+ typedef __true_type __type; >+ }; >+ >+ template<typename _Tp> >+ struct __is_pointer<_Tp* volatile> >+ { >+ enum { __value = 1 }; >+ typedef __true_type __type; >+ }; >+ >+ template<typename _Tp> >+ struct __is_pointer<_Tp* const volatile> >+ { >+ enum { __value = 1 }; >+ typedef __true_type __type; >+ }; >+#endif >+ > // > // An arithmetic type is an integer type or a floating point type > // >@@ -387,7 +416,7 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > > // > // A scalar type is an arithmetic type or a pointer type >- // >+ // > template<typename _Tp> > struct __is_scalar > : public __traitor<__is_arithmetic<_Tp>, __is_pointer<_Tp> > >diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits >index 748fa186881..ea013b4b7bc 100644 >--- a/libstdc++-v3/include/std/type_traits >+++ b/libstdc++-v3/include/std/type_traits >@@ -542,19 +542,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > : public true_type { }; > #endif > >- template<typename> >- struct __is_pointer_helper >+ /// is_pointer >+#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) >+ template<typename _Tp> >+ struct is_pointer >+ : public __bool_constant<__is_pointer(_Tp)> >+ { }; >+#else >+ template<typename _Tp> >+ struct is_pointer > : public false_type { }; > > template<typename _Tp> >- struct __is_pointer_helper<_Tp*> >+ struct is_pointer<_Tp*> > : public true_type { }; > >- /// is_pointer > template<typename _Tp> >- struct is_pointer >- : public __is_pointer_helper<__remove_cv_t<_Tp>>::type >- { }; >+ struct is_pointer<_Tp* const> >+ : public true_type { }; >+ >+ template<typename _Tp> >+ struct is_pointer<_Tp* volatile> >+ : public true_type { }; >+ >+ template<typename _Tp> >+ struct is_pointer<_Tp* const volatile> >+ : public true_type { }; >+#endif > > /// is_lvalue_reference > template<typename> >@@ -3268,8 +3282,22 @@ template <typename _Tp, size_t _Num> > inline constexpr bool is_array_v<_Tp[_Num]> = true; > #endif > >+#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) >+template <typename _Tp> >+ inline constexpr bool is_pointer_v = __is_pointer(_Tp); >+#else > template <typename _Tp> >- inline constexpr bool is_pointer_v = is_pointer<_Tp>::value; >+ inline constexpr bool is_pointer_v = false; >+template <typename _Tp> >+ inline constexpr bool is_pointer_v<_Tp*> = true; >+template <typename _Tp> >+ inline constexpr bool is_pointer_v<_Tp* const> = true; >+template <typename _Tp> >+ inline constexpr bool is_pointer_v<_Tp* volatile> = true; >+template <typename _Tp> >+ inline constexpr bool is_pointer_v<_Tp* const volatile> = true; >+#endif >+ > template <typename _Tp> > inline constexpr bool is_lvalue_reference_v = false; > template <typename _Tp> >-- >2.44.0 >
On Thu, Jun 13, 2024 at 5:31 AM Jonathan Wakely <jwakely@redhat.com> wrote: > > On 11/05/24 02:01 -0700, Ken Matsui wrote: > >This patch optimizes the compilation performance of std::is_pointer > >by dispatching to the new __is_pointer built-in trait. > > > >libstdc++-v3/ChangeLog: > > > > * include/bits/cpp_type_traits.h (__is_pointer): Use > > __is_pointer built-in trait. Optimize its implementation. > > * include/std/type_traits (is_pointer): Likewise. > > (is_pointer_v): Likewise. > > > >Co-authored-by: Jonathan Wakely <jwakely@redhat.com> > >Signed-off-by: Ken Matsui <kmatsui@gcc.gnu.org> > >--- > > libstdc++-v3/include/bits/cpp_type_traits.h | 31 ++++++++++++++- > > libstdc++-v3/include/std/type_traits | 44 +++++++++++++++++---- > > 2 files changed, 66 insertions(+), 9 deletions(-) > > > >diff --git a/libstdc++-v3/include/bits/cpp_type_traits.h b/libstdc++-v3/include/bits/cpp_type_traits.h > >index 59f1a1875eb..210a9ea00da 100644 > >--- a/libstdc++-v3/include/bits/cpp_type_traits.h > >+++ b/libstdc++-v3/include/bits/cpp_type_traits.h > >@@ -363,6 +363,13 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > > // > > // Pointer types > > // > >+#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) > >+ template<typename _Tp, bool _IsPtr = __is_pointer(_Tp)> > >+ struct __is_pointer : __truth_type<_IsPtr> > > I was worried that reusing __is_pointer this way would cause a problem > with Clang, because it has an __is_pointer built-in and the code above > causes a warning: > > isp.cc:2:12: warning: keyword '__is_pointer' will be made available as an identifier for the remainder of the translation unit [-Wkeyword-compat] > 2 | struct __is_pointer > | ^ > > I thought this warning meant it was only available as an identifier. > But in fact it becomes available as both an identifier and as the > built-in. > > This is what I tested: > > template<typename T, bool IsPtr = __is_pointer(T)> > struct __is_pointer > { > enum { value = IsPtr }; > }; > static_assert( __is_pointer<int*>::value, "" ); > > template<typename T> > struct is_pointer > { > static constexpr bool value = __is_pointer(T); > }; > static_assert( is_pointer<int*>::value, "" ); > > So the is_pointer template can still use the built-in even though the > name '__is_pointer' has been declared as an identifier. > > So Clang matches GCC and it works fine. Good! Thank you! > > >+ { > >+ enum { __value = _IsPtr }; > >+ }; > >+#else > > template<typename _Tp> > > struct __is_pointer > > { > >@@ -377,6 +384,28 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > > typedef __true_type __type; > > }; > > > >+ template<typename _Tp> > >+ struct __is_pointer<_Tp* const> > > None of the other traits in bits/cpp_type_traits.h is true for > cv-qualified types, so these partial specializations make __is_pointer > the odd one out. Are they necessary? It looks like we don't need these specializations. I'll remove these. > > >+ { > >+ enum { __value = 1 }; > >+ typedef __true_type __type; > >+ }; > >+ > >+ template<typename _Tp> > >+ struct __is_pointer<_Tp* volatile> > >+ { > >+ enum { __value = 1 }; > >+ typedef __true_type __type; > >+ }; > >+ > >+ template<typename _Tp> > >+ struct __is_pointer<_Tp* const volatile> > >+ { > >+ enum { __value = 1 }; > >+ typedef __true_type __type; > >+ }; > >+#endif > >+ > > // > > // An arithmetic type is an integer type or a floating point type > > // > >@@ -387,7 +416,7 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > > > > // > > // A scalar type is an arithmetic type or a pointer type > >- // > >+ // > > template<typename _Tp> > > struct __is_scalar > > : public __traitor<__is_arithmetic<_Tp>, __is_pointer<_Tp> > > >diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits > >index 748fa186881..ea013b4b7bc 100644 > >--- a/libstdc++-v3/include/std/type_traits > >+++ b/libstdc++-v3/include/std/type_traits > >@@ -542,19 +542,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > : public true_type { }; > > #endif > > > >- template<typename> > >- struct __is_pointer_helper > >+ /// is_pointer > >+#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) > >+ template<typename _Tp> > >+ struct is_pointer > >+ : public __bool_constant<__is_pointer(_Tp)> > >+ { }; > >+#else > >+ template<typename _Tp> > >+ struct is_pointer > > : public false_type { }; > > > > template<typename _Tp> > >- struct __is_pointer_helper<_Tp*> > >+ struct is_pointer<_Tp*> > > : public true_type { }; > > > >- /// is_pointer > > template<typename _Tp> > >- struct is_pointer > >- : public __is_pointer_helper<__remove_cv_t<_Tp>>::type > >- { }; > >+ struct is_pointer<_Tp* const> > >+ : public true_type { }; > >+ > >+ template<typename _Tp> > >+ struct is_pointer<_Tp* volatile> > >+ : public true_type { }; > >+ > >+ template<typename _Tp> > >+ struct is_pointer<_Tp* const volatile> > >+ : public true_type { }; > >+#endif > > > > /// is_lvalue_reference > > template<typename> > >@@ -3268,8 +3282,22 @@ template <typename _Tp, size_t _Num> > > inline constexpr bool is_array_v<_Tp[_Num]> = true; > > #endif > > > >+#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v = __is_pointer(_Tp); > >+#else > > template <typename _Tp> > >- inline constexpr bool is_pointer_v = is_pointer<_Tp>::value; > >+ inline constexpr bool is_pointer_v = false; > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v<_Tp*> = true; > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v<_Tp* const> = true; > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v<_Tp* volatile> = true; > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v<_Tp* const volatile> = true; > >+#endif > >+ > > template <typename _Tp> > > inline constexpr bool is_lvalue_reference_v = false; > > template <typename _Tp> > >-- > >2.44.0 > > >
On Thu, Jun 13, 2024 at 5:32 AM Jonathan Wakely <jwakely@redhat.com> wrote: > > On 11/05/24 02:01 -0700, Ken Matsui wrote: > >This patch optimizes the compilation performance of std::is_pointer > >by dispatching to the new __is_pointer built-in trait. > > > >libstdc++-v3/ChangeLog: > > > > * include/bits/cpp_type_traits.h (__is_pointer): Use > > __is_pointer built-in trait. Optimize its implementation. > > * include/std/type_traits (is_pointer): Likewise. > > (is_pointer_v): Likewise. > > > >Co-authored-by: Jonathan Wakely <jwakely@redhat.com> > >Signed-off-by: Ken Matsui <kmatsui@gcc.gnu.org> > >--- > > libstdc++-v3/include/bits/cpp_type_traits.h | 31 ++++++++++++++- > > libstdc++-v3/include/std/type_traits | 44 +++++++++++++++++---- > > 2 files changed, 66 insertions(+), 9 deletions(-) > > > >diff --git a/libstdc++-v3/include/bits/cpp_type_traits.h b/libstdc++-v3/include/bits/cpp_type_traits.h > >index 59f1a1875eb..210a9ea00da 100644 > >--- a/libstdc++-v3/include/bits/cpp_type_traits.h > >+++ b/libstdc++-v3/include/bits/cpp_type_traits.h > >@@ -363,6 +363,13 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > > // > > // Pointer types > > // > >+#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) > >+ template<typename _Tp, bool _IsPtr = __is_pointer(_Tp)> > >+ struct __is_pointer : __truth_type<_IsPtr> > > I was worried that reusing __is_pointer this way would cause a problem > with Clang, because it has an __is_pointer built-in and the code above > causes a warning: > > isp.cc:2:12: warning: keyword '__is_pointer' will be made available as an identifier for the remainder of the translation unit [-Wkeyword-compat] > 2 | struct __is_pointer > | ^ > > I thought this warning meant it was only available as an identifier. > But in fact it becomes available as both an identifier and as the > built-in. > > This is what I tested: > > template<typename T, bool IsPtr = __is_pointer(T)> > struct __is_pointer > { > enum { value = IsPtr }; > }; > static_assert( __is_pointer<int*>::value, "" ); > > template<typename T> > struct is_pointer > { > static constexpr bool value = __is_pointer(T); > }; > static_assert( is_pointer<int*>::value, "" ); > > So the is_pointer template can still use the built-in even though the > name '__is_pointer' has been declared as an identifier. > > So Clang matches GCC and it works fine. Good! Actually clang does not work the same as GCC in the end; it is much more inconsistent in what it allows. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115497 (and https://github.com/llvm/llvm-project/issues/95598). Where sometimes the usage works as a trait after the use of it as an identifier and sometimes it fails. Thanks, Andrew Pinski > > >+ { > >+ enum { __value = _IsPtr }; > >+ }; > >+#else > > template<typename _Tp> > > struct __is_pointer > > { > >@@ -377,6 +384,28 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > > typedef __true_type __type; > > }; > > > >+ template<typename _Tp> > >+ struct __is_pointer<_Tp* const> > > None of the other traits in bits/cpp_type_traits.h is true for > cv-qualified types, so these partial specializations make __is_pointer > the odd one out. Are they necessary? > > >+ { > >+ enum { __value = 1 }; > >+ typedef __true_type __type; > >+ }; > >+ > >+ template<typename _Tp> > >+ struct __is_pointer<_Tp* volatile> > >+ { > >+ enum { __value = 1 }; > >+ typedef __true_type __type; > >+ }; > >+ > >+ template<typename _Tp> > >+ struct __is_pointer<_Tp* const volatile> > >+ { > >+ enum { __value = 1 }; > >+ typedef __true_type __type; > >+ }; > >+#endif > >+ > > // > > // An arithmetic type is an integer type or a floating point type > > // > >@@ -387,7 +416,7 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > > > > // > > // A scalar type is an arithmetic type or a pointer type > >- // > >+ // > > template<typename _Tp> > > struct __is_scalar > > : public __traitor<__is_arithmetic<_Tp>, __is_pointer<_Tp> > > >diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits > >index 748fa186881..ea013b4b7bc 100644 > >--- a/libstdc++-v3/include/std/type_traits > >+++ b/libstdc++-v3/include/std/type_traits > >@@ -542,19 +542,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > : public true_type { }; > > #endif > > > >- template<typename> > >- struct __is_pointer_helper > >+ /// is_pointer > >+#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) > >+ template<typename _Tp> > >+ struct is_pointer > >+ : public __bool_constant<__is_pointer(_Tp)> > >+ { }; > >+#else > >+ template<typename _Tp> > >+ struct is_pointer > > : public false_type { }; > > > > template<typename _Tp> > >- struct __is_pointer_helper<_Tp*> > >+ struct is_pointer<_Tp*> > > : public true_type { }; > > > >- /// is_pointer > > template<typename _Tp> > >- struct is_pointer > >- : public __is_pointer_helper<__remove_cv_t<_Tp>>::type > >- { }; > >+ struct is_pointer<_Tp* const> > >+ : public true_type { }; > >+ > >+ template<typename _Tp> > >+ struct is_pointer<_Tp* volatile> > >+ : public true_type { }; > >+ > >+ template<typename _Tp> > >+ struct is_pointer<_Tp* const volatile> > >+ : public true_type { }; > >+#endif > > > > /// is_lvalue_reference > > template<typename> > >@@ -3268,8 +3282,22 @@ template <typename _Tp, size_t _Num> > > inline constexpr bool is_array_v<_Tp[_Num]> = true; > > #endif > > > >+#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v = __is_pointer(_Tp); > >+#else > > template <typename _Tp> > >- inline constexpr bool is_pointer_v = is_pointer<_Tp>::value; > >+ inline constexpr bool is_pointer_v = false; > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v<_Tp*> = true; > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v<_Tp* const> = true; > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v<_Tp* volatile> = true; > >+template <typename _Tp> > >+ inline constexpr bool is_pointer_v<_Tp* const volatile> = true; > >+#endif > >+ > > template <typename _Tp> > > inline constexpr bool is_lvalue_reference_v = false; > > template <typename _Tp> > >-- > >2.44.0 > > >
diff --git a/libstdc++-v3/include/bits/cpp_type_traits.h b/libstdc++-v3/include/bits/cpp_type_traits.h index 59f1a1875eb..210a9ea00da 100644 --- a/libstdc++-v3/include/bits/cpp_type_traits.h +++ b/libstdc++-v3/include/bits/cpp_type_traits.h @@ -363,6 +363,13 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) // // Pointer types // +#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) + template<typename _Tp, bool _IsPtr = __is_pointer(_Tp)> + struct __is_pointer : __truth_type<_IsPtr> + { + enum { __value = _IsPtr }; + }; +#else template<typename _Tp> struct __is_pointer { @@ -377,6 +384,28 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) typedef __true_type __type; }; + template<typename _Tp> + struct __is_pointer<_Tp* const> + { + enum { __value = 1 }; + typedef __true_type __type; + }; + + template<typename _Tp> + struct __is_pointer<_Tp* volatile> + { + enum { __value = 1 }; + typedef __true_type __type; + }; + + template<typename _Tp> + struct __is_pointer<_Tp* const volatile> + { + enum { __value = 1 }; + typedef __true_type __type; + }; +#endif + // // An arithmetic type is an integer type or a floating point type // @@ -387,7 +416,7 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) // // A scalar type is an arithmetic type or a pointer type - // + // template<typename _Tp> struct __is_scalar : public __traitor<__is_arithmetic<_Tp>, __is_pointer<_Tp> > diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits index 748fa186881..ea013b4b7bc 100644 --- a/libstdc++-v3/include/std/type_traits +++ b/libstdc++-v3/include/std/type_traits @@ -542,19 +542,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION : public true_type { }; #endif - template<typename> - struct __is_pointer_helper + /// is_pointer +#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) + template<typename _Tp> + struct is_pointer + : public __bool_constant<__is_pointer(_Tp)> + { }; +#else + template<typename _Tp> + struct is_pointer : public false_type { }; template<typename _Tp> - struct __is_pointer_helper<_Tp*> + struct is_pointer<_Tp*> : public true_type { }; - /// is_pointer template<typename _Tp> - struct is_pointer - : public __is_pointer_helper<__remove_cv_t<_Tp>>::type - { }; + struct is_pointer<_Tp* const> + : public true_type { }; + + template<typename _Tp> + struct is_pointer<_Tp* volatile> + : public true_type { }; + + template<typename _Tp> + struct is_pointer<_Tp* const volatile> + : public true_type { }; +#endif /// is_lvalue_reference template<typename> @@ -3268,8 +3282,22 @@ template <typename _Tp, size_t _Num> inline constexpr bool is_array_v<_Tp[_Num]> = true; #endif +#if _GLIBCXX_USE_BUILTIN_TRAIT(__is_pointer) +template <typename _Tp> + inline constexpr bool is_pointer_v = __is_pointer(_Tp); +#else template <typename _Tp> - inline constexpr bool is_pointer_v = is_pointer<_Tp>::value; + inline constexpr bool is_pointer_v = false; +template <typename _Tp> + inline constexpr bool is_pointer_v<_Tp*> = true; +template <typename _Tp> + inline constexpr bool is_pointer_v<_Tp* const> = true; +template <typename _Tp> + inline constexpr bool is_pointer_v<_Tp* volatile> = true; +template <typename _Tp> + inline constexpr bool is_pointer_v<_Tp* const volatile> = true; +#endif + template <typename _Tp> inline constexpr bool is_lvalue_reference_v = false; template <typename _Tp>
This patch optimizes the compilation performance of std::is_pointer by dispatching to the new __is_pointer built-in trait. libstdc++-v3/ChangeLog: * include/bits/cpp_type_traits.h (__is_pointer): Use __is_pointer built-in trait. Optimize its implementation. * include/std/type_traits (is_pointer): Likewise. (is_pointer_v): Likewise. Co-authored-by: Jonathan Wakely <jwakely@redhat.com> Signed-off-by: Ken Matsui <kmatsui@gcc.gnu.org> --- libstdc++-v3/include/bits/cpp_type_traits.h | 31 ++++++++++++++- libstdc++-v3/include/std/type_traits | 44 +++++++++++++++++---- 2 files changed, 66 insertions(+), 9 deletions(-)