Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

What state is asm allowed to modify? #5

Closed
Amanieu opened this issue Dec 16, 2019 · 12 comments · Fixed by #10
Closed

What state is asm allowed to modify? #5

Amanieu opened this issue Dec 16, 2019 · 12 comments · Fixed by #10

Comments

@Amanieu
Copy link
Member

Amanieu commented Dec 16, 2019

While the input/output operands make it clear what general purpose registers inline asm code is allowed to modify/clobber, it is not clear what other registers are allowed to be modified.

The following examples are x86-specific, but the general principles should apply to all architectures. For the purposes of this discussion, "modified" means that a register is not restored to its original value at the end of the asm.

Case 1: Flags

Is asm code allowed to modify the condition flags? What about other flags in the EFLAGS register? What about floating-point status registers, such as FPCR, FPSR, MXCSR?

Which of these are/aren't covered by preserves_flags?

Case 2: Segment registers

Is asm code allowed to modify the segment registers? For example, initialization code may need to change the fs register to point to the thread-local storage area. Does this conflict with the compiler's use of fs?

Case 3: Future registers

Suppose that some new registers are added in the future, such as xmm[32-63]. How can current asm code be forward-compatible if they call a function which may clobber these registers?

@bjorn3
Copy link
Member

bjorn3 commented Dec 16, 2019

What about floating-point status registers, such as FPCR, FPSR, MXCSR?

I believe that is UB with LLVM.

Is asm code allowed to modify the segment registers? For example, initialization code may need to change the fs register to point to the thread-local storage area. Does this conflict with the compiler's use of fs?

Shouldn't initialization code use global_asm! instead? Otherwise the compiler could for example spill values before the stack is setup. (Yes #[naked] exists, but it is pretty much equivalent to just using global_asm!)

@Lokathor
Copy link

I think that in the same way that we're probably going to assume stack usage and then let people specify "nostack", we should probably assume status is altered and then let people specify if they know it won't be.

@Amanieu
Copy link
Member Author

Amanieu commented Dec 16, 2019

The main issue here is that we should explicitly and exhaustively specify exactly what state is & isn't allowed to be modified by asm code.

@Lokathor
Copy link

Right. I was only speaking to Case 1.

Case 2 I'm not familiar with because I guess it's some Intel thing? I only do assembly on old ARM devices (ARMv4T), and I'm not familiar with the term "segment registers".

Case 3 sounds dag near impossible to tackle and I'm glad I don't have to solve that one myself.

@Amanieu
Copy link
Member Author

Amanieu commented Dec 17, 2019

For case 2, the ARM equivalent would be the the TPIDRURO/TPIDRURW registers in CP15, which hold the base address of TLS for the current thread.

@gnzlbg
Copy link

gnzlbg commented Jan 10, 2020

EFLAGS

__writeflags and __readeflags were deprecated because they were deemed impossible to use correctly: rust-lang/stdarch#485

@comex
Copy link

comex commented Jan 10, 2020

__writeflags and __readeflags were deprecated because they were deemed impossible to use correctly: rust-lang/stdarch#485

Not sure how that's relevant. They were deprecated because compiler-generated code can always clobber EFLAGS. But this issue is about what asm blocks are allowed to clobber. When it comes to the condition flags within EFLAGS, the answer is "can clobber unless preserves_flags is passed", but the question is whether other parts of EFLAGS are included, like perhaps the direction flag.

@gnzlbg
Copy link

gnzlbg commented Jan 11, 2020

If we allow inline assembly to clobber flags, we should specify what happens with those flags when we exit the inline assembly. Are they restored to their previous value ? Are they left unchanged ?

If they are left unchanged, the user might expect:

asm!("modify EFLAGS to state A");
asm!("assert EFLAGS is in state A"); 

to never panic, but we cannot currently guarantee that.

Also, if they are left unchanged, then e.g. leaving MXCSR.RC in an inline assembly statement with a value different from RN would mean that the default rounding mode is not round-to-nearest. So even if that inline assembly does not claim to preserve flags, the behavior would still be undefined, and at that point, we probably would need to, for each architecture and register, specify what are the valid values that each flag in each register is allowed to have.

An alternative would be to "restore" all flags to some safe state after an inline assembly statement without preserves_flags.

@Amanieu
Copy link
Member Author

Amanieu commented Jan 11, 2020

The intent is that preserves_flags works the same way as the "cc" clobber in C: when it is set, asm code must leave the flags with the same contents it had the start of the asm. When it is not set, asm code is allowed to leave the flags modified.

In between asm blocks, the compiler is free to modify the flags in any way whatsoever, so a second asm block cannot assume any initial value for the flags.

To clarify, when I talk about flags I am only referring to some bits of the EFLAGS register. Specifically the status bits (CF, PF, AF, ZF, SF, OF) and the direction flag (DF). The other bits are system-level control flags which are not used by the compiler.

@gnzlbg
Copy link

gnzlbg commented Jan 11, 2020

To clarify, when I talk about flags I am only referring to some bits of the EFLAGS register. Specifically the status bits (CF, PF, AF, ZF, SF, OF) and the direction flag (DF). The other bits are system-level control flags which are not used by the compiler.

You also mentioned MXCSR which allows changing the default floating-point environment in the OP.

It might be worth it to more precisely spell out which "flags" are being discussed here on each architecture, and to which values they can be modified when preserves_flags is omitted (e.g. as mentioned, exiting an inline assembly block with MXCSR.RC != RN is probably instant UB).

@Amanieu
Copy link
Member Author

Amanieu commented Jan 11, 2020

OK so let's go with this definition of what is covered by preserves_flags:
x86: EFLAGS (status flags + direction flag), FPSR (all), MXCSR (status flags)
ARM: CPSR (NZCV + GE + Q), FPSCR (NZCV + status flags)
AArch64: NZCV, FPSR
RISCV: FCSR (status flags)

The general rule I used is that it covers the parts of flags registers that is not preserved across function calls. (ARM, AArch64, x86)

@Amanieu
Copy link
Member Author

Amanieu commented Jan 11, 2020

Everything outside of these flags is basically considered global state, which you modify at your own risk and which is outside the scope of this RFC.

Regarding case 3, I think we can defer that for now. As a future extension we may offer a clobber_abi("C") flag which adds all the necessary clobbers for calling a function of the "C" ABI.

Amanieu added a commit to Amanieu/project-inline-asm that referenced this issue Jan 11, 2020
Also add ABI-clobbers as a future extension.

Fixes rust-lang#5
Amanieu added a commit to Amanieu/project-inline-asm that referenced this issue Jan 11, 2020
Also add ABI-clobbers as a future extension.

Fixes rust-lang#5
Amanieu added a commit to Amanieu/project-inline-asm that referenced this issue Jan 12, 2020
Also add ABI-clobbers as a future extension.

Fixes rust-lang#5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants