Can't find free registers #36

Closed
opened 2025-09-14 13:41:43 +00:00 by Furkan · 2 comments

Error:
panic: no free registers

goroutine 67 [running]:
git.urbach.dev/cli/q/src/codegen.(*Function).findFreeRegister(0xc0001023d0, 0xc0003f1118)

Code:

import fs
import io
import mem
import strings

read_32(arr string) -> int {
    return (arr[0] << 24) | (arr[1] << 16) | (arr[2] << 8) | arr[3] as int
}

main() {
	source, err := fs.readFile("image.qoi")

	if err != 0 {
		io.write("error reading file\n")
		return
	}

	if !strings.equal("qoif", source[..4]) {
		io.write("Wrong magic numbers")
	    mem.free(source)
		return
	}

	width := read_32(source[4..8])
	height := read_32(source[8..12])
	channels := source[12] as int
	colorspace := source[13] as int

    io.write(width)
    io.write("\n")
    io.write(height)
    io.write("\n")
    io.write(channels)
    io.write("\n")
    io.write(colorspace)
    io.write("\n")

	mem.free(source)
}
Error: panic: no free registers goroutine 67 [running]: git.urbach.dev/cli/q/src/codegen.(*Function).findFreeRegister(0xc0001023d0, 0xc0003f1118) Code: ``` import fs import io import mem import strings read_32(arr string) -> int { return (arr[0] << 24) | (arr[1] << 16) | (arr[2] << 8) | arr[3] as int } main() { source, err := fs.readFile("image.qoi") if err != 0 { io.write("error reading file\n") return } if !strings.equal("qoif", source[..4]) { io.write("Wrong magic numbers") mem.free(source) return } width := read_32(source[4..8]) height := read_32(source[8..12]) channels := source[12] as int colorspace := source[13] as int io.write(width) io.write("\n") io.write(height) io.write("\n") io.write(channels) io.write("\n") io.write(colorspace) io.write("\n") mem.free(source) } ```
Owner

This is called register spilling and it's on the roadmap.

In the meantime, you can try to reduce the variables that are alive at the same time.

Here are 3 tips:


1:

	width := read_32(source[4..8])
	height := read_32(source[8..12])
	channels := source[12] as int
	colorspace := source[13] as int

    // width, height, channels and colorspace are all alive here

    io.write(width)
    io.write("\n")
    io.write(height)
    io.write("\n")
    io.write(channels)
    io.write("\n")
    io.write(colorspace)
    io.write("\n")

This could potentially avoid register spilling (a stack allocation) if you rewrite it to let the variables die early:

    width := read_32(source[4..8])
    io.write(width)
    io.write("\n")
    
    height := read_32(source[8..12])
    io.write(height)
    io.write("\n")
    
    channels := source[12] as int
    io.write(channels)
    io.write("\n")
    
    colorspace := source[13] as int
    io.write(colorspace)
    io.write("\n")

However that only works for this particular case. In reality you'd want all of them to be alive.


2:

Sometimes splitting a big function into multiple functions also works, although (!) the reverse effect is also possible. So you have to apply this logic:

Does the function already call other functions?

  • Yes? Splitting into smaller functions is good.
  • No? Splitting into smaller functions can have the reverse effect (give you less registers to work with).

3:

Another way to reduce the number of live values is to just hold a single pointer value to a struct in memory.
Your pointer lives in a single register and gives you access to everything you need.
This is basically manual spilling by using new(Struct).

This is called **register spilling** and it's on the roadmap. In the meantime, you can try to reduce the variables that are alive at the same time. Here are 3 tips: --- 1: ``` width := read_32(source[4..8]) height := read_32(source[8..12]) channels := source[12] as int colorspace := source[13] as int // width, height, channels and colorspace are all alive here io.write(width) io.write("\n") io.write(height) io.write("\n") io.write(channels) io.write("\n") io.write(colorspace) io.write("\n") ``` This could potentially avoid register spilling (a stack allocation) if you rewrite it to let the variables die early: ``` width := read_32(source[4..8]) io.write(width) io.write("\n") height := read_32(source[8..12]) io.write(height) io.write("\n") channels := source[12] as int io.write(channels) io.write("\n") colorspace := source[13] as int io.write(colorspace) io.write("\n") ``` However that only works for this particular case. In reality you'd want all of them to be alive. --- 2: Sometimes splitting a big function into multiple functions also works, although (!) the reverse effect is also possible. So you have to apply this logic: Does the function already call other functions? - Yes? Splitting into smaller functions is good. - No? Splitting into smaller functions can have the reverse effect (give you less registers to work with). --- 3: Another way to reduce the number of live values is to just hold a single pointer value to a struct in memory. Your pointer lives in a single register and gives you access to everything you need. This is basically manual spilling by using `new(Struct)`.
Author

Thank for the explanation. Since this was alredy on roadmap issue I think we can close this issue

Thank for the explanation. Since this was alredy on roadmap issue I think we can close this issue
Sign in to join this conversation.
No milestone
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
cli/q#36
No description provided.