On 29/01/16 17:07, Andre Vieira (lists) wrote:
> On 26/12/15 01:54, Thomas Preud'homme wrote:
>> [Sending on behalf of Andre Vieira]
>>
>> Hello,
>>
>> This patch extends support for the ARMv8-M Security Extensions
>> 'cmse_nonsecure_entry' attribute to safeguard against leak of
>> information through unbanked registers.
>>
>> When returning from a nonsecure entry function we clear all
>> caller-saved registers that are not used to pass return values, by
>> writing either the LR, in case of general purpose registers, or the
>> value 0, in case of FP registers. We use the LR to write to APSR and
>> FPSCR too. We currently only support 32 FP registers as in we only
>> clear D0-D7.
>> We currently do not support entry functions that pass arguments or
>> return variables on the stack and we diagnose this. This patch relies
>> on the existing code to make sure callee-saved registers used in
>> cmse_nonsecure_entry functions are saved and restored thus retaining
>> their nonsecure mode value, this should be happening already as it is
>> required by AAPCS.
>>
>>
>> *** gcc/ChangeLog ***
>> 2015-10-27 Andre Vieira <[email protected]>
>> Thomas Preud'homme <[email protected]>
>>
>> * gcc/config/arm/arm.c (output_return_instruction): Clear
>> registers.
>> (thumb2_expand_return): Likewise.
>> (thumb1_expand_epilogue): Likewise.
>> (arm_expand_epilogue): Likewise.
>> (cmse_nonsecure_entry_clear_before_return): New.
>> * gcc/config/arm/arm.h (TARGET_DSP_ADD): New macro define.
>> * gcc/config/arm/thumb1.md (*epilogue_insns): Change length
>> attribute.
>> * gcc/config/arm/thumb2.md (*thumb2_return): Likewise.
>>
>> *** gcc/testsuite/ChangeLog ***
>> 2015-10-27 Andre Vieira <[email protected]>
>> Thomas Preud'homme <[email protected]>
>>
>> * gcc.target/arm/cmse/cmse.exp: Test different multilibs
>> separate.
>> * gcc.target/arm/cmse/baseline/cmse-2.c: Test that registers
>> are cleared.
>> * gcc.target/arm/cmse/mainline/soft/cmse-5.c: New.
>> * gcc.target/arm/cmse/mainline/hard/cmse-5.c: New.
>> * gcc.target/arm/cmse/mainline/hard-sp/cmse-5.c: New.
>> * gcc.target/arm/cmse/mainline/softfp/cmse-5.c: New.
>> * gcc.target/arm/cmse/mainline/softfp-sp/cmse-5.c: New.
>>
>>
>> diff --git a/gcc/config/arm/arm.h b/gcc/config/arm/arm.h
>> index
>> f12e3c93bbe24b10ed8eee6687161826773ef649..b06e0586a3da50f57645bda13629bc4dbd3d53b7
>> 100644
>> --- a/gcc/config/arm/arm.h
>> +++ b/gcc/config/arm/arm.h
>> @@ -230,6 +230,9 @@ extern void
>> (*arm_lang_output_object_attributes_hook)(void);
>> /* Integer SIMD instructions, and extend-accumulate instructions. */
>> #define TARGET_INT_SIMD \
>> (TARGET_32BIT && arm_arch6 && (arm_arch_notm || arm_arch7em))
>> +/* Parallel addition and subtraction instructions. */
>> +#define TARGET_DSP_ADD \
>> + (TARGET_ARM_ARCH >= 6 && (arm_arch_notm || arm_arch7em))
>>
>> /* Should MOVW/MOVT be used in preference to a constant pool. */
>> #define TARGET_USE_MOVT \
>> diff --git a/gcc/config/arm/arm.c b/gcc/config/arm/arm.c
>> index
>> e530b772e3cc053c16421a2a2861d815d53ebb01..0700478ca38307f35d0cb01f83ea182802ba28fa
>> 100644
>> --- a/gcc/config/arm/arm.c
>> +++ b/gcc/config/arm/arm.c
>> @@ -19755,6 +19755,24 @@ output_return_instruction (rtx operand, bool
>> really_return, bool reverse,
>> default:
>> if (IS_CMSE_ENTRY (func_type))
>> {
>> + char flags[12] = "APSR_nzcvq";
>> + /* Check if we have to clear the 'GE bits' which is only
>> used if
>> + parallel add and subtraction instructions are available. */
>> + if (TARGET_DSP_ADD)
>> + {
>> + /* If so also clear the ge flags. */
>> + flags[10] = 'g';
>> + flags[11] = '\0';
>> + }
>> + snprintf (instr, sizeof (instr), "msr%s\t%s, %%|lr",
>> conditional,
>> + flags);
>> + output_asm_insn (instr, & operand);
>> + if (TARGET_HARD_FLOAT && TARGET_VFP)
>> + {
>> + snprintf (instr, sizeof (instr), "vmsr%s\tfpscr, %%|lr",
>> + conditional);
>> + output_asm_insn (instr, & operand);
>> + }
>> snprintf (instr, sizeof (instr), "bxns%s\t%%|lr",
>> conditional);
>> }
>> /* Use bx if it's available. */
>> @@ -23999,6 +24017,17 @@ thumb_pop (FILE *f, unsigned long mask)
>> static void
>> thumb1_cmse_nonsecure_entry_return (FILE *f, int
>> reg_containing_return_addr)
>> {
>> + char flags[12] = "APSR_nzcvq";
>> + /* Check if we have to clear the 'GE bits' which is only used if
>> + parallel add and subtraction instructions are available. */
>> + if (TARGET_DSP_ADD)
>> + {
>> + flags[10] = 'g';
>> + flags[11] = '\0';
>> + }
>> + asm_fprintf (f, "\tmsr\t%s, %r\n", flags, reg_containing_return_addr);
>> + if (TARGET_HARD_FLOAT && TARGET_VFP)
>> + asm_fprintf (f, "\tvmsr\tfpscr, %r\n", reg_containing_return_addr);
>> asm_fprintf (f, "\tbxns\t%r\n", reg_containing_return_addr);
>> }
>>
>> @@ -25140,6 +25169,139 @@ thumb1_expand_prologue (void)
>> cfun->machine->lr_save_eliminated = 0;
>> }
>>
>> +/* Clear caller saved registers not used to pass return values and
>> leaked
>> + condition flags before exiting a cmse_nonsecure_entry function. */
>> +
>> +void
>> +cmse_nonsecure_entry_clear_before_return (void)
>> +{
>> + uint64_t to_clear_mask;
>> + int regno, maxregno = IP_REGNUM;
>> + tree result_type;
>> + rtx result_rtl;
>> +
>> + to_clear_mask = (1LL << (NUM_ARG_REGS)) - 1;
>> + to_clear_mask |= (1LL << IP_REGNUM);
>> + /* If we are not dealing with -mfloat-abi=softfp we will need to
>> clear VFP
>> + registers. We also check TARGET_VFP to make sure these are
>> present. */
>> + if (TARGET_HARD_FLOAT && TARGET_VFP)
>> + {
>> + uint64_t float_mask = (1LL << (D7_VFP_REGNUM + 1)) - 1;
>> + float_mask &= ~((1LL << FIRST_VFP_REGNUM) - 1);
>> + to_clear_mask |= float_mask;
>> + maxregno = LAST_VFP_REGNUM;
>> + }
>> +
>> + /* If the user has defined registers to be caller saved, these are
>> no longer
>> + restored by the function before returning and must thus be
>> cleared for
>> + security purposes. */
>> + for (regno = NUM_ARG_REGS; regno < LAST_VFP_REGNUM; regno++)
>> + {
>> + /* We leave registers traditionally used to pass arguments
>> untouched,
>> + since if these should not be made callee-saved by the user. */
>> + if (regno >= FIRST_VFP_REGNUM && regno <= D7_VFP_REGNUM)
>> + continue;
>> + if (regno >= IP_REGNUM && regno <= PC_REGNUM)
>> + continue;
>> + if (call_used_regs[regno])
>> + to_clear_mask |= (1LL << regno);
>> + }
>> +
>> + /* Make sure we do not clear the registers used to pass the result
>> in. */
>> + result_type = TREE_TYPE (DECL_RESULT (current_function_decl));
>> + if (!VOID_TYPE_P (result_type))
>> + {
>> + rtx reg;
>> +
>> + result_rtl = arm_function_value (result_type,
>> current_function_decl, 0);
>> +
>> + /* No need to check that we return in registers, because we don't
>> + support returning on stack yet. */
>> + switch (GET_MODE (result_rtl))
>> + {
>> + case BLKmode:
>> + /* We are dealing with a return in multiple VFP registers. */
>> + {
>> + int i;
>> + /* This should really only occur when dealing with an hard float
>> + abi. */
>> + gcc_assert (TARGET_HARD_FLOAT_ABI && TARGET_VFP);
>> +
>> + for (i = 0; i < XVECLEN (result_rtl, 0); i++)
>> + {
>> + reg = XEXP (XVECEXP (result_rtl, 0, i), 0);
>> + gcc_assert (REG_P (reg));
>> + /* If we are dealing with DF mode, make sure you don't clear
>> + either registers it addresses. */
>> + if (GET_MODE (reg) == DFmode)
>> + to_clear_mask &= ~(1LL << (REGNO (reg) + 1));
>> + to_clear_mask &= ~(1LL << REGNO (reg));
>> + }
>> + }
>> + break;
>> + case DImode:
>> + to_clear_mask &= ~2; /* Don't clear r0 and r1. */
>> + case SImode:
>> + to_clear_mask &= ~1; /* Don't clear r0. */
>> + break;
>> + case DFmode:
>> + /* If we have -mfloat-abi=hard, do not clear registers used to
>> pass
>> + return value. */
>> + if (TARGET_HARD_FLOAT_ABI && TARGET_VFP)
>> + /* Don't clear s0 and s1. */
>> + to_clear_mask &= ~(1LL << (FIRST_VFP_REGNUM + 1));
>> + else
>> + to_clear_mask &= ~2; /* Don't clear r0, r1. */
>> + case SFmode:
>> + /* If we have -mfloat-abi=hard, do not clear registers used to
>> pass
>> + return value. */
>> + if (TARGET_HARD_FLOAT_ABI && TARGET_VFP)
>> + /* Don't clear s0. */
>> + to_clear_mask &= ~(1LL << FIRST_VFP_REGNUM);
>> + else
>> + to_clear_mask &= ~1; /* Don't clear r0. */
>> + break;
>> + default:
>> + /* We are missing a mode, though AAPCS says we may only return in
>> + r0 and r1, s0-d15/d0-d7 so something went wrong here. */
>> + gcc_unreachable ();
>> + break;
>> + }
>> + }
>> +
>> + for (regno = 0; regno <= maxregno; regno++)
>> + {
>> + if (!(to_clear_mask & (1LL << regno)))
>> + continue;
>> +
>> + /* If regno is a vfp register, even and its successor is also to
>> + be cleared, use vmov. */
>> + if (IS_VFP_REGNUM (regno))
>> + {
>> + if (TARGET_VFP_DOUBLE
>> + && VFP_REGNO_OK_FOR_DOUBLE (regno)
>> + && to_clear_mask & (1LL << (regno + 1)))
>> + {
>> + emit_move_insn (gen_rtx_REG (DFmode, regno++),
>> + CONST1_RTX (DFmode));
>> + emit_use (gen_rtx_REG (DFmode, regno));
>> + }
>> + else
>> + {
>> + emit_move_insn (gen_rtx_REG (SFmode, regno),
>> + CONST1_RTX (SFmode));
>> + emit_use (gen_rtx_REG (SFmode, regno));
>> + }
>> + }
>> + else
>> + {
>> + emit_move_insn (gen_rtx_REG (SImode, regno),
>> + gen_rtx_REG (SImode, LR_REGNUM));
>> + emit_use (gen_rtx_REG (SImode, regno));
>> + }
>> + }
>> +}
>> +
>> /* Generate pattern *pop_multiple_with_stack_update_and_return if
>> single
>> POP instruction can be generated. LR should be replaced by PC. All
>> the checks required are already done by USE_RETURN_INSN (). Hence,
>> @@ -25189,6 +25351,8 @@ thumb2_expand_return (bool simple_return)
>> }
>> else
>> {
>> + if (IS_CMSE_ENTRY (arm_current_func_type ()))
>> + cmse_nonsecure_entry_clear_before_return ();
>> emit_jump_insn (simple_return_rtx);
>> }
>> }
>> @@ -25247,6 +25411,10 @@ thumb1_expand_epilogue (void)
>>
>> if (! df_regs_ever_live_p (LR_REGNUM))
>> emit_use (gen_rtx_REG (SImode, LR_REGNUM));
>> +
>> + /* Clear all caller-saved regs that are not used to return. */
>> + if (IS_CMSE_ENTRY (arm_current_func_type ()))
>> + cmse_nonsecure_entry_clear_before_return ();
>> }
>>
>> /* Epilogue code for APCS frame. */
>> @@ -25681,6 +25849,14 @@ arm_expand_epilogue (bool really_return)
>> stack_pointer_rtx, stack_pointer_rtx);
>> }
>>
>> + /* Clear all caller-saved regs that are not used to return. */
>> + if (IS_CMSE_ENTRY (arm_current_func_type ()))
>> + {
>> + /* CMSE_ENTRY always returns! */
>> + gcc_assert (really_return);
>> + cmse_nonsecure_entry_clear_before_return ();
>> + }
>> +
>> if (!really_return)
>> return;
>>
>> diff --git a/gcc/config/arm/thumb1.md b/gcc/config/arm/thumb1.md
>> index
>> d2a0420a2e9c71bb954d134fb88a86d694cf786d..cdcf397b0cab0ea7f246ad830ad28b07fed71d8b
>> 100644
>> --- a/gcc/config/arm/thumb1.md
>> +++ b/gcc/config/arm/thumb1.md
>> @@ -1757,8 +1757,15 @@
>> "*
>> return thumb1_unexpanded_epilogue ();
>> "
>> - ; Length is absolute worst case
>> - [(set_attr "length" "44")
>> + ; Length is absolute worst case, when using CMSE and if this is an
>> entry
>> + ; function an extra 4 (msr) to 8 (vmsr) extra might be added.
>> + [(set (attr "length")
>> + (if_then_else
>> + (match_test "IS_CMSE_ENTRY (arm_current_func_type ())")
>> + (if_then_else (match_test "TARGET_HARD_FLOAT && TARGET_VFP")
>> + (const_int 52)
>> + (const_int 48))
>> + (const_int 44)))
>> (set_attr "type" "block")
>> ;; We don't clobber the conditions, but the potential length of this
>> ;; operation is sufficient to make conditionalizing the sequence
>> diff --git a/gcc/config/arm/thumb2.md b/gcc/config/arm/thumb2.md
>> index
>> a724752a39cf2862c68a53ef28a239de2ae1ed96..0c07df91fe8844a7e7437ef5b7f00f74815d63f4
>> 100644
>> --- a/gcc/config/arm/thumb2.md
>> +++ b/gcc/config/arm/thumb2.md
>> @@ -1106,7 +1106,15 @@
>> "TARGET_THUMB2"
>> "* return output_return_instruction (const_true_rtx, true, false,
>> true);"
>> [(set_attr "type" "branch")
>> - (set_attr "length" "4")]
>> + ; If this is a return from a cmse_nonsecure_entry function then
>> code will be
>> + ; added to clear the APSR and potentially the FPSCR if VFP is
>> available, so
>> + ; we adapt the length accordingly.
>> + (set (attr "length")
>> + (if_then_else (match_test "IS_CMSE_ENTRY (arm_current_func_type
>> ())")
>> + (if_then_else (match_test "TARGET_HARD_FLOAT && TARGET_VFP")
>> + (const_int 12)
>> + (const_int 8))
>> + (const_int 4)))]
>> )
>>
>> (define_insn_and_split "thumb2_eh_return"
>> diff --git a/gcc/testsuite/gcc.target/arm/cmse/baseline/cmse-2.c
>> b/gcc/testsuite/gcc.target/arm/cmse/baseline/cmse-2.c
>> new file mode 100644
>> index
>> 0000000000000000000000000000000000000000..7cef73372fd5f5be080bdfe30999694564deaa9a
>>
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/arm/cmse/baseline/cmse-2.c
>> @@ -0,0 +1,18 @@
>> +/* { dg-do compile } */
>> +/* { dg-require-effective-target arm_arch_v8m_base_ok } */
>> +/* { dg-add-options arm_arch_v8m_base } */
>> +/* { dg-options "-mcmse" } */
>> +
>> +extern float bar (void);
>> +
>> +float __attribute__ ((cmse_nonsecure_entry))
>> +foo (void)
>> +{
>> + return bar ();
>> +}
>> +/* { dg-final { scan-assembler "mov\tr1, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr2, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr3, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tip, lr" } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, r1" } } */
>> +/* { dg-final { scan-assembler "bxns\tr1" } } */
>> diff --git a/gcc/testsuite/gcc.target/arm/cmse/cmse.exp
>> b/gcc/testsuite/gcc.target/arm/cmse/cmse.exp
>> index
>> 6d4bfec1d12bc575218e0b8c33559d0a4da9ec70..592349ad07257f8074f9443a599ae08c36136a80
>> 100644
>> --- a/gcc/testsuite/gcc.target/arm/cmse/cmse.exp
>> +++ b/gcc/testsuite/gcc.target/arm/cmse/cmse.exp
>> @@ -38,6 +38,26 @@ set LTO_TORTURE_OPTIONS ""
>> gcc-dg-runtest [lsort [glob $srcdir/$subdir/*.c]] \
>> "" $DEFAULT_CFLAGS
>>
>> +if {[check_effective_target_arm_arch_v8m_base_ok]} then {
>> + # Baseline only
>> + gcc-dg-runtest [lsort [glob $srcdir/$subdir/baseline/*.c]] \
>> + "" $DEFAULT_CFLAGS
>> +}
>> +
>> +if {[check_effective_target_arm_arch_v8m_main_ok]} then {
>> + # Mainline -mfloat-abi=soft
>> + gcc-dg-runtest [lsort [glob $srcdir/$subdir/mainline/soft/*.c]] \
>> + "-mfloat-abi=soft" $DEFAULT_CFLAGS
>> + gcc-dg-runtest [lsort [glob $srcdir/$subdir/mainline/softfp/*.c]] \
>> + "" $DEFAULT_CFLAGS
>> + gcc-dg-runtest [lsort [glob
>> $srcdir/$subdir/mainline/softfp-sp/*.c]] \
>> + "" $DEFAULT_CFLAGS
>> + gcc-dg-runtest [lsort [glob $srcdir/$subdir/mainline/hard/*.c]] \
>> + "" $DEFAULT_CFLAGS
>> + gcc-dg-runtest [lsort [glob $srcdir/$subdir/mainline/hard-sp/*.c]] \
>> + "" $DEFAULT_CFLAGS
>> +}
>> +
>> set LTO_TORTURE_OPTIONS ${saved-lto_torture_options}
>> set dg-do-what-default ${saved-dg-do-what-default}
>>
>> diff --git
>> a/gcc/testsuite/gcc.target/arm/cmse/mainline/hard-sp/cmse-5.c
>> b/gcc/testsuite/gcc.target/arm/cmse/mainline/hard-sp/cmse-5.c
>> new file mode 100644
>> index
>> 0000000000000000000000000000000000000000..ab607aa1b7210acc7983bfb401e95117eb8c0d17
>>
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/hard-sp/cmse-5.c
>> @@ -0,0 +1,37 @@
>> +/* { dg-do compile } */
>> +/* { dg-require-effective-target arm_arch_v8m_main_ok } */
>> +/* { dg-add-options arm_arch_v8m_main } */
>> +/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp"
>> {*-*-*} {"-mfloat-abi=soft" -mfloat-abi=softfp } {""} } */
>> +/* { dg-skip-if "Skip these if testing double precision" {*-*-*}
>> {"-mfpu=fpv[4-5]-d16"} {""} } */
>> +/* { dg-options "-mcmse -mfloat-abi=hard -mfpu=fpv5-sp-d16" } */
>> +
>> +extern float bar (void);
>> +
>> +float __attribute__ ((cmse_nonsecure_entry))
>> +foo (void)
>> +{
>> + return bar ();
>> +}
>> +/* { dg-final { scan-assembler "mov\tr0, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr1, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr2, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr3, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tip, lr" } } */
>> +/* { dg-final { scan-assembler-not "vmov\.f32\ts0, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts1, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts2, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts3, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts4, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts5, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts6, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts7, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts8, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts9, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts10, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts11, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts12, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts13, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts14, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts15, #1\.0" } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
>> arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
>> arm_arch_v8m_main_ok && arm_dsp } } } } */
>> diff --git a/gcc/testsuite/gcc.target/arm/cmse/mainline/hard/cmse-5.c
>> b/gcc/testsuite/gcc.target/arm/cmse/mainline/hard/cmse-5.c
>> new file mode 100644
>> index
>> 0000000000000000000000000000000000000000..5ed7e1d6404b8f17d630895eb06df5921a1a965f
>>
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/hard/cmse-5.c
>> @@ -0,0 +1,30 @@
>> +/* { dg-do compile } */
>> +/* { dg-require-effective-target arm_arch_v8m_main_ok } */
>> +/* { dg-add-options arm_arch_v8m_main } */
>> +/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp"
>> {*-*-*} {"-mfloat-abi=soft" -mfloat-abi=softfp } {""} } */
>> +/* { dg-skip-if "Skip these if testing single precision" {*-*-*}
>> {"-mfpu=*-sp-*"} {""} } */
>> +/* { dg-options "-mcmse -mfloat-abi=hard -mfpu=fpv5-d16" } */
>> +
>> +extern float bar (void);
>> +
>> +float __attribute__ ((cmse_nonsecure_entry))
>> +foo (void)
>> +{
>> + return bar ();
>> +}
>> +/* { dg-final { scan-assembler "mov\tr0, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr1, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr2, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr3, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tip, lr" } } */
>> +/* { dg-final { scan-assembler-not "vmov\.f32\ts0, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts1, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td1, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td2, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td3, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td4, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td5, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td6, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td7, #1\.0" } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
>> arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
>> arm_arch_v8m_main_ok && arm_dsp } } } } */
>> diff --git a/gcc/testsuite/gcc.target/arm/cmse/mainline/soft/cmse-5.c
>> b/gcc/testsuite/gcc.target/arm/cmse/mainline/soft/cmse-5.c
>> new file mode 100644
>> index
>> 0000000000000000000000000000000000000000..d24683f52eba11df5b41eef0e5a8e365fb206fe8
>>
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/soft/cmse-5.c
>> @@ -0,0 +1,22 @@
>> +/* { dg-do compile } */
>> +/* { dg-require-effective-target arm_arch_v8m_main_ok } */
>> +/* { dg-add-options arm_arch_v8m_main } */
>> +/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp"
>> {*-*-*} {"-mfloat-abi=hard" -mfloat-abi=softfp } {""} } */
>> +/* { dg-options "-mcmse -mfloat-abi=soft" } */
>> +
>> +extern float bar (void);
>> +
>> +float __attribute__ ((cmse_nonsecure_entry))
>> +foo (void)
>> +{
>> + return bar ();
>> +}
>> +
>> +/* { dg-final { scan-assembler "mov\tr1, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr2, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr3, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tip, lr" } } */
>> +/* { dg-final { scan-assembler-not "vmov" } } */
>> +/* { dg-final { scan-assembler-not "vmsr" } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
>> arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
>> arm_arch_v8m_main_ok && arm_dsp } } } } */
>> diff --git
>> a/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp-sp/cmse-5.c
>> b/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp-sp/cmse-5.c
>> new file mode 100644
>> index
>> 0000000000000000000000000000000000000000..c22775ace8361729c36130422475522fbaca05b5
>>
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp-sp/cmse-5.c
>> @@ -0,0 +1,39 @@
>> +/* { dg-do compile } */
>> +/* { dg-require-effective-target arm_arch_v8m_main_ok } */
>> +/* { dg-add-options arm_arch_v8m_main } */
>> +/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp"
>> {*-*-*} {"-mfloat-abi=soft" -mfloat-abi=hard } {""} } */
>> +/* { dg-skip-if "Skip these if testing double precision" {*-*-*}
>> {"-mfpu=fpv[4-5]-d16"} {""} } */
>> +/* { dg-options "-mcmse -mfloat-abi=softfp -mfpu=fpv5-sp-d16" } */
>> +
>> +extern float bar (void);
>> +
>> +float __attribute__ ((cmse_nonsecure_entry))
>> +foo (void)
>> +{
>> + return bar ();
>> +}
>> +/* { dg-final { scan-assembler "__acle_se_foo:" } } */
>> +/* { dg-final { scan-assembler-not "mov\tr0, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr1, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr2, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr3, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tip, lr" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts0, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts1, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts2, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts3, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts4, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts5, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts6, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts7, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts8, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts9, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts10, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts11, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts12, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts13, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts14, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f32\ts15, #1\.0" } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
>> arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
>> arm_arch_v8m_main_ok && arm_dsp } } } } */
>> +/* { dg-final { scan-assembler "bxns" } } */
>> diff --git
>> a/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp/cmse-5.c
>> b/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp/cmse-5.c
>> new file mode 100644
>> index
>> 0000000000000000000000000000000000000000..28b05c391081bd42407b4e56a1e84daa549fa06e
>>
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp/cmse-5.c
>> @@ -0,0 +1,31 @@
>> +/* { dg-do compile } */
>> +/* { dg-require-effective-target arm_arch_v8m_main_ok } */
>> +/* { dg-add-options arm_arch_v8m_main } */
>> +/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp"
>> {*-*-*} {"-mfloat-abi=soft" -mfloat-abi=hard } {""} } */
>> +/* { dg-skip-if "Skip these if testing single precision" {*-*-*}
>> {"-mfpu=*-sp-*"} {""} } */
>> +/* { dg-options "-mcmse -mfloat-abi=softfp -mfpu=fpv5-d16" } */
>> +
>> +extern float bar (void);
>> +
>> +float __attribute__ ((cmse_nonsecure_entry))
>> +foo (void)
>> +{
>> + return bar ();
>> +}
>> +/* { dg-final { scan-assembler "__acle_se_foo:" } } */
>> +/* { dg-final { scan-assembler-not "mov\tr0, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr1, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr2, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tr3, lr" } } */
>> +/* { dg-final { scan-assembler "mov\tip, lr" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td0, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td1, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td2, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td3, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td4, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td5, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td6, #1\.0" } } */
>> +/* { dg-final { scan-assembler "vmov\.f64\td7, #1\.0" } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
>> arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
>> +/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
>> arm_arch_v8m_main_ok && arm_dsp } } } } */
>> +/* { dg-final { scan-assembler "bxns" } } */
>>
>> We welcome any comments.
>>
>> Cheers,
>>
>> Andre
>>
> Ping.
>
Hi there,
This is an updated version of the previous patch to clear the unused
bits when returning a compound type. As per the ABI these have undefined
values and thus may leak security sensitive information.
Any comments?
Cheers,
Andre
*** ChangeLog ***
2016-03-29 Andre Vieira <[email protected]>
Thomas Preud'homme <[email protected]>
* gcc/config/arm/arm.c (output_return_instruction): Clear
registers.
(thumb2_expand_return): Likewise.
(thumb1_expand_epilogue): Likewise.
(arm_expand_epilogue): Likewise.
(cmse_nonsecure_entry_clear_before_return): New.
* gcc/config/arm/thumb1.md (*epilogue_insns): Change length
attribute.
* gcc/config/arm/thumb2.md (*thumb2_return): Likewise.
*** gcc/testsuite/ChangeLog ***
2016-03-29 Andre Vieira <[email protected]>
Thomas Preud'homme <[email protected]>
* gcc.target/arm/cmse/cmse.exp: Test different multilibs separate.
* gcc.target/arm/cmse/struct-1.c: New.
* gcc.target/arm/cmse/bitfield-1.c: New.
* gcc.target/arm/cmse/bitfield-2.c: New.
* gcc.target/arm/cmse/bitfield-3.c: New.
* gcc.target/arm/cmse/baseline/cmse-2.c: Test that registers are
cleared.
* gcc.target/arm/cmse/mainline/soft/cmse-5.c: New.
* gcc.target/arm/cmse/mainline/hard/cmse-5.c: New.
* gcc.target/arm/cmse/mainline/hard-sp/cmse-5.c: New.
* gcc.target/arm/cmse/mainline/softfp/cmse-5.c: New.
* gcc.target/arm/cmse/mainline/softfp-sp/cmse-5.c: New.
diff --git a/gcc/config/arm/arm.c b/gcc/config/arm/arm.c
index
40e6a786b933b09dbfd89b64449acb0356c66f24..7e250a489e0cf3b6588ae3f816ad77233f9be370
100644
--- a/gcc/config/arm/arm.c
+++ b/gcc/config/arm/arm.c
@@ -19808,6 +19808,23 @@ output_return_instruction (rtx operand, bool
really_return, bool reverse,
default:
if (IS_CMSE_ENTRY (func_type))
{
+ char flags[12] = "APSR_nzcvq";
+ /* Check if we have to clear the 'GE bits' which is only used if
+ parallel add and subtraction instructions are available. */
+ if (TARGET_INT_SIMD)
+ {
+ flags[10] = 'g';
+ flags[11] = '\0';
+ }
+ snprintf (instr, sizeof (instr), "msr%s\t%s, %%|lr", conditional,
+ flags);
+ output_asm_insn (instr, & operand);
+ if (TARGET_HARD_FLOAT && TARGET_VFP)
+ {
+ snprintf (instr, sizeof (instr), "vmsr%s\tfpscr, %%|lr",
+ conditional);
+ output_asm_insn (instr, & operand);
+ }
snprintf (instr, sizeof (instr), "bxns%s\t%%|lr", conditional);
}
/* Use bx if it's available. */
@@ -24045,6 +24062,17 @@ thumb_pop (FILE *f, unsigned long mask)
static void
thumb1_cmse_nonsecure_entry_return (FILE *f, int reg_containing_return_addr)
{
+ char flags[12] = "APSR_nzcvq";
+ /* Check if we have to clear the 'GE bits' which is only used if
+ parallel add and subtraction instructions are available. */
+ if (TARGET_INT_SIMD)
+ {
+ flags[10] = 'g';
+ flags[11] = '\0';
+ }
+ asm_fprintf (f, "\tmsr\t%s, %r\n", flags, reg_containing_return_addr);
+ if (TARGET_HARD_FLOAT && TARGET_VFP)
+ asm_fprintf (f, "\tvmsr\tfpscr, %r\n", reg_containing_return_addr);
asm_fprintf (f, "\tbxns\t%r\n", reg_containing_return_addr);
}
@@ -25186,6 +25214,235 @@ thumb1_expand_prologue (void)
cfun->machine->lr_save_eliminated = 0;
}
+/* Clear caller saved registers not used to pass return values and leaked
+ condition flags before exiting a cmse_nonsecure_entry function. */
+
+void
+cmse_nonsecure_entry_clear_before_return (void)
+{
+ uint64_t to_clear_mask;
+ uint32_t padding_bits_to_clear = 0;
+ int regno, maxregno = IP_REGNUM;
+ tree result_type;
+ rtx result_rtl;
+
+ to_clear_mask = (1LL << (NUM_ARG_REGS)) - 1;
+ to_clear_mask |= (1LL << IP_REGNUM);
+ /* If we are not dealing with -mfloat-abi=softfp we will need to clear VFP
+ registers. We also check TARGET_VFP to make sure these are present. */
+ if (TARGET_HARD_FLOAT && TARGET_VFP)
+ {
+ uint64_t float_mask = (1LL << (D7_VFP_REGNUM + 1)) - 1;
+ float_mask &= ~((1LL << FIRST_VFP_REGNUM) - 1);
+ to_clear_mask |= float_mask;
+ maxregno = LAST_VFP_REGNUM;
+ }
+
+ /* If the user has defined registers to be caller saved, these are no longer
+ restored by the function before returning and must thus be cleared for
+ security purposes. */
+ for (regno = NUM_ARG_REGS; regno < LAST_VFP_REGNUM; regno++)
+ {
+ /* We leave registers traditionally used to pass arguments untouched,
+ since if these should not be made callee-saved by the user. */
+ if (regno >= FIRST_VFP_REGNUM && regno <= D7_VFP_REGNUM)
+ continue;
+ if (regno >= IP_REGNUM && regno <= PC_REGNUM)
+ continue;
+ if (call_used_regs[regno])
+ to_clear_mask |= (1LL << regno);
+ }
+
+ /* Make sure we do not clear the registers used to pass the result in. */
+ result_type = TREE_TYPE (DECL_RESULT (current_function_decl));
+ if (!VOID_TYPE_P (result_type))
+ {
+ rtx reg;
+
+ result_rtl = arm_function_value (result_type, current_function_decl, 0);
+
+ /* No need to check that we return in registers, because we don't
+ support returning on stack yet. */
+ if (RECORD_OR_UNION_TYPE_P (result_type))
+ {
+ unsigned current_addr, cont_width, alignment, bitfield_width,
+ next_addr;
+ tree field, field_t;
+
+ /* May only contain one register as the AAPCS limits the
+ returning of composite types to one register. */
+ gcc_assert (REG_P (result_rtl));
+ reg = result_rtl;
+
+ /* Which must thus be r0. */
+ gcc_assert (REG_P (reg) && REGNO (reg) == 0);
+ to_clear_mask &= ~1LL;
+
+ /* Clear unused bits as per the AAPCS these have unspecified
+ values and may thus leak secret information. We use the algorithm
+ specified in Section 7.1.7.1 "Bit-fields no larger than their
+ container" of the Procedure Call Standard for the ARM Architecture
+ to determine which bits are unused. */
+ field = TYPE_FIELDS (result_type);
+ /* 'current_addr' is the current bit address. */
+ current_addr = 0;
+ while (field && TREE_CODE (field) == FIELD_DECL)
+ {
+ field_t = TREE_TYPE (field);
+ if (DECL_BIT_FIELD (field))
+ {
+ tree bitfield_t = DECL_BIT_FIELD_TYPE (field);
+ /* 'cont_width' is the container's width. */
+ cont_width = TREE_INT_CST_ELT (TYPE_SIZE (bitfield_t), 0);
+ /* 'bitfield_width' is the width of the bitfield. */
+ bitfield_width = TREE_INT_CST_ELT (DECL_SIZE (field), 0);
+ /* 'alignment' is the alignment of the bitfield type. */
+ alignment = TYPE_ALIGN (bitfield_t);
+ }
+ else
+ {
+ /* For non-bitfields 'bitfield_width' and 'cont_width' are
+ the same, the size of the field member type. */
+ cont_width = TREE_INT_CST_ELT (TYPE_SIZE (field_t), 0);
+ bitfield_width = cont_width;
+ alignment = TYPE_ALIGN (field_t);
+ }
+
+ if ((bitfield_width == 0)
+ || (bitfield_width >
+ (cont_width - (current_addr % alignment))))
+ {
+ /* 'next_addr' is the next container bit address. */
+ next_addr =
+ alignment * ((current_addr + alignment - 1)/alignment);
+ /* We make sure we clear all bits between the current and
+ next address. */
+ padding_bits_to_clear |= (unsigned)
+ (((uint64_t)UINT_MAX >> (32 - (next_addr))) + 1u)
+ - (1u << current_addr);
+ current_addr = next_addr;
+ }
+ current_addr += bitfield_width;
+ field = TREE_CHAIN (field);
+ }
+
+ /* Don't forget to clear all unused trailing bits in r0. */
+ if (current_addr < 32)
+ padding_bits_to_clear |= UINT_MAX - (1u << current_addr) + 1u;
+ }
+ else
+ {
+ switch (GET_MODE (result_rtl))
+ {
+ case BLKmode:
+ {
+ /* We are dealing with a return in multiple VFP registers. */
+ int i;
+ /* This should really only occur when dealing with an hard
+ float abi. */
+ gcc_assert (TARGET_HARD_FLOAT_ABI && TARGET_VFP);
+
+ for (i = 0; i < XVECLEN (result_rtl, 0); i++)
+ {
+ reg = XEXP (XVECEXP (result_rtl, 0, i), 0);
+ gcc_assert (REG_P (reg));
+ /* If we are dealing with DF mode, make sure you don't
+ clear either registers it addresses. */
+ if (GET_MODE (reg) == DFmode)
+ to_clear_mask &= ~(1LL << (REGNO (reg) + 1));
+ to_clear_mask &= ~(1LL << REGNO (reg));
+ }
+ }
+ break;
+ case DImode:
+ to_clear_mask &= ~2; /* Don't clear r0 and r1. */
+ case SImode:
+ to_clear_mask &= ~1; /* Don't clear r0. */
+ break;
+ case DFmode:
+ /* If we have -mfloat-abi=hard, do not clear registers used to
+ pass return value. */
+ if (TARGET_HARD_FLOAT_ABI && TARGET_VFP)
+ /* Don't clear s0 and s1. */
+ to_clear_mask &= ~(1LL << (FIRST_VFP_REGNUM + 1));
+ else
+ to_clear_mask &= ~2; /* Don't clear r0, r1. */
+ case SFmode:
+ /* If we have -mfloat-abi=hard, do not clear registers used to
+ pass return value. */
+ if (TARGET_HARD_FLOAT_ABI && TARGET_VFP)
+ /* Don't clear s0. */
+ to_clear_mask &= ~(1LL << FIRST_VFP_REGNUM);
+ else
+ to_clear_mask &= ~1; /* Don't clear r0. */
+ break;
+ default:
+ /* We are missing a mode, though AAPCS says we may only return in
+ r0 and r1, s0-d15/d0-d7 so something went wrong here. */
+ gcc_unreachable ();
+ break;
+ }
+ }
+ }
+
+ if (padding_bits_to_clear != 0)
+ {
+ rtx reg_rtx;
+ /* Padding bits to clear is not 0 so we know we are dealing with
+ returning a composite type, which only uses r0. Let's make sure that
+ r1-r3 is cleared too, we will use r1 as a scratch register. */
+ gcc_assert ((to_clear_mask & 0xe) == 0xe);
+
+ reg_rtx = gen_rtx_REG (SImode, 1);
+
+ emit_move_insn (reg_rtx,
+ GEN_INT ((((~padding_bits_to_clear) << 16u) >> 16u)));
+
+ /* Also fill the top half of the negated padding_bits_to_clear. */
+ if (((~padding_bits_to_clear) >> 16) > 0)
+ emit_insn (gen_rtx_SET (gen_rtx_ZERO_EXTRACT (SImode, reg_rtx,
+ GEN_INT (16),
+ GEN_INT (16)),
+ GEN_INT ((~padding_bits_to_clear) >> 16)));
+
+ emit_insn (gen_andsi3 (gen_rtx_REG (SImode, 0),
+ gen_rtx_REG (SImode, 0),
+ reg_rtx));
+ }
+
+ for (regno = 0; regno <= maxregno; regno++)
+ {
+ if (!(to_clear_mask & (1LL << regno)))
+ continue;
+
+ /* If regno is a vfp register, even and its successor is also to
+ be cleared, use vmov. */
+ if (IS_VFP_REGNUM (regno))
+ {
+ if (TARGET_VFP_DOUBLE
+ && VFP_REGNO_OK_FOR_DOUBLE (regno)
+ && to_clear_mask & (1LL << (regno + 1)))
+ {
+ emit_move_insn (gen_rtx_REG (DFmode, regno++),
+ CONST1_RTX (DFmode));
+ emit_use (gen_rtx_REG (DFmode, regno));
+ }
+ else
+ {
+ emit_move_insn (gen_rtx_REG (SFmode, regno),
+ CONST1_RTX (SFmode));
+ emit_use (gen_rtx_REG (SFmode, regno));
+ }
+ }
+ else
+ {
+ emit_move_insn (gen_rtx_REG (SImode, regno),
+ gen_rtx_REG (SImode, LR_REGNUM));
+ emit_use (gen_rtx_REG (SImode, regno));
+ }
+ }
+}
+
/* Generate pattern *pop_multiple_with_stack_update_and_return if single
POP instruction can be generated. LR should be replaced by PC. All
the checks required are already done by USE_RETURN_INSN (). Hence,
@@ -25235,6 +25492,8 @@ thumb2_expand_return (bool simple_return)
}
else
{
+ if (IS_CMSE_ENTRY (arm_current_func_type ()))
+ cmse_nonsecure_entry_clear_before_return ();
emit_jump_insn (simple_return_rtx);
}
}
@@ -25293,6 +25552,10 @@ thumb1_expand_epilogue (void)
if (! df_regs_ever_live_p (LR_REGNUM))
emit_use (gen_rtx_REG (SImode, LR_REGNUM));
+
+ /* Clear all caller-saved regs that are not used to return. */
+ if (IS_CMSE_ENTRY (arm_current_func_type ()))
+ cmse_nonsecure_entry_clear_before_return ();
}
/* Epilogue code for APCS frame. */
@@ -25727,6 +25990,14 @@ arm_expand_epilogue (bool really_return)
stack_pointer_rtx, stack_pointer_rtx);
}
+ /* Clear all caller-saved regs that are not used to return. */
+ if (IS_CMSE_ENTRY (arm_current_func_type ()))
+ {
+ /* CMSE_ENTRY always returns! */
+ gcc_assert (really_return);
+ cmse_nonsecure_entry_clear_before_return ();
+ }
+
if (!really_return)
return;
diff --git a/gcc/config/arm/thumb1.md b/gcc/config/arm/thumb1.md
index
1b01ef6ce731fe3ff37c3d8c048fb9d5e7829b35..e895fc77d5dc68cc723f1daf7e80b8b83ce3b20b
100644
--- a/gcc/config/arm/thumb1.md
+++ b/gcc/config/arm/thumb1.md
@@ -1843,8 +1843,15 @@
"*
return thumb1_unexpanded_epilogue ();
"
- ; Length is absolute worst case
- [(set_attr "length" "44")
+ ; Length is absolute worst case, when using CMSE and if this is an entry
+ ; function an extra 4 (msr) to 8 (vmsr) extra might be added.
+ [(set (attr "length")
+ (if_then_else
+ (match_test "IS_CMSE_ENTRY (arm_current_func_type ())")
+ (if_then_else (match_test "TARGET_HARD_FLOAT && TARGET_VFP")
+ (const_int 52)
+ (const_int 48))
+ (const_int 44)))
(set_attr "type" "block")
;; We don't clobber the conditions, but the potential length of this
;; operation is sufficient to make conditionalizing the sequence
diff --git a/gcc/config/arm/thumb2.md b/gcc/config/arm/thumb2.md
index
992536593d6c0a8b8fe5a324f32e279c69746157..7161571fbbd08af739c64d78119ee28032aaa412
100644
--- a/gcc/config/arm/thumb2.md
+++ b/gcc/config/arm/thumb2.md
@@ -1118,7 +1118,15 @@
"TARGET_THUMB2"
"* return output_return_instruction (const_true_rtx, true, false, true);"
[(set_attr "type" "branch")
- (set_attr "length" "4")]
+ ; If this is a return from a cmse_nonsecure_entry function then code will be
+ ; added to clear the APSR and potentially the FPSCR if VFP is available, so
+ ; we adapt the length accordingly.
+ (set (attr "length")
+ (if_then_else (match_test "IS_CMSE_ENTRY (arm_current_func_type ())")
+ (if_then_else (match_test "TARGET_HARD_FLOAT && TARGET_VFP")
+ (const_int 12)
+ (const_int 8))
+ (const_int 4)))]
)
(define_insn_and_split "thumb2_eh_return"
diff --git a/gcc/testsuite/gcc.target/arm/cmse/baseline/cmse-2.c
b/gcc/testsuite/gcc.target/arm/cmse/baseline/cmse-2.c
new file mode 100644
index
0000000000000000000000000000000000000000..7cef73372fd5f5be080bdfe30999694564deaa9a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/baseline/cmse-2.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v8m_base_ok } */
+/* { dg-add-options arm_arch_v8m_base } */
+/* { dg-options "-mcmse" } */
+
+extern float bar (void);
+
+float __attribute__ ((cmse_nonsecure_entry))
+foo (void)
+{
+ return bar ();
+}
+/* { dg-final { scan-assembler "mov\tr1, lr" } } */
+/* { dg-final { scan-assembler "mov\tr2, lr" } } */
+/* { dg-final { scan-assembler "mov\tr3, lr" } } */
+/* { dg-final { scan-assembler "mov\tip, lr" } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, r1" } } */
+/* { dg-final { scan-assembler "bxns\tr1" } } */
diff --git a/gcc/testsuite/gcc.target/arm/cmse/bitfield-1.c
b/gcc/testsuite/gcc.target/arm/cmse/bitfield-1.c
new file mode 100644
index
0000000000000000000000000000000000000000..4c33b00a826ecd69ff770f3ede91aca6f96f828d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/bitfield-1.c
@@ -0,0 +1,41 @@
+/* { dg-do run } */
+/* { dg-require-effective-target arm_cmse_ok } */
+/* { dg-options "--save-temps -mcmse
-Wl,--section-start,.gnu.sgstubs=0x20400000" } */
+#include <limits.h>
+#include <stdlib.h>
+
+typedef struct
+{
+ unsigned short a : 6;
+ unsigned char b : 3;
+ unsigned char c;
+ unsigned short d : 8;
+} test_st;
+
+test_st __attribute__ ((cmse_nonsecure_entry)) foo (void)
+{
+ test_st t;
+ t.a = 63u;
+ t.b = 7u;
+ t.c = UCHAR_MAX;
+ t.d = 255u;
+ return t;
+}
+
+int
+main (void)
+{
+ test_st t;
+ t = foo ();
+ if (t.a != 63u
+ || t.b != 7u
+ || t.c != UCHAR_MAX
+ || t.d != 255u)
+ abort ();
+ return 0;
+}
+
+/* { dg-final { scan-assembler "movw\tr1, #1855" } } */
+/* { dg-final { scan-assembler "movt\tr1, 65535" } } */
+/* { dg-final { scan-assembler "ands\tr0(, r0)?, r1" } } */
+
diff --git a/gcc/testsuite/gcc.target/arm/cmse/bitfield-2.c
b/gcc/testsuite/gcc.target/arm/cmse/bitfield-2.c
new file mode 100644
index
0000000000000000000000000000000000000000..5e732d3dd0778fb67140ac004c2df24d9a352fb2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/bitfield-2.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_cmse_ok } */
+/* { dg-options "--save-temps -mcmse
-Wl,--section-start,.gnu.sgstubs=0x20400000" } */
+#include <stdlib.h>
+
+typedef struct
+{
+ short a : 7;
+ char b : 3;
+ short c : 11;
+} test_st;
+
+test_st __attribute__ ((cmse_nonsecure_entry)) foo (void)
+{
+ test_st t;
+ t.a = -64;
+ t.b = -4 ;
+ t.c = -1024;
+ return t;
+}
+
+int
+main (void)
+{
+ test_st t;
+ t = foo ();
+ if (t.a != -64
+ || t.b != -4
+ || t.c != -1024)
+ abort ();
+ return 0;
+}
+
+/* { dg-final { scan-assembler "movw\tr1, #1919" } } */
+/* { dg-final { scan-assembler "movt\tr1, 2047" } } */
+/* { dg-final { scan-assembler "ands\tr0(, r0)?, r1" } } */
+
+
diff --git a/gcc/testsuite/gcc.target/arm/cmse/bitfield-3.c
b/gcc/testsuite/gcc.target/arm/cmse/bitfield-3.c
new file mode 100644
index
0000000000000000000000000000000000000000..9b2b2f90c28fb5d910795b99e6395feeb7b134b0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/bitfield-3.c
@@ -0,0 +1,40 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_cmse_ok } */
+/* { dg-options "--save-temps -mcmse
-Wl,--section-start,.gnu.sgstubs=0x20400000" } */
+#include <limits.h>
+#include <stdlib.h>
+
+typedef struct
+{
+ short a;
+ char b : 2;
+ short : 1;
+ char c : 3;
+} test_st;
+
+test_st __attribute__ ((cmse_nonsecure_entry)) foo (void)
+{
+ test_st t;
+ t.a = SHRT_MIN;
+ t.b = -2;
+ t.c = -4;
+ return t;
+}
+
+int
+main (void)
+{
+ test_st t;
+ t = foo ();
+ if (t.a != SHRT_MIN
+ || t.b != -2
+ || t.c != -4)
+ abort ();
+ return 0;
+}
+
+/* { dg-final { scan-assembler "movw\tr1, #65535" } } */
+/* { dg-final { scan-assembler "movt\tr1, 63" } } */
+/* { dg-final { scan-assembler "ands\tr0(, r0)?, r1" } } */
+
+
diff --git a/gcc/testsuite/gcc.target/arm/cmse/cmse.exp
b/gcc/testsuite/gcc.target/arm/cmse/cmse.exp
index
d42235174203100f8015fe1785108f70efe1c805..b22fb81774897ff4582fa656413f86a4aec2ff9e
100644
--- a/gcc/testsuite/gcc.target/arm/cmse/cmse.exp
+++ b/gcc/testsuite/gcc.target/arm/cmse/cmse.exp
@@ -38,6 +38,26 @@ set LTO_TORTURE_OPTIONS ""
gcc-dg-runtest [lsort [glob $srcdir/$subdir/*.c]] \
"" $DEFAULT_CFLAGS
+if {[check_effective_target_arm_arch_v8m_base_ok]} then {
+ # Baseline only
+ gcc-dg-runtest [lsort [glob $srcdir/$subdir/baseline/*.c]] \
+ "" $DEFAULT_CFLAGS
+}
+
+if {[check_effective_target_arm_arch_v8m_main_ok]} then {
+ # Mainline -mfloat-abi=soft
+ gcc-dg-runtest [lsort [glob $srcdir/$subdir/mainline/soft/*.c]] \
+ "-mfloat-abi=soft" $DEFAULT_CFLAGS
+ gcc-dg-runtest [lsort [glob $srcdir/$subdir/mainline/softfp/*.c]] \
+ "" $DEFAULT_CFLAGS
+ gcc-dg-runtest [lsort [glob $srcdir/$subdir/mainline/softfp-sp/*.c]] \
+ "" $DEFAULT_CFLAGS
+ gcc-dg-runtest [lsort [glob $srcdir/$subdir/mainline/hard/*.c]] \
+ "" $DEFAULT_CFLAGS
+ gcc-dg-runtest [lsort [glob $srcdir/$subdir/mainline/hard-sp/*.c]] \
+ "" $DEFAULT_CFLAGS
+}
+
set LTO_TORTURE_OPTIONS ${saved-lto_torture_options}
set dg-do-what-default ${saved-dg-do-what-default}
diff --git a/gcc/testsuite/gcc.target/arm/cmse/mainline/hard-sp/cmse-5.c
b/gcc/testsuite/gcc.target/arm/cmse/mainline/hard-sp/cmse-5.c
new file mode 100644
index
0000000000000000000000000000000000000000..ab607aa1b7210acc7983bfb401e95117eb8c0d17
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/hard-sp/cmse-5.c
@@ -0,0 +1,37 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v8m_main_ok } */
+/* { dg-add-options arm_arch_v8m_main } */
+/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp" {*-*-*}
{"-mfloat-abi=soft" -mfloat-abi=softfp } {""} } */
+/* { dg-skip-if "Skip these if testing double precision" {*-*-*}
{"-mfpu=fpv[4-5]-d16"} {""} } */
+/* { dg-options "-mcmse -mfloat-abi=hard -mfpu=fpv5-sp-d16" } */
+
+extern float bar (void);
+
+float __attribute__ ((cmse_nonsecure_entry))
+foo (void)
+{
+ return bar ();
+}
+/* { dg-final { scan-assembler "mov\tr0, lr" } } */
+/* { dg-final { scan-assembler "mov\tr1, lr" } } */
+/* { dg-final { scan-assembler "mov\tr2, lr" } } */
+/* { dg-final { scan-assembler "mov\tr3, lr" } } */
+/* { dg-final { scan-assembler "mov\tip, lr" } } */
+/* { dg-final { scan-assembler-not "vmov\.f32\ts0, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts1, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts2, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts3, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts4, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts5, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts6, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts7, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts8, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts9, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts10, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts11, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts12, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts13, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts14, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts15, #1\.0" } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
arm_arch_v8m_main_ok && arm_dsp } } } } */
diff --git a/gcc/testsuite/gcc.target/arm/cmse/mainline/hard/cmse-5.c
b/gcc/testsuite/gcc.target/arm/cmse/mainline/hard/cmse-5.c
new file mode 100644
index
0000000000000000000000000000000000000000..5ed7e1d6404b8f17d630895eb06df5921a1a965f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/hard/cmse-5.c
@@ -0,0 +1,30 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v8m_main_ok } */
+/* { dg-add-options arm_arch_v8m_main } */
+/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp" {*-*-*}
{"-mfloat-abi=soft" -mfloat-abi=softfp } {""} } */
+/* { dg-skip-if "Skip these if testing single precision" {*-*-*}
{"-mfpu=*-sp-*"} {""} } */
+/* { dg-options "-mcmse -mfloat-abi=hard -mfpu=fpv5-d16" } */
+
+extern float bar (void);
+
+float __attribute__ ((cmse_nonsecure_entry))
+foo (void)
+{
+ return bar ();
+}
+/* { dg-final { scan-assembler "mov\tr0, lr" } } */
+/* { dg-final { scan-assembler "mov\tr1, lr" } } */
+/* { dg-final { scan-assembler "mov\tr2, lr" } } */
+/* { dg-final { scan-assembler "mov\tr3, lr" } } */
+/* { dg-final { scan-assembler "mov\tip, lr" } } */
+/* { dg-final { scan-assembler-not "vmov\.f32\ts0, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts1, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td1, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td2, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td3, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td4, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td5, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td6, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td7, #1\.0" } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
arm_arch_v8m_main_ok && arm_dsp } } } } */
diff --git a/gcc/testsuite/gcc.target/arm/cmse/mainline/soft/cmse-5.c
b/gcc/testsuite/gcc.target/arm/cmse/mainline/soft/cmse-5.c
new file mode 100644
index
0000000000000000000000000000000000000000..d24683f52eba11df5b41eef0e5a8e365fb206fe8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/soft/cmse-5.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v8m_main_ok } */
+/* { dg-add-options arm_arch_v8m_main } */
+/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp" {*-*-*}
{"-mfloat-abi=hard" -mfloat-abi=softfp } {""} } */
+/* { dg-options "-mcmse -mfloat-abi=soft" } */
+
+extern float bar (void);
+
+float __attribute__ ((cmse_nonsecure_entry))
+foo (void)
+{
+ return bar ();
+}
+
+/* { dg-final { scan-assembler "mov\tr1, lr" } } */
+/* { dg-final { scan-assembler "mov\tr2, lr" } } */
+/* { dg-final { scan-assembler "mov\tr3, lr" } } */
+/* { dg-final { scan-assembler "mov\tip, lr" } } */
+/* { dg-final { scan-assembler-not "vmov" } } */
+/* { dg-final { scan-assembler-not "vmsr" } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
arm_arch_v8m_main_ok && arm_dsp } } } } */
diff --git a/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp-sp/cmse-5.c
b/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp-sp/cmse-5.c
new file mode 100644
index
0000000000000000000000000000000000000000..c22775ace8361729c36130422475522fbaca05b5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp-sp/cmse-5.c
@@ -0,0 +1,39 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v8m_main_ok } */
+/* { dg-add-options arm_arch_v8m_main } */
+/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp" {*-*-*}
{"-mfloat-abi=soft" -mfloat-abi=hard } {""} } */
+/* { dg-skip-if "Skip these if testing double precision" {*-*-*}
{"-mfpu=fpv[4-5]-d16"} {""} } */
+/* { dg-options "-mcmse -mfloat-abi=softfp -mfpu=fpv5-sp-d16" } */
+
+extern float bar (void);
+
+float __attribute__ ((cmse_nonsecure_entry))
+foo (void)
+{
+ return bar ();
+}
+/* { dg-final { scan-assembler "__acle_se_foo:" } } */
+/* { dg-final { scan-assembler-not "mov\tr0, lr" } } */
+/* { dg-final { scan-assembler "mov\tr1, lr" } } */
+/* { dg-final { scan-assembler "mov\tr2, lr" } } */
+/* { dg-final { scan-assembler "mov\tr3, lr" } } */
+/* { dg-final { scan-assembler "mov\tip, lr" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts0, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts1, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts2, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts3, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts4, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts5, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts6, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts7, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts8, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts9, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts10, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts11, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts12, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts13, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts14, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f32\ts15, #1\.0" } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
arm_arch_v8m_main_ok && arm_dsp } } } } */
+/* { dg-final { scan-assembler "bxns" } } */
diff --git a/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp/cmse-5.c
b/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp/cmse-5.c
new file mode 100644
index
0000000000000000000000000000000000000000..28b05c391081bd42407b4e56a1e84daa549fa06e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/mainline/softfp/cmse-5.c
@@ -0,0 +1,31 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v8m_main_ok } */
+/* { dg-add-options arm_arch_v8m_main } */
+/* { dg-skip-if "Do not combine float-abi= hard | soft | softfp" {*-*-*}
{"-mfloat-abi=soft" -mfloat-abi=hard } {""} } */
+/* { dg-skip-if "Skip these if testing single precision" {*-*-*}
{"-mfpu=*-sp-*"} {""} } */
+/* { dg-options "-mcmse -mfloat-abi=softfp -mfpu=fpv5-d16" } */
+
+extern float bar (void);
+
+float __attribute__ ((cmse_nonsecure_entry))
+foo (void)
+{
+ return bar ();
+}
+/* { dg-final { scan-assembler "__acle_se_foo:" } } */
+/* { dg-final { scan-assembler-not "mov\tr0, lr" } } */
+/* { dg-final { scan-assembler "mov\tr1, lr" } } */
+/* { dg-final { scan-assembler "mov\tr2, lr" } } */
+/* { dg-final { scan-assembler "mov\tr3, lr" } } */
+/* { dg-final { scan-assembler "mov\tip, lr" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td0, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td1, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td2, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td3, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td4, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td5, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td6, #1\.0" } } */
+/* { dg-final { scan-assembler "vmov\.f64\td7, #1\.0" } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvq, lr" { target {
arm_arch_v8m_main_ok && { ! arm_dsp } } } } } */
+/* { dg-final { scan-assembler "msr\tAPSR_nzcvqg, lr" { target {
arm_arch_v8m_main_ok && arm_dsp } } } } */
+/* { dg-final { scan-assembler "bxns" } } */
diff --git a/gcc/testsuite/gcc.target/arm/cmse/struct-1.c
b/gcc/testsuite/gcc.target/arm/cmse/struct-1.c
new file mode 100644
index
0000000000000000000000000000000000000000..16258d1417556e558989b7c3ad192338a781f503
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/cmse/struct-1.c
@@ -0,0 +1,34 @@
+/* { dg-do run } */
+/* { dg-require-effective-target arm_cmse_ok } */
+/* { dg-options "--save-temps -mcmse
-Wl,--section-start,.gnu.sgstubs=0x20400000" } */
+#include <limits.h>
+#include <stdlib.h>
+typedef struct
+{
+ unsigned char a;
+ unsigned short b;
+} test_st;
+
+test_st __attribute__ ((cmse_nonsecure_entry)) foo (void)
+{
+ test_st t;
+ t.a = UCHAR_MAX;
+ t.b = USHRT_MAX;
+ return t;
+}
+
+int
+main (void)
+{
+ test_st t;
+ t = foo ();
+ if (t.a != UCHAR_MAX || t.b != USHRT_MAX)
+ abort ();
+ return 0;
+}
+
+/* { dg-final { scan-assembler "movs\tr1, #255" } } */
+/* { dg-final { scan-assembler "movt\tr1, 65535" } } */
+/* { dg-final { scan-assembler "ands\tr0(, r0)?, r1" } } */
+
+