Open diegonehab opened 4 months ago
After some thought, here is a possibility.
A machine is halted if tohost
has dev=HTIF_DEV_HALT
, cmd=HTIF_HALT_CMD_HALT
, and (data & 1)
.
A machine is yielded manually if tohost
has dev = HTIF_DEV_YIELD
and cmd = HTIF_YIELD_CMD_MANUAL
.
We change the part of the interpret loop that checks for fixed-point yield/halt to the following:
tohost = read_tohost();
if (halted(tohost)) { // dev=HTIF_DEV_HALT, cmd=HTIF_HALT_CMD_HALT, (data & 1)
return break_reason::halted;
}
if (yielded(tohost)) { // dev = HTIF_DEV_YIELD, cmd = HTIF_YIELD_CMD_MANUAL
formhost = read_fromhost();
if (!yielded(fromhost)) { // unless host wrote a response to this htif-yield command...
return break_reason::yielded_manually;
}
// here we know the host responded, so we clear tohost and the machine is not yielded anymore
write_tohost(0);
}
We change the HTIF protocol to be as follows:
From the inside, to use HTIF, guest code writes dev+cmd+data to tohost
. HTIF device itself then clears fromhost
. If device is halt or yield, the run()
returns. From the outside, host can check tohost
to see what is up. To respond to a yield, host copies dev+cmd to fromhost
, but changes the data as desired and resumes the machine. If device was yield and fromhost
has the right combination of dev+cmd
, the machine clears tohost
. From the inside, guest code reads the response in fromhost
.
We also change write_tohost()
to guard against the removal of a halted combination of dev+cmd even from the outside.
Thinking ahead even more, when we have "multi-machines", there will be a situation in which one machine is servicing a GIO request from another machine. The concrete situation is this.
A machine wants to use some external sequencer. It really only wanted to receive inputs addressed to it on that sequencer, one after the other. Something specific like a GIO with (domain, id) = (sequencer domain, application address there). It would then be woken up with the next input sent to its address on that sequencer.
This is great for Sunodo. It would keep the machine sleeping, maybe even stored to disk, until some service it is running detects a new input for that application. It would then wake the machine up, feed it the input, and so on.
Unfortunately, from the point of view of Dave, this might be impossible or hard or too complicated to do. Can Dave, on L1, figure out what the new input is for that application on the sequencer? So here is what Dave would prefer.
The machine itself has to do the filtering. So it would do something generic a GIO (domain, id) = (sequencer next block, null). With the next block, it would do a bunch of GIO calls and "dehash" its way through the sequencer data-structures searching for its next input, if any.
The complicated logic would be inside the machine, so Dave can be a lot simpler.
The problem for sunodo is that it would have to wake the Cartesi Machine up for every sequencer block. Mostly, this would be a waste of time, because there would be new input for it. Even if there was an input, it would be a lot less efficient. First, the routing-by-dehashing is awkward at best, and is running inside the emulator. Second, if run outside, it would not only be faster, but could be done only once for all applications managed by Sunodo.
We could make both Dave and Sunodo happy if we had multi-machines.
Basically, we a main sub-machine, which would be doing the specific (sequencer domain, application address there) requests. It would have a domain sub-machine that would service requests from that domain. To do so, it would do the generic (sequencer next block, null) request followed by the "dehashing" crap. Let's call this "Dave's mode". When running the multi-machine this way, the only requests leaking out are the low-level ones.
Now let's deal with "Sunodo's mode". The key is that, once the domain sub-machine is done running, the only trace it leaves behind is the response to the generic call. What this means is that, if the host can trap the call made by the main machine, it can skip running the sub-machine and service the request itself. So the only requests leaking out are the high-level ones.
To make this work, we need a few things.
There are details missing, of course, but I think this would work.
Context
Controlling a machine engaged with rollups involves reading and writing to a bunch of different machine CSRs. The introduction of the
send_cmio_response
simplified, but it is still complicated.run
now returns thebreak_reason
, which simplifies it further. But now there are unnecessary redundancies that may cause confusion.Possible solutions
At the moment,
iflags
has fieldsPRV
and theX
,Y
, andH
flags.PRV
really is something internal that the host should never mess with (the current machine privilege level).Let's promote
iflags.PRV
to its own full CSRiprv
. This will simplify the state-access implementations, since they won't have to do field manipulation there anymore.X
,Y
, andH
, on the other hand, are things the host needs to look at and, in the case ofY
, change.There is never a case in which more than one of these flags is set. They are always set via HTIF from the inside.
In the case of
X
andY
, the host will also need to look intohtif.tohost
. This is becausehtif.tohost
contains thereason
for the yield and the amount of data written totx_buffer
.X
is set when the machine returns from an automatic yield. Let's removeX
altogether, since the machinerun
already returns this as a break reason andX
is cleared automatically.Y
when it returns from a manual yield.H
when it is permanently halted.Let's relocate
Y
andH
tohtif.tohost
. With some reorganization there, we can make this happen. (This will also simplify HTIF implementation, since it won't need to change the iflags register anymore.) Let's renameY
toYM
to make the distinction obvious. It's not a generic yield flag, but rather a Manual Yield flag.Perhaps we can be smart and use the
device+cmd
fields together as "the flag", with a few changes to make them uniquely identify the halt and the manual yields.There already are many
WARL
CSRs that prevent certain bits from being changed.htif.tohost
would be one of these. IfH
is set, it would remain set forever. I think we can even use a write tohtif.fromhost
to clearYM
, saving the need to modifyhtif.tohost
when returning from a manual yield.