diff --git a/runtime/NanoLogCpp17.h b/runtime/NanoLogCpp17.h index ffb063e..bf0ce7f 100644 --- a/runtime/NanoLogCpp17.h +++ b/runtime/NanoLogCpp17.h @@ -17,10 +17,12 @@ #include #include +#include #include #include #include +#include #include "Common.h" #include "Cycles.h" @@ -43,6 +45,21 @@ */ namespace NanoLogInternal { +/** + * A struct that contains a list of ParamType enum alongside with its actual + * size. Mainly used as return type of getParamInfos to store the ParamType + * extracted from format string. + * + * \tparam N + * The maximum number of elements avaliable in this struct. + */ + + template + struct ParamTypeContainer{ + std::array data; + std::size_t size; + }; + /** * Checks whether a character is with the terminal set of format specifier * characters according to the printf specification: @@ -116,26 +133,29 @@ isDigit(char c) { /** * Analyzes a static printf style format string and extracts type information - * about the p-th parameter that would be used in a corresponding NANO_LOG() + * about all of the parameters that would be used in a corresponding NANO_LOG() * invocation. * * \tparam N * Length of the static format string (automatically deduced) * \param fmt * Format string to parse - * \param paramNum - * p-th parameter to return type information for (starts from zero) * \return - * Returns an ParamType enum describing the type of the parameter + * Returns a ParamTypeContainer struct that contains all ParamType info + * in order. Note: The maximum avaliable size template parameter N is set + * with the fact that, the number of parameters in the format string shall + * be less than the length of that string. And exceeding the maximum size + * limit in core constant expression anyways will cause the program to be + * ill-formed. */ -template -constexpr inline ParamType -getParamInfo(const char (&fmt)[N], - int paramNum=0) +template +constexpr inline ParamTypeContainer +getParamInfos(const char (&fmt)[N]) { - int pos = 0; - while (pos < N - 1) { - + ParamTypeContainer paramTypes = {}; + std::size_t pos = 0; + std::size_t paramIdx = 0; + while (pos + 1 < N) { // The code below searches for something that looks like a printf // specifier (i.e. something that follows the format of // %.). We only care @@ -163,10 +183,9 @@ getParamInfo(const char (&fmt)[N], // Consume width if (fmt[pos] == '*') { - if (paramNum == 0) - return ParamType::DYNAMIC_WIDTH; + paramTypes.data[paramIdx] = ParamType::DYNAMIC_WIDTH; - --paramNum; + ++paramIdx; ++pos; } else { while (NanoLogInternal::isDigit(fmt[pos])) @@ -180,11 +199,11 @@ getParamInfo(const char (&fmt)[N], ++pos; // consume '.' if (fmt[pos] == '*') { - if (paramNum == 0) - return ParamType::DYNAMIC_PRECISION; + paramTypes.data[paramIdx] = + ParamType::DYNAMIC_PRECISION; hasDynamicPrecision = true; - --paramNum; + ++paramIdx; ++pos; } else { precision = 0; @@ -212,128 +231,90 @@ getParamInfo(const char (&fmt)[N], "%n specifiers are not support in NanoLog!"); } - if (paramNum != 0) { - --paramNum; - ++pos; - continue; + if (fmt[pos] != 's'){ + paramTypes.data[paramIdx] = ParamType::NON_STRING; + ++paramIdx; + } else if (hasDynamicPrecision) { + paramTypes.data[paramIdx] = + ParamType::STRING_WITH_DYNAMIC_PRECISION; + ++paramIdx; + } else if (precision == -1) { + paramTypes.data[paramIdx] = + ParamType::STRING_WITH_NO_PRECISION; + ++paramIdx; } else { - if (fmt[pos] != 's') - return ParamType::NON_STRING; - - if (hasDynamicPrecision) - return ParamType::STRING_WITH_DYNAMIC_PRECISION; - - if (precision == -1) - return ParamType::STRING_WITH_NO_PRECISION; - else - return ParamType(precision); + paramTypes.data[paramIdx] = ParamType(precision); + ++paramIdx; } } } } - return ParamType::INVALID; -} - - -/** - * Helper to analyzeFormatString. This level of indirection is needed to - * unpack the index_sequence generated in analyzeFormatString and - * use the sequence as indices for calling getParamInfo. - * - * \tparam N - * Length of the format string (automatically deduced) - * \tparam Indices - * An index sequence from [0, N) where N is the number of parameters in - * the format string (automatically deduced) - * - * \param fmt - * printf format string to analyze - * - * \return - * An std::array describing the types at each index (zero based). - */ -template -constexpr std::array -analyzeFormatStringHelper(const char (&fmt)[N], std::index_sequence) -{ - return {{ getParamInfo(fmt, Indices)... }}; + paramTypes.size = paramIdx; + return paramTypes; } /** * Computes a ParamType array describing the parameters that would be used - * with the provided printf style format string. The indices of the array - * correspond with the parameter position in the variable args portion of - * the invocation. + * with the provided ParamTypeContainer analyzed from the printf style format + * string. The indices of the array correspond with the parameter position in + * the variable args portion of the invocation. * * \template NParams * The number of additional format parameters that follow the format * string in a printf-like function. For example printf("%*.*d", 9, 8, 7) * would have NParams = 3 * \template N - * length of the printf style format string (automatically deduced) - * - * \param fmt - * Format string to generate the array for + * template parameter N of the ParamTypeContainer parameter (automatically + * deduced) + * \param paramTypes + * ParamTypeContainer object to generate the array for * * \return * An std::array where the n-th index indicates that the * n-th format parameter is a "%s" or not. */ -template +template constexpr std::array -analyzeFormatString(const char (&fmt)[N]) -{ - return analyzeFormatStringHelper(fmt, std::make_index_sequence{}); -} - -/** - * Counts the number of parameters that need to be passed in for a particular - * printf style format string. - * - * One subtle point is that we are counting parameters, not specifiers, so a - * specifier of "%*.*d" will actually count as 3 since the two '*" will result - * in a parameter being passed in each. - * - * \tparam N - * length of the printf style format string (automatically deduced) - * - * \param fmt - * printf style format string to analyze - * - * @return - */ -template -constexpr inline int -countFmtParams(const char (&fmt)[N]) +analyzeParamTypeContainer(const ParamTypeContainer ¶mTypes) { - int count = 0; - while (getParamInfo(fmt, count) != ParamType::INVALID) - ++count; - return count; + std::array ret = {}; + int fillSize = NParams; + if (NParams > static_cast(paramTypes.size)) + fillSize = static_cast(paramTypes.size); + for (int i = 0; i < fillSize; ++i) { + ret[i] = paramTypes.data[i]; + } + if (NParams > static_cast(paramTypes.size)) { + for (int i = paramTypes.size; i < NParams; ++i) { + ret[i] = ParamType::INVALID; + } + } + return ret; } /** * Counts the number of nibbles that would be needed to represent all * the non-string and dynamic width/precision specifiers for a given - * printf style format string in the NanoLog system. + * ParamTypeContainer in the NanoLog system. * * \tparam N - * length of the printf style format string (automatically deduced) - * \param fmt - * printf style format string to analyze + * template parameter N of the ParamTypeContainer parameter (automatically + * deduced) + * \param paramTypes + * ParamTypeContainer object to analyze * * \return - * Number of non-string specifiers in the format string + * Number of non-string specifiers in the paramTypes object. */ template constexpr int -getNumNibblesNeeded(const char (&fmt)[N]) +getNumNibblesNeeded(const ParamTypeContainer ¶mTypes) { int numNibbles = 0; - for (int i = 0; i < countFmtParams(fmt); ++i) { - ParamType t = getParamInfo(fmt, i); + for (int i = 0; i < paramTypes.size; ++i) { + ParamType t = paramTypes.data[i]; if (t == NON_STRING || t == DYNAMIC_PRECISION || t == DYNAMIC_WIDTH) ++numNibbles; } @@ -1071,8 +1052,9 @@ checkFormat(NANOLOG_PRINTF_FORMAT const char *, ...) {} * Log arguments associated with the printf-like string. */ #define NANO_LOG(severity, format, ...) do { \ - constexpr int numNibbles = NanoLogInternal::getNumNibblesNeeded(format); \ - constexpr int nParams = NanoLogInternal::countFmtParams(format); \ + constexpr auto paramTypeInfo = NanoLogInternal::getParamInfos(format); \ + constexpr int numNibbles = NanoLogInternal::getNumNibblesNeeded(paramTypeInfo); \ + constexpr int nParams = paramTypeInfo.size; \ \ /*** Very Important*** These must be 'static' so that we can save pointers * to these variables and have them persist beyond the invocation. @@ -1081,7 +1063,7 @@ checkFormat(NANOLOG_PRINTF_FORMAT const char *, ...) {} * used by the compression function, which is invoked in another thread * at a much later time. */ \ static constexpr std::array paramTypes = \ - NanoLogInternal::analyzeFormatString(format); \ + NanoLogInternal::analyzeParamTypeContainer(paramTypeInfo); \ static int logId = NanoLogInternal::UNASSIGNED_LOGID; \ \ if (NanoLog::severity > NanoLog::getLogLevel()) \ diff --git a/runtime/NanoLogCpp17Test.cc b/runtime/NanoLogCpp17Test.cc index 88e1be9..cb1e92c 100644 --- a/runtime/NanoLogCpp17Test.cc +++ b/runtime/NanoLogCpp17Test.cc @@ -13,6 +13,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include + #include "gtest/gtest.h" #include "TestUtil.h" @@ -70,88 +72,98 @@ class NanoLogCpp17Test : public ::testing::Test { // Objects declared here can be used by all tests in the test case for Foo. }; -TEST_F(NanoLogCpp17Test, getParamInfo_constexpr) { - constexpr ParamType ret1 = getParamInfo("Hello World %*.*s asdf", 1); +TEST_F(NanoLogCpp17Test, getParamInfos_constexpr) { + constexpr auto paramInfos = getParamInfos("Hello World %*.*s asdf"); + + constexpr ParamType ret1 = paramInfos.data.at(1); EXPECT_EQ(ParamType::DYNAMIC_PRECISION, ret1); - constexpr ParamType ret2 = getParamInfo("Hello World %*.*s asdf", 2); + constexpr ParamType ret2 = paramInfos.data.at(2); EXPECT_EQ(ParamType::STRING_WITH_DYNAMIC_PRECISION, ret2); - constexpr ParamType ret3 = getParamInfo("Hello World %*.*s asdf", 3); - EXPECT_EQ(ParamType::INVALID, ret3); + EXPECT_EQ(3, paramInfos.size); } -TEST_F(NanoLogCpp17Test, getParamInfo) { +TEST_F(NanoLogCpp17Test, getParamInfos) { // Regular testing const char testString[] = "Hello %*.*d %%%s %*.*s %10.500s %10.500d %+#.s"; - EXPECT_EQ(ParamType::DYNAMIC_WIDTH, getParamInfo(testString, 0)); - EXPECT_EQ(ParamType::DYNAMIC_PRECISION, getParamInfo(testString, 1)); - EXPECT_EQ(ParamType::NON_STRING, getParamInfo(testString, 2)); - EXPECT_EQ(ParamType::STRING_WITH_NO_PRECISION, getParamInfo(testString, 3)); - EXPECT_EQ(ParamType::DYNAMIC_WIDTH, getParamInfo(testString, 4)); - EXPECT_EQ(ParamType::DYNAMIC_PRECISION, getParamInfo(testString, 5)); - EXPECT_EQ(ParamType::STRING_WITH_DYNAMIC_PRECISION, getParamInfo(testString, 6)); - EXPECT_EQ(ParamType(500), getParamInfo(testString, 7)); - EXPECT_EQ(ParamType::NON_STRING, getParamInfo(testString, 8)); - EXPECT_EQ(ParamType(0), getParamInfo(testString, 9)); - EXPECT_EQ(ParamType::INVALID, getParamInfo(testString, 10)); - EXPECT_EQ(ParamType::INVALID, getParamInfo(testString, 11)); + auto pI_testString = getParamInfos(testString); + EXPECT_EQ(ParamType::DYNAMIC_WIDTH, pI_testString.data.at(0)); + EXPECT_EQ(ParamType::DYNAMIC_PRECISION, pI_testString.data.at(1)); + EXPECT_EQ(ParamType::NON_STRING, pI_testString.data.at(2)); + EXPECT_EQ(ParamType::STRING_WITH_NO_PRECISION, pI_testString.data.at(3)); + EXPECT_EQ(ParamType::DYNAMIC_WIDTH, pI_testString.data.at(4)); + EXPECT_EQ(ParamType::DYNAMIC_PRECISION, pI_testString.data.at(5)); + EXPECT_EQ(ParamType::STRING_WITH_DYNAMIC_PRECISION, pI_testString.data.at(6)); + EXPECT_EQ(ParamType(500), pI_testString.data.at(7)); + EXPECT_EQ(ParamType::NON_STRING, pI_testString.data.at(8)); + EXPECT_EQ(ParamType(0), pI_testString.data.at(9)); + EXPECT_EQ(10, pI_testString.size); // Test escaped parameters - EXPECT_EQ(INVALID, getParamInfo("~S!@#$^&*()_+1234567890qwertyu\n" - "iopasdfghjkl;zxcv bnm,\\\\\\\\r\\\\n\n" - "%%ud \\%%lf osdif<>\":L:];")); + auto pI_escParam = getParamInfos("~S!@#$^&*()_+1234567890qwertyu\n" + "iopasdfghjkl;zxcv bnm,\\\\\\\\r\\\\n\n" + "%%ud \\%%lf osdif<>\":L:];"); + EXPECT_EQ(0, pI_escParam.size); // Test all the types (ported from python unit tests) const char test_jzt[] = "%10.12jd %9ji %0*.*ju %jo %jx %jX %zu %zd %tu %td"; - EXPECT_EQ(NON_STRING, getParamInfo(test_jzt, 0)); - EXPECT_EQ(NON_STRING, getParamInfo(test_jzt, 1)); - EXPECT_EQ(DYNAMIC_WIDTH, getParamInfo(test_jzt, 2)); - EXPECT_EQ(DYNAMIC_PRECISION, getParamInfo(test_jzt, 3)); + auto pI_test_jzt = getParamInfos(test_jzt); + EXPECT_EQ(NON_STRING, pI_test_jzt.data.at(0)); + EXPECT_EQ(NON_STRING, pI_test_jzt.data.at(1)); + EXPECT_EQ(DYNAMIC_WIDTH, pI_test_jzt.data.at(2)); + EXPECT_EQ(DYNAMIC_PRECISION, pI_test_jzt.data.at(3)); for (int i = 4; i <= 11; ++i) - EXPECT_EQ(NON_STRING, getParamInfo(test_jzt, i)); - EXPECT_EQ(INVALID, getParamInfo(test_jzt, 12)); + EXPECT_EQ(NON_STRING, pI_test_jzt.data.at(i)); + EXPECT_EQ(12, pI_test_jzt.size); const char doubles[] = "%12.0f %12.3F %e %55.3E %-10.5g %G %a %A"; + auto pI_doubles = getParamInfos(doubles); for (int i = 0; i <= 7; ++i) - EXPECT_EQ(NON_STRING, getParamInfo(doubles, i)); - EXPECT_EQ(INVALID, getParamInfo(doubles, 8)); + EXPECT_EQ(NON_STRING, pI_doubles.data.at(i)); + EXPECT_EQ(8, pI_doubles.size); const char longDoubles[] = "%12.0Lf %12.3LF %Le %55.3LE %-10.5Lg %LG %La%LA"; + auto pI_longDoubles = getParamInfos(longDoubles); for (int i = 0; i <= 7; ++i) - EXPECT_EQ(NON_STRING, getParamInfo(longDoubles, i)); - EXPECT_EQ(INVALID, getParamInfo(longDoubles, 8)); + EXPECT_EQ(NON_STRING, pI_longDoubles.data.at(i)); + EXPECT_EQ(8, pI_longDoubles.size); const char basicInts[] = "%d %i %u %o %x %X %c %p"; + auto pI_basicInts = getParamInfos(basicInts); for (int i = 0; i <= 7; ++i) - EXPECT_EQ(NON_STRING, getParamInfo(basicInts, i)); - EXPECT_EQ(INVALID, getParamInfo(basicInts, 8)); + EXPECT_EQ(NON_STRING, pI_basicInts.data.at(i)); + EXPECT_EQ(8, pI_basicInts.size); const char lengthMods[] = "%hhd %hd %ld %lld %jd %zd %09.2td"; + auto pI_lengthMods = getParamInfos(lengthMods); for (int i = 0; i <= 6; ++i) - EXPECT_EQ(NON_STRING, getParamInfo(lengthMods, i)); - EXPECT_EQ(INVALID, getParamInfo(lengthMods, 7)); + EXPECT_EQ(NON_STRING, pI_lengthMods.data.at(i)); + EXPECT_EQ(7, pI_lengthMods.size); // Test all the types - EXPECT_EQ(INVALID, getParamInfo("Hello World!")); + EXPECT_EQ(0, getParamInfos("Hello, World!").size); - EXPECT_EQ(NON_STRING, getParamInfo("%hhd %hhi", 0)); - EXPECT_EQ(NON_STRING, getParamInfo("%hhd %hhi", 1)); - EXPECT_EQ(INVALID, getParamInfo("%hhd %hhi", 2)); + auto pI_types = getParamInfos("%hhd %hhi"); + EXPECT_EQ(NON_STRING, pI_types.data.at(0)); + EXPECT_EQ(NON_STRING, pI_types.data.at(1)); + EXPECT_EQ(2, pI_types.size); // Unsupported variations of %n - EXPECT_ANY_THROW(getParamInfo("%hhn")); - EXPECT_ANY_THROW(getParamInfo("%jn")); - EXPECT_ANY_THROW(getParamInfo("%zn")); - EXPECT_ANY_THROW(getParamInfo("%#0t4.02n")); + EXPECT_ANY_THROW(getParamInfos("%hhn")); + EXPECT_ANY_THROW(getParamInfos("%jn")); + EXPECT_ANY_THROW(getParamInfos("%zn")); + EXPECT_ANY_THROW(getParamInfos("%#0t4.02n")); // invalid specifier - EXPECT_ANY_THROW(getParamInfo("%hhj")); + EXPECT_ANY_THROW(getParamInfos("%hhj")); } -TEST_F(NanoLogCpp17Test, analyzeFormatString) { - constexpr std::array testArray = analyzeFormatString<10>( - "Hello %*.*d %%%s %*.*s %10.500s %10.500d %+#.s"); +TEST_F(NanoLogCpp17Test, analyzeParamTypeContainer) { + constexpr std::array testArray = + analyzeParamTypeContainer<10>( + getParamInfos( + "Hello %*.*d %%%s %*.*s %10.500s %10.500d %+#.s")); EXPECT_EQ(ParamType::DYNAMIC_WIDTH, testArray.at(0)); EXPECT_EQ(ParamType::DYNAMIC_PRECISION, testArray.at(1)); @@ -165,24 +177,42 @@ TEST_F(NanoLogCpp17Test, analyzeFormatString) { EXPECT_EQ(ParamType(0), testArray.at(9)); } -TEST_F(NanoLogCpp17Test, analyzeFormatString_empty) { - constexpr std::array testArray = analyzeFormatString<0>("a"); +TEST_F(NanoLogCpp17Test, analyzeParamTypeContainer_empty) { + constexpr std::array testArray = + analyzeParamTypeContainer<0>(getParamInfos("a")); EXPECT_EQ(0U, testArray.size()); } -TEST_F(NanoLogCpp17Test, countFmtParams) { - EXPECT_EQ(10, countFmtParams("d %*.*d %%%s %*.*s %10.500s %10.500d %+#.s")); - EXPECT_EQ(0, countFmtParams("alsdjaklsjflsajfkdasjl%%%%f")); - EXPECT_EQ(0, countFmtParams("")); +TEST_F(NanoLogCpp17Test, getParamInfos_count) { + EXPECT_EQ(10, + getParamInfos("d %*.*d %%%s %*.*s %10.500s %10.500d %+#.s").size); + EXPECT_EQ(0, getParamInfos("alsdjaklsjflsajfkdasjl%%%%f").size); + EXPECT_EQ(0, getParamInfos("").size); } TEST_F(NanoLogCpp17Test, getNumNibblesNeeded) { EXPECT_EQ(6, getNumNibblesNeeded( - "d %*.*d %%%s %*.*s %10.500s %10.500d %+#.s")); - EXPECT_EQ(0, getNumNibblesNeeded("")); - EXPECT_EQ(0, getNumNibblesNeeded("asldkfjaslkfjasfd")); - EXPECT_EQ(0, getNumNibblesNeeded("%s")); - EXPECT_EQ(1, getNumNibblesNeeded("%d")); + getParamInfos("d %*.*d %%%s %*.*s %10.500s %10.500d %+#.s"))); + EXPECT_EQ(0, getNumNibblesNeeded(getParamInfos(""))); + EXPECT_EQ(0, getNumNibblesNeeded(getParamInfos("asldkfjaslkfjasfd"))); + EXPECT_EQ(0, getNumNibblesNeeded(getParamInfos("%s"))); + EXPECT_EQ(1, getNumNibblesNeeded(getParamInfos("%d"))); +} + +TEST_F(NanoLogCpp17Test, getNumNibblesNeeded_large) { + // This test is mainly intended to ensure that the constexpr implementation + // of getNumNibblesNeeded shall not exceed the maximum constexpr evaluation + // steps of a compiler. Implementation as of commit 2a94d70 would be + // evaluated in 1395813 steps under Clang 13.0.1-rc1, which is greater than + // the default value of Clang's `-fconstexpr-steps`, 1048576. + constexpr auto paramInfo = getParamInfos( + "%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, " + "%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, " + "%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, " + "%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, "); + std::integral_constant justTypeObj = + std::integral_constant{}; + EXPECT_EQ(40, justTypeObj()); } TEST_F(NanoLogCpp17Test, store_argument) { @@ -332,8 +362,8 @@ TEST_F(NanoLogCpp17Test, store_arguments) { char backing_buffer[1024]; char *buffer = backing_buffer; - constexpr std::array testArray = analyzeFormatString<5>( - "Hello %s %p %*.*s"); + constexpr std::array testArray = analyzeParamTypeContainer<5>( + getParamInfos("Hello %s %p %*.*s")); size_t stringSizes[5]; // Do nothing @@ -628,8 +658,8 @@ TEST_F(NanoLogCpp17Test, getArgSizes) { stringArgSizes[1] = 0; // reset // A few things - constexpr std::array finalArray = analyzeFormatString<5>( - "Hello %s %p %*.*s"); + constexpr std::array finalArray = + analyzeParamTypeContainer<5>(getParamInfos("Hello %s %p %*.*s")); EXPECT_EQ(sizeof(uint32_t) + strlen("Stephen Yang") + sizeof(void*) + sizeof(int)