mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 12:57:42 +00:00
[vm/aot] Move precompiled position handling to CodeSourceMap classes.
Since CodeSourceMaps are not serialized when non-symbolic stack traces are enabled, use a different CSM encoding in this mode. Here, we write out the line number as before and then the column as a separately written integer. Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-nnbd-linux-release-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-release-simarm_x64-try Change-Id: If9aec2f52551d04eb45626744957e625bcc17d3d Bug: https://github.com/dart-lang/sdk/issues/42397 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/151824 Commit-Queue: Tess Strickland <sstrickl@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
parent
dbefbc3753
commit
ed0eeaebca
|
@ -573,18 +573,32 @@ CodeSourceMapPtr CodeSourceMapBuilder::Finalize() {
|
|||
|
||||
void CodeSourceMapBuilder::WriteChangePosition(TokenPosition pos) {
|
||||
stream_.Write<uint8_t>(kChangePosition);
|
||||
intptr_t position_or_line = pos.value();
|
||||
#if defined(DART_PRECOMPILER)
|
||||
intptr_t column = TokenPosition::kNoSourcePos;
|
||||
if (FLAG_precompiled_mode) {
|
||||
int32_t loc = TokenPosition::kNoSourcePos;
|
||||
// Don't use the raw position value directly in precompiled mode. Instead,
|
||||
// use the value of kNoSource as a fallback when no line or column
|
||||
// information is found.
|
||||
position_or_line = TokenPosition::kNoSourcePos;
|
||||
intptr_t inline_id = buffered_inline_id_stack_.Last();
|
||||
if (inline_id < inline_id_to_function_.length()) {
|
||||
const Function* function = inline_id_to_function_[inline_id];
|
||||
Script& script = Script::Handle(function->script());
|
||||
loc = script.GetTokenLocationUsingLineStarts(pos.SourcePosition());
|
||||
script.GetTokenLocationUsingLineStarts(pos.SourcePosition(),
|
||||
&position_or_line, &column);
|
||||
}
|
||||
stream_.Write(loc);
|
||||
} else {
|
||||
stream_.Write<int32_t>(pos.value());
|
||||
}
|
||||
#endif
|
||||
stream_.Write<int32_t>(position_or_line);
|
||||
#if defined(DART_PRECOMPILER)
|
||||
// For non-symbolic stack traces, the CodeSourceMaps are not serialized,
|
||||
// so we need not worry about increasing snapshot size by including more
|
||||
// information here.
|
||||
if (FLAG_dwarf_stack_traces_mode) {
|
||||
stream_.Write<int32_t>(column);
|
||||
}
|
||||
#endif
|
||||
written_token_pos_stack_.Last() = pos;
|
||||
}
|
||||
|
||||
|
@ -606,9 +620,8 @@ void CodeSourceMapReader::GetInlinedFunctionsAt(
|
|||
uint8_t opcode = stream.Read<uint8_t>();
|
||||
switch (opcode) {
|
||||
case CodeSourceMapBuilder::kChangePosition: {
|
||||
int32_t position = stream.Read<int32_t>();
|
||||
(*token_positions)[token_positions->length() - 1] =
|
||||
TokenPosition(position);
|
||||
ReadPosition(&stream);
|
||||
break;
|
||||
}
|
||||
case CodeSourceMapBuilder::kAdvancePC: {
|
||||
|
@ -668,7 +681,7 @@ void CodeSourceMapReader::PrintJSONInlineIntervals(JSONObject* jsobj) {
|
|||
uint8_t opcode = stream.Read<uint8_t>();
|
||||
switch (opcode) {
|
||||
case CodeSourceMapBuilder::kChangePosition: {
|
||||
stream.Read<int32_t>();
|
||||
ReadPosition(&stream);
|
||||
break;
|
||||
}
|
||||
case CodeSourceMapBuilder::kAdvancePC: {
|
||||
|
@ -721,7 +734,7 @@ void CodeSourceMapReader::DumpInlineIntervals(uword start) {
|
|||
uint8_t opcode = stream.Read<uint8_t>();
|
||||
switch (opcode) {
|
||||
case CodeSourceMapBuilder::kChangePosition: {
|
||||
stream.Read<int32_t>();
|
||||
ReadPosition(&stream);
|
||||
break;
|
||||
}
|
||||
case CodeSourceMapBuilder::kAdvancePC: {
|
||||
|
@ -775,8 +788,7 @@ void CodeSourceMapReader::DumpSourcePositions(uword start) {
|
|||
uint8_t opcode = stream.Read<uint8_t>();
|
||||
switch (opcode) {
|
||||
case CodeSourceMapBuilder::kChangePosition: {
|
||||
int32_t position = stream.Read<int32_t>();
|
||||
token_positions[token_positions.length() - 1] = TokenPosition(position);
|
||||
token_positions[token_positions.length() - 1] = ReadPosition(&stream);
|
||||
break;
|
||||
}
|
||||
case CodeSourceMapBuilder::kAdvancePC: {
|
||||
|
@ -830,7 +842,7 @@ intptr_t CodeSourceMapReader::GetNullCheckNameIndexAt(int32_t pc_offset) {
|
|||
uint8_t opcode = stream.Read<uint8_t>();
|
||||
switch (opcode) {
|
||||
case CodeSourceMapBuilder::kChangePosition: {
|
||||
stream.Read<int32_t>();
|
||||
ReadPosition(&stream);
|
||||
break;
|
||||
}
|
||||
case CodeSourceMapBuilder::kAdvancePC: {
|
||||
|
@ -862,4 +874,17 @@ intptr_t CodeSourceMapReader::GetNullCheckNameIndexAt(int32_t pc_offset) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
TokenPosition CodeSourceMapReader::ReadPosition(ReadStream* stream) {
|
||||
const intptr_t line = stream->Read<int32_t>();
|
||||
#if defined(DART_PRECOMPILER)
|
||||
// The special handling for non-symbolic stack trace mode only needs to
|
||||
// happen in the precompiler, because those CSMs are not serialized in
|
||||
// precompiled snapshots.
|
||||
if (FLAG_dwarf_stack_traces_mode) {
|
||||
stream->Read<int32_t>(); // Discard the column information.
|
||||
}
|
||||
#endif
|
||||
return TokenPosition(line);
|
||||
}
|
||||
|
||||
} // namespace dart
|
||||
|
|
|
@ -359,6 +359,10 @@ class CodeSourceMapReader : public ValueObject {
|
|||
intptr_t GetNullCheckNameIndexAt(int32_t pc_offset);
|
||||
|
||||
private:
|
||||
// Reads a TokenPosition value from a CSM, handling the different encoding for
|
||||
// when non-symbolic stack traces are enabled.
|
||||
static TokenPosition ReadPosition(ReadStream* stream);
|
||||
|
||||
const CodeSourceMap& map_;
|
||||
const Array& functions_;
|
||||
const Function& root_;
|
||||
|
|
|
@ -11,15 +11,36 @@
|
|||
|
||||
namespace dart {
|
||||
|
||||
#ifdef DART_PRECOMPILER
|
||||
#if defined(DART_PRECOMPILER)
|
||||
|
||||
class DwarfPosition {
|
||||
public:
|
||||
// The DWARF standard uses 0 to denote missing line or column information.
|
||||
DwarfPosition(intptr_t line, intptr_t column)
|
||||
: line_(line > 0 ? line : 0), column_(column > 0 ? column : 0) {
|
||||
// Should only have no line information if also no column information.
|
||||
ASSERT(line_ > 0 || column_ == 0);
|
||||
}
|
||||
explicit DwarfPosition(intptr_t line) : DwarfPosition(line, 0) {}
|
||||
constexpr DwarfPosition() : line_(0), column_(0) {}
|
||||
|
||||
intptr_t line() const { return line_; }
|
||||
intptr_t column() const { return column_; }
|
||||
|
||||
private:
|
||||
intptr_t line_;
|
||||
intptr_t column_;
|
||||
};
|
||||
|
||||
static constexpr auto kNoDwarfPositionInfo = DwarfPosition();
|
||||
|
||||
class InliningNode : public ZoneAllocated {
|
||||
public:
|
||||
InliningNode(const Function& function,
|
||||
TokenPosition call_pos,
|
||||
const DwarfPosition& position,
|
||||
int32_t start_pc_offset)
|
||||
: function(function),
|
||||
call_pos(call_pos),
|
||||
position(position),
|
||||
start_pc_offset(start_pc_offset),
|
||||
end_pc_offset(-1),
|
||||
children_head(NULL),
|
||||
|
@ -39,7 +60,7 @@ class InliningNode : public ZoneAllocated {
|
|||
}
|
||||
|
||||
const Function& function;
|
||||
TokenPosition call_pos;
|
||||
DwarfPosition position;
|
||||
int32_t start_pc_offset;
|
||||
int32_t end_pc_offset;
|
||||
InliningNode* children_head;
|
||||
|
@ -402,6 +423,15 @@ void Dwarf::WriteConcreteFunctions(DwarfWriteStream* stream) {
|
|||
}
|
||||
}
|
||||
|
||||
static DwarfPosition ReadPosition(ReadStream* stream) {
|
||||
const intptr_t line = stream->Read<int32_t>();
|
||||
if (!FLAG_dwarf_stack_traces_mode) {
|
||||
return DwarfPosition(line);
|
||||
}
|
||||
const intptr_t column = stream->Read<int32_t>();
|
||||
return DwarfPosition(line, column);
|
||||
}
|
||||
|
||||
// Our state machine encodes position metadata such that we don't know the
|
||||
// end pc for an inlined function until it is popped, but DWARF DIEs encode
|
||||
// it where the function is pushed. We expand the state transitions into
|
||||
|
@ -419,24 +449,23 @@ InliningNode* Dwarf::ExpandInliningTree(const Code& code) {
|
|||
}
|
||||
|
||||
GrowableArray<InliningNode*> node_stack(zone_, 4);
|
||||
GrowableArray<TokenPosition> token_positions(zone_, 4);
|
||||
GrowableArray<DwarfPosition> token_positions(zone_, 4);
|
||||
|
||||
NoSafepointScope no_safepoint;
|
||||
ReadStream stream(map.Data(), map.Length());
|
||||
|
||||
int32_t current_pc_offset = 0;
|
||||
token_positions.Add(kNoDwarfPositionInfo);
|
||||
InliningNode* root_node =
|
||||
new (zone_) InliningNode(root_function, TokenPosition(), 0);
|
||||
new (zone_) InliningNode(root_function, token_positions.Last(), 0);
|
||||
root_node->end_pc_offset = code.Size();
|
||||
node_stack.Add(root_node);
|
||||
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
|
||||
|
||||
while (stream.PendingBytes() > 0) {
|
||||
uint8_t opcode = stream.Read<uint8_t>();
|
||||
switch (opcode) {
|
||||
case CodeSourceMapBuilder::kChangePosition: {
|
||||
int32_t position = stream.Read<int32_t>();
|
||||
token_positions[token_positions.length() - 1] = TokenPosition(position);
|
||||
token_positions[token_positions.length() - 1] = ReadPosition(&stream);
|
||||
break;
|
||||
}
|
||||
case CodeSourceMapBuilder::kAdvancePC: {
|
||||
|
@ -448,12 +477,11 @@ InliningNode* Dwarf::ExpandInliningTree(const Code& code) {
|
|||
int32_t func = stream.Read<int32_t>();
|
||||
const Function& child_func =
|
||||
Function::ZoneHandle(zone_, Function::RawCast(functions.At(func)));
|
||||
TokenPosition call_pos = token_positions.Last();
|
||||
InliningNode* child_node =
|
||||
new (zone_) InliningNode(child_func, call_pos, current_pc_offset);
|
||||
InliningNode* child_node = new (zone_)
|
||||
InliningNode(child_func, token_positions.Last(), current_pc_offset);
|
||||
node_stack.Last()->AppendChild(child_node);
|
||||
node_stack.Add(child_node);
|
||||
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
|
||||
token_positions.Add(kNoDwarfPositionInfo);
|
||||
break;
|
||||
}
|
||||
case CodeSourceMapBuilder::kPopFunction: {
|
||||
|
@ -489,7 +517,6 @@ void Dwarf::WriteInliningNode(DwarfWriteStream* stream,
|
|||
const Script& parent_script,
|
||||
SnapshotTextObjectNamer* namer) {
|
||||
intptr_t file = LookupScript(parent_script);
|
||||
const auto& token_pos = node->call_pos;
|
||||
intptr_t function_index = LookupFunction(node->function);
|
||||
const Script& script = Script::Handle(zone_, node->function.script());
|
||||
|
||||
|
@ -504,13 +531,10 @@ void Dwarf::WriteInliningNode(DwarfWriteStream* stream,
|
|||
stream->OffsetFromSymbol(root_asm_name, node->end_pc_offset);
|
||||
// DW_AT_call_file
|
||||
stream->uleb128(file);
|
||||
intptr_t line = kNoLineInformation;
|
||||
intptr_t col = kNoColumnInformation;
|
||||
Script::DecodePrecompiledPosition(token_pos, &line, &col);
|
||||
// DW_AT_call_line
|
||||
stream->uleb128(line);
|
||||
stream->uleb128(node->position.line());
|
||||
// DW_at_call_column
|
||||
stream->uleb128(col);
|
||||
stream->uleb128(node->position.column());
|
||||
|
||||
for (InliningNode* child = node->children_head; child != NULL;
|
||||
child = child->children_next) {
|
||||
|
@ -592,7 +616,7 @@ void Dwarf::WriteLineNumberProgram(DwarfWriteStream* stream) {
|
|||
CodeSourceMap& map = CodeSourceMap::Handle(zone_);
|
||||
Array& functions = Array::Handle(zone_);
|
||||
GrowableArray<const Function*> function_stack(zone_, 8);
|
||||
GrowableArray<TokenPosition> token_positions(zone_, 8);
|
||||
GrowableArray<DwarfPosition> token_positions(zone_, 8);
|
||||
SnapshotTextObjectNamer namer(zone_);
|
||||
|
||||
for (intptr_t i = 0; i < codes_.length(); i++) {
|
||||
|
@ -614,15 +638,14 @@ void Dwarf::WriteLineNumberProgram(DwarfWriteStream* stream) {
|
|||
|
||||
int32_t current_pc_offset = 0;
|
||||
function_stack.Add(&root_function);
|
||||
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
|
||||
token_positions.Add(kNoDwarfPositionInfo);
|
||||
|
||||
while (code_map_stream.PendingBytes() > 0) {
|
||||
uint8_t opcode = code_map_stream.Read<uint8_t>();
|
||||
switch (opcode) {
|
||||
case CodeSourceMapBuilder::kChangePosition: {
|
||||
int32_t position = code_map_stream.Read<int32_t>();
|
||||
token_positions[token_positions.length() - 1] =
|
||||
TokenPosition(position);
|
||||
ReadPosition(&code_map_stream);
|
||||
break;
|
||||
}
|
||||
case CodeSourceMapBuilder::kAdvancePC: {
|
||||
|
@ -641,10 +664,8 @@ void Dwarf::WriteLineNumberProgram(DwarfWriteStream* stream) {
|
|||
}
|
||||
|
||||
// 2. Update LNP line.
|
||||
auto const position = token_positions.Last();
|
||||
intptr_t line = kNoLineInformation;
|
||||
intptr_t column = kNoColumnInformation;
|
||||
Script::DecodePrecompiledPosition(position, &line, &column);
|
||||
const intptr_t line = token_positions.Last().line();
|
||||
const intptr_t column = token_positions.Last().column();
|
||||
if (line != previous_line) {
|
||||
stream->u1(DW_LNS_advance_line);
|
||||
stream->sleb128(line - previous_line);
|
||||
|
@ -684,7 +705,7 @@ void Dwarf::WriteLineNumberProgram(DwarfWriteStream* stream) {
|
|||
const Function& child_func = Function::Handle(
|
||||
zone_, Function::RawCast(functions.At(func_index)));
|
||||
function_stack.Add(&child_func);
|
||||
token_positions.Add(CodeSourceMapBuilder::kInitialPosition);
|
||||
token_positions.Add(kNoDwarfPositionInfo);
|
||||
break;
|
||||
}
|
||||
case CodeSourceMapBuilder::kPopFunction: {
|
||||
|
|
|
@ -325,9 +325,6 @@ class Dwarf : public ZoneAllocated {
|
|||
kInlinedFunction,
|
||||
};
|
||||
|
||||
static constexpr intptr_t kNoLineInformation = 0;
|
||||
static constexpr intptr_t kNoColumnInformation = 0;
|
||||
|
||||
void WriteAbstractFunctions(DwarfWriteStream* stream);
|
||||
void WriteConcreteFunctions(DwarfWriteStream* stream);
|
||||
InliningNode* ExpandInliningTree(const Code& code);
|
||||
|
|
|
@ -10755,96 +10755,30 @@ void Script::SetLocationOffset(intptr_t line_offset,
|
|||
StoreNonPointer(&raw_ptr()->col_offset_, col_offset);
|
||||
}
|
||||
|
||||
// Whether a precompiled (non-special) position contains column information.
|
||||
using PrecompiledPositionContainsColumn = BitField<int32_t, bool, 0, 1>;
|
||||
// Can be used if PrecompiledPositionContainsColumn::decode(v) is true.
|
||||
using PrecompiledPositionColumn =
|
||||
BitField<int32_t,
|
||||
uint32_t,
|
||||
PrecompiledPositionContainsColumn::kNextBit,
|
||||
10>;
|
||||
// Can be used if PrecompiledPositionContainsColumn::decode(v) is true.
|
||||
// Does not include the sign bit, which should be 0 for encoded values.
|
||||
using PrecompiledPositionLine =
|
||||
BitField<int32_t,
|
||||
uint32_t,
|
||||
PrecompiledPositionColumn::kNextBit,
|
||||
(sizeof(int32_t) * kBitsPerByte - 1) -
|
||||
(PrecompiledPositionColumn::bitsize() +
|
||||
PrecompiledPositionContainsColumn::bitsize())>;
|
||||
// Can be used if PrecompiledPositionContainsColumn::decode(v) is false.
|
||||
// Does not include the sign bit, which should be 0 for encoded values.
|
||||
using PrecompiledPositionLineOnly =
|
||||
BitField<int32_t,
|
||||
uint32_t,
|
||||
PrecompiledPositionContainsColumn::kNextBit,
|
||||
(sizeof(int32_t) * kBitsPerByte - 1) -
|
||||
PrecompiledPositionContainsColumn::bitsize()>;
|
||||
|
||||
bool Script::DecodePrecompiledPosition(TokenPosition token_pos,
|
||||
intptr_t* line,
|
||||
intptr_t* column) {
|
||||
ASSERT(line != nullptr);
|
||||
ASSERT(column != nullptr);
|
||||
auto const value = token_pos.value();
|
||||
if (value < 0) return false;
|
||||
// When storing CodeSourceMaps in the snapshot, we just add unencoded lines.
|
||||
if (!FLAG_dwarf_stack_traces_mode) {
|
||||
*line = value;
|
||||
return true;
|
||||
}
|
||||
// We encode zero-based offsets from start, so convert back to ordinals.
|
||||
if (PrecompiledPositionContainsColumn::decode(value)) {
|
||||
*line = PrecompiledPositionLine::decode(value) + 1;
|
||||
*column = PrecompiledPositionColumn::decode(value) + 1;
|
||||
} else {
|
||||
*line = PrecompiledPositionLineOnly::decode(value) + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Specialized for AOT compilation, which does this lookup for every token
|
||||
// position that could be part of a stack trace.
|
||||
int32_t Script::GetTokenLocationUsingLineStarts(
|
||||
TokenPosition target_token_pos) const {
|
||||
#if !defined(DART_PRECOMPILED_RUNTIME)
|
||||
bool Script::GetTokenLocationUsingLineStarts(TokenPosition target_token_pos,
|
||||
intptr_t* line,
|
||||
intptr_t* column) const {
|
||||
#if defined(DART_PRECOMPILED_RUNTIME)
|
||||
return false;
|
||||
#else
|
||||
// Negative positions denote positions that do not correspond to Dart code.
|
||||
if (target_token_pos.value() < 0) return TokenPosition::kNoSourcePos;
|
||||
if (target_token_pos.value() < 0) return false;
|
||||
|
||||
Zone* zone = Thread::Current()->zone();
|
||||
TypedData& line_starts_data = TypedData::Handle(zone, line_starts());
|
||||
// Scripts loaded from bytecode may have null line_starts().
|
||||
if (line_starts_data.IsNull()) return TokenPosition::kNoSourcePos;
|
||||
if (line_starts_data.IsNull()) return false;
|
||||
|
||||
kernel::KernelLineStartsReader line_starts_reader(line_starts_data, zone);
|
||||
intptr_t line = -1;
|
||||
intptr_t col = -1;
|
||||
line_starts_reader.LocationForPosition(target_token_pos.value(), &line, &col);
|
||||
line_starts_reader.LocationForPosition(target_token_pos.value(), line,
|
||||
column);
|
||||
// The line and column numbers returned are ordinals, so we shouldn't get 0.
|
||||
ASSERT(line > 0);
|
||||
ASSERT(col > 0);
|
||||
// Only return (unencoded) line information when storing CodeSourceMaps.
|
||||
if (!FLAG_dwarf_stack_traces_mode) {
|
||||
if (Utils::IsUint(31, line)) {
|
||||
return line;
|
||||
}
|
||||
return TokenPosition::kNoSourcePos;
|
||||
}
|
||||
// Encode the returned line and column numbers as 0-based offsets from start
|
||||
// instead of ordinal numbers for better encoding.
|
||||
line -= 1;
|
||||
col -= 1;
|
||||
if (PrecompiledPositionLine::is_valid(line) &&
|
||||
PrecompiledPositionColumn::is_valid(col)) {
|
||||
return PrecompiledPositionLine::encode(line) |
|
||||
PrecompiledPositionColumn::encode(col) |
|
||||
PrecompiledPositionContainsColumn::encode(true);
|
||||
} else if (PrecompiledPositionLineOnly::is_valid(line)) {
|
||||
return PrecompiledPositionLineOnly::encode(line) |
|
||||
PrecompiledPositionContainsColumn::encode(false);
|
||||
}
|
||||
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
||||
return TokenPosition::kNoSourcePos;
|
||||
ASSERT(*line > 0);
|
||||
ASSERT(*column > 0);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(DART_PRECOMPILED_RUNTIME)
|
||||
|
@ -23967,7 +23901,7 @@ static void PrintSymbolicStackFrame(Zone* zone,
|
|||
intptr_t line = -1;
|
||||
intptr_t column = -1;
|
||||
if (FLAG_precompiled_mode) {
|
||||
Script::DecodePrecompiledPosition(token_pos, &line, &column);
|
||||
line = token_pos.value();
|
||||
} else if (token_pos.IsSourcePosition()) {
|
||||
ASSERT(!script.IsNull());
|
||||
script.GetTokenLocation(token_pos.SourcePosition(), &line, &column);
|
||||
|
|
|
@ -4495,14 +4495,9 @@ class Script : public Object {
|
|||
|
||||
void SetLocationOffset(intptr_t line_offset, intptr_t col_offset) const;
|
||||
|
||||
// Decode line number and column information if present. Returns false if
|
||||
// this is a special location and thus undecodable.
|
||||
static bool DecodePrecompiledPosition(TokenPosition token_pos,
|
||||
intptr_t* line,
|
||||
intptr_t* column);
|
||||
// For positions that have line numbers and columns, returns a non-negative
|
||||
// value. Otherwise, returns -1.
|
||||
int32_t GetTokenLocationUsingLineStarts(TokenPosition token_pos) const;
|
||||
bool GetTokenLocationUsingLineStarts(TokenPosition token_pos,
|
||||
intptr_t* line,
|
||||
intptr_t* column) const;
|
||||
void GetTokenLocation(TokenPosition token_pos,
|
||||
intptr_t* line,
|
||||
intptr_t* column,
|
||||
|
|
Loading…
Reference in a new issue