Skip to content

all: prototype of RAM persistence across reset #1715

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

Draft
wants to merge 10 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/persistence
@$(MD5SUM) test.hex
# test simulated boards on play.tinygo.org
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
@$(MD5SUM) test.wasm
Expand Down
12 changes: 9 additions & 3 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -709,11 +709,11 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
// Create the function definition.
b := newBuilder(c, irbuilder, member)
if member.Blocks == nil {
continue // external function
}
// Create the function definition.
b := newBuilder(c, irbuilder, member)
b.createFunction()
case *ssa.Type:
if types.IsInterface(member.Type()) {
Expand Down Expand Up @@ -752,10 +752,13 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
case *ssa.Global:
// Global variable.
info := c.getGlobalInfo(member)
global := c.getGlobal(member)
if !info.extern {
global := c.getGlobal(member)
global.SetInitializer(llvm.ConstNull(global.Type().ElementType()))
global.SetVisibility(llvm.HiddenVisibility)
if info.section != "" {
global.SetSection(info.section)
}
}
}
}
Expand All @@ -781,6 +784,9 @@ func (b *builder) createFunction() {
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
b.llvmFn.SetUnnamedAddr(true)
}
if b.info.section != "" {
b.llvmFn.SetSection(b.info.section)
}
if b.info.exported && strings.HasPrefix(b.Triple, "wasm") {
// Set the exported name. This is necessary for WebAssembly because
// otherwise the function is not exported.
Expand Down
1 change: 1 addition & 0 deletions compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func TestCompiler(t *testing.T) {
"string.go",
"float.go",
"interface.go",
"pragma.go",
}

for _, testCase := range tests {
Expand Down
10 changes: 10 additions & 0 deletions compiler/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type functionInfo struct {
module string // go:wasm-module
importName string // go:linkname, go:export - The name the developer assigns
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
section string // go:section - object file section name
exported bool // go:export, CGo
nobounds bool // go:nobounds
variadic bool // go:variadic (CGo only)
Expand Down Expand Up @@ -259,6 +260,10 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:section":
if len(parts) == 2 {
info.section = parts[1]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
Expand Down Expand Up @@ -314,6 +319,7 @@ type globalInfo struct {
linkName string // go:extern
extern bool // go:extern
align int // go:align
section string // go:section
}

// loadASTComments loads comments on globals from the AST, for use later in the
Expand Down Expand Up @@ -425,6 +431,10 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
if err == nil {
info.align = align
}
case "//go:section":
if len(parts) == 2 {
info.section = parts[1]
}
}
}
}
Expand Down
66 changes: 66 additions & 0 deletions compiler/testdata/pragma.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import _ "unsafe"

// Creates an external global with name extern_global.
//go:extern extern_global
var externGlobal [0]byte

// Creates a
//go:align 32
var alignedGlobal [4]uint32

// Test conflicting pragmas (the last one counts).
//go:align 64
//go:align 16
var alignedGlobal16 [4]uint32

// Test exported functions.
//export extern_func
func externFunc() {
}

// Define a function in a different package using go:linkname.
//go:linkname withLinkageName1 somepkg.someFunction1
func withLinkageName1() {
}

// Import a function from a different package using go:linkname.
//go:linkname withLinkageName2 somepkg.someFunction2
func withLinkageName2()

// Function has an 'inline hint', similar to the inline keyword in C.
//go:inline
func inlineFunc() {
}

// Function should never be inlined, equivalent to GCC
// __attribute__((noinline)).
//go:noinline
func noinlineFunc() {
}

// This function should have the specified section.
//go:section .special_function_section
func functionInSection() {
}

//export exportedFunctionInSection
//go:section .special_function_section
func exportedFunctionInSection() {
}

// This function should not: it's only a declaration and not a definition.
//go:section .special_function_section
func undefinedFunctionNotInSection()

//go:section .special_global_section
var globalInSection uint32

//go:section .special_global_section
//go:extern undefinedGlobalNotInSection
var undefinedGlobalNotInSection uint32

//go:align 1024
//go:section .global_section
var multipleGlobalPragmas uint32
57 changes: 57 additions & 0 deletions compiler/testdata/pragma.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
; ModuleID = 'pragma.go'
source_filename = "pragma.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"

@extern_global = external global [0 x i8]
@main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32
@main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16
@main.globalInSection = hidden global i32 0, section ".special_global_section"
@undefinedGlobalNotInSection = external global i32
@main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024

declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)

define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}

define void @extern_func() {
entry:
ret void
}

define hidden void @somepkg.someFunction1(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}

declare void @somepkg.someFunction2(i8*, i8*)

; Function Attrs: inlinehint
define hidden void @main.inlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}

; Function Attrs: noinline
define hidden void @main.noinlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #1 {
entry:
ret void
}

define hidden void @main.functionInSection(i8* %context, i8* %parentHandle) unnamed_addr section ".special_function_section" {
entry:
ret void
}

define void @exportedFunctionInSection() section ".special_function_section" {
entry:
ret void
}

declare void @main.undefinedFunctionNotInSection(i8*, i8*)

attributes #0 = { inlinehint }
attributes #1 = { noinline }
19 changes: 19 additions & 0 deletions src/examples/persistence/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import (
"time"
)

//go:section .persist
var buffer [32]byte

func main() {
println("\n*** ** * RESET * ** ***\n")

for {
time.Sleep(1 * time.Second)

println("value: ", buffer[0])
buffer[0]++
}
}
11 changes: 11 additions & 0 deletions targets/arm.ld
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ SECTIONS
_stack_top = .;
} >RAM

/* Persistent RAM that is not re-initialized on reset. PRAM is either
* a specific area of RAM which is powered during low-power states, or
* will be an alias for main system RAM on devices without such support. */
.persist (NOLOAD) :
{
. = ALIGN(4);
*(.persist)
*(.persist.*)
Comment on lines +40 to +41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never understood the convention with these includes, are they both necessary? Wouldn't *(.persist*), or, in this case, simply *(.persist) be sufficient?

Copy link
Member

@aykevl aykevl Mar 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's simply a glob. So .persist matches exactly the string .persist while .persist.* matches anything that starts with .persist.. This emulates somewhat the similar conventions around -ffunction-sections and -fdata-sections but may not be necessary in this case.

. = ALIGN(4);
} >PRAM

/* Start address (in flash) of .data, used by startup code. */
_sidata = LOADADDR(.data);

Expand Down
3 changes: 3 additions & 0 deletions targets/atsamd21.ld
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ MEMORY
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00008000
}

# Allocate 'Persistent RAM' from regular RAM (some chips have specific PRAM)
REGION_ALIAS("PRAM", RAM);

_stack_size = 2K;

INCLUDE "targets/arm.ld"
3 changes: 3 additions & 0 deletions targets/atsamd51.ld
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ MEMORY
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00030000
}

# Allocate 'Persistent RAM' from regular RAM (some chips have specific PRAM)
REGION_ALIAS("PRAM", RAM);

_stack_size = 4K;

INCLUDE "targets/arm.ld"
3 changes: 3 additions & 0 deletions targets/atsamd51j20a.ld
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ MEMORY
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00040000
}

# Allocate 'Persistent RAM' from regular RAM (some chips have specific PRAM)
REGION_ALIAS("PRAM", RAM);

_stack_size = 4K;

INCLUDE "targets/arm.ld"
11 changes: 11 additions & 0 deletions targets/avr.ld
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ SECTIONS
_stack_top = .;
} >RAM

/* Persistent RAM that is not re-initialized on reset. PRAM is either
* a specific area of RAM which is powered during low-power states, or
* will be an alias for main system RAM on devices without such support. */
.persist (NOLOAD) :
{
. = ALIGN(4);
*(.persist)
*(.persist.*)
. = ALIGN(4);
} >PRAM

_sidata = LOADADDR(.data);

.data :
Expand Down
3 changes: 3 additions & 0 deletions targets/circuitplay-bluefruit.ld
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ MEMORY
RAM (xrw) : ORIGIN = 0x20004180, LENGTH = 37K
}

# Allocate 'Persistent RAM' from regular RAM (some chips have specific PRAM)
REGION_ALIAS("PRAM", RAM);

_stack_size = 2K;

/* This value is needed by the Nordic SoftDevice. */
Expand Down
12 changes: 12 additions & 0 deletions targets/esp32.ld
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ MEMORY

/* Instruction RAM. */
IRAM (x) : ORIGIN = 0x40080000, LENGTH = 128K /* Internal SRAM 0 */

/* Persistent RAM. Data storage across low-power states */
PRAM (rw) : ORIGIN = 0x3FF80000, LENGTH = 8K /* RTC FAST Memory */
}

/* The entry point. It is set in the image flashed to the chip, so must be
Expand Down Expand Up @@ -45,6 +48,15 @@ SECTIONS
_stack_top = .;
} >DRAM

/* Persistent RAM that is not re-initialized on wake-from-sleep. */
.persist (NOLOAD) :
{
. = ALIGN(4);
*(.persist)
*(.persist.*)
. = ALIGN(4);
} >PRAM

/* Constant global variables.
* They are loaded in DRAM for ease of use. Eventually they should be stored
* in flash and loaded directly from there but they're kept in RAM to make
Expand Down
9 changes: 9 additions & 0 deletions targets/esp8266.ld
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ SECTIONS
_ebss = ABSOLUTE(.);
} >DRAM

/* Persistent RAM emulated using normal RAM. */
.persist (NOLOAD) :
{
. = ALIGN(4);
*(.persist)
*(.persist.*)
. = ALIGN(4);
} >DRAM

/* Constant literals and code. Loaded into IRAM for now. Eventually, most
* code should be executed directly from flash.
* Note that literals must be before code for the l32r instruction to work.
Expand Down
3 changes: 3 additions & 0 deletions targets/hifive1-qemu.ld
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ MEMORY
RAM (xrw) : ORIGIN = 0x80000000, LENGTH = 0x4000
}

# Allocate 'Persistent RAM' from regular RAM (some chips have specific PRAM)
REGION_ALIAS("PRAM", RAM);

_stack_size = 2K;

INCLUDE "targets/riscv.ld"
3 changes: 3 additions & 0 deletions targets/hifive1b.ld
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ MEMORY
RAM (xrw) : ORIGIN = 0x80000000, LENGTH = 0x4000
}

# Allocate 'Persistent RAM' from regular RAM (some chips have specific PRAM)
REGION_ALIAS("PRAM", RAM);

_stack_size = 2K;

INCLUDE "targets/riscv.ld"
3 changes: 3 additions & 0 deletions targets/lm3s6965.ld
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ MEMORY
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}

# Allocate 'Persistent RAM' from regular RAM (some chips have specific PRAM)
REGION_ALIAS("PRAM", RAM);

_stack_size = 4K;

INCLUDE "targets/arm.ld"
Loading