Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Expr(:funcinfo, ... and unsafe verifier #43747

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Base, is_primary_base_module)
# until after array.jl, and so we will mark them within a function body instead.
macro inline() Expr(:meta, :inline) end
macro noinline() Expr(:meta, :noinline) end
macro unsafe() Expr(:funcinfo, (Symbol("julia.unsafe"), true)) end

# Try to help prevent users from shooting them-selves in the foot
# with ambiguities by defining a few common and critical operations
Expand Down
1 change: 1 addition & 0 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ end

macro inline() Expr(:meta, :inline) end
macro noinline() Expr(:meta, :noinline) end
macro unsafe() Expr(:funcinfo, (Symbol("julia.unsafe"), true)) end

struct BoundsError <: Exception
a::Any
Expand Down
1 change: 1 addition & 0 deletions base/compiler/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ include(mod, x) = Core.include(mod, x)
# until after array.jl, and so we will mark them within a function body instead.
macro inline() Expr(:meta, :inline) end
macro noinline() Expr(:meta, :noinline) end
macro unsafe() Expr(:funcinfo, (Symbol("julia.unsafe"), true)) end

# essential files and libraries
include("essentials.jl")
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ function stmt_affects_purity(@nospecialize(stmt), ir)
return !(t ⊑ Bool)
end
if isa(stmt, Expr)
return stmt.head !== :loopinfo && stmt.head !== :enter
return stmt.head !== :loopinfo && stmt.head !== :enter && stmt.head !== :funcinfo
end
return true
end
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ end

# Meta expression head, these generally can't be deleted even when they are
# in a dead branch but can be ignored when analyzing uses/liveness.
is_meta_expr_head(head::Symbol) = head === :boundscheck || head === :meta || head === :loopinfo
is_meta_expr_head(head::Symbol) = head === :boundscheck || head === :meta || head === :loopinfo || head === :funcinfo

sym_isless(a::Symbol, b::Symbol) = ccall(:strcmp, Int32, (Ptr{UInt8}, Ptr{UInt8}), a, b) < 0

Expand Down
3 changes: 2 additions & 1 deletion base/compiler/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}(
:isdefined => 1:1,
:code_coverage_effect => 0:0,
:loopinfo => 0:typemax(Int),
:funcinfo => 0:typemax(Int),
:gc_preserve_begin => 0:typemax(Int),
:gc_preserve_end => 0:typemax(Int),
:thunk => 1:1,
Expand Down Expand Up @@ -145,7 +146,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_
head === :inbounds || head === :foreigncall || head === :cfunction ||
head === :const || head === :enter || head === :leave || head === :pop_exception ||
head === :method || head === :global || head === :static_parameter ||
head === :new || head === :splatnew || head === :thunk || head === :loopinfo ||
head === :new || head === :splatnew || head === :thunk || head === :loopinfo || head === :funcinfo ||
head === :throw_undef_if_not || head === :code_coverage_effect || head === :inline || head === :noinline
validate_val!(x)
else
Expand Down
2 changes: 2 additions & 0 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ macro noinline(x)
return annotate_meta_def_or_block(x, :noinline)
end

macro unsafe() Expr(:funcinfo, (Symbol("julia.unsafe"), true)) end

"""
@pure ex
@pure(ex)
Expand Down
3 changes: 2 additions & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ RUNTIME_CODEGEN_SRCS := jitlayers aotcompile debuginfo disasm llvm-simdloop llvm
llvm-final-gc-lowering llvm-pass-helpers llvm-late-gc-lowering \
llvm-lower-handlers llvm-gc-invariant-verifier llvm-propagate-addrspaces \
llvm-multiversioning llvm-alloc-opt llvm-alloc-helpers cgmemmgr llvm-remove-addrspaces \
llvm-remove-ni llvm-julia-licm llvm-demote-float16 llvm-cpufeatures
llvm-remove-ni llvm-julia-licm llvm-demote-float16 llvm-cpufeatures llvm-unsafe-verifier
FLAGS += -I$(shell $(LLVM_CONFIG_HOST) --includedir)
CG_LLVM_LIBS := all
ifeq ($(USE_POLLY),1)
Expand Down Expand Up @@ -298,6 +298,7 @@ $(BUILDDIR)/llvm-alloc-helpers.o $(BUILDDIR)/llvm-alloc-helpers.dbg.obj: $(SRCDI
$(BUILDDIR)/llvm-alloc-opt.o $(BUILDDIR)/llvm-alloc-opt.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h
$(BUILDDIR)/llvm-final-gc-lowering.o $(BUILDDIR)/llvm-final-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h
$(BUILDDIR)/llvm-gc-invariant-verifier.o $(BUILDDIR)/llvm-gc-invariant-verifier.dbg.obj: $(SRCDIR)/codegen_shared.h
$(BUILDDIR)/llvm-unsafe-verifier.o $(BUILDDIR)/llvm-unsafe-verifier.dbg.obj: $(SRCDIR)/codegen_shared.h
$(BUILDDIR)/llvm-julia-licm.o $(BUILDDIR)/llvm-julia-licm.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/llvm-alloc-helpers.h $(SRCDIR)/llvm-pass-helpers.h
$(BUILDDIR)/llvm-late-gc-lowering.o $(BUILDDIR)/llvm-late-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/codegen_shared.h
$(BUILDDIR)/llvm-lower-handlers.o $(BUILDDIR)/llvm-lower-handlers.dbg.obj: $(SRCDIR)/codegen_shared.h
Expand Down
2 changes: 2 additions & 0 deletions src/aotcompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level,
PM->add(createLowerExcHandlersPass());
PM->add(createGCInvariantVerifierPass(false));
PM->add(createRemoveNIPass());
PM->add(createUnsafeVerifierPass());
PM->add(createLateLowerGCFramePass());
PM->add(createFinalLowerGCPass());
PM->add(createLowerPTLSPass(dump_native));
Expand Down Expand Up @@ -805,6 +806,7 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level,
// Needed **before** LateLowerGCFrame on LLVM < 12
// due to bug in `CreateAlignmentAssumption`.
PM->add(createRemoveNIPass());
PM->add(createUnsafeVerifierPass());
PM->add(createLateLowerGCFramePass());
PM->add(createFinalLowerGCPass());
// We need these two passes and the instcombine below
Expand Down
2 changes: 2 additions & 0 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ JL_DLLEXPORT jl_sym_t *jl_copyast_sym;
JL_DLLEXPORT jl_sym_t *jl_cfunction_sym;
JL_DLLEXPORT jl_sym_t *jl_pure_sym;
JL_DLLEXPORT jl_sym_t *jl_loopinfo_sym;
JL_DLLEXPORT jl_sym_t *jl_funcinfo_sym;
JL_DLLEXPORT jl_sym_t *jl_meta_sym;
JL_DLLEXPORT jl_sym_t *jl_inert_sym;
JL_DLLEXPORT jl_sym_t *jl_polly_sym;
Expand Down Expand Up @@ -318,6 +319,7 @@ void jl_init_common_symbols(void)
jl_newvar_sym = jl_symbol("newvar");
jl_copyast_sym = jl_symbol("copyast");
jl_loopinfo_sym = jl_symbol("loopinfo");
jl_funcinfo_sym = jl_symbol("funcinfo");
jl_pure_sym = jl_symbol("pure");
jl_meta_sym = jl_symbol("meta");
jl_list_sym = jl_symbol("list");
Expand Down
2 changes: 1 addition & 1 deletion src/ast.scm
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@
;; predicates and accessors

(define (quoted? e)
(memq (car e) '(quote top core globalref outerref line break inert meta inbounds inline noinline loopinfo)))
(memq (car e) '(quote top core globalref outerref line break inert meta inbounds inline noinline loopinfo funcinfo)))
(define (quotify e) `',e)
(define (unquote e)
(if (and (pair? e) (memq (car e) '(quote inert)))
Expand Down
1 change: 1 addition & 0 deletions src/codegen-stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ JL_DLLEXPORT void LLVMExtraAddLowerPTLSPass_fallback(void *PM, bool_t imaging_mo
JL_DLLEXPORT void LLVMExtraAddRemoveNIPass_fallback(void *PM) UNAVAILABLE

JL_DLLEXPORT void LLVMExtraAddGCInvariantVerifierPass_fallback(void *PM, bool_t Strong) UNAVAILABLE
JL_DLLEXPORT void LLVMExtraAddUnsafeVerifierPass_fallback(void *PM) UNAVAILABLE

JL_DLLEXPORT void LLVMExtraAddDemoteFloat16Pass_fallback(void *PM) UNAVAILABLE

Expand Down
20 changes: 20 additions & 0 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4714,6 +4714,26 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval)
I->setMetadata("julia.loopinfo", MD);
return jl_cgval_t();
}
else if (head == jl_funcinfo_sym) {
// parse Expr(:funcinfo, (kind, md))
// TODO: Errors
if (!ctx.f)
return jl_cgval_t();
for (int i = 0, ie = nargs; i < ie; ++i) {
if jl_is_tuple(args[i]) {
if (jl_nfields(args[i]) == 2) {
MDNode *MD = MDNode::get(jl_LLVMContext, to_md_tree(jl_fieldref(args[i], 1)));
jl_value_t* kind = jl_fieldref(args[i], 0);
if (jl_is_symbol(kind)) {
ctx.f->setMetadata(jl_symbol_name((jl_sym_t*)kind), MD);
} else if (jl_is_long(kind)) {
ctx.f->setMetadata((unsigned)jl_unbox_long(kind), MD);
}
}
}
}
return jl_cgval_t();
}
else if (head == jl_leave_sym || head == jl_coverageeffect_sym
|| head == jl_pop_exception_sym || head == jl_enter_sym || head == jl_inbounds_sym
|| head == jl_aliasscope_sym || head == jl_popaliasscope_sym || head == jl_inline_sym || head == jl_noinline_sym) {
Expand Down
5 changes: 3 additions & 2 deletions src/interpreter.c
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,9 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s)
else if (head == jl_boundscheck_sym) {
return jl_true;
}
else if (head == jl_meta_sym || head == jl_coverageeffect_sym || head == jl_inbounds_sym || head == jl_loopinfo_sym ||
head == jl_aliasscope_sym || head == jl_popaliasscope_sym || head == jl_inline_sym || head == jl_noinline_sym) {
else if (head == jl_meta_sym || head == jl_coverageeffect_sym || head == jl_inbounds_sym ||
head == jl_loopinfo_sym || head == jl_funcinfo_sym || head == jl_aliasscope_sym ||
head == jl_popaliasscope_sym || head == jl_inline_sym || head == jl_noinline_sym) {
return jl_nothing;
}
else if (head == jl_gc_preserve_begin_sym || head == jl_gc_preserve_end_sym) {
Expand Down
1 change: 1 addition & 0 deletions src/jitlayers.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ Pass *createFinalLowerGCPass();
Pass *createLateLowerGCFramePass();
Pass *createLowerExcHandlersPass();
Pass *createGCInvariantVerifierPass(bool Strong);
Pass *createUnsafeVerifierPass();
Pass *createPropagateJuliaAddrspaces();
Pass *createRemoveJuliaAddrspacesPass();
Pass *createRemoveNIPass();
Expand Down
1 change: 1 addition & 0 deletions src/jl_exported_funcs.inc
Original file line number Diff line number Diff line change
Expand Up @@ -565,5 +565,6 @@
YY(LLVMExtraAddLowerPTLSPass) \
YY(LLVMExtraAddRemoveNIPass) \
YY(LLVMExtraAddGCInvariantVerifierPass) \
YY(LLVMExtraAddUnsafeVerifierPass) \
YY(LLVMExtraAddDemoteFloat16Pass) \
YY(LLVMExtraAddCPUFeaturesPass) \
4 changes: 2 additions & 2 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -3516,7 +3516,7 @@ f(x) = yt(x)

(define lambda-opt-ignored-exprs
(Set '(quote top core line inert local-def unnecessary copyast
meta inbounds boundscheck loopinfo decl aliasscope popaliasscope
meta inbounds boundscheck loopinfo funcinfo decl aliasscope popaliasscope
thunk with-static-parameters toplevel-only
global globalref outerref const-if-global thismodule
const atomic null true false ssavalue isdefined toplevel module lambda
Expand Down Expand Up @@ -4625,7 +4625,7 @@ f(x) = yt(x)
(cons (car e) args)))

;; metadata expressions
((line meta inbounds loopinfo gc_preserve_end aliasscope popaliasscope inline noinline)
((line meta inbounds loopinfo funcinfo gc_preserve_end aliasscope popaliasscope inline noinline)
(let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return))))
(cond ((eq? (car e) 'line)
(set! current-loc e)
Expand Down
1 change: 1 addition & 0 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_copyast_sym;
extern JL_DLLEXPORT jl_sym_t *jl_cfunction_sym;
extern JL_DLLEXPORT jl_sym_t *jl_pure_sym;
extern JL_DLLEXPORT jl_sym_t *jl_loopinfo_sym;
extern JL_DLLEXPORT jl_sym_t *jl_funcinfo_sym;
extern JL_DLLEXPORT jl_sym_t *jl_meta_sym;
extern JL_DLLEXPORT jl_sym_t *jl_inert_sym;
extern JL_DLLEXPORT jl_sym_t *jl_polly_sym;
Expand Down
81 changes: 81 additions & 0 deletions src/llvm-unsafe-verifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// This file is a part of Julia. License is MIT: https://julialang.org/license

// This LLVM pass verifies invariants required for correct GC root placement.
// See the devdocs for a description of these invariants.

#include "llvm-version.h"

#include <llvm-c/Core.h>
#include <llvm-c/Types.h>

#include <llvm/IR/Value.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/IntrinsicInst.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/Pass.h>
#include <llvm/Support/Debug.h>

#include "codegen_shared.h"
#include "julia.h"
#include "julia_assert.h"
#include "llvm-pass-helpers.h"

#define DEBUG_TYPE "verify_unsafe"
#undef DEBUG

using namespace llvm;

struct UnsafeVerifier : public FunctionPass, private JuliaPassContext {
static char ID;
UnsafeVerifier() : FunctionPass(ID) {}

public:
void getAnalysisUsage(AnalysisUsage &AU) const override {
FunctionPass::getAnalysisUsage(AU);
AU.setPreservesAll();
}

bool runOnFunction(Function &F) override;
};

bool UnsafeVerifier::runOnFunction(Function &F) {
initFunctions(*F.getParent());
// pgcstack_getter doesn't exist in this module -> safe
if (!pgcstack_getter)
return false;

// pgcstack doesn't exist in this function -> safe
llvm::Instruction* pgcstack = getPGCstack(F);
if (!pgcstack)
return false;

// Check metadata
MDNode *MD = F.getMetadata("julia.unsafe");
if (!MD)
return false;

assert(MD->getNumOperands() == 1);
auto* V = cast<ConstantAsMetadata>(MD->getOperand(0));
auto C = cast<ConstantInt>(V->getValue())->getZExtValue();
if (C == 0)
return false;

dbgs() << "Verifying unsafe function " << F.getName() << " failed\n";
abort();

return false;
}

char UnsafeVerifier::ID = 0;
static RegisterPass<UnsafeVerifier> X("UnsafeVerifier", "Julia Unsafe Verification Pass", false, false);

Pass *createUnsafeVerifierPass() {
return new UnsafeVerifier();
}

extern "C" JL_DLLEXPORT void LLVMExtraAddUnsafeVerifierPass_impl(LLVMPassManagerRef PM)
{
unwrap(PM)->add(createUnsafeVerifierPass());
}
2 changes: 1 addition & 1 deletion src/method.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve
e->head == jl_coverageeffect_sym || e->head == jl_copyast_sym ||
e->head == jl_quote_sym || e->head == jl_inert_sym ||
e->head == jl_meta_sym || e->head == jl_inbounds_sym ||
e->head == jl_boundscheck_sym || e->head == jl_loopinfo_sym ||
e->head == jl_boundscheck_sym || e->head == jl_loopinfo_sym || e->head == jl_funcinfo_sym ||
e->head == jl_aliasscope_sym || e->head == jl_popaliasscope_sym ||
e->head == jl_inline_sym || e->head == jl_noinline_sym) {
// ignore these
Expand Down