Skip to content

Commit

Permalink
[flang]Add new intrinsic function backtrace and complete the TODO of …
Browse files Browse the repository at this point in the history
…abort (llvm#117603)

Hey guys, I found that Flang's built-in ABORT function is incomplete
when I was using it. Compared with gfortran's ABORT (which can both
abort and print out a backtrace), flang's ABORT implementation lacks the
function of printing out a backtrace. This feature is essential for
debugging and understanding the call stack at the failure point.

To solve this problem, I completed the "// TODO:" of the abort function,
and then implemented an additional built-in function BACKTRACE for
flang. After a brief reading of the relevant source code, I used
backtrace and backtrace_symbols in "execinfo.h" to quickly implement
this. But since I used the above two functions directly, my
implementation is slightly different from gfortran's implementation (in
the output, the function call stack before main is additionally output,
and the function line number is missing). In addition, since I used the
above two functions, I did not need to add -g to embed debug information
into the ELF file, but needed -rdynamic to ensure that the symbols are
added to the dynamic symbol table (so that the function name will be
printed out).

Here is a comparison of the output between gfortran 's backtrace and my
implementation:
gfortran's implemention output:
```
#0  0x557eb71f4184 in testfun2_
        at /home/hunter/plct/fortran/test.f90:5
#1  0x557eb71f4165 in testfun1_
        at /home/hunter/plct/fortran/test.f90:13
#2  0x557eb71f4192 in test_backtrace
        at /home/hunter/plct/fortran/test.f90:17
#3  0x557eb71f41ce in main
        at /home/hunter/plct/fortran/test.f90:18
```
my impelmention output:
```
Backtrace:
#0 ./test(_FortranABacktrace+0x32) [0x574f07efcf92]
#1 ./test(testfun2_+0x14) [0x574f07efc7b4]
#2 ./test(testfun1_+0xd) [0x574f07efc7cd]
#3 ./test(_QQmain+0x9) [0x574f07efc7e9]
#4 ./test(main+0x12) [0x574f07efc802]
#5 /usr/lib/libc.so.6(+0x25e08) [0x76954694fe08]
llvm#6 /usr/lib/libc.so.6(__libc_start_main+0x8c) [0x76954694fecc]
llvm#7 ./test(_start+0x25) [0x574f07efc6c5]
```
test program is:
```
function testfun2() result(err)
  implicit none
  integer :: err
  err = 1
  call backtrace
end function testfun2

subroutine testfun1()
  implicit none
  integer :: err
  integer :: testfun2

  err = testfun2()
end subroutine testfun1

program test_backtrace
  call testfun1()
end program test_backtrace
```
I am well aware of the importance of line numbers, so I am now working
on implementing line numbers (by parsing DWARF information) and
supporting cross-platform (Windows) support.
  • Loading branch information
