mirror of
https://github.com/RPCSX/llvm.git
synced 2024-11-25 04:39:44 +00:00
[Sparc] Add Soft Float support
This change adds support for software floating point operations for Sparc targets. This is the first in a set of patches to enable software floating point on Sparc. The next patch will enable the option to be used with Clang. Differential Revision: http://reviews.llvm.org/D19265 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@269892 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
parent
24466cded7
commit
06dac21852
@ -46,6 +46,9 @@ def FeatureHardQuad
|
||||
def UsePopc : SubtargetFeature<"popc", "UsePopc", "true",
|
||||
"Use the popc (population count) instruction">;
|
||||
|
||||
def FeatureSoftFloat : SubtargetFeature<"soft-float", "UseSoftFloat", "true",
|
||||
"Use software emulation for floating point">;
|
||||
|
||||
//==== Features added predmoninantly for LEON subtarget support
|
||||
include "LeonFeatures.td"
|
||||
|
||||
|
@ -1463,9 +1463,11 @@ SparcTargetLowering::SparcTargetLowering(const TargetMachine &TM,
|
||||
|
||||
// Set up the register classes.
|
||||
addRegisterClass(MVT::i32, &SP::IntRegsRegClass);
|
||||
addRegisterClass(MVT::f32, &SP::FPRegsRegClass);
|
||||
addRegisterClass(MVT::f64, &SP::DFPRegsRegClass);
|
||||
addRegisterClass(MVT::f128, &SP::QFPRegsRegClass);
|
||||
if (!Subtarget->useSoftFloat()) {
|
||||
addRegisterClass(MVT::f32, &SP::FPRegsRegClass);
|
||||
addRegisterClass(MVT::f64, &SP::DFPRegsRegClass);
|
||||
addRegisterClass(MVT::f128, &SP::QFPRegsRegClass);
|
||||
}
|
||||
if (Subtarget->is64Bit()) {
|
||||
addRegisterClass(MVT::i64, &SP::I64RegsRegClass);
|
||||
} else {
|
||||
@ -1760,7 +1762,7 @@ SparcTargetLowering::SparcTargetLowering(const TargetMachine &TM,
|
||||
setOperationAction(ISD::FP_ROUND, MVT::f32, Custom);
|
||||
|
||||
// Setup Runtime library names.
|
||||
if (Subtarget->is64Bit()) {
|
||||
if (Subtarget->is64Bit() && !Subtarget->useSoftFloat()) {
|
||||
setLibcallName(RTLIB::ADD_F128, "_Qp_add");
|
||||
setLibcallName(RTLIB::SUB_F128, "_Qp_sub");
|
||||
setLibcallName(RTLIB::MUL_F128, "_Qp_mul");
|
||||
@ -1778,7 +1780,7 @@ SparcTargetLowering::SparcTargetLowering(const TargetMachine &TM,
|
||||
setLibcallName(RTLIB::FPEXT_F64_F128, "_Qp_dtoq");
|
||||
setLibcallName(RTLIB::FPROUND_F128_F32, "_Qp_qtos");
|
||||
setLibcallName(RTLIB::FPROUND_F128_F64, "_Qp_qtod");
|
||||
} else {
|
||||
} else if (!Subtarget->useSoftFloat()) {
|
||||
setLibcallName(RTLIB::ADD_F128, "_Q_add");
|
||||
setLibcallName(RTLIB::SUB_F128, "_Q_sub");
|
||||
setLibcallName(RTLIB::MUL_F128, "_Q_mul");
|
||||
@ -1806,6 +1808,10 @@ SparcTargetLowering::SparcTargetLowering(const TargetMachine &TM,
|
||||
computeRegisterProperties(Subtarget->getRegisterInfo());
|
||||
}
|
||||
|
||||
bool SparcTargetLowering::useSoftFloat() const {
|
||||
return Subtarget->useSoftFloat();
|
||||
}
|
||||
|
||||
const char *SparcTargetLowering::getTargetNodeName(unsigned Opcode) const {
|
||||
switch ((SPISD::NodeType)Opcode) {
|
||||
case SPISD::FIRST_NUMBER: break;
|
||||
|
@ -59,7 +59,9 @@ namespace llvm {
|
||||
public:
|
||||
SparcTargetLowering(const TargetMachine &TM, const SparcSubtarget &STI);
|
||||
SDValue LowerOperation(SDValue Op, SelectionDAG &DAG) const override;
|
||||
|
||||
|
||||
bool useSoftFloat() const override;
|
||||
|
||||
/// computeKnownBitsForTargetNode - Determine which of the bits specified
|
||||
/// in Mask are known to be either zero or one and return them in the
|
||||
/// KnownZero/KnownOne bitsets.
|
||||
|
@ -34,6 +34,7 @@ SparcSubtarget &SparcSubtarget::initializeSubtargetDependencies(StringRef CPU,
|
||||
IsVIS = false;
|
||||
HasHardQuad = false;
|
||||
UsePopc = false;
|
||||
UseSoftFloat = false;
|
||||
|
||||
// Leon features
|
||||
HasLeonCasa = false;
|
||||
|
@ -39,6 +39,7 @@ class SparcSubtarget : public SparcGenSubtargetInfo {
|
||||
bool Is64Bit;
|
||||
bool HasHardQuad;
|
||||
bool UsePopc;
|
||||
bool UseSoftFloat;
|
||||
|
||||
// LEON features
|
||||
bool HasUmacSmac;
|
||||
@ -77,6 +78,7 @@ public:
|
||||
bool useDeprecatedV8Instructions() const { return V8DeprecatedInsts; }
|
||||
bool hasHardQuad() const { return HasHardQuad; }
|
||||
bool usePopc() const { return UsePopc; }
|
||||
bool useSoftFloat() const { return UseSoftFloat; }
|
||||
|
||||
// Leon options
|
||||
bool hasUmacSmac() const { return HasUmacSmac; }
|
||||
|
@ -62,13 +62,47 @@ SparcTargetMachine::SparcTargetMachine(const Target &T, const Triple &TT,
|
||||
CodeGenOpt::Level OL, bool is64bit)
|
||||
: LLVMTargetMachine(T, computeDataLayout(TT, is64bit), TT, CPU, FS, Options,
|
||||
RM, CM, OL),
|
||||
TLOF(make_unique<SparcELFTargetObjectFile>()),
|
||||
Subtarget(TT, CPU, FS, *this, is64bit) {
|
||||
TLOF(make_unique<SparcELFTargetObjectFile>()) {
|
||||
initAsmInfo();
|
||||
this->is64Bit = is64bit;
|
||||
}
|
||||
|
||||
SparcTargetMachine::~SparcTargetMachine() {}
|
||||
|
||||
const SparcSubtarget *
|
||||
SparcTargetMachine::getSubtargetImpl(const Function &F) const {
|
||||
Attribute CPUAttr = F.getFnAttribute("target-cpu");
|
||||
Attribute FSAttr = F.getFnAttribute("target-features");
|
||||
|
||||
std::string CPU = !CPUAttr.hasAttribute(Attribute::None)
|
||||
? CPUAttr.getValueAsString().str()
|
||||
: TargetCPU;
|
||||
std::string FS = !FSAttr.hasAttribute(Attribute::None)
|
||||
? FSAttr.getValueAsString().str()
|
||||
: TargetFS;
|
||||
|
||||
// FIXME: This is related to the code below to reset the target options,
|
||||
// we need to know whether or not the soft float flag is set on the
|
||||
// function, so we can enable it as a subtarget feature.
|
||||
bool softFloat =
|
||||
F.hasFnAttribute("use-soft-float") &&
|
||||
F.getFnAttribute("use-soft-float").getValueAsString() == "true";
|
||||
|
||||
if (softFloat)
|
||||
FS += FS.empty() ? "+soft-float" : ",+soft-float";
|
||||
|
||||
auto &I = SubtargetMap[CPU + FS];
|
||||
if (!I) {
|
||||
// This needs to be done before we create a new subtarget since any
|
||||
// creation will depend on the TM and the code generation flags on the
|
||||
// function that reside in TargetOptions.
|
||||
resetTargetOptions(F);
|
||||
I = llvm::make_unique<SparcSubtarget>(TargetTriple, CPU, FS, *this,
|
||||
this->is64Bit);
|
||||
}
|
||||
return I.get();
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// Sparc Code Generator Pass Configuration Options.
|
||||
class SparcPassConfig : public TargetPassConfig {
|
||||
|
@ -22,7 +22,8 @@ namespace llvm {
|
||||
|
||||
class SparcTargetMachine : public LLVMTargetMachine {
|
||||
std::unique_ptr<TargetLoweringObjectFile> TLOF;
|
||||
SparcSubtarget Subtarget;
|
||||
bool is64Bit;
|
||||
mutable StringMap<std::unique_ptr<SparcSubtarget>> SubtargetMap;
|
||||
public:
|
||||
SparcTargetMachine(const Target &T, const Triple &TT, StringRef CPU,
|
||||
StringRef FS, const TargetOptions &Options,
|
||||
@ -30,9 +31,7 @@ public:
|
||||
bool is64bit);
|
||||
~SparcTargetMachine() override;
|
||||
|
||||
const SparcSubtarget *getSubtargetImpl(const Function &) const override {
|
||||
return &Subtarget;
|
||||
}
|
||||
const SparcSubtarget *getSubtargetImpl(const Function &) const override;
|
||||
|
||||
// Pass Pipeline Configuration
|
||||
TargetPassConfig *createPassConfig(PassManagerBase &PM) override;
|
||||
|
@ -1,5 +1,6 @@
|
||||
; RUN: llc < %s -march=sparc -disable-sparc-delay-filler -disable-sparc-leaf-proc | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-BE
|
||||
; RUN: llc < %s -march=sparcel -disable-sparc-delay-filler -disable-sparc-leaf-proc | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-LE
|
||||
; RUN: llc < %s -march=sparc -disable-sparc-delay-filler -disable-sparc-leaf-proc | FileCheck %s --check-prefix=CHECK --check-prefix=HARD --check-prefix=CHECK-BE
|
||||
; RUN: llc < %s -march=sparcel -disable-sparc-delay-filler -disable-sparc-leaf-proc | FileCheck %s --check-prefix=CHECK --check-prefix=HARD --check-prefix=CHECK-LE
|
||||
; RUN: llc < %s -march=sparc -disable-sparc-delay-filler -disable-sparc-leaf-proc -mattr=soft-float | FileCheck %s --check-prefix=CHECK --check-prefix=SOFT --check-prefix=CHECK-BE
|
||||
|
||||
; CHECK-LABEL: intarg:
|
||||
; The save/restore frame is not strictly necessary here, but we would need to
|
||||
@ -55,29 +56,80 @@ define void @call_intarg(i32 %i0, i8* %i1) {
|
||||
;; straddling the boundary of regs and mem, and floats in regs and mem.
|
||||
;
|
||||
; CHECK-LABEL: floatarg:
|
||||
; CHECK: save %sp, -120, %sp
|
||||
; CHECK: mov %i5, %g2
|
||||
; CHECK-NEXT: ld [%fp+92], %g3
|
||||
; CHECK-NEXT: mov %i4, %i5
|
||||
; CHECK-NEXT: std %g2, [%fp+-24]
|
||||
; CHECK-NEXT: mov %i3, %i4
|
||||
; CHECK-NEXT: std %i4, [%fp+-16]
|
||||
; CHECK-NEXT: std %i0, [%fp+-8]
|
||||
; CHECK-NEXT: st %i2, [%fp+-28]
|
||||
; CHECK-NEXT: ld [%fp+104], %f0
|
||||
; CHECK-NEXT: ldd [%fp+96], %f2
|
||||
; CHECK-NEXT: ld [%fp+-28], %f1
|
||||
; CHECK-NEXT: ldd [%fp+-8], %f4
|
||||
; CHECK-NEXT: ldd [%fp+-16], %f6
|
||||
; CHECK-NEXT: ldd [%fp+-24], %f8
|
||||
; CHECK-NEXT: fstod %f1, %f10
|
||||
; CHECK-NEXT: faddd %f4, %f10, %f4
|
||||
; CHECK-NEXT: faddd %f6, %f4, %f4
|
||||
; CHECK-NEXT: faddd %f8, %f4, %f4
|
||||
; CHECK-NEXT: faddd %f2, %f4, %f2
|
||||
; CHECK-NEXT: fstod %f0, %f0
|
||||
; CHECK-NEXT: faddd %f0, %f2, %f0
|
||||
; CHECK-NEXT: restore
|
||||
; HARD: save %sp, -120, %sp
|
||||
; HARD: mov %i5, %g2
|
||||
; HARD-NEXT: ld [%fp+92], %g3
|
||||
; HARD-NEXT: mov %i4, %i5
|
||||
; HARD-NEXT: std %g2, [%fp+-24]
|
||||
; HARD-NEXT: mov %i3, %i4
|
||||
; HARD-NEXT: std %i4, [%fp+-16]
|
||||
; HARD-NEXT: std %i0, [%fp+-8]
|
||||
; HARD-NEXT: st %i2, [%fp+-28]
|
||||
; HARD-NEXT: ld [%fp+104], %f0
|
||||
; HARD-NEXT: ldd [%fp+96], %f2
|
||||
; HARD-NEXT: ld [%fp+-28], %f1
|
||||
; HARD-NEXT: ldd [%fp+-8], %f4
|
||||
; HARD-NEXT: ldd [%fp+-16], %f6
|
||||
; HARD-NEXT: ldd [%fp+-24], %f8
|
||||
; HARD-NEXT: fstod %f1, %f10
|
||||
; HARD-NEXT: faddd %f4, %f10, %f4
|
||||
; HARD-NEXT: faddd %f6, %f4, %f4
|
||||
; HARD-NEXT: faddd %f8, %f4, %f4
|
||||
; HARD-NEXT: faddd %f2, %f4, %f2
|
||||
; HARD-NEXT: fstod %f0, %f0
|
||||
; HARD-NEXT: faddd %f0, %f2, %f0
|
||||
; SOFT: save %sp, -96, %sp
|
||||
; SOFT: ld [%fp+104], %l0
|
||||
; SOFT-NEXT: ld [%fp+96], %l1
|
||||
; SOFT-NEXT: ld [%fp+100], %l2
|
||||
; SOFT-NEXT: ld [%fp+92], %l3
|
||||
; SOFT-NEXT: mov %i2, %o0
|
||||
; SOFT-NEXT: call __extendsfdf2
|
||||
; SOFT-NEXT: nop
|
||||
; SOFT-NEXT: mov %o0, %i2
|
||||
; SOFT-NEXT: mov %o1, %g2
|
||||
; SOFT-NEXT: mov %i0, %o0
|
||||
; SOFT-NEXT: mov %i1, %o1
|
||||
; SOFT-NEXT: mov %i2, %o2
|
||||
; SOFT-NEXT: mov %g2, %o3
|
||||
; SOFT-NEXT: call __adddf3
|
||||
; SOFT-NEXT: nop
|
||||
; SOFT-NEXT: mov %o0, %i0
|
||||
; SOFT-NEXT: mov %o1, %i1
|
||||
; SOFT-NEXT: mov %i3, %o0
|
||||
; SOFT-NEXT: mov %i4, %o1
|
||||
; SOFT-NEXT: mov %i0, %o2
|
||||
; SOFT-NEXT: mov %i1, %o3
|
||||
; SOFT-NEXT: call __adddf3
|
||||
; SOFT-NEXT: nop
|
||||
; SOFT-NEXT: mov %o0, %i0
|
||||
; SOFT-NEXT: mov %o1, %i1
|
||||
; SOFT-NEXT: mov %i5, %o0
|
||||
; SOFT-NEXT: mov %l3, %o1
|
||||
; SOFT-NEXT: mov %i0, %o2
|
||||
; SOFT-NEXT: mov %i1, %o3
|
||||
; SOFT-NEXT: call __adddf3
|
||||
; SOFT-NEXT: nop
|
||||
; SOFT-NEXT: mov %o0, %i0
|
||||
; SOFT-NEXT: mov %o1, %i1
|
||||
; SOFT-NEXT: mov %l1, %o0
|
||||
; SOFT-NEXT: mov %l2, %o1
|
||||
; SOFT-NEXT: mov %i0, %o2
|
||||
; SOFT-NEXT: mov %i1, %o3
|
||||
; SOFT-NEXT: call __adddf3
|
||||
; SOFT-NEXT: nop
|
||||
; SOFT-NEXT: mov %o0, %i0
|
||||
; SOFT-NEXT: mov %o1, %i1
|
||||
; SOFT-NEXT: mov %l0, %o0
|
||||
; SOFT-NEXT: call __extendsfdf2
|
||||
; SOFT-NEXT: nop
|
||||
; SOFT-NEXT: mov %i0, %o2
|
||||
; SOFT-NEXT: mov %i1, %o3
|
||||
; SOFT-NEXT: call __adddf3
|
||||
; SOFT-NEXT: nop
|
||||
; SOFT-NEXT: mov %o0, %i0
|
||||
; SOFT-NEXT: mov %o1, %i1
|
||||
; CHECK: restore
|
||||
define double @floatarg(double %a0, ; %i0,%i1
|
||||
float %a1, ; %i2
|
||||
double %a2, ; %i3, %i4
|
||||
@ -95,18 +147,30 @@ define double @floatarg(double %a0, ; %i0,%i1
|
||||
}
|
||||
|
||||
; CHECK-LABEL: call_floatarg:
|
||||
; CHECK: save %sp, -112, %sp
|
||||
; CHECK: mov %i2, %o1
|
||||
; CHECK-NEXT: mov %i1, %o0
|
||||
; CHECK-NEXT: st %i0, [%sp+104]
|
||||
; CHECK-NEXT: std %o0, [%sp+96]
|
||||
; CHECK-NEXT: st %o1, [%sp+92]
|
||||
; CHECK-NEXT: mov %i0, %o2
|
||||
; CHECK-NEXT: mov %o0, %o3
|
||||
; CHECK-NEXT: mov %o1, %o4
|
||||
; CHECK-NEXT: mov %o0, %o5
|
||||
; CHECK-NEXT: call floatarg
|
||||
; CHECK: std %f0, [%i4]
|
||||
; HARD: save %sp, -112, %sp
|
||||
; HARD: mov %i2, %o1
|
||||
; HARD-NEXT: mov %i1, %o0
|
||||
; HARD-NEXT: st %i0, [%sp+104]
|
||||
; HARD-NEXT: std %o0, [%sp+96]
|
||||
; HARD-NEXT: st %o1, [%sp+92]
|
||||
; HARD-NEXT: mov %i0, %o2
|
||||
; HARD-NEXT: mov %o0, %o3
|
||||
; HARD-NEXT: mov %o1, %o4
|
||||
; HARD-NEXT: mov %o0, %o5
|
||||
; HARD-NEXT: call floatarg
|
||||
; HARD: std %f0, [%i4]
|
||||
; SOFT: st %i0, [%sp+104]
|
||||
; SOFT-NEXT: st %i2, [%sp+100]
|
||||
; SOFT-NEXT: st %i1, [%sp+96]
|
||||
; SOFT-NEXT: st %i2, [%sp+92]
|
||||
; SOFT-NEXT: mov %i1, %o0
|
||||
; SOFT-NEXT: mov %i2, %o1
|
||||
; SOFT-NEXT: mov %i0, %o2
|
||||
; SOFT-NEXT: mov %i1, %o3
|
||||
; SOFT-NEXT: mov %i2, %o4
|
||||
; SOFT-NEXT: mov %i1, %o5
|
||||
; SOFT-NEXT: call floatarg
|
||||
; SOFT: std %o0, [%i4]
|
||||
; CHECK: restore
|
||||
define void @call_floatarg(float %f1, double %d2, float %f5, double *%p) {
|
||||
%r = call double @floatarg(double %d2, float %f1, double %d2, double %d2,
|
||||
|
@ -1,4 +1,5 @@
|
||||
; RUN: llc < %s -march=sparcv9 -disable-sparc-delay-filler -disable-sparc-leaf-proc | FileCheck %s
|
||||
; RUN: llc < %s -march=sparcv9 -disable-sparc-delay-filler -disable-sparc-leaf-proc | FileCheck %s --check-prefix=CHECK --check-prefix=HARD
|
||||
; RUN: llc < %s -march=sparcv9 -disable-sparc-delay-filler -disable-sparc-leaf-proc -mattr=soft-float | FileCheck %s --check-prefix=CHECK --check-prefix=SOFT
|
||||
|
||||
; CHECK-LABEL: intarg:
|
||||
; The save/restore frame is not strictly necessary here, but we would need to
|
||||
@ -54,13 +55,22 @@ define void @call_intarg(i32 %i0, i8* %i1) {
|
||||
}
|
||||
|
||||
; CHECK-LABEL: floatarg:
|
||||
; CHECK: save %sp, -128, %sp
|
||||
; CHECK: ld [%fp+2307], [[F:%f[0-9]+]]
|
||||
; CHECK: fstod %f1,
|
||||
; CHECK: faddd %f2,
|
||||
; CHECK: faddd %f4,
|
||||
; CHECK: faddd %f6,
|
||||
; CHECK: fadds %f31, [[F]]
|
||||
; HARD: save %sp, -128, %sp
|
||||
; HARD: ld [%fp+2307], [[F:%f[0-9]+]]
|
||||
; HARD: fstod %f1,
|
||||
; HARD: faddd %f2,
|
||||
; HARD: faddd %f4,
|
||||
; HARD: faddd %f6,
|
||||
; HARD: fadds %f31, [[F]]
|
||||
; SOFT: save %sp, -176, %sp
|
||||
; SOFT: srl %i0, 0, %o0
|
||||
; SOFT-NEXT: call __extendsfdf2
|
||||
; SOFT: mov %o0, %i0
|
||||
; SOFT: mov %i1, %o0
|
||||
; SOFT: mov %i2, %o0
|
||||
; SOFT: mov %i3, %o0
|
||||
; SOFT: ld [%fp+2299], %o0
|
||||
; SOFT: ld [%fp+2307], %o1
|
||||
define double @floatarg(float %a0, ; %f1
|
||||
double %a1, ; %d2
|
||||
double %a2, ; %d4
|
||||
@ -92,13 +102,32 @@ define double @floatarg(float %a0, ; %f1
|
||||
; CHECK-LABEL: call_floatarg:
|
||||
; CHECK: save %sp, -272, %sp
|
||||
; Store 8 bytes in full slot.
|
||||
; CHECK: std %f2, [%sp+2311]
|
||||
; HARD: std %f2, [%sp+2311]
|
||||
; Store 4 bytes, right-aligned in slot.
|
||||
; CHECK: st %f1, [%sp+2307]
|
||||
; CHECK: fmovd %f2, %f4
|
||||
; HARD: st %f1, [%sp+2307]
|
||||
; HARD: fmovd %f2, %f4
|
||||
; SOFT: stx %i1, [%sp+2311]
|
||||
; SOFT: stx %i0, [%sp+2303]
|
||||
; SOFT: stx %i2, [%sp+2295]
|
||||
; SOFT: stx %i2, [%sp+2287]
|
||||
; SOFT: stx %i2, [%sp+2279]
|
||||
; SOFT: stx %i2, [%sp+2271]
|
||||
; SOFT: stx %i2, [%sp+2263]
|
||||
; SOFT: stx %i2, [%sp+2255]
|
||||
; SOFT: stx %i2, [%sp+2247]
|
||||
; SOFT: stx %i2, [%sp+2239]
|
||||
; SOFT: stx %i2, [%sp+2231]
|
||||
; SOFT: stx %i2, [%sp+2223]
|
||||
; SOFT: mov %i2, %o0
|
||||
; SOFT: mov %i1, %o1
|
||||
; SOFT: mov %i1, %o2
|
||||
; SOFT: mov %i1, %o3
|
||||
; SOFT: mov %i2, %o4
|
||||
; SOFT: mov %i2, %o5
|
||||
; CHECK: call floatarg
|
||||
; CHECK-NOT: add %sp
|
||||
; CHECK: restore
|
||||
|
||||
define void @call_floatarg(float %f1, double %d2, float %f5, double *%p) {
|
||||
%r = call double @floatarg(float %f5, double %d2, double %d2, double %d2,
|
||||
float %f5, float %f5, float %f5, float %f5,
|
||||
@ -112,9 +141,21 @@ define void @call_floatarg(float %f1, double %d2, float %f5, double *%p) {
|
||||
; CHECK-LABEL: mixedarg:
|
||||
; CHECK: ldx [%fp+2247]
|
||||
; CHECK: ldx [%fp+2231]
|
||||
; CHECK: fstod %f3
|
||||
; CHECK: faddd %f6
|
||||
; CHECK: faddd %f16
|
||||
; SOFT: ldx [%fp+2239], %i0
|
||||
; HARD: fstod %f3
|
||||
; HARD: faddd %f6
|
||||
; HARD: faddd %f16
|
||||
; SOFT: mov %o0, %i1
|
||||
; SOFT-NEXT: mov %i3, %o0
|
||||
; SOFT-NEXT: mov %i1, %o1
|
||||
; SOFT-NEXT: call __adddf3
|
||||
; SOFT: mov %o0, %i1
|
||||
; SOFT-NEXT: mov %i0, %o0
|
||||
; SOFT-NEXT: mov %i1, %o1
|
||||
; SOFT-NEXT: call __adddf3
|
||||
; HARD: std %f0, [%i1]
|
||||
; SOFT: stx %o0, [%i5]
|
||||
|
||||
define void @mixedarg(i8 %a0, ; %i0
|
||||
float %a1, ; %f3
|
||||
i16 %a2, ; %i2
|
||||
@ -135,12 +176,15 @@ define void @mixedarg(i8 %a0, ; %i0
|
||||
|
||||
; CHECK-LABEL: call_mixedarg:
|
||||
; CHECK: stx %i2, [%sp+2247]
|
||||
; SOFT: stx %i1, [%sp+2239]
|
||||
; CHECK: stx %i0, [%sp+2223]
|
||||
; CHECK: fmovd %f2, %f6
|
||||
; CHECK: fmovd %f2, %f16
|
||||
; HARD: fmovd %f2, %f6
|
||||
; HARD: fmovd %f2, %f16
|
||||
; SOFT: mov %i1, %o3
|
||||
; CHECK: call mixedarg
|
||||
; CHECK-NOT: add %sp
|
||||
; CHECK: restore
|
||||
|
||||
define void @call_mixedarg(i64 %i0, double %f2, i16* %i2) {
|
||||
call void @mixedarg(i8 undef,
|
||||
float undef,
|
||||
@ -158,8 +202,10 @@ define void @call_mixedarg(i64 %i0, double %f2, i16* %i2) {
|
||||
; The inreg attribute is used to indicate 32-bit sized struct elements that
|
||||
; share an 8-byte slot.
|
||||
; CHECK-LABEL: inreg_fi:
|
||||
; CHECK: fstoi %f1
|
||||
; CHECK: srlx %i0, 32, [[R:%[gilo][0-7]]]
|
||||
; SOFT: srlx %i0, 32, [[R:%[gilo][0-7]]]
|
||||
; HARD: fstoi %f1
|
||||
; SOFT: call __fixsfsi
|
||||
; HARD: srlx %i0, 32, [[R:%[gilo][0-7]]]
|
||||
; CHECK: sub [[R]],
|
||||
define i32 @inreg_fi(i32 inreg %a0, ; high bits of %i0
|
||||
float inreg %a1) { ; %f1
|
||||
@ -171,8 +217,11 @@ define i32 @inreg_fi(i32 inreg %a0, ; high bits of %i0
|
||||
; CHECK-LABEL: call_inreg_fi:
|
||||
; Allocate space for 6 arguments, even when only 2 are used.
|
||||
; CHECK: save %sp, -176, %sp
|
||||
; CHECK: sllx %i1, 32, %o0
|
||||
; CHECK: fmovs %f5, %f1
|
||||
; HARD: sllx %i1, 32, %o0
|
||||
; HARD: fmovs %f5, %f1
|
||||
; SOFT: srl %i2, 0, %i0
|
||||
; SOFT: sllx %i1, 32, %i1
|
||||
; SOFT: or %i1, %i0, %o0
|
||||
; CHECK: call inreg_fi
|
||||
define void @call_inreg_fi(i32* %p, i32 %i1, float %f5) {
|
||||
%x = call i32 @inreg_fi(i32 %i1, float %f5)
|
||||
@ -180,7 +229,10 @@ define void @call_inreg_fi(i32* %p, i32 %i1, float %f5) {
|
||||
}
|
||||
|
||||
; CHECK-LABEL: inreg_ff:
|
||||
; CHECK: fsubs %f0, %f1, %f0
|
||||
; HARD: fsubs %f0, %f1, %f0
|
||||
; SOFT: srlx %i0, 32, %o0
|
||||
; SOFT: srl %i0, 0, %o1
|
||||
; SOFT: call __subsf3
|
||||
define float @inreg_ff(float inreg %a0, ; %f0
|
||||
float inreg %a1) { ; %f1
|
||||
%rv = fsub float %a0, %a1
|
||||
@ -188,8 +240,11 @@ define float @inreg_ff(float inreg %a0, ; %f0
|
||||
}
|
||||
|
||||
; CHECK-LABEL: call_inreg_ff:
|
||||
; CHECK: fmovs %f3, %f0
|
||||
; CHECK: fmovs %f5, %f1
|
||||
; HARD: fmovs %f3, %f0
|
||||
; HARD: fmovs %f5, %f1
|
||||
; SOFT: srl %i2, 0, %i0
|
||||
; SOFT: sllx %i1, 32, %i1
|
||||
; SOFT: or %i1, %i0, %o0
|
||||
; CHECK: call inreg_ff
|
||||
define void @call_inreg_ff(i32* %p, float %f3, float %f5) {
|
||||
%x = call float @inreg_ff(float %f3, float %f5)
|
||||
@ -197,7 +252,9 @@ define void @call_inreg_ff(i32* %p, float %f3, float %f5) {
|
||||
}
|
||||
|
||||
; CHECK-LABEL: inreg_if:
|
||||
; CHECK: fstoi %f0
|
||||
; HARD: fstoi %f0
|
||||
; SOFT: srlx %i0, 32, %o0
|
||||
; SOFT: call __fixsfsi
|
||||
; CHECK: sub %i0
|
||||
define i32 @inreg_if(float inreg %a0, ; %f0
|
||||
i32 inreg %a1) { ; low bits of %i0
|
||||
@ -207,8 +264,11 @@ define i32 @inreg_if(float inreg %a0, ; %f0
|
||||
}
|
||||
|
||||
; CHECK-LABEL: call_inreg_if:
|
||||
; CHECK: fmovs %f3, %f0
|
||||
; CHECK: mov %i2, %o0
|
||||
; HARD: fmovs %f3, %f0
|
||||
; HARD: mov %i2, %o0
|
||||
; SOFT: srl %i2, 0, %i0
|
||||
; SOFT: sllx %i1, 32, %i1
|
||||
; SOFT: or %i1, %i0, %o0
|
||||
; CHECK: call inreg_if
|
||||
define void @call_inreg_if(i32* %p, float %f3, i32 %i2) {
|
||||
%x = call i32 @inreg_if(float %f3, i32 %i2)
|
||||
@ -265,7 +325,8 @@ define void @call_ret_i64_pair(i64* %i0) {
|
||||
; This is not a C struct, the i32 member uses 8 bytes, but the float only 4.
|
||||
; CHECK-LABEL: ret_i32_float_pair:
|
||||
; CHECK: ld [%i2], %i0
|
||||
; CHECK: ld [%i3], %f2
|
||||
; HARD: ld [%i3], %f2
|
||||
; SOFT: ld [%i3], %i1
|
||||
define { i32, float } @ret_i32_float_pair(i32 %a0, i32 %a1,
|
||||
i32* %p, float* %q) {
|
||||
%r1 = load i32, i32* %p
|
||||
@ -279,7 +340,8 @@ define { i32, float } @ret_i32_float_pair(i32 %a0, i32 %a1,
|
||||
; CHECK-LABEL: call_ret_i32_float_pair:
|
||||
; CHECK: call ret_i32_float_pair
|
||||
; CHECK: st %o0, [%i0]
|
||||
; CHECK: st %f2, [%i1]
|
||||
; HARD: st %f2, [%i1]
|
||||
; SOFT: st %o1, [%i1]
|
||||
define void @call_ret_i32_float_pair(i32* %i0, float* %i1) {
|
||||
%rv = call { i32, float } @ret_i32_float_pair(i32 undef, i32 undef,
|
||||
i32* undef, float* undef)
|
||||
@ -293,7 +355,8 @@ define void @call_ret_i32_float_pair(i32* %i0, float* %i1) {
|
||||
; This is a C struct, each member uses 4 bytes.
|
||||
; CHECK-LABEL: ret_i32_float_packed:
|
||||
; CHECK: ld [%i2], [[R:%[gilo][0-7]]]
|
||||
; CHECK: ld [%i3], %f1
|
||||
; HARD: ld [%i3], %f1
|
||||
; SOFT: ld [%i3], %i1
|
||||
; CHECK: sllx [[R]], 32, %i0
|
||||
define inreg { i32, float } @ret_i32_float_packed(i32 %a0, i32 %a1,
|
||||
i32* %p, float* %q) {
|
||||
@ -309,7 +372,8 @@ define inreg { i32, float } @ret_i32_float_packed(i32 %a0, i32 %a1,
|
||||
; CHECK: call ret_i32_float_packed
|
||||
; CHECK: srlx %o0, 32, [[R:%[gilo][0-7]]]
|
||||
; CHECK: st [[R]], [%i0]
|
||||
; CHECK: st %f1, [%i1]
|
||||
; HARD: st %f1, [%i1]
|
||||
; SOFT: st %o0, [%i1]
|
||||
define void @call_ret_i32_float_packed(i32* %i0, float* %i1) {
|
||||
%rv = call { i32, float } @ret_i32_float_packed(i32 undef, i32 undef,
|
||||
i32* undef, float* undef)
|
||||
@ -413,13 +477,21 @@ entry:
|
||||
declare i32 @use_buf(i32, i8*)
|
||||
|
||||
; CHECK-LABEL: test_fp128_args:
|
||||
; CHECK-DAG: std %f0, [%fp+{{.+}}]
|
||||
; CHECK-DAG: std %f2, [%fp+{{.+}}]
|
||||
; CHECK-DAG: std %f6, [%fp+{{.+}}]
|
||||
; CHECK-DAG: std %f4, [%fp+{{.+}}]
|
||||
; CHECK: add %fp, [[Offset:[0-9]+]], %o0
|
||||
; CHECK: call _Qp_add
|
||||
; CHECK: ldd [%fp+[[Offset]]], %f0
|
||||
; HARD-DAG: std %f0, [%fp+{{.+}}]
|
||||
; HARD-DAG: std %f2, [%fp+{{.+}}]
|
||||
; HARD-DAG: std %f6, [%fp+{{.+}}]
|
||||
; HARD-DAG: std %f4, [%fp+{{.+}}]
|
||||
; HARD: add %fp, [[Offset:[0-9]+]], %o0
|
||||
; HARD: call _Qp_add
|
||||
; HARD: ldd [%fp+[[Offset]]], %f0
|
||||
; SOFT-DAG: mov %i0, %o0
|
||||
; SOFT-DAG: mov %i1, %o1
|
||||
; SOFT-DAG: mov %i2, %o2
|
||||
; SOFT-DAG: mov %i3, %o3
|
||||
; SOFT: call __addtf3
|
||||
; SOFT: mov %o0, %i0
|
||||
; SOFT: mov %o1, %i1
|
||||
|
||||
define fp128 @test_fp128_args(fp128 %a, fp128 %b) {
|
||||
entry:
|
||||
%0 = fadd fp128 %a, %b
|
||||
@ -429,11 +501,14 @@ entry:
|
||||
declare i64 @receive_fp128(i64 %a, ...)
|
||||
|
||||
; CHECK-LABEL: test_fp128_variable_args:
|
||||
; CHECK-DAG: std %f4, [%sp+[[Offset0:[0-9]+]]]
|
||||
; CHECK-DAG: std %f6, [%sp+[[Offset1:[0-9]+]]]
|
||||
; CHECK-DAG: ldx [%sp+[[Offset0]]], %o2
|
||||
; CHECK-DAG: ldx [%sp+[[Offset1]]], %o3
|
||||
; CHECK: call receive_fp128
|
||||
; HARD-DAG: std %f4, [%sp+[[Offset0:[0-9]+]]]
|
||||
; HARD-DAG: std %f6, [%sp+[[Offset1:[0-9]+]]]
|
||||
; HARD-DAG: ldx [%sp+[[Offset0]]], %o2
|
||||
; HARD-DAG: ldx [%sp+[[Offset1]]], %o3
|
||||
; SOFT-DAG: mov %i0, %o0
|
||||
; SOFT-DAG: mov %i1, %o1
|
||||
; SOFT-DAG: mov %i2, %o2
|
||||
; CHECK: call receive_fp128
|
||||
define i64 @test_fp128_variable_args(i64 %a, fp128 %b) {
|
||||
entry:
|
||||
%0 = call i64 (i64, ...) @receive_fp128(i64 %a, fp128 %b)
|
||||
@ -441,14 +516,22 @@ entry:
|
||||
}
|
||||
|
||||
; CHECK-LABEL: test_call_libfunc:
|
||||
; CHECK: st %f1, [%fp+[[Offset0:[0-9]+]]]
|
||||
; CHECK: fmovs %f3, %f1
|
||||
; CHECK: call cosf
|
||||
; CHECK: st %f0, [%fp+[[Offset1:[0-9]+]]]
|
||||
; CHECK: ld [%fp+[[Offset0]]], %f1
|
||||
; CHECK: call sinf
|
||||
; CHECK: ld [%fp+[[Offset1]]], %f1
|
||||
; CHECK: fmuls %f1, %f0, %f0
|
||||
; HARD: st %f1, [%fp+[[Offset0:[0-9]+]]]
|
||||
; HARD: fmovs %f3, %f1
|
||||
; SOFT: srl %i1, 0, %o0
|
||||
; CHECK: call cosf
|
||||
; HARD: st %f0, [%fp+[[Offset1:[0-9]+]]]
|
||||
; HARD: ld [%fp+[[Offset0]]], %f1
|
||||
; SOFT: mov %o0, %i1
|
||||
; SOFT: srl %i0, 0, %o0
|
||||
; CHECK: call sinf
|
||||
; HARD: ld [%fp+[[Offset1]]], %f1
|
||||
; HARD: fmuls %f1, %f0, %f0
|
||||
; SOFT: mov %o0, %i0
|
||||
; SOFT: mov %i1, %o0
|
||||
; SOFT: mov %i0, %o1
|
||||
; SOFT: call __mulsf3
|
||||
; SOFT: sllx %o0, 32, %i0
|
||||
|
||||
define inreg float @test_call_libfunc(float %arg0, float %arg1) {
|
||||
entry:
|
||||
@ -460,5 +543,3 @@ entry:
|
||||
|
||||
declare inreg float @cosf(float %arg) readnone nounwind
|
||||
declare inreg float @sinf(float %arg) readnone nounwind
|
||||
|
||||
|
||||
|
235
test/CodeGen/SPARC/soft-float.ll
Normal file
235
test/CodeGen/SPARC/soft-float.ll
Normal file
@ -0,0 +1,235 @@
|
||||
; RUN: llc -march=sparc -mattr=soft-float -O0 < %s | FileCheck %s
|
||||
|
||||
; Arithmetic functions
|
||||
|
||||
define float @test_addsf3(float %a, float %b) #0 {
|
||||
; CHECK-LABEL: test_addsf3:
|
||||
; CHECK: call __addsf3
|
||||
%add = fadd float %a, %b
|
||||
ret float %add
|
||||
}
|
||||
|
||||
define double @test_adddf3(double %a, double %b) #0 {
|
||||
; CHECK-LABEL: test_adddf3:
|
||||
; CHECK: call __adddf3
|
||||
%add = fadd double %a, %b
|
||||
ret double %add
|
||||
}
|
||||
|
||||
define fp128 @test_addtf3(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABEL: test_addtf3:
|
||||
; CHECK: call __addtf3
|
||||
%add = fadd fp128 %a, %b
|
||||
ret fp128 %add
|
||||
}
|
||||
|
||||
define float @test_mulsf3(float %a, float %b) #0 {
|
||||
; CHECK-LABEL: test_mulsf3:
|
||||
; CHECK: call __mulsf3
|
||||
%mul = fmul float %a, %b
|
||||
ret float %mul
|
||||
}
|
||||
|
||||
define double @test_muldf3(double %a, double %b) #0 {
|
||||
; CHECK-LABEL: test_muldf3:
|
||||
; CHECK: call __muldf3
|
||||
%mul = fmul double %a, %b
|
||||
ret double %mul
|
||||
}
|
||||
|
||||
define fp128 @test_multf3(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABEL: test_multf3:
|
||||
; CHECK: call __multf3
|
||||
%mul = fmul fp128 %a, %b
|
||||
ret fp128 %mul
|
||||
}
|
||||
|
||||
define float @test_subsf3(float %a, float %b) #0 {
|
||||
; CHCEK-LABEL: test_subsf3:
|
||||
; CHECK: call __subsf3
|
||||
%sub = fsub float %a, %b
|
||||
ret float %sub
|
||||
}
|
||||
|
||||
define double @test_subdf3(double %a, double %b) #0 {
|
||||
; CHCEK-LABEL: test_subdf3:
|
||||
; CHECK: call __subdf3
|
||||
%sub = fsub double %a, %b
|
||||
ret double %sub
|
||||
}
|
||||
|
||||
define fp128 @test_subtf3(fp128 %a, fp128 %b) #0 {
|
||||
; CHCEK-LABEL: test_subtf3:
|
||||
; CHECK: call __subtf3
|
||||
%sub = fsub fp128 %a, %b
|
||||
ret fp128 %sub
|
||||
}
|
||||
|
||||
define float @test_divsf3(float %a, float %b) #0 {
|
||||
; CHECK-LABEL: test_divsf3:
|
||||
; CHECK: call __divsf3
|
||||
%div = fdiv float %a, %b
|
||||
ret float %div
|
||||
}
|
||||
|
||||
define double @test_divdf3(double %a, double %b) #0 {
|
||||
; CHECK-LABEL: test_divdf3:
|
||||
; CHECK: call __divdf3
|
||||
%div = fdiv double %a, %b
|
||||
ret double %div
|
||||
}
|
||||
|
||||
define fp128 @test_divtf3(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABEL: test_divtf3:
|
||||
; CHECK: call __divtf3
|
||||
%div = fdiv fp128 %a, %b
|
||||
ret fp128 %div
|
||||
}
|
||||
|
||||
; Comparison functions
|
||||
define i1 @test_unordsf2(float %a, float %b) #0 {
|
||||
; CHECK-LABEL: test_unordsf2:
|
||||
; CHECK: call __unordsf2
|
||||
%cmp = fcmp uno float %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_unorddf2(double %a, double %b) #0 {
|
||||
; CHECK-LABEL: test_unorddf2:
|
||||
; CHECK: call __unorddf2
|
||||
%cmp = fcmp uno double %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_unordtf2(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABEL: test_unordtf2:
|
||||
; CHECK: call __unordtf2
|
||||
%cmp = fcmp uno fp128 %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_eqsf2(float %a, float %b) #0 {
|
||||
; CHECK-LABEL: test_eqsf2:
|
||||
; CHECK: call __eqsf2
|
||||
%cmp = fcmp oeq float %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_eqdf2(double %a, double %b) #0 {
|
||||
; CHECK-LABEL: test_eqdf2:
|
||||
; CHECK: call __eqdf2
|
||||
%cmp = fcmp oeq double %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_eqtf2(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABEL: test_eqtf2:
|
||||
; CHECK: call __eqtf2
|
||||
%cmp = fcmp oeq fp128 %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_nesf2(float %a, float %b) #0 {
|
||||
; CHECK-LABEL: test_nesf2:
|
||||
; CHECK: call __nesf2
|
||||
%cmp = fcmp une float %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_nedf2(double %a, double %b) #0 {
|
||||
; CHECK-LABEL: test_nedf2:
|
||||
; CHECK: call __nedf2
|
||||
%cmp = fcmp une double %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_netf2(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABEL: test_netf2:
|
||||
; CHECK: call __netf2
|
||||
%cmp = fcmp une fp128 %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_gesf2(float %a, float %b) #0 {
|
||||
; CHECK-LABLE: test_gesf2:
|
||||
; CHECK: call __gesf2
|
||||
%cmp = fcmp oge float %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_gedf2(double %a, double %b) #0 {
|
||||
; CHECK-LABLE: test_gedf2:
|
||||
; CHECK: call __gedf2
|
||||
%cmp = fcmp oge double %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_getf2(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABLE: test_getf2:
|
||||
; CHECK: call __getf2
|
||||
%cmp = fcmp oge fp128 %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_ltsf2(float %a, float %b) #0 {
|
||||
; CHECK-LABEL: test_ltsf2:
|
||||
; CHECK: call __ltsf2
|
||||
%cmp = fcmp olt float %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_ltdf2(double %a, double %b) #0 {
|
||||
; CHECK-LABEL: test_ltdf2:
|
||||
; CHECK: call __ltdf2
|
||||
%cmp = fcmp olt double %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_lttf2(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABEL: test_lttf2:
|
||||
; CHECK: call __lttf2
|
||||
%cmp = fcmp olt fp128 %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_lesf2(float %a, float %b) #0 {
|
||||
; CHECK-LABEL: test_lesf2:
|
||||
; CHECK: call __lesf2
|
||||
%cmp = fcmp ole float %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_ledf2(double %a, double %b) #0 {
|
||||
; CHECK-LABEL: test_ledf2:
|
||||
; CHECK: call __ledf2
|
||||
%cmp = fcmp ole double %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_letf2(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABEL: test_letf2:
|
||||
; CHECK: call __letf2
|
||||
%cmp = fcmp ole fp128 %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_gtsf2(float %a, float %b) #0 {
|
||||
; CHECK-LABEL: test_gtsf2:
|
||||
; CHECK: call __gtsf2
|
||||
%cmp = fcmp ogt float %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_gtdf2(double %a, double %b) #0 {
|
||||
; CHECK-LABEL: test_gtdf2:
|
||||
; CHECK: call __gtdf2
|
||||
%cmp = fcmp ogt double %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
||||
|
||||
define i1 @test_gttf2(fp128 %a, fp128 %b) #0 {
|
||||
; CHECK-LABEL: test_gttf2:
|
||||
; CHECK: call __gttf2
|
||||
%cmp = fcmp ogt fp128 %a, %b
|
||||
ret i1 %cmp
|
||||
}
|
Loading…
Reference in New Issue
Block a user