Skip to content

Why does llc compile direct calls to RISCV interrupts? #115640

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

Open
workingjubilee opened this issue Nov 10, 2024 · 10 comments
Open

Why does llc compile direct calls to RISCV interrupts? #115640

workingjubilee opened this issue Nov 10, 2024 · 10 comments
Labels
backend:RISC-V question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead!

Comments

@workingjubilee
Copy link
Contributor

The following code compiles:

source_filename = "example.925e6eb0586113f0-cgu.0"
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64-unknown-linux-gnu"

define void @_ZN7example17interrupt_machine17h9aedfc539b69d0e4E() unnamed_addr #0 {
  ret void
}

define void @_ZN7example20interrupt_supervisor17h3af0168b331d21a0E() unnamed_addr #1 {
  ret void
}

define void @_ZN7example4main17h90b0fda4f240e4d2E() unnamed_addr #2 {
  call void @_ZN7example17interrupt_machine17h9aedfc539b69d0e4E() #3
  call void @_ZN7example20interrupt_supervisor17h3af0168b331d21a0E() #3
  ret void
}

attributes #0 = { nounwind uwtable "interrupt"="machine" "target-cpu"="generic-rv64" "target-features"="+m,+a,+f,+d,+c" }
attributes #1 = { nounwind uwtable "interrupt"="supervisor" "target-cpu"="generic-rv64" "target-features"="+m,+a,+f,+d,+c" }
attributes #2 = { uwtable "target-cpu"="generic-rv64" "target-features"="+m,+a,+f,+d,+c" }
attributes #3 = { nounwind }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 1, !"Code Model", i32 3}
!2 = !{i32 1, !"target-abi", !"lp64d"}
!3 = !{!"rustc version 1.84.0-nightly (b91a3a056 2024-11-07)"}

It seems like it should not, given that this code does not compile:

source_filename = "example.925e6eb0586113f0-cgu.0"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

define x86_intrcc void @_ZN7example13lol_interrupt17h978ba38488afd6e7E() unnamed_addr #0 {
  ret void
}

define void @_ZN7example4main17h90b0fda4f240e4d2E() unnamed_addr #1 {
  call x86_intrcc void @_ZN7example13lol_interrupt17h978ba38488afd6e7E() #2
  ret void
}

attributes #0 = { nounwind nonlazybind uwtable "probe-stack"="inline-asm" "target-cpu"="x86-64" }
attributes #1 = { nonlazybind uwtable "probe-stack"="inline-asm" "target-cpu"="x86-64" }
attributes #2 = { nounwind }

!llvm.module.flags = !{!0, !1}
!llvm.ident = !{!2}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 2, !"RtLibUseGOT", i32 1}
!2 = !{!"rustc version 1.84.0-nightly (b91a3a056 2024-11-07)"}

Of course, x86 is uniquely "quirky", especially on interrupt entry. I'm not aware if this is... conceptually valid? ...for RISCV, so feel free to close this if this is truly intentional.

@llvmbot
Copy link
Member

llvmbot commented Nov 10, 2024

@llvm/issue-subscribers-backend-risc-v

Author: Jubilee (workingjubilee)

The following code compiles:
source_filename = "example.925e6eb0586113f0-cgu.0"
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64-unknown-linux-gnu"

define void @<!-- -->_ZN7example17interrupt_machine17h9aedfc539b69d0e4E() unnamed_addr #<!-- -->0 {
  ret void
}

define void @<!-- -->_ZN7example20interrupt_supervisor17h3af0168b331d21a0E() unnamed_addr #<!-- -->1 {
  ret void
}

define void @<!-- -->_ZN7example4main17h90b0fda4f240e4d2E() unnamed_addr #<!-- -->2 {
  call void @<!-- -->_ZN7example17interrupt_machine17h9aedfc539b69d0e4E() #<!-- -->3
  call void @<!-- -->_ZN7example20interrupt_supervisor17h3af0168b331d21a0E() #<!-- -->3
  ret void
}

attributes #<!-- -->0 = { nounwind uwtable "interrupt"="machine" "target-cpu"="generic-rv64" "target-features"="+m,+a,+f,+d,+c" }
attributes #<!-- -->1 = { nounwind uwtable "interrupt"="supervisor" "target-cpu"="generic-rv64" "target-features"="+m,+a,+f,+d,+c" }
attributes #<!-- -->2 = { uwtable "target-cpu"="generic-rv64" "target-features"="+m,+a,+f,+d,+c" }
attributes #<!-- -->3 = { nounwind }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 1, !"Code Model", i32 3}
!2 = !{i32 1, !"target-abi", !"lp64d"}
!3 = !{!"rustc version 1.84.0-nightly (b91a3a056 2024-11-07)"}

It seems like it should not, given that this code does not compile:

source_filename = "example.925e6eb0586113f0-cgu.0"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

define x86_intrcc void @<!-- -->_ZN7example13lol_interrupt17h978ba38488afd6e7E() unnamed_addr #<!-- -->0 {
  ret void
}

define void @<!-- -->_ZN7example4main17h90b0fda4f240e4d2E() unnamed_addr #<!-- -->1 {
  call x86_intrcc void @<!-- -->_ZN7example13lol_interrupt17h978ba38488afd6e7E() #<!-- -->2
  ret void
}

attributes #<!-- -->0 = { nounwind nonlazybind uwtable "probe-stack"="inline-asm" "target-cpu"="x86-64" }
attributes #<!-- -->1 = { nonlazybind uwtable "probe-stack"="inline-asm" "target-cpu"="x86-64" }
attributes #<!-- -->2 = { nounwind }

!llvm.module.flags = !{!0, !1}
!llvm.ident = !{!2}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 2, !"RtLibUseGOT", i32 1}
!2 = !{!"rustc version 1.84.0-nightly (b91a3a056 2024-11-07)"}

Of course, x86 is uniquely "quirky", especially on interrupt entry. I'm not aware if this is... conceptually valid? ...for RISCV, so feel free to close this if this is truly intentional.

@lenary
Copy link
Member

lenary commented Nov 11, 2024

I'm not sure if this is a problem per se.

On RISC-V, the interrupt attributes cause three things to happen:

  • there must be no arguments and the return type must be void
  • all used GPRs/FPRs are saved/restored, not just those the ABI denotes to be callee-saved
  • a different instruction is used to return

I think the last point could be a problem - using mret or sret doesn't return to the caller, it returns to a different privilege level.

Because of this, interrupt handlers are effectively noreturn functions, which the frontend would have to diagnose as such, but I'm not sure it's a problem if someone directly calls them - presumably if they're working with such low-level functionality, they understand how interrupt handler functions actually work, and thus want the behaviour they're asking for by directly calling them.

@lenary lenary added the question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead! label Nov 11, 2024
@workingjubilee
Copy link
Contributor Author

workingjubilee commented Nov 11, 2024

I'm not sure if this is a problem per se.

On RISC-V, the interrupt attributes cause three things to happen:

  • there must be no arguments and the return type must be void
  • all used GPRs/FPRs are saved/restored, not just those the ABI denotes to be callee-saved
  • a different instruction is used to return

To clarify, I've done a survey of interrupts, and most of them work this way except for x86, which has the peculiar distinction of accepting 0-2 arguments. They also reject the direct call at the machine lowering level, however. I am thus also asking, in a sense, why the other backends reject it if preventing the call should be considered a frontend problem (or not, as the case may be).

@topperc
Copy link
Collaborator

topperc commented Nov 11, 2024

I'm not sure if this is a problem per se.
On RISC-V, the interrupt attributes cause three things to happen:

  • there must be no arguments and the return type must be void
  • all used GPRs/FPRs are saved/restored, not just those the ABI denotes to be callee-saved
  • a different instruction is used to return

To clarify, I've done a survey of interrupts, and most of them work this way except for x86, which has the peculiar distinction of accepting 0-2 arguments. They also reject the direct call at the machine lowering level, however. I am thus also asking, in a sense, why the other backends reject it if preventing the call should be considered a frontend problem (or not, as the case may be).

Which other backends have this?

The frontend will be able to produce a much better error than the backend issuing a fatal error. So even if the backend rejects it, the frontend probably should too.

@workingjubilee
Copy link
Contributor Author

Which other backends have this?

The frontend will be able to produce a much better error than the backend issuing a fatal error. So even if the backend rejects it, the frontend probably should too.

@topperc x86 and msp430 reject it.

Yes, I have filed relevant bugs for the Rust frontend:

Another element of the reason I am asking is that sometimes, interrupt handlers do one thing and then delegate to the behavior of another interrupt handler. In Rust, people do this for using architecture-specific hijinx: asm! and the like. So I am partially wondering what would be more ergonomic, i.e. if we can "get away with" "just" calling another interrupt handler while inside an interrupt handler.

@workingjubilee
Copy link
Contributor Author

workingjubilee commented Nov 11, 2024

I think we would want to prohibit ordinary call syntax, to be clear... but supporting a specific syntax that is allowed to do such a "special" call, and that also applies a small suite of checks, so that when you delegate between interrupt handlers, you are actually delegating between interrupt handlers in a correct way... seems very plausible to me.

Alternatively, we could expose the "preserves-all-registers" ABI in Rust... there's many ways to handle this, hypothetically, and I'm just wondering which ones "should" work. This obviously is easier if we understand the way the backend is reasoning about this and thus what they are most inclined to cooperate on.

@topperc
Copy link
Collaborator

topperc commented Nov 12, 2024

I think we would want to prohibit ordinary call syntax, to be clear... but supporting a specific syntax that is allowed to do such a "special" call, and that also applies a small suite of checks, so that when you delegate between interrupt handlers, you are actually delegating between interrupt handlers in a correct way... seems very plausible to me.

Would this need to be a tail call? The mret/sret in the called interrupt handler wouldn't return back to the caller.

@workingjubilee
Copy link
Contributor Author

I think we would want to prohibit ordinary call syntax, to be clear... but supporting a specific syntax that is allowed to do such a "special" call, and that also applies a small suite of checks, so that when you delegate between interrupt handlers, you are actually delegating between interrupt handlers in a correct way... seems very plausible to me.

Would this need to be a tail call? The mret/sret in the called interrupt handler wouldn't return back to the caller.

Yes, I was thinking that might be what we would support if we did support it.

@topperc
Copy link
Collaborator

topperc commented Nov 12, 2024

I think we would want to prohibit ordinary call syntax, to be clear... but supporting a specific syntax that is allowed to do such a "special" call, and that also applies a small suite of checks, so that when you delegate between interrupt handlers, you are actually delegating between interrupt handlers in a correct way... seems very plausible to me.

Would this need to be a tail call? The mret/sret in the called interrupt handler wouldn't return back to the caller.

Yes, I was thinking that might be what we would support if we did support it.

Though I'm not sure where we would get the free GPR we need to perform the jump.

@workingjubilee
Copy link
Contributor Author

workingjubilee commented Nov 12, 2024

I am guessing I'd have to study the assembly tricks that are already in-use to better understand how it's implemented in-practice if we did go down that route.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:RISC-V question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead!
Projects
None yet
Development

No branches or pull requests

5 participants