What is the difference between *(*uintptr) and **(**uintptr)

Introduction

A function value in Go denotes the funtion's code. From far, it is a pointer to the function's code. It acts like a pointer.

From a closer look, it's a struct something like this (taken from runtime/runtime2.go):

type funcval struct {
    fn uintptr
    // variable-size, fn-specific data here
}

So a function value holds a pointer to the function's code as its first field which we can dereference to get to the function's code.

Explaining your example

To get the address of a function('s code), you may use reflection:

fmt.Println("test() address:", reflect.ValueOf(test).Pointer())

To verify we get the right address, we may use runtime.FuncForPC().

This gives the same value as your funcPC() function. See this example:

fmt.Println("reflection test() address:", reflect.ValueOf(test).Pointer())
fmt.Println("funcPC(test):", funcPC(test))
fmt.Println("funcPC1(test):", funcPC1(test))

fmt.Println("func name for reflect ptr:",
    runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name())

It outputs (try it on the Go Playground):

reflection test() address: 919136
funcPC(test): 919136
funcPC1(test): 1357256
func name for reflect ptr: main.test

Why? Because a function value itself is a pointer (it just has a different type than a pointer, but the value it stores is a pointer) that needs to be dereferenced to get the code address.

So what you would need to get this to uintptr (code address) inside funcPC() would be simply:

func funcPC(f func()) uintptr {
    return *(*uintptr)(f) // Compiler error!
}

Of course it doesn't compile, conversion rules do not allow converting a function value to *uintptr.

Another attempt may be to convert it first to unsafe.Pointer, and then to *uintptr:

func funcPC(f func()) uintptr {
    return *(*uintptr)(unsafe.Pointer(f)) // Compiler error!
}

Again: conversion rules do not allow converting function values to unsafe.Pointer. Any pointer type and uintptr values may be converted to unsafe.Pointer and vice versa, but not function values.

That's why we have to have a pointer value to start with. And what pointer value could we have? Yes, the address of f: &f. But this will not be the function value, this is the address of the f parameter (local variable). So &f schematically is not (just) a pointer, it's a pointer to pointer (that both need to be dereferenced). We can still convert it to unsafe.Pointer (because any pointer value qualifies for that), but it's not the function value (as a pointer), but a pointer to it.

And we need the code address from the function value, so we have to use **uintptr to convert the unsafe.Pointer value, and we have to use 2 dereferences to get the address (and not just the pointer in f).

This is exactly why funcPC1() gives a different, unexpected, incorrect result:

func funcPC1(f func()) uintptr {
    return *(*uintptr)(unsafe.Pointer(&f))
}

It returns the pointer in f, not the actual code address.