diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 index 8412149..81a5cef 100644 --- a/build/Jamfile.v2 +++ b/build/Jamfile.v2 @@ -27,27 +27,33 @@ project boost/coroutine : source-location ../src ; -alias allocator_sources - : detail/standard_stack_allocator_windows.cpp +alias protected_allocator_sources + : protected_stack_allocator_windows.cpp : windows ; -alias allocator_sources - : detail/standard_stack_allocator_posix.cpp - detail/segmented_stack_allocator.cpp - : on +alias protected_allocator_sources + : protected_stack_allocator_posix.cpp ; -alias allocator_sources - : detail/standard_stack_allocator_posix.cpp +explicit protected_allocator_sources ; + +alias segmented_allocator_sources + : segmented_stack_allocator.cpp + : on ; -explicit allocator_sources ; +alias segmented_allocator_sources + ; + +explicit segmented_allocator_sources ; lib boost_coroutine - : allocator_sources - detail/coroutine_context.cpp + : detail/coroutine_context.cpp exceptions.cpp + simple_stack_allocator.cpp + protected_allocator_sources + segmented_allocator_sources : shared:../../context/build//boost_context shared:../../system/build//boost_system shared:../../thread/build//boost_thread diff --git a/doc/coro.qbk b/doc/coro.qbk index 66853c8..eda668f 100644 --- a/doc/coro.qbk +++ b/doc/coro.qbk @@ -10,6 +10,7 @@ [authors [Kowalke, Oliver]] [copyright 2009 Oliver Kowalke] [purpose C++ Library providing coroutine facility] + [id coroutine] [category text] [license Distributed under the Boost Software License, Version 1.0. @@ -69,13 +70,16 @@ [def __getline__ ['std::getline()]] [def __handle_read__ ['session::handle_read()]] [def __io_service__ ['boost::asio::io_sevice]] +[def __protected_allocator__ ['boost::coroutines::protected_stack_allocator]] [def __pull_coro_bool__ ['boost::coroutines::coroutine<>::pull_type::operator bool]] [def __pull_coro_get__ ['boost::coroutines::coroutine<>::pull_type::get()]] [def __pull_coro_it__ ['boost::coroutines::coroutine<>::pull_type::iterator]] [def __pull_coro_op__ ['boost::coroutines::coroutine<>::pull_type::operator()]] [def __push_coro_op__ ['boost::coroutines::coroutine<>::push_type::operator()]] +[def __segmented_allocator__ ['boost::coroutines::segmented_stack_allocator]] [def __server__ ['server]] [def __session__ ['session]] +[def __simple_allocator__ ['boost::coroutines::simple_stack_allocator]] [def __stack_context__ ['boost::coroutines::stack_context]] [def __start__ ['session::start()]] [def __thread__ ['boost::thread]] diff --git a/doc/stack.qbk b/doc/stack.qbk index 5a38c33..91a5e45 100644 --- a/doc/stack.qbk +++ b/doc/stack.qbk @@ -45,19 +45,23 @@ __coro__.] top of the stack (growing downwards) or the bottom of the stack (growing upwards).] +class __coro_allocator__ is a typedef of __protected_allocator__. -[section:stack_allocator Class ['stack_allocator]] -__boost_coroutine__ provides the class __coro_allocator__ which models +[section:protected_stack_allocator Class ['protected_stack_allocator]] + +__boost_coroutine__ provides the class __protected_allocator__ which models the __stack_allocator_concept__. It appends a guard page at the end of each stack to protect against exceeding the stack. If the guard page is accessed (read or write operation) a segmentation fault/access violation is generated by the operating system. +[important Using __protected_allocator__ is expensive.] + [note The appended `guard page` is [*not] mapped to physical memory, only virtual addresses are used.] - class stack_allocator + class protected_stack_allocator { static bool is_stack_unbound(); @@ -74,13 +78,85 @@ virtual addresses are used.] [heading `static bool is_stack_unbound()`] [variablelist -[[Returns:] [Returns `true` if the environment defines no limit for the size of a stack.]] +[[Returns:] [Returns `true` if the environment defines no limit for the size of +a stack.]] ] [heading `static std::size_t maximum_stacksize()`] [variablelist [[Preconditions:] [`is_stack_unbound()` returns `false`.]] -[[Returns:] [Returns the maximum size in bytes of stack defined by the environment.]] +[[Returns:] [Returns the maximum size in bytes of stack defined by the +environment.]] +] + +[heading `static std::size_t default_stacksize()`] +[variablelist +[[Returns:] [Returns a default stack size, which may be platform specific. +If the stack is unbound then the present implementation returns the maximum of +`64 kB` and `minimum_stacksize()`.]] +] + +[heading `static std::size_t minimum_stacksize()`] +[variablelist +[[Returns:] [Returns the minimum size in bytes of stack defined by the +environment (Win32 4kB/Win64 8kB, defined by rlimit on POSIX).]] +] + +[heading `void allocate( stack_context & sctx, std::size_t size)`] +[variablelist +[[Preconditions:] [`minimum_stacksize() > size` and +`! is_stack_unbound() && ( maximum_stacksize() < size)`.]] +[[Effects:] [Allocates memory of at least `size` Bytes and stores a pointer +to the stack and its actual size in `sctx`.]] +[[Returns:] [Returns pointer to the start address of the new stack. Depending +on the architecture the stack grows downwards/upwards the returned address is +the highest/lowest address of the stack.]] +] + +[heading `void deallocate( stack_context & sctx)`] +[variablelist +[[Preconditions:] [`sctx.sp` is valid, `minimum_stacksize() > sctx.size` and +`! is_stack_unbound() && ( maximum_stacksize() < size)`.]] +[[Effects:] [Deallocates the stack space.]] +] + +[endsect] + + +[section:simple_stack_allocator Class ['simple_stack_allocator]] + +__boost_coroutine__ provides the class __simple_allocator__ which models +the __stack_allocator_concept__. +In contrast to __protected_allocator__ it does not append a guard page at the +end of each stack. The memory is simply managed by `std::calloc()` and +`std::free()`. + + class simple_stack_allocator + { + static bool is_stack_unbound(); + + static std::size_t maximum_stacksize(); + + static std::size_t default_stacksize(); + + static std::size_t minimum_stacksize(); + + void allocate( stack_context &, std::size_t size); + + void deallocate( stack_context &); + } + +[heading `static bool is_stack_unbound()`] +[variablelist +[[Returns:] [Returns `true` if the environment defines no limit for the size of +a stack.]] +] + +[heading `static std::size_t maximum_stacksize()`] +[variablelist +[[Preconditions:] [`is_stack_unbound()` returns `false`.]] +[[Returns:] [Returns the maximum size in bytes of stack defined by the +environment.]] ] [heading `static std::size_t default_stacksize()`] diff --git a/include/boost/coroutine/all.hpp b/include/boost/coroutine/all.hpp index 96327d6..0c8b760 100644 --- a/include/boost/coroutine/all.hpp +++ b/include/boost/coroutine/all.hpp @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include #endif // BOOST_COROUTINES_ALL_H diff --git a/include/boost/coroutine/detail/standard_stack_allocator.hpp b/include/boost/coroutine/protected_stack_allocator.hpp similarity index 75% rename from include/boost/coroutine/detail/standard_stack_allocator.hpp rename to include/boost/coroutine/protected_stack_allocator.hpp index 9c207d5..73bb978 100644 --- a/include/boost/coroutine/detail/standard_stack_allocator.hpp +++ b/include/boost/coroutine/protected_stack_allocator.hpp @@ -4,8 +4,8 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_COROUTINES_DETAIL_STANDARD_STACK_ALLOCATOR_H -#define BOOST_COROUTINES_DETAIL_STANDARD_STACK_ALLOCATOR_H +#ifndef BOOST_COROUTINES_PROTECTED_STACK_ALLOCATOR_H +#define BOOST_COROUTINES_PROTECTED_STACK_ALLOCATOR_H #include @@ -22,9 +22,7 @@ namespace coroutines { struct stack_context; -namespace detail { - -class BOOST_COROUTINES_DECL standard_stack_allocator +class BOOST_COROUTINES_DECL protected_stack_allocator { public: static bool is_stack_unbound(); @@ -40,10 +38,10 @@ public: void deallocate( stack_context &); }; -}}} +}} #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_SUFFIX #endif -#endif // BOOST_COROUTINES_DETAIL_STANDARD_STACK_ALLOCATOR_H +#endif // BOOST_COROUTINES_PROTECTED_STACK_ALLOCATOR_H diff --git a/include/boost/coroutine/detail/segmented_stack_allocator.hpp b/include/boost/coroutine/segmented_stack_allocator.hpp similarity index 81% rename from include/boost/coroutine/detail/segmented_stack_allocator.hpp rename to include/boost/coroutine/segmented_stack_allocator.hpp index 73d33b1..984fedb 100644 --- a/include/boost/coroutine/detail/segmented_stack_allocator.hpp +++ b/include/boost/coroutine/segmented_stack_allocator.hpp @@ -4,8 +4,8 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_COROUTINES_DETAIL_SEGMENTED_STACK_ALLOCATOR_H -#define BOOST_COROUTINES_DETAIL_SEGMENTED_STACK_ALLOCATOR_H +#ifndef BOOST_COROUTINES_SEGMENTED_STACK_ALLOCATOR_H +#define BOOST_COROUTINES_SEGMENTED_STACK_ALLOCATOR_H #include @@ -22,8 +22,6 @@ namespace coroutines { struct stack_context; -namespace detail { - #if defined(BOOST_USE_SEGMENTED_STACKS) class BOOST_COROUTINES_DECL segmented_stack_allocator { @@ -42,10 +40,10 @@ public: }; #endif -}}} +}} #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_SUFFIX #endif -#endif // BOOST_COROUTINES_DETAIL_SEGMENTED_STACK_ALLOCATOR_H +#endif // BOOST_COROUTINES_SEGMENTED_STACK_ALLOCATOR_H diff --git a/include/boost/coroutine/simple_stack_allocator.hpp b/include/boost/coroutine/simple_stack_allocator.hpp new file mode 100644 index 0000000..c0a4ab7 --- /dev/null +++ b/include/boost/coroutine/simple_stack_allocator.hpp @@ -0,0 +1,47 @@ + +// Copyright Oliver Kowalke 2009. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_COROUTINES_SIMPLE_STACK_ALLOCATOR_H +#define BOOST_COROUTINES_SIMPLE_STACK_ALLOCATOR_H + +#include + +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines { + +struct stack_context; + +class BOOST_COROUTINES_DECL simple_stack_allocator +{ +public: + static bool is_stack_unbound(); + + static std::size_t maximum_stacksize(); + + static std::size_t default_stacksize(); + + static std::size_t minimum_stacksize(); + + void allocate( stack_context & ctx, std::size_t size); + + void deallocate( stack_context & ctx); +}; + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_COROUTINES_SIMPLE_STACK_ALLOCATOR_H diff --git a/include/boost/coroutine/stack_allocator.hpp b/include/boost/coroutine/stack_allocator.hpp index 3b52679..9238628 100644 --- a/include/boost/coroutine/stack_allocator.hpp +++ b/include/boost/coroutine/stack_allocator.hpp @@ -4,16 +4,17 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_COROUTINES_DETAIL_STACK_ALLOCATOR_H -#define BOOST_COROUTINES_DETAIL_STACK_ALLOCATOR_H +#ifndef BOOST_COROUTINES_STACK_ALLOCATOR_H +#define BOOST_COROUTINES_STACK_ALLOCATOR_H #include #include #include -#include -#include +#include +#include +#include #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX @@ -23,9 +24,9 @@ namespace boost { namespace coroutines { #if defined(BOOST_USE_SEGMENTED_STACKS) -typedef detail::segmented_stack_allocator stack_allocator; +typedef segmented_stack_allocator stack_allocator; #else -typedef detail::standard_stack_allocator stack_allocator; +typedef protected_stack_allocator stack_allocator; #endif }} @@ -34,4 +35,4 @@ typedef detail::standard_stack_allocator stack_allocator; # include BOOST_ABI_SUFFIX #endif -#endif // BOOST_COROUTINES_DETAIL_STACK_ALLOCATOR_H +#endif // BOOST_COROUTINES_STACK_ALLOCATOR_H diff --git a/src/detail/standard_stack_allocator_posix.cpp b/src/protected_stack_allocator_posix.cpp similarity index 81% rename from src/detail/standard_stack_allocator_posix.cpp rename to src/protected_stack_allocator_posix.cpp index 752a6e6..dfafe3b 100644 --- a/src/detail/standard_stack_allocator_posix.cpp +++ b/src/protected_stack_allocator_posix.cpp @@ -4,7 +4,7 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#include "boost/coroutine/detail/standard_stack_allocator.hpp" +#include "boost/coroutine/protected_stack_allocator.hpp" extern "C" { #include @@ -41,7 +41,6 @@ extern "C" { namespace boost { namespace coroutines { -namespace detail { void pagesize_( std::size_t * size) { @@ -85,11 +84,11 @@ std::size_t page_count( std::size_t stacksize) } bool -standard_stack_allocator::is_stack_unbound() -{ return RLIM_INFINITY == detail::stacksize_limit().rlim_max; } +protected_stack_allocator::is_stack_unbound() +{ return RLIM_INFINITY == stacksize_limit().rlim_max; } std::size_t -standard_stack_allocator::default_stacksize() +protected_stack_allocator::default_stacksize() { std::size_t size = 8 * minimum_stacksize(); if ( is_stack_unbound() ) return size; @@ -101,25 +100,25 @@ standard_stack_allocator::default_stacksize() } std::size_t -standard_stack_allocator::minimum_stacksize() +protected_stack_allocator::minimum_stacksize() { return SIGSTKSZ + sizeof( context::fcontext_t) + 15; } std::size_t -standard_stack_allocator::maximum_stacksize() +protected_stack_allocator::maximum_stacksize() { BOOST_ASSERT( ! is_stack_unbound() ); - return static_cast< std::size_t >( detail::stacksize_limit().rlim_max); + return static_cast< std::size_t >( stacksize_limit().rlim_max); } void -standard_stack_allocator::allocate( stack_context & ctx, std::size_t size) +protected_stack_allocator::allocate( stack_context & ctx, std::size_t size) { BOOST_ASSERT( minimum_stacksize() <= size); BOOST_ASSERT( is_stack_unbound() || ( maximum_stacksize() >= size) ); - const std::size_t pages( detail::page_count( size) ); // page at bottom will be used as guard-page + const std::size_t pages( page_count( size) ); // page at bottom will be used as guard-page BOOST_ASSERT_MSG( 2 <= pages, "at least two pages must fit into stack (one page is guard-page)"); - const std::size_t size_( pages * detail::pagesize() ); + const std::size_t size_( pages * pagesize() ); BOOST_ASSERT( 0 < size && 0 < size_); BOOST_ASSERT( size_ <= size); @@ -139,9 +138,9 @@ standard_stack_allocator::allocate( stack_context & ctx, std::size_t size) // conforming to POSIX.1-2001 #if defined(BOOST_DISABLE_ASSERTS) - ::mprotect( limit, detail::pagesize(), PROT_NONE); + ::mprotect( limit, pagesize(), PROT_NONE); #else - const int result( ::mprotect( limit, detail::pagesize(), PROT_NONE) ); + const int result( ::mprotect( limit, pagesize(), PROT_NONE) ); BOOST_ASSERT( 0 == result); #endif @@ -150,7 +149,7 @@ standard_stack_allocator::allocate( stack_context & ctx, std::size_t size) } void -standard_stack_allocator::deallocate( stack_context & ctx) +protected_stack_allocator::deallocate( stack_context & ctx) { BOOST_ASSERT( ctx.sp); BOOST_ASSERT( minimum_stacksize() <= ctx.size); @@ -161,7 +160,7 @@ standard_stack_allocator::deallocate( stack_context & ctx) ::munmap( limit, ctx.size); } -}}} +}} #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_SUFFIX diff --git a/src/detail/standard_stack_allocator_windows.cpp b/src/protected_stack_allocator_windows.cpp similarity index 82% rename from src/detail/standard_stack_allocator_windows.cpp rename to src/protected_stack_allocator_windows.cpp index 6351c03..5612bb9 100644 --- a/src/detail/standard_stack_allocator_windows.cpp +++ b/src/protected_stack_allocator_windows.cpp @@ -4,7 +4,7 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#include "boost/coroutine/detail/standard_stack_allocator.hpp" +#include "boost/coroutine/protected_stack_allocator.hpp" extern "C" { #include @@ -49,7 +49,6 @@ extern "C" { namespace boost { namespace coroutines { -namespace detail { void system_info_( SYSTEM_INFO * si) { ::GetSystemInfo( si); } @@ -74,11 +73,11 @@ std::size_t page_count( std::size_t stacksize) // Windows seams not to provide a limit for the stacksize bool -standard_stack_allocator::is_stack_unbound() +protected_stack_allocator::is_stack_unbound() { return true; } std::size_t -standard_stack_allocator::default_stacksize() +protected_stack_allocator::default_stacksize() { std::size_t size = 64 * 1024; // 64 kB if ( is_stack_unbound() ) @@ -92,27 +91,27 @@ standard_stack_allocator::default_stacksize() // because Windows seams not to provide a limit for minimum stacksize std::size_t -standard_stack_allocator::minimum_stacksize() +protected_stack_allocator::minimum_stacksize() { return MIN_STACKSIZE; } // because Windows seams not to provide a limit for maximum stacksize // maximum_stacksize() can never be called (pre-condition ! is_stack_unbound() ) std::size_t -standard_stack_allocator::maximum_stacksize() +protected_stack_allocator::maximum_stacksize() { BOOST_ASSERT( ! is_stack_unbound() ); return 1 * 1024 * 1024 * 1024; // 1GB } void -standard_stack_allocator::allocate( stack_context & ctx, std::size_t size) +protected_stack_allocator::allocate( stack_context & ctx, std::size_t size) { BOOST_ASSERT( minimum_stacksize() <= size); BOOST_ASSERT( is_stack_unbound() || ( maximum_stacksize() >= size) ); - const std::size_t pages( detail::page_count( size) ); // page at bottom will be used as guard-page + const std::size_t pages( page_count( size) ); // page at bottom will be used as guard-page BOOST_ASSERT_MSG( 2 <= pages, "at least two pages must fit into stack (one page is guard-page)"); - const std::size_t size_ = pages * detail::pagesize(); + const std::size_t size_ = pages * pagesize(); BOOST_ASSERT( 0 < size && 0 < size_); void * limit = ::VirtualAlloc( 0, size_, MEM_COMMIT, PAGE_READWRITE); @@ -123,10 +122,10 @@ standard_stack_allocator::allocate( stack_context & ctx, std::size_t size) DWORD old_options; #if defined(BOOST_DISABLE_ASSERTS) ::VirtualProtect( - limit, detail::pagesize(), PAGE_READWRITE | PAGE_GUARD /*PAGE_NOACCESS*/, & old_options); + limit, pagesize(), PAGE_READWRITE | PAGE_GUARD /*PAGE_NOACCESS*/, & old_options); #else const BOOL result = ::VirtualProtect( - limit, detail::pagesize(), PAGE_READWRITE | PAGE_GUARD /*PAGE_NOACCESS*/, & old_options); + limit, pagesize(), PAGE_READWRITE | PAGE_GUARD /*PAGE_NOACCESS*/, & old_options); BOOST_ASSERT( FALSE != result); #endif @@ -135,7 +134,7 @@ standard_stack_allocator::allocate( stack_context & ctx, std::size_t size) } void -standard_stack_allocator::deallocate( stack_context & ctx) +protected_stack_allocator::deallocate( stack_context & ctx) { BOOST_ASSERT( ctx.sp); BOOST_ASSERT( minimum_stacksize() <= ctx.size); @@ -145,7 +144,7 @@ standard_stack_allocator::deallocate( stack_context & ctx) ::VirtualFree( limit, 0, MEM_RELEASE); } -}}} +}} #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_SUFFIX diff --git a/src/detail/segmented_stack_allocator.cpp b/src/segmented_stack_allocator.cpp similarity index 94% rename from src/detail/segmented_stack_allocator.cpp rename to src/segmented_stack_allocator.cpp index 732e230..3ca8975 100644 --- a/src/detail/segmented_stack_allocator.cpp +++ b/src/segmented_stack_allocator.cpp @@ -4,15 +4,15 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#include +#include #include #include #include +// forward declaration for splitstack-functions defined in libgcc extern "C" { - void *__splitstack_makecontext( std::size_t, void * [BOOST_COROUTINES_SEGMENTS], std::size_t *); @@ -36,7 +36,6 @@ void __splitstack_block_signals_context( void * [BOOST_COROUTINES_SEGMENTS], namespace boost { namespace coroutines { -namespace detail { bool segmented_stack_allocator::is_stack_unbound() @@ -74,7 +73,7 @@ segmented_stack_allocator::deallocate( stack_context & ctx) __splitstack_releasecontext( ctx.segments_ctx); } -}}} +}} #ifdef UDEF_SIGSTKSZ # undef SIGSTKSZ diff --git a/src/simple_stack_allocator.cpp b/src/simple_stack_allocator.cpp new file mode 100644 index 0000000..e372047 --- /dev/null +++ b/src/simple_stack_allocator.cpp @@ -0,0 +1,68 @@ + +// Copyright Oliver Kowalke 2009. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include + +#include +#include + +#include + +#include +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace coroutines { + +bool +simple_stack_allocator::is_stack_unbound() +{ return protected_stack_allocator::is_stack_unbound(); } + +std::size_t +simple_stack_allocator::maximum_stacksize() +{ return protected_stack_allocator::maximum_stacksize(); } + +std::size_t +simple_stack_allocator::default_stacksize() +{ return protected_stack_allocator::default_stacksize(); } + +std::size_t +simple_stack_allocator::minimum_stacksize() +{ return protected_stack_allocator::minimum_stacksize(); } + +void +simple_stack_allocator::allocate( stack_context & ctx, std::size_t size) +{ + BOOST_ASSERT( minimum_stacksize() <= size); + BOOST_ASSERT( is_stack_unbound() || ( maximum_stacksize() >= size) ); + + void * limit = std::calloc( size, sizeof( char) ); + if ( ! limit) throw std::bad_alloc(); + + ctx.size = size; + ctx.sp = static_cast< char * >( limit) + ctx.size; +} + +void +simple_stack_allocator::deallocate( stack_context & ctx) +{ + BOOST_ASSERT( ctx.sp); + BOOST_ASSERT( minimum_stacksize() <= ctx.size); + BOOST_ASSERT( is_stack_unbound() || ( maximum_stacksize() >= ctx.size) ); + + void * limit = static_cast< char * >( ctx.sp) - ctx.size; + std::free( limit); +} + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif