Skip to content

Instantly share code, notes, and snippets.

@sethp
Created June 24, 2024 00:27
Show Gist options
  • Save sethp/3f158929160935d83a4f976f3b752d69 to your computer and use it in GitHub Desktop.
Save sethp/3f158929160935d83a4f976f3b752d69 to your computer and use it in GitHub Desktop.
context_t example mirroring
#include <fesvr/context.h>
#include <cstdio>
context_t top;
context_t initial;
context_t always;
context_t bottom;
context_t *ret;
context_t *target;
void Top(void*) {
// a.
target = context_t::current();
initial.switch_to();
always.switch_to();
always.switch_to();
always.switch_to();
always.switch_to();
}
void Initial(void*) {
// b.
// target = context_t::current();
printf("hello from initial\n");
while (true) {
bottom.switch_to();
printf(" back to initial\n");
top.switch_to();
}
}
void Always(void*) {
// c.
// target = context_t::current();
printf("hello from always\n");
while (true) {
bottom.switch_to();
printf(" back to always\n");
top.switch_to();
}
}
void Bottom(void*) {
while (true) target->switch_to();
}
int main() {
top.init(Top, NULL);
initial.init(Initial, NULL);
always.init(Always, NULL);
bottom.init(Bottom, NULL);
context_t *ret = context_t::current();
//^ side effect: invents a `main` context
//^ (which is distinct from the four above)
//^ and leaks it, so we don't fail the
//^ `assert (this != cur)` in the destructor
top.switch_to();
ret->switch_to(); //< immediately returns
//< side effect: sets the `cur`
//< thread-local to `ret`, so
//< we can destruct `top`
printf("all done!\n");
return 0;
}

build & run

# with a working directory wherever the conda env lives (i.e. `chipyard` root)
c++ ./scratch/context.cc -g -o ./scratch/context -I "$RISCV/include" \
  -L.conda-env/riscv-tools/lib -Wl,-rpath,.conda-env/riscv-tools/lib \
  ./toolchains/riscv-tools/riscv-isa-sim/fesvr/context.cc \
&& ./scratch/context

ought to produce output like:

hello from initial
hello from always
 back to always
 back to always
all done!

Which is perhaps not quite what we'd expect from a cursory read of context.cc.

Well, we never see back to initial and only half as many of the back to always as we might've expected because top and target are the same:

$ gdb ./scratch/context -ex 'b Bottom' -ex 'run'
...
hello from initial                                                                                         

Breakpoint 1, Bottom () at ./scratch/context.cc:45
45		while (true) target->switch_to();
(gdb) p target
$1 = (context_t *) 0x5555555580a0 <top>
(gdb) c
Continuing.
hello from always

Breakpoint 1, Bottom () at ./scratch/context.cc:45
45		while (true) target->switch_to();
(gdb) p target
$1 = (context_t *) 0x5555555580a0 <top>

So, bottom.switch_to() and top.switch_to() are aliases: that is, the line in Initial that says bottom.switch_to() will send control flow back to Top, where it'll continue to execute on the line after initial.switch_to().

Similarly, we get half as many prints because Always loop is effectively:

while (true) {
  top.switch_to();
  printf(" back to always\n");
  top.switch_to();
}

meaning, we have to resume the Always task twice to see the first print, and then the next always.switch_to() will trigger the loop back-edge and stop.

The result I was expecting:

hello from initial
 back to initial
hello from always
 back to always
 back to always
 back to always
 back to always
all done!

can be obtained by overwriting target at points b. and c. instead of (or, in addition to) a..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment