Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 13 additions & 20 deletions doc/src/devdocs/gc-sa.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@
## Running the analysis

The analyzer plugin that drives the analysis ships with julia. Its
source code can be found in `src/clangsa`. Running it requires
the clang dependency to be built. Set the `BUILD_LLVM_CLANG` variable
in your Make.user in order to build an appropriate version of clang.
You may also want to use the prebuilt binaries using the
`USE_BINARYBUILDER_LLVM` options.

Alternatively (or if these do not suffice), try
source code can be found in `src/clangsa`. Running it uses the
the optional clang dependency from `deps`. This can be installed by running:

```sh
make -C src install-analysis-deps
Expand All @@ -18,7 +13,8 @@ make -C src install-analysis-deps
from Julia's toplevel directory.


Afterwards, running the analysis over the source tree is as simple as running `make -C src analyzegc`.
Afterwards, running the analysis over the source tree is as simple as running
`make -C src analyzegc`.

## General Overview

Expand Down Expand Up @@ -104,24 +100,21 @@ These annotations are found in src/support/analyzer_annotations.h.
They are only active when the analyzer is being used and expand either
to nothing (for prototype annotations) or to no-ops (for function like annotations).

### `JL_NOTSAFEPOINT`
### `JL_CANSAFEPOINT`

This is perhaps the most common annotation, and should be placed on any function
that is known not to possibly lead to reaching a GC safepoint. In general, it is
only safe for such a function to perform arithmetic, memory accesses and calls to
functions either annotated `JL_NOTSAFEPOINT` or otherwise known not to be safepoints (e.g.
function in the C standard library, which are hardcoded as such in the analyzer)
that needs to interact with a GC safepoint. It has a few variants also, to express
when the function expects to be called in an gc-safe region for example.

It is valid to keep values unrooted across calls to any function annotated with this
attribute:
It is valid to keep values unrooted across calls to any unannotated function:

Usage Example:
```c
void jl_get_one() JL_NOTSAFEPOINT {
void jl_get_one() {
return 1;
}

jl_value_t *example() {
jl_value_t *example() JL_CANSAFEPOINT {
jl_value_t *val = jl_alloc_whatever();
// This is valid, even though `val` is unrooted, because
// jl_get_one is not a safepoint
Expand All @@ -143,9 +136,9 @@ The `ROOTS_TEMPORARILY` annotation provides the stronger guarantee that,
not only may the value be unrooted when passed, it will also be preserved
across any internal safepoints by the callee.

Note that `JL_NOTSAFEPOINT` essentially implies `JL_MAYBE_UNROOTED`/`JL_ROOTS_TEMPORARILY`,
because the rootedness of an argument is irrelevant if the function contains
no safepoints.
Note that not annotating with `JL_CANSAFEPOINT` essentially implies
`JL_MAYBE_UNROOTED`/`JL_ROOTS_TEMPORARILY`, because the rootedness of an
argument is irrelevant if the function contains no safepoints.

One additional point to note is that these annotations apply on both the
caller and the callee side. On the caller side, they lift rootedness
Expand Down
31 changes: 28 additions & 3 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -632,13 +632,18 @@ $(CODEGEN_OBJS) $(CODEGEN_DOBJS): FLAGS_COMMON += -DJL_LIBRARY_EXPORTS_CODEGEN -
# set the exports for the source files based on where they are getting linked
$(addprefix clang-sa-,$(SRCS)) \
$(addprefix clang-sagc-,$(SRCS)) \
$(addprefix clang-safety-,$(SRCS)) \
$(addprefix clang-tidy-,$(SRCS)): \
FLAGS_COMMON += -DJL_LIBRARY_EXPORTS_INTERNAL
$(addprefix clang-sa-,$(CODEGEN_SRCS)) \
$(addprefix clang-sagc-,$(CODEGEN_SRCS)) \
$(addprefix clang-safety-,$(CODEGEN_SRCS)) \
$(addprefix clang-tidy-,$(CODEGEN_SRCS)): \
FLAGS_COMMON += -DJL_LIBRARY_EXPORTS_CODEGEN

$(addprefix clang-safety-,$(SRCS) $(CODEGEN_SRCS)): \
FLAGS_COMMON += -D__clang_analyzer__

# Common flag patterns for all clang tooling (clang-sa, clang-tidy, compile-database)
CLANG_TOOLING_C_FLAGS = $(CLANGSA_FLAGS) $(LLVM_CFLAGS) $(DEBUGFLAGS_CLANG) $(JCPPFLAGS_CLANG) $(JCFLAGS_CLANG)
CLANG_TOOLING_CXX_FLAGS = $(CLANGSA_FLAGS) $(CLANGSA_CXXFLAGS) $(DEBUGFLAGS_CLANG) $(LLVM_CXXFLAGS) $(JCPPFLAGS_CLANG) $(JCXXFLAGS_CLANG)
Expand Down Expand Up @@ -755,7 +760,7 @@ SA_EXCEPTIONS-gc-stock.c := -Xanalyzer -analyzer-config -Xana
SKIP_GC_CHECK := codegen.cpp rtutils.c

# make sure LLVM's invariant information is not discarded with -DNDEBUG
clang-sagc-%: JCXXFLAGS_COMMON += -UNDEBUG
clang-sagc-%: JCPPFLAGS_COMMON += -UNDEBUG
clang-sagc-%: $(SRCDIR)/%.c $(GCCHECKER_PLUGIN) .FORCE | analyzegc-deps-check
@$(call PRINT_ANALYZE, $(build_depsbindir)/clang -D__clang_gcanalyzer__ --analyze -Xanalyzer -analyzer-werror -Xanalyzer -analyzer-output=text --analyzer-no-default-checks \
-Xclang -load -Xclang $(GCCHECKER_PLUGIN) -Xclang -analyzer-checker=core$(COMMA)julia.GCChecker \
Expand All @@ -767,7 +772,7 @@ clang-sagc-%: $(SRCDIR)/%.cpp $(GCCHECKER_PLUGIN) .FORCE | analyzegc-deps-check
$(SA_EXCEPTIONS-$(notdir $<)) \
$(CLANG_TOOLING_CXX_FLAGS) -fcolor-diagnostics -x c++ $<)

clang-sa-%: JCXXFLAGS_COMMON += -UNDEBUG
clang-sa-%: JCPPFLAGS_COMMON += -UNDEBUG
clang-sa-%: $(SRCDIR)/%.c .FORCE | analyzegc-deps-check
@$(call PRINT_ANALYZE, $(build_depsbindir)/clang --analyze -Xanalyzer -analyzer-werror -Xanalyzer -analyzer-output=text \
-Xanalyzer -analyzer-disable-checker=deadcode.DeadStores \
Expand All @@ -779,6 +784,24 @@ clang-sa-%: $(SRCDIR)/%.cpp .FORCE | analyzegc-deps-check
$(SA_EXCEPTIONS-$(notdir $<)) \
$(CLANG_TOOLING_CXX_FLAGS) -fcolor-diagnostics -Werror -x c++ $<)

# Clang Thread Safety Analysis of Julia's safepoint annotations. Compiling with
# -D__clang_safetyanalysis__ makes JL_CANSAFEPOINT and the ENTER/LEAVE annotations expand to
# capability attributes on the reentrant `jl_notsafepoint` token (see
# src/support/analyzer_annotations.h), which -Wthread-safety then checks.
#
# -Wno-ignored-attributes and -Wno-thread-safety-attributes: JL_CANSAFEPOINT (and friends) expand to a
# `requires_capability` attribute, which clang-21's thread-safety analysis only
# accepts on functions and parameters -- not on a function-pointer *typedef*.
# This should be revisisted after clang-23, which added some support for this.
clang-safety-%: JCPPFLAGS_COMMON += -UNDEBUG -D__clang_safetyanalysis__ -fsyntax-only -fcolor-diagnostics \
-Wthread-safety -Wthread-safety-negative -Werror=thread-safety \
-Wno-ignored-attributes -Wno-thread-safety-attributes
clang-safety-%: $(SRCDIR)/%.c .FORCE | analyzegc-deps-check
@$(call PRINT_ANALYZE, $(build_depsbindir)/clang $(CLANG_TOOLING_C_FLAGS) -x c $<)
clang-safety-%: $(SRCDIR)/%.cpp .FORCE | analyzegc-deps-check
@$(call PRINT_ANALYZE, $(build_depsbindir)/clang \
$(CLANG_TOOLING_CXX_FLAGS) --system-header-prefix=llvm -x c++ $<)

# Run clang-tidy with our plugins: `-header-filter='.*'` so the first
# (header) declarations participate in the comparison. The annotation macros
# only expand to an `annotate` attribute under `-D__clang_gcanalyzer__` (see
Expand Down Expand Up @@ -807,12 +830,14 @@ clang-tidy-%: $(SRCDIR)/%.cpp $(TIDY_PLUGIN_LIBS) .FORCE | analyzegc-deps-check
# Add C files as a target of `analyzesrc` and `analyzegc` and `tidysrc`
.PHONY: tidysrc
tidysrc: $(addprefix clang-tidy-,$(CODEGEN_SRCS) $(SRCS))
.PHONY: safesrc
safesrc: $(addprefix clang-safety-,$(CODEGEN_SRCS) $(SRCS))
.PHONY: analyzesrc
analyzesrc: $(addprefix clang-sa-,$(CODEGEN_SRCS) $(SRCS))
.PHONY: analyzegc
analyzegc: $(addprefix clang-sagc-,$(filter-out $(basename $(SKIP_GC_CHECK)),$(CODEGEN_SRCS) $(SRCS)))
.PHONY: analyze
analyze: analyzesrc analyzegc tidysrc
analyze: analyzesrc analyzegc tidysrc safesrc

$(addprefix analyze-,$(CODEGEN_SRCS) $(SRCS)) : analyze-% : clang-sa-%
$(addprefix analyze-,$(CODEGEN_SRCS) $(SRCS)) : analyze-% : clang-tidy-%
Expand Down
18 changes: 9 additions & 9 deletions src/abi_aarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

struct ABI_AArch64Layout : AbiLayout {

Type *get_llvm_vectype(jl_datatype_t *dt, LLVMContext &ctx) const
Type *get_llvm_vectype(jl_datatype_t *dt, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Assume jl_is_datatype(dt) && !jl_is_abstracttype(dt)
// `!dt->name->mutabl && dt->pointerfree && !dt->haspadding && dt->isbitsegal && dt->nfields > 0`
Expand Down Expand Up @@ -59,7 +59,7 @@ Type *get_llvm_vectype(jl_datatype_t *dt, LLVMContext &ctx) const
}

#define jl_is_floattype(v) jl_subtype(v,(jl_value_t*)jl_floatingpoint_type)
Type *get_llvm_fptype(jl_datatype_t *dt, LLVMContext &ctx) const
Type *get_llvm_fptype(jl_datatype_t *dt, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Assume jl_is_datatype(dt) && !jl_is_abstracttype(dt)
// `!dt->name->mutabl && dt->pointerfree && !dt->haspadding && dt->isbitsegal && dt->nfields == 0`
Expand All @@ -85,7 +85,7 @@ Type *get_llvm_fptype(jl_datatype_t *dt, LLVMContext &ctx) const
lltype : nullptr);
}

Type *get_llvm_fp_or_vectype(jl_datatype_t *dt, LLVMContext &ctx) const
Type *get_llvm_fp_or_vectype(jl_datatype_t *dt, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Assume jl_is_datatype(dt) && !jl_is_abstracttype(dt)
if (dt->name->mutabl || dt->layout->npointers || !dt->layout->flags.isbitsegal || dt->layout->flags.haspadding)
Expand All @@ -105,7 +105,7 @@ struct ElementType {
// Data Types of the members that compose the type are the same.
// Note that it is the fundamental types that are important and not the member
// types.
bool isHFAorHVA(jl_datatype_t *dt, size_t dsz, size_t &nele, ElementType &ele, LLVMContext &ctx) const
bool isHFAorHVA(jl_datatype_t *dt, size_t dsz, size_t &nele, ElementType &ele, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Assume:
// dt is a pointerfree type, (all members are isbits)
Expand Down Expand Up @@ -172,7 +172,7 @@ bool isHFAorHVA(jl_datatype_t *dt, size_t dsz, size_t &nele, ElementType &ele, L
return false;
}

Type *isHFAorHVA(jl_datatype_t *dt, size_t &nele, LLVMContext &ctx) const
Type *isHFAorHVA(jl_datatype_t *dt, size_t &nele, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Assume jl_is_datatype(dt) && !jl_is_abstracttype(dt)

Expand All @@ -193,7 +193,7 @@ Type *isHFAorHVA(jl_datatype_t *dt, size_t &nele, LLVMContext &ctx) const
return NULL;
}

bool needPassByRef(jl_datatype_t *dt, AttrBuilder &ab, LLVMContext &ctx, Type *Ty) override
bool needPassByRef(jl_datatype_t *dt, AttrBuilder &ab, LLVMContext &ctx, Type *Ty) override JL_CANSAFEPOINT
{
// B.2
// If the argument type is an HFA or an HVA, then the argument is used
Expand Down Expand Up @@ -226,7 +226,7 @@ bool needPassByRef(jl_datatype_t *dt, AttrBuilder &ab, LLVMContext &ctx, Type *T
//
// All the out parameters should be default to `false`.
Type *classify_arg(jl_datatype_t *dt, bool *fpreg, bool *onstack,
size_t *rewrite_len, LLVMContext &ctx) const
size_t *rewrite_len, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Based on section 5.4 C of the Procedure Call Standard
// C.1
Expand Down Expand Up @@ -350,7 +350,7 @@ Type *classify_arg(jl_datatype_t *dt, bool *fpreg, bool *onstack,
// <handled by C.10 above>
}

bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override
bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override JL_CANSAFEPOINT
{
// Section 5.5
// If the type, T, of the result of a function is such that
Expand All @@ -368,7 +368,7 @@ bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override
return onstack;
}

Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const override
Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const override JL_CANSAFEPOINT
{
if (Type *fptype = get_llvm_fp_or_vectype(dt, ctx))
return fptype;
Expand Down
16 changes: 8 additions & 8 deletions src/abi_arm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ bool needPassByRef(jl_datatype_t *dt, AttrBuilder &abi, LLVMContext &ctx, Type *

#define jl_is_floattype(v) jl_subtype(v,(jl_value_t*)jl_floatingpoint_type)

Type *get_llvm_fptype(jl_datatype_t *dt, LLVMContext &ctx) const
Type *get_llvm_fptype(jl_datatype_t *dt, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Assume jl_is_datatype(dt) && !jl_is_abstracttype(dt)
if (dt->name->mutabl || jl_datatype_nfields(dt) != 0)
Expand Down Expand Up @@ -58,7 +58,7 @@ Type *get_llvm_fptype(jl_datatype_t *dt, LLVMContext &ctx) const
// fundamental type.
//
// Returns the corresponding LLVM type.
Type *isLegalHAType(jl_datatype_t *dt, LLVMContext &ctx) const
Type *isLegalHAType(jl_datatype_t *dt, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// single- or double-precision floating-point type
if (Type *fp = get_llvm_fptype(dt, ctx))
Expand All @@ -74,7 +74,7 @@ Type *isLegalHAType(jl_datatype_t *dt, LLVMContext &ctx) const
//
// Legality of the HA is determined by a nonzero return value.
// In case of a non-legal HA, the value of 'base' is undefined.
size_t isLegalHA(jl_datatype_t *dt, Type *&base, LLVMContext &ctx) const
size_t isLegalHA(jl_datatype_t *dt, Type *&base, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Homogeneous aggregates are only used for VFP registers,
// so use that definition of legality (section 6.1.2.1)
Expand Down Expand Up @@ -122,7 +122,7 @@ size_t isLegalHA(jl_datatype_t *dt, Type *&base, LLVMContext &ctx) const
// Determine if an argument can be passed through a coprocessor register.
//
// All the out parameters should be default to `false`.
void classify_cprc(jl_datatype_t *dt, bool *vfp, LLVMContext &ctx) const
void classify_cprc(jl_datatype_t *dt, bool *vfp, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Based on section 6.1 of the Procedure Call Standard

Expand All @@ -146,7 +146,7 @@ void classify_cprc(jl_datatype_t *dt, bool *vfp, LLVMContext &ctx) const
}

void classify_return_arg(jl_datatype_t *dt, bool *reg, bool *onstack,
bool *need_rewrite, LLVMContext &ctx) const
bool *need_rewrite, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Based on section 5.4 of the Procedure Call Standard

Expand Down Expand Up @@ -198,7 +198,7 @@ void classify_return_arg(jl_datatype_t *dt, bool *reg, bool *onstack,
*onstack = true;
}

bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override
bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override JL_CANSAFEPOINT
{
bool reg = false;
bool onstack = false;
Expand All @@ -220,7 +220,7 @@ bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override
//
// All the out parameters should be default to `false`.
void classify_arg(jl_datatype_t *dt, bool *reg,
bool *onstack, bool *need_rewrite, LLVMContext &ctx) const
bool *onstack, bool *need_rewrite, LLVMContext &ctx) const JL_CANSAFEPOINT
{
// Based on section 5.5 of the Procedure Call Standard

Expand All @@ -241,7 +241,7 @@ void classify_arg(jl_datatype_t *dt, bool *reg,
*need_rewrite = true;
}

Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const override
Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const override JL_CANSAFEPOINT
{
if (Type *fptype = get_llvm_fptype(dt, ctx))
return fptype;
Expand Down
8 changes: 4 additions & 4 deletions src/abi_ppc64le.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
struct ABI_PPC64leLayout : AbiLayout {

// count the homogeneous floating aggregate size (saturating at max count of 8)
unsigned isHFA(jl_datatype_t *ty, jl_datatype_t **ty0, bool *hva) const
unsigned isHFA(jl_datatype_t *ty, jl_datatype_t **ty0, bool *hva) const JL_CANSAFEPOINT
{
if (jl_datatype_size(ty) > 128 || ty->layout->npointers || !ty->layout->flags.isbitsegal || ty->layout->flags.haspadding)
return 9;
Expand Down Expand Up @@ -95,7 +95,7 @@ unsigned isHFA(jl_datatype_t *ty, jl_datatype_t **ty0, bool *hva) const
return n;
}

bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override
bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override JL_CANSAFEPOINT
{
jl_datatype_t *ty0 = NULL;
bool hva = false;
Expand All @@ -104,7 +104,7 @@ bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override
return false;
}

bool needPassByRef(jl_datatype_t *dt, AttrBuilder &ab, LLVMContext &ctx, Type *Ty) override
bool needPassByRef(jl_datatype_t *dt, AttrBuilder &ab, LLVMContext &ctx, Type *Ty) override JL_CANSAFEPOINT
{
jl_datatype_t *ty0 = NULL;
bool hva = false;
Expand All @@ -115,7 +115,7 @@ bool needPassByRef(jl_datatype_t *dt, AttrBuilder &ab, LLVMContext &ctx, Type *T
return false;
}

Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const override
Type *preferred_llvm_type(jl_datatype_t *dt, bool isret, LLVMContext &ctx) const override JL_CANSAFEPOINT
{
// Arguments are either scalar or passed by value

Expand Down
8 changes: 4 additions & 4 deletions src/abi_riscv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Type *get_llvm_inttype(jl_datatype_t *dt, LLVMContext &ctx) const
return Type::getIntNTy(ctx, nb * 8);
}

bool should_use_fp_conv(jl_datatype_t *dt, ElementType &ele1, ElementType &ele2) const
bool should_use_fp_conv(jl_datatype_t *dt, ElementType &ele1, ElementType &ele2) const JL_CANSAFEPOINT
{
if (jl_is_primitivetype(dt)) {
size_t dsz = jl_datatype_size(dt);
Expand Down Expand Up @@ -179,7 +179,7 @@ Type *get_llvm_inttype_byxlen(size_t xlen, LLVMContext &ctx) const
}

Type *classify_arg(jl_datatype_t *ty, int &avail_gprs, int &avail_fprs, bool &onstack,
LLVMContext &ctx) const
LLVMContext &ctx) const JL_CANSAFEPOINT
{
onstack = false;
if (ty == jl_nothing_type) {
Expand Down Expand Up @@ -282,7 +282,7 @@ Type *classify_arg(jl_datatype_t *ty, int &avail_gprs, int &avail_fprs, bool &on
return get_llvm_inttype(ty, ctx);
}

bool use_sret(jl_datatype_t *ty, LLVMContext &ctx) override
bool use_sret(jl_datatype_t *ty, LLVMContext &ctx) override JL_CANSAFEPOINT
{
bool onstack = false;
int gprs = 2;
Expand All @@ -298,7 +298,7 @@ bool use_sret(jl_datatype_t *ty, LLVMContext &ctx) override
}

bool needPassByRef(jl_datatype_t *ty, AttrBuilder &ab, LLVMContext &ctx,
Type *Ty) override
Type *Ty) override JL_CANSAFEPOINT
{
bool onstack = false;
this->cached_llvmtype =
Expand Down
2 changes: 1 addition & 1 deletion src/abi_win64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct ABI_Win64Layout : AbiLayout {
int nargs;
ABI_Win64Layout() : nargs(0) { }

bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override
bool use_sret(jl_datatype_t *dt, LLVMContext &ctx) override JL_CANSAFEPOINT
{
size_t size = jl_datatype_size(dt);
if (win64_reg_size(size) || is_native_simd_type(dt))
Expand Down
Loading