Currently, middle-end generates 'unreachable' insn if the compiler
feels the code is indeed unreachable or the code becomes invalid
due to some optimizaiton (e.g. code optimization with uninitialized
variables).
Right now BPF backend ignores 'unreachable' insn during selectiondag
lowering. For cases where 'unreachable' is due to invalid code
transformation, such a signal will be missed. Later on, users needs
some effort to debug it which impacts developer productivity.
This patch enabled selectiondag lowering for 'unreachable' insn.
Previous attempt ([1]) tries to have a backend IR pass to filter
out 'unreachable' insns in a number of cases. But such pattern
matching may misalign with future middle-end optimization with
'unreachable' insns.
This patch takes a different approach. The 'unreachable' insn is
lowered with special encoding in bpf object file and verifier
will do proper verification for the bpf prog. More specifically,
the 'unreachable' insn is replaced by a __bpf_trap() function.
This function will be a kfunc (in ".ksyms" section) with a weak
attribute, but does not have definition. The actual kfunc definition
is expected to be in kernel. The __bpf_trap() extern function
is also encoded in BTF. The name __bpf_trap() is chosen to satisfy
reserved identifier requirement.
Besides the uninitialized variable case, the builtin function
'__builtin_trap' can also generate kfunc __bpf_trap(). For example
in [3], we have
```
# define __bpf_unreachable() __builtin_trap()
```
If the compiler didn't remove __builtin_trap() during middle-end
optimization, compilation will fail.
With this patch, compilation will not fail and __builtin_trap()
is converted to __bpf_trap() kfunc. The eventual failure will be
in verifier instead of llvm compilation. To keep compilation
time failure, user can add an option like `-ftrap-function=<something>`.
I tested this patch on bpf selftests and all tests are passed.
I also tried original example in [2] and the code looks like below:
```
; {
0: bf 16 00 00 00 00 00 00 r6 = r1
; bpf_printk("Start");
1: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll
0000000000000008: R_BPF_64_64 .rodata
3: b4 02 00 00 06 00 00 00 w2 = 0x6
4: 85 00 00 00 06 00 00 00 call 0x6
; DEFINE_FUNC_CTX_POINTER(data)
5: 61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c)
; bpf_printk("pre ipv6_hdrlen_offset");
6: 18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll
0000000000000030: R_BPF_64_64 .rodata
8: b4 02 00 00 17 00 00 00 w2 = 0x17
9: 85 00 00 00 06 00 00 00 call 0x6
10: 85 10 00 00 ff ff ff ff call -0x1
0000000000000050: R_BPF_64_32 __bpf_trap
11: 95 00 00 00 00 00 00 00 exit
<END>
```
Eventually kernel verifier will emit the following logs:
```
10: (85) call __bpf_trap#74479
unexpected __bpf_trap() due to uninitialized variable?
```
In another internal sched-ext bpf prog, with the patch we have bpf code:
```
Disassembly of section .text:
0000000000000000 <scx_storage_init_single>:
; {
0: bc 13 00 00 00 00 00 00 w3 = w1
1: b4 01 00 00 00 00 00 00 w1 = 0x0
; const u32 zero = 0;
...
0000000000003a80 <create_dom>:
; {
1872: bc 16 00 00 00 00 00 00 w6 = w1
; bpf_printk("dom_id %d", dom_id);
1873: 18 01 00 00 3f 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x3f ll
0000000000003a88: R_BPF_64_64 .rodata
1875: b4 02 00 00 0a 00 00 00 w2 = 0xa
1876: bc 63 00 00 00 00 00 00 w3 = w6
1877: 85 00 00 00 06 00 00 00 call 0x6
; ret = scx_bpf_create_dsq(dom_id, 0);
1878: bc 61 00 00 00 00 00 00 w1 = w6
1879: b4 02 00 00 00 00 00 00 w2 = 0x0
1880: 85 10 00 00 ff ff ff ff call -0x1
0000000000003ac0: R_BPF_64_32 scx_bpf_create_dsq
; domc->node_cpumask = node_data[node_id];
1881: 85 10 00 00 ff ff ff ff call -0x1
0000000000003ac8: R_BPF_64_32 __bpf_trap
1882: 95 00 00 00 00 00 00 00 exit
<END>
```
The verifier can easily report the error too.
A bpf flag `-bpf-disable-trap-unreachable` is introduced to disable
trapping for 'unreachable' or __builtin_trap.
[1] https://github.com/llvm/llvm-project/pull/126858
[2] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
[3] https://github.com/libbpf/libbpf/blob/master/src/bpf_helpers.h
218 lines
7.4 KiB
C++
218 lines
7.4 KiB
C++
//===-- BPFTargetMachine.cpp - Define TargetMachine for BPF ---------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Implements the info about BPF target spec.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "BPFTargetMachine.h"
|
|
#include "BPF.h"
|
|
#include "BPFTargetTransformInfo.h"
|
|
#include "MCTargetDesc/BPFMCAsmInfo.h"
|
|
#include "TargetInfo/BPFTargetInfo.h"
|
|
#include "llvm/CodeGen/GlobalISel/IRTranslator.h"
|
|
#include "llvm/CodeGen/GlobalISel/InstructionSelect.h"
|
|
#include "llvm/CodeGen/GlobalISel/Legalizer.h"
|
|
#include "llvm/CodeGen/GlobalISel/RegBankSelect.h"
|
|
#include "llvm/CodeGen/Passes.h"
|
|
#include "llvm/CodeGen/TargetLoweringObjectFileImpl.h"
|
|
#include "llvm/CodeGen/TargetPassConfig.h"
|
|
#include "llvm/IR/PassManager.h"
|
|
#include "llvm/InitializePasses.h"
|
|
#include "llvm/MC/TargetRegistry.h"
|
|
#include "llvm/Passes/PassBuilder.h"
|
|
#include "llvm/Target/TargetOptions.h"
|
|
#include "llvm/Transforms/Scalar.h"
|
|
#include "llvm/Transforms/Scalar/SimplifyCFG.h"
|
|
#include "llvm/Transforms/Utils/SimplifyCFGOptions.h"
|
|
#include <optional>
|
|
using namespace llvm;
|
|
|
|
static cl::
|
|
opt<bool> DisableMIPeephole("disable-bpf-peephole", cl::Hidden,
|
|
cl::desc("Disable machine peepholes for BPF"));
|
|
|
|
static cl::opt<bool>
|
|
DisableCheckUnreachable("bpf-disable-trap-unreachable", cl::Hidden,
|
|
cl::desc("Disable Trap Unreachable for BPF"));
|
|
|
|
extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeBPFTarget() {
|
|
// Register the target.
|
|
RegisterTargetMachine<BPFTargetMachine> X(getTheBPFleTarget());
|
|
RegisterTargetMachine<BPFTargetMachine> Y(getTheBPFbeTarget());
|
|
RegisterTargetMachine<BPFTargetMachine> Z(getTheBPFTarget());
|
|
|
|
PassRegistry &PR = *PassRegistry::getPassRegistry();
|
|
initializeGlobalISel(PR);
|
|
initializeBPFAsmPrinterPass(PR);
|
|
initializeBPFCheckAndAdjustIRPass(PR);
|
|
initializeBPFMIPeepholePass(PR);
|
|
initializeBPFMIPreEmitPeepholePass(PR);
|
|
initializeBPFDAGToDAGISelLegacyPass(PR);
|
|
initializeBPFMISimplifyPatchablePass(PR);
|
|
initializeBPFMIPreEmitCheckingPass(PR);
|
|
}
|
|
|
|
// DataLayout: little or big endian
|
|
static std::string computeDataLayout(const Triple &TT) {
|
|
if (TT.getArch() == Triple::bpfeb)
|
|
return "E-m:e-p:64:64-i64:64-i128:128-n32:64-S128";
|
|
else
|
|
return "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128";
|
|
}
|
|
|
|
static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
|
|
return RM.value_or(Reloc::PIC_);
|
|
}
|
|
|
|
BPFTargetMachine::BPFTargetMachine(const Target &T, const Triple &TT,
|
|
StringRef CPU, StringRef FS,
|
|
const TargetOptions &Options,
|
|
std::optional<Reloc::Model> RM,
|
|
std::optional<CodeModel::Model> CM,
|
|
CodeGenOptLevel OL, bool JIT)
|
|
: CodeGenTargetMachineImpl(T, computeDataLayout(TT), TT, CPU, FS, Options,
|
|
getEffectiveRelocModel(RM),
|
|
getEffectiveCodeModel(CM, CodeModel::Small), OL),
|
|
TLOF(std::make_unique<TargetLoweringObjectFileELF>()),
|
|
Subtarget(TT, std::string(CPU), std::string(FS), *this) {
|
|
if (!DisableCheckUnreachable) {
|
|
this->Options.TrapUnreachable = true;
|
|
this->Options.NoTrapAfterNoreturn = true;
|
|
}
|
|
|
|
initAsmInfo();
|
|
|
|
BPFMCAsmInfo *MAI =
|
|
static_cast<BPFMCAsmInfo *>(const_cast<MCAsmInfo *>(AsmInfo.get()));
|
|
MAI->setDwarfUsesRelocationsAcrossSections(!Subtarget.getUseDwarfRIS());
|
|
}
|
|
|
|
namespace {
|
|
// BPF Code Generator Pass Configuration Options.
|
|
class BPFPassConfig : public TargetPassConfig {
|
|
public:
|
|
BPFPassConfig(BPFTargetMachine &TM, PassManagerBase &PM)
|
|
: TargetPassConfig(TM, PM) {}
|
|
|
|
BPFTargetMachine &getBPFTargetMachine() const {
|
|
return getTM<BPFTargetMachine>();
|
|
}
|
|
|
|
void addIRPasses() override;
|
|
bool addInstSelector() override;
|
|
void addMachineSSAOptimization() override;
|
|
void addPreEmitPass() override;
|
|
|
|
bool addIRTranslator() override;
|
|
bool addLegalizeMachineIR() override;
|
|
bool addRegBankSelect() override;
|
|
bool addGlobalInstructionSelect() override;
|
|
};
|
|
}
|
|
|
|
TargetPassConfig *BPFTargetMachine::createPassConfig(PassManagerBase &PM) {
|
|
return new BPFPassConfig(*this, PM);
|
|
}
|
|
|
|
static Expected<bool> parseBPFPreserveStaticOffsetOptions(StringRef Params) {
|
|
return PassBuilder::parseSinglePassOption(Params, "allow-partial",
|
|
"BPFPreserveStaticOffsetPass");
|
|
}
|
|
|
|
void BPFTargetMachine::registerPassBuilderCallbacks(PassBuilder &PB) {
|
|
#define GET_PASS_REGISTRY "BPFPassRegistry.def"
|
|
#include "llvm/Passes/TargetPassRegistry.inc"
|
|
|
|
PB.registerPipelineStartEPCallback(
|
|
[=](ModulePassManager &MPM, OptimizationLevel) {
|
|
FunctionPassManager FPM;
|
|
FPM.addPass(BPFPreserveStaticOffsetPass(true));
|
|
FPM.addPass(BPFAbstractMemberAccessPass(this));
|
|
FPM.addPass(BPFPreserveDITypePass());
|
|
FPM.addPass(BPFIRPeepholePass());
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
});
|
|
PB.registerPeepholeEPCallback([=](FunctionPassManager &FPM,
|
|
OptimizationLevel Level) {
|
|
FPM.addPass(SimplifyCFGPass(SimplifyCFGOptions().hoistCommonInsts(true)));
|
|
FPM.addPass(BPFASpaceCastSimplifyPass());
|
|
});
|
|
PB.registerScalarOptimizerLateEPCallback(
|
|
[=](FunctionPassManager &FPM, OptimizationLevel Level) {
|
|
// Run this after loop unrolling but before
|
|
// SimplifyCFGPass(... .sinkCommonInsts(true))
|
|
FPM.addPass(BPFPreserveStaticOffsetPass(false));
|
|
});
|
|
PB.registerPipelineEarlySimplificationEPCallback(
|
|
[=](ModulePassManager &MPM, OptimizationLevel, ThinOrFullLTOPhase) {
|
|
MPM.addPass(BPFAdjustOptPass());
|
|
});
|
|
}
|
|
|
|
void BPFPassConfig::addIRPasses() {
|
|
addPass(createAtomicExpandLegacyPass());
|
|
addPass(createBPFCheckAndAdjustIR());
|
|
|
|
TargetPassConfig::addIRPasses();
|
|
}
|
|
|
|
TargetTransformInfo
|
|
BPFTargetMachine::getTargetTransformInfo(const Function &F) const {
|
|
return TargetTransformInfo(std::make_unique<BPFTTIImpl>(this, F));
|
|
}
|
|
|
|
// Install an instruction selector pass using
|
|
// the ISelDag to gen BPF code.
|
|
bool BPFPassConfig::addInstSelector() {
|
|
addPass(createBPFISelDag(getBPFTargetMachine()));
|
|
|
|
return false;
|
|
}
|
|
|
|
void BPFPassConfig::addMachineSSAOptimization() {
|
|
addPass(createBPFMISimplifyPatchablePass());
|
|
|
|
// The default implementation must be called first as we want eBPF
|
|
// Peephole ran at last.
|
|
TargetPassConfig::addMachineSSAOptimization();
|
|
|
|
const BPFSubtarget *Subtarget = getBPFTargetMachine().getSubtargetImpl();
|
|
if (!DisableMIPeephole) {
|
|
if (Subtarget->getHasAlu32())
|
|
addPass(createBPFMIPeepholePass());
|
|
}
|
|
}
|
|
|
|
void BPFPassConfig::addPreEmitPass() {
|
|
addPass(createBPFMIPreEmitCheckingPass());
|
|
if (getOptLevel() != CodeGenOptLevel::None)
|
|
if (!DisableMIPeephole)
|
|
addPass(createBPFMIPreEmitPeepholePass());
|
|
}
|
|
|
|
bool BPFPassConfig::addIRTranslator() {
|
|
addPass(new IRTranslator());
|
|
return false;
|
|
}
|
|
|
|
bool BPFPassConfig::addLegalizeMachineIR() {
|
|
addPass(new Legalizer());
|
|
return false;
|
|
}
|
|
|
|
bool BPFPassConfig::addRegBankSelect() {
|
|
addPass(new RegBankSelect());
|
|
return false;
|
|
}
|
|
|
|
bool BPFPassConfig::addGlobalInstructionSelect() {
|
|
addPass(new InstructionSelect(getOptLevel()));
|
|
return false;
|
|
}
|