wcc/cgenexpr: cgexpr node-kind if-ladder -> switch (Wave-2 structural)
23-arm top-level if (k == nkind.N_X) dispatch ladder becomes one switch (k) with an empty-label default case for the AX=0 fallback. N_RUNELIT stays a separate arm (no float check, unlike cstage's INTLIT grouping). Not byte-id-neutral (if-chain -> switch); 990-997 cstage==wwstage byte-id is the functional-equivalence gate. w6c + wwdump combined.ww regenerated.
This commit is contained in:
@@ -19774,7 +19774,8 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
if (n == nil) { return; };
|
if (n == nil) { return; };
|
||||||
let k: nkind = n.kind;
|
let k: nkind = n.kind;
|
||||||
|
|
||||||
if (k == nkind.N_INTLIT) {
|
switch (k) {
|
||||||
|
case nkind.N_INTLIT:
|
||||||
// A no-decimal `0f64`/`8f64` is an N_INTLIT carrying float
|
// A no-decimal `0f64`/`8f64` is an N_INTLIT carrying float
|
||||||
// TYPE; it must reach X0 like a true float literal, not the
|
// TYPE; it must reach X0 like a true float literal, not the
|
||||||
// integer-immediate path (which strands it in AX and an SSE
|
// integer-immediate path (which strands it in AX and an SSE
|
||||||
@@ -19802,8 +19803,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
emitint(n.uval: i64);
|
emitint(n.uval: i64);
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_FLOATLIT:
|
||||||
if (k == nkind.N_FLOATLIT) {
|
|
||||||
// The bits come from n.uval — the parser populates it from
|
// The bits come from n.uval — the parser populates it from
|
||||||
// the lexer's bitcast of t.fval.
|
// the lexer's bitcast of t.fval.
|
||||||
cgfloatbits(c, n.uval);
|
cgfloatbits(c, n.uval);
|
||||||
@@ -19812,71 +19812,55 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tCVTSD2SS\tX0, X0\n");
|
emitline("\tCVTSD2SS\tX0, X0\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_RUNELIT:
|
||||||
if (k == nkind.N_RUNELIT) {
|
|
||||||
emitline("\tMOVQ\t$");
|
emitline("\tMOVQ\t$");
|
||||||
emitint(n.uval: i64);
|
emitint(n.uval: i64);
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_STRLIT: cgstrlit(c, n); return;
|
||||||
if (k == nkind.N_STRLIT) { cgstrlit(c, n); return; };
|
case nkind.N_TRUE:
|
||||||
if (k == nkind.N_TRUE) {
|
|
||||||
emitline("\tMOVQ\t$1, AX\n");
|
emitline("\tMOVQ\t$1, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_FALSE:
|
||||||
if (k == nkind.N_FALSE) {
|
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_NIL:
|
||||||
if (k == nkind.N_NIL) {
|
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_VOIDLIT:
|
||||||
if (k == nkind.N_VOIDLIT) {
|
|
||||||
// void value: zero-size, but the consumer's ABI expects a
|
// void value: zero-size, but the consumer's ABI expects a
|
||||||
// deterministic AX. Emit 0 like nil/false do.
|
// deterministic AX. Emit 0 like nil/false do.
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_IDENT: cgident(c, n); return;
|
||||||
|
case nkind.N_INDEX: cgindex(c, n); return;
|
||||||
if (k == nkind.N_IDENT) { cgident(c, n); return; };
|
case nkind.N_SLICE: cgslice(c, n); return;
|
||||||
|
case nkind.N_MATCH: cgmatch(c, n); return;
|
||||||
if (k == nkind.N_INDEX) { cgindex(c, n); return; };
|
case nkind.N_CAST: cgcast(c, n); return;
|
||||||
|
case nkind.N_DOT: cgdot(c, n); return;
|
||||||
if (k == nkind.N_SLICE) { cgslice(c, n); return; };
|
case nkind.N_UN: cgun(c, n); return;
|
||||||
|
case nkind.N_BIN: cgbin(c, n); return;
|
||||||
if (k == nkind.N_MATCH) { cgmatch(c, n); return; };
|
case nkind.N_CALL: cgcall(c, n); return;
|
||||||
|
case nkind.N_ASSIGN: cgassign(c, n); return;
|
||||||
if (k == nkind.N_CAST) { cgcast(c, n); return; };
|
case nkind.N_TRYPROP: cgtryprop(c, n); return;
|
||||||
|
case nkind.N_TRYUNW: cgtryunw(c, n); return;
|
||||||
if (k == nkind.N_DOT) { cgdot(c, n); return; };
|
case nkind.N_TYPETEST: cgtypetest(c, n); return;
|
||||||
|
case nkind.N_TYPEASSERT: cgtypeassert(c, n); return;
|
||||||
if (k == nkind.N_UN) { cgun(c, n); return; };
|
case nkind.N_TUPLE:
|
||||||
|
|
||||||
if (k == nkind.N_BIN) { cgbin(c, n); return; };
|
|
||||||
|
|
||||||
if (k == nkind.N_CALL) { cgcall(c, n); return; };
|
|
||||||
|
|
||||||
if (k == nkind.N_ASSIGN) { cgassign(c, n); return; };
|
|
||||||
|
|
||||||
if (k == nkind.N_TRYPROP) { cgtryprop(c, n); return; };
|
|
||||||
if (k == nkind.N_TRYUNW) { cgtryunw(c, n); return; };
|
|
||||||
if (k == nkind.N_TYPETEST) { cgtypetest(c, n); return; };
|
|
||||||
if (k == nkind.N_TYPEASSERT) { cgtypeassert(c, n); return; };
|
|
||||||
if (k == nkind.N_TUPLE) {
|
|
||||||
// #241: a literal tuple rvalue `(a, b)` is a value — pack its
|
// #241: a literal tuple rvalue `(a, b)` is a value — pack its
|
||||||
// elements into the register cursor (mirror cgreturn's N_TUPLE
|
// elements into the register cursor (mirror cgreturn's N_TUPLE
|
||||||
// arm) so a let-bind / destructure consumer reads every element,
|
// arm) so a let-bind / destructure consumer reads every element,
|
||||||
// not just AX = 0 from the default arm below.
|
// not just AX = 0 from the default arm below.
|
||||||
cgtuplelittocursor(c, n);
|
cgtuplelittocursor(c, n);
|
||||||
return;
|
return;
|
||||||
};
|
case:
|
||||||
// Default fallback: produce a deterministic AX = 0. Mirrors
|
// Default fallback: produce a deterministic AX = 0. Mirrors
|
||||||
// the C cgen's `default: cgexpr_int(c, 0)` branch, which is
|
// the C cgen's `default: cgexpr_int(c, 0)` branch, which is
|
||||||
// what `return eof{};` (N_STRUCTLIT with an empty !void
|
// what `return eof{};` (N_STRUCTLIT with an empty !void
|
||||||
// variant) silently relies on — without this AX carries a
|
// variant) silently relies on — without this AX carries a
|
||||||
// stale value into the tagged-union return shuffle.
|
// stale value into the tagged-union return shuffle.
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
if (n == nil) { return; };
|
if (n == nil) { return; };
|
||||||
let k: nkind = n.kind;
|
let k: nkind = n.kind;
|
||||||
|
|
||||||
if (k == nkind.N_INTLIT) {
|
switch (k) {
|
||||||
|
case nkind.N_INTLIT:
|
||||||
// A no-decimal `0f64`/`8f64` is an N_INTLIT carrying float
|
// A no-decimal `0f64`/`8f64` is an N_INTLIT carrying float
|
||||||
// TYPE; it must reach X0 like a true float literal, not the
|
// TYPE; it must reach X0 like a true float literal, not the
|
||||||
// integer-immediate path (which strands it in AX and an SSE
|
// integer-immediate path (which strands it in AX and an SSE
|
||||||
@@ -66,8 +67,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
emitint(n.uval: i64);
|
emitint(n.uval: i64);
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_FLOATLIT:
|
||||||
if (k == nkind.N_FLOATLIT) {
|
|
||||||
// The bits come from n.uval — the parser populates it from
|
// The bits come from n.uval — the parser populates it from
|
||||||
// the lexer's bitcast of t.fval.
|
// the lexer's bitcast of t.fval.
|
||||||
cgfloatbits(c, n.uval);
|
cgfloatbits(c, n.uval);
|
||||||
@@ -76,71 +76,55 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tCVTSD2SS\tX0, X0\n");
|
emitline("\tCVTSD2SS\tX0, X0\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_RUNELIT:
|
||||||
if (k == nkind.N_RUNELIT) {
|
|
||||||
emitline("\tMOVQ\t$");
|
emitline("\tMOVQ\t$");
|
||||||
emitint(n.uval: i64);
|
emitint(n.uval: i64);
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_STRLIT: cgstrlit(c, n); return;
|
||||||
if (k == nkind.N_STRLIT) { cgstrlit(c, n); return; };
|
case nkind.N_TRUE:
|
||||||
if (k == nkind.N_TRUE) {
|
|
||||||
emitline("\tMOVQ\t$1, AX\n");
|
emitline("\tMOVQ\t$1, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_FALSE:
|
||||||
if (k == nkind.N_FALSE) {
|
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_NIL:
|
||||||
if (k == nkind.N_NIL) {
|
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_VOIDLIT:
|
||||||
if (k == nkind.N_VOIDLIT) {
|
|
||||||
// void value: zero-size, but the consumer's ABI expects a
|
// void value: zero-size, but the consumer's ABI expects a
|
||||||
// deterministic AX. Emit 0 like nil/false do.
|
// deterministic AX. Emit 0 like nil/false do.
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_IDENT: cgident(c, n); return;
|
||||||
|
case nkind.N_INDEX: cgindex(c, n); return;
|
||||||
if (k == nkind.N_IDENT) { cgident(c, n); return; };
|
case nkind.N_SLICE: cgslice(c, n); return;
|
||||||
|
case nkind.N_MATCH: cgmatch(c, n); return;
|
||||||
if (k == nkind.N_INDEX) { cgindex(c, n); return; };
|
case nkind.N_CAST: cgcast(c, n); return;
|
||||||
|
case nkind.N_DOT: cgdot(c, n); return;
|
||||||
if (k == nkind.N_SLICE) { cgslice(c, n); return; };
|
case nkind.N_UN: cgun(c, n); return;
|
||||||
|
case nkind.N_BIN: cgbin(c, n); return;
|
||||||
if (k == nkind.N_MATCH) { cgmatch(c, n); return; };
|
case nkind.N_CALL: cgcall(c, n); return;
|
||||||
|
case nkind.N_ASSIGN: cgassign(c, n); return;
|
||||||
if (k == nkind.N_CAST) { cgcast(c, n); return; };
|
case nkind.N_TRYPROP: cgtryprop(c, n); return;
|
||||||
|
case nkind.N_TRYUNW: cgtryunw(c, n); return;
|
||||||
if (k == nkind.N_DOT) { cgdot(c, n); return; };
|
case nkind.N_TYPETEST: cgtypetest(c, n); return;
|
||||||
|
case nkind.N_TYPEASSERT: cgtypeassert(c, n); return;
|
||||||
if (k == nkind.N_UN) { cgun(c, n); return; };
|
case nkind.N_TUPLE:
|
||||||
|
|
||||||
if (k == nkind.N_BIN) { cgbin(c, n); return; };
|
|
||||||
|
|
||||||
if (k == nkind.N_CALL) { cgcall(c, n); return; };
|
|
||||||
|
|
||||||
if (k == nkind.N_ASSIGN) { cgassign(c, n); return; };
|
|
||||||
|
|
||||||
if (k == nkind.N_TRYPROP) { cgtryprop(c, n); return; };
|
|
||||||
if (k == nkind.N_TRYUNW) { cgtryunw(c, n); return; };
|
|
||||||
if (k == nkind.N_TYPETEST) { cgtypetest(c, n); return; };
|
|
||||||
if (k == nkind.N_TYPEASSERT) { cgtypeassert(c, n); return; };
|
|
||||||
if (k == nkind.N_TUPLE) {
|
|
||||||
// #241: a literal tuple rvalue `(a, b)` is a value — pack its
|
// #241: a literal tuple rvalue `(a, b)` is a value — pack its
|
||||||
// elements into the register cursor (mirror cgreturn's N_TUPLE
|
// elements into the register cursor (mirror cgreturn's N_TUPLE
|
||||||
// arm) so a let-bind / destructure consumer reads every element,
|
// arm) so a let-bind / destructure consumer reads every element,
|
||||||
// not just AX = 0 from the default arm below.
|
// not just AX = 0 from the default arm below.
|
||||||
cgtuplelittocursor(c, n);
|
cgtuplelittocursor(c, n);
|
||||||
return;
|
return;
|
||||||
};
|
case:
|
||||||
// Default fallback: produce a deterministic AX = 0. Mirrors
|
// Default fallback: produce a deterministic AX = 0. Mirrors
|
||||||
// the C cgen's `default: cgexpr_int(c, 0)` branch, which is
|
// the C cgen's `default: cgexpr_int(c, 0)` branch, which is
|
||||||
// what `return eof{};` (N_STRUCTLIT with an empty !void
|
// what `return eof{};` (N_STRUCTLIT with an empty !void
|
||||||
// variant) silently relies on — without this AX carries a
|
// variant) silently relies on — without this AX carries a
|
||||||
// stale value into the tagged-union return shuffle.
|
// stale value into the tagged-union return shuffle.
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
||||||
|
|||||||
@@ -19774,7 +19774,8 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
if (n == nil) { return; };
|
if (n == nil) { return; };
|
||||||
let k: nkind = n.kind;
|
let k: nkind = n.kind;
|
||||||
|
|
||||||
if (k == nkind.N_INTLIT) {
|
switch (k) {
|
||||||
|
case nkind.N_INTLIT:
|
||||||
// A no-decimal `0f64`/`8f64` is an N_INTLIT carrying float
|
// A no-decimal `0f64`/`8f64` is an N_INTLIT carrying float
|
||||||
// TYPE; it must reach X0 like a true float literal, not the
|
// TYPE; it must reach X0 like a true float literal, not the
|
||||||
// integer-immediate path (which strands it in AX and an SSE
|
// integer-immediate path (which strands it in AX and an SSE
|
||||||
@@ -19802,8 +19803,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
emitint(n.uval: i64);
|
emitint(n.uval: i64);
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_FLOATLIT:
|
||||||
if (k == nkind.N_FLOATLIT) {
|
|
||||||
// The bits come from n.uval — the parser populates it from
|
// The bits come from n.uval — the parser populates it from
|
||||||
// the lexer's bitcast of t.fval.
|
// the lexer's bitcast of t.fval.
|
||||||
cgfloatbits(c, n.uval);
|
cgfloatbits(c, n.uval);
|
||||||
@@ -19812,71 +19812,55 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tCVTSD2SS\tX0, X0\n");
|
emitline("\tCVTSD2SS\tX0, X0\n");
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_RUNELIT:
|
||||||
if (k == nkind.N_RUNELIT) {
|
|
||||||
emitline("\tMOVQ\t$");
|
emitline("\tMOVQ\t$");
|
||||||
emitint(n.uval: i64);
|
emitint(n.uval: i64);
|
||||||
emitline(", AX\n");
|
emitline(", AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_STRLIT: cgstrlit(c, n); return;
|
||||||
if (k == nkind.N_STRLIT) { cgstrlit(c, n); return; };
|
case nkind.N_TRUE:
|
||||||
if (k == nkind.N_TRUE) {
|
|
||||||
emitline("\tMOVQ\t$1, AX\n");
|
emitline("\tMOVQ\t$1, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_FALSE:
|
||||||
if (k == nkind.N_FALSE) {
|
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_NIL:
|
||||||
if (k == nkind.N_NIL) {
|
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_VOIDLIT:
|
||||||
if (k == nkind.N_VOIDLIT) {
|
|
||||||
// void value: zero-size, but the consumer's ABI expects a
|
// void value: zero-size, but the consumer's ABI expects a
|
||||||
// deterministic AX. Emit 0 like nil/false do.
|
// deterministic AX. Emit 0 like nil/false do.
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
case nkind.N_IDENT: cgident(c, n); return;
|
||||||
|
case nkind.N_INDEX: cgindex(c, n); return;
|
||||||
if (k == nkind.N_IDENT) { cgident(c, n); return; };
|
case nkind.N_SLICE: cgslice(c, n); return;
|
||||||
|
case nkind.N_MATCH: cgmatch(c, n); return;
|
||||||
if (k == nkind.N_INDEX) { cgindex(c, n); return; };
|
case nkind.N_CAST: cgcast(c, n); return;
|
||||||
|
case nkind.N_DOT: cgdot(c, n); return;
|
||||||
if (k == nkind.N_SLICE) { cgslice(c, n); return; };
|
case nkind.N_UN: cgun(c, n); return;
|
||||||
|
case nkind.N_BIN: cgbin(c, n); return;
|
||||||
if (k == nkind.N_MATCH) { cgmatch(c, n); return; };
|
case nkind.N_CALL: cgcall(c, n); return;
|
||||||
|
case nkind.N_ASSIGN: cgassign(c, n); return;
|
||||||
if (k == nkind.N_CAST) { cgcast(c, n); return; };
|
case nkind.N_TRYPROP: cgtryprop(c, n); return;
|
||||||
|
case nkind.N_TRYUNW: cgtryunw(c, n); return;
|
||||||
if (k == nkind.N_DOT) { cgdot(c, n); return; };
|
case nkind.N_TYPETEST: cgtypetest(c, n); return;
|
||||||
|
case nkind.N_TYPEASSERT: cgtypeassert(c, n); return;
|
||||||
if (k == nkind.N_UN) { cgun(c, n); return; };
|
case nkind.N_TUPLE:
|
||||||
|
|
||||||
if (k == nkind.N_BIN) { cgbin(c, n); return; };
|
|
||||||
|
|
||||||
if (k == nkind.N_CALL) { cgcall(c, n); return; };
|
|
||||||
|
|
||||||
if (k == nkind.N_ASSIGN) { cgassign(c, n); return; };
|
|
||||||
|
|
||||||
if (k == nkind.N_TRYPROP) { cgtryprop(c, n); return; };
|
|
||||||
if (k == nkind.N_TRYUNW) { cgtryunw(c, n); return; };
|
|
||||||
if (k == nkind.N_TYPETEST) { cgtypetest(c, n); return; };
|
|
||||||
if (k == nkind.N_TYPEASSERT) { cgtypeassert(c, n); return; };
|
|
||||||
if (k == nkind.N_TUPLE) {
|
|
||||||
// #241: a literal tuple rvalue `(a, b)` is a value — pack its
|
// #241: a literal tuple rvalue `(a, b)` is a value — pack its
|
||||||
// elements into the register cursor (mirror cgreturn's N_TUPLE
|
// elements into the register cursor (mirror cgreturn's N_TUPLE
|
||||||
// arm) so a let-bind / destructure consumer reads every element,
|
// arm) so a let-bind / destructure consumer reads every element,
|
||||||
// not just AX = 0 from the default arm below.
|
// not just AX = 0 from the default arm below.
|
||||||
cgtuplelittocursor(c, n);
|
cgtuplelittocursor(c, n);
|
||||||
return;
|
return;
|
||||||
};
|
case:
|
||||||
// Default fallback: produce a deterministic AX = 0. Mirrors
|
// Default fallback: produce a deterministic AX = 0. Mirrors
|
||||||
// the C cgen's `default: cgexpr_int(c, 0)` branch, which is
|
// the C cgen's `default: cgexpr_int(c, 0)` branch, which is
|
||||||
// what `return eof{};` (N_STRUCTLIT with an empty !void
|
// what `return eof{};` (N_STRUCTLIT with an empty !void
|
||||||
// variant) silently relies on — without this AX carries a
|
// variant) silently relies on — without this AX carries a
|
||||||
// stale value into the tagged-union return shuffle.
|
// stale value into the tagged-union return shuffle.
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
||||||
|
|||||||
Reference in New Issue
Block a user