Commit 2a25448c by Igor Tsimbalist Committed by Igor Tsimbalist

Update x86 backend to enable Intel CET.

All platforms except i386 will report the error and do no
instrumentation with -finstrument-control-flow option. i386
will provide the implementation based on a specification
published by Intel for a new technology called Control-flow
Enforcement Technology (CET). The spec is available at

https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf

The implementation in this patch:
1) enables Control-flow Enforcement Technology (CET), published by
Intel. This part introduces i386 specific options -mcet, -mibt and
-mshstk, new instructions and intrinsics;

2) provides support for -fcf-protection option and 'nocf_check'
attribute by doing needed code instrumentation, which is based on
CET features.

gcc/

	* common/config/i386/i386-common.c (OPTION_MASK_ISA_IBT_SET): New.
	(OPTION_MASK_ISA_SHSTK_SET): Likewise.
	(OPTION_MASK_ISA_IBT_UNSET): Likewise.
	(OPTION_MASK_ISA_SHSTK_UNSET): Likewise.
	(ix86_handle_option): Add -mibt, -mshstk, -mcet handling.
	* config.gcc (extra_headers): Add cetintrin.h for x86 targets.
	(extra_objs): Add cet.o for Linux/x86 targets.
	(tmake_file): Add i386/t-cet for Linux/x86 targets.
	* config/i386/cet.c: New file.
	* config/i386/cetintrin.h: Likewise.
	* config/i386/t-cet: Likewise.
	* config/i386/cpuid.h (bit_SHSTK): New.
	(bit_IBT): Likewise.
	* config/i386/driver-i386.c (host_detect_local_cpu): Detect and
	pass IBT and SHSTK bits.
	* config/i386/i386-builtin-types.def
	(VOID_FTYPE_UNSIGNED_PVOID): New.
	(VOID_FTYPE_UINT64_PVOID): Likewise.
	* config/i386/i386-builtin.def: Add CET intrinsics.
	* config/i386/i386-c.c (ix86_target_macros_internal): Add
	OPTION_MASK_ISA_IBT, OPTION_MASK_ISA_SHSTK handling.
	* config/i386/i386-passes.def: Add pass_insert_endbranch pass.
	* config/i386/i386-protos.h (make_pass_insert_endbranch): New
	prototype.
	* config/i386/i386.c (rest_of_insert_endbranch): New.
	(pass_data_insert_endbranch): Likewise.
	(pass_insert_endbranch): Likewise.
	(make_pass_insert_endbranch): Likewise.
	(ix86_notrack_prefixed_insn_p): Likewise.
	(ix86_target_string): Add -mibt, -mshstk flags.
	(ix86_option_override_internal): Add flag_cf_protection
	processing.
	(ix86_valid_target_attribute_inner_p): Set OPT_mibt, OPT_mshstk.
	(ix86_print_operand): Add 'notrack' prefix output.
	(ix86_init_mmx_sse_builtins): Add CET intrinsics.
	(ix86_expand_builtin): Expand CET intrinsics.
	(x86_output_mi_thunk): Add 'endbranch' instruction.
	* config/i386/i386.h (TARGET_IBT): New.
	(TARGET_IBT_P): Likewise.
	(TARGET_SHSTK): Likewise.
	(TARGET_SHSTK_P): Likewise.
	   * config/i386/i386.md (unspecv): Add UNSPECV_NOP_RDSSP,
	UNSPECV_INCSSP, UNSPECV_SAVEPREVSSP, UNSPECV_RSTORSSP,
	UNSPECV_WRSS, UNSPECV_WRUSS, UNSPECV_SETSSBSY, UNSPECV_CLRSSBSY.
	(builtin_setjmp_setup): New pattern.
	(builtin_longjmp): Likewise.
	(rdssp<mode>): Likewise.
	(incssp<mode>): Likewise.
	(saveprevssp): Likewise.
	(rstorssp): Likewise.
	(wrss<mode>): Likewise.
	(wruss<mode>): Likewise.
	(setssbsy): Likewise.
	(clrssbsy): Likewise.
	(nop_endbr): Likewise.
	* config/i386/i386.opt: Add -mcet, -mibt, -mshstk and -mcet-switch
	options.
	* config/i386/immintrin.h: Include <cetintrin.h>.
	* config/i386/linux-common.h
	(file_end_indicate_exec_stack_and_cet): New prototype.
	(TARGET_ASM_FILE_END): New.

From-SVN: r253977
parent f6fd8f2b
2017-10-21 Igor Tsimbalist <igor.v.tsimbalist@intel.com>
* common/config/i386/i386-common.c (OPTION_MASK_ISA_IBT_SET): New.
(OPTION_MASK_ISA_SHSTK_SET): Likewise.
(OPTION_MASK_ISA_IBT_UNSET): Likewise.
(OPTION_MASK_ISA_SHSTK_UNSET): Likewise.
(ix86_handle_option): Add -mibt, -mshstk, -mcet handling.
* config.gcc (extra_headers): Add cetintrin.h for x86 targets.
(extra_objs): Add cet.o for Linux/x86 targets.
(tmake_file): Add i386/t-cet for Linux/x86 targets.
* config/i386/cet.c: New file.
* config/i386/cetintrin.h: Likewise.
* config/i386/t-cet: Likewise.
* config/i386/cpuid.h (bit_SHSTK): New.
(bit_IBT): Likewise.
* config/i386/driver-i386.c (host_detect_local_cpu): Detect and
pass IBT and SHSTK bits.
* config/i386/i386-builtin-types.def
(VOID_FTYPE_UNSIGNED_PVOID): New.
(VOID_FTYPE_UINT64_PVOID): Likewise.
* config/i386/i386-builtin.def: Add CET intrinsics.
* config/i386/i386-c.c (ix86_target_macros_internal): Add
OPTION_MASK_ISA_IBT, OPTION_MASK_ISA_SHSTK handling.
* config/i386/i386-passes.def: Add pass_insert_endbranch pass.
* config/i386/i386-protos.h (make_pass_insert_endbranch): New
prototype.
* config/i386/i386.c (rest_of_insert_endbranch): New.
(pass_data_insert_endbranch): Likewise.
(pass_insert_endbranch): Likewise.
(make_pass_insert_endbranch): Likewise.
(ix86_notrack_prefixed_insn_p): Likewise.
(ix86_target_string): Add -mibt, -mshstk flags.
(ix86_option_override_internal): Add flag_cf_protection
processing.
(ix86_valid_target_attribute_inner_p): Set OPT_mibt, OPT_mshstk.
(ix86_print_operand): Add 'notrack' prefix output.
(ix86_init_mmx_sse_builtins): Add CET intrinsics.
(ix86_expand_builtin): Expand CET intrinsics.
(x86_output_mi_thunk): Add 'endbranch' instruction.
* config/i386/i386.h (TARGET_IBT): New.
(TARGET_IBT_P): Likewise.
(TARGET_SHSTK): Likewise.
(TARGET_SHSTK_P): Likewise.
* config/i386/i386.md (unspecv): Add UNSPECV_NOP_RDSSP,
UNSPECV_INCSSP, UNSPECV_SAVEPREVSSP, UNSPECV_RSTORSSP,
UNSPECV_WRSS, UNSPECV_WRUSS, UNSPECV_SETSSBSY, UNSPECV_CLRSSBSY.
(builtin_setjmp_setup): New pattern.
(builtin_longjmp): Likewise.
(rdssp<mode>): Likewise.
(incssp<mode>): Likewise.
(saveprevssp): Likewise.
(rstorssp): Likewise.
(wrss<mode>): Likewise.
(wruss<mode>): Likewise.
(setssbsy): Likewise.
(clrssbsy): Likewise.
(nop_endbr): Likewise.
* config/i386/i386.opt: Add -mcet, -mibt, -mshstk and -mcet-switch
options.
* config/i386/immintrin.h: Include <cetintrin.h>.
* config/i386/linux-common.h
(file_end_indicate_exec_stack_and_cet): New prototype.
(TARGET_ASM_FILE_END): New.
2017-10-20 Jan Hubicka <hubicka@ucw.cz> 2017-10-20 Jan Hubicka <hubicka@ucw.cz>
* x86-tune-costs.h (intel_cost, generic_cost): Fix move costs. * x86-tune-costs.h (intel_cost, generic_cost): Fix move costs.
...@@ -138,6 +138,8 @@ along with GCC; see the file COPYING3. If not see ...@@ -138,6 +138,8 @@ along with GCC; see the file COPYING3. If not see
#define OPTION_MASK_ISA_PKU_SET OPTION_MASK_ISA_PKU #define OPTION_MASK_ISA_PKU_SET OPTION_MASK_ISA_PKU
#define OPTION_MASK_ISA_RDPID_SET OPTION_MASK_ISA_RDPID #define OPTION_MASK_ISA_RDPID_SET OPTION_MASK_ISA_RDPID
#define OPTION_MASK_ISA_GFNI_SET OPTION_MASK_ISA_GFNI #define OPTION_MASK_ISA_GFNI_SET OPTION_MASK_ISA_GFNI
#define OPTION_MASK_ISA_IBT_SET OPTION_MASK_ISA_IBT
#define OPTION_MASK_ISA_SHSTK_SET OPTION_MASK_ISA_SHSTK
/* Define a set of ISAs which aren't available when a given ISA is /* Define a set of ISAs which aren't available when a given ISA is
disabled. MMX and SSE ISAs are handled separately. */ disabled. MMX and SSE ISAs are handled separately. */
...@@ -204,6 +206,8 @@ along with GCC; see the file COPYING3. If not see ...@@ -204,6 +206,8 @@ along with GCC; see the file COPYING3. If not see
#define OPTION_MASK_ISA_PKU_UNSET OPTION_MASK_ISA_PKU #define OPTION_MASK_ISA_PKU_UNSET OPTION_MASK_ISA_PKU
#define OPTION_MASK_ISA_RDPID_UNSET OPTION_MASK_ISA_RDPID #define OPTION_MASK_ISA_RDPID_UNSET OPTION_MASK_ISA_RDPID
#define OPTION_MASK_ISA_GFNI_UNSET OPTION_MASK_ISA_GFNI #define OPTION_MASK_ISA_GFNI_UNSET OPTION_MASK_ISA_GFNI
#define OPTION_MASK_ISA_IBT_UNSET OPTION_MASK_ISA_IBT
#define OPTION_MASK_ISA_SHSTK_UNSET OPTION_MASK_ISA_SHSTK
/* SSE4 includes both SSE4.1 and SSE4.2. -mno-sse4 should the same /* SSE4 includes both SSE4.1 and SSE4.2. -mno-sse4 should the same
as -mno-sse4.1. */ as -mno-sse4.1. */
...@@ -499,6 +503,35 @@ ix86_handle_option (struct gcc_options *opts, ...@@ -499,6 +503,35 @@ ix86_handle_option (struct gcc_options *opts,
} }
return true; return true;
case OPT_mcet:
case OPT_mibt:
if (value)
{
opts->x_ix86_isa_flags2 |= OPTION_MASK_ISA_IBT_SET;
opts->x_ix86_isa_flags2_explicit |= OPTION_MASK_ISA_IBT_SET;
}
else
{
opts->x_ix86_isa_flags2 &= ~OPTION_MASK_ISA_IBT_UNSET;
opts->x_ix86_isa_flags2_explicit |= OPTION_MASK_ISA_IBT_UNSET;
}
if (code != OPT_mcet)
return true;
/* fall through. */
case OPT_mshstk:
if (value)
{
opts->x_ix86_isa_flags2 |= OPTION_MASK_ISA_SHSTK_SET;
opts->x_ix86_isa_flags2_explicit |= OPTION_MASK_ISA_SHSTK_SET;
}
else
{
opts->x_ix86_isa_flags2 &= ~OPTION_MASK_ISA_SHSTK_UNSET;
opts->x_ix86_isa_flags2_explicit |= OPTION_MASK_ISA_SHSTK_UNSET;
}
return true;
case OPT_mavx5124fmaps: case OPT_mavx5124fmaps:
if (value) if (value)
{ {
......
...@@ -378,7 +378,7 @@ i[34567]86-*-*) ...@@ -378,7 +378,7 @@ i[34567]86-*-*)
avx512ifmaintrin.h avx512ifmavlintrin.h avx512vbmiintrin.h avx512ifmaintrin.h avx512ifmavlintrin.h avx512vbmiintrin.h
avx512vbmivlintrin.h avx5124fmapsintrin.h avx5124vnniwintrin.h avx512vbmivlintrin.h avx5124fmapsintrin.h avx5124vnniwintrin.h
avx512vpopcntdqintrin.h clwbintrin.h mwaitxintrin.h avx512vpopcntdqintrin.h clwbintrin.h mwaitxintrin.h
clzerointrin.h pkuintrin.h sgxintrin.h" clzerointrin.h pkuintrin.h sgxintrin.h cetintrin.h"
;; ;;
x86_64-*-*) x86_64-*-*)
cpu_type=i386 cpu_type=i386
...@@ -402,7 +402,7 @@ x86_64-*-*) ...@@ -402,7 +402,7 @@ x86_64-*-*)
avx512ifmaintrin.h avx512ifmavlintrin.h avx512vbmiintrin.h avx512ifmaintrin.h avx512ifmavlintrin.h avx512vbmiintrin.h
avx512vbmivlintrin.h avx5124fmapsintrin.h avx5124vnniwintrin.h avx512vbmivlintrin.h avx5124fmapsintrin.h avx5124vnniwintrin.h
avx512vpopcntdqintrin.h clwbintrin.h mwaitxintrin.h avx512vpopcntdqintrin.h clwbintrin.h mwaitxintrin.h
clzerointrin.h pkuintrin.h sgxintrin.h" clzerointrin.h pkuintrin.h sgxintrin.h cetintrin.h"
;; ;;
ia64-*-*) ia64-*-*)
extra_headers=ia64intrin.h extra_headers=ia64intrin.h
...@@ -4551,7 +4551,8 @@ case ${target} in ...@@ -4551,7 +4551,8 @@ case ${target} in
i[34567]86-*-darwin* | x86_64-*-darwin*) i[34567]86-*-darwin* | x86_64-*-darwin*)
;; ;;
i[34567]86-*-linux* | x86_64-*-linux*) i[34567]86-*-linux* | x86_64-*-linux*)
tmake_file="$tmake_file i386/t-linux" extra_objs="${extra_objs} cet.o"
tmake_file="$tmake_file i386/t-linux i386/t-cet"
;; ;;
i[34567]86-*-kfreebsd*-gnu | x86_64-*-kfreebsd*-gnu) i[34567]86-*-kfreebsd*-gnu | x86_64-*-kfreebsd*-gnu)
tmake_file="$tmake_file i386/t-kfreebsd" tmake_file="$tmake_file i386/t-kfreebsd"
......
/* Functions for CET/x86.
Copyright (C) 2017 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "output.h"
#include "linux-common.h"
void
file_end_indicate_exec_stack_and_cet (void)
{
file_end_indicate_exec_stack ();
if (flag_cf_protection == CF_NONE)
return;
unsigned int feature_1 = 0;
if (TARGET_IBT)
/* GNU_PROPERTY_X86_FEATURE_1_IBT. */
feature_1 |= 0x1;
if (TARGET_SHSTK)
/* GNU_PROPERTY_X86_FEATURE_1_SHSTK. */
feature_1 |= 0x2;
if (feature_1)
{
int p2align = ptr_mode == SImode ? 2 : 3;
/* Generate GNU_PROPERTY_X86_FEATURE_1_XXX. */
switch_to_section (get_section (".note.gnu.property",
SECTION_NOTYPE, NULL));
ASM_OUTPUT_ALIGN (asm_out_file, p2align);
/* name length. */
fprintf (asm_out_file, ASM_LONG " 1f - 0f\n");
/* data length. */
fprintf (asm_out_file, ASM_LONG " 4f - 1f\n");
/* note type: NT_GNU_PROPERTY_TYPE_0. */
fprintf (asm_out_file, ASM_LONG " 5\n");
ASM_OUTPUT_LABEL (asm_out_file, "0");
/* vendor name: "GNU". */
fprintf (asm_out_file, STRING_ASM_OP " \"GNU\"\n");
ASM_OUTPUT_LABEL (asm_out_file, "1");
ASM_OUTPUT_ALIGN (asm_out_file, p2align);
/* pr_type: GNU_PROPERTY_X86_FEATURE_1_AND. */
fprintf (asm_out_file, ASM_LONG " 0xc0000002\n");
/* pr_datasz. */\
fprintf (asm_out_file, ASM_LONG " 3f - 2f\n");
ASM_OUTPUT_LABEL (asm_out_file, "2");
/* GNU_PROPERTY_X86_FEATURE_1_XXX. */
fprintf (asm_out_file, ASM_LONG " 0x%x\n", feature_1);
ASM_OUTPUT_LABEL (asm_out_file, "3");
ASM_OUTPUT_ALIGN (asm_out_file, p2align);
ASM_OUTPUT_LABEL (asm_out_file, "4");
}
}
/* Copyright (C) 2015-2017 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
#if !defined _IMMINTRIN_H_INCLUDED
# error "Never use <cetintrin.h> directly; include <x86intrin.h> instead."
#endif
#ifndef _CETINTRIN_H_INCLUDED
#define _CETINTRIN_H_INCLUDED
#ifndef __SHSTK__
#pragma GCC push_options
#pragma GCC target ("shstk")
#define __DISABLE_SHSTK__
#endif /* __SHSTK__ */
extern __inline unsigned int
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_rdsspd (unsigned int __B)
{
return __builtin_ia32_rdsspd (__B);
}
#ifdef __x86_64__
extern __inline unsigned long long
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_rdsspq (unsigned long long __B)
{
return __builtin_ia32_rdsspq (__B);
}
#endif
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_incsspd (unsigned int __B)
{
__builtin_ia32_incsspd (__B);
}
#ifdef __x86_64__
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_incsspq (unsigned long long __B)
{
__builtin_ia32_incsspq (__B);
}
#endif
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_saveprevssp (void)
{
__builtin_ia32_saveprevssp ();
}
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_rstorssp (void *__B)
{
__builtin_ia32_rstorssp (__B);
}
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_wrssd (unsigned int __B, void *__C)
{
__builtin_ia32_wrssd (__B, __C);
}
#ifdef __x86_64__
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_wrssq (unsigned long long __B, void *__C)
{
__builtin_ia32_wrssq (__B, __C);
}
#endif
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_wrussd (unsigned int __B, void *__C)
{
__builtin_ia32_wrussd (__B, __C);
}
#ifdef __x86_64__
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_wrussq (unsigned long long __B, void *__C)
{
__builtin_ia32_wrussq (__B, __C);
}
#endif
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_setssbsy (void)
{
__builtin_ia32_setssbsy ();
}
extern __inline void
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
_clrssbsy (void *__B)
{
__builtin_ia32_clrssbsy (__B);
}
#ifdef __DISABLE_SHSTK__
#undef __DISABLE_SHSTK__
#pragma GCC pop_options
#endif /* __DISABLE_SHSTK__ */
#endif /* _CETINTRIN_H_INCLUDED. */
...@@ -97,6 +97,7 @@ ...@@ -97,6 +97,7 @@
#define bit_AVX512VBMI (1 << 1) #define bit_AVX512VBMI (1 << 1)
#define bit_PKU (1 << 3) #define bit_PKU (1 << 3)
#define bit_OSPKE (1 << 4) #define bit_OSPKE (1 << 4)
#define bit_SHSTK (1 << 7)
#define bit_GFNI (1 << 8) #define bit_GFNI (1 << 8)
#define bit_AVX512VPOPCNTDQ (1 << 14) #define bit_AVX512VPOPCNTDQ (1 << 14)
#define bit_RDPID (1 << 22) #define bit_RDPID (1 << 22)
...@@ -104,6 +105,7 @@ ...@@ -104,6 +105,7 @@
/* %edx */ /* %edx */
#define bit_AVX5124VNNIW (1 << 2) #define bit_AVX5124VNNIW (1 << 2)
#define bit_AVX5124FMAPS (1 << 3) #define bit_AVX5124FMAPS (1 << 3)
#define bit_IBT (1 << 20)
/* XFEATURE_ENABLED_MASK register bits (%eax == 13, %ecx == 0) */ /* XFEATURE_ENABLED_MASK register bits (%eax == 13, %ecx == 0) */
#define bit_BNDREGS (1 << 3) #define bit_BNDREGS (1 << 3)
......
...@@ -416,6 +416,7 @@ const char *host_detect_local_cpu (int argc, const char **argv) ...@@ -416,6 +416,7 @@ const char *host_detect_local_cpu (int argc, const char **argv)
unsigned int has_mwaitx = 0, has_clzero = 0, has_pku = 0, has_rdpid = 0; unsigned int has_mwaitx = 0, has_clzero = 0, has_pku = 0, has_rdpid = 0;
unsigned int has_avx5124fmaps = 0, has_avx5124vnniw = 0; unsigned int has_avx5124fmaps = 0, has_avx5124vnniw = 0;
unsigned int has_gfni = 0; unsigned int has_gfni = 0;
unsigned int has_ibt = 0, has_shstk = 0;
bool arch; bool arch;
...@@ -509,6 +510,9 @@ const char *host_detect_local_cpu (int argc, const char **argv) ...@@ -509,6 +510,9 @@ const char *host_detect_local_cpu (int argc, const char **argv)
has_avx5124vnniw = edx & bit_AVX5124VNNIW; has_avx5124vnniw = edx & bit_AVX5124VNNIW;
has_avx5124fmaps = edx & bit_AVX5124FMAPS; has_avx5124fmaps = edx & bit_AVX5124FMAPS;
has_shstk = ecx & bit_SHSTK;
has_ibt = edx & bit_IBT;
} }
if (max_level >= 13) if (max_level >= 13)
...@@ -1051,6 +1055,8 @@ const char *host_detect_local_cpu (int argc, const char **argv) ...@@ -1051,6 +1055,8 @@ const char *host_detect_local_cpu (int argc, const char **argv)
const char *pku = has_pku ? " -mpku" : " -mno-pku"; const char *pku = has_pku ? " -mpku" : " -mno-pku";
const char *rdpid = has_rdpid ? " -mrdpid" : " -mno-rdpid"; const char *rdpid = has_rdpid ? " -mrdpid" : " -mno-rdpid";
const char *gfni = has_gfni ? " -mgfni" : " -mno-gfni"; const char *gfni = has_gfni ? " -mgfni" : " -mno-gfni";
const char *ibt = has_ibt ? " -mibt" : " -mno-ibt";
const char *shstk = has_shstk ? " -mshstk" : " -mno-shstk";
options = concat (options, mmx, mmx3dnow, sse, sse2, sse3, ssse3, options = concat (options, mmx, mmx3dnow, sse, sse2, sse3, ssse3,
sse4a, cx16, sahf, movbe, aes, sha, pclmul, sse4a, cx16, sahf, movbe, aes, sha, pclmul,
popcnt, abm, lwp, fma, fma4, xop, bmi, sgx, bmi2, popcnt, abm, lwp, fma, fma4, xop, bmi, sgx, bmi2,
...@@ -1060,7 +1066,7 @@ const char *host_detect_local_cpu (int argc, const char **argv) ...@@ -1060,7 +1066,7 @@ const char *host_detect_local_cpu (int argc, const char **argv)
avx512cd, avx512pf, prefetchwt1, clflushopt, avx512cd, avx512pf, prefetchwt1, clflushopt,
xsavec, xsaves, avx512dq, avx512bw, avx512vl, xsavec, xsaves, avx512dq, avx512bw, avx512vl,
avx512ifma, avx512vbmi, avx5124fmaps, avx5124vnniw, avx512ifma, avx512vbmi, avx5124fmaps, avx5124vnniw,
clwb, mwaitx, clzero, pku, rdpid, gfni, NULL); clwb, mwaitx, clzero, pku, rdpid, gfni, ibt, shstk, NULL);
} }
done: done:
......
...@@ -286,7 +286,9 @@ DEF_FUNCTION_TYPE (V8SI, V8SI) ...@@ -286,7 +286,9 @@ DEF_FUNCTION_TYPE (V8SI, V8SI)
DEF_FUNCTION_TYPE (VOID, PCVOID) DEF_FUNCTION_TYPE (VOID, PCVOID)
DEF_FUNCTION_TYPE (VOID, PVOID) DEF_FUNCTION_TYPE (VOID, PVOID)
DEF_FUNCTION_TYPE (VOID, UINT64) DEF_FUNCTION_TYPE (VOID, UINT64)
DEF_FUNCTION_TYPE (VOID, UINT64, PVOID)
DEF_FUNCTION_TYPE (VOID, UNSIGNED) DEF_FUNCTION_TYPE (VOID, UNSIGNED)
DEF_FUNCTION_TYPE (VOID, UNSIGNED, PVOID)
DEF_FUNCTION_TYPE (INT, PUSHORT) DEF_FUNCTION_TYPE (INT, PUSHORT)
DEF_FUNCTION_TYPE (INT, PUNSIGNED) DEF_FUNCTION_TYPE (INT, PUNSIGNED)
DEF_FUNCTION_TYPE (INT, PULONGLONG) DEF_FUNCTION_TYPE (INT, PULONGLONG)
......
...@@ -2779,4 +2779,25 @@ BDESC (OPTION_MASK_ISA_XOP, CODE_FOR_xop_vpermil2v4sf3, "__builtin_ia32_vper ...@@ -2779,4 +2779,25 @@ BDESC (OPTION_MASK_ISA_XOP, CODE_FOR_xop_vpermil2v4sf3, "__builtin_ia32_vper
BDESC (OPTION_MASK_ISA_XOP, CODE_FOR_xop_vpermil2v4df3, "__builtin_ia32_vpermil2pd256", IX86_BUILTIN_VPERMIL2PD256, UNKNOWN, (int)MULTI_ARG_4_DF2_DI_I1) BDESC (OPTION_MASK_ISA_XOP, CODE_FOR_xop_vpermil2v4df3, "__builtin_ia32_vpermil2pd256", IX86_BUILTIN_VPERMIL2PD256, UNKNOWN, (int)MULTI_ARG_4_DF2_DI_I1)
BDESC (OPTION_MASK_ISA_XOP, CODE_FOR_xop_vpermil2v8sf3, "__builtin_ia32_vpermil2ps256", IX86_BUILTIN_VPERMIL2PS256, UNKNOWN, (int)MULTI_ARG_4_SF2_SI_I1) BDESC (OPTION_MASK_ISA_XOP, CODE_FOR_xop_vpermil2v8sf3, "__builtin_ia32_vpermil2ps256", IX86_BUILTIN_VPERMIL2PS256, UNKNOWN, (int)MULTI_ARG_4_SF2_SI_I1)
BDESC_END (MULTI_ARG, MAX) BDESC_END (MULTI_ARG, CET)
/* CET. */
BDESC_FIRST (cet, CET,
OPTION_MASK_ISA_SHSTK, CODE_FOR_incsspsi, "__builtin_ia32_incsspd", IX86_BUILTIN_INCSSPD, UNKNOWN, (int) VOID_FTYPE_UNSIGNED)
BDESC (OPTION_MASK_ISA_SHSTK | OPTION_MASK_ISA_64BIT, CODE_FOR_incsspdi, "__builtin_ia32_incsspq", IX86_BUILTIN_INCSSPQ, UNKNOWN, (int) VOID_FTYPE_UINT64)
BDESC (OPTION_MASK_ISA_SHSTK, CODE_FOR_saveprevssp, "__builtin_ia32_saveprevssp", IX86_BUILTIN_SAVEPREVSSP, UNKNOWN, (int) VOID_FTYPE_VOID)
BDESC (OPTION_MASK_ISA_SHSTK, CODE_FOR_rstorssp, "__builtin_ia32_rstorssp", IX86_BUILTIN_RSTORSSP, UNKNOWN, (int) VOID_FTYPE_PVOID)
BDESC (OPTION_MASK_ISA_SHSTK, CODE_FOR_wrsssi, "__builtin_ia32_wrssd", IX86_BUILTIN_WRSSD, UNKNOWN, (int) VOID_FTYPE_UNSIGNED_PVOID)
BDESC (OPTION_MASK_ISA_SHSTK | OPTION_MASK_ISA_64BIT, CODE_FOR_wrssdi, "__builtin_ia32_wrssq", IX86_BUILTIN_WRSSQ, UNKNOWN, (int) VOID_FTYPE_UINT64_PVOID)
BDESC (OPTION_MASK_ISA_SHSTK, CODE_FOR_wrusssi, "__builtin_ia32_wrussd", IX86_BUILTIN_WRUSSD, UNKNOWN, (int) VOID_FTYPE_UNSIGNED_PVOID)
BDESC (OPTION_MASK_ISA_SHSTK | OPTION_MASK_ISA_64BIT, CODE_FOR_wrussdi, "__builtin_ia32_wrussq", IX86_BUILTIN_WRUSSQ, UNKNOWN, (int) VOID_FTYPE_UINT64_PVOID)
BDESC (OPTION_MASK_ISA_SHSTK, CODE_FOR_setssbsy, "__builtin_ia32_setssbsy", IX86_BUILTIN_SETSSBSY, UNKNOWN, (int) VOID_FTYPE_VOID)
BDESC (OPTION_MASK_ISA_SHSTK, CODE_FOR_clrssbsy, "__builtin_ia32_clrssbsy", IX86_BUILTIN_CLRSSBSY, UNKNOWN, (int) VOID_FTYPE_PVOID)
BDESC_END (CET, CET_NORMAL)
BDESC_FIRST (cet_rdssp, CET_NORMAL,
OPTION_MASK_ISA_SHSTK, CODE_FOR_rdsspsi, "__builtin_ia32_rdsspd", IX86_BUILTIN_RDSSPD, UNKNOWN, (int) UINT_FTYPE_UINT)
BDESC (OPTION_MASK_ISA_SHSTK | OPTION_MASK_ISA_64BIT, CODE_FOR_rdsspdi, "__builtin_ia32_rdsspq", IX86_BUILTIN_RDSSPQ, UNKNOWN, (int) UINT64_FTYPE_UINT64)
BDESC_END (CET_NORMAL, MAX)
...@@ -459,6 +459,18 @@ ix86_target_macros_internal (HOST_WIDE_INT isa_flag, ...@@ -459,6 +459,18 @@ ix86_target_macros_internal (HOST_WIDE_INT isa_flag,
def_or_undef (parse_in, "__RDPID__"); def_or_undef (parse_in, "__RDPID__");
if (isa_flag2 & OPTION_MASK_ISA_GFNI) if (isa_flag2 & OPTION_MASK_ISA_GFNI)
def_or_undef (parse_in, "__GFNI__"); def_or_undef (parse_in, "__GFNI__");
if (isa_flag2 & OPTION_MASK_ISA_IBT)
{
def_or_undef (parse_in, "__IBT__");
if (flag_cf_protection != CF_NONE)
def_or_undef (parse_in, "__CET__");
}
if (isa_flag2 & OPTION_MASK_ISA_SHSTK)
{
def_or_undef (parse_in, "__SHSTK__");
if (flag_cf_protection != CF_NONE)
def_or_undef (parse_in, "__CET__");
}
if (TARGET_IAMCU) if (TARGET_IAMCU)
{ {
def_or_undef (parse_in, "__iamcu"); def_or_undef (parse_in, "__iamcu");
......
...@@ -29,3 +29,5 @@ along with GCC; see the file COPYING3. If not see ...@@ -29,3 +29,5 @@ along with GCC; see the file COPYING3. If not see
/* Run the 64-bit STV pass before the CSE pass so that CONST0_RTX and /* Run the 64-bit STV pass before the CSE pass so that CONST0_RTX and
CONSTM1_RTX generated by the STV pass can be CSEed. */ CONSTM1_RTX generated by the STV pass can be CSEed. */
INSERT_PASS_BEFORE (pass_cse2, 1, pass_stv, true /* timode_p */); INSERT_PASS_BEFORE (pass_cse2, 1, pass_stv, true /* timode_p */);
INSERT_PASS_BEFORE (pass_shorten_branches, 1, pass_insert_endbranch);
...@@ -354,3 +354,4 @@ class rtl_opt_pass; ...@@ -354,3 +354,4 @@ class rtl_opt_pass;
extern rtl_opt_pass *make_pass_insert_vzeroupper (gcc::context *); extern rtl_opt_pass *make_pass_insert_vzeroupper (gcc::context *);
extern rtl_opt_pass *make_pass_stv (gcc::context *); extern rtl_opt_pass *make_pass_stv (gcc::context *);
extern rtl_opt_pass *make_pass_insert_endbranch (gcc::context *);
...@@ -169,6 +169,10 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see ...@@ -169,6 +169,10 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
#define TARGET_MWAITX_P(x) TARGET_ISA_MWAITX_P(x) #define TARGET_MWAITX_P(x) TARGET_ISA_MWAITX_P(x)
#define TARGET_PKU TARGET_ISA_PKU #define TARGET_PKU TARGET_ISA_PKU
#define TARGET_PKU_P(x) TARGET_ISA_PKU_P(x) #define TARGET_PKU_P(x) TARGET_ISA_PKU_P(x)
#define TARGET_IBT TARGET_ISA_IBT
#define TARGET_IBT_P(x) TARGET_ISA_IBT_P(x)
#define TARGET_SHSTK TARGET_ISA_SHSTK
#define TARGET_SHSTK_P(x) TARGET_ISA_SHSTK_P(x)
#define TARGET_LP64 TARGET_ABI_64 #define TARGET_LP64 TARGET_ABI_64
#define TARGET_LP64_P(x) TARGET_ABI_64_P(x) #define TARGET_LP64_P(x) TARGET_ABI_64_P(x)
......
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
;; ; -- print a semicolon (after prefixes due to bug in older gas). ;; ; -- print a semicolon (after prefixes due to bug in older gas).
;; ~ -- print "i" if TARGET_AVX2, "f" otherwise. ;; ~ -- print "i" if TARGET_AVX2, "f" otherwise.
;; ^ -- print addr32 prefix if TARGET_64BIT and Pmode != word_mode ;; ^ -- print addr32 prefix if TARGET_64BIT and Pmode != word_mode
;; ! -- print MPX prefix for jxx/call/ret instructions if required. ;; ! -- print MPX or NOTRACK prefix for jxx/call/ret instructions if required.
(define_c_enum "unspec" [ (define_c_enum "unspec" [
;; Relocation specifiers ;; Relocation specifiers
...@@ -274,6 +274,17 @@ ...@@ -274,6 +274,17 @@
;; For RDPID support ;; For RDPID support
UNSPECV_RDPID UNSPECV_RDPID
;; For CET support
UNSPECV_NOP_ENDBR
UNSPECV_NOP_RDSSP
UNSPECV_INCSSP
UNSPECV_SAVEPREVSSP
UNSPECV_RSTORSSP
UNSPECV_WRSS
UNSPECV_WRUSS
UNSPECV_SETSSBSY
UNSPECV_CLRSSBSY
]) ])
;; Constants to represent rounding modes in the ROUND instruction ;; Constants to represent rounding modes in the ROUND instruction
...@@ -18215,6 +18226,28 @@ ...@@ -18215,6 +18226,28 @@
"* return output_probe_stack_range (operands[0], operands[2]);" "* return output_probe_stack_range (operands[0], operands[2]);"
[(set_attr "type" "multi")]) [(set_attr "type" "multi")])
/* Additional processing for builtin_setjmp. Store the shadow stack pointer
as a forth element in jmpbuf. */
(define_expand "builtin_setjmp_setup"
[(match_operand 0 "address_operand")]
"TARGET_SHSTK"
{
if (flag_cf_protection & CF_RETURN)
{
rtx mem, reg_ssp;
mem = gen_rtx_MEM (Pmode, plus_constant (Pmode, operands[0],
3 * GET_MODE_SIZE (Pmode)));
reg_ssp = gen_reg_rtx (Pmode);
emit_insn (gen_rtx_SET (reg_ssp, const0_rtx));
emit_insn ((Pmode == SImode)
? gen_rdsspsi (reg_ssp, reg_ssp)
: gen_rdsspdi (reg_ssp, reg_ssp));
emit_move_insn (mem, reg_ssp);
}
DONE;
})
(define_expand "builtin_setjmp_receiver" (define_expand "builtin_setjmp_receiver"
[(label_ref (match_operand 0))] [(label_ref (match_operand 0))]
"!TARGET_64BIT && flag_pic" "!TARGET_64BIT && flag_pic"
...@@ -18235,6 +18268,83 @@ ...@@ -18235,6 +18268,83 @@
DONE; DONE;
}) })
(define_expand "builtin_longjmp"
[(match_operand 0 "address_operand")]
"TARGET_SHSTK"
{
rtx fp, lab, stack;
rtx jump, label, reg_adj, reg_ssp, reg_minus, mem_buf, tmp, clob;
machine_mode sa_mode = STACK_SAVEAREA_MODE (SAVE_NONLOCAL);
/* Adjust the shadow stack pointer (ssp) to the value saved in the
jmp_buf. The saving was done in the builtin_setjmp_setup. */
if (flag_cf_protection & CF_RETURN)
{
/* Get current shadow stack pointer. The code below will check if
SHSTK feature is enabled. If it's not enabled RDSSP instruction
is a NOP. */
reg_ssp = gen_reg_rtx (Pmode);
emit_insn (gen_rtx_SET (reg_ssp, const0_rtx));
emit_insn ((Pmode == SImode)
? gen_rdsspsi (reg_ssp, reg_ssp)
: gen_rdsspdi (reg_ssp, reg_ssp));
mem_buf = gen_rtx_MEM (Pmode, plus_constant (Pmode, operands[0],
3 * GET_MODE_SIZE (Pmode))),
/* Compare through substraction the saved and the current ssp to decide
if ssp has to be adjusted. */
reg_minus = gen_reg_rtx (Pmode);
tmp = gen_rtx_SET (reg_minus, gen_rtx_MINUS (Pmode, reg_ssp, mem_buf));
clob = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (CCmode, FLAGS_REG));
tmp = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, tmp, clob));
emit_insn (tmp);
/* Jump over adjustment code. */
label = gen_label_rtx ();
tmp = gen_rtx_REG (CCmode, FLAGS_REG);
tmp = gen_rtx_EQ (VOIDmode, tmp, const0_rtx);
tmp = gen_rtx_IF_THEN_ELSE (VOIDmode, tmp,
gen_rtx_LABEL_REF (VOIDmode, label),
pc_rtx);
jump = emit_jump_insn (gen_rtx_SET (pc_rtx, tmp));
JUMP_LABEL (jump) = label;
/* Adjust the ssp. */
reg_adj = gen_reg_rtx (Pmode);
tmp = gen_rtx_SET (reg_adj,
gen_rtx_LSHIFTRT (Pmode, negate_rtx (Pmode, reg_minus),
GEN_INT (3)));
clob = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (CCmode, FLAGS_REG));
tmp = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, tmp, clob));
emit_insn (tmp);
emit_insn ((Pmode == SImode)
? gen_incsspsi (reg_adj)
: gen_incsspdi (reg_adj));
emit_label (label);
LABEL_NUSES (label) = 1;
}
/* This code is the same as in expand_buildin_longjmp. */
fp = gen_rtx_MEM (Pmode, operands[0]);
lab = gen_rtx_MEM (Pmode, plus_constant (Pmode, operands[0],
GET_MODE_SIZE (Pmode)));
stack = gen_rtx_MEM (sa_mode, plus_constant (Pmode, operands[0],
2 * GET_MODE_SIZE (Pmode)));
lab = copy_to_reg (lab);
emit_clobber (gen_rtx_MEM (BLKmode, gen_rtx_SCRATCH (VOIDmode)));
emit_clobber (gen_rtx_MEM (BLKmode, hard_frame_pointer_rtx));
emit_move_insn (hard_frame_pointer_rtx, fp);
emit_stack_restore (SAVE_NONLOCAL, stack);
emit_use (hard_frame_pointer_rtx);
emit_use (stack_pointer_rtx);
emit_indirect_jump (lab);
})
;; Avoid redundant prefixes by splitting HImode arithmetic to SImode. ;; Avoid redundant prefixes by splitting HImode arithmetic to SImode.
;; Do not split instructions with mask registers. ;; Do not split instructions with mask registers.
(define_split (define_split
...@@ -19855,6 +19965,83 @@ ...@@ -19855,6 +19965,83 @@
[(set_attr "length" "2") [(set_attr "length" "2")
(set_attr "memory" "unknown")]) (set_attr "memory" "unknown")])
;; CET instructions
(define_insn "rdssp<mode>"
[(set (match_operand:SWI48x 0 "register_operand" "=r")
(unspec_volatile:SWI48x
[(match_operand:SWI48x 1 "register_operand" "0")]
UNSPECV_NOP_RDSSP))]
"TARGET_SHSTK"
"rdssp<mskmodesuffix>\t%0"
[(set_attr "length" "4")
(set_attr "type" "other")])
(define_insn "incssp<mode>"
[(unspec_volatile [(match_operand:SWI48x 0 "register_operand" "r")]
UNSPECV_INCSSP)]
"TARGET_SHSTK"
"incssp<mskmodesuffix>\t%0"
[(set_attr "length" "4")
(set_attr "type" "other")])
(define_insn "saveprevssp"
[(unspec_volatile [(const_int 0)] UNSPECV_SAVEPREVSSP)]
"TARGET_SHSTK"
"saveprevssp"
[(set_attr "length" "5")
(set_attr "type" "other")])
(define_insn "rstorssp"
[(unspec_volatile [(match_operand 0 "memory_operand" "m")]
UNSPECV_RSTORSSP)]
"TARGET_SHSTK"
"rstorssp\t%0"
[(set_attr "length" "5")
(set_attr "type" "other")])
(define_insn "wrss<mode>"
[(unspec_volatile [(match_operand:SWI48x 0 "register_operand" "r")
(match_operand:SWI48x 1 "memory_operand" "m")]
UNSPECV_WRSS)]
"TARGET_SHSTK"
"wrss<mskmodesuffix>\t%0, %1"
[(set_attr "length" "3")
(set_attr "type" "other")])
(define_insn "wruss<mode>"
[(unspec_volatile [(match_operand:SWI48x 0 "register_operand" "r")
(match_operand:SWI48x 1 "memory_operand" "m")]
UNSPECV_WRUSS)]
"TARGET_SHSTK"
"wruss<mskmodesuffix>\t%0, %1"
[(set_attr "length" "4")
(set_attr "type" "other")])
(define_insn "setssbsy"
[(unspec_volatile [(const_int 0)] UNSPECV_SETSSBSY)]
"TARGET_SHSTK"
"setssbsy"
[(set_attr "length" "4")
(set_attr "type" "other")])
(define_insn "clrssbsy"
[(unspec_volatile [(match_operand 0 "memory_operand" "m")]
UNSPECV_CLRSSBSY)]
"TARGET_SHSTK"
"clrssbsy\t%0"
[(set_attr "length" "4")
(set_attr "type" "other")])
(define_insn "nop_endbr"
[(unspec_volatile [(const_int 0)] UNSPECV_NOP_ENDBR)]
"TARGET_IBT"
"*
{ return (TARGET_64BIT)? \"endbr64\" : \"endbr32\"; }"
[(set_attr "length" "4")
(set_attr "length_immediate" "0")
(set_attr "modrm" "0")])
;; For RTM support
(define_expand "xbegin" (define_expand "xbegin"
[(set (match_operand:SI 0 "register_operand") [(set (match_operand:SI 0 "register_operand")
(unspec_volatile:SI [(const_int 0)] UNSPECV_XBEGIN))] (unspec_volatile:SI [(const_int 0)] UNSPECV_XBEGIN))]
......
...@@ -957,3 +957,23 @@ Attempt to avoid generating instruction sequences containing ret bytes. ...@@ -957,3 +957,23 @@ Attempt to avoid generating instruction sequences containing ret bytes.
mgeneral-regs-only mgeneral-regs-only
Target Report RejectNegative Mask(GENERAL_REGS_ONLY) Var(ix86_target_flags) Save Target Report RejectNegative Mask(GENERAL_REGS_ONLY) Var(ix86_target_flags) Save
Generate code which uses only the general registers. Generate code which uses only the general registers.
mcet
Target Report Var(flag_cet) Init(0)
Support Control-flow Enforcment Technology (CET) built-in functions
and code generation.
mibt
Target Report Mask(ISA_IBT) Var(ix86_isa_flags2) Save
Specifically enables an indirect branch tracking feature from Control-flow
Enforcment Technology (CET).
mshstk
Target Report Mask(ISA_SHSTK) Var(ix86_isa_flags2) Save
Specifically enables an shadow stack support feature from Control-flow
Enforcment Technology (CET).
mcet-switch
Target Report Undocumented Var(flag_cet_switch) Init(0)
Turn on CET instrumentation for switch statements, which use jump table and
indirect jump.
...@@ -90,6 +90,8 @@ ...@@ -90,6 +90,8 @@
#include <xtestintrin.h> #include <xtestintrin.h>
#include <cetintrin.h>
#ifndef __RDRND__ #ifndef __RDRND__
#pragma GCC push_options #pragma GCC push_options
#pragma GCC target("rdrnd") #pragma GCC target("rdrnd")
......
...@@ -121,3 +121,8 @@ along with GCC; see the file COPYING3. If not see ...@@ -121,3 +121,8 @@ along with GCC; see the file COPYING3. If not see
#define CHKP_SPEC "\ #define CHKP_SPEC "\
%{!nostdlib:%{!nodefaultlibs:" LIBMPX_SPEC LIBMPXWRAPPERS_SPEC "}}" MPX_SPEC %{!nostdlib:%{!nodefaultlibs:" LIBMPX_SPEC LIBMPXWRAPPERS_SPEC "}}" MPX_SPEC
#endif #endif
extern void file_end_indicate_exec_stack_and_cet (void);
#undef TARGET_ASM_FILE_END
#define TARGET_ASM_FILE_END file_end_indicate_exec_stack_and_cet
# Copyright (C) 2017 Free Software Foundation, Inc.
#
# This file is part of GCC.
#
# GCC is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GCC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3. If not see
# <http://www.gnu.org/licenses/>.
cet.o: $(srcdir)/config/i386/cet.c
$(COMPILE) $<
$(POSTCOMPILE)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment