Skip to content
Open
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
18 changes: 11 additions & 7 deletions clang/docs/TypeSanitizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ code is build with ``-fno-strict-aliasing``, sacrificing performance.
TypeSanitizer is built to catch when these strict aliasing rules have been violated, helping
users find where such bugs originate in their code despite the code looking valid at first glance.

As TypeSanitizer is still experimental, it can currently have a large impact on runtime speed,
memory use, and code size. It also has a large compile-time overhead. Work is being done to
reduce these impacts.
Typical memory overhead introduced by TypeSanitizer is about **8x**. Runtime slowdown varies greatly
depending on how often the instrumented code relies on type aliasing. In the best case slowdown is
**2x-3x**.

The TypeSanitizer Algorithm
===========================
Expand Down Expand Up @@ -128,6 +128,14 @@ references to LLVM IR specific terms.
Sanitizer features
==================

Instrumentation code inlining
------------------------------

By default TypeSanitizer inserts instrumentation through function calls. This may lead to a reduction in
runtime performance. ``-fno-sanitize-type-outline-instrumentation`` (default: ``false``) forces all
code instrumentation to be inlined. This will increase the size of the generated code and compiler
overhead, but may improve the runtime performance of the resulting code.

``__has_feature(type_sanitizer)``
------------------------------------

Expand Down Expand Up @@ -179,10 +187,6 @@ Limitations
shadow memory for each byte of user memory.
* There are transformation passes which run before TypeSanitizer. If these
passes optimize out an aliasing violation, TypeSanitizer cannot catch it.
* Currently, all instrumentation is inlined. This can result in a **15x**
(on average) increase in generated file size, and **3x** to **7x** increase
in compile time. In some documented cases this can cause the compiler to hang.
There are plans to improve this in the future.
* Codebases that use unions and struct-initialized variables can see incorrect
results, as TypeSanitizer doesn't yet instrument these reliably.
* Since Clang & LLVM's TBAA system is used to generate the checks used by the
Expand Down
9 changes: 9 additions & 0 deletions clang/docs/UsersManual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2277,6 +2277,15 @@ are listed below.

See :doc: `AddressSanitizer` for more details.

.. option:: -f[no-]sanitize-type-outline-instrumentation

Controls how type sanitizer code is generated. If enabled will always use
a function call instead of inlining the code. Turning this option off may
result in better run-time performance, but will increase binary size and
compilation overhead.

See :doc: `TypeSanitizer` for more details.

.. option:: -f[no-]sanitize-stats

Enable simple statistics gathering for the enabled sanitizers.
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Driver/SanitizerArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class SanitizerArgs {
bool TsanFuncEntryExit = true;
bool TsanAtomics = true;
bool MinimalRuntime = false;
bool TysanOutlineInstrumentation = true;
// True if cross-dso CFI support if provided by the system (i.e. Android).
bool ImplicitCfiRuntime = false;
bool NeedsMemProfRt = false;
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Options/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -2459,6 +2459,12 @@ def fsanitize_address_outline_instrumentation : Flag<["-"], "fsanitize-address-o
def fno_sanitize_address_outline_instrumentation : Flag<["-"], "fno-sanitize-address-outline-instrumentation">,
Group<f_clang_Group>,
HelpText<"Use default code inlining logic for the address sanitizer">;
def fsanitize_type_outline_instrumentation : Flag<["-"], "fsanitize-type-outline-instrumentation">,
Group<f_clang_Group>,
HelpText<"Always generate function calls for type sanitizer instrumentation">;
def fno_sanitize_type_outline_instrumentation : Flag<["-"], "fno-sanitize-type-outline-instrumentation">,
Group<f_clang_Group>,
HelpText<"Use code inlining logic for the type sanitizer">;
defm sanitize_stable_abi
: OptInCC1FFlag<"sanitize-stable-abi", "Stable ", "Conventional ",
"ABI instrumentation for sanitizer runtime. Default: Conventional">;
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/Driver/SanitizerArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,13 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
options::OPT_fno_sanitize_alloc_token_extended, AllocTokenExtended);
}

if (AllAddedKinds & SanitizerKind::Type) {
TysanOutlineInstrumentation =
Args.hasFlag(options::OPT_fsanitize_type_outline_instrumentation,
options::OPT_fno_sanitize_type_outline_instrumentation,
TysanOutlineInstrumentation);
}

