Compare commits
9 Commits
cdcc1f6d3d
...
7569420fe0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7569420fe0 | ||
![]() |
fb6fd76d0b | ||
![]() |
e80f6643dc | ||
![]() |
8d8d1cf067 | ||
![]() |
1708faf14d | ||
![]() |
14ab1f432d | ||
![]() |
f03e8517df | ||
![]() |
273e6d1058 | ||
![]() |
251d24fb30 |
@ -2,7 +2,7 @@
|
||||
* ListDLC: Dynamic, Linear growth, C-managed
|
||||
*/
|
||||
|
||||
extern u8*(u8*, u32) realloc;
|
||||
extern u8*(u8*, ugpr) realloc;
|
||||
|
||||
record ListDLC[T, S; growth] {
|
||||
S capacity;
|
||||
@ -31,7 +31,7 @@ ListDLC_remove: [T, S; growth]u0(ListDLC[T, S; growth]* this, S index) -> {
|
||||
|
||||
ListDLC_add: [T, S; growth]u0(ListDLC[T, S; growth]* this, T value) -> {
|
||||
if((*this).size == (*this).capacity) {
|
||||
u32 newcap = (*this).capacity + growth;
|
||||
S newcap = (*this).capacity + growth;
|
||||
(*this).capacity = newcap;
|
||||
(*this).data = realloc((*this).data, newcap * @sizeof T);
|
||||
}
|
||||
|
31
examples/MapSOaLDhS.nct
Normal file
31
examples/MapSOaLDhS.nct
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SOaLDhS: Statically-allocated, Open-addressing, Linear probing, Dynamic hash, sentinels
|
||||
*/
|
||||
|
||||
record KVPair[K, V] {
|
||||
K key;
|
||||
V value;
|
||||
}
|
||||
|
||||
record MapSOaLDhS[K, V, S; capacity, sentinel] {
|
||||
S(K*) hash;
|
||||
KVPair[capacity] data;
|
||||
}
|
||||
|
||||
MapSOaLDhS_add: [K, V, S; capacity, sentinel]u1(MapSOaLDhS[K, V, S; capacity, sentinel]* this, K* key, V* value) -> {
|
||||
S index = ((*this).hash)(value);
|
||||
index = index & (capacity - 1);
|
||||
loop {
|
||||
KVPair[K, V]* pair = &((*this).data[index]);
|
||||
if(((*pair).key == *key) || ((*pair).value == sentinel)) {
|
||||
(*pair).key = *key;
|
||||
(*pair).value = *value;
|
||||
}
|
||||
index = index + 1;
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
@instantiate MapSOaLDhS_add[ugpr, ugpr, ugpr; 128, 0];
|
||||
|
||||
MapSOaLDhS[ugpr, ugpr, ugpr; 128, 0] map;
|
@ -2,16 +2,16 @@ use ListDLC;
|
||||
|
||||
@section(".text");
|
||||
|
||||
@instantiate ListDLC_remove[u32, u32; 9];
|
||||
@instantiate ListDLC_add[u32, u32; 9];
|
||||
@instantiate ListDLC_remove[u16, u16; 9];
|
||||
@instantiate ListDLC_add[u16, u16; 9];
|
||||
|
||||
main: u0() -> {
|
||||
ListDLC[u32, u32; 9] list;
|
||||
ListDLC_add[u32, u32; 9](&list, 1234);
|
||||
ListDLC_add[u32, u32; 9](&list, 4321);
|
||||
ListDLC_add[u32, u32; 9](&list, 7777);
|
||||
ListDLC_add[u32, u32; 9](&list, 6969);
|
||||
ListDLC_remove[u32, u32; 9](&list, 1);
|
||||
ListDLC[u16, u16; 9] list;
|
||||
ListDLC_add[u16, u16; 9](&list, 1234);
|
||||
ListDLC_add[u16, u16; 9](&list, 4321);
|
||||
ListDLC_add[u16, u16; 9](&list, 7777);
|
||||
ListDLC_add[u16, u16; 9](&list, 6969);
|
||||
ListDLC_remove[u16, u16; 9](&list, 1);
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
fibonacci: u32(u32 n) -> {
|
||||
fibonacci: u16(u16 n) -> {
|
||||
if(n <= 1) {
|
||||
return n;
|
||||
}
|
||||
|
@ -177,6 +177,11 @@ static void *memdup(void *a, size_t len) {
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* WARNING: Just because you deep copy an AST node, does not mean
|
||||
* ast_expression_equal will return true! This matters for example with
|
||||
* function calls (a function call is not equal to itself).
|
||||
*/
|
||||
AST *ast_deep_copy(AST *src) {
|
||||
if(src->nodeKind == AST_EXPR_VAR) {
|
||||
return memdup(src, sizeof(ASTExprVar));
|
||||
|
@ -56,6 +56,8 @@ static char *ast_dumpe(AST *tlc, AST *e) {
|
||||
return strdup(vte->data.var.name);
|
||||
} else if(vte->kind == SCOPEITEM_SYMBOL) {
|
||||
return strdup(vte->data.symbol.name);
|
||||
} else if(vte->kind == SCOPEITEM_CEXPR) {
|
||||
return ast_dumpe(tlc, vte->data.cexpr.concrete);
|
||||
} else abort();
|
||||
} else if(e->nodeKind == AST_EXPR_UNARY_OP) {
|
||||
const char *op = NULL;
|
||||
@ -274,6 +276,18 @@ static char *ast_dumps(AST *tlc, AST *s) {
|
||||
return malp("%s; /* loop guard */", name);
|
||||
} else if(s->nodeKind == AST_STMT_EXPR) {
|
||||
return ast_dumpe(tlc, s->stmtExpr.expr);
|
||||
} else if(s->nodeKind == AST_STMT_DECL) {
|
||||
char *a = type_to_string(s->stmtDecl.thing->type);
|
||||
char *c;
|
||||
if(s->stmtDecl.expression) {
|
||||
char *b = ast_dumpe(tlc, s->stmtDecl.expression);
|
||||
c = malp("%s %s = %s;", a, s->stmtDecl.thing->data.var.name, b);
|
||||
free(b);
|
||||
} else {
|
||||
c = malp("%s %s;", a, s->stmtDecl.thing->data.var.name);
|
||||
}
|
||||
free(a);
|
||||
return c;
|
||||
} else if(s->nodeKind == AST_STMT_RETURN) {
|
||||
if(s->stmtReturn.val) {
|
||||
char *e = ast_dumpe(tlc, s->stmtReturn.val);
|
||||
|
@ -31,7 +31,7 @@ static void spill2stack_visitor(AST **aptr, AST *stmt, AST *stmtPrev, AST *chunk
|
||||
|
||||
ASTExprStackPointer *rsp = calloc(1, sizeof(*rsp));
|
||||
rsp->nodeKind = AST_EXPR_STACK_POINTER;
|
||||
rsp->type = type_u(arch_gpr_size());
|
||||
rsp->type = type_u(8 * arch_gpr_size());
|
||||
|
||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
|
13
src/parse.c
13
src/parse.c
@ -152,9 +152,10 @@ static char *parametrize_function_name(Type *t, const char *original, Scope *sco
|
||||
static void binop_implicit_cast(/*Parser *P, */ASTExprBinaryOp *binop) {
|
||||
if(type_size(binop->operands[0]->expression.type) < type_size(binop->operands[1]->expression.type)) {
|
||||
binop->operands[0] = ast_cast_expr(binop->operands[0], binop->operands[1]->expression.type);
|
||||
}
|
||||
|
||||
if(type_size(binop->operands[1]->expression.type) < type_size(binop->operands[0]->expression.type)) {
|
||||
} else if(type_size(binop->operands[1]->expression.type) < type_size(binop->operands[0]->expression.type)) {
|
||||
binop->operands[1] = ast_cast_expr(binop->operands[1], binop->operands[0]->expression.type);
|
||||
} else {
|
||||
binop->operands[0] = ast_cast_expr(binop->operands[0], binop->operands[0]->expression.type);
|
||||
binop->operands[1] = ast_cast_expr(binop->operands[1], binop->operands[0]->expression.type);
|
||||
}
|
||||
|
||||
@ -228,7 +229,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
|
||||
ASTExprStackPointer *ret = alloc_node(P, sizeof(*ret));
|
||||
ret->nodeKind = AST_EXPR_STACK_POINTER;
|
||||
ret->type = type_u(arch_sp_size());
|
||||
ret->type = type_u(8 * arch_sp_size());
|
||||
|
||||
e = (AST*) ret;
|
||||
} else if(!strcmp(peek(P, 0).content, "@salloc")) {
|
||||
@ -255,7 +256,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
ret->ofExpr = nct_parse_expression(P, lOP - 1);
|
||||
}
|
||||
|
||||
ret->type = type_u(arch_gpr_size());
|
||||
ret->type = type_u(8 * arch_gpr_size());
|
||||
|
||||
e = (AST*) ret;
|
||||
} else {
|
||||
@ -461,7 +462,7 @@ AST *nct_parse_expression(Parser *P, int lOP) {
|
||||
if(typesize != 1) {
|
||||
ASTExprPrimitive *scale = alloc_node(P, sizeof(*scale));
|
||||
scale->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
scale->type = type_u(arch_gpr_size());
|
||||
scale->type = type_u(8 * arch_gpr_size());
|
||||
scale->val = typesize;
|
||||
|
||||
ASTExprBinaryOp *mul = alloc_node(P, sizeof(*mul));
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include<string.h>
|
||||
#include<assert.h>
|
||||
#include<stdio.h>
|
||||
#include"x86/arch.h"
|
||||
|
||||
Scope *scope_new(Scope *parent) {
|
||||
Scope *ret = calloc(1, sizeof(*ret));
|
||||
@ -81,7 +82,12 @@ void vte_precolor(ScopeItem *vte, int class, int color) {
|
||||
assert(vte->kind == SCOPEITEM_VAR && "vte must be var");
|
||||
assert(!vte->data.var.precolored && "already precolored");
|
||||
|
||||
if(type_size(vte->type) > 0) {
|
||||
assert(type_size(vte->type) == REG_CLASSES[class].rsS[color] && "Sizes must match in precoloring");
|
||||
}
|
||||
|
||||
vte->data.var.precolored = true;
|
||||
vte->data.var.preclassed = true;
|
||||
vte->data.var.registerClass = class;
|
||||
vte->data.var.color = color;
|
||||
}
|
||||
|
39
src/types.c
39
src/types.c
@ -7,6 +7,7 @@
|
||||
#include"ast/ast.h"
|
||||
#include"reporting.h"
|
||||
#include<assert.h>
|
||||
#include"x86/arch.h"
|
||||
|
||||
#include"ntc.h"
|
||||
|
||||
@ -23,6 +24,12 @@ Type *primitive_parse(const char *src) {
|
||||
}
|
||||
}
|
||||
|
||||
if(!strcmp(src, "ugpr")) {
|
||||
return type_u(8 * arch_gpr_size());
|
||||
} else if(!strcmp(src, "umem")) {
|
||||
return type_u(arch_memory_width());
|
||||
}
|
||||
|
||||
TypePrimitive *ret = calloc(1, sizeof(*ret));
|
||||
ret->type = TYPE_TYPE_PRIMITIVE;
|
||||
ret->src = strdup(src);
|
||||
@ -286,17 +293,19 @@ Type *type_parametrize(Type *t, Scope *scope) {
|
||||
|
||||
AST *n = vte->data.cexpr.concrete;
|
||||
|
||||
if(n) {
|
||||
if(n->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
t->array.length = n->exprPrim.val;
|
||||
t->array.lengthIsGeneric = false;
|
||||
} else if(n->nodeKind == AST_EXPR_VAR && n->exprVar.thing->kind == SCOPEITEM_CEXPR) {
|
||||
t->array.lengthGenericParamIdx = n->exprVar.thing->data.cexpr.paramIdx;
|
||||
t->array.lengthGenericParamName = n->exprVar.thing->data.cexpr.paramName;
|
||||
t->array.lengthIsGeneric = true;
|
||||
} else {
|
||||
stahp_node(n, "Invalid parametrization expression.");
|
||||
}
|
||||
while(n->nodeKind == AST_EXPR_VAR && n->exprVar.thing->kind == SCOPEITEM_CEXPR && n->exprVar.thing->data.cexpr.concrete != NULL) {
|
||||
n = n->exprVar.thing->data.cexpr.concrete;
|
||||
}
|
||||
|
||||
if(n->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
t->array.length = n->exprPrim.val;
|
||||
t->array.lengthIsGeneric = false;
|
||||
} else if(n->nodeKind == AST_EXPR_VAR && n->exprVar.thing->kind == SCOPEITEM_CEXPR) {
|
||||
t->array.lengthGenericParamIdx = n->exprVar.thing->data.cexpr.paramIdx;
|
||||
t->array.lengthGenericParamName = n->exprVar.thing->data.cexpr.paramName;
|
||||
t->array.lengthIsGeneric = true;
|
||||
} else {
|
||||
stahp_node(n, "Invalid parametrization expression.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -357,3 +366,11 @@ Type *type_shallow_copy(Type *t) {
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
Type *type_prim_cast(Type *t, size_t bits) {
|
||||
assert(t->type == TYPE_TYPE_PRIMITIVE);
|
||||
|
||||
Type *t2 = type_shallow_copy(t);
|
||||
t2->primitive.width = bits;
|
||||
return t2;
|
||||
}
|
||||
|
12
src/types.h
12
src/types.h
@ -111,13 +111,21 @@ Type *type_shallow_copy(Type *t);
|
||||
|
||||
bool type_is_generic(Type *t);
|
||||
|
||||
Type *type_prim_cast(Type *t, size_t bits);
|
||||
|
||||
static inline bool type_is_segmented_pointer(Type *type) {
|
||||
return type->type == TYPE_TYPE_RECORD && !!strstr(type->record.name, " @far*");
|
||||
}
|
||||
|
||||
static inline Type *type_u(size_t size) {
|
||||
static inline Type *type_u(size_t bits) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "u%lu", size);
|
||||
snprintf(buf, sizeof(buf), "u%lu", bits);
|
||||
return primitive_parse(buf);
|
||||
}
|
||||
|
||||
static inline Type *type_s(size_t bits) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "s%lu", bits);
|
||||
return primitive_parse(buf);
|
||||
}
|
||||
|
||||
|
@ -241,9 +241,23 @@ static inline WhysItBad_Huh x86_test_mul(AST *stmtPrev, AST *stmt) {
|
||||
}
|
||||
|
||||
if(x86_imul_supported()) {
|
||||
return GUCCI;
|
||||
} else if(stmt->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
return SRC1_IS_BAD_XOP;
|
||||
if(ast_expression_equal(stmt->stmtAssign.what, stmt->stmtAssign.to->exprBinOp.operands[0])) {
|
||||
if(is_xop(stmt->stmtAssign.to->exprBinOp.operands[1]) == XOP_NOT_XOP) {
|
||||
return SRC1_IS_BAD_XOP;
|
||||
}
|
||||
return GUCCI;
|
||||
} else {
|
||||
if(is_xop(stmt->stmtAssign.to->exprBinOp.operands[0]) == XOP_NOT_XOP) {
|
||||
return SRC0_IS_BAD_XOP;
|
||||
}
|
||||
if(stmt->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
return GUCCI;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(stmt->stmtAssign.to->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
return SRC1_IS_BAD_XOP;
|
||||
}
|
||||
}
|
||||
|
||||
if(is_xop(stmt->stmtAssign.to->exprBinOp.operands[1]) == XOP_NOT_XOP) {
|
||||
@ -294,6 +308,18 @@ static inline int arch_gpr_size() {
|
||||
return x86_max_gpr_size();
|
||||
}
|
||||
|
||||
static inline int arch_memory_width() {
|
||||
switch(x86_target()) {
|
||||
case I086:
|
||||
case I186:
|
||||
return 20;
|
||||
case I286:
|
||||
return 24;
|
||||
default:
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
|
||||
// lol
|
||||
static inline bool is_reg_b(int cls, int res) {
|
||||
const char *name = REG_CLASSES[cls].rsN[res];
|
||||
@ -333,4 +359,4 @@ static inline void reg_cast_to_gpr(int *cls, int *res) {
|
||||
}
|
||||
}
|
||||
|
||||
void arch_init();
|
||||
void arch_init();
|
||||
|
217
src/x86/cg.c
217
src/x86/cg.c
@ -12,6 +12,11 @@ static const char *BINOP_SIMPLE_INSTRS[] = {[BINOP_ADD] = "add", [BINOP_SUB] = "
|
||||
|
||||
/*static size_t nextLocalLabel = 0;*/
|
||||
|
||||
struct CalleeSavedState {
|
||||
const char *reg[MAX_REGS_PER_CLASS];
|
||||
size_t stackOffset[MAX_REGS_PER_CLASS];
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
/*#define LOOPSTACKSIZE 96
|
||||
size_t loopStackStart[LOOPSTACKSIZE];
|
||||
@ -21,6 +26,8 @@ typedef struct {
|
||||
int isFunction;
|
||||
|
||||
AST *tlc;
|
||||
|
||||
struct CalleeSavedState calleeSaved;
|
||||
} CGState;
|
||||
|
||||
static const char *direct(int size) {
|
||||
@ -129,6 +136,10 @@ static AST *is_field_access(AST *e) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a XOP-able expression into an x86 operand.
|
||||
* Result MUST be determinstic and always the same, for the same given expression.
|
||||
* */
|
||||
static const char *xop_sz(AST *tlc, AST *e, int sz) {
|
||||
#define XOPBUFS 16
|
||||
#define XOPBUFSZ 32
|
||||
@ -254,6 +265,17 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
} else {
|
||||
printf("sub esp, %lu\n", a->chunk.stackReservation);
|
||||
}
|
||||
|
||||
for(int i = 0; i < MAX_REGS_PER_CLASS && cg->calleeSaved.reg[i]; i++) {
|
||||
if(x86_ia16()) {
|
||||
printf("mov [bp + %li], %s\n", cg->calleeSaved.stackOffset[i] - a->chunk.stackReservation, cg->calleeSaved.reg[i]);
|
||||
} else {
|
||||
printf("mov [esp + %li], %s\n", cg->calleeSaved.stackOffset[i], cg->calleeSaved.reg[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If there's no stack reservation, there can't be callee-saved regs.
|
||||
assert(cg->calleeSaved.reg[0] == NULL);
|
||||
}
|
||||
|
||||
// Potentially complex pattern matching
|
||||
@ -412,7 +434,7 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
assert(s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR);
|
||||
|
||||
if(!strcmp(xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[0]))) {
|
||||
printf("imul %s, %s, %s\n", xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[1]));
|
||||
printf("imul %s, %s\n", xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[1]));
|
||||
} else {
|
||||
printf("imul %s, %s, %s\n", xop(a, s->stmtAssign.what), xop(a, s->stmtAssign.to->exprBinOp.operands[0]), xop(a, s->stmtAssign.to->exprBinOp.operands[1]));
|
||||
}
|
||||
@ -475,10 +497,18 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
|
||||
} else if(is_xop(s->stmtAssign.what) && s->stmtAssign.to->nodeKind == AST_EXPR_CAST) {
|
||||
|
||||
if(type_size(s->stmtAssign.what->expression.type) > type_size(s->stmtAssign.to->expression.type)) {
|
||||
if(type_size(s->stmtAssign.what->expression.type) > type_size(s->stmtAssign.to->exprCast.what->expression.type)) {
|
||||
|
||||
printf("movzx %s, %s\n", xop(cg->tlc, s->stmtAssign.what), xop(cg->tlc, s->stmtAssign.to->exprCast.what));
|
||||
|
||||
} else if(type_size(s->stmtAssign.what->expression.type) > type_size(s->stmtAssign.to->exprCast.what->expression.type)) {
|
||||
|
||||
/*
|
||||
* LEMMA: For every register in x86, its lowest bits are accessible.
|
||||
*/
|
||||
|
||||
assert(0 && "Not implemented.");
|
||||
|
||||
} else {
|
||||
|
||||
const char *dest = xop_sz(cg->tlc, s->stmtAssign.what, x86_max_gpr_size());
|
||||
@ -542,10 +572,17 @@ void cg_chunk(CGState *cg, AST *a) {
|
||||
if(s->stmtReturn.val) {
|
||||
assert(s->stmtReturn.val->nodeKind == AST_EXPR_VAR);
|
||||
assert(s->stmtReturn.val->exprVar.thing->kind == SCOPEITEM_VAR);
|
||||
//assert(s->stmtReturn.val->exprVar.thing->data.var.color == COLOR_EAX);
|
||||
}
|
||||
|
||||
if(a->chunk.stackReservation) {
|
||||
for(int i = 0; i < MAX_REGS_PER_CLASS && cg->calleeSaved.reg[i]; i++) {
|
||||
if(x86_ia16()) {
|
||||
printf("mov [bp + %li], %s\n", cg->calleeSaved.stackOffset[i] - a->chunk.stackReservation, cg->calleeSaved.reg[i]);
|
||||
} else {
|
||||
printf("mov [esp + %li], %s\n", cg->calleeSaved.stackOffset[i], cg->calleeSaved.reg[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if(x86_ia16()) {
|
||||
printf("add sp, %lu\n", a->chunk.stackReservation);
|
||||
} else {
|
||||
@ -599,170 +636,40 @@ static bool var_collision(AST *tlc, ScopeItem *v1, ScopeItem *v2) {
|
||||
return liveRangeIntersection && resourceIntersection;
|
||||
}
|
||||
|
||||
struct CalleeSavedState {
|
||||
AST *targetTLC;
|
||||
|
||||
ScopeItem *calleeUsers[MAX_REGS_PER_CLASS];
|
||||
size_t calleeOffsets[MAX_REGS_PER_CLASS];
|
||||
|
||||
// To make sure we don't process the same return statement to infinity
|
||||
AST *lastProcessedReturn;
|
||||
};
|
||||
static void callee_saved_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
struct CalleeSavedState *this = ud;
|
||||
|
||||
AST *n = *nptr;
|
||||
|
||||
if(tlc != this->targetTLC) {
|
||||
// Don't do anything.
|
||||
return;
|
||||
}
|
||||
|
||||
if(n == tlc) {
|
||||
// Function entry
|
||||
|
||||
for(int i = 0; i < MAX_REGS_PER_CLASS; i++) {
|
||||
ScopeItem *vte = this->calleeUsers[i];
|
||||
|
||||
if(!vte) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ASTExprStackPointer *stk = calloc(1, sizeof(*stk));
|
||||
stk->nodeKind = AST_EXPR_STACK_POINTER;
|
||||
stk->type = primitive_parse("u32");
|
||||
|
||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
offset->type = primitive_parse("u32");
|
||||
offset->val = this->calleeOffsets[i];
|
||||
offset->stackGrowth = true;
|
||||
|
||||
ASTExprBinaryOp *sum = calloc(1, sizeof(*sum));
|
||||
sum->nodeKind = AST_EXPR_BINARY_OP;
|
||||
sum->type = offset->type;
|
||||
sum->operator = BINOP_ADD;
|
||||
sum->operands[0] = (AST*) stk;
|
||||
sum->operands[1] = (AST*) offset;
|
||||
|
||||
ASTExprUnaryOp *deref = calloc(1, sizeof(*deref));
|
||||
deref->nodeKind = AST_EXPR_UNARY_OP;
|
||||
deref->type = offset->type;
|
||||
deref->operator = UNOP_DEREF;
|
||||
deref->operand = (AST*) sum;
|
||||
|
||||
ASTExprVar *ev = calloc(1, sizeof(*ev));
|
||||
ev->nodeKind = AST_EXPR_VAR;
|
||||
ev->type = vte->type;
|
||||
ev->thing = vte;
|
||||
|
||||
ASTStmtAssign *assign = calloc(1, sizeof(*assign));
|
||||
assign->nodeKind = AST_STMT_ASSIGN;
|
||||
assign->what = (AST*) deref;
|
||||
assign->to = (AST*) ev;
|
||||
|
||||
assign->next = tlc->chunk.statementFirst;
|
||||
tlc->chunk.statementFirst = (AST*) assign;
|
||||
|
||||
assert(tlc->chunk.statementLast != NULL);
|
||||
}
|
||||
|
||||
} else if(n->nodeKind == AST_STMT_RETURN && n != this->lastProcessedReturn) {
|
||||
// Function exit
|
||||
|
||||
this->lastProcessedReturn = n;
|
||||
|
||||
for(int i = 0; i < MAX_REGS_PER_CLASS; i++) {
|
||||
ScopeItem *vte = this->calleeUsers[i];
|
||||
|
||||
if(!vte) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ASTExprStackPointer *stk = calloc(1, sizeof(*stk));
|
||||
stk->nodeKind = AST_EXPR_STACK_POINTER;
|
||||
stk->type = primitive_parse("u32");
|
||||
|
||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
offset->type = primitive_parse("u32");
|
||||
offset->val = this->calleeOffsets[i];
|
||||
offset->stackGrowth = true;
|
||||
|
||||
ASTExprBinaryOp *sum = calloc(1, sizeof(*sum));
|
||||
sum->nodeKind = AST_EXPR_BINARY_OP;
|
||||
sum->type = offset->type;
|
||||
sum->operator = BINOP_ADD;
|
||||
sum->operands[0] = (AST*) stk;
|
||||
sum->operands[1] = (AST*) offset;
|
||||
|
||||
ASTExprUnaryOp *deref = calloc(1, sizeof(*deref));
|
||||
deref->nodeKind = AST_EXPR_UNARY_OP;
|
||||
deref->type = offset->type;
|
||||
deref->operator = UNOP_DEREF;
|
||||
deref->operand = (AST*) sum;
|
||||
|
||||
ASTExprVar *ev = calloc(1, sizeof(*ev));
|
||||
ev->nodeKind = AST_EXPR_VAR;
|
||||
ev->type = vte->type;
|
||||
ev->thing = vte;
|
||||
|
||||
ASTStmtAssign *assign = calloc(1, sizeof(*assign));
|
||||
assign->nodeKind = AST_STMT_ASSIGN;
|
||||
assign->what = (AST*) ev;
|
||||
assign->to = (AST*) deref;
|
||||
assign->next = stmt;
|
||||
|
||||
if(stmtPrev) {
|
||||
stmtPrev->statement.next = (AST*) assign;
|
||||
} else {
|
||||
tlc->chunk.statementFirst = (AST*) assign;
|
||||
}
|
||||
stmtPrev = (AST*) assign;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static void callee_saved(AST *tlc) {
|
||||
ScopeItem *ebxuser = NULL, *ediuser = NULL, *esiuser = NULL;
|
||||
static void callee_saved(AST *tlc, struct CalleeSavedState *state) {
|
||||
bool ebxused = false, ediused = false, esiused = false;
|
||||
|
||||
for(size_t v = 0; v < tlc->chunk.varCount; v++) {
|
||||
if(is_reg_b(tlc->chunk.vars[v]->data.var.registerClass, tlc->chunk.vars[v]->data.var.color)) {
|
||||
ebxuser = tlc->chunk.vars[v];
|
||||
size_t resource = REG_CLASSES[tlc->chunk.vars[v]->data.var.registerClass].rs[tlc->chunk.vars[v]->data.var.color];
|
||||
|
||||
if(resource & HWR_EBX) {
|
||||
ebxused = true;
|
||||
}
|
||||
if(is_reg_di(tlc->chunk.vars[v]->data.var.registerClass, tlc->chunk.vars[v]->data.var.color)) {
|
||||
ediuser = tlc->chunk.vars[v];
|
||||
if(resource & HWR_EDI) {
|
||||
ediused = true;
|
||||
}
|
||||
if(is_reg_si(tlc->chunk.vars[v]->data.var.registerClass, tlc->chunk.vars[v]->data.var.color)) {
|
||||
esiuser = tlc->chunk.vars[v];
|
||||
if(resource & HWR_ESI) {
|
||||
esiused = true;
|
||||
}
|
||||
}
|
||||
|
||||
struct CalleeSavedState state = {};
|
||||
state.targetTLC = tlc;
|
||||
|
||||
size_t nextUser = 0;
|
||||
if(ebxuser) {
|
||||
state.calleeOffsets[nextUser] = nextUser * 4;
|
||||
state.calleeUsers[nextUser] = ebxuser;
|
||||
if(ebxused) {
|
||||
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||
state->reg[nextUser] = x86_ia16() ? "bx" : "ebx";
|
||||
nextUser++;
|
||||
}
|
||||
if(esiuser) {
|
||||
state.calleeOffsets[nextUser] = nextUser * 4;
|
||||
state.calleeUsers[nextUser] = esiuser;
|
||||
if(esiused) {
|
||||
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||
state->reg[nextUser] = x86_ia16() ? "si" : "esi";
|
||||
nextUser++;
|
||||
}
|
||||
if(ediuser) {
|
||||
state.calleeOffsets[nextUser] = nextUser * 4;
|
||||
state.calleeUsers[nextUser] = ediuser;
|
||||
if(ediused) {
|
||||
state->stackOffset[nextUser] = nextUser * x86_max_gpr_size();
|
||||
state->reg[nextUser] = x86_ia16() ? "di" : "edi";
|
||||
nextUser++;
|
||||
}
|
||||
ast_grow_stack_frame(tlc, nextUser * 4);
|
||||
|
||||
if(nextUser) {
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, &state, callee_saved_visitor, NULL);
|
||||
}
|
||||
ast_grow_stack_frame(tlc, nextUser * x86_max_gpr_size());
|
||||
}
|
||||
|
||||
static void determine_register_classes(AST *tlc) {
|
||||
@ -945,14 +852,16 @@ cont:;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct CalleeSavedState calleeSaved = {};
|
||||
if(a->chunk.functionType) {
|
||||
callee_saved(a);
|
||||
callee_saved(a, &calleeSaved);
|
||||
}
|
||||
|
||||
CGState cg;
|
||||
memset(&cg, 0, sizeof(cg));
|
||||
cg.tlc = a;
|
||||
cg.isFunction = !!a->chunk.functionType;
|
||||
cg.calleeSaved = calleeSaved;
|
||||
|
||||
cg_chunk(&cg, a);
|
||||
|
||||
|
@ -83,6 +83,32 @@ static void mark_ptr(AST *a) {
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_a(ScopeItem *si) {
|
||||
size_t sz = type_size(si->type);
|
||||
if(sz <= 1) {
|
||||
vte_precolor(si, REG_CLASS_8, 0);
|
||||
} else if(sz == 2) {
|
||||
vte_precolor(si, REG_CLASS_16_32, 0);
|
||||
} else if(sz == 4 || sz == 0) {
|
||||
vte_precolor(si, REG_CLASS_16_32, 1);
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_d(ScopeItem *si) {
|
||||
size_t sz = type_size(si->type);
|
||||
if(sz <= 1) {
|
||||
vte_precolor(si, REG_CLASS_8, 6);
|
||||
} else if(sz == 2) {
|
||||
vte_precolor(si, REG_CLASS_16_32, 6);
|
||||
} else if(sz == 4 || sz == 0) {
|
||||
vte_precolor(si, REG_CLASS_16_32, 7);
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
struct DumbenState {
|
||||
AST *targetTLC;
|
||||
int effective;
|
||||
@ -225,7 +251,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
|
||||
} else if(s->nodeKind == AST_STMT_RETURN) {
|
||||
|
||||
// ret specifically returns in eax always, so it needs to be in a precolored var
|
||||
// ret returns in eax always, so it needs to be in a precolored var
|
||||
AST *retval = s->stmtReturn.val;
|
||||
|
||||
if(retval && (!is_xop(retval) || retval->nodeKind == AST_EXPR_PRIMITIVE || (retval->nodeKind == AST_EXPR_VAR && retval->exprVar.thing->kind == SCOPEITEM_VAR && (!retval->exprVar.thing->data.var.precolored || !strchr(REG_CLASSES[retval->exprVar.thing->data.var.registerClass].rsN[retval->exprVar.thing->data.var.color], 'a'))))) {
|
||||
@ -233,7 +259,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
retval = s->stmtReturn.val = varify(tlc, chu, stmtPrev, s, retval);
|
||||
this->effective = 1;
|
||||
|
||||
vte_precolor(retval->exprVar.thing, REG_CLASS_16_32, 1);
|
||||
mark_a(retval->exprVar.thing);
|
||||
|
||||
}
|
||||
|
||||
@ -246,7 +272,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
|
||||
s->stmtExpr.expr = varify(tlc, chu, stmtPrev, s, s->stmtExpr.expr);
|
||||
|
||||
vte_precolor(s->stmtExpr.expr->exprVar.thing, REG_CLASS_16_32, 1);
|
||||
mark_a(s->stmtExpr.expr->exprVar.thing);
|
||||
|
||||
// Purge this statement entirely, otherwise we'd have
|
||||
// a = f(x);
|
||||
@ -273,7 +299,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
if(s->stmtAssign.to->nodeKind == AST_EXPR_CALL && (s->stmtAssign.what->nodeKind != AST_EXPR_VAR || s->stmtAssign.what->exprVar.thing->kind != SCOPEITEM_VAR || !s->stmtAssign.what->exprVar.thing->data.var.precolored)) {
|
||||
|
||||
ScopeItem *tmp = create_dumbtemp(tlc, s->stmtAssign.what->expression.type);
|
||||
vte_precolor(tmp, REG_CLASS_16_32, 1);
|
||||
mark_a(tmp);
|
||||
|
||||
ASTExprVar *ev[2] = {calloc(1, sizeof(**ev)), calloc(1, sizeof(**ev))};
|
||||
ev[0]->nodeKind = AST_EXPR_VAR;
|
||||
@ -307,6 +333,12 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
mark_ptr(s->stmtAssign.what->exprUnOp.operand);
|
||||
|
||||
this->effective = 1;
|
||||
|
||||
} else if(s->stmtAssign.what && s->stmtAssign.what->nodeKind == AST_EXPR_VAR && s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR && s->stmtAssign.to->nodeKind == AST_EXPR_CALL && is_xop(s->stmtAssign.to->exprCall.what) == XOP_NOT_XOP) {
|
||||
|
||||
s->stmtAssign.to->exprCall.what = varify(tlc, chu, stmtPrev, s, s->stmtAssign.to->exprCall.what);
|
||||
|
||||
this->effective = 1;
|
||||
|
||||
} else if(s->stmtAssign.what && s->stmtAssign.what->nodeKind == AST_EXPR_VAR && s->stmtAssign.what->exprVar.thing->kind == SCOPEITEM_VAR && s->stmtAssign.to->nodeKind == AST_EXPR_CALL) {
|
||||
ASTExprCall *call = &s->stmtAssign.to->exprCall;
|
||||
@ -392,7 +424,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
|
||||
AST *hihalf = varify(tlc, chu, stmtPrev->statement.next, s, mulhi);
|
||||
|
||||
vte_precolor(hihalf->exprVar.thing, REG_CLASS_16_32, 7);
|
||||
mark_d(hihalf->exprVar.thing);
|
||||
}
|
||||
|
||||
s->stmtAssign.what = ast_deep_copy(s->stmtAssign.to->exprBinOp.operands[0]);
|
||||
@ -405,7 +437,7 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
|
||||
s->stmtAssign.next = (AST*) redest;
|
||||
|
||||
vte_precolor(s->stmtAssign.to->exprBinOp.operands[0]->exprVar.thing, REG_CLASS_16_32, 1);
|
||||
mark_a(s->stmtAssign.to->exprBinOp.operands[0]->exprVar.thing);
|
||||
|
||||
this->effective = 1;
|
||||
} else assert(because == NOT_AT_ALL_IT || because == GUCCI);
|
||||
@ -452,11 +484,6 @@ static void dumben_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chu, AST *
|
||||
}
|
||||
}
|
||||
|
||||
struct DenoopState {
|
||||
AST *targetTLC;
|
||||
bool success;
|
||||
};
|
||||
|
||||
static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
AST *n = *nptr;
|
||||
|
||||
@ -474,7 +501,7 @@ static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, A
|
||||
|
||||
ASTExprPrimitive *offset = calloc(1, sizeof(*offset));
|
||||
offset->nodeKind = AST_EXPR_PRIMITIVE;
|
||||
offset->type = type_u(x86_max_gpr_size());
|
||||
offset->type = type_u(8 * x86_max_gpr_size());
|
||||
offset->val = 4 + i * x86_max_gpr_size();
|
||||
offset->stackGrowth = true;
|
||||
|
||||
@ -500,7 +527,7 @@ static void pre_dumb_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, A
|
||||
ass->nodeKind = AST_STMT_ASSIGN;
|
||||
ass->next = NULL;
|
||||
ass->what = (AST*) evar;
|
||||
ass->to = (AST*) deref;
|
||||
ass->to = ast_cast_expr((AST*) deref, vte->type); // Must cast because of "convention correctness"
|
||||
ass->next = n->chunk.statementFirst;
|
||||
|
||||
if(n->chunk.statementFirst) {
|
||||
@ -567,6 +594,11 @@ static bool is_double_field_access(AST *e) {
|
||||
return e->nodeKind == AST_EXPR_BINARY_OP && is_pointer2pointer_cast(e->exprBinOp.operands[0]) && e->exprBinOp.operands[0]->exprCast.what->nodeKind == AST_EXPR_BINARY_OP && e->exprBinOp.operands[0]->exprCast.what->exprBinOp.operator == e->exprBinOp.operator && e->exprBinOp.operator == BINOP_ADD && e->exprBinOp.operands[0]->exprCast.what->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && e->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE;
|
||||
}
|
||||
|
||||
struct DenoopState {
|
||||
AST *targetTLC;
|
||||
bool success;
|
||||
};
|
||||
|
||||
static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
struct DenoopState *state = ud;
|
||||
|
||||
@ -579,6 +611,9 @@ static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST
|
||||
if(n->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operator == UNOP_REF && n->exprUnOp.operand->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operand->exprUnOp.operator == UNOP_DEREF) {
|
||||
// Turn `&*a` into `a`
|
||||
|
||||
// Artificially change type of casted expression to keep types valid for subsequent passes
|
||||
n->exprUnOp.operand->exprUnOp.operand->expression.type = n->expression.type;
|
||||
|
||||
*nptr = n->exprUnOp.operand->exprUnOp.operand;
|
||||
|
||||
*success = true;
|
||||
@ -603,6 +638,15 @@ static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST
|
||||
|
||||
*nptr = n->exprBinOp.operands[0];
|
||||
|
||||
*success = true;
|
||||
} else if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_ADD && n->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE && n->exprBinOp.operands[1]->exprPrim.val == 0) {
|
||||
// Turn `x + 0` into `x`
|
||||
|
||||
// Artificially change type of casted expression to keep types valid for subsequent passes
|
||||
n->exprBinOp.operands[0]->expression.type = n->expression.type;
|
||||
|
||||
*nptr = n->exprBinOp.operands[0];
|
||||
|
||||
*success = true;
|
||||
} else if(n->nodeKind == AST_EXPR_UNARY_OP && n->exprUnOp.operator == UNOP_NOT && n->exprUnOp.operand->nodeKind == AST_EXPR_BINARY_OP && binop_comp_opposite(n->exprUnOp.operand->exprBinOp.operator) != BINOP_WTF) {
|
||||
// Turn `!(a op b)` to `(a !op b)`
|
||||
@ -621,8 +665,31 @@ static void denoop_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST
|
||||
} else if(n->nodeKind == AST_EXPR_CAST && n->exprCast.what->expression.type->type == TYPE_TYPE_POINTER && n->exprCast.to->type == TYPE_TYPE_POINTER) {
|
||||
// Turn (x as A*) into x, since all pointer types are identical in Nectar's AST
|
||||
|
||||
// Artificially change type of casted expression to keep types valid for subsequent passes
|
||||
n->exprCast.what->expression.type = n->exprCast.to;
|
||||
|
||||
*nptr = n->exprCast.what;
|
||||
|
||||
*success = true;
|
||||
} else if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_ADD && n->exprBinOp.operands[0]->nodeKind == AST_EXPR_PRIMITIVE && n->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
// Constant propagation of + operator
|
||||
|
||||
AST *prim = n->exprBinOp.operands[0];
|
||||
prim->expression.type = n->exprBinOp.type;
|
||||
prim->exprPrim.val = n->exprBinOp.operands[0]->exprPrim.val + n->exprBinOp.operands[1]->exprPrim.val;
|
||||
|
||||
*nptr = prim;
|
||||
|
||||
*success = true;
|
||||
} else if(n->nodeKind == AST_EXPR_BINARY_OP && n->exprBinOp.operator == BINOP_SUB && n->exprBinOp.operands[0]->nodeKind == AST_EXPR_PRIMITIVE && n->exprBinOp.operands[1]->nodeKind == AST_EXPR_PRIMITIVE) {
|
||||
// Constant propagation of - operator
|
||||
|
||||
AST *prim = n->exprBinOp.operands[0];
|
||||
prim->expression.type = n->exprBinOp.type;
|
||||
prim->exprPrim.val = n->exprBinOp.operands[0]->exprPrim.val - n->exprBinOp.operands[1]->exprPrim.val;
|
||||
|
||||
*nptr = prim;
|
||||
|
||||
*success = true;
|
||||
} else if(n->nodeKind == AST_EXPR_EXT_SIZEOF) {
|
||||
ASTExprPrimitive *prim = calloc(1, sizeof(*prim));
|
||||
@ -676,7 +743,40 @@ void ast_denoop(AST *tlc, AST **node) {
|
||||
} while(state.success);
|
||||
}
|
||||
|
||||
/*
|
||||
* The convention correctness pass converts all function calls & function sources to the form
|
||||
* hat matches the architecture most closely. For example, arguments (and return values) in
|
||||
* cdecl are always passed as 32-bit integers, even if they are defined as 8-bit or 16-bit in
|
||||
* the source.
|
||||
*
|
||||
* TODO: convert records to proper form also.
|
||||
*/
|
||||
static void convention_correctness_visitor(AST **nptr, AST *stmt, AST *stmtPrev, AST *chunk, AST *tlc, void *ud) {
|
||||
if(tlc != ud) {
|
||||
return;
|
||||
}
|
||||
|
||||
AST *n = *nptr;
|
||||
|
||||
if(n->nodeKind == AST_EXPR_CALL) {
|
||||
Type *type = n->exprCall.what->expression.type;
|
||||
|
||||
assert(type->type == TYPE_TYPE_POINTER);
|
||||
|
||||
type = type->pointer.of;
|
||||
|
||||
assert(type->type == TYPE_TYPE_FUNCTION);
|
||||
|
||||
for(size_t i = 0; i < type->function.argCount; i++) {
|
||||
if(type->function.args[i]->type == TYPE_TYPE_PRIMITIVE) {
|
||||
n->exprCall.args[i] = ast_cast_expr(n->exprCall.args[i], type_prim_cast(type->function.args[i], 8 * x86_max_gpr_size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dumben_pre(AST *tlc) {
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, convention_correctness_visitor, NULL);
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, pre_dumb_visitor, NULL);
|
||||
generic_visitor(&tlc, NULL, NULL, tlc, tlc, tlc, decompose_symbol_record_field_access, NULL);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user