diff --git a/compiler/symbol.go b/compiler/symbol.go index 67012b14..c431e76f 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -279,8 +279,12 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) { info.linkName = parts[2] } case "//go:section": + // Only enable go:section when the package imports "unsafe". + // go:section also implies go:noinline since inlining could + // move the code to a different section than that requested. if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) { info.section = parts[1] + info.inline = inlineNone } case "//go:nobounds": // Skip bounds checking in this function. Useful for some diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index 68288804..35afcf7f 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -47,13 +47,13 @@ entry: ret void } -; Function Attrs: nounwind -define hidden void @main.functionInSection(ptr %context) unnamed_addr #1 section ".special_function_section" { +; Function Attrs: noinline nounwind +define hidden void @main.functionInSection(ptr %context) unnamed_addr #4 section ".special_function_section" { entry: ret void } -; Function Attrs: nounwind +; Function Attrs: noinline nounwind define void @exportedFunctionInSection() #5 section ".special_function_section" { entry: ret void @@ -66,4 +66,4 @@ attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint, attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="extern_func" "wasm-import-module"="env" "wasm-import-name"="extern_func" } attributes #3 = { inlinehint nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #4 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } -attributes #5 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exportedFunctionInSection" "wasm-import-module"="env" "wasm-import-name"="exportedFunctionInSection" } +attributes #5 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exportedFunctionInSection" "wasm-import-module"="env" "wasm-import-name"="exportedFunctionInSection" } diff --git a/src/examples/ram-func/main.go b/src/examples/ram-func/main.go new file mode 100644 index 00000000..b4556c19 --- /dev/null +++ b/src/examples/ram-func/main.go @@ -0,0 +1,93 @@ +package main + +// This example demonstrates how to use go:section to place code into RAM for +// execution. The code is present in flash in the `.data` region and copied +// into the correct place in RAM early in startup sequence (at the same time +// as non-zero global variables are initialized). +// +// This example should work on any ARM Cortex MCU. +// +// For Go code use the pragma "//go:section", for cgo use the "section" and +// "noinline" attributes. The `.ramfuncs` section is explicitly placed into +// the `.data` region by the linker script. +// +// Running the example should print out the program counter from the functions +// below. The program counters should be in different memory regions. +// +// On RP2040, for example, the output is something like this: +// +// Go in RAM: 0x20000DB4 +// Go in flash: 0x10007610 +// cgo in RAM: 0x20000DB8 +// cgo in flash: 0x10002C26 +// +// This can be confirmed using `objdump -t xxx.elf | grep main | sort`: +// +// 00000000 l df *ABS* 00000000 main +// 1000760d l F .text 00000004 main.in_flash +// 10007611 l F .text 0000000c __Thumbv6MABSLongThunk_main.in_ram +// 1000761d l F .text 0000000c __Thumbv6MABSLongThunk__Cgo_static_eea7585d7291176ad3bb_main_c_in_ram +// 1000bdb5 l O .text 00000013 main$string +// 1000bdc8 l O .text 00000013 main$string.1 +// 1000bddb l O .text 00000013 main$string.2 +// 1000bdee l O .text 00000013 main$string.3 +// 20000db1 l F .data 00000004 main.in_ram +// 20000db5 l F .data 00000004 _Cgo_static_eea7585d7291176ad3bb_main_c_in_ram +// + +import ( + "device" + "fmt" + "time" + _ "unsafe" // unsafe is required for "//go:section" +) + +/* + #define ram_func __attribute__((section(".ramfuncs"),noinline)) + + static ram_func void* main_c_in_ram() { + void* p = 0; + + asm( + "MOV %0, PC" + : "=r"(p) + ); + + return p; + } + + static void* main_c_in_flash() { + void* p = 0; + + asm( + "MOV %0, PC" + : "=r"(p) + ); + + return p; + } +*/ +import "C" + +func main() { + time.Sleep(2 * time.Second) + + fmt.Printf("Go in RAM: 0x%X\n", in_ram()) + fmt.Printf("Go in flash: 0x%X\n", in_flash()) + fmt.Printf("cgo in RAM: 0x%X\n", C.main_c_in_ram()) + fmt.Printf("cgo in flash: 0x%X\n", C.main_c_in_flash()) +} + +//go:section .ramfuncs +func in_ram() uintptr { + return device.AsmFull("MOV {}, PC", nil) +} + +// go:noinline used here to prevent function being 'inlined' into main() +// so it appears in objdump output. In normal use, go:inline is not +// required for functions running from flash (flash is the default). +// +//go:noinline +func in_flash() uintptr { + return device.AsmFull("MOV {}, PC", nil) +} diff --git a/targets/arm.ld b/targets/arm.ld index ad6c541a..39b5c75d 100644 --- a/targets/arm.ld +++ b/targets/arm.ld @@ -42,6 +42,8 @@ SECTIONS *(.data) *(.data.*) . = ALIGN(4); + *(.ramfuncs*) /* Functions that must execute from RAM */ + . = ALIGN(4); _edata = .; /* used by startup code */ } >RAM AT>FLASH_TEXT