xo-alloc2: + Allocator.alloc_range() with DArena input
This commit is contained in:
parent
d8ed0d6235
commit
181ae9f12b
13 changed files with 158 additions and 59 deletions
|
|
@ -37,4 +37,4 @@ namespace xo {
|
||||||
} /*namespace mm*/
|
} /*namespace mm*/
|
||||||
} /*namespace xo*/
|
} /*namespace xo*/
|
||||||
|
|
||||||
/* end AllocIterator.hpp */
|
/* end AAllocIterator.hpp */
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@
|
||||||
|
|
||||||
#include "AllocError.hpp"
|
#include "AllocError.hpp"
|
||||||
#include "AllocInfo.hpp"
|
#include "AllocInfo.hpp"
|
||||||
#include "xo/facet/facet_implementation.hpp"
|
#include "AllocIterator.hpp"
|
||||||
#include "xo/facet/typeseq.hpp"
|
#include <xo/facet/obj.hpp>
|
||||||
|
#include <xo/facet/facet_implementation.hpp>
|
||||||
|
#include <xo/facet/typeseq.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace xo {
|
namespace xo {
|
||||||
|
|
@ -32,6 +34,8 @@ namespace xo {
|
||||||
using value_type = std::byte *;
|
using value_type = std::byte *;
|
||||||
/** object header, if configured **/
|
/** object header, if configured **/
|
||||||
using header_type = std::uint64_t;
|
using header_type = std::uint64_t;
|
||||||
|
/** iterator range. These are forward iterators over allocs **/
|
||||||
|
using range_type = std::pair<obj<AAllocIterator>, obj<AAllocIterator>>;
|
||||||
///@}
|
///@}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -85,20 +89,21 @@ namespace xo {
|
||||||
* Non-const @p d because may stash error details
|
* Non-const @p d because may stash error details
|
||||||
**/
|
**/
|
||||||
virtual AllocInfo alloc_info(Copaque d, value_type mem) const noexcept = 0;
|
virtual AllocInfo alloc_info(Copaque d, value_type mem) const noexcept = 0;
|
||||||
/** Ideally we want to control allocation for iterator here.
|
/** Ideally we want to control allocator for iterator here.
|
||||||
* Awkward to describe to compiler since we don't have vt<AAllocator> yet.
|
* Awkward to supply to compiler since we don't have obj<AAllocator> yet.
|
||||||
* OTOH iteration over allocs is a niche feature.
|
* OTOH iteration over allocs is a super-niche feature.
|
||||||
* Consider alternatives:
|
*
|
||||||
|
* Rejected alternatives:
|
||||||
* - put begin/end in separate interface. e.g. extend AAllocator
|
* - put begin/end in separate interface. e.g. extend AAllocator
|
||||||
* - layer of indirection: begin/end return iterator factory.
|
* - layer of indirection: begin/end return iterator factory.
|
||||||
* Then allocator can be passed to iterator factory separately.
|
* Then allocator can be passed to iterator factory separately.
|
||||||
* Helps because factory can be static
|
* Helps because factory can be static
|
||||||
* - abandon allocator support in this case. Instead will need to
|
* - abandon allocator support in this case. Instead will need to
|
||||||
* reinstate uvt<AAllocIterator> (unique variant), use heap
|
* reinstate uvt<AAllocIterator> (unique variant), use heap
|
||||||
* - just pass DArena& for alloc-iterator-allocation
|
*
|
||||||
|
* @p mm is allocator for resulting iterator range
|
||||||
**/
|
**/
|
||||||
//virtual facet::vt<AAllocIterator> begin(Copaque d, DArena & ialloc) const noexcept;
|
virtual range_type alloc_range(Copaque d, DArena & mm) const noexcept = 0;
|
||||||
// virtual obj<AAllocIterator> end() const noexcept = 0;
|
|
||||||
|
|
||||||
/** expand committed space in arena @p d
|
/** expand committed space in arena @p d
|
||||||
* to size at least @p z
|
* to size at least @p z
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
/** @file AllocIterator.hpp
|
|
||||||
*
|
|
||||||
* @author Roland Conybeare, Dec 2025
|
|
||||||
**/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace xo {
|
|
||||||
namespace mm {
|
|
||||||
|
|
||||||
/** @class AllocIterator
|
|
||||||
* @brief iterator over arena allocations.
|
|
||||||
*
|
|
||||||
* Intended for instrumentation/diagnostics.
|
|
||||||
* Not needed for normal operator
|
|
||||||
**/
|
|
||||||
struct AllocIterator {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
} /*namespace mm*/
|
|
||||||
} /*namespace xo*/
|
|
||||||
|
|
||||||
/* end AllocIterator.hpp */
|
|
||||||
|
|
@ -45,6 +45,7 @@ namespace xo {
|
||||||
[[noreturn]] AllocInfo alloc_info(Copaque, value_type) const noexcept override { _fatal(); }
|
[[noreturn]] AllocInfo alloc_info(Copaque, value_type) const noexcept override { _fatal(); }
|
||||||
// defn in .cpp - problematic to require compiler know vt<AAllocIterator> defn here
|
// defn in .cpp - problematic to require compiler know vt<AAllocIterator> defn here
|
||||||
//[[noreturn]] facet::vt<AAllocIterator> begin(Copaque, DArena &) const noexcept override; // { _fatal(); }
|
//[[noreturn]] facet::vt<AAllocIterator> begin(Copaque, DArena &) const noexcept override; // { _fatal(); }
|
||||||
|
[[noreturn]] range_type alloc_range(Copaque, DArena &) const noexcept override { _fatal(); }
|
||||||
|
|
||||||
// non-const methods
|
// non-const methods
|
||||||
[[noreturn]] bool expand(Opaque, std::size_t) const noexcept override { _fatal(); }
|
[[noreturn]] bool expand(Opaque, std::size_t) const noexcept override { _fatal(); }
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,13 @@ namespace xo {
|
||||||
return I::contains(_dcast(d), p);
|
return I::contains(_dcast(d), p);
|
||||||
}
|
}
|
||||||
AllocError last_error(Copaque d) const noexcept override { return I::last_error(_dcast(d)); }
|
AllocError last_error(Copaque d) const noexcept override { return I::last_error(_dcast(d)); }
|
||||||
|
|
||||||
// non-const methods
|
|
||||||
|
|
||||||
AllocInfo alloc_info(Copaque d, value_type mem) const noexcept override {
|
AllocInfo alloc_info(Copaque d, value_type mem) const noexcept override {
|
||||||
return I::alloc_info(_dcast(d), mem);
|
return I::alloc_info(_dcast(d), mem);
|
||||||
}
|
}
|
||||||
|
range_type alloc_range(Copaque d, DArena & mm) const noexcept override { return I::alloc_range(_dcast(d), mm); }
|
||||||
|
|
||||||
|
// non-const methods
|
||||||
|
|
||||||
bool expand(Opaque d,
|
bool expand(Opaque d,
|
||||||
std::size_t z) const noexcept override { return I::expand(_dcast(d), z); }
|
std::size_t z) const noexcept override { return I::expand(_dcast(d), z); }
|
||||||
value_type alloc(Opaque d,
|
value_type alloc(Opaque d,
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,14 @@ namespace xo {
|
||||||
**/
|
**/
|
||||||
static DArenaIterator end(const DArena * arena);
|
static DArenaIterator end(const DArena * arena);
|
||||||
|
|
||||||
|
/** Address of allocation header for beginning of alloc range in @p arena **/
|
||||||
|
static AllocHeader * begin_header(const DArena * arena);
|
||||||
|
/** Address of allocation header for end of alloc range.
|
||||||
|
* This is the address of header for _next_ allocation in @p arena
|
||||||
|
* i.e. free pointer
|
||||||
|
**/
|
||||||
|
static AllocHeader * end_header(const DArena * arena);
|
||||||
|
|
||||||
/** A valid iterator can be compared, at least with itself
|
/** A valid iterator can be compared, at least with itself
|
||||||
* It can be dereferenced if is also non-empty
|
* It can be dereferenced if is also non-empty
|
||||||
**/
|
**/
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,20 @@ namespace xo {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace mm {
|
namespace mm {
|
||||||
/* changes here coordinate with:
|
/** @class IAllocator_DArena
|
||||||
* AAllocator AAllocator.hpp
|
* @brief Provide AAllocator interface for DArena state
|
||||||
* IAllocator_Any IAllocator_Any.hpp
|
**/
|
||||||
* IAllocator_Xfer IAllocator_Xfer.hpp
|
|
||||||
* RAllocator RAllocator.hpp
|
|
||||||
*/
|
|
||||||
struct IAllocator_DArena {
|
struct IAllocator_DArena {
|
||||||
|
/* changes here coordinate with:
|
||||||
|
* AAllocator AAllocator.hpp
|
||||||
|
* IAllocator_Any IAllocator_Any.hpp
|
||||||
|
* IAllocator_Xfer IAllocator_Xfer.hpp
|
||||||
|
* RAllocator RAllocator.hpp
|
||||||
|
*/
|
||||||
using size_type = std::size_t;
|
using size_type = std::size_t;
|
||||||
using value_type = std::byte *;
|
using value_type = std::byte *;
|
||||||
|
using range_type = std::pair<obj<AAllocIterator>,
|
||||||
|
obj<AAllocIterator>>;
|
||||||
|
|
||||||
enum class alloc_mode : uint8_t {
|
enum class alloc_mode : uint8_t {
|
||||||
standard,
|
standard,
|
||||||
|
|
@ -44,9 +49,13 @@ namespace xo {
|
||||||
static size_type allocated(const DArena &) noexcept;
|
static size_type allocated(const DArena &) noexcept;
|
||||||
static bool contains(const DArena &, const void * p) noexcept;
|
static bool contains(const DArena &, const void * p) noexcept;
|
||||||
static AllocError last_error(const DArena &) noexcept;
|
static AllocError last_error(const DArena &) noexcept;
|
||||||
|
|
||||||
/** retrieve allocation bookkeeping info for @p mem from arena @p d **/
|
/** retrieve allocation bookkeeping info for @p mem from arena @p d **/
|
||||||
static AllocInfo alloc_info(const DArena &, value_type mem) noexcept;
|
static AllocInfo alloc_info(const DArena &, value_type mem) noexcept;
|
||||||
|
/** create alloc-iterator range over allocs on @d,
|
||||||
|
* Iterators themselves allocated from @p ialloc.
|
||||||
|
**/
|
||||||
|
static range_type alloc_range(const DArena & d, DArena & ialloc) noexcept;
|
||||||
|
|
||||||
/** expand committed space in arena @p d
|
/** expand committed space in arena @p d
|
||||||
* to size at least @p z
|
* to size at least @p z
|
||||||
* In practice will round up to a multiple of @ref page_z_.
|
* In practice will round up to a multiple of @ref page_z_.
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ namespace xo {
|
||||||
struct IAllocator_DX1Collector {
|
struct IAllocator_DX1Collector {
|
||||||
using size_type = std::size_t;
|
using size_type = std::size_t;
|
||||||
using value_type = std::byte *;
|
using value_type = std::byte *;
|
||||||
|
using range_type = std::pair<obj<AAllocIterator>,
|
||||||
|
obj<AAllocIterator>>;
|
||||||
|
|
||||||
// todo: available()
|
// todo: available()
|
||||||
|
|
||||||
|
|
@ -51,6 +53,10 @@ namespace xo {
|
||||||
static AllocError last_error(const DX1Collector &) noexcept;
|
static AllocError last_error(const DX1Collector &) noexcept;
|
||||||
/** fetch allocation bookkeeping info **/
|
/** fetch allocation bookkeeping info **/
|
||||||
static AllocInfo alloc_info(const DX1Collector & d, value_type mem) noexcept;
|
static AllocInfo alloc_info(const DX1Collector & d, value_type mem) noexcept;
|
||||||
|
/** create alloc-iterator range over allocs in @d,
|
||||||
|
* with iterator working storage obtained from @p ialloc
|
||||||
|
**/
|
||||||
|
static range_type alloc_range(const DX1Collector & d, DArena & ialloc) noexcept;
|
||||||
|
|
||||||
/** always alloc in gen0 to-space **/
|
/** always alloc in gen0 to-space **/
|
||||||
static value_type alloc(DX1Collector & d, size_type z) noexcept;
|
static value_type alloc(DX1Collector & d, size_type z) noexcept;
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,10 @@ namespace xo {
|
||||||
constexpr bool c_debug_flag = false;
|
constexpr bool c_debug_flag = false;
|
||||||
scope log(XO_DEBUG(c_debug_flag));
|
scope log(XO_DEBUG(c_debug_flag));
|
||||||
|
|
||||||
assert(arena);
|
AllocHeader * begin_hdr = begin_header(arena);
|
||||||
|
|
||||||
if (arena->config_.store_header_flag_ == false) {
|
|
||||||
arena->capture_error(error::alloc_iterator_not_supported);
|
|
||||||
|
|
||||||
|
if (!begin_hdr)
|
||||||
return DArenaIterator::invalid();
|
return DArenaIterator::invalid();
|
||||||
}
|
|
||||||
|
|
||||||
byte * begin_byte = arena->lo_;
|
|
||||||
AllocHeader * begin_hdr = (AllocHeader *)begin_byte;
|
|
||||||
|
|
||||||
log && log(xtag("begin_hdr", begin_hdr));
|
log && log(xtag("begin_hdr", begin_hdr));
|
||||||
|
|
||||||
|
|
@ -41,20 +35,48 @@ namespace xo {
|
||||||
constexpr bool c_debug_flag = false;
|
constexpr bool c_debug_flag = false;
|
||||||
scope log(XO_DEBUG(c_debug_flag));
|
scope log(XO_DEBUG(c_debug_flag));
|
||||||
|
|
||||||
|
AllocHeader * end_hdr = end_header(arena);
|
||||||
|
|
||||||
|
if (!end_hdr)
|
||||||
|
return DArenaIterator::invalid();
|
||||||
|
|
||||||
|
log && log(xtag("end_hdr", end_hdr));
|
||||||
|
|
||||||
|
return DArenaIterator(arena, end_hdr);
|
||||||
|
}
|
||||||
|
|
||||||
|
AllocHeader *
|
||||||
|
DArenaIterator::begin_header(const DArena * arena)
|
||||||
|
{
|
||||||
assert(arena);
|
assert(arena);
|
||||||
|
|
||||||
if (arena->config_.store_header_flag_ == false) {
|
if (arena->config_.store_header_flag_ == false) {
|
||||||
arena->capture_error(error::alloc_iterator_not_supported);
|
arena->capture_error(error::alloc_iterator_not_supported);
|
||||||
|
|
||||||
return DArenaIterator::invalid();
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte * begin_byte = arena->lo_;
|
||||||
|
AllocHeader * begin_hdr = (AllocHeader *)begin_byte;
|
||||||
|
|
||||||
|
return begin_hdr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllocHeader *
|
||||||
|
DArenaIterator::end_header(const DArena * arena)
|
||||||
|
{
|
||||||
|
assert(arena);
|
||||||
|
|
||||||
|
if (arena->config_.store_header_flag_ == false) {
|
||||||
|
arena->capture_error(error::alloc_iterator_not_supported);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte * end_byte = arena->free_;
|
byte * end_byte = arena->free_;
|
||||||
AllocHeader * end_hdr = (AllocHeader *)end_byte;
|
AllocHeader * end_hdr = (AllocHeader *)end_byte;
|
||||||
|
|
||||||
log && log(xtag("end_hdr", end_hdr));
|
return end_hdr;
|
||||||
|
|
||||||
return DArenaIterator(arena, end_hdr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AllocInfo
|
AllocInfo
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,20 @@
|
||||||
* @author Roland Conybeare, Dec 2025
|
* @author Roland Conybeare, Dec 2025
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
#include "AllocIterator.hpp"
|
||||||
#include "arena/IAllocator_DArena.hpp"
|
#include "arena/IAllocator_DArena.hpp"
|
||||||
|
#include "arena/IAllocIterator_DArenaIterator.hpp" // for alloc_range
|
||||||
|
#include "arena/DArenaIterator.hpp"
|
||||||
#include "padding.hpp"
|
#include "padding.hpp"
|
||||||
#include "xo/indentlog/scope.hpp"
|
#include <xo/facet/obj.hpp>
|
||||||
|
#include <xo/indentlog/scope.hpp>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
namespace xo {
|
namespace xo {
|
||||||
|
using xo::facet::with_facet;
|
||||||
using std::size_t;
|
using std::size_t;
|
||||||
using std::byte;
|
using std::byte;
|
||||||
|
|
||||||
|
|
@ -65,6 +70,35 @@ namespace xo {
|
||||||
return s.alloc_info(mem);
|
return s.alloc_info(mem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dummy(const DArena & s) {
|
||||||
|
byte * begin_mem = nullptr;
|
||||||
|
DArenaIterator * ix = new (begin_mem) DArenaIterator(&s, DArenaIterator::begin_header(&s));
|
||||||
|
obj<AAllocIterator,DArenaIterator> ix_vt{ix};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
IAllocator_DArena::alloc_range(const DArena & s,
|
||||||
|
DArena & ialloc) noexcept -> range_type
|
||||||
|
{
|
||||||
|
byte * begin_mem = IAllocator_DArena::alloc(ialloc,
|
||||||
|
sizeof(DArenaIterator));
|
||||||
|
byte * end_mem = IAllocator_DArena::alloc(ialloc,
|
||||||
|
sizeof(DArenaIterator));
|
||||||
|
|
||||||
|
assert(begin_mem);
|
||||||
|
assert(end_mem);
|
||||||
|
|
||||||
|
DArenaIterator * begin_ix = new (begin_mem) DArenaIterator(&s, DArenaIterator::begin_header(&s));
|
||||||
|
DArenaIterator * end_ix = new ( end_mem) DArenaIterator(&s, DArenaIterator::end_header(&s));
|
||||||
|
|
||||||
|
obj<AAllocIterator> begin_obj = with_facet<AAllocIterator>::mkobj(begin_ix);
|
||||||
|
obj<AAllocIterator> end_obj = with_facet<AAllocIterator>::mkobj( end_ix);
|
||||||
|
|
||||||
|
return std::make_pair(begin_obj, end_obj);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
IAllocator_DArena::expand(DArena & s, size_t target_z) noexcept
|
IAllocator_DArena::expand(DArena & s, size_t target_z) noexcept
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,14 @@
|
||||||
**/
|
**/
|
||||||
|
|
||||||
#include "gc/IAllocator_DX1Collector.hpp"
|
#include "gc/IAllocator_DX1Collector.hpp"
|
||||||
|
#include "gc/IAllocIterator_DX1CollectorIterator.hpp"
|
||||||
|
#include "gc/DX1CollectorIterator.hpp"
|
||||||
|
#include "arena/IAllocator_DArena.hpp"
|
||||||
|
|
||||||
namespace xo {
|
namespace xo {
|
||||||
|
using xo::facet::with_facet;
|
||||||
using std::size_t;
|
using std::size_t;
|
||||||
|
using std::byte;
|
||||||
|
|
||||||
namespace mm {
|
namespace mm {
|
||||||
using value_type = IAllocator_DX1Collector::value_type;
|
using value_type = IAllocator_DX1Collector::value_type;
|
||||||
|
|
@ -61,6 +66,29 @@ namespace xo {
|
||||||
return d.last_error();
|
return d.last_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
IAllocator_DX1Collector::alloc_range(const DX1Collector & d,
|
||||||
|
DArena & ialloc) noexcept -> range_type
|
||||||
|
{
|
||||||
|
byte * begin_mem = IAllocator_DArena::alloc(ialloc,
|
||||||
|
sizeof(DX1CollectorIterator));
|
||||||
|
byte * end_mem = IAllocator_DArena::alloc(ialloc,
|
||||||
|
sizeof(DX1CollectorIterator));
|
||||||
|
|
||||||
|
assert(begin_mem);
|
||||||
|
assert(end_mem);
|
||||||
|
|
||||||
|
DX1CollectorIterator * begin_ix
|
||||||
|
= new (begin_mem) DX1CollectorIterator(d.begin());
|
||||||
|
DX1CollectorIterator * end_ix
|
||||||
|
= new ( end_mem) DX1CollectorIterator(d.end());
|
||||||
|
|
||||||
|
obj<AAllocIterator> begin_obj = with_facet<AAllocIterator>::mkobj(begin_ix);
|
||||||
|
obj<AAllocIterator> end_obj = with_facet<AAllocIterator>::mkobj( end_ix);
|
||||||
|
|
||||||
|
return std::make_pair(begin_obj, end_obj);
|
||||||
|
}
|
||||||
|
|
||||||
auto
|
auto
|
||||||
IAllocator_DX1Collector::alloc(DX1Collector & d, size_type z) noexcept -> value_type
|
IAllocator_DX1Collector::alloc(DX1Collector & d, size_type z) noexcept -> value_type
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,14 @@ namespace xo {
|
||||||
{
|
{
|
||||||
switch (x) {
|
switch (x) {
|
||||||
case comparison::invalid:
|
case comparison::invalid:
|
||||||
return "?comparison";
|
break;
|
||||||
case comparison::comparable:
|
case comparison::comparable:
|
||||||
return "cmp";
|
return "cmp";
|
||||||
case comparison::incomparable:
|
case comparison::incomparable:
|
||||||
return "!cmp";
|
return "!cmp";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "?comparison";
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,14 @@ namespace xo {
|
||||||
REQUIRE(ix.is_valid());
|
REQUIRE(ix.is_valid());
|
||||||
REQUIRE(end_ix.is_valid());
|
REQUIRE(end_ix.is_valid());
|
||||||
|
|
||||||
/* iteration not supported since we did not set */
|
/* verify obj 'fat pointer' packaging */
|
||||||
|
obj<AAllocIterator,DArenaIterator> ix_vt{&ix};
|
||||||
|
obj<AAllocIterator,DArenaIterator> end_ix_vt{&end_ix};
|
||||||
|
|
||||||
|
REQUIRE(ix_vt.iface());
|
||||||
|
REQUIRE(ix_vt.data());
|
||||||
|
REQUIRE(end_ix_vt.iface());
|
||||||
|
REQUIRE(end_ix_vt.data());
|
||||||
|
|
||||||
/* arena is empty, so begin==end */
|
/* arena is empty, so begin==end */
|
||||||
REQUIRE(ix == end_ix);
|
REQUIRE(ix == end_ix);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue