use one buffer for both compiled code and data
This commit is contained in:
+3
-1
@@ -12,9 +12,11 @@
|
||||
#define RETURN_STACK_DEPTH 1024
|
||||
#define MAX_NAME_LEN 32
|
||||
#define DICT_SIZE 1024
|
||||
#define COMPILED_BUFFER_SIZE (1024*1024)
|
||||
#define HEAP_SIZE (1024*1024)
|
||||
#define MAXLINE 65535
|
||||
#define PAD_SIZE 256
|
||||
#define WORDBUF_SIZE 128
|
||||
#define INPUT_BUFFER_SIZE 256
|
||||
|
||||
#define CELL 4
|
||||
|
||||
|
||||
+12
-8
@@ -24,14 +24,18 @@ enum fh_error fh_heap_reserve(
|
||||
);
|
||||
void fh_heap_write(struct fh_thread_s *fh, uint32_t addr, const void *src, uint32_t len);
|
||||
enum fh_error fh_heap_put(struct fh_thread_s *fh, const void *src, uint32_t len);
|
||||
void fh_heap_copy_from_compile(struct fh_thread_s *fh, uint32_t addr, uint32_t srcaddr, uint32_t len);
|
||||
void fh_heap_copy(struct fh_thread_s *fh, uint32_t addr, uint32_t srcaddr, uint32_t len);
|
||||
|
||||
enum fh_error fh_compile_reserve(
|
||||
struct fh_thread_s *fh,
|
||||
size_t len,
|
||||
uint32_t *addr
|
||||
);
|
||||
void fh_compile_write(struct fh_thread_s *fh, uint32_t addr, const void *src, uint32_t len);
|
||||
enum fh_error fh_compile_put(struct fh_thread_s *fh, const void *src, uint32_t len);
|
||||
static inline char *fh_str_at(struct fh_thread_s *fh, uint32_t addr) {
|
||||
return (char *) &fh->heap[addr];
|
||||
}
|
||||
|
||||
static inline struct fh_instruction_s *fh_instr_at(struct fh_thread_s *fh, uint32_t addr) {
|
||||
return (void *) &fh->heap[addr];
|
||||
}
|
||||
|
||||
static inline struct fh_word_s *fh_word_at(struct fh_thread_s *fh, uint32_t addr) {
|
||||
return (struct fh_word_s *) &fh->heap[addr];
|
||||
}
|
||||
|
||||
#endif //FORTH_FH_MEM_H
|
||||
|
||||
+12
-7
@@ -108,8 +108,7 @@ struct fh_word_s {
|
||||
word_exec_t handler;
|
||||
/** Indicates that this is a built-in instruction and not a word call */
|
||||
bool builtin;
|
||||
/** Indicates that this instruction should always be treated as interpreted,
|
||||
* in practice this is only used for `;` */
|
||||
/** Indicates that this instruction should always be treated as interpreted */
|
||||
bool immediate;
|
||||
/** Start address in case of user words, or param for builtins */
|
||||
union {
|
||||
@@ -140,13 +139,10 @@ struct fh_thread_s {
|
||||
size_t return_stack_top;
|
||||
size_t return_stack_hwm;
|
||||
|
||||
/** Data heap */
|
||||
/** Data buffer used for everything */
|
||||
uint8_t heap[HEAP_SIZE];
|
||||
size_t heap_top; // aka "HERE"
|
||||
size_t here;
|
||||
|
||||
/** Compile buffer, used for both word data and literals */
|
||||
uint8_t compile[COMPILED_BUFFER_SIZE];
|
||||
size_t compile_top;
|
||||
/** Pointer into the compile buffer for execution */
|
||||
uint32_t execptr;
|
||||
|
||||
@@ -154,6 +150,15 @@ struct fh_thread_s {
|
||||
struct fh_word_s dict[DICT_SIZE];
|
||||
uint32_t dict_top;
|
||||
|
||||
/** Pad buffer */
|
||||
uint8_t pad[PAD_SIZE];
|
||||
/** WORD and pictured output buffer */
|
||||
uint8_t wordbuf[WORDBUF_SIZE];
|
||||
|
||||
/** Input buffer */
|
||||
uint8_t inbuf[INPUT_BUFFER_SIZE];
|
||||
uint32_t inbuf_ptr;
|
||||
|
||||
/** Forth state */
|
||||
enum fh_state state;
|
||||
|
||||
|
||||
+24
-24
@@ -368,7 +368,7 @@ static enum fh_error w_colon(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
}
|
||||
struct fh_word_s *new_word = &fh->dict[fh->dict_top];
|
||||
new_word->index = fh->dict_top;
|
||||
new_word->start = fh->compile_top;
|
||||
new_word->start = fh->here;
|
||||
new_word->handler = w_user_word;
|
||||
|
||||
return FH_OK;
|
||||
@@ -409,7 +409,7 @@ static enum fh_error w_literal(struct fh_thread_s *fh, const struct fh_word_s *w
|
||||
uint32_t val;
|
||||
TRY(ds_pop(fh, &val));
|
||||
instr_init(&instr, FH_INSTR_NUMBER, val);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
|
||||
return FH_OK;
|
||||
}
|
||||
@@ -423,7 +423,7 @@ static enum fh_error w_semicolon(struct fh_thread_s *fh, const struct fh_word_s
|
||||
ENSURE_STATE(FH_STATE_COMPILE);
|
||||
|
||||
instr_init(&instr, FH_INSTR_ENDWORD, 0);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
|
||||
/* Return to interpret state */
|
||||
fh_setstate(fh, FH_STATE_INTERPRET, 0);
|
||||
@@ -472,7 +472,7 @@ static enum fh_error w_recurse(struct fh_thread_s *fh, const struct fh_word_s *w
|
||||
ENSURE_STATE(FH_STATE_COMPILE);
|
||||
|
||||
instr_init(&instr, FH_INSTR_WORD, fh->dict_top);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
|
||||
return FH_OK;
|
||||
}
|
||||
@@ -724,7 +724,7 @@ static enum fh_error w_type(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
TRY(ds_pop(fh, &count));
|
||||
TRY(ds_pop(fh, &addr));
|
||||
|
||||
FHPRINT("%.*s", count, &fh->heap[addr]);
|
||||
FHPRINT("%.*s", count, fh_str_at(fh, addr));
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
@@ -850,9 +850,9 @@ static enum fh_error w_if(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
|
||||
ENSURE_STATE(FH_STATE_COMPILE);
|
||||
|
||||
TRY(cs_push(fh, fh->compile_top));
|
||||
TRY(cs_push(fh, fh->here));
|
||||
instr_init(&instr, FH_INSTR_JUMPZERO, MAGICADDR_UNRESOLVED);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
@@ -866,17 +866,17 @@ static enum fh_error w_else(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
|
||||
uint32_t ifaddr = 0;
|
||||
TRY(cs_pop(fh, &ifaddr));
|
||||
struct fh_instruction_s *if_instr = (void *) &fh->compile[ifaddr];
|
||||
struct fh_instruction_s *if_instr = fh_instr_at(fh, ifaddr);
|
||||
if (if_instr->data != MAGICADDR_UNRESOLVED) {
|
||||
LOGE("IF-ELSE control stack corruption");
|
||||
return FH_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
if_instr->data = fh->compile_top + INSTR_SIZE;
|
||||
if_instr->data = fh->here + INSTR_SIZE;
|
||||
|
||||
TRY(cs_push(fh, fh->compile_top));
|
||||
TRY(cs_push(fh, fh->here));
|
||||
instr_init(&instr, FH_INSTR_JUMP, MAGICADDR_UNRESOLVED);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
@@ -889,13 +889,13 @@ static enum fh_error w_then(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
|
||||
uint32_t ifaddr = 0;
|
||||
TRY(cs_pop(fh, &ifaddr));
|
||||
struct fh_instruction_s *if_instr = (void *) &fh->compile[ifaddr];
|
||||
struct fh_instruction_s *if_instr = fh_instr_at(fh, ifaddr);
|
||||
if (if_instr->data != MAGICADDR_UNRESOLVED) {
|
||||
LOGE("IF-ELSE control stack corruption");
|
||||
return FH_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
if_instr->data = fh->compile_top;
|
||||
if_instr->data = fh->here;
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
@@ -911,7 +911,7 @@ static enum fh_error w_until(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
TRY(cs_pop(fh, &destaddr));
|
||||
|
||||
instr_init(&instr, FH_INSTR_JUMPZERO, destaddr);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
@@ -920,7 +920,7 @@ static enum fh_error w_begin(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
(void) w;
|
||||
enum fh_error rv;
|
||||
ENSURE_STATE(FH_STATE_COMPILE);
|
||||
TRY(cs_push(fh, fh->compile_top)); /* dest */
|
||||
TRY(cs_push(fh, fh->here)); /* dest */
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
@@ -935,11 +935,11 @@ static enum fh_error w_while(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
uint32_t destaddr = 0;
|
||||
TRY(cs_pop(fh, &destaddr));
|
||||
|
||||
TRY(cs_push(fh, fh->compile_top)); // orig
|
||||
TRY(cs_push(fh, fh->here)); // orig
|
||||
TRY(cs_push(fh, destaddr)); // dest
|
||||
|
||||
instr_init(&instr, FH_INSTR_JUMPZERO, MAGICADDR_UNRESOLVED);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
@@ -956,15 +956,15 @@ static enum fh_error w_repeat(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
TRY(cs_pop(fh, &destaddr));
|
||||
TRY(cs_pop(fh, &origaddr));
|
||||
|
||||
struct fh_instruction_s *branch_instr = (void *) &fh->compile[origaddr];
|
||||
struct fh_instruction_s *branch_instr = fh_instr_at(fh, origaddr);
|
||||
if (branch_instr->data != MAGICADDR_UNRESOLVED) {
|
||||
LOGE("REPEAT control stack corruption");
|
||||
return FH_ERR_INTERNAL;
|
||||
}
|
||||
branch_instr->data = fh->compile_top + INSTR_SIZE;
|
||||
branch_instr->data = fh->here + INSTR_SIZE;
|
||||
|
||||
instr_init(&instr, FH_INSTR_JUMP, destaddr);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
|
||||
return FH_OK;
|
||||
}
|
||||
@@ -981,7 +981,7 @@ static enum fh_error w_again(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
TRY(cs_pop(fh, &destaddr));
|
||||
|
||||
instr_init(&instr, FH_INSTR_JUMP, destaddr);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
|
||||
return FH_OK;
|
||||
}
|
||||
@@ -1031,7 +1031,7 @@ static enum fh_error w_unused(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
{
|
||||
(void) w;
|
||||
enum fh_error rv;
|
||||
TRY(ds_push(fh, HEAP_SIZE - fh->heap_top));
|
||||
TRY(ds_push(fh, HEAP_SIZE - fh->here));
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
@@ -1126,7 +1126,7 @@ static enum fh_error w_comma(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
(void) w;
|
||||
enum fh_error rv;
|
||||
|
||||
if (fh->heap_top & 3) {
|
||||
if (fh->here & 3) {
|
||||
LOGE("HERE not aligned before 'comma'");
|
||||
return FH_ERR_ILLEGAL_STORE;
|
||||
}
|
||||
@@ -1141,7 +1141,7 @@ static enum fh_error w_align(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
{
|
||||
(void) w;
|
||||
enum fh_error rv;
|
||||
fh->heap_top = WORDALIGNED(fh->heap_top);
|
||||
fh->here = WORDALIGNED(fh->here);
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
|
||||
+5
-43
@@ -18,7 +18,7 @@ enum fh_error fh_fetch(struct fh_thread_s *fh, uint32_t addr, uint32_t *dst)
|
||||
break;
|
||||
|
||||
case MAGICADDR_HERE:
|
||||
*dst = fh->heap_top;
|
||||
*dst = fh->here;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -95,7 +95,7 @@ enum fh_error fh_heap_reserve(
|
||||
uint32_t *addr
|
||||
)
|
||||
{
|
||||
uint32_t p = fh->heap_top;
|
||||
uint32_t p = fh->here;
|
||||
|
||||
if (p + len > HEAP_SIZE) {
|
||||
return FH_ERR_HEAP_FULL;
|
||||
@@ -105,7 +105,7 @@ enum fh_error fh_heap_reserve(
|
||||
*addr = p;
|
||||
}
|
||||
|
||||
fh->heap_top = WORDALIGNED(p + len);
|
||||
fh->here = WORDALIGNED(p + len);
|
||||
|
||||
return FH_OK;
|
||||
}
|
||||
@@ -127,45 +127,7 @@ enum fh_error fh_heap_put(struct fh_thread_s *fh, const void *src, uint32_t len)
|
||||
}
|
||||
|
||||
/** Copy bytes from compile area to heap. The region must have been previously allocated! */
|
||||
void fh_heap_copy_from_compile(struct fh_thread_s *fh, uint32_t addr, uint32_t srcaddr, uint32_t len)
|
||||
void fh_heap_copy(struct fh_thread_s *fh, uint32_t addr, uint32_t srcaddr, uint32_t len)
|
||||
{
|
||||
memcpy(&fh->heap[addr], &fh->compile[srcaddr], len);
|
||||
}
|
||||
|
||||
/** Reserve space in the compile memory area */
|
||||
enum fh_error fh_compile_reserve(
|
||||
struct fh_thread_s *fh,
|
||||
size_t len,
|
||||
uint32_t *addr
|
||||
)
|
||||
{
|
||||
uint32_t p = fh->compile_top; // FIXME this shouldn't be needed
|
||||
|
||||
if (p + len > COMPILED_BUFFER_SIZE) {
|
||||
return FH_ERR_HEAP_FULL;
|
||||
}
|
||||
|
||||
if (addr) {
|
||||
*addr = p;
|
||||
}
|
||||
|
||||
fh->compile_top = WORDALIGNED(p + len);
|
||||
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
/** Write bytes to compile area at a given location. The region must have been previously allocated! */
|
||||
void fh_compile_write(struct fh_thread_s *fh, uint32_t addr, const void *src, uint32_t len)
|
||||
{
|
||||
memcpy(&fh->compile[addr], src, len);
|
||||
}
|
||||
|
||||
/** Allocate compile region and write bytes to it */
|
||||
enum fh_error fh_compile_put(struct fh_thread_s *fh, const void *src, uint32_t len)
|
||||
{
|
||||
enum fh_error rv;
|
||||
uint32_t addr;
|
||||
TRY(fh_compile_reserve(fh, len, &addr));
|
||||
fh_compile_write(fh, addr, src, len);
|
||||
return FH_OK;
|
||||
memcpy(&fh->heap[addr], &fh->heap[srcaddr], len);
|
||||
}
|
||||
|
||||
+14
-14
@@ -93,7 +93,7 @@ enum fh_error w_user_word(struct fh_thread_s *fh, const struct fh_word_s *w0)
|
||||
}
|
||||
// make sure it's aligned
|
||||
fh->execptr = WORDALIGNED(fh->execptr);
|
||||
const struct fh_instruction_s *instr = (const struct fh_instruction_s *) &fh->compile[fh->execptr];
|
||||
const struct fh_instruction_s *instr = fh_instr_at(fh, fh->execptr);
|
||||
fh->execptr += INSTR_SIZE;
|
||||
|
||||
uint32_t strl;
|
||||
@@ -115,7 +115,7 @@ enum fh_error w_user_word(struct fh_thread_s *fh, const struct fh_word_s *w0)
|
||||
} else {
|
||||
LOG("Add postponed word: %s", w2->name);
|
||||
instr_init(&instr2, FH_INSTR_WORD, instr->data);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
}
|
||||
} else {
|
||||
LOGE("Postpone in interpret mode!");
|
||||
@@ -169,13 +169,13 @@ enum fh_error w_user_word(struct fh_thread_s *fh, const struct fh_word_s *w0)
|
||||
strl = instr->data;
|
||||
if (instr->kind == FH_INSTR_ALLOCSTR) {
|
||||
TRY(fh_heap_reserve(fh, strl, &addr));
|
||||
fh_heap_copy_from_compile(fh, addr, fh->execptr, strl);
|
||||
LOG("Exec: alloc-str \"%.*s\"", strl, &fh->heap[addr]);
|
||||
LOG("Exec: alloc-str \"%.*s\"", strl, fh_str_at(fh, fh->execptr));
|
||||
fh_heap_copy(fh, addr, fh->execptr, strl);
|
||||
TRY(ds_push(fh, addr));
|
||||
TRY(ds_push(fh, strl));
|
||||
} else {
|
||||
LOG("Exec: type-str \"%.*s\"", strl, &fh->compile[fh->execptr]);
|
||||
FHPRINT("%.*s", (int) strl, &fh->compile[fh->execptr]);
|
||||
LOG("Exec: type-str \"%.*s\"", strl, fh_str_at(fh, fh->execptr));
|
||||
FHPRINT("%.*s", (int) strl, fh_str_at(fh, fh->execptr));
|
||||
}
|
||||
fh->execptr += strl;
|
||||
goto instr;
|
||||
@@ -242,8 +242,8 @@ static enum fh_error fh_handle_quoted_string(
|
||||
} else {
|
||||
instr_init(&instr, FH_INSTR_TYPESTR, len);
|
||||
}
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_compile_put(fh, start, len));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, start, len));
|
||||
}
|
||||
return FH_OK;
|
||||
}
|
||||
@@ -255,7 +255,7 @@ enum fh_error fh_handle_word(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
if (fh->state == FH_STATE_COMPILE && !w->immediate) {
|
||||
LOG("Compile word call: %s", w->name);
|
||||
instr_init(&instr, FH_INSTR_WORD, w->index);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
} else {
|
||||
/* interpret or immediate in compiled code */
|
||||
LOG("Run word: %s", w->name);
|
||||
@@ -324,7 +324,7 @@ enum fh_error fh_handle_ascii_word(
|
||||
if (fh->state == FH_STATE_COMPILE) {
|
||||
LOG("Compile number: %ld", v);
|
||||
instr_init(&instr, FH_INSTR_NUMBER, (uint32_t) v);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
} else {
|
||||
/* interpret */
|
||||
LOG("Interpret number: %ld", v);
|
||||
@@ -343,7 +343,7 @@ static void show_word(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
// make sure it's aligned
|
||||
execptr = WORDALIGNED(execptr);
|
||||
FHPRINT("0x%08x: ", execptr);
|
||||
const struct fh_instruction_s *instr = (const struct fh_instruction_s *) &fh->compile[execptr];
|
||||
const struct fh_instruction_s *instr = fh_instr_at(fh, execptr);
|
||||
execptr += INSTR_SIZE;
|
||||
|
||||
uint32_t strl;
|
||||
@@ -385,10 +385,10 @@ static void show_word(struct fh_thread_s *fh, const struct fh_word_s *w)
|
||||
case FH_INSTR_TYPESTR:
|
||||
strl = instr->data;
|
||||
if (instr->kind == FH_INSTR_ALLOCSTR) {
|
||||
FHPRINT("AllocStr(\"%.*s\")\n", strl, &fh->compile[execptr]);
|
||||
FHPRINT("AllocStr(\"%.*s\")\n", strl, fh_str_at(fh, execptr));
|
||||
execptr += strl;
|
||||
} else {
|
||||
FHPRINT("PrintStr(\"%.*s\")\n", strl, &fh->compile[execptr]);
|
||||
FHPRINT("PrintStr(\"%.*s\")\n", strl, fh_str_at(fh, execptr));
|
||||
execptr += strl;
|
||||
}
|
||||
goto instr;
|
||||
@@ -434,7 +434,7 @@ static enum fh_error fh_postpone_word(
|
||||
struct fh_instruction_s instr;
|
||||
LOG("Postpone %s", w->name);
|
||||
instr_init(&instr, FH_INSTR_POSTPONED_WORD, w->index);
|
||||
TRY(fh_compile_put(fh, &instr, INSTR_SIZE));
|
||||
TRY(fh_heap_put(fh, &instr, INSTR_SIZE));
|
||||
|
||||
return FH_OK;
|
||||
}
|
||||
|
||||
+2
-2
@@ -84,9 +84,9 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Show resource usage
|
||||
LOG("\nResources used: DS %dW, RS %dW, CS %dW, heap %dB, program %dB, dict %dx\n",
|
||||
LOG("\nResources used: DS %dW, RS %dW, CS %dW, memory %dB\n",
|
||||
(int) fh.data_stack_hwm, (int) fh.return_stack_hwm, (int) fh.control_stack_hwm,
|
||||
(int) fh.heap_top, (int) fh.compile_top, (int) fh.dict_top);
|
||||
(int) fh.here);
|
||||
|
||||
FHPRINT_SVC("Bye.\n");
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user