Index: libcxxabi/trunk/src/cxa_demangle.cpp =================================================================== --- libcxxabi/trunk/src/cxa_demangle.cpp +++ libcxxabi/trunk/src/cxa_demangle.cpp @@ -1407,117 +1407,6 @@ } }; -template -class arena -{ - static const std::size_t alignment = 16; - alignas(alignment) char buf_[N]; - char* ptr_; - - std::size_t - align_up(std::size_t n) noexcept - {return (n + (alignment-1)) & ~(alignment-1);} - - bool - pointer_in_buffer(char* p) noexcept - {return buf_ <= p && p <= buf_ + N;} - -public: - arena() noexcept : ptr_(buf_) {} - ~arena() {ptr_ = nullptr;} - arena(const arena&) = delete; - arena& operator=(const arena&) = delete; - - char* allocate(std::size_t n); - void deallocate(char* p, std::size_t n) noexcept; - - static constexpr std::size_t size() {return N;} - std::size_t used() const {return static_cast(ptr_ - buf_);} - void reset() {ptr_ = buf_;} -}; - -template -char* -arena::allocate(std::size_t n) -{ - n = align_up(n); - if (static_cast(buf_ + N - ptr_) >= n) - { - char* r = ptr_; - ptr_ += n; - return r; - } - return static_cast(std::malloc(n)); -} - -template -void -arena::deallocate(char* p, std::size_t n) noexcept -{ - if (pointer_in_buffer(p)) - { - n = align_up(n); - if (p + n == ptr_) - ptr_ = p; - } - else - std::free(p); -} - -template -class short_alloc -{ - arena& a_; -public: - typedef T value_type; - -public: - template struct rebind {typedef short_alloc<_Up, N> other;}; - - short_alloc(arena& a) noexcept : a_(a) {} - template - short_alloc(const short_alloc& a) noexcept - : a_(a.a_) {} - short_alloc(const short_alloc&) = default; - short_alloc& operator=(const short_alloc&) = delete; - - T* allocate(std::size_t n) - { - return reinterpret_cast(a_.allocate(n*sizeof(T))); - } - void deallocate(T* p, std::size_t n) noexcept - { - a_.deallocate(reinterpret_cast(p), n*sizeof(T)); - } - - template - friend - bool - operator==(const short_alloc& x, const short_alloc& y) noexcept; - - template friend class short_alloc; -}; - -template -inline -bool -operator==(const short_alloc& x, const short_alloc& y) noexcept -{ - return N == M && &x.a_ == &y.a_; -} - -template -inline -bool -operator!=(const short_alloc& x, const short_alloc& y) noexcept -{ - return !(x == y); -} - -const size_t bs = 4 * 1024; -template using Alloc = short_alloc; -template using Vector = std::vector>; - class BumpPointerAllocator { struct BlockMeta { BlockMeta* Next; @@ -1568,13 +1457,209 @@ } }; +template +class PODSmallVector { + static_assert(std::is_pod::value, + "T is required to be a plain old data type"); + + T* First; + T* Last; + T* Cap; + T Inline[N]; + + bool isInline() const { return First == Inline; } + + void clearInline() { + First = Inline; + Last = Inline; + Cap = Inline + N; + } + + void reserve(size_t NewCap) { + size_t S = size(); + if (isInline()) { + auto* Tmp = static_cast(std::malloc(NewCap * sizeof(T))); + std::copy(First, Last, Tmp); + First = Tmp; + } else + First = static_cast(std::realloc(First, NewCap * sizeof(T))); + Last = First + S; + Cap = First + NewCap; + } + +public: + PODSmallVector() : First(Inline), Last(First), Cap(Inline + N) {} + + PODSmallVector(const PODSmallVector&) = delete; + PODSmallVector& operator=(const PODSmallVector&) = delete; + + PODSmallVector(PODSmallVector&& Other) : PODSmallVector() { + if (Other.isInline()) { + std::copy(Other.begin(), Other.end(), First); + Last = First + Other.size(); + Other.clear(); + return; + } + + First = Other.First; + Last = Other.Last; + Cap = Other.Cap; + Other.clearInline(); + } + + PODSmallVector& operator=(PODSmallVector&& Other) { + if (Other.isInline()) { + if (!isInline()) { + std::free(First); + clearInline(); + } + std::copy(Other.begin(), Other.end(), First); + Last = First + Other.size(); + Other.clear(); + return *this; + } + + if (isInline()) { + First = Other.First; + Last = Other.Last; + Cap = Other.Cap; + Other.clearInline(); + return *this; + } + + std::swap(First, Other.First); + std::swap(Last, Other.Last); + std::swap(Cap, Other.Cap); + Other.clear(); + return *this; + } + + void push_back(const T& Elem) { + if (Last == Cap) + reserve(size() * 2); + *Last++ = Elem; + } + + void pop_back() { + assert(Last != First && "Popping empty vector!"); + --Last; + } + + void dropBack(size_t Index) { + assert(Index <= size() && "dropBack() can't expand!"); + Last = First + Index; + } + + T* begin() { return First; } + T* end() { return Last; } + + bool empty() const { return First == Last; } + size_t size() const { return static_cast(Last - First); } + T& back() { + assert(Last != First && "Calling back() on empty vector!"); + return *(Last - 1); + } + T& operator[](size_t Index) { + assert(Index < size() && "Invalid access!"); + return *(begin() + Index); + } + void clear() { Last = First; } + + ~PODSmallVector() { + if (!isInline()) + std::free(First); + } +}; + +// Substitution table. This type is used to track the substitutions that are +// known by the parser. +template +class SubstitutionTable { + // Substitutions hold the actual entries in the table, and PackIndices tells + // us which entries are members of which pack. For example, if the + // substitutions we're tracking are: {int, {float, FooBar}, char}, with + // {float, FooBar} being a parameter pack, we represent the substitutions as: + // Substitutions: int, float, FooBar, char + // PackIndices: 0, 1, 3 + // So, PackIndicies[I] holds the offset of the begin of the Ith pack, and + // PackIndices[I + 1] holds the offset of the end. + PODSmallVector Substitutions; + PODSmallVector PackIndices; + +public: + // Add a substitution that represents a single name to the table. This is + // modeled as a parameter pack with just one element. + void pushSubstitution(Node* Entry) { + pushPack(); + pushSubstitutionIntoPack(Entry); + } + + // Add a new empty pack to the table. Subsequent calls to + // pushSubstitutionIntoPack() will add to this pack. + void pushPack() { + PackIndices.push_back(static_cast(Substitutions.size())); + } + void pushSubstitutionIntoPack(Node* Entry) { + assert(!PackIndices.empty() && "No pack to push substitution into!"); + Substitutions.push_back(Entry); + } + + // Remove the last pack from the table. + void popPack() { + unsigned Last = PackIndices.back(); + PackIndices.pop_back(); + Substitutions.dropBack(Last); + } + + // For use in a range-for loop. + struct NodeRange { + Node** First; + Node** Last; + Node** begin() { return First; } + Node** end() { return Last; } + }; + + // Retrieve the Nth substitution. This is represented as a range, as the + // substitution could be referring to a parameter pack. + NodeRange nthSubstitution(size_t N) { + assert(PackIndices[N] <= Substitutions.size()); + // The Nth parameter pack starts at offset PackIndices[N], and ends at + // PackIndices[N + 1]. + Node** Begin = Substitutions.begin() + PackIndices[N]; + Node** End; + if (N + 1 != PackIndices.size()) { + assert(PackIndices[N + 1] <= Substitutions.size()); + End = Substitutions.begin() + PackIndices[N + 1]; + } else + End = Substitutions.end(); + assert(Begin <= End); + return NodeRange{Begin, End}; + } + + size_t size() const { return PackIndices.size(); } + bool empty() const { return PackIndices.empty(); } + void clear() { + Substitutions.clear(); + PackIndices.clear(); + } +}; + struct Db { - typedef Vector sub_type; - typedef Vector template_param_type; - sub_type Names; - template_param_type Subs; - Vector TemplateParams; + // Name stack, this is used by the parser to hold temporary names that were + // parsed. The parser colapses multiple names into new nodes to construct + // the AST. Once the parser is finished, names.size() == 1. + PODSmallVector Names; + + // Substitution table. Itanium supports name substitutions as a means of + // compression. The string "S42_" refers to the 42nd entry in this table. + SubstitutionTable<32> Subs; + + // Template parameter table. Like the above, but referenced like "T42_". + // This has a smaller size compared to Subs and Names because it can be + // stored on the stack. + SubstitutionTable<4> TemplateParams; + Qualifiers CV = QualNone; FunctionRefQual RefQuals = FrefQualNone; unsigned EncodingDepth = 0; @@ -1585,13 +1670,6 @@ BumpPointerAllocator ASTAllocator; - template - Db(arena& ar) : - Names(ar), - Subs(0, Names, ar), - TemplateParams(0, Subs, ar) - {} - template T* make(Args&& ...args) { return new (ASTAllocator.allocate(sizeof(T))) @@ -1612,7 +1690,7 @@ assert(FromPosition <= Names.size()); NodeArray res = makeNodeArray( Names.begin() + (long)FromPosition, Names.end()); - Names.erase(Names.begin() + (long)FromPosition, Names.end()); + Names.dropBack(FromPosition); return res; } }; @@ -1801,7 +1879,7 @@ case '_': if (!db.Subs.empty()) { - for (const auto& n : db.Subs.front()) + for (Node* n : db.Subs.nthSubstitution(0)) db.Names.push_back(n); first += 2; } @@ -1828,7 +1906,7 @@ ++sub; if (sub < db.Subs.size()) { - for (const auto& n : db.Subs[sub]) + for (Node* n : db.Subs.nthSubstitution(sub)) db.Names.push_back(n); first = t+1; } @@ -2058,11 +2136,9 @@ { if (first[1] == '_') { - if (db.TemplateParams.empty()) - return first; - if (!db.TemplateParams.back().empty()) + if (!db.TemplateParams.empty()) { - for (auto& t : db.TemplateParams.back().front()) + for (Node *t : db.TemplateParams.nthSubstitution(0)) db.Names.push_back(t); first += 2; } @@ -2082,12 +2158,12 @@ sub *= 10; sub += static_cast(*t - '0'); } - if (t == last || *t != '_' || db.TemplateParams.empty()) + if (t == last || *t != '_') return first; ++sub; - if (sub < db.TemplateParams.back().size()) + if (sub < db.TemplateParams.size()) { - for (auto& temp : db.TemplateParams.back()[sub]) + for (Node *temp : db.TemplateParams.nthSubstitution(sub)) db.Names.push_back(temp); first = t+1; } @@ -2469,7 +2545,7 @@ size_t k1 = db.Names.size(); if (t != first && k1 == k0 + 1) { - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); first = t; } else @@ -2485,7 +2561,7 @@ { if (db.Names.empty()) return first; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); first = t; } break; @@ -2504,7 +2580,7 @@ return first; db.Names.back() = db.make(db.Names.back()); - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); first = t; } } @@ -2959,8 +3035,7 @@ db.Names.begin() + (long)expr_list_begin); auto* conv_expr = db.make( types, expressions); - db.Names.erase( - db.Names.begin() + (long)type_begin, db.Names.end()); + db.Names.dropBack(type_begin); db.Names.push_back(conv_expr); first = t; } @@ -3313,8 +3388,8 @@ if (t1 != t) { if (is_function) - db.Subs.pop_back(); - db.Subs.emplace_back(db.Names.get_allocator()); + db.Subs.popPack(); + db.Subs.pushPack(); for (size_t k = k0; k < k1; ++k) { if (cv) { @@ -3325,7 +3400,7 @@ db.Names[k] = db.make(db.Names[k], cv); } - db.Subs.back().push_back(db.Names[k]); + db.Subs.pushSubstitutionIntoPack(db.Names[k]); } first = t1; } @@ -3350,7 +3425,7 @@ if (db.Names.empty()) return first; first = t; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); } break; case 'C': @@ -3362,7 +3437,7 @@ db.Names.back() = db.make( db.Names.back(), " complex"); first = t; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); } break; case 'F': @@ -3372,7 +3447,7 @@ if (db.Names.empty()) return first; first = t; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); } break; case 'G': @@ -3384,7 +3459,7 @@ db.Names.back() = db.make( db.Names.back(), " imaginary"); first = t; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); } break; case 'M': @@ -3394,7 +3469,7 @@ if (db.Names.empty()) return first; first = t; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); } break; case 'O': @@ -3404,12 +3479,12 @@ size_t k1 = db.Names.size(); if (t != first+1) { - db.Subs.emplace_back(db.Names.get_allocator()); + db.Subs.pushPack(); for (size_t k = k0; k < k1; ++k) { db.Names[k] = db.make(db.Names[k]); - db.Subs.back().push_back(db.Names[k]); + db.Subs.pushSubstitutionIntoPack(db.Names[k]); } first = t; } @@ -3422,11 +3497,11 @@ size_t k1 = db.Names.size(); if (t != first+1) { - db.Subs.emplace_back(db.Names.get_allocator()); + db.Subs.pushPack(); for (size_t k = k0; k < k1; ++k) { db.Names[k] = db.make(db.Names[k]); - db.Subs.back().push_back(db.Names[k]); + db.Subs.pushSubstitutionIntoPack(db.Names[k]); } first = t; } @@ -3439,12 +3514,12 @@ size_t k1 = db.Names.size(); if (t != first+1) { - db.Subs.emplace_back(db.Names.get_allocator()); + db.Subs.pushPack(); for (size_t k = k0; k < k1; ++k) { db.Names[k] = db.make(db.Names[k]); - db.Subs.back().push_back(db.Names[k]); + db.Subs.pushSubstitutionIntoPack(db.Names[k]); } first = t; } @@ -3457,9 +3532,9 @@ size_t k1 = db.Names.size(); if (t != first) { - db.Subs.emplace_back(db.Names.get_allocator()); + db.Subs.pushPack(); for (size_t k = k0; k < k1; ++k) - db.Subs.back().push_back(db.Names[k]); + db.Subs.pushSubstitutionIntoPack(db.Names[k]); if (db.TryToParseTemplateArgs && k1 == k0+1) { const char* t1 = parse_template_args(t, last, db); @@ -3470,9 +3545,7 @@ db.Names.back() = db.make< NameWithTemplateArgs>( db.Names.back(), args); - db.Subs.push_back(Db::sub_type( - 1, db.Names.back(), - db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); t = t1; } } @@ -3512,7 +3585,7 @@ db.Names.push_back(db.make(type, proto)); } } - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); first = t2; } } @@ -3526,7 +3599,7 @@ { if (db.Names.empty()) return first; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); first = t; } } @@ -3551,7 +3624,7 @@ NameWithTemplateArgs>( db.Names.back(), template_args); // Need to create substitution for - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); first = t; } } @@ -3570,9 +3643,9 @@ size_t k1 = db.Names.size(); if (t != first+2) { - db.Subs.emplace_back(db.Names.get_allocator()); + db.Subs.pushPack(); for (size_t k = k0; k < k1; ++k) - db.Subs.back().push_back(db.Names[k]); + db.Subs.pushSubstitutionIntoPack(db.Names[k]); first = t; return first; } @@ -3585,7 +3658,7 @@ { if (db.Names.empty()) return first; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); first = t; return first; } @@ -3596,7 +3669,7 @@ { if (db.Names.empty()) return first; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); first = t; return first; } @@ -3619,7 +3692,7 @@ { if (db.Names.empty()) return first; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); first = t; } } @@ -5119,26 +5192,32 @@ if (last - first >= 2 && *first == 'I') { if (db.TagTemplates) - db.TemplateParams.back().clear(); + db.TemplateParams.clear(); const char* t = first+1; size_t begin_idx = db.Names.size(); while (*t != 'E') { if (db.TagTemplates) - db.TemplateParams.emplace_back(db.Names.get_allocator()); + { + auto TmpParams = std::move(db.TemplateParams); + size_t k0 = db.Names.size(); + const char* t1 = parse_template_arg(t, last, db); + size_t k1 = db.Names.size(); + db.TemplateParams = std::move(TmpParams); + + if (t1 == t || t1 == last || k0 > k1) + return first; + db.TemplateParams.pushPack(); + for (size_t k = k0; k < k1; ++k) + db.TemplateParams.pushSubstitutionIntoPack(db.Names[k]); + t = t1; + continue; + } size_t k0 = db.Names.size(); const char* t1 = parse_template_arg(t, last, db); size_t k1 = db.Names.size(); - if (db.TagTemplates) - db.TemplateParams.pop_back(); if (t1 == t || t1 == last || k0 > k1) - return first; - if (db.TagTemplates) - { - db.TemplateParams.back().emplace_back(db.Names.get_allocator()); - for (size_t k = k0; k < k1; ++k) - db.TemplateParams.back().back().push_back(db.Names[k]); - } + return first; t = t1; } if (begin_idx > db.Names.size()) @@ -5218,9 +5297,7 @@ { db.Names.back() = db.make( db.Names.back(), name); - db.Subs.push_back( - Db::sub_type(1, db.Names.back(), - db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); } else db.Names.back() = name; @@ -5243,7 +5320,7 @@ db.make(db.Names.back(), name); else db.Names.back() = name; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); pop_subs = true; t0 = t1; } @@ -5265,7 +5342,7 @@ db.make(db.Names.back(), name); else db.Names.back() = name; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); pop_subs = true; t0 = t1; } @@ -5282,8 +5359,7 @@ db.Names.pop_back(); db.Names.back() = db.make( db.Names.back(), name); - db.Subs.push_back(Db::sub_type( - 1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); t0 = t1; component_ends_with_template_args = true; } @@ -5308,7 +5384,7 @@ db.make(db.Names.back(), name); else db.Names.back() = name; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); pop_subs = true; t0 = t1; } @@ -5319,7 +5395,7 @@ first = t0 + 1; db.CV = cv; if (pop_subs && !db.Subs.empty()) - db.Subs.pop_back(); + db.Subs.popPack(); if (ends_with_template_args) *ends_with_template_args = component_ends_with_template_args; } @@ -5484,7 +5560,7 @@ { if (db.Names.empty()) return first; - db.Subs.push_back(Db::sub_type(1, db.Names.back(), db.Names.get_allocator())); + db.Subs.pushSubstitution(db.Names.back()); t0 = t1; t1 = parse_template_args(t0, last, db); if (t1 != t0) @@ -6035,16 +6111,14 @@ } size_t internal_size = buf != nullptr ? *n : 0; - arena a; - Db db(a); - db.TemplateParams.emplace_back(a); + Db db; int internal_status = success; size_t len = std::strlen(mangled_name); demangle(mangled_name, mangled_name + len, db, internal_status); if (internal_status == success && db.FixForwardReferences && - !db.TemplateParams.empty() && !db.TemplateParams.front().empty()) + !db.TemplateParams.empty()) { db.FixForwardReferences = false; db.TagTemplates = false; Index: libcxxabi/trunk/test/unittest_demangle.pass.cpp =================================================================== --- libcxxabi/trunk/test/unittest_demangle.pass.cpp +++ libcxxabi/trunk/test/unittest_demangle.pass.cpp @@ -0,0 +1,123 @@ +//===----------------------- unittest_demangle.cpp ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../src/cxa_demangle.cpp" + +using namespace __cxxabiv1; + +void testPODSmallVector() { + { // {push/pop}_back + PODSmallVector PSV; + PSV.push_back(0); + PSV.push_back(1); + PSV.push_back(2); + PSV.push_back(3); + for (int i = 0; i < 4; ++i) + assert(PSV[i] == i); + PSV.pop_back(); + for (int i = 0; i < 3; ++i) + assert(PSV[i] == i); + PSV.pop_back(); + PSV.pop_back(); + assert(!PSV.empty() && PSV.size() == 1); + PSV.pop_back(); + assert(PSV.empty() && PSV.size() == 0); + } + + { + PODSmallVector PSV1; + PSV1.push_back(1); + PSV1.push_back(2); + PSV1.push_back(3); + + PODSmallVector PSV2; + std::swap(PSV1, PSV2); + assert(PSV1.size() == 0); + assert(PSV2.size() == 3); + int i = 1; + for (int x : PSV2) { + assert(x == i); + ++i; + } + assert(i == 4); + std::swap(PSV1, PSV2); + assert(PSV1.size() == 3); + assert(PSV2.size() == 0); + i = 1; + for (int x : PSV1) { + assert(x == i); + ++i; + } + assert(i == 4); + } + + { + PODSmallVector PSV1; + PODSmallVector PSV2; + PSV1.push_back(0); + PSV1.push_back(1); + PSV1.push_back(2); + assert(PSV1.size() == 3); + assert(PSV2.size() == 0); + std::swap(PSV1, PSV2); + assert(PSV1.size() == 0); + assert(PSV2.size() == 3); + int i = 0; + for (int x : PSV2) { + assert(x == i); + ++i; + } + for (int x : PSV1) { + assert(false); + (void)x; + } + } +} + +void testSubstitutionTable() { + { + SubstitutionTable<2> Tab; + + NameType Names[] = {{"MERP"}, {"MARP"}, {"MAMP"}}; + Tab.pushPack(); + Tab.pushSubstitutionIntoPack(&Names[0]); + Tab.pushSubstitutionIntoPack(&Names[1]); + Tab.pushSubstitutionIntoPack(&Names[2]); + + int Index = 0; + for (Node* N : Tab.nthSubstitution(0)) { + assert(static_cast(N)->getName() == Names[Index].getName()); + ++Index; + } + assert(Index == 3); + + Tab.popPack(); + assert(Tab.empty() && Tab.size() == 0); + Tab.pushSubstitution(&Names[0]); + Tab.pushSubstitution(&Names[1]); + assert(!Tab.empty() && Tab.size() == 2); + + int I = 0; + for (Node* N : Tab.nthSubstitution(0)) { + assert(static_cast(N)->getName() == "MERP"); + assert(I == 0); + ++I; + } + for (Node* N : Tab.nthSubstitution(1)) { + assert(static_cast(N)->getName() == "MARP"); + assert(I == 1); + ++I; + } + } +} + +int main() { + testPODSmallVector(); + testSubstitutionTable(); +}