LinkRuntimes = Args.hasFlag(options::OPT_fsanitize_link_runtime,
options::OPT_fno_sanitize_link_runtime,
!Args.hasArg(options::OPT_r));
Expand Down Expand Up @@ -1500,6 +1507,11 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
CmdArgs.push_back("-asan-instrumentation-with-call-threshold=0");
}

if (!TysanOutlineInstrumentation) {
CmdArgs.push_back("-mllvm");
CmdArgs.push_back("-tysan-outline-instrumentation=false");
}

// When emitting Stable ABI instrumentation, force outlining calls and avoid
// inlining shadow memory poisoning. While this is a big performance burden
// for now it allows full abstraction from implementation details.
Expand Down
22 changes: 22 additions & 0 deletions clang/test/CodeGen/sanitize-type-outlined.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// UNSUPPORTED: target={{.*}}-windows-{{.*}}

// RUN: %clang -S -fsanitize=type -emit-llvm -o - -fsanitize=type %s \
// RUN: -fno-sanitize-type-outline-instrumentation \
// RUN: | FileCheck %s --check-prefixes=CHECK-NO-OUTLINE
// RUN: %clang -S -fsanitize=type -emit-llvm -o - -fsanitize=type %s \
// RUN: -fsanitize-type-outline-instrumentation \
// RUN: | FileCheck %s --check-prefixes=CHECK-OUTLINE

// CHECK-LABEL: @alias
// CHECK: __tysan_app_memory_mask
// CHECK: __tysan_shadow_memory_address
// CHECK-NO-OUTLINE-NOT: call{{.*}}@__tysan_instrument_mem_inst
// CHECK-NO-OUTLINE-NOT: call{{.*}}@__tysan_instrument_with_shadow_update
// CHECK-OUTLINE: call{{.*}}@__tysan_instrument_mem_inst
// CHECK-OUTLINE: call{{.*}}@__tysan_instrument_with_shadow_update

float alias(int *ptr){
float *aliasedPtr = (float *)ptr;
*aliasedPtr *= 2.0f;
return *aliasedPtr;
}
4 changes: 4 additions & 0 deletions llvm/docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ Changes to BOLT
Changes to Sanitizers
---------------------

* TypeSanitizer no longer inlines all instrumentation by default. Added the
`-f[no-]sanitize-type-outline-instrumentation` flags to give users control
over this behaviour.

Other Changes
-------------

Expand Down
2 changes: 1 addition & 1 deletion llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ static cl::opt<bool> ClOutlineInstrumentation(
"tysan-outline-instrumentation",
cl::desc("Uses function calls for all TySan instrumentation, reducing "
"ELF size"),
cl::Hidden, cl::init(false));
cl::Hidden, cl::init(true));

