Skip to content

Refactor volatile operations (try 2) #334

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

Merged
merged 4 commits into from
May 14, 2019
Merged
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
28 changes: 21 additions & 7 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ func (c *Compiler) Compile(mainPath string) []error {
},
ShouldOverlay: func(path string) bool {
switch path {
case "machine", "os", "reflect", "runtime", "sync":
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync":
return true
default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
Expand Down Expand Up @@ -696,6 +696,14 @@ func (c *Compiler) parseFunc(frame *Frame) {
frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
}

// Some functions have a pragma controlling the inlining level.
switch frame.fn.Inline() {
case ir.InlineHint:
// Add LLVM inline hint to functions with //go:inline pragma.
inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
frame.fn.LLVMFn.AddFunctionAttr(inline)
}

// Add debug info, if needed.
if c.Debug {
if frame.fn.Synthetic == "package initializer" {
Expand Down Expand Up @@ -907,6 +915,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
case *ssa.Store:
llvmAddr := c.getValue(frame, instr.Addr)
llvmVal := c.getValue(frame, instr.Val)
c.emitNilCheck(frame, llvmAddr, "store")
if c.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
// nothing to store
return
Expand Down Expand Up @@ -1117,17 +1126,22 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e

// Try to call the function directly for trivially static calls.
if fn := instr.StaticCallee(); fn != nil {
switch fn.RelString(nil) {
case "device/arm.ReadRegister":
name := fn.RelString(nil)
switch {
case name == "device/arm.ReadRegister":
return c.emitReadRegister(instr.Args)
case "device/arm.Asm", "device/avr.Asm":
case name == "device/arm.Asm" || name == "device/avr.Asm":
return c.emitAsm(instr.Args)
case "device/arm.AsmFull", "device/avr.AsmFull":
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull":
return c.emitAsmFull(frame, instr)
case "device/arm.SVCall0", "device/arm.SVCall1", "device/arm.SVCall2", "device/arm.SVCall3", "device/arm.SVCall4":
case strings.HasPrefix(name, "device/arm.SVCall"):
return c.emitSVCall(frame, instr.Args)
case "syscall.Syscall", "syscall.Syscall6", "syscall.Syscall9":
case strings.HasPrefix(name, "syscall.Syscall"):
return c.emitSyscall(frame, instr)
case strings.HasPrefix(name, "runtime/volatile.Load"):
return c.emitVolatileLoad(frame, instr)
case strings.HasPrefix(name, "runtime/volatile.Store"):
return c.emitVolatileStore(frame, instr)
}

targetFunc := c.ir.GetFunction(fn)
Expand Down
26 changes: 26 additions & 0 deletions compiler/volatile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package compiler

// This file implements volatile loads/stores in runtime/volatile.LoadT and
// runtime/volatile.StoreT as compiler builtins.

import (
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)

func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
c.emitNilCheck(frame, addr, "deref")
val := c.builder.CreateLoad(addr, "")
val.SetVolatile(true)
return val, nil
}

func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
val := c.getValue(frame, instr.Args[1])
c.emitNilCheck(frame, addr, "deref")
store := c.builder.CreateStore(val, addr)
store.SetVolatile(true)
return llvm.Value{}, nil
}
33 changes: 28 additions & 5 deletions ir/ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ type Program struct {
type Function struct {
*ssa.Function
LLVMFn llvm.Value
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
interrupt bool // go:interrupt
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
interrupt bool // go:interrupt
inline InlineType // go:inline
}

// Global variable, possibly constant.
Expand Down Expand Up @@ -69,6 +70,21 @@ type Interface struct {
Type *types.Interface
}

type InlineType int

// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
InlineDefault InlineType = iota

// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
InlineHint
)

// Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
comments := map[string]*ast.CommentGroup{}
Expand Down Expand Up @@ -279,6 +295,8 @@ func (f *Function) parsePragmas() {
}
f.linkName = parts[1]
f.exported = true
case "//go:inline":
f.inline = InlineHint
case "//go:interrupt":
if len(parts) != 2 {
continue
Expand Down Expand Up @@ -332,6 +350,11 @@ func (f *Function) IsInterrupt() bool {
return f.interrupt
}

// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
}

// Return the link name for this function.
func (f *Function) LinkName() string {
if f.linkName != "" {
Expand Down
100 changes: 50 additions & 50 deletions src/machine/machine_atmega.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,31 @@ import (
func (p GPIO) Configure(config GPIOConfig) {
if config.Mode == GPIO_OUTPUT { // set output bit
if p.Pin < 8 {
*avr.DDRD |= 1 << p.Pin
avr.DDRD.SetBits(1 << p.Pin)
} else {
*avr.DDRB |= 1 << (p.Pin - 8)
avr.DDRB.SetBits(1 << (p.Pin - 8))
}
} else { // configure input: clear output bit
if p.Pin < 8 {
*avr.DDRD &^= 1 << p.Pin
avr.DDRD.ClearBits(1 << p.Pin)
} else {
*avr.DDRB &^= 1 << (p.Pin - 8)
avr.DDRB.ClearBits(1 << (p.Pin - 8))
}
}
}

// Get returns the current value of a GPIO pin.
func (p GPIO) Get() bool {
if p.Pin < 8 {
val := *avr.PIND & (1 << p.Pin)
val := avr.PIND.Get() & (1 << p.Pin)
return (val > 0)
} else {
val := *avr.PINB & (1 << (p.Pin - 8))
val := avr.PINB.Get() & (1 << (p.Pin - 8))
return (val > 0)
}
}

func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
func (p GPIO) getPortMask() (*avr.Register8, uint8) {
if p.Pin < 8 {
return avr.PORTD, 1 << p.Pin
} else {
Expand All @@ -45,64 +45,64 @@ func (p GPIO) getPortMask() (*avr.RegValue, uint8) {
// InitPWM initializes the registers needed for PWM.
func InitPWM() {
// use waveform generation
*avr.TCCR0A |= avr.TCCR0A_WGM00
avr.TCCR0A.SetBits(avr.TCCR0A_WGM00)

// set timer 0 prescale factor to 64
*avr.TCCR0B |= avr.TCCR0B_CS01 | avr.TCCR0B_CS00
avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00)

// set timer 1 prescale factor to 64
*avr.TCCR1B |= avr.TCCR1B_CS11
avr.TCCR1B.SetBits(avr.TCCR1B_CS11)

// put timer 1 in 8-bit phase correct pwm mode
*avr.TCCR1A |= avr.TCCR1A_WGM10
avr.TCCR1A.SetBits(avr.TCCR1A_WGM10)

// set timer 2 prescale factor to 64
*avr.TCCR2B |= avr.TCCR2B_CS22
avr.TCCR2B.SetBits(avr.TCCR2B_CS22)

// configure timer 2 for phase correct pwm (8-bit)
*avr.TCCR2A |= avr.TCCR2A_WGM20
avr.TCCR2A.SetBits(avr.TCCR2A_WGM20)
}

// Configure configures a PWM pin for output.
func (pwm PWM) Configure() {
if pwm.Pin < 8 {
*avr.DDRD |= 1 << pwm.Pin
avr.DDRD.SetBits(1 << pwm.Pin)
} else {
*avr.DDRB |= 1 << (pwm.Pin - 8)
avr.DDRB.SetBits(1 << (pwm.Pin - 8))
}
}

// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a
// 8-bit value ranging from 0 to 255.
func (pwm PWM) Set(value uint16) {
value8 := value >> 8
value8 := uint8(value >> 8)
switch pwm.Pin {
case 3:
// connect pwm to pin on timer 2, channel B
*avr.TCCR2A |= avr.TCCR2A_COM2B1
*avr.OCR2B = avr.RegValue(value8) // set pwm duty
avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1)
avr.OCR2B.Set(value8) // set pwm duty
case 5:
// connect pwm to pin on timer 0, channel B
*avr.TCCR0A |= avr.TCCR0A_COM0B1
*avr.OCR0B = avr.RegValue(value8) // set pwm duty
avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1)
avr.OCR0B.Set(value8) // set pwm duty
case 6:
// connect pwm to pin on timer 0, channel A
*avr.TCCR0A |= avr.TCCR0A_COM0A1
*avr.OCR0A = avr.RegValue(value8) // set pwm duty
avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1)
avr.OCR0A.Set(value8) // set pwm duty
case 9:
// connect pwm to pin on timer 1, channel A
*avr.TCCR1A |= avr.TCCR1A_COM1A1
avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1)
// this is a 16-bit value, but we only currently allow the low order bits to be set
*avr.OCR1AL = avr.RegValue(value8) // set pwm duty
avr.OCR1AL.Set(value8) // set pwm duty
case 10:
// connect pwm to pin on timer 1, channel B
*avr.TCCR1A |= avr.TCCR1A_COM1B1
avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1)
// this is a 16-bit value, but we only currently allow the low order bits to be set
*avr.OCR1BL = avr.RegValue(value8) // set pwm duty
avr.OCR1BL.Set(value8) // set pwm duty
case 11:
// connect pwm to pin on timer 2, channel A
*avr.TCCR2A |= avr.TCCR2A_COM2A1
*avr.OCR2A = avr.RegValue(value8) // set pwm duty
avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1)
avr.OCR2A.Set(value8) // set pwm duty
default:
panic("Invalid PWM pin")
}
Expand All @@ -121,19 +121,19 @@ func (i2c I2C) Configure(config I2CConfig) {
}

// Activate internal pullups for twi.
*avr.PORTC |= (avr.DIDR0_ADC4D | avr.DIDR0_ADC5D)
avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D))

// Initialize twi prescaler and bit rate.
*avr.TWSR |= (avr.TWSR_TWPS0 | avr.TWSR_TWPS1)
avr.TWSR.SetBits((avr.TWSR_TWPS0 | avr.TWSR_TWPS1))

// twi bit rate formula from atmega128 manual pg. 204:
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
// NOTE: TWBR should be 10 or higher for master mode.
// It is 72 for a 16mhz board with 100kHz TWI
*avr.TWBR = avr.RegValue(((CPU_FREQUENCY / config.Frequency) - 16) / 2)
avr.TWBR.Set(uint8(((CPU_FREQUENCY / config.Frequency) - 16) / 2))

// Enable twi module.
*avr.TWCR = avr.TWCR_TWEN
avr.TWCR.Set(avr.TWCR_TWEN)
}

// Tx does a single I2C transaction at the specified address.
Expand Down Expand Up @@ -162,10 +162,10 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error {
// start starts an I2C communication session.
func (i2c I2C) start(address uint8, write bool) {
// Clear TWI interrupt flag, put start condition on SDA, and enable TWI.
*avr.TWCR = (avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN)
avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN))

// Wait till start condition is transmitted.
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
}

