sb0 changed the topic of #m-labs to: ARTIQ, Migen, MiSoC, Mixxeo & other M-Labs projects :: fka #milkymist :: Logs http://irclog.whitequark.org/m-labs
sb0 has quit [Ping timeout: 240 seconds]
<rjo>
whitequark: imagine you have a kernel that is complicated, consists of quite a bit of code (both source and compiled) takes long time to compile, but little time to execute (many pulses), and is called frequently by other kernels.
<whitequark>
rjo: does it have to be a separate kernel?
<rjo>
whitequark: naively i would think that the objects' lifetimes is just the lifetime of the kernel, there are no implicit host objects (all handled explicitly by rpcs)
<rjo>
whitequark: yes. it is called often and too expensive to be compiled and uploaded with every kernel that calls it.
<whitequark>
rjo: kernels currently assume a LIFO discipline
<whitequark>
er
<whitequark>
scratch that
<whitequark>
rjo: "it is called often and too expensive to be compiled" this is a wrong line of reasoning
<whitequark>
i'm not asking you to tell me how to implement it, i'm asking what do you want
<rjo>
at least from at the ARTIQlang level kernels can call each other. where is the LIFO there?
<whitequark>
call stack is LIFO
<rjo>
i want "dynamic additions to the runtime"
<rjo>
ah yes. there.
<whitequark>
ok, that is much better, that allows me to design it to actual constraints
<whitequark>
how dynamic? let's say I would like to introduce some restrictions, as a thought experiment
<whitequark>
obviously adding new code is a must
<whitequark>
what about adding new host objects?
<rjo>
more specifically an example: we have persistent data now. lets take a big list of lists that parametrizes a huge pulse sequence. the code for that is short (iteration over the data). but in many cases you can not write the huge sequence as a simple/static list but you actually want to do some kind of metaprogramming the intermediate result being ARTIQlang.
<whitequark>
you mean like scans?
<whitequark>
I can add direct support for scans in the new compiler, it's fairly straightforward
<rjo>
the handling of these kernels would be similar to the persistent arrays: you would ask for them to be compiled and then set them like the persistent data.
<rjo>
not like scans.
<whitequark>
ok.
<rjo>
scans are the most basic parametrization (one-variable) of an experiment.
<whitequark>
it's not realistic to expect kernels to be handled like persistent arrays, because correctness and safety of running kernels depends on too much state elsewhere
<whitequark>
concrete issue number 1.
<rjo>
doesn't the call stack guarantee that correctness?
<whitequark>
we in general are unable to unload any newly added code or data, because it might have mutated global state and put references to it into it
<whitequark>
e.g. if new code refers to a list, that list will be compiled into .data in the new code
<whitequark>
the list is global, so new code can ask old code to give it a list of lists and it can put its new list there.
<whitequark>
unloading new code now violates memory safety. there are no practical means of preventing that
<whitequark>
(also works with closures)
<rjo>
assume/enforce that the "dynamic/new" code is idempotent?
<rjo>
or is that the same question as: "how does a dynamic linker work"?
<rjo>
assume no global state is modified/maintained?
<whitequark>
that's unsafe. not only this violates the Python contracts, but also we provide no tools to help debug unsafe code
<whitequark>
so if you ever, for any reason, violate that? you're screwed. you'll never figure out why
<whitequark>
now, mind you, this is not completely broken yet; "never unload any code" is something that surprisingly many systems do in production
<rjo>
isn't it sufficient to restrict the arguments and return values (to non-mutable things or the like)?
<whitequark>
nope, since new code can also use quoted values, and quoted values ~ globals
<rjo>
quoted values, as in strings?
<rjo>
rodata?
<whitequark>
no, quoting in the sense of quasiquoting
<whitequark>
anytime you refer to host object from kernel code, it (the host object) is "quoted"
<whitequark>
that is, put into global storage, and a reference to that storage is used when the object is encountered at runtime
<whitequark>
immutable values are simple; lists are copied and left like that; proper objects get attribute resynchronization
<whitequark>
rjo: anyway, so the API I propose for what you want is as follows.
<whitequark>
let's say you have several functions, f, g, h, ... that implicitly make use of some other (large, heavy) functions in kernel mode
<whitequark>
to avoid recompilation, you do:
<whitequark>
with NamingIsHard(f, g, h):
<whitequark>
f(); g(); h()
<whitequark>
("NamingIsHard" is some context manager that I don't know how to name.)
<whitequark>
compilation happens once, during entering the with statement.
<whitequark>
the heavy kernel functions get dragged in implicitly, by virtue of being a dependency of f, g, h...
<rjo>
and that "with ..." is in host-mode?
<whitequark>
yes
<whitequark>
in fact, this requires no changes to the compiler at all, just a bit of host Python code
<rjo>
with bundled(f,g,h): ...
<whitequark>
sure.
<rjo>
and within that bundle the set of available kernels is frozen?
<whitequark>
yes.
<rjo>
afaict now this is very nice but might be just orthogonal enough to the actual use cases... will discuss this.
<whitequark>
so otherwise, dynamic loading of code is trivial on runtime side, almost trivial on compiler backend side
<whitequark>
however, it is very hard on correctness/safety side
<whitequark>
if it is doable, I think it is likely that a form of `with bundled` would still be present, and severe restrictions on what the kernels not in the bundle can do
<rjo>
is the unloading the only really hard problem?
<whitequark>
hrm
<rjo>
these kernels would be somewhat special "@freeze" and be compiled with a reduced ARTIQlang support, right?
<whitequark>
no, the restrictions aren't on the preloaded kernels, they are on the things you want later
<whitequark>
so another problem is attribute writeback.
<rjo>
well. i suspect we would have to at least sketch these restrictions to decide whether the remaining stuff would be useful or not.
<rjo>
yeah. kernels would not be methods, right?
<whitequark>
no, that's mostly irrelevant
<whitequark>
let me check some compiler guts...
<rjo>
otoh, i think "with bundle_kernels(f,g,h):" or similar and that pageflipping/backbuffering/staging of kernel we should be able to do a lot.
<whitequark>
ok, so for sure, you will not be able to use any attributes that you did not previously use in your longrunning code
<whitequark>
for any objects that you *did* use in your longrunning code.
<rjo>
i presume "with bundle_kernels()" would be recursive in its arguments.
<whitequark>
i.e. since longrunning code refers to self.core, you will not be able to use self.core.ttl1.
<whitequark>
(if you didn't use that one in longrunning code)
<whitequark>
the one which compiles for a long time. longcompiling is a better word.
<whitequark>
the reason for that restriction is that once you emit code, memory layout is fixed once and for all. no way around that.
<whitequark>
ok, so shortcompiling code should be able to quote arrays. that seems easy.
<rjo>
so if "def heavy(self): self.core.ttl.pulse()" and "def outer(self): self.heavy()" what can't I do?
<rjo>
heavy being the one that hard to compile
<rjo>
and should be "persistent"
<whitequark>
you can't do "def outer(self): self.core.pmt.pulse()"
<rjo>
unless i already used core.pmt in outer() before calling heavy()?
<whitequark>
unless it is used in heavy()
<rjo>
ah. dependency inversion?
<whitequark>
you could put it like that, I guess.
<rjo>
ok. i have no idea where that restriction comes from but i believe you that it would have to be enforced/assumed.
<rjo>
and that restriction makes the entire excercise rather pointless.
<whitequark>
"self.core" has to be compiled while compiling "heavy"; "heavy" has to have hardcoded knowledge of "self.core"'s memory layout
<whitequark>
once "heavy" is on the core device, no changes to its layout are possible. so, no new fields.
<whitequark>
there are quite similar but very slightly less severe issues with getting *any* host objects, that were not already used before, into shortcompiling code
<whitequark>
the restrictions are less severe but severe enough that it probably makes no sense to allow to introduce new host objects in shortcompiling code at all either
<rjo>
can't we have two independent and unsynchronized self.cores?
<whitequark>
then if you use self.ttl1 in longcompiling code and same self.ttl1 in shortcompiling code, they will have unsynchronized state
<whitequark>
so your inputs will break, your ddses will break, etc
<whitequark>
and there's probably some way to violate memory safety with it but I don't yet see how exactly
<whitequark>
and of course attribute writeback gets broken completely
<whitequark>
btw what does bundle_kernels being recursive in its arguments mean?
<rjo>
they don't have state. there is only state that is accessed through the runtime IIRC.
<whitequark>
they do: self.i_previous_timestamp self.o_previous_timestamp
<rjo>
recursive meanint that it will (recursively) gobble up all kernels that are used in that set and compile them together.
<rjo>
ah.
<rjo>
maybe that should go into the runtime then.
<whitequark>
actually, your solution completely defeats the point of sharing kernels
<rjo>
with the unsynchronized self?
<rjo>
selfs?
<whitequark>
yeah
<whitequark>
you can't call any of the old methods because object layout might have changed
<whitequark>
so, you cannot actually share anything at all.
<rjo>
no. you would not do "self.heavy()" but something like "self.core.frozen['heavy']
<rjo>
()"
<whitequark>
that makes no difference.
<rjo>
you can share zero state. true.
<rjo>
state in self.
<rjo>
but you can share all state in the runtime
<whitequark>
then why do we bother with precompiling? fast kernel swap should be enough.
<rjo>
and you can share all the stuff that "happens".
<whitequark>
well, not precompiling, with persistent kernels.
<whitequark>
there's nothing to persist then!
<rjo>
the code is.
<rjo>
self.core.frozen[] persists kernel swaps.
<whitequark>
no, you cannot reuse old code.
<rjo>
that would be precisely the idea, assuming we have the same notion of "old".
<whitequark>
I mean, you cannot ever pass any data to functions in "self.core.frozen[]" (it makes no sense to make those a hash, we don't have hashes, or put it into self.core...)
<whitequark>
I guess you could pass tuples of numbers at most
<sb0__>
also, regarding host object attributes and interleaving
<whitequark>
yeah?
<sb0__>
mh, no that won
<sb0__>
't work
<whitequark>
there has to be a type system addition and the variable alias (const_x / with const:) trick
<whitequark>
the former is relatively easy though a large amount of work. the latter needs consensus
<rjo>
please don't encode metadata in the variable names.
<whitequark>
oh, yes, it's an absolutely horrible solution and I hate it
<whitequark>
but it does serve to illustrate my point
<whitequark>
the problem is that we have condemned ourselves to a subset of Python, and Python is a really bad fit as a DSL for the problem it should solve here
<sb0__>
whitequark: re. #228. seems the rtio channel number is borked.