static cl::opt<bool> ClVerifyOutlinedInstrumentation(
"tysan-verify-outlined-instrumentation",
Expand Down
70 changes: 64 additions & 6 deletions llvm/test/Instrumentation/TypeSanitizer/access-with-offset.ll
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 6
; RUN: opt -passes='tysan' -S %s | FileCheck %s
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6

; RUN: opt -passes='tysan' -tysan-outline-instrumentation=false -S %s | FileCheck %s --check-prefix=CHECK-INLINE
; RUN: opt -passes='tysan' -S %s | FileCheck %s --check-prefix=CHECK-OUTLINE

;.
; CHECK: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @tysan.module_ctor, ptr null }]
; CHECK: @__tysan_v1_Simple_20C_2fC_2b_2b_20TBAA = linkonce_odr constant { i64, i64, [18 x i8] } { i64 2, i64 0, [18 x i8] c"Simple C/C++ TBAA\00" }, comdat
; CHECK: @__tysan_v1_omnipotent_20char = linkonce_odr constant { i64, i64, ptr, i64, [16 x i8] } { i64 2, i64 1, ptr @__tysan_v1_Simple_20C_2fC_2b_2b_20TBAA, i64 0, [16 x i8] c"omnipotent char\00" }, comdat
Expand All @@ -10,7 +11,6 @@
; CHECK: @llvm.used = appending global [5 x ptr] [ptr @tysan.module_ctor, ptr @__tysan_v1_Simple_20C_2fC_2b_2b_20TBAA, ptr @__tysan_v1_omnipotent_20char, ptr @__tysan_v1_any_20pointer, ptr @__tysan_v1_any_20pointer_o_0], section "llvm.metadata"
; CHECK: @__tysan_shadow_memory_address = external global i64
; CHECK: @__tysan_app_memory_mask = external global i64
;.
define ptr @test_load_offset(ptr %argv) {
; CHECK-LABEL: define ptr @test_load_offset(
; CHECK-SAME: ptr [[ARGV:%.*]]) {
Expand Down Expand Up @@ -52,6 +52,55 @@ define ptr @test_load_offset(ptr %argv) {
; CHECK-NEXT: [[L:%.*]] = load ptr, ptr null, align 8, !tbaa [[ANYPTR_TBAA1:![0-9]+]]
; CHECK-NEXT: ret ptr [[L]]
;
; CHECK-INLINE-LABEL: define ptr @test_load_offset(
; CHECK-INLINE-SAME: ptr [[ARGV:%.*]]) {
; CHECK-INLINE-NEXT: [[ENTRY:.*:]]
; CHECK-INLINE-NEXT: [[APP_MEM_MASK:%.*]] = load i64, ptr @__tysan_app_memory_mask, align 4
; CHECK-INLINE-NEXT: [[SHADOW_BASE:%.*]] = load i64, ptr @__tysan_shadow_memory_address, align 4
; CHECK-INLINE-NEXT: [[APP_PTR_MASKED:%.*]] = and i64 0, [[APP_MEM_MASK]]
; CHECK-INLINE-NEXT: [[APP_PTR_SHIFTED:%.*]] = shl i64 [[APP_PTR_MASKED]], 3
; CHECK-INLINE-NEXT: [[SHADOW_PTR_INT:%.*]] = add i64 [[APP_PTR_SHIFTED]], [[SHADOW_BASE]]
; CHECK-INLINE-NEXT: [[SHADOW_PTR:%.*]] = inttoptr i64 [[SHADOW_PTR_INT]] to ptr
; CHECK-INLINE-NEXT: [[SHADOW_DESC:%.*]] = load ptr, ptr [[SHADOW_PTR]], align 8
; CHECK-INLINE-NEXT: [[DESC_SET:%.*]] = icmp eq ptr [[SHADOW_DESC]], null
; CHECK-INLINE-NEXT: br i1 [[DESC_SET]], label %[[SET_TYPE:.*]], label %[[BB0:.*]], !prof [[PROF0:![0-9]+]]
; CHECK-INLINE: [[SET_TYPE]]:
; CHECK-INLINE-NEXT: store ptr @__tysan_v1_any_20pointer_o_0, ptr [[SHADOW_PTR]], align 8
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_1_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 8
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_1_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_1_OFFSET]] to ptr
; CHECK-INLINE-NEXT: store ptr inttoptr (i64 -1 to ptr), ptr [[SHADOW_BYTE_1_PTR]], align 8
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_2_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 16
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_2_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_2_OFFSET]] to ptr
; CHECK-INLINE-NEXT: store ptr inttoptr (i64 -2 to ptr), ptr [[SHADOW_BYTE_2_PTR]], align 8
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_3_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 24
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_3_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_3_OFFSET]] to ptr
; CHECK-INLINE-NEXT: store ptr inttoptr (i64 -3 to ptr), ptr [[SHADOW_BYTE_3_PTR]], align 8
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_4_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 32
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_4_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_4_OFFSET]] to ptr
; CHECK-INLINE-NEXT: store ptr inttoptr (i64 -4 to ptr), ptr [[SHADOW_BYTE_4_PTR]], align 8
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_5_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 40
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_5_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_5_OFFSET]] to ptr
; CHECK-INLINE-NEXT: store ptr inttoptr (i64 -5 to ptr), ptr [[SHADOW_BYTE_5_PTR]], align 8
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_6_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 48
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_6_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_6_OFFSET]] to ptr
; CHECK-INLINE-NEXT: store ptr inttoptr (i64 -6 to ptr), ptr [[SHADOW_BYTE_6_PTR]], align 8
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_7_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 56
; CHECK-INLINE-NEXT: [[SHADOW_BYTE_7_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_7_OFFSET]] to ptr
; CHECK-INLINE-NEXT: store ptr inttoptr (i64 -7 to ptr), ptr [[SHADOW_BYTE_7_PTR]], align 8
; CHECK-INLINE-NEXT: br label %[[BB0]]
; CHECK-INLINE: [[BB0]]:
; CHECK-INLINE-NEXT: [[L:%.*]] = load ptr, ptr null, align 8, !tbaa [[ANYPTR_TBAA1:![0-9]+]]
; CHECK-INLINE-NEXT: ret ptr [[L]]
;
; CHECK-OUTLINE-LABEL: define ptr @test_load_offset(
; CHECK-OUTLINE-SAME: ptr [[ARGV:%.*]]) {
; CHECK-OUTLINE-NEXT: [[ENTRY:.*:]]
; CHECK-OUTLINE-NEXT: [[APP_MEM_MASK:%.*]] = load i64, ptr @__tysan_app_memory_mask, align 4
; CHECK-OUTLINE-NEXT: [[SHADOW_BASE:%.*]] = load i64, ptr @__tysan_shadow_memory_address, align 4
; CHECK-OUTLINE-NEXT: call void @__tysan_instrument_with_shadow_update(ptr null, ptr @__tysan_v1_any_20pointer_o_0, i1 false, i64 8, i32 1)
; CHECK-OUTLINE-NEXT: [[L:%.*]] = load ptr, ptr null, align 8, !tbaa [[ANYPTR_TBAA0:![0-9]+]]
; CHECK-OUTLINE-NEXT: ret ptr [[L]]
;
entry:
%l = load ptr, ptr null, align 8, !tbaa !0
ret ptr %l
Expand All @@ -61,12 +110,21 @@ entry:
!1 = !{!"any pointer", !2, i64 0}
!2 = !{!"omnipotent char", !3, i64 0}
!3 = !{!"Simple C/C++ TBAA"}
;.
; CHECK: attributes #[[ATTR0:[0-9]+]] = { nounwind }
;.
; CHECK: [[PROF0]] = !{!"branch_weights", i32 1, i32 100000}
; CHECK: [[ANYPTR_TBAA1]] = !{[[META2:![0-9]+]], [[META2]], i64 0}
; CHECK: [[META2]] = !{!"any pointer", [[META3:![0-9]+]], i64 0}
; CHECK: [[META3]] = !{!"omnipotent char", [[META4:![0-9]+]], i64 0}
; CHECK: [[META4]] = !{!"Simple C/C++ TBAA"}
;.
; CHECK-INLINE: [[PROF0]] = !{!"branch_weights", i32 1, i32 100000}
; CHECK-INLINE: [[ANYPTR_TBAA1]] = !{[[META2:![0-9]+]], [[META2]], i64 0}
; CHECK-INLINE: [[META2]] = !{!"any pointer", [[META3:![0-9]+]], i64 0}
; CHECK-INLINE: [[META3]] = !{!"omnipotent char", [[META4:![0-9]+]], i64 0}
; CHECK-INLINE: [[META4]] = !{!"Simple C/C++ TBAA"}
;.
; CHECK-OUTLINE: [[ANYPTR_TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0}
; CHECK-OUTLINE: [[META1]] = !{!"any pointer", [[META2:![0-9]+]], i64 0}
; CHECK-OUTLINE: [[META2]] = !{!"omnipotent char", [[META3:![0-9]+]], i64 0}
; CHECK-OUTLINE: [[META3]] = !{!"Simple C/C++ TBAA"}
;.
45 changes: 42 additions & 3 deletions llvm/test/Instrumentation/TypeSanitizer/alloca-only.ll
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals
; Test basic type sanitizer instrumentation.
;
; RUN: opt -passes='tysan' -S %s | FileCheck %s
; RUN: opt -passes='tysan' -tysan-outline-instrumentation=false -S %s | FileCheck %s --check-prefix=CHECK-INLINE
; RUN: opt -passes='tysan' -S %s | FileCheck %s --check-prefix=CHECK-OUTLINE

target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

;.
; CHECK: @llvm.used = appending global [1 x ptr] [ptr @tysan.module_ctor], section "llvm.metadata"
; CHECK: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @tysan.module_ctor, ptr null }]
; CHECK: @__tysan_shadow_memory_address = external global i64
; CHECK: @__tysan_app_memory_mask = external global i64
;.
; CHECK-INLINE: @llvm.used = appending global [1 x ptr] [ptr @tysan.module_ctor], section "llvm.metadata"
; CHECK-INLINE: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @tysan.module_ctor, ptr null }]
; CHECK-INLINE: @__tysan_shadow_memory_address = external global i64
; CHECK-INLINE: @__tysan_app_memory_mask = external global i64
;.
; CHECK-OUTLINE: @llvm.used = appending global [1 x ptr] [ptr @tysan.module_ctor], section "llvm.metadata"
; CHECK-OUTLINE: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @tysan.module_ctor, ptr null }]
; CHECK-OUTLINE: @__tysan_shadow_memory_address = external global i64
; CHECK-OUTLINE: @__tysan_app_memory_mask = external global i64
;.
define void @test_alloca_only() sanitize_type {
; CHECK-LABEL: @test_alloca_only(
; CHECK-NEXT: entry:
Expand All @@ -26,6 +36,29 @@ define void @test_alloca_only() sanitize_type {
; CHECK-NEXT: call void @foo(ptr [[TMP1]])
; CHECK-NEXT: ret void
;
; CHECK-INLINE-LABEL: @test_alloca_only(
; CHECK-INLINE-NEXT: entry:
; CHECK-INLINE-NEXT: [[APP_MEM_MASK:%.*]] = load i64, ptr @__tysan_app_memory_mask, align 8
; CHECK-INLINE-NEXT: [[SHADOW_BASE:%.*]] = load i64, ptr @__tysan_shadow_memory_address, align 8
; CHECK-INLINE-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-INLINE-NEXT: [[TMP0:%.*]] = ptrtoint ptr [[A]] to i64
; CHECK-INLINE-NEXT: [[TMP1:%.*]] = and i64 [[TMP0]], [[APP_MEM_MASK]]
; CHECK-INLINE-NEXT: [[TMP2:%.*]] = shl i64 [[TMP1]], 3
; CHECK-INLINE-NEXT: [[TMP3:%.*]] = add i64 [[TMP2]], [[SHADOW_BASE]]
; CHECK-INLINE-NEXT: [[TMP4:%.*]] = inttoptr i64 [[TMP3]] to ptr
; CHECK-INLINE-NEXT: call void @llvm.memset.p0.i64(ptr align 8 [[TMP4]], i8 0, i64 32, i1 false)
; CHECK-INLINE-NEXT: call void @foo(ptr [[A]])
; CHECK-INLINE-NEXT: ret void
;
; CHECK-OUTLINE-LABEL: @test_alloca_only(
; CHECK-OUTLINE-NEXT: entry:
; CHECK-OUTLINE-NEXT: [[APP_MEM_MASK:%.*]] = load i64, ptr @__tysan_app_memory_mask, align 8
; CHECK-OUTLINE-NEXT: [[SHADOW_BASE:%.*]] = load i64, ptr @__tysan_shadow_memory_address, align 8
; CHECK-OUTLINE-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-OUTLINE-NEXT: call void @__tysan_instrument_mem_inst(ptr [[A]], ptr null, i64 4, i1 false)
; CHECK-OUTLINE-NEXT: call void @foo(ptr [[A]])
; CHECK-OUTLINE-NEXT: ret void
;
entry:
%a = alloca i32
call void @foo(ptr %a)
Expand All @@ -42,8 +75,14 @@ declare void @foo(ptr)
!4 = !{!"_ZTS1x", !2, i64 0, !2, i64 4}
!5 = !{!"_ZTS1v", !2, i64 8, !2, i64 12, !4, i64 16}
!6 = !{!5, !2, i64 12}
;.
; CHECK: attributes #[[ATTR0:[0-9]+]] = { sanitize_type }
; CHECK: attributes #[[ATTR1:[0-9]+]] = { nounwind }
; CHECK: attributes #[[ATTR2:[0-9]+]] = { nocallback nofree nounwind willreturn memory(argmem: write) }
;.
; CHECK-INLINE: attributes #[[ATTR0:[0-9]+]] = { sanitize_type }
; CHECK-INLINE: attributes #[[ATTR1:[0-9]+]] = { nounwind }
; CHECK-INLINE: attributes #[[ATTR2:[0-9]+]] = { nocallback nofree nounwind willreturn memory(argmem: write) }
;.
; CHECK-OUTLINE: attributes #[[ATTR0:[0-9]+]] = { sanitize_type }
; CHECK-OUTLINE: attributes #[[ATTR1:[0-9]+]] = { nounwind }
;.
Loading
Loading