// Write 7-bit shifted peripheral address.
Expand All @@ -179,36 +179,36 @@ func (i2c I2C) start(address uint8, write bool) {
// stop ends an I2C communication session.
func (i2c I2C) stop() {
// Send stop condition.
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)

// Wait for stop condition to be executed on bus.
for (*avr.TWCR & avr.TWCR_TWSTO) == 0 {
for (avr.TWCR.Get() & avr.TWCR_TWSTO) == 0 {
}
}

// writeByte writes a single byte to the I2C bus.
func (i2c I2C) writeByte(data byte) {
// Write data to register.
*avr.TWDR = avr.RegValue(data)
avr.TWDR.Set(data)

// Clear TWI interrupt flag and enable TWI.
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT)
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT)

// Wait till data is transmitted.
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
}
}

// readByte reads a single byte from the I2C bus.
func (i2c I2C) readByte() byte {
// Clear TWI interrupt flag and enable TWI.
*avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)

// Wait till read request is transmitted.
for (*avr.TWCR & avr.TWCR_TWINT) == 0 {
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
}

return byte(*avr.TWDR)
return byte(avr.TWDR.Get())
}

// UART on the AVR.
Expand All @@ -226,32 +226,32 @@ func (uart UART) Configure(config UARTConfig) {
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
ps := ((CPU_FREQUENCY+config.BaudRate*8)/(config.BaudRate*16) - 1)
*avr.UBRR0H = avr.RegValue(ps >> 8)
*avr.UBRR0L = avr.RegValue(ps & 0xff)
avr.UBRR0H.Set(uint8(ps >> 8))
avr.UBRR0L.Set(uint8(ps & 0xff))

// enable RX, TX and RX interrupt
*avr.UCSR0B = avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0
avr.UCSR0B.Set(avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0)

// 8-bits data
*avr.UCSR0C = avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00
avr.UCSR0C.Set(avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00)
}

// WriteByte writes a byte of data to the UART.
func (uart UART) WriteByte(c byte) error {
// Wait until UART buffer is not busy.
for (*avr.UCSR0A & avr.UCSR0A_UDRE0) == 0 {
for (avr.UCSR0A.Get() & avr.UCSR0A_UDRE0) == 0 {
}
*avr.UDR0 = avr.RegValue(c) // send char
avr.UDR0.Set(c) // send char
return nil
}

//go:interrupt USART_RX_vect
func handleUSART_RX() {
// Read register to clear it.
data := *avr.UDR0
data := avr.UDR0.Get()

// Ensure no error.
if (*avr.UCSR0A & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
if (avr.UCSR0A.Get() & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
// Put data from UDR register into buffer.
UART0.Receive(byte(data))
}
Expand Down
Loading