Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types

From: Alexandre Courbot

Date: Fri Mar 06 2026 - 19:06:21 EST


On Sat Mar 7, 2026 at 12:35 AM JST, Gary Guo wrote:
> On Fri Mar 6, 2026 at 2:32 PM GMT, Alexandre Courbot wrote:
>> On Fri Mar 6, 2026 at 10:20 PM JST, Gary Guo wrote:
>>> I mean not sure `at` gives me that impression at all. It would just let me know
>>> that I am accessing it at a different location. If you omit the `MyRegArray`
>>> part then there's no real indication that this is an array to me.
>>
>> `at` is a function name, we can change it - I picked it because it is
>> short and reasonably descriptive. The point being: we have a unique
>> function that indicates unambigously that we are using a location for
>> an array of registers.
>
> Okay, maybe `at` is not the biggest issue. I just instinctively feel the usage
> example being awkward.
>
> Perhaps the fact that `Reg` exist itself is awkward to me. It looks like a type
> that exists only for things to typecheck, and not itself represent a meaningful
> concept.

After partially implementing your two-args proposal it really turns out
the two alternatives are very similar in essence, with most of the
differences being details like naming and scope.

The one fundamental difference is that it didn't occur to me that we
could resolve a generic argument for a function in first position of
write using another argument, hence I went for:

bar.write(location_builder(location_info, value));

but it is also possible to do

bar.write(location_builder(location_info), value);

And that's really the main difference. All the rest is mostly details
about whether `location_builder` is an associated function of a type, a
method of a ZST, or just a module-level function. From the point of view
of `Io`, none of this matters as long as it gets an `IoLoc` that is
compatible with the value. IOW, each Io submodule can provide the
location builders that make sense for it.

The `Reg` thing in my previous email was just a way to provide a scope
for these location builders, but in retrospect a module-level function
seems better to me if we want to keep things concise. So if we turn the
location builders into module-level methods we could have:

use kernel::io::register as reg;

bar.write(reg::at(10), SOME_ARRAY_SCRATCH::foo());

Or does

bar.write(SOME_ARRAY_SCRATCH::foo(), reg::at(10));

Flow better now?

>
>>
>> You seem to reject this design because the syntax isn't obvious and
>> natural to you at first read. We are trying to build something new, so
>> of course if will look a bit alien at the first encounter. That's why we
>> have documentation, and I think it is not very difficult to wrap your
>> head around it after seeing a few examples.
>>
>>>
>>> If `at` is only for array, how would you represent the case where the same type
>>> is being used in multiple registers?
>>
>> That's not something that is supported by the register macro currently
>> (probably not a big change, but not something I will do in this series).
>>
>> But to try and answer your question, such register types would not have
>> an `IoLoc` implementation of their own and would need to have their
>> location constructed explicitly. In accordance, the construction of
>> their value would not bear any location information; thus there would be
>> no redundancy.
>>
>> We do have a case that is pretty close to this with relative registers.
>> These are accessed like this (real examples):
>>
>> bar.write(Reg::of::<Gsp>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>>
>> and
>>
>> bar.write(Reg::of::<Sec2>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>
> Relative registers are something that on my list to eliminate and replace with
> projections, so I don't particular care about how it looks like.

That's interesting, I thought register arrays would be easier to replace
than relative registers, I really need to take a closer look.

>
>>
>> But for register types that can be used at several arbitrary locations,
>> I think this would be even simpler. The different locations would just
>> need to implement `IoLoc<T>`, where `T` is the shared register type.
>> Then, considering that the two-arguments version is called `write_at`,
>> you can simply do:
>>
>> bar.write_at(REG_LOCATION, reg_value);
>
> Having `write_at` as the name of the two-argument version is okay to me.

I picked that for illustrative purposes, but `write` should be the
version that is going to be the most used. And if we remove the value
from the location builder, then the most used version is clearly going
to be the two arguments one.

The one argument variant would then become a shortcut - `put` sounds
adequate to me, but `write_val` also works.

>
>>
>> ... but this design also makes this possible:
>>
>> bar.write((REG_LOCATION, reg_value));
>
> I considered about this, but IMO this looks awkward.

Funny how you spell "elegant". :)

>
>>
>> Tuples of (location, value) do implement `IoLoc` themselves, so we can
>> use this little trick to support a 2-arguments syntax with a single
>> method.
>>
>>>
>>>>
>>>>>
>>>>> If you want to make things more explicit you could also have
>>>>> `bar.write(at_array(10), ...)` or something similar.
>>>>
>>>> Is it possible to generate an `IoLoc<T>` without having `T` mentioned
>>>> anywhere in the call to `at_array`?
>>>
>>> Exactly same as the `impl IoLoc<REG> for usize`:
>>>
>>> struct AtArray(usize);
>>>
>>> impl IoLoc<REG> for AtArray {
>>> ...
>>> }
>>
>> Right, but can the correct `REG` be inferred when the call to `at_array`
>> doesn't bear that information? The type inferred by the second argument
>> would have to be propagated to the first. Guess I'll try and see.
>
> RE: inference and bounds checking issue that you mentioned in another email, I
> think you can have
>
> fn at_array(i: usize) -> Result<AtArray<T>> { .. }
>
> and
>
> impl IoReg<REG> for AtArray<REG> {}
>
> The type inference here is no different to `Reg::at`.

Yes, this clearly works and then it actually becomes `RegisterArrayLoc`,
which already exists and also implements `IoLoc`. This convergence looks
like positive sign to me.

>
>>
>>>
>>>>
>>>>>
>>>>> For the array case I really think trying to shove everything into a single
>>>>> argument is a footgun. The type of value in this case *doesn't* tell us the
>>>>> location, and the location needs to be explicit.
>>>>
>>>> bar.write(Reg::at(10, regs::MyRegArray::foo()))
>>>>
>>>> "write the constructed value at the 10th position of the `MyRegArray`
>>>> register array"
>>>>
>>>> What is missing here?
>>>
>>> This is completely un-natural if I try to read it with fresh mind (try to forget
>>> about implementation details for a second).
>>
>> That's what documentation is for. Please give it a fair chance and ask
>> yourself: would it still look unnatural after working with it for
>> 20 minutes?
>>
>>>
>>> `MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
>>> single value and not an array. "at" here is saying that the register is at a
>>> specific location and doesn't really indicate the array nature.
>>>
>>> This is why I insist that I would prefer an explicit location
>>>
>>> bar.write(REG_ARRAY.at(10), Reg::foo())
>>>
>>> would have no ambiguity whatsoever about user's intent.
>>
>> IIUC `REG_ARRAY` would be a const ZST and `at` a method returning an
>> `AtArray(usize)`? I still have doubts that its generic type could be
>> inferred automatically but it's worth giving it a go.
>>
>> If that works, then I assume fixed register writes would look like
>>
>> bar.write(FIXED, Reg::foo());
>>
>> Unless we have a specialized `write` variant for them.
>
> That, or `()` as I mentioned.

Wouldn't it look... awkward? :)

But if we can agree on

bar.put(Reg::foo());

or even

bar.write_val(Reg::foo());

Then I believe we are getting close to something that can make everyone
happy (pending Danilo's blessing). The `()` syntax can also be supported
for generic code.

I'll try to speedrun the remainder of the implementation and send a new
revision for review, but AFAICT this ticks all the boxes.