In the minimal self-contained example below, the functions f_hardcoded, f_with_self and f_handover do the same thing hardware-wise. (I have tested this on my Sinara system.) Do they also function identically software-wise? Or does one have more overhead than the others? Does one of them (or 2 of them) trigger an RPC call?

from artiq.experiment import EnvExperiment, kernel, rpc
from artiq.language.types import TNone, TBool, TInt32, TStr, TList
from artiq.language.units import ns, us, ms, s

@kernel
def f_handover(led_ch_list):
    for ch in led_ch_list:
        ch.on()

class TestSatellites(EnvExperiment):

    def build(self):
        self.setattr_device("core")
        self.setattr_device("led0")
        self.setattr_device("led1")
        self.led_ch_list = [self.led0, self.led1]

    @kernel
    def f_with_self(self):
        for ch in self.led_ch_list:
            ch.on()

    @kernel
    def f_hardcoded(self):
        self.led0.on()
        self.led1.on()
    
    @kernel
    def run(self):
        self.core.reset()
        # self.f_hardcoded()
        # self.f_with_self()
        f_handover(led_ch_list = self.led_ch_list)
        delay(1*s)
        self.led0.off()
        self.led1.off()

That depends on the choices the compiler makes. If the compiler does a good job all three will end up being the same. Otherwise one could expect f_hardcoded to be the fastest. Disassemble and evaluate yourself and/or use the core analyzer to measure/benchmark. YMMV

No RPCs though.

Ahhh, so if

class MyExperiment(EnvExperiment):
    def build(self):
        compile_time_value = [...]
        setattr(self, "label", compile_time_value)

then the compiler gives each compiled @kernel function a separate copy of self.label with value compile_time_value?

And if any @rpc or @kernel function changes the value of self.label at run-time via self.label = new_run_time_value, then all (other) @kernel functions are unaffected and retain the old value compile_time_value?