| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Rip out everything to fix one failing unit test (commit 3290; type
abbreviations).
This commit does several things at once that I couldn't come up with a
clean way to unpack:
A. It moves to a new representation for type trees without changing
the actual definition of the `type_tree` struct.
B. It adds unit tests for our type metadata precomputation, so that
errors there show up early and in a simpler setting rather than dying
when we try to load Mu code.
C. It fixes a bug, guarding against infinite loops when precomputing
metadata for recursive shape-shifting containers. To do this it uses a
dumb way of comparing type_trees, comparing their string
representations instead. That is likely incredibly inefficient.
Perhaps due to C, this commit has made Mu incredibly slow. Running all
tests for the core and the edit/ app now takes 6.5 minutes rather than
3.5 minutes.
== more notes and details
I've been struggling for the past week now to back out of a bad design
decision, a premature optimization from the early days: storing atoms
directly in the 'value' slot of a cons cell rather than creating a
special 'atom' cons cell and storing it on the 'left' slot. In other
words, if a cons cell looks like this:
o
/ | \
left val right
..then the type_tree (a b c) used to look like this (before this
commit):
o
| \
a o
| \
b o
| \
c null
..rather than like this 'classic' approach to s-expressions which never
mixes val and right (which is what we now have):
o
/ \
o o
| / \
a o o
| / \
b o null
|
c
The old approach made several operations more complicated, most recently
the act of replacing a (possibly atom/leaf) sub-tree with another. That
was the final straw that got me to realize the contortions I was going
through to save a few type_tree nodes (cons cells).
Switching to the new approach was hard partly because I've been using
the old approach for so long and type_tree manipulations had pervaded
everything. Another issue I ran into was the realization that my layers
were not cleanly separated. Key parts of early layers (precomputing type
metadata) existed purely for far later ones (shape-shifting types).
Layers I got repeatedly stuck at:
1. the transform for precomputing type sizes (layer 30)
2. type-checks on merge instructions (layer 31)
3. the transform for precomputing address offsets in types (layer 36)
4. replace operations in supporting shape-shifting recipes (layer 55)
After much thrashing I finally noticed that it wasn't the entirety of
these layers that was giving me trouble, but just the type metadata
precomputation, which had bugs that weren't manifesting until 30 layers
later. Or, worse, when loading .mu files before any tests had had a
chance to run. A common failure mode was running into types at run time
that I hadn't precomputed metadata for at transform time.
Digging into these bugs got me to realize that what I had before wasn't
really very good, but a half-assed heuristic approach that did a whole
lot of extra work precomputing metadata for utterly meaningless types
like `((address number) 3)` which just happened to be part of a larger
type like `(array (address number) 3)`.
So, I redid it all. I switched the representation of types (because the
old representation made unit tests difficult to retrofit) and added unit
tests to the metadata precomputation. I also made layer 30 only do the
minimal metadata precomputation it needs for the concepts introduced
until then. In the process, I also made the precomputation more correct
than before, and added hooks in the right place so that I could augment
the logic when I introduced shape-shifting containers.
== lessons learned
There's several levels of hygiene when it comes to layers:
1. Every layer introduces precisely what it needs and in the simplest
way possible. If I was building an app until just that layer, nothing
would seem over-engineered.
2. Some layers are fore-shadowing features in future layers. Sometimes
this is ok. For example, layer 10 foreshadows containers and arrays and
so on without actually supporting them. That is a net win because it
lets me lay out the core of Mu's data structures out in one place. But
if the fore-shadowing gets too complex things get nasty. Not least
because it can be hard to write unit tests for features before you
provide the plumbing to visualize and manipulate them.
3. A layer is introducing features that are tested only in later layers.
4. A layer is introducing features with tests that are invalidated in
later layers. (This I knew from early on to be an obviously horrendous
idea.)
Summary: avoid Level 2 (foreshadowing layers) as much as possible.
Tolerate it indefinitely for small things where the code stays simple
over time, but become strict again when things start to get more
complex.
Level 3 is mostly a net lose, but sometimes it can be expedient (a real
case of the usually grossly over-applied term "technical debt"), and
it's better than the conventional baseline of no layers and no
scenarios. Just clean it up as soon as possible.
Definitely avoid layer 4 at any time.
== minor lessons
Avoid unit tests for trivial things, write scenarios in context as much as
possible. But within those margins unit tests are fine. Just introduce them
before any scenarios (commit 3297).
Reorganizing layers can be easy. Just merge layers for starters! Punt on
resplitting them in some new way until you've gotten them to work. This is the
wisdom of Refactoring: small steps.
What made it hard was not wanting to merge *everything* between layer 30
and 55. The eventual insight was realizing I just need to move those two
full-strength transforms and nothing else.
|
| |
|
| |
|
|
|
|
|
|
|
|
| |
Always show instruction before any transforms in error messages.
This is likely going to make some errors unclear because they *need* to
show the original instruction. But if we don't have tests for those
situations did they ever really work?
|
| |
|
|
|
|
| |
Clean up 3020.
|
|
|
|
|
|
|
|
| |
Extremely ugly change.
Also ended up fixing some places where I was mixing up sources and
sinks. But I'm not going to bother updating edit/ and sandbox/ apps.
Just too many scenarios to clean up.
|
|
|
|
|
|
|
|
|
|
| |
Standardize quotes around reagents in error messages.
I'm still sure there's issues. For example, the messages when
type-checking 'copy'. I'm not putting quotes around them because in
layer 60 I end up creating dilated reagents, and then it's a bit much to
have quotes and (two kinds of) brackets. But I'm sure I'm doing that
somewhere..
|
|
|
|
|
| |
Update refcounts of address elements when copying containers.
Still lots to do; see todo list at end of 036refcount.cc.
|
|
|
|
|
|
|
| |
This commit covers instructions 'put', 'put-index' and 'maybe-convert'.
Next up are the harder ones: 'copy' and 'merge'. In these cases there's
a non-scalar being copied, and we need to figure out which locations
within it need to update their refcount.
|
|
|
|
|
|
|
| |
It's a bit of a trade-off because we need to store copies of
container metadata in each reagent (to support shape-shifting
containers), and metadata is not lightweight and will get heavier. But
it'll become more unambiguously useful when we switch to a compiler.
|
|
|
|
|
|
|
| |
Now that we no longer have non-shared addresses, we can just always
track refcounts for all addresses.
Phew!
|
| |
|
|
|
|
|
|
| |
This reinfoces that it's only really intended to be used by
'wait-for-location'. To reinforce that we also move it to the same layer
as 'wait-for-location'.
|
|
|
|
|
|
|
|
|
|
|
| |
I'd started using size_of() in transforms at some point, but not gotten
around to actually updating it to support arrays before run-time. Wish
there was a way I could statically enforce that something is only called
at transform time vs runtime.
Thanks Ella and Caleb Couch for finding this issue. Static arrays are
likely still half-baked, but should get a thorough working-over in
coming weeks.
|
|
|
|
| |
Issue 1 in 2829 is now fixed.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
1. It turns out we couldn't overload 'get' and 'get-address' until now,
because transform_names looks for those names, and the
resolve_ambiguous_calls transform happens before transform_names. Why
does resolve_ambiguous_calls happen before transform_names? Because if
my students made mistakes in the ingredients to an instruction they got
overzealous errors from resolve_ambiguous_calls. Now this impacts 'put'
as well, which is already overloaded for tables. Not sure what to do
about this; I'm going to go back to the overzealous errors, and just
teach students to visually scan past them for now.
2. I need addresses in a third place besides storing to containers and
arrays, and managing the heap -- to synchronize routines.
wait-for-location requires an address. Not sure what to do about this..
|
|
|
|
|
| |
Now to extend 'stash' for arrays, just extend array-to-text-line instead
and perform the lookup inside it.
|
|
|
|
|
| |
Show more thorough information about instructions in the trace, but keep
the original form in error messages.
|
|
|
|
|
|
|
|
| |
As outlined at the end of 2797. This worked out surprisingly well. Now
the snapshotting code touches fewer layers, and it's much better
behaved, with less need for special-case logic, particularly inside
run_interactive(). 30% slower, but should hopefully not cause any more
bugs.
|
|
|
|
| |
This should eradicate the issue of 2771.
|
|
|
|
|
|
|
|
|
|
|
|
| |
I'm dropping all mention of 'recipe' terminology from the Readme. That
way I hope to avoid further bike-shedding discussions while I very
slowly decide on the right terminology with my students.
I could be smarter in my error messages and use 'recipe' when code uses
it and 'function' otherwise. But what about other words like ingredient?
It would all add complexity that I'm not yet sure is worthwhile. But I
do want separate experiences for veteran programmers reading about Mu on
github and for people learning programming using Mu.
|
|
|
|
|
| |
I'm going to stop wasting precious first-line characters on 'bugfix:'.
It's going to be all bugfixes for a while I think.
|
| |
|
|
|
|
|
| |
Only Hide_errors when strictly necessary. In other places let test
failures directly show the unexpected error.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Stack of plans for cleaning up replace_type_ingredients() and a couple
of other things, from main problem to subproblems:
include type names in the type_tree rather than in the separate properties vector
make type_tree and string_tree real cons cells, with separate leaf nodes
redo the vocabulary for dumping various objects:
do we really need to_string and debug_string?
can we have a version with *all* information?
can we have to_string not call debug_string?
This commit nibbles at the edges of the final task, switching from
member method syntax to global function like almost everything else. I'm
mostly using methods just for STL in this project.
|
|
|
|
| |
I've been gradually Greenspunning reagents. Just go all the way.
|
| |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This is the one major refinement on the C programming model I'm planning
to introduce in mu. Instead of Rust's menagerie of pointer types and
static checking, I want to introduce just one new type, and use it to
perform ref-counting at runtime.
So far all we're doing is updating new's interface. The actual
ref-counting implementation is next.
One implication: I might sometimes need duplicate implementations for a
recipe with allocated vs vanilla addresses of the same type. So far it
seems I can get away with just always passing in allocated addresses;
the situations when you want to pass an unallocated address to a recipe
should be few and far between.
|
|
|
|
|
|
|
|
|
|
|
|
| |
We want to use the type 'recipe' for recipe *variables*, because it
seems nicer to say `recipe number -> number` rather than recipe-ordinal,
etc. To support this we'll allow recipe names to be mentioned without
any type.
This might make a couple of places in this commit more brittle. I'm
dropping error messages, causing them to not happen in some situations.
Maybe I should just bite the bullet and require an explicit
:recipe-literal. We'll see.
|
| |
|
|
|
|
| |
Thanks Caleb Couch.
|
| |
|
| |
|
|
|
|
|
| |
Some more structure to transforms, and flattening of dependencies
between them.
|
|
|
|
|
| |
/raw is to express absolute addresses
/unsafe is to sidestep type-checking in test setup
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
I tried to not populate the type at an early stage, and to pull out the
type computations for all reagents into a separate transform grouped
with but before the other type deduction transforms. But it seemed less
readable to not mention types at all in layer 10. So we'll stick with
our current approach, but try to be disciplined about grouping all the
type transforms together, so that we can reason about whether a pass
belongs before or after type deduction. (Doesn't seem rigorous enough
for the name 'type inference'.) In particular, static dispatch and
specialization of generics (resolve_ambiguous_calls) needs to happen
after all type inference has completed, so that the only missing types
are the generic type ingredients.
In general I've been living in constant fear of the phase-ordering
problem. No matter how many tests I write, I can't be sure that there
isn't some corner case where my phases will be proven to be in a
sub-optimal ordering. When I build the mu compiler in mu I'll want to
also use the ability to perform static analyses in mu programs using mu
userland capabilities. That would allow me to be sure that no phase
writes to some field of reagent after some other purely checking phase
reads it. Then all you have to do is be disciplined about not doing
checking in mutating phases (which we currently aren't; hello
check_or_set_invalid_types).
Hmm, but I think this line of thought gives me some confidence now that
I'm ok so far. The only field of reagents being modified after
parsing/initialization is the type. So all I care about is whether each
transform happens before or after all types are available. If I later
start writing other fields or properties then I'll need to perform
similar analysis for them, and it might get complicated enough to need a
state diagram where partially filled out properties inhabit separate
states from completely inferred properties.
|
|
|
|
|
| |
I'd not paid any attention to it so far, but I need to do so from now
on.
|
|
|
|
| |
Starting to leave commented out prints again out of desperation.
|
|
|
|
|
|
|
| |
Commands run:
$ sed -i 's/\([^. (]*\)\.find(\([^)]*\)) != [^.]*\.end()/contains_key(\1, \2)/g' 0[^0]*cc
$ sed -i 's/\([^. (]*\)\.find(\([^)]*\)) == [^.]*\.end()/!contains_key(\1, \2)/g' 0[^0]*cc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
I'm still seeing all sorts of failures in turning on layer 11 of edit/,
so I'm backing away and nailing down every culprit I run into. First up:
stop accidentally inserting empty objects into maps during lookups.
Commands run:
$ sed -i 's/\(Recipe_ordinal\|Recipe\|Type_ordinal\|Type\|Memory\)\[\([^]]*\)\] = \(.*\);/put(\1, \2, \3);/' 0[1-9]*
$ vi 075scenario_console.cc # manually fix up Memory[Memory[CONSOLE]]
$ sed -i 's/\(Memory\)\[\([^]]*\)\]/get_or_insert(\1, \2)/' 0[1-9]*
$ sed -i 's/\(Recipe_ordinal\|Type_ordinal\)\[\([^]]*\)\]/get(\1, \2)/' 0[1-9]*
$ sed -i 's/\(Recipe\|Type\)\[\([^]]*\)\]/get(\1, \2)/' 0[1-9]*
Now mu dies pretty quickly because of all the places I try to lookup a
missing value.
|
|
|
|
|
|
|
| |
A new externality is starting to make its presence felt.
Until I sort this out it's going to be hard to finish making duplex-list
generic.
|
|
|
|
|
| |
Deduce operation id from name during transform rather than load, so that
earlier transforms have a chance to modify the name.
|
| |
|
| |
|
|
|
|
| |
Thanks Caleb Couch.
|
|
|
|
|
|
|
| |
At the lowest level I'm reluctantly starting to see the need for errors
that stop the program in its tracks. Only way to avoid memory corruption
and security issues. But beyond that core I still want to be as lenient
as possible at higher levels of abstraction.
|
|
|
|
|
|
|
|
| |
Always show recipe name where error occurred. But don't show internal
'interactive' name for sandboxes, that's just confusing.
What started out as warnings are now ossifying into errors that halt all
execution. Is this how things went with C and Unix as well?
|