dty2 authored Nov 28, 2024
1 parent b869f1b commit 159e601
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 1 deletion.
1 change: 1 addition & 0 deletions flang/include/flang/Optimizer/Builder/IntrinsicCall.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ struct IntrinsicLibrary {
fir::ExtendedValue genAssociated(mlir::Type,
llvm::ArrayRef<fir::ExtendedValue>);
mlir::Value genAtand(mlir::Type, llvm::ArrayRef<mlir::Value>);
void genBacktrace(llvm::ArrayRef<fir::ExtendedValue>);
fir::ExtendedValue genBesselJn(mlir::Type,
llvm::ArrayRef<fir::ExtendedValue>);
fir::ExtendedValue genBesselYn(mlir::Type,
Expand Down
3 changes: 3 additions & 0 deletions flang/include/flang/Optimizer/Builder/Runtime/Stop.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ void genExit(fir::FirOpBuilder &, mlir::Location, mlir::Value status);
/// Generate call to ABORT intrinsic runtime routine.
void genAbort(fir::FirOpBuilder &, mlir::Location);

/// Generate call to BACKTRACE intrinsic runtime routine.
void genBacktrace(fir::FirOpBuilder &builder, mlir::Location loc);

/// Generate call to crash the program with an error message when detecting
/// an invalid situation at runtime.
void genReportFatalUserError(fir::FirOpBuilder &, mlir::Location,
Expand Down
1 change: 1 addition & 0 deletions flang/include/flang/Runtime/stop.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ NORETURN void RTNAME(ProgramEndStatement)(NO_ARGUMENTS);
// Extensions
NORETURN void RTNAME(Exit)(int status DEFAULT_VALUE(EXIT_SUCCESS));
NORETURN void RTNAME(Abort)(NO_ARGUMENTS);
void RTNAME(Backtrace)(NO_ARGUMENTS);

// Crash with an error message when the program dynamically violates a Fortran
// constraint.
Expand Down
1 change: 1 addition & 0 deletions flang/lib/Evaluate/intrinsics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,7 @@ static const IntrinsicInterface intrinsicSubroutine[]{
{"stat", AnyInt, Rank::scalar, Optionality::optional,
common::Intent::Out}},
{}, Rank::elemental, IntrinsicClass::atomicSubroutine},
{"backtrace", {}, {}, Rank::elemental, IntrinsicClass::pureSubroutine},
{"co_broadcast",
{{"a", AnyData, Rank::anyOrAssumedRank, Optionality::required,
common::Intent::InOut},
Expand Down
7 changes: 7 additions & 0 deletions flang/lib/Optimizer/Builder/IntrinsicCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ static constexpr IntrinsicHandler handlers[]{
{"atan2pi", &I::genAtanpi},
{"atand", &I::genAtand},
{"atanpi", &I::genAtanpi},
{"backtrace", &I::genBacktrace},
{"bessel_jn",
&I::genBesselJn,
{{{"n1", asValue}, {"n2", asValue}, {"x", asValue}}},
Expand Down Expand Up @@ -2682,6 +2683,12 @@ IntrinsicLibrary::genBesselJn(mlir::Type resultType,
}
}

// Backtrace
void IntrinsicLibrary::genBacktrace(llvm::ArrayRef<fir::ExtendedValue> args) {
assert(args.size() == 0);
fir::runtime::genBacktrace(builder, loc);
}

// BESSEL_YN
fir::ExtendedValue
IntrinsicLibrary::genBesselYn(mlir::Type resultType,
Expand Down
7 changes: 7 additions & 0 deletions flang/lib/Optimizer/Builder/Runtime/Stop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ void fir::runtime::genAbort(fir::FirOpBuilder &builder, mlir::Location loc) {
builder.create<fir::CallOp>(loc, abortFunc, std::nullopt);
}

void fir::runtime::genBacktrace(fir::FirOpBuilder &builder,
mlir::Location loc) {
mlir::func::FuncOp backtraceFunc =
fir::runtime::getRuntimeFunc<mkRTKey(Backtrace)>(loc, builder);
builder.create<fir::CallOp>(loc, backtraceFunc, std::nullopt);
}

void fir::runtime::genReportFatalUserError(fir::FirOpBuilder &builder,
mlir::Location loc,
llvm::StringRef message) {
Expand Down
32 changes: 31 additions & 1 deletion flang/runtime/stop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
#include <cstdio>
#include <cstdlib>

#include "llvm/Config/config.h"
#ifdef HAVE_BACKTRACE
#include BACKTRACE_HEADER
#endif

extern "C" {

static void DescribeIEEESignaledExceptions() {
Expand Down Expand Up @@ -152,11 +157,36 @@ void RTNAME(PauseStatementText)(const char *code, std::size_t length) {
std::exit(status);
}

static void PrintBacktrace() {
#ifdef HAVE_BACKTRACE
// TODO: Need to parse DWARF information to print function line numbers
constexpr int MAX_CALL_STACK{999};
void *buffer[MAX_CALL_STACK];
int nptrs{backtrace(buffer, MAX_CALL_STACK)};

if (char **symbols{backtrace_symbols(buffer, nptrs)}) {
for (int i = 0; i < nptrs; i++) {
Fortran::runtime::Terminator{}.PrintCrashArgs("#%d %s\n", i, symbols[i]);
}
free(symbols);
}

#else

// TODO: Need to implement the version for other platforms.
Fortran::runtime::Terminator{}.PrintCrashArgs(
"Handle the case when a backtrace is not available");

#endif
}

[[noreturn]] void RTNAME(Abort)() {
// TODO: Add backtrace call, unless with `-fno-backtrace`.
PrintBacktrace();
std::abort();
}

void RTNAME(Backtrace)() { PrintBacktrace(); }

[[noreturn]] void RTNAME(ReportFatalUserError)(
const char *message, const char *source, int line) {
Fortran::runtime::Terminator{source, line}.Crash(message);
Expand Down
10 changes: 10 additions & 0 deletions flang/test/Lower/Intrinsics/backtrace.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
! RUN: bbc -emit-fir %s -o - | FileCheck %s

! CHECK-LABEL: func.func @_QPbacktrace_test() {
! CHECK: %[[VAL_0:.*]] = fir.call @_FortranABacktrace() {{.*}}: () -> none
! CHECK: return
! CHECK: }

subroutine backtrace_test()
call backtrace
end subroutine

0 comments on commit 159e601

Please sign in to comment.