-
Notifications
You must be signed in to change notification settings - Fork 14
Switch lookup tables are not properly placed in program memory #47
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
In the fully linked
|
Should these lookup tables even be generated?! avr-llvm/llvm#88 |
Using a linker script, I moved the lookup table into
Where 0xff2e from 0xc2/0xc4 is exactly -0xe0, so this seems like it should work. Unfortunately, |
Details of
|
I've fixed this locally by matching This works for the test case from this ticket, but unfortunately doesn't readily generalize to 16-bit |
LLVM IR was not actually attached in the description so here is what I get when I compile it locally ; ModuleID = 'test.cgu-0.rs'
source_filename = "test.cgu-0.rs"
target datalayout = "e-p:16:16:16-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-n8"
target triple = "avr-atmel-none"
%"lib::option::Option<()>" = type { i8, [0 x i8], [0 x i8] }
; Function Attrs: noinline uwtable
define internal i8 @_ZN4test6decode17hcd01465f37da56a3E(i8) unnamed_addr #0 {
start:
%_0 = alloca %"lib::option::Option<()>"
switch i8 %0, label %bb7 [
i8 0, label %bb1
i8 1, label %bb2
i8 2, label %bb3
i8 3, label %bb4
i8 4, label %bb5
i8 14, label %bb6
]
bb1: ; preds = %start
%1 = getelementptr inbounds %"lib::option::Option<()>", %"lib::option::Option<()>"* %_0, i32 0, i32 0
store i8 1, i8* %1
br label %bb8
bb2: ; preds = %start
%2 = getelementptr inbounds %"lib::option::Option<()>", %"lib::option::Option<()>"* %_0, i32 0, i32 0
store i8 1, i8* %2
br label %bb8
bb3: ; preds = %start
%3 = getelementptr inbounds %"lib::option::Option<()>", %"lib::option::Option<()>"* %_0, i32 0, i32 0
store i8 1, i8* %3
br label %bb8
bb4: ; preds = %start
%4 = getelementptr inbounds %"lib::option::Option<()>", %"lib::option::Option<()>"* %_0, i32 0, i32 0
store i8 1, i8* %4
br label %bb8
bb5: ; preds = %start
%5 = getelementptr inbounds %"lib::option::Option<()>", %"lib::option::Option<()>"* %_0, i32 0, i32 0
store i8 1, i8* %5
br label %bb8
bb6: ; preds = %start
%6 = getelementptr inbounds %"lib::option::Option<()>", %"lib::option::Option<()>"* %_0, i32 0, i32 0
store i8 1, i8* %6
br label %bb8
bb7: ; preds = %start
%7 = getelementptr inbounds %"lib::option::Option<()>", %"lib::option::Option<()>"* %_0, i32 0, i32 0
store i8 0, i8* %7
br label %bb8
bb8: ; preds = %bb1, %bb2, %bb3, %bb4, %bb5, %bb6, %bb7
%8 = bitcast %"lib::option::Option<()>"* %_0 to i8*
%9 = load i8, i8* %8, align 1
ret i8 %9
}
; Function Attrs: nounwind uwtable
define void @rust_eh_personality() unnamed_addr #1 {
start:
ret void
}
; Function Attrs: uwtable
define internal void @_ZN4test4main17hf4d700377696cbb0E() unnamed_addr #2 {
start:
ret void
}
; Function Attrs: uwtable
define internal i16 @_ZN4test5start17h518035a39d5f5ca0E(i16, i8**) unnamed_addr #2 {
start:
ret i16 0
}
define i16 @main(i16, i8**) unnamed_addr {
top:
%2 = call i16 @_ZN4test5start17h518035a39d5f5ca0E(i16 %0, i8** %1)
ret i16 %2
}
attributes #0 = { noinline uwtable }
attributes #1 = { nounwind uwtable }
attributes #2 = { uwtable }
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"PIE Level", i32 2} |
Continuing conversation from #53 It's interesting because it looks like LLVM is making the switch -> lookup table optimisation. That means that in order to correctly mark the global variable with the program memory address space, we will need to modify LLVM. |
This completely untested/uncompiled hack would probably fix it diff --git a/lib/Transforms/Utils/SimplifyCFG.cpp b/lib/Transforms/Utils/SimplifyCFG.cpp
index 7b0bddbbb83..d0506c2ef17 100644
--- a/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -4973,7 +4973,8 @@ SwitchLookupTable::SwitchLookupTable(
Array = new GlobalVariable(M, ArrayTy, /*constant=*/true,
GlobalVariable::PrivateLinkage, Initializer,
- "switch.table");
+ "switch.table", /*InsertBefore=*/ nullptr,
+ GlobalValue::NotThreadLocal, /*AddressSpace=*/1);
Array->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
Kind = ArrayKind;
} Of course, if we wanted to upstream it, it would have to be more general purpose. I can think of two solutions
|
I've been looking into fixing this properly via adding target-specific hooks to the I've been thinking about it and I don't think these lookup tables are a special case. The switch table itself is just a global variable, i.e. @switch.table.foo = private unnamed_addr constant [15 x i8] c"\01\01\01\01\01\00\00\00\00\00\00\00\00\00\01" Now, there is nothing special about this - it is just an array of characters. In general, we should always be able to load things from RAM using the Now, at chip startup, RAM is completely empty and all variables are stored in program memory. What should happen is that the startup code copies all variables that should exist in RAM, into the RAM. Now this should also include the switch table, which shouldn't work any different. Now if we use Granted, we really would want switch tables to exist in program memory because it's just static branching data. I will finish writing my patch because it's good anyway, but I think there must be something deeper afoot. Here is where I'm getting my information, questions 1-3. |
Opened D34983. |
I am 100% percent against any solution that involves mindlessly copying switch lookup tables into RAM just so we can use These lookup tables are generated by the compiler. On MCUs that have 512 bytes of RAM or even less, having them in RAM is definitely observable behaviour -- an observable leak of compiler behaviour. We have everything we need to know, end to end, to avoid this copying: the Rust compiler synthesizes these globals and it also generates the LLVM IR for accessing them. This cannot be an unsolvable problem. |
I completely agree, having lookup tables copied into ram would be a very ugly wart. My point is that I can fix switch tables but if we currently cannot load those globals, are we even loading globals correctly now? My patch completely fixes the switch table issue by the way, so they they only exist and are correctly loaded from program memory. |
No, I don't think we are, but I think that responsibility is outside of the compiler proper. I have some old hacked up code locally that adds some linker things to get the size of the global section and then copies it out into RAM before Rust's |
Do you know if this functionality is a part of the CRT? If so, that means we will get it for free if we link correctly with avr-gcc's CRT library. |
I think so, but to be honest I don't fully recall and I'm not even sure I have a great distinction of what is and is not the CRT.
That would be the trick, wouldn't it? :-) In case it wasn't clear, I really want to write as much of that in Rust as possible, so I'm always trying to remove any GCC bits ;-) |
I think there are a few unresolved questions with switch tables:
I think the easiest way forward is to disable switch lookup tables on AVR until these two problems are resolved (#47 and #69). |
I have a WIP branch here. |
The input code here is a slight tweak of #46, but the generated code is very different because it uses a lookup instead of bit-twiddling:
Rust:
LLVM IR:
AVR assembly:
The text was updated successfully, but these errors were encountered: