-
-
Save cotto/2581153 to your computer and use it in GitHub Desktop.
per-callframe constants segment layout: | |
constants segment starts with a list of fixed-width pointers. I/N entries are stored directly. S/P entries are pointers to later in the constants segment | |
The data segment has a couple options. It can be appended after the constants segment (either on the next page or immediately after), with the constants data being marked read-only. Alternately, it can just be another segment attached to a special register. | |
If callframes are analogous to subs, the constants and data segment can be analogous to lexical data. | |
If callframes are analogous to subs, how will recursion work? | |
* clone a callframe (when? before entering ( -> no state) or after entering (less waste, leftover state)) | |
* need to work out calling conventions | |
Overall, m0 is a fantastic pain in the ass to write and especially debug. It needs a handful of features to ease the process. Possibilities include: | |
* basic interactive debugger | |
* something to dump (a subset of) the state of a register | |
unresolved questions: | |
* how will global data work? Will it be part of m0b or purely runtime? | |
* hang it off cf[INTERP][GLOBALS] or something similar | |
* it'll need to be dynamic | |
* add a .globals segment | |
* how can code grow/shrink the rw data segment (sbrk-like op?) | |
* alternately, each cf can have a fixed-size data segment. It'd result in hacks for large functions, but a whole virtual memory implementation wouldn't be needed. | |
* does this mean that any M0 implementation needs to implement virtual memory (or virtual virtual memory, as it were)? | |
* realloc is a good-enough first approximation | |
* alternately, we can have a list of data segments that can be created with alloc | |
* this probably adds too much complexity to m0 (not a lot, but a little that can be avoided) | |
* is there any value in naming registers [INSP]n at all? Why not Rn | |
M0 spec todo: | |
* change CONSTS to be a single chunk of memory with internal pointers | |
* go with flat memory space + realloc to expand; update the spec to reflect this | |
* spec out the op that'll expand/contract the data segment | |
* spec out the op that'll expand/contract the globals data segment | |
* spec out the globals m0b chunk | |
* make the constants segment a read-only part of the data segment | |
* make GLOBALS work similar to the constants segment, except that the segment can grow | |
* float the idea of untyped register names by nbrown (i.e. I0 = R0, etc) | |
* spec out the m0b and runtime representation for hex constants and data | |
* In m0b, similar to strings but with a new encoding # | |
* In memory, simply a pointer to the data | |
M0 testing TODO: | |
* write out a test that recursively does something (naive recursion-based addition is fine) | |
* fizzbuzz, just because | |
* tests for special registers | |
* test to ensure that constants data can't be overwritten |
I don't understand the first question. There are ~240 general-purpose registers available that will be able to point anywhere in the DATA segment or contain a numeric value. For something like a linked list, most of the structure would live in the DATA segment and only a handful of registers would be needed to manipulate the list. Register spilling is explicitly provided for. Remember that x86 has only 6 general-purpose registers.
Once we get library loading ironed out it'll be possible to dynamically create and load a m0b file at runtime, but I don't think that's important for most use cases. Remember that at the point when M0 is being generated, we know which functions will need to be generated and how they'll call each other. Anonymous functions still get compiled as functions.
Thanks for your answer. I saw there were many init instructions before goto_chunk and some init instructions before goto caller's chunk in "m0_poke_caller.test". I think it'd be very helpful to reduce the bytecode size(and increase some performace) if there will be a init_chunk op(or init chunk in the goto_chunk op before a real goto, I'm not sure which is better) and a return_caller op. And it also makes writing tests less painful. Any ideas?
That's a possibility later on, but adding calling convention-specific (hereafter "m0cc") ops is an optimization that'd be premature at this point. Some factors to weigh are: 1) Does it make the common case more efficient while not degrading unusual cases and 2) What effect does it have on implementation complexity.
We have the poke_caller example, but it's nothing more than a proof of concept and may not be representative of how the full complexity of what m0cc will need to handle. Full m0cc hasn't been specified (patches accepted!), so we don't know for sure what operations would make them more efficient. M0 has a stated goal of being aggressively minimal, so any added complexity from an extra op needs to have a very clear benefit, including benchmarks. M0 isn't at the point yet where such a benefit can be shown.
I'm wary of doing too much work to optimize writing tests. I did just add register name aliases to make writing M0 less painful, but M0 isn't intended as a primarily human-writable language and shouldn't be too heavily optimized as such. Suggestions that don't effect bytecode complexity (e.g. macros) will have an easier time making it into the spec than complex ops.
I agree with you, I'm not sure the full complexity too. Speaking of macro, I'm not sure whether M0 needs it or not. Maybe M1 needs it?
If there's a sane and simple way to specify macros for M0, I'd love to see them. That said, I'm not sure there's a good way to implement them. They'll probably be easier to add at the m1 level or higher.
I'm +1 on it. :)
M0 spec todo:
* how will list structure works? Is 256 registers enough if structure only points to register?
* how to create chunk and call it dynamically?