mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 04:48:20 +00:00
Merge pull request #10977 from joachimschmidt557/stage2-aarch64
stage2 AArch64: more support for PIE targets (Mach-O)
This commit is contained in:
commit
9d098318e2
@ -1717,6 +1717,8 @@ fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_ind
|
||||
|
||||
fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void {
|
||||
const elem_ty = ptr_ty.elemType();
|
||||
const elem_size = elem_ty.abiSize(self.target.*);
|
||||
|
||||
switch (ptr) {
|
||||
.none => unreachable,
|
||||
.undef => unreachable,
|
||||
@ -1736,17 +1738,16 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo
|
||||
self.register_manager.freezeRegs(&.{addr_reg});
|
||||
defer self.register_manager.unfreezeRegs(&.{addr_reg});
|
||||
|
||||
const abi_size = elem_ty.abiSize(self.target.*);
|
||||
switch (dst_mcv) {
|
||||
.dead => unreachable,
|
||||
.undef => unreachable,
|
||||
.compare_flags_signed, .compare_flags_unsigned => unreachable,
|
||||
.embedded_in_code => unreachable,
|
||||
.register => |dst_reg| {
|
||||
try self.genLdrRegister(dst_reg, addr_reg, abi_size);
|
||||
try self.genLdrRegister(dst_reg, addr_reg, elem_size);
|
||||
},
|
||||
.stack_offset => |off| {
|
||||
if (abi_size <= 8) {
|
||||
if (elem_size <= 8) {
|
||||
const tmp_reg = try self.register_manager.allocReg(null);
|
||||
self.register_manager.freezeRegs(&.{tmp_reg});
|
||||
defer self.register_manager.unfreezeRegs(&.{tmp_reg});
|
||||
@ -1766,17 +1767,7 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo
|
||||
const tmp_reg = regs[3];
|
||||
|
||||
// sub dst_reg, fp, #off
|
||||
const elem_size = @intCast(u32, elem_ty.abiSize(self.target.*));
|
||||
const adj_off = off + elem_size;
|
||||
const offset = math.cast(u12, adj_off) catch return self.fail("TODO load: larger stack offsets", .{});
|
||||
_ = try self.addInst(.{
|
||||
.tag = .sub_immediate,
|
||||
.data = .{ .rr_imm12_sh = .{
|
||||
.rd = dst_reg,
|
||||
.rn = .x29,
|
||||
.imm12 = offset,
|
||||
} },
|
||||
});
|
||||
try self.genSetReg(ptr_ty, dst_reg, .{ .ptr_stack_offset = off });
|
||||
|
||||
// mov len, #elem_size
|
||||
try self.genSetReg(Type.usize, len_reg, .{ .immediate = elem_size });
|
||||
@ -2046,14 +2037,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type
|
||||
},
|
||||
.memory,
|
||||
.stack_offset,
|
||||
=> {
|
||||
const addr_reg = try self.copyToTmpRegister(ptr_ty, ptr);
|
||||
try self.store(.{ .register = addr_reg }, value, ptr_ty, value_ty);
|
||||
},
|
||||
.got_load,
|
||||
.direct_load,
|
||||
=> {
|
||||
return self.fail("TODO implement storing to {}", .{ptr});
|
||||
const addr_reg = try self.copyToTmpRegister(ptr_ty, ptr);
|
||||
try self.store(.{ .register = addr_reg }, value, ptr_ty, value_ty);
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -3142,10 +3130,6 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
|
||||
},
|
||||
.got_load,
|
||||
.direct_load,
|
||||
=> |sym_index| {
|
||||
_ = sym_index;
|
||||
return self.fail("TODO implement set stack variable from {}", .{mcv});
|
||||
},
|
||||
.memory,
|
||||
.stack_offset,
|
||||
=> {
|
||||
@ -3187,6 +3171,25 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
|
||||
});
|
||||
},
|
||||
.memory => |addr| try self.genSetReg(Type.usize, src_reg, .{ .immediate = addr }),
|
||||
.got_load,
|
||||
.direct_load,
|
||||
=> |sym_index| {
|
||||
const tag: Mir.Inst.Tag = switch (mcv) {
|
||||
.got_load => .load_memory_ptr_got,
|
||||
.direct_load => .load_memory_ptr_direct,
|
||||
else => unreachable,
|
||||
};
|
||||
_ = try self.addInst(.{
|
||||
.tag = tag,
|
||||
.data = .{
|
||||
.payload = try self.addExtra(Mir.LoadMemoryPie{
|
||||
.register = @enumToInt(src_reg),
|
||||
.atom_index = self.mod_fn.owner_decl.link.macho.local_sym_index,
|
||||
.sym_index = sym_index,
|
||||
}),
|
||||
},
|
||||
});
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
@ -3318,15 +3321,10 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
|
||||
});
|
||||
},
|
||||
.memory => |addr| {
|
||||
_ = try self.addInst(.{
|
||||
.tag = .load_memory,
|
||||
.data = .{
|
||||
.load_memory = .{
|
||||
.register = @enumToInt(reg),
|
||||
.addr = @intCast(u32, addr),
|
||||
},
|
||||
},
|
||||
});
|
||||
// The value is in memory at a hard-coded address.
|
||||
// If the type is a pointer, it means the pointer address is at this memory location.
|
||||
try self.genSetReg(ty, reg, .{ .immediate = addr });
|
||||
try self.genLdrRegister(reg, reg, ty.abiSize(self.target.*));
|
||||
},
|
||||
.stack_offset => |unadjusted_off| {
|
||||
const abi_size = ty.abiSize(self.target.*);
|
||||
|
||||
@ -108,9 +108,10 @@ pub fn emitMir(
|
||||
|
||||
.eor_shifted_register => try emit.mirLogicalShiftedRegister(inst),
|
||||
|
||||
.load_memory => try emit.mirLoadMemory(inst),
|
||||
.load_memory_got => try emit.mirLoadMemoryPie(inst),
|
||||
.load_memory_direct => try emit.mirLoadMemoryPie(inst),
|
||||
.load_memory_ptr_got => try emit.mirLoadMemoryPie(inst),
|
||||
.load_memory_ptr_direct => try emit.mirLoadMemoryPie(inst),
|
||||
|
||||
.ldp => try emit.mirLoadStoreRegisterPair(inst),
|
||||
.stp => try emit.mirLoadStoreRegisterPair(inst),
|
||||
@ -209,17 +210,9 @@ fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize {
|
||||
switch (tag) {
|
||||
.load_memory_got,
|
||||
.load_memory_direct,
|
||||
.load_memory_ptr_got,
|
||||
.load_memory_ptr_direct,
|
||||
=> return 2 * 4,
|
||||
.load_memory => {
|
||||
const load_memory = emit.mir.instructions.items(.data)[inst].load_memory;
|
||||
const addr = load_memory.addr;
|
||||
|
||||
// movz, [movk, ...], ldr
|
||||
if (addr <= math.maxInt(u16)) return 2 * 4;
|
||||
if (addr <= math.maxInt(u32)) return 3 * 4;
|
||||
if (addr <= math.maxInt(u48)) return 4 * 4;
|
||||
return 5 * 4;
|
||||
},
|
||||
.pop_regs, .push_regs => {
|
||||
const reg_list = emit.mir.instructions.items(.data)[inst].reg_list;
|
||||
const number_of_regs = @popCount(u32, reg_list);
|
||||
@ -655,21 +648,6 @@ fn mirLogicalShiftedRegister(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn mirLoadMemory(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
assert(emit.mir.instructions.items(.tag)[inst] == .load_memory);
|
||||
const load_memory = emit.mir.instructions.items(.data)[inst].load_memory;
|
||||
const reg = @intToEnum(Register, load_memory.register);
|
||||
const addr = load_memory.addr;
|
||||
// The value is in memory at a hard-coded address.
|
||||
// If the type is a pointer, it means the pointer address is at this memory location.
|
||||
try emit.moveImmediate(reg, addr);
|
||||
try emit.writeInstruction(Instruction.ldr(
|
||||
reg,
|
||||
reg,
|
||||
Instruction.LoadStoreOffset.none,
|
||||
));
|
||||
}
|
||||
|
||||
fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const payload = emit.mir.instructions.items(.data)[inst].payload;
|
||||
@ -681,12 +659,25 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const offset = @intCast(u32, emit.code.items.len);
|
||||
try emit.writeInstruction(Instruction.adrp(reg, 0));
|
||||
|
||||
// ldr reg, reg, offset
|
||||
try emit.writeInstruction(Instruction.ldr(
|
||||
reg,
|
||||
reg,
|
||||
Instruction.LoadStoreOffset.imm(0),
|
||||
));
|
||||
switch (tag) {
|
||||
.load_memory_got,
|
||||
.load_memory_direct,
|
||||
=> {
|
||||
// ldr reg, reg, offset
|
||||
try emit.writeInstruction(Instruction.ldr(
|
||||
reg,
|
||||
reg,
|
||||
Instruction.LoadStoreOffset.imm(0),
|
||||
));
|
||||
},
|
||||
.load_memory_ptr_got,
|
||||
.load_memory_ptr_direct,
|
||||
=> {
|
||||
// add reg, reg, offset
|
||||
try emit.writeInstruction(Instruction.add(reg, reg, 0, false));
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
|
||||
const atom = macho_file.atom_by_index_table.get(data.atom_index).?;
|
||||
@ -699,8 +690,12 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
.pcrel = true,
|
||||
.length = 2,
|
||||
.@"type" = switch (tag) {
|
||||
.load_memory_got => @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGE21),
|
||||
.load_memory_direct => @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_PAGE21),
|
||||
.load_memory_got,
|
||||
.load_memory_ptr_got,
|
||||
=> @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGE21),
|
||||
.load_memory_direct,
|
||||
.load_memory_ptr_direct,
|
||||
=> @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_PAGE21),
|
||||
else => unreachable,
|
||||
},
|
||||
});
|
||||
@ -713,8 +708,12 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
.pcrel = false,
|
||||
.length = 2,
|
||||
.@"type" = switch (tag) {
|
||||
.load_memory_got => @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGEOFF12),
|
||||
.load_memory_direct => @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_PAGEOFF12),
|
||||
.load_memory_got,
|
||||
.load_memory_ptr_got,
|
||||
=> @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGEOFF12),
|
||||
.load_memory_direct,
|
||||
.load_memory_ptr_direct,
|
||||
=> @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_PAGEOFF12),
|
||||
else => unreachable,
|
||||
},
|
||||
});
|
||||
@ -741,6 +740,7 @@ fn mirLoadStoreRegisterPair(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
fn mirLoadStoreStack(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const load_store_stack = emit.mir.instructions.items(.data)[inst].load_store_stack;
|
||||
const rt = load_store_stack.rt;
|
||||
|
||||
const raw_offset = emit.stack_size - load_store_stack.offset;
|
||||
const offset = switch (tag) {
|
||||
@ -760,7 +760,7 @@ fn mirLoadStoreStack(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
}
|
||||
},
|
||||
.ldr_stack, .str_stack => blk: {
|
||||
const alignment: u32 = switch (load_store_stack.rt.size()) {
|
||||
const alignment: u32 = switch (rt.size()) {
|
||||
32 => 4,
|
||||
64 => 8,
|
||||
else => unreachable,
|
||||
@ -777,36 +777,12 @@ fn mirLoadStoreStack(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
};
|
||||
|
||||
switch (tag) {
|
||||
.ldr_stack => try emit.writeInstruction(Instruction.ldr(
|
||||
load_store_stack.rt,
|
||||
Register.sp,
|
||||
offset,
|
||||
)),
|
||||
.ldrb_stack => try emit.writeInstruction(Instruction.ldrb(
|
||||
load_store_stack.rt,
|
||||
Register.sp,
|
||||
offset,
|
||||
)),
|
||||
.ldrh_stack => try emit.writeInstruction(Instruction.ldrh(
|
||||
load_store_stack.rt,
|
||||
Register.sp,
|
||||
offset,
|
||||
)),
|
||||
.str_stack => try emit.writeInstruction(Instruction.str(
|
||||
load_store_stack.rt,
|
||||
Register.sp,
|
||||
offset,
|
||||
)),
|
||||
.strb_stack => try emit.writeInstruction(Instruction.strb(
|
||||
load_store_stack.rt,
|
||||
Register.sp,
|
||||
offset,
|
||||
)),
|
||||
.strh_stack => try emit.writeInstruction(Instruction.strh(
|
||||
load_store_stack.rt,
|
||||
Register.sp,
|
||||
offset,
|
||||
)),
|
||||
.ldr_stack => try emit.writeInstruction(Instruction.ldr(rt, .sp, offset)),
|
||||
.ldrb_stack => try emit.writeInstruction(Instruction.ldrb(rt, .sp, offset)),
|
||||
.ldrh_stack => try emit.writeInstruction(Instruction.ldrh(rt, .sp, offset)),
|
||||
.str_stack => try emit.writeInstruction(Instruction.str(rt, .sp, offset)),
|
||||
.strb_stack => try emit.writeInstruction(Instruction.strb(rt, .sp, offset)),
|
||||
.strh_stack => try emit.writeInstruction(Instruction.strh(rt, .sp, offset)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
@ -914,7 +890,7 @@ fn mirPushPopRegs(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
if (count == number_of_regs - 1) {
|
||||
try emit.writeInstruction(Instruction.ldr(
|
||||
reg,
|
||||
Register.sp,
|
||||
.sp,
|
||||
Instruction.LoadStoreOffset.imm_post_index(16),
|
||||
));
|
||||
} else {
|
||||
@ -924,7 +900,7 @@ fn mirPushPopRegs(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
try emit.writeInstruction(Instruction.ldp(
|
||||
reg,
|
||||
other_reg,
|
||||
Register.sp,
|
||||
.sp,
|
||||
Instruction.LoadStorePairOffset.post_index(16),
|
||||
));
|
||||
}
|
||||
@ -944,7 +920,7 @@ fn mirPushPopRegs(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
if (count == number_of_regs - 1) {
|
||||
try emit.writeInstruction(Instruction.str(
|
||||
reg,
|
||||
Register.sp,
|
||||
.sp,
|
||||
Instruction.LoadStoreOffset.imm_pre_index(-16),
|
||||
));
|
||||
} else {
|
||||
@ -954,7 +930,7 @@ fn mirPushPopRegs(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
try emit.writeInstruction(Instruction.stp(
|
||||
other_reg,
|
||||
reg,
|
||||
Register.sp,
|
||||
.sp,
|
||||
Instruction.LoadStorePairOffset.pre_index(-16),
|
||||
));
|
||||
}
|
||||
|
||||
@ -56,14 +56,22 @@ pub const Inst = struct {
|
||||
dbg_line,
|
||||
/// Bitwise Exclusive OR (shifted register)
|
||||
eor_shifted_register,
|
||||
/// Pseudo-instruction: Load memory
|
||||
/// Loads the contents into a register
|
||||
///
|
||||
/// Payload is `load_memory`
|
||||
load_memory,
|
||||
/// Payload is `LoadMemoryPie`
|
||||
load_memory_got,
|
||||
/// Loads the contents into a register
|
||||
///
|
||||
/// Payload is `LoadMemoryPie`
|
||||
load_memory_direct,
|
||||
/// Loads the address into a register
|
||||
///
|
||||
/// Payload is `LoadMemoryPie`
|
||||
load_memory_ptr_got,
|
||||
/// Loads the address into a register
|
||||
///
|
||||
/// Payload is `LoadMemoryPie`
|
||||
load_memory_ptr_direct,
|
||||
/// Load Pair of Registers
|
||||
ldp,
|
||||
/// Pseudo-instruction: Load from stack
|
||||
|
||||
@ -1486,19 +1486,19 @@ test "serialize instructions" {
|
||||
.expected = 0b1_00_10000_1111111111111111110_00010,
|
||||
},
|
||||
.{ // stp x1, x2, [sp, #8]
|
||||
.inst = Instruction.stp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.signed(8)),
|
||||
.inst = Instruction.stp(.x1, .x2, .sp, Instruction.LoadStorePairOffset.signed(8)),
|
||||
.expected = 0b10_101_0_010_0_0000001_00010_11111_00001,
|
||||
},
|
||||
.{ // ldp x1, x2, [sp, #8]
|
||||
.inst = Instruction.ldp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.signed(8)),
|
||||
.inst = Instruction.ldp(.x1, .x2, .sp, Instruction.LoadStorePairOffset.signed(8)),
|
||||
.expected = 0b10_101_0_010_1_0000001_00010_11111_00001,
|
||||
},
|
||||
.{ // stp x1, x2, [sp, #-16]!
|
||||
.inst = Instruction.stp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.pre_index(-16)),
|
||||
.inst = Instruction.stp(.x1, .x2, .sp, Instruction.LoadStorePairOffset.pre_index(-16)),
|
||||
.expected = 0b10_101_0_011_0_1111110_00010_11111_00001,
|
||||
},
|
||||
.{ // ldp x1, x2, [sp], #16
|
||||
.inst = Instruction.ldp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.post_index(16)),
|
||||
.inst = Instruction.ldp(.x1, .x2, .sp, Instruction.LoadStorePairOffset.post_index(16)),
|
||||
.expected = 0b10_101_0_001_1_0000010_00010_11111_00001,
|
||||
},
|
||||
.{ // and x0, x4, x2
|
||||
|
||||
@ -305,7 +305,6 @@ fn testIndex2(ptr: [*]align(4) u8, index: usize, comptime T: type) !void {
|
||||
}
|
||||
|
||||
test "alignment of function with c calling convention" {
|
||||
if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
||||
|
||||
var runtime_nothing = ¬hing;
|
||||
|
||||
@ -48,7 +48,6 @@ const g1: i32 = 1233 + 1;
|
||||
var g2: i32 = 0;
|
||||
|
||||
test "global variables" {
|
||||
if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest;
|
||||
try expect(g2 == 0);
|
||||
g2 = g1;
|
||||
try expect(g2 == 1234);
|
||||
@ -604,7 +603,6 @@ test "comptime cast fn to ptr" {
|
||||
}
|
||||
|
||||
test "equality compare fn ptrs" {
|
||||
if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
||||
|
||||
var a = &emptyFn;
|
||||
@ -612,7 +610,6 @@ test "equality compare fn ptrs" {
|
||||
}
|
||||
|
||||
test "self reference through fn ptr field" {
|
||||
if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ const S = struct {
|
||||
p: *S,
|
||||
};
|
||||
test "bug 2006" {
|
||||
if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
|
||||
var a: S = undefined;
|
||||
a = S{ .p = undefined };
|
||||
|
||||
@ -1013,7 +1013,6 @@ test "cast from array reference to fn: comptime fn ptr" {
|
||||
try expect(@ptrToInt(f) == @ptrToInt(&global_array));
|
||||
}
|
||||
test "cast from array reference to fn: runtime fn ptr" {
|
||||
if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
||||
|
||||
@ -81,7 +81,6 @@ fn assertLenIsZero(msg: []const u8) !void {
|
||||
}
|
||||
|
||||
test "access len index of sentinel-terminated slice" {
|
||||
if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
|
||||
const S = struct {
|
||||
|
||||
@ -66,7 +66,6 @@ const SmallStruct = struct {
|
||||
};
|
||||
|
||||
test "lower unnamed constants" {
|
||||
if (builtin.zig_backend == .stage2_aarch64 and builtin.os.tag == .macos) return error.SkipZigTest;
|
||||
var foo = SmallStruct{ .a = 1, .b = 255 };
|
||||
try expect(foo.first() == 1);
|
||||
try expect(foo.second() == 255);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user