mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-03-21 18:01:15 +00:00
redo relocations
This commit is contained in:
parent
442ccc3deb
commit
00a54ad918
12 changed files with 240 additions and 116 deletions
|
@ -43,7 +43,7 @@ typedef struct def_s {
|
|||
int ofs;
|
||||
int initialized; // for uninit var detection
|
||||
int constant; // 1 when a declaration included "= immediate"
|
||||
struct statref_s *refs; // for relocations
|
||||
struct reloc_s *refs; // for relocations
|
||||
|
||||
unsigned freed:1; // already freed from the scope
|
||||
unsigned removed:1; // already removed from the symbol table
|
||||
|
|
|
@ -56,8 +56,9 @@ typedef enum {
|
|||
ex_short,
|
||||
} expr_type;
|
||||
|
||||
typedef struct {
|
||||
struct statref_s *refs;
|
||||
typedef struct ex_label_s {
|
||||
struct ex_label_s *next;
|
||||
struct reloc_s *refs;
|
||||
int ofs;
|
||||
const char *name;
|
||||
} ex_label_t;
|
||||
|
|
|
@ -32,31 +32,23 @@
|
|||
#ifndef __opcodes_h
|
||||
#define __opcodes_h
|
||||
|
||||
#include "QF/pr_comp.h"
|
||||
extern struct opcode_s *op_done;
|
||||
extern struct opcode_s *op_return;
|
||||
extern struct opcode_s *op_if;
|
||||
extern struct opcode_s *op_ifnot;
|
||||
extern struct opcode_s *op_ifbe;
|
||||
extern struct opcode_s *op_ifb;
|
||||
extern struct opcode_s *op_ifae;
|
||||
extern struct opcode_s *op_ifa;
|
||||
extern struct opcode_s *op_state;
|
||||
extern struct opcode_s *op_goto;
|
||||
extern struct opcode_s *op_jump;
|
||||
extern struct opcode_s *op_jumpb;
|
||||
|
||||
typedef struct statref_s {
|
||||
struct statref_s *next;
|
||||
int ofs;
|
||||
int field; // a, b, c (0, 1, 2)
|
||||
} statref_t;
|
||||
struct def_s;
|
||||
|
||||
extern opcode_t *op_done;
|
||||
extern opcode_t *op_return;
|
||||
extern opcode_t *op_if;
|
||||
extern opcode_t *op_ifnot;
|
||||
extern opcode_t *op_ifbe;
|
||||
extern opcode_t *op_ifb;
|
||||
extern opcode_t *op_ifae;
|
||||
extern opcode_t *op_ifa;
|
||||
extern opcode_t *op_state;
|
||||
extern opcode_t *op_goto;
|
||||
extern opcode_t *op_jump;
|
||||
extern opcode_t *op_jumpb;
|
||||
|
||||
statref_t *PR_NewStatref (int ofs, int field);
|
||||
void PR_AddStatementRef (struct def_s *def, dstatement_t *st, int field);
|
||||
opcode_t *PR_Opcode_Find (const char *name, struct def_s *var_a,
|
||||
struct def_s *var_b, struct def_s *var_c);
|
||||
void PR_Opcode_Init_Tables (void);
|
||||
struct opcode_s *opcode_find (const char *name, struct def_s *var_a,
|
||||
struct def_s *var_b, struct def_s *var_c);
|
||||
void opcode_init (void);
|
||||
|
||||
#endif//__opcodes_h
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
//
|
||||
typedef struct {
|
||||
struct type_s *types;
|
||||
struct ex_label_s *labels;
|
||||
|
||||
struct def_s *def_head; // unused head of linked list
|
||||
struct def_s **def_tail; // add new defs after this and move it
|
||||
|
|
58
tools/qfcc/include/reloc.h
Normal file
58
tools/qfcc/include/reloc.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
reloc.h
|
||||
|
||||
#DESCRIPTION#
|
||||
|
||||
Copyright (C) 2001 #AUTHOR#
|
||||
|
||||
Author: #AUTHOR#
|
||||
Date: #DATE#
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
|
||||
Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA
|
||||
|
||||
$Id$
|
||||
*/
|
||||
|
||||
#ifndef __reloc_h
|
||||
#define __reloc_h
|
||||
|
||||
typedef enum {
|
||||
rel_none,
|
||||
rel_op_a_def,
|
||||
rel_op_b_def,
|
||||
rel_op_c_def,
|
||||
rel_op_a_op,
|
||||
rel_op_b_op,
|
||||
rel_op_c_op,
|
||||
rel_def_op,
|
||||
rel_def_def,
|
||||
} reloc_type;
|
||||
|
||||
typedef struct reloc_s {
|
||||
struct reloc_s *next;
|
||||
int ofs;
|
||||
reloc_type type;
|
||||
} reloc_t;
|
||||
|
||||
struct statement_s;
|
||||
|
||||
reloc_t *new_reloc (int ofs, reloc_type type);
|
||||
void relocate_refs (reloc_t *refs, int ofs);
|
||||
|
||||
#endif//__reloc_h
|
|
@ -40,7 +40,7 @@ bin_PROGRAMS= qfcc
|
|||
qfcc_SOURCES= \
|
||||
class.c cmdlib.c cpp.c debug.c emit.c expr.c function.c idstuff.c \
|
||||
immediate.c method.c opcodes.c options.c pr_def.c qc-lex.l qc-parse.y \
|
||||
qfcc.c struct.c switch.c type.c
|
||||
qfcc.c reloc.c struct.c switch.c type.c
|
||||
|
||||
qfcc_LDADD= $(QFCC_LIBS)
|
||||
qfcc_DEPENDENCIES= $(QFCC_DEPS)
|
||||
|
|
|
@ -51,11 +51,26 @@ static const char rcsid[] =
|
|||
#include "opcodes.h"
|
||||
#include "options.h"
|
||||
#include "qfcc.h"
|
||||
#include "reloc.h"
|
||||
#include "type.h"
|
||||
#include "qc-parse.h"
|
||||
|
||||
def_t *emit_sub_expr (expr_t *e, def_t *dest);
|
||||
|
||||
void
|
||||
add_statement_ref (def_t *def, dstatement_t *st, int field)
|
||||
{
|
||||
if (def) {
|
||||
reloc_t *ref = new_reloc (st - pr.statements, field);
|
||||
|
||||
ref->next = def->refs;
|
||||
def->refs = ref;
|
||||
|
||||
def->users--;
|
||||
def->used = 1;
|
||||
}
|
||||
}
|
||||
|
||||
def_t *
|
||||
emit_statement (int sline, opcode_t *op, def_t *var_a, def_t *var_b,
|
||||
def_t *var_c)
|
||||
|
@ -118,9 +133,9 @@ emit_statement (int sline, opcode_t *op, def_t *var_a, def_t *var_b,
|
|||
var_c ? var_c->name : "", statement->c);
|
||||
#endif
|
||||
|
||||
PR_AddStatementRef (var_a, statement, 0);
|
||||
PR_AddStatementRef (var_b, statement, 1);
|
||||
PR_AddStatementRef (var_c, statement, 2);
|
||||
add_statement_ref (var_a, statement, 0);
|
||||
add_statement_ref (var_b, statement, 1);
|
||||
add_statement_ref (var_c, statement, 2);
|
||||
|
||||
if (op->right_associative)
|
||||
return var_a;
|
||||
|
@ -131,7 +146,7 @@ void
|
|||
emit_branch (int line, opcode_t *op, expr_t *e, expr_t *l)
|
||||
{
|
||||
dstatement_t *st;
|
||||
statref_t *ref;
|
||||
reloc_t *ref;
|
||||
def_t *def = 0;
|
||||
int ofs;
|
||||
|
||||
|
@ -145,7 +160,7 @@ emit_branch (int line, opcode_t *op, expr_t *e, expr_t *l)
|
|||
else
|
||||
st->b = l->e.label.ofs - ofs;
|
||||
} else {
|
||||
ref = PR_NewStatref (ofs, op != op_goto);
|
||||
ref = new_reloc (ofs, op == op_goto ? rel_op_a_op : rel_op_b_op);
|
||||
ref->next = l->e.label.refs;
|
||||
l->e.label.refs = ref;
|
||||
}
|
||||
|
@ -170,17 +185,17 @@ emit_function_call (expr_t *e, def_t *dest)
|
|||
parm.type = types[extract_type (earg)];
|
||||
arg = emit_sub_expr (earg, &parm);
|
||||
if (earg->type != ex_expr && earg->type != ex_uexpr) {
|
||||
op = PR_Opcode_Find ("=", arg, &parm, &def_void);
|
||||
op = opcode_find ("=", arg, &parm, &def_void);
|
||||
emit_statement (e->line, op, arg, &parm, 0);
|
||||
}
|
||||
}
|
||||
op = PR_Opcode_Find (va ("<CALL%d>", count), &def_function, &def_void,
|
||||
op = opcode_find (va ("<CALL%d>", count), &def_function, &def_void,
|
||||
&def_void);
|
||||
emit_statement (e->line, op, func, 0, 0);
|
||||
|
||||
def_ret.type = func->type->aux_type;
|
||||
if (dest) {
|
||||
op = PR_Opcode_Find ("=", dest, &def_ret, &def_void);
|
||||
op = opcode_find ("=", dest, &def_ret, &def_void);
|
||||
emit_statement (e->line, op, &def_ret, dest, 0);
|
||||
return dest;
|
||||
} else {
|
||||
|
@ -223,7 +238,7 @@ emit_assign_expr (int oper, expr_t *e)
|
|||
}
|
||||
def_b = emit_sub_expr (e2, def_a);
|
||||
if (def_b != def_a) {
|
||||
op = PR_Opcode_Find (operator, def_b, def_a, &def_void);
|
||||
op = opcode_find (operator, def_b, def_a, &def_void);
|
||||
emit_statement (e->line, op, def_b, def_a, 0);
|
||||
}
|
||||
return def_a;
|
||||
|
@ -234,11 +249,11 @@ emit_assign_expr (int oper, expr_t *e)
|
|||
if (e1->type == ex_expr && extract_type (e1->e.expr.e1) == ev_pointer) {
|
||||
def_a = emit_sub_expr (e1->e.expr.e1, 0);
|
||||
def_c = emit_sub_expr (e1->e.expr.e2, 0);
|
||||
op = PR_Opcode_Find (operator, def_b, def_a, def_c);
|
||||
op = opcode_find (operator, def_b, def_a, def_c);
|
||||
} else {
|
||||
def_a = emit_sub_expr (e1, 0);
|
||||
def_c = 0;
|
||||
op = PR_Opcode_Find (operator, def_b, def_a, &def_void);
|
||||
op = opcode_find (operator, def_b, def_a, &def_void);
|
||||
}
|
||||
emit_statement (e->line, op, def_b, def_a, def_c);
|
||||
return def_b;
|
||||
|
@ -316,7 +331,7 @@ emit_sub_expr (expr_t *e, def_t *dest)
|
|||
dest = PR_GetTempDef (e->e.expr.type, pr_scope);
|
||||
dest->users += 2;
|
||||
}
|
||||
op = PR_Opcode_Find (operator, def_a, def_b, dest);
|
||||
op = opcode_find (operator, def_a, def_b, dest);
|
||||
d = emit_statement (e->line, op, def_a, def_b, dest);
|
||||
break;
|
||||
case ex_uexpr:
|
||||
|
@ -391,7 +406,7 @@ emit_sub_expr (expr_t *e, def_t *dest)
|
|||
default:
|
||||
abort ();
|
||||
}
|
||||
op = PR_Opcode_Find (operator, def_a, def_b, dest);
|
||||
op = opcode_find (operator, def_a, def_b, dest);
|
||||
d = emit_statement (e->line, op, def_a, def_b, dest);
|
||||
break;
|
||||
case ex_def:
|
||||
|
@ -447,7 +462,6 @@ emit_expr (expr_t *e)
|
|||
def_t *def;
|
||||
def_t *def_a;
|
||||
def_t *def_b;
|
||||
statref_t *ref;
|
||||
ex_label_t *label;
|
||||
|
||||
//printf ("%d ", e->line);
|
||||
|
@ -459,24 +473,6 @@ emit_expr (expr_t *e)
|
|||
case ex_label:
|
||||
label = &e->e.label;
|
||||
label->ofs = pr.num_statements;
|
||||
for (ref = label->refs; ref; ref = ref->next) {
|
||||
switch (ref->field) {
|
||||
case 0:
|
||||
pr.statements[ref->ofs].a = label->ofs - ref->ofs;
|
||||
break;
|
||||
case 1:
|
||||
pr.statements[ref->ofs].b = label->ofs - ref->ofs;
|
||||
break;
|
||||
case 2:
|
||||
pr.statements[ref->ofs].c = label->ofs - ref->ofs;
|
||||
break;
|
||||
case 3:
|
||||
G_INT (ref->ofs) = label->ofs;
|
||||
break;
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ex_block:
|
||||
for (e = e->e.block.head; e; e = e->next)
|
||||
|
|
|
@ -384,6 +384,8 @@ new_label_expr (void)
|
|||
|
||||
l->type = ex_label;
|
||||
l->e.label.name = new_label_name ();
|
||||
l->e.label.next = pr.labels;
|
||||
pr.labels = &l->e.label;
|
||||
return l;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,10 +32,10 @@ static const char rcsid[] =
|
|||
|
||||
#include <QF/hash.h>
|
||||
|
||||
#include "qfcc.h"
|
||||
#include "def.h"
|
||||
#include "opcodes.h"
|
||||
#include "options.h"
|
||||
#include "qfcc.h"
|
||||
#include "type.h"
|
||||
|
||||
hashtab_t *opcode_type_table_ab;
|
||||
|
@ -54,30 +54,6 @@ opcode_t *op_goto;
|
|||
opcode_t *op_jump;
|
||||
opcode_t *op_jumpb;
|
||||
|
||||
statref_t *
|
||||
PR_NewStatref (int ofs, int field)
|
||||
{
|
||||
statref_t *ref = calloc (1, sizeof (statref_t));
|
||||
|
||||
ref->ofs = ofs;
|
||||
ref->field = field;
|
||||
return ref;
|
||||
}
|
||||
|
||||
void
|
||||
PR_AddStatementRef (def_t *def, dstatement_t *st, int field)
|
||||
{
|
||||
if (def) {
|
||||
statref_t *ref = PR_NewStatref (st - pr.statements, field);
|
||||
|
||||
ref->next = def->refs;
|
||||
def->refs = ref;
|
||||
|
||||
def->users--;
|
||||
def->used = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#define ROTL(x,n) (((x)<<(n))|(x)>>(32-n))
|
||||
|
||||
static unsigned long
|
||||
|
@ -120,7 +96,7 @@ compare (void *_opa, void *_opb, void *_tab)
|
|||
}
|
||||
|
||||
opcode_t *
|
||||
PR_Opcode_Find (const char *name, def_t *var_a, def_t *var_b, def_t *var_c)
|
||||
opcode_find (const char *name, def_t *var_a, def_t *var_b, def_t *var_c)
|
||||
{
|
||||
opcode_t op;
|
||||
hashtab_t **tab;
|
||||
|
@ -142,7 +118,7 @@ PR_Opcode_Find (const char *name, def_t *var_a, def_t *var_b, def_t *var_c)
|
|||
}
|
||||
|
||||
void
|
||||
PR_Opcode_Init_Tables (void)
|
||||
opcode_init (void)
|
||||
{
|
||||
opcode_t *op;
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ static const char rcsid[] =
|
|||
#include "immediate.h"
|
||||
#include "opcodes.h"
|
||||
#include "options.h"
|
||||
#include "reloc.h"
|
||||
#include "type.h"
|
||||
|
||||
options_t options;
|
||||
|
@ -311,28 +312,6 @@ PR_BeginCompilation (void)
|
|||
pr_error_count = 0;
|
||||
}
|
||||
|
||||
void
|
||||
PR_RelocateRefs (def_t *def)
|
||||
{
|
||||
statref_t *ref;
|
||||
|
||||
for (ref = def->refs; ref; ref = ref->next) {
|
||||
switch (ref->field) {
|
||||
case 0:
|
||||
pr.statements[ref->ofs].a = def->ofs;
|
||||
break;
|
||||
case 1:
|
||||
pr.statements[ref->ofs].b = def->ofs;
|
||||
break;
|
||||
case 2:
|
||||
pr.statements[ref->ofs].c = def->ofs;
|
||||
break;
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
PR_FinishCompilation
|
||||
|
||||
|
@ -346,6 +325,7 @@ qboolean PR_FinishCompilation (void)
|
|||
function_t *f;
|
||||
def_t *def;
|
||||
expr_t e;
|
||||
ex_label_t *l;
|
||||
|
||||
class_finish_module ();
|
||||
// check to make sure all functions prototyped have code
|
||||
|
@ -374,7 +354,7 @@ qboolean PR_FinishCompilation (void)
|
|||
for (def = pr.def_head; def; def = def->def_next) {
|
||||
if (def->scope || def->absolute)
|
||||
continue;
|
||||
PR_RelocateRefs (def);
|
||||
relocate_refs (def->refs, def->ofs);
|
||||
}
|
||||
|
||||
for (f = pr.func_head; f; f = f->next) {
|
||||
|
@ -389,11 +369,14 @@ qboolean PR_FinishCompilation (void)
|
|||
if (def->absolute)
|
||||
continue;
|
||||
def->ofs += pr.num_globals;
|
||||
PR_RelocateRefs (def);
|
||||
relocate_refs (def->refs, def->ofs);
|
||||
}
|
||||
}
|
||||
pr.num_globals += num_localdefs;
|
||||
|
||||
for (l = pr.labels; l; l = l->next)
|
||||
relocate_refs (l->refs, l->ofs);
|
||||
|
||||
return !errors;
|
||||
}
|
||||
|
||||
|
@ -443,7 +426,7 @@ main (int argc, char **argv)
|
|||
printf ("progs.src: %s\n", progs_src);
|
||||
}
|
||||
|
||||
PR_Opcode_Init_Tables ();
|
||||
opcode_init ();
|
||||
|
||||
InitData ();
|
||||
init_types ();
|
||||
|
|
113
tools/qfcc/source/reloc.c
Normal file
113
tools/qfcc/source/reloc.c
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
#FILENAME#
|
||||
|
||||
#DESCRIPTION#
|
||||
|
||||
Copyright (C) 2001 #AUTHOR#
|
||||
|
||||
Author: #AUTHOR#
|
||||
Date: #DATE#
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
|
||||
Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
static const char rcsid[] =
|
||||
"$Id$";
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "def.h"
|
||||
#include "expr.h"
|
||||
#include "qfcc.h"
|
||||
#include "reloc.h"
|
||||
|
||||
void
|
||||
relocate_refs (reloc_t *refs, int ofs)
|
||||
{
|
||||
int o;
|
||||
|
||||
while (refs) {
|
||||
switch (refs->type) {
|
||||
case rel_none:
|
||||
break;
|
||||
case rel_op_a_def:
|
||||
if (ofs > 65535)
|
||||
error (0, "def offset too large");
|
||||
else
|
||||
pr.statements[refs->ofs].a = ofs;
|
||||
break;
|
||||
case rel_op_b_def:
|
||||
if (ofs > 65535)
|
||||
error (0, "def offset too large");
|
||||
else
|
||||
pr.statements[refs->ofs].b = ofs;
|
||||
break;
|
||||
case rel_op_c_def:
|
||||
if (ofs > 65535)
|
||||
error (0, "def offset too large");
|
||||
else
|
||||
pr.statements[refs->ofs].c = ofs;
|
||||
break;
|
||||
case rel_op_a_op:
|
||||
o = ofs - refs->ofs;
|
||||
if (o < -32768 || o > 32767)
|
||||
error (0, "relative offset too large");
|
||||
else
|
||||
pr.statements[refs->ofs].a = o;
|
||||
break;
|
||||
case rel_op_b_op:
|
||||
o = ofs - refs->ofs;
|
||||
if (o < -32768 || o > 32767)
|
||||
error (0, "relative offset too large");
|
||||
else
|
||||
pr.statements[refs->ofs].b = o;
|
||||
break;
|
||||
case rel_op_c_op:
|
||||
o = ofs - refs->ofs;
|
||||
if (o < -32768 || o > 32767)
|
||||
error (0, "relative offset too large");
|
||||
else
|
||||
pr.statements[refs->ofs].c = o;
|
||||
break;
|
||||
case rel_def_op:
|
||||
if (ofs >= pr.num_statements)
|
||||
error (0, "invalid statement offset");
|
||||
else
|
||||
G_INT (refs->ofs) = ofs;
|
||||
break;
|
||||
case rel_def_def:
|
||||
G_INT (refs->ofs) = ofs;
|
||||
break;
|
||||
}
|
||||
refs = refs->next;
|
||||
}
|
||||
}
|
||||
|
||||
reloc_t *
|
||||
new_reloc (int ofs, reloc_type type)
|
||||
{
|
||||
reloc_t *ref = calloc (1, sizeof (reloc_t));
|
||||
|
||||
ref->ofs = ofs;
|
||||
ref->type = type;
|
||||
return ref;
|
||||
}
|
|
@ -44,13 +44,15 @@ static const char rcsid[] =
|
|||
#include <QF/hash.h>
|
||||
#include <QF/sys.h>
|
||||
|
||||
#include "qfcc.h"
|
||||
#include "def.h"
|
||||
#include "expr.h"
|
||||
#include "opcodes.h"
|
||||
#include "options.h"
|
||||
#include "type.h"
|
||||
#include "qfcc.h"
|
||||
#include "reloc.h"
|
||||
#include "switch.h"
|
||||
#include "type.h"
|
||||
|
||||
#include "qc-parse.h"
|
||||
|
||||
typedef struct case_node_s {
|
||||
|
@ -320,9 +322,9 @@ build_switch (expr_t *sw, case_node_t *tree, int op, expr_t *sw_val,
|
|||
build_switch (sw, tree->right, op, sw_val, temp, default_label);
|
||||
}
|
||||
for (i = 0; i <= high - low; i++) {
|
||||
statref_t *ref;
|
||||
reloc_t *ref;
|
||||
|
||||
ref = PR_NewStatref (G_INT (def->ofs) + i, 3);
|
||||
ref = new_reloc (G_INT (def->ofs) + i, rel_def_op);
|
||||
ref->next = tree->labels[i]->e.label.refs;
|
||||
tree->labels[i]->e.label.refs = ref;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue