-
Notifications
You must be signed in to change notification settings - Fork 13.4k
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
Comments
@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. |
I'm not sure if this is a problem per se. On RISC-V, the interrupt attributes cause three things to happen:
I think the last point could be a problem - using Because of this, interrupt handlers are effectively |
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. |
@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: |
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. |
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. |
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. |
The following code compiles:
It seems like it should not, given that this code does not compile:
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.
The text was updated successfully, but these errors were encountered: