From 34e8cafa29a5240bb7a426c69a5fd8e8ad741dc4 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Tue, 23 Jan 2018 13:10:37 +0100 Subject: Document the new qfloat16 functions with the other qfloat16 functions Change-Id: I82d3fcda44a9a49d9027d865f8ed200134d0f79e Reviewed-by: Thiago Macieira --- src/corelib/global/qfloat16.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/corelib/global') diff --git a/src/corelib/global/qfloat16.cpp b/src/corelib/global/qfloat16.cpp index 3046a47292..fd608efe55 100644 --- a/src/corelib/global/qfloat16.cpp +++ b/src/corelib/global/qfloat16.cpp @@ -178,6 +178,7 @@ static void qFloatFromFloat16_fast(float *, const quint16 *, qsizetype) Q_DECL_N #endif /*! \since 5.11 + \relates Converts \a len floats from \a in to qfloat16 and stores them in \a out. Both \a in and \a out must have \a len allocated entries. @@ -193,6 +194,7 @@ Q_CORE_EXPORT void qFloatToFloat16(qfloat16 *out, const float *in, qsizetype len /*! \since 5.11 + \relates Converts \a len qfloat16 from \a in to floats and stores them in \a out. Both \a in and \a out must have \a len allocated entries. -- cgit v1.2.3 From 43c44d05ca6af4ec78c1dea84635375a637ff80d Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 30 Nov 2017 20:37:57 -0800 Subject: Update the overflow functions to include qsizetype Commit 29bc68cf169b8cf87d306a1c72e12eb9b6fbfce7 added support for unsigned and commit 5ff7a3d96e0ce0dcb3d388b53d038cdd40c7a975 later added support for int. This commit adds support for qsizetype, which isn't int on 64-bit platforms. We do this by reorganizing the code and using the generic version of __builtin_{add,sub,mul}_overflow from GCC 5 and Clang 3.8, which ICC 18 seems to support now too on Linux. That leaves older versions of GCC and Clang, as well as MSVC, ICC on Windows, and the GHS compiler, to use the generic implementations, as I've removed the assembly code those versions of GCC and Clang on x86 are now uncommon. Note: any older version of ICC probably breaks. We only support the latest. Change-Id: I9e2892cb6c374e93bcb7fffd14fc11bcd5f067a7 Reviewed-by: Allan Sandfeld Jensen --- src/corelib/global/qnumeric_p.h | 277 +++++++++++++++++----------------------- 1 file changed, 117 insertions(+), 160 deletions(-) (limited to 'src/corelib/global') diff --git a/src/corelib/global/qnumeric_p.h b/src/corelib/global/qnumeric_p.h index a352e39ef0..9b86a16516 100644 --- a/src/corelib/global/qnumeric_p.h +++ b/src/corelib/global/qnumeric_p.h @@ -1,7 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. +** Copyright (C) 2018 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -58,8 +58,6 @@ #if defined(Q_CC_MSVC) # include -#elif defined(Q_CC_INTEL) -# include // for _addcarry_u #endif #if defined(Q_CC_MSVC) @@ -164,10 +162,33 @@ Q_DECL_CONST_FUNCTION static inline bool qt_is_finite(float f) } #ifndef Q_CLANG_QDOC -// -// Unsigned overflow math -// namespace { +// Overflow math. +// This provides efficient implementations for int, unsigned, qsizetype and +// size_t. Implementations for 8- and 16-bit types will work but may not be as +// efficient. Implementations for 64-bit may be missing on 32-bit platforms. + +#if (defined(Q_CC_GNU) && (Q_CC_GNU >= 500) || defined(Q_CC_INTEL)) || QT_HAS_BUILTIN(__builtin_add_overflowx) +// GCC 5, ICC 18, and Clang 3.8 have builtins to detect overflows + +template inline +typename std::enable_if::value || std::is_signed::value, bool>::type +add_overflow(T v1, T v2, T *r) +{ return __builtin_add_overflow(v1, v2, r); } + +template inline +typename std::enable_if::value || std::is_signed::value, bool>::type +sub_overflow(T v1, T v2, T *r) +{ return __builtin_sub_overflow(v1, v2, r); } + +template inline +typename std::enable_if::value || std::is_signed::value, bool>::type +mul_overflow(T v1, T v2, T *r) +{ return __builtin_mul_overflow(v1, v2, r); } + +#else +// Generic implementations + template inline typename std::enable_if::value, bool>::type add_overflow(T v1, T v2, T *r) { @@ -176,69 +197,92 @@ add_overflow(T v1, T v2, T *r) return v1 > T(v1 + v2); } +template inline typename std::enable_if::value, bool>::type +add_overflow(T v1, T v2, T *r) +{ + // Here's how we calculate the overflow: + // 1) unsigned addition is well-defined, so we can always execute it + // 2) conversion from unsigned back to signed is implementation- + // defined and in the implementations we use, it's a no-op. + // 3) signed integer overflow happens if the sign of the two input operands + // is the same but the sign of the result is different. In other words, + // the sign of the result must be the same as the sign of either + // operand. + + using U = typename std::make_unsigned::type; + *r = T(U(v1) + U(v2)); + + // If int is two's complement, assume all integer types are too. + if (std::is_same::value) { + // Two's complement equivalent (generates slightly shorter code): + // x ^ y is negative if x and y have different signs + // x & y is negative if x and y are negative + // (x ^ z) & (y ^ z) is negative if x and z have different signs + // AND y and z have different signs + return ((v1 ^ *r) & (v2 ^ *r)) < 0; + } + + bool s1 = (v1 < 0); + bool s2 = (v2 < 0); + bool sr = (*r < 0); + return s1 != sr && s2 != sr; + // also: return s1 == s2 && s1 != sr; +} + template inline typename std::enable_if::value, bool>::type +sub_overflow(T v1, T v2, T *r) +{ + // unsigned subtractions are well-defined + *r = v1 - v2; + return v1 < v2; +} + +template inline typename std::enable_if::value, bool>::type +sub_overflow(T v1, T v2, T *r) +{ + // See above for explanation. This is the same with some signs reversed. + // We can't use add_overflow(v1, -v2, r) because it would be UB if + // v2 == std::numeric_limits::min(). + + using U = typename std::make_unsigned::type; + *r = T(U(v1) - U(v2)); + + if (std::is_same::value) + return ((v1 ^ *r) & (~v2 ^ *r)) < 0; + + bool s1 = (v1 < 0); + bool s2 = !(v2 < 0); + bool sr = (*r < 0); + return s1 != sr && s2 != sr; + // also: return s1 == s2 && s1 != sr; +} + +template inline +typename std::enable_if::value || std::is_signed::value, bool>::type mul_overflow(T v1, T v2, T *r) { // use the next biggest type // Note: for 64-bit systems where __int128 isn't supported, this will cause an error. - // A fallback is present below. - typedef typename QIntegerForSize::Unsigned Larger; + using LargerInt = QIntegerForSize; + using Larger = typename std::conditional::value, + typename LargerInt::Signed, typename LargerInt::Unsigned>::type; Larger lr = Larger(v1) * Larger(v2); *r = T(lr); - return lr > std::numeric_limits::max(); + return lr > std::numeric_limits::max() || lr < std::numeric_limits::min(); } -#if defined(__SIZEOF_INT128__) -# define HAVE_MUL64_OVERFLOW -#endif - -// GCC 5 and Clang have builtins to detect overflows -#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || QT_HAS_BUILTIN(__builtin_uadd_overflow) +# if defined(Q_CC_MSVC) && defined(Q_PROCESSOR_X86) +// We can use intrinsics for the unsigned operations with MSVC template <> inline bool add_overflow(unsigned v1, unsigned v2, unsigned *r) -{ return __builtin_uadd_overflow(v1, v2, r); } -#endif -#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || QT_HAS_BUILTIN(__builtin_uaddl_overflow) -template <> inline bool add_overflow(unsigned long v1, unsigned long v2, unsigned long *r) -{ return __builtin_uaddl_overflow(v1, v2, r); } -#endif -#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || QT_HAS_BUILTIN(__builtin_uaddll_overflow) -template <> inline bool add_overflow(unsigned long long v1, unsigned long long v2, unsigned long long *r) -{ return __builtin_uaddll_overflow(v1, v2, r); } -#endif +{ return _addcarry_u32(0, v1, v2, r); } -#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || QT_HAS_BUILTIN(__builtin_umul_overflow) -template <> inline bool mul_overflow(unsigned v1, unsigned v2, unsigned *r) -{ return __builtin_umul_overflow(v1, v2, r); } -#endif -#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || QT_HAS_BUILTIN(__builtin_umull_overflow) -template <> inline bool mul_overflow(unsigned long v1, unsigned long v2, unsigned long *r) -{ return __builtin_umull_overflow(v1, v2, r); } -#endif -#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || QT_HAS_BUILTIN(__builtin_umulll_overflow) -template <> inline bool mul_overflow(unsigned long long v1, unsigned long long v2, unsigned long long *r) -{ return __builtin_umulll_overflow(v1, v2, r); } -# define HAVE_MUL64_OVERFLOW -#endif +// 32-bit mul_overflow is fine with the generic code above -#if (defined(Q_CC_MSVC) || defined(Q_CC_INTEL)) && defined(Q_PROCESSOR_X86) && !QT_HAS_BUILTIN(__builtin_uadd_overflow) -template <> inline bool add_overflow(unsigned v1, unsigned v2, unsigned *r) -{ return _addcarry_u32(0, v1, v2, r); } -# ifdef Q_CC_MSVC // longs are 32-bit -template <> inline bool add_overflow(unsigned long v1, unsigned long v2, unsigned long *r) -{ return _addcarry_u32(0, v1, v2, reinterpret_cast(r)); } -# endif -#endif -#if (defined(Q_CC_MSVC) || defined(Q_CC_INTEL)) && defined(Q_PROCESSOR_X86_64) && !QT_HAS_BUILTIN(__builtin_uadd_overflow) +# if defined(Q_PROCESSOR_X86_64) template <> inline bool add_overflow(quint64 v1, quint64 v2, quint64 *r) { return _addcarry_u64(0, v1, v2, reinterpret_cast(r)); } -# ifndef Q_CC_MSVC // longs are 64-bit -template <> inline bool add_overflow(unsigned long v1, unsigned long v2, unsigned long *r) -{ return _addcarry_u64(0, v1, v2, reinterpret_cast(r)); } -# endif -#endif -#if defined(Q_CC_MSVC) && (defined(Q_PROCESSOR_X86_64) || defined(Q_PROCESSOR_IA64)) && !QT_HAS_BUILTIN(__builtin_uadd_overflow) -#pragma intrinsic(_umul128) +# pragma intrinsic(_umul128) template <> inline bool mul_overflow(quint64 v1, quint64 v2, quint64 *r) { // use 128-bit multiplication with the _umul128 intrinsic @@ -247,117 +291,30 @@ template <> inline bool mul_overflow(quint64 v1, quint64 v2, quint64 *r) *r = _umul128(v1, v2, &high); return high; } -# define HAVE_MUL64_OVERFLOW -#endif - -#if !defined(HAVE_MUL64_OVERFLOW) && defined(__LP64__) -// no 128-bit multiplication, we need to figure out with a slow division -template <> inline bool mul_overflow(quint64 v1, quint64 v2, quint64 *r) -{ - if (v2 && v1 > std::numeric_limits::max() / v2) - return true; - *r = v1 * v2; - return false; -} -template <> inline bool mul_overflow(unsigned long v1, unsigned long v2, unsigned long *r) -{ - return mul_overflow(v1, v2, reinterpret_cast(r)); -} -#else -# undef HAVE_MUL64_OVERFLOW -#endif - -// -// Signed overflow math -// -// In C++, signed overflow math is Undefined Behavior. However, many CPUs do implement some way to -// check for overflow. Some compilers expose intrinsics to use this functionality. If the no -// intrinsic is exposed, overflow checking can be done by widening the result type and "manually" -// checking for overflow. Or, alternatively, by using inline assembly to use the CPU features. -// -// Only int overflow checking is implemented, because it's the only one used. -#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || QT_HAS_BUILTIN(__builtin_sadd_overflow) -inline bool add_overflow(int v1, int v2, int *r) -{ return __builtin_sadd_overflow(v1, v2, r); } -#elif defined(Q_CC_GNU) && defined(Q_PROCESSOR_X86) -inline bool add_overflow(int v1, int v2, int *r) -{ - quint8 overflow = 0; - int res = v1; - - asm ("addl %2, %1\n" - "seto %0" - : "=q" (overflow), "=r" (res) - : "r" (v2), "1" (res) - : "cc" - ); - *r = res; - return overflow; -} -#else -inline bool add_overflow(int v1, int v2, int *r) -{ - qint64 t = qint64(v1) + v2; - *r = static_cast(t); - return t > std::numeric_limits::max() || t < std::numeric_limits::min(); -} -#endif -#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || QT_HAS_BUILTIN(__builtin_ssub_overflow) -inline bool sub_overflow(int v1, int v2, int *r) -{ return __builtin_ssub_overflow(v1, v2, r); } -#elif defined(Q_CC_GNU) && defined(Q_PROCESSOR_X86) -inline bool sub_overflow(int v1, int v2, int *r) +# pragma intrinsic(_mul128) +template <> inline bool mul_overflow(qint64 v1, qint64 v2, qint64 *r) { - quint8 overflow = 0; - int res = v1; - - asm ("subl %2, %1\n" - "seto %0" - : "=q" (overflow), "=r" (res) - : "r" (v2), "1" (res) - : "cc" - ); - *r = res; - return overflow; + // Use 128-bit multiplication with the _mul128 intrinsic + // https://msdn.microsoft.com/en-us/library/82cxdw50.aspx + + // This is slightly more complex than the unsigned case above: the sign bit + // of 'low' must be replicated as the entire 'high', so the only valid + // values for 'high' are 0 and -1. + + qint64 high; + *r = _mul128(v1, v2, &high); + if (high == 0) + return *r < 0; + if (high == -1) + return *r >= 0; + return true; } -#else -inline bool sub_overflow(int v1, int v2, int *r) -{ - qint64 t = qint64(v1) - v2; - *r = static_cast(t); - return t > std::numeric_limits::max() || t < std::numeric_limits::min(); +# endif // x86-64 +# endif // MSVC x86 +#endif // !GCC } -#endif - -#if (defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && Q_CC_GNU >= 500) || QT_HAS_BUILTIN(__builtin_smul_overflow) -inline bool mul_overflow(int v1, int v2, int *r) -{ return __builtin_smul_overflow(v1, v2, r); } -#elif defined(Q_CC_GNU) && defined(Q_PROCESSOR_X86) -inline bool mul_overflow(int v1, int v2, int *r) -{ - quint8 overflow = 0; - int res = v1; - - asm ("imul %2, %1\n" - "seto %0" - : "=q" (overflow), "=r" (res) - : "r" (v2), "1" (res) - : "cc" - ); - *r = res; - return overflow; -} -#else -inline bool mul_overflow(int v1, int v2, int *r) -{ - qint64 t = qint64(v1) * v2; - *r = static_cast(t); - return t > std::numeric_limits::max() || t < std::numeric_limits::min(); -} -#endif #endif // Q_CLANG_QDOC -} QT_END_NAMESPACE -- cgit v1.2.3 From 48bce2e8f0d787342f3e0f86335460fa25e8ac8f Mon Sep 17 00:00:00 2001 From: Rafael Roquetto Date: Wed, 1 Feb 2017 17:32:13 -0200 Subject: Support for LTTNG and ETW tracing This commit introduces minimal support for instrumentation within Qt. Currently, only LTTNG/Linux and ETW/Windows are supported. Change-Id: I59b48cf83acf5532a998bb493e6379e9177e14c8 Reviewed-by: Oswald Buddenhagen Reviewed-by: Shawn Rutledge Reviewed-by: Thiago Macieira --- src/corelib/global/qconfig-bootstrapped.h | 2 + src/corelib/global/qtrace_p.h | 126 ++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 src/corelib/global/qtrace_p.h (limited to 'src/corelib/global') diff --git a/src/corelib/global/qconfig-bootstrapped.h b/src/corelib/global/qconfig-bootstrapped.h index 0df593941d..86ef1a2613 100644 --- a/src/corelib/global/qconfig-bootstrapped.h +++ b/src/corelib/global/qconfig-bootstrapped.h @@ -78,6 +78,7 @@ #define QT_FEATURE_cxx11_random (QT_HAS_INCLUDE() ? 1 : -1) #define QT_NO_DATASTREAM #define QT_FEATURE_datetimeparser -1 +#define QT_FEATURE_etw -1 #define QT_FEATURE_getauxval (QT_HAS_INCLUDE() ? 1 : -1) #define QT_FEATURE_getentropy -1 #define QT_NO_GEOM_VARIANT @@ -92,6 +93,7 @@ #else # define QT_FEATURE_linkat -1 #endif +#define QT_FEATURE_lttng -1 #define QT_NO_QOBJECT #define QT_FEATURE_process -1 #define QT_FEATURE_regularexpression -1 diff --git a/src/corelib/global/qtrace_p.h b/src/corelib/global/qtrace_p.h new file mode 100644 index 0000000000..ab8fc14af5 --- /dev/null +++ b/src/corelib/global/qtrace_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTRACE_P_H +#define QTRACE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +/* + * The Qt tracepoints API consists of only three macros: + * + * - Q_TRACE(tracepoint, args...) + * Fires 'tracepoint' if it is enabled. + * + * - Q_UNCONDITIONAL_TRACE(tracepoint, args...) + * Fires 'tracepoint' unconditionally: no check is performed to query + * whether 'tracepoint' is enabled. + * + * - Q_TRACE_ENABLED(tracepoint) + * Returns 'true' if 'tracepoint' is enabled; false otherwise. + * + * When using LTTNG, Q_TRACE, Q_UNCONDITIONAL_TRACE and Q_TRACE_ENABLED map + * ultimately to tracepoint(), do_tracepoint() and tracepoint_enabled(), + * respectively, described on the lttng-ust manpage (man 3 lttng-ust). + * + * On ETW, Q_TRACE() and Q_UNCONDITIONAL_TRACE() are equivalent, ultimately + * amounting to a call to TraceLoggingWrite(), whereas Q_TRACE_ENABLED() + * wraps around TraceLoggingProviderEnabled(). + * + * A tracepoint provider is defined in a separate file, that follows the + * following format: + * + * tracepoint_name(arg_type arg_name, ...) + * + * For instance: + * + * qcoreapplication_ctor(int argc, const char * const argv) + * qcoreapplication_foo(int argc, const char[10] argv) + * qcoreapplication_baz(const char[len] some_string, unsigned int len) + * qcoreapplication_qstring(const QString &foo) + * qcoreapplication_qrect(const QRect &rect) + * + * The provider file is then parsed by src/tools/tracegen, which can be + * switched to output either ETW or LTTNG tracepoint definitions. The provider + * name is deduced to be basename(provider_file). + * + * To use the above (inside qtcore), you need to include + * . After that, the following call becomes + * possible: + * + * Q_TRACE(qcoreapplication_qrect, myRect); + * + * Currently, all C++ primitive non-pointer types are supported for + * arguments. Additionally, char * is supported, and is assumed to + * be a NULL-terminated string. Finally, the following subset of Qt types also + * currently supported: + * + * - QString + * - QByteArray + * - QUrl + * - QRect + * + * Dynamic arrays are supported using the syntax illustrated by + * qcoreapplication_baz above. + */ + +QT_BEGIN_NAMESPACE + +#if defined(Q_TRACEPOINT) && !defined(QT_BOOTSTRAPPED) +# define Q_TRACE(x, ...) QtPrivate::trace_ ## x(__VA_ARGS__) +# define Q_UNCONDITIONAL_TRACE(x, ...) QtPrivate::do_trace_ ## x(__VA_ARGS__) +# define Q_TRACE_ENABLED(x) QtPrivate::trace_ ## x ## _enabled() +#else +# define Q_TRACE(x, ...) +# define Q_UNCONDITIONAL_TRACE(x, ...) +# define Q_TRACE_ENABLED(x) false +#endif // defined(Q_TRACEPOINT) && !defined(QT_BOOTSTRAPPED) + +QT_END_NAMESPACE + +#endif // QTRACE_P_H -- cgit v1.2.3 From cf1b732e21b8768c0e423f900a3f11f923863edd Mon Sep 17 00:00:00 2001 From: Sergio Martins Date: Fri, 2 Feb 2018 11:44:22 +0000 Subject: Introduce QT6_VIRTUAL and QT6_NOT_VIRTUAL macros To avoid QT_VERSION_CHECK ifdefs Change-Id: I364903964c72f4df19b8b7c10c19b82d24f63600 Reviewed-by: Lars Knoll --- src/corelib/global/qglobal.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/corelib/global') diff --git a/src/corelib/global/qglobal.h b/src/corelib/global/qglobal.h index 7764707de0..aa9446221b 100644 --- a/src/corelib/global/qglobal.h +++ b/src/corelib/global/qglobal.h @@ -88,6 +88,11 @@ #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) # define QT_NO_UNSHARABLE_CONTAINERS +# define QT6_VIRTUAL virtual +# define QT6_NOT_VIRTUAL +#else +# define QT6_VIRTUAL +# define QT6_NOT_VIRTUAL virtual #endif /* These two macros makes it possible to turn the builtin line expander into a -- cgit v1.2.3