Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

split stack #60

Merged
merged 2 commits into from
Aug 11, 2023
Merged

split stack #60

merged 2 commits into from
Aug 11, 2023

Conversation

drbeefsupreme
Copy link
Contributor

@drbeefsupreme drbeefsupreme commented Jun 15, 2023

Addresses #50

Following @eamsden's advice and a couple attempts so far at refactoring the old version, I am rewriting mem.rs from scratch since this change touches most of the file.

@drbeefsupreme drbeefsupreme self-assigned this Jun 15, 2023
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
@drbeefsupreme
Copy link
Contributor Author

drbeefsupreme commented Jun 15, 2023

In order to not make @eamsden navigate through a huge, constantly changing mess, here's how I plan to iterate:

Aspirationally, push one batch of commits every day or two, and don't push more commits until Ed has time to look them over and leave feedback. Only include functions with a todo!() that I'm reasonably sure actually need to exist. Try to flesh out todo!()s) in terms of dependence from a root task (allocation, copying, pushing, etc).

So I'm marking 0b4029a as my last commit I'll be pushing from this batch. Feedback I'm hoping to get:

  • answers to questions listed in //TODO comments
  • pointing out functions that just have a todo!() in them that I shouldn't end up needing (based on their usage pre-split frame)
  • check the logic on initialization (NockStack::new()), pushing (push()), and allocation (raw_alloc(), indirect_alloc(), struct_alloc())
  • style and conventions

rust/ares/src/mem.rs Outdated Show resolved Hide resolved
@drbeefsupreme
Copy link
Contributor Author

Another batch of commits.

I'm mostly looking for comments on copy_west(). The pre_copy() stuff won't be fully wired up until I write pop_west() and pop_east(), and might need to change depending on where the lightweight stack actually goes (I think at least for copying, we can't put it next to the FP-relative slots as suggested in @eamsden's split stack writeup - but I think it might be fine to put it next to the allocations).

@drbeefsupreme
Copy link
Contributor Author

@eamsden

Based on what we talked about, it seems we do actually want a 3rd pointer in NockStack. The last two commits add the stack_pointer and then I rewrote copy_west() taking this into account. This also "steals" stack_pointer for the lightweight stack for traversing nouns being copied. Knowing that stack_pointer is the one and only pointer used for lightweight stacks might make things less confusing (we could have introduced a new local variable pointer instead), but it might also create confusion since we're moving the lightweight stack from (in the west case) the high edge of low memory to the low edge of high memory. But once we're at the copying phase, the frame is being destroyed anyways, so maybe that's fine.

It is safe to ignore the copy_west() from yesterday.

@drbeefsupreme
Copy link
Contributor Author

drbeefsupreme commented Jun 28, 2023

@eamsden Here's the new copy_west() and senior_pointer_first() that treats the FP-relative slots correctly (in the previous version, they were not adjacent to the frame pointer) and refactors slot accessing to be a bit more readable using magic numbers.

I'm feeling pretty sure that this is mostly right, so I'm going to move on to unifying_equality() next.

@drbeefsupreme
Copy link
Contributor Author

@eamsden here's my first attempt at unifying_equality()

It may be evident that I still have some confusion over Rust's raw pointer interface (you can see me casting the same pointer to both *mut u64 and *mut (*mut Noun, *mut Noun)). Here's what I attempted to do:

  • Unlike the currently implemented unifying_equality(), we do not need to push a new frame to do this
  • Allocations for unifying_equality() are done in the current frame's allocation arena. We push pointers to these allocations onto the lightweight stack (using stack_pointer).
  • The loop is declared over when the stack is empty (frame_pointer == stack_pointer). However I don't know if unifying_equality() might be called when the lightweight stack wasn't already empty. It shouldn't be right now since its the only place using stack_pointer besides copy_west(), but I don't know if the lightweight stack might be used for other tasks inside of which unifying_equality() might be called (maybe tail recursion?). I considered putting an assert!(frame_pointer == stack_pointer) at the start of this to check.
  • We loop by popping a (*mut Noun, *mut Noun) off the stack and testing their raw equality, mug equality, etc as usual.
  • When two cells are found to be unequal, their head and tail are allocated, and pointers to those allocations are added to the lightweight stack, and we loop again.

I haven't thought through how the memory looks at the end. Should we be expecting that all allocations made for this have been reclaimed? Basically I'm not sure which of these two lines is right

    assert!(stack.alloc_pointer == start_alloc_pointer)
    stack.alloc_pointer = start_alloc_pointer;

@drbeefsupreme
Copy link
Contributor Author

I rushed through copy_pma(), mug(), jam(), and cue() mostly just copying what I did for copy_west() and unifying_equality() just to get something that builds. It does build and I do some dumb things (like made alloc_pointer public) just to get it building, but here's the branch for it:

https://github.com/urbit/ares/tree/jon/stack-split-mem-rewrite-gogo

Copy link
Contributor

@ashelkovnykov ashelkovnykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a really quick look over without diving too deeply, since this is still WIP

rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
@drbeefsupreme
Copy link
Contributor Author

drbeefsupreme commented Jul 17, 2023

Here's my extremely shameful debugging branch: https://github.com/urbit/ares/tree/jon/stack-split-mem-rewrite-gogo-debug

So I think I have working implementations of copy_east(), copy_west(), cue(), jam(), unifying_equality(), and mug(). cargo run test_data/hurray.jam works fine. But it crashes partway through running cargo run test_data/decrement2.jam (which just decrements 100 once, instead of the thousand times for the other decrement jamfile).

I've been littering printfs everywhere trying to find the culprit. The subject changes to a cell of what appears to be two identical pointers into the memory arena, but I've been running in circles trying to figure out how this is happening.

...
NockStack pop 7
frame_pointer: 0x7f68fea000c0
stack_pointer: 0x7f68fea000c0
alloc_pointer: 0x7f69029ffd28
interpret loop subject: [[6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 0 100]
next instruction Nock9ComputeResult
Nock9ComputeResult0 subject: [[6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 0 100]
Nock9ComputeResult1 subject: [[6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 0 100]
Nock9ComputeResult2 subject: [[6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 1 100]
slot() noun: [[6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 1 100]
slot loop
Ok(cell) = noun.as_cell()
slot loop
push_formula formula: [6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7]
NockStack push 8
frame_pointer: 0x7f69029ffcf0
stack_pointer: 0x7f69029ffcf0
alloc_pointer: 0x7f68fea000c0
interpret loop subject: [[6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 1 100]
next instruction Nock6ComputeTest
Nock6ComputeTest subject: [[6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 1 100]
Nock6ComputeTest formula: [5 [0 7] 4 0 6]
push_formula formula: [5 [0 7] 4 0 6]
push_formula(): 5
NockStack push 9
frame_pointer: 0x7f68fea000f0
stack_pointer: 0x7f68fea000f0
alloc_pointer: 0x7f69029ffcf0
push_formula(): 5 *local_noun_pointers: [0 7] [4 0 6]
interpret loop subject: [140088992333040 140088992333040]
next instruction Nock5ComputeLeftChild
Nock5ComputeLeftChild subject: [140088992333040 140088992333040]
Nock5ComputeLeftChild formula: [0 7]
push_formula formula: [0 7]
NockStack push 10
frame_pointer: 0x7f69029ffcc8
stack_pointer: 0x7f69029ffcc8
alloc_pointer: 0x7f68fea000f0
0 => *stack.local_noun_pointer(1) 7
interpret loop subject: [140088992333040 140088992333040]
next instruction Nock0Axis
local_noun_pointers 0x7f69029ffce0 0x7f69029ffce8
*local_noun_pointers 4 7
Nock0Axis subject: [140088992333040 140088992333040]
slot() noun: [140088992333040 140088992333040]
slot loop
Ok(cell) = noun.as_cell()
slot loop
thread 'main' panicked at 'Axis tried to descend through atom: 140088992333040', src/interpreter.rs:612:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

140088992333040 in hex is 0x7f69029ffcf0, which we see present above:

NockStack push 8
frame_pointer: 0x7f69029ffcf0
stack_pointer: 0x7f69029ffcf0
alloc_pointer: 0x7f68fea000c0

So, it looks like the subject might be turning into the tuple [frame_pointer stack_pointer] somehow. But putting printfs before and after every place the subject is changing, I don't see where its actually happening. The interpret() loops and we suddenly have a new subject - or so it seems.

@eamsden anything sticking out that looks strange?

@drbeefsupreme
Copy link
Contributor Author

Ok I just noticed that 0x7f69029ffcf0 is also the alloc_pointer in the same frame. When you stack.push(), the new alloc_pointer is set to the previous stack_pointer. The subject, while being a local variable for interpret(), is also stored in FP-relative slots. So I'm guessing the subject is being overwritten somehow when pushing a new stack frame.

Strange that it seems to get through a fair number of steps before this happens (including at least one similar looking sequence). But that should also narrow it down somewhat. That probably rules out something as simple as the push() or pop() logic being wrong, and instead something particular to the alloc_pointer or (more likely I think) the stack_pointer in one of the several functions making use of the lightweight stack.

@drbeefsupreme
Copy link
Contributor Author

I added another printf to Nock6ComputeTest after push_formula and see that the subject actually changes after running push_formula. The formula it pushes uses Nock 5, but there's nothing there that changes the subject. Pretty confused as to what's happening at this point.

@drbeefsupreme
Copy link
Contributor Author

drbeefsupreme commented Jul 18, 2023

So I noticed another thing this morning that probably isn't related but I'll mention anyways - the preserve() call for the hamt structure is incorrect. Right now, when a frame is popped, a function called pre_copy() is called which moves the reserved FP-relative slots to store the pointers for the previous frame to be alloc_pointer-relative slots. At this point, no more allocations are supposed to be made to avoid overwriting them.

However, the hamt preserve() call, which always happens after pre_copy(), does make an allocation, for its traversal stack. This should be easy to fix - it will probably end up looking like copy_west()/copy_east() and just allocate further into the free memory adjacent to from-space, away from the slots holding the previous frame pointers.

But - it also looks like the if in_frame() statement for hamt preserve() never actually catches anyways - either for split-stack or non-split stack. So this probably isn't related to the above subject mysteriously changing bug.

@drbeefsupreme
Copy link
Contributor Author

drbeefsupreme commented Jul 18, 2023

OK, got it working by just adding 1000 extra slots to every stack frame. Probably this means that the allocation or lightweight stack is destroying the frames somewhere, or there's something off about slot pointer arithmetic.

EDIT: I had swapped <= and > in one of the copy functions.

@drbeefsupreme
Copy link
Contributor Author

The last batch of commits cleans things up a bit.

I think the main trouble that's left is surrounding the behavior preparing to pop a stack frame. When we evacuate allocations in the current frame to the previous frame, we destroy the FP-relative slots and lightweight stack. To save the pointers to the previous frame, we call pre_copy(), which copies them into the free space adjacent to the allocation arena of the current frame.

Then, in order to do noun traversal for e.g copy_west(), we implement another lightweight stack in the free memory adjacent to those pointers. Then despite the current frame being a e.g. west frame, this lightweight stack is oriented like an normal east frame's lightweight stack. Furthermore, finding the saved pointers requires calling a different set of functions.

This makes things pretty confusing for the programmer. They have to know whether they're working before or after pre_copy() has been called in order to know which functions to call. I think it will be more ergonomic for NockStack to instead track whether pre_copy() has been called yet on the current frame, and have one unified interface for working with the saved pointers and the lightweight stack. So my next batch (probably later today) will be an attempt at implementing this.

@drbeefsupreme drbeefsupreme marked this pull request as ready for review July 21, 2023 13:14
@drbeefsupreme drbeefsupreme changed the title WIP: split stack split stack Jul 21, 2023
@drbeefsupreme
Copy link
Contributor Author

Ready for review. A few things I wish to draw attention to:

  1. hamt::preserve() uses a lightweight stack slightly differently than all the rest. It might make sense to change it to use to the lightweight stack interface, but I do not even know what hamt is for exactly just yet.
  2. There is now a flag for NockStack called pc that tracks whether or not pre_copy() has been called on the current stack frame. This definitely improves ergonomics, but the other choice that I think there is would be to use an enum to say whether the lightweight stack is east-facing or west-facing. I decided to go with the boolean since this created a naming catastrophe with _west and _east functions.
  3. You have to know what is on the lightweight stack to pop it - i.e. you called stack_pop::<foo>() instead of just stack_pop(). This feels wrong but I don't know a way around it.

rust/ares/src/main.rs Outdated Show resolved Hide resolved
rust/ares/src/hamt.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@eamsden eamsden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really need to know what's going on with the magic # change, otherwise looking good

rust/ares/src/main.rs Outdated Show resolved Hide resolved
@drbeefsupreme
Copy link
Contributor Author

Having done enough of #66 to know how the split stack interface is going to be used for it, at this point the only thing that's gone on the lightweight stack are nouns. If this is always the case, then the weirdness I'm worried about with respect to needing to know the type of what's on top of the stack in order to pop it wouldn't actually be a problem.

@drbeefsupreme
Copy link
Contributor Author

drbeefsupreme commented Jul 27, 2023

So the choice of where the frame_pointer should be and what the saved pointers are isn't obvious - I wrote up my rationale here:

#64 (comment)

@ashelkovnykov ashelkovnykov mentioned this pull request Jul 27, 2023
Copy link
Contributor

@ashelkovnykov ashelkovnykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review. Will finish the rest shortly.

rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/interpreter.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mug.rs Outdated Show resolved Hide resolved
rust/ares/src/main.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@ashelkovnykov ashelkovnykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of opportunities to unify some functions. Should be very sleek & elegant indeed, afterwards.

rust/ares/src/hamt.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
rust/ares/src/mem.rs Outdated Show resolved Hide resolved
@drbeefsupreme
Copy link
Contributor Author

@ashelkovnykov Thanks for your comments, I'll get to them in the morning!

@drbeefsupreme
Copy link
Contributor Author

Having done enough of #66 to know how the split stack interface is going to be used for it, at this point the only thing that's gone on the lightweight stack are nouns. If this is always the case, then the weirdness I'm worried about with respect to needing to know the type of what's on top of the stack in order to pop it wouldn't actually be a problem.

I was mistaken about this. We have nouns, *mut Noun, and (*mut Noun, *mut Noun) all on the stack. Talking with Ed yesterday, he said the lightweight stack interface for pushing and popping is fine.

@drbeefsupreme
Copy link
Contributor Author

I believe I have addressed all comments. Would you please give it another look over @ashelkovnykov @eamsden

Copy link
Contributor

@ashelkovnykov ashelkovnykov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, pending:

  • Fix to pass CI
  • Reformat w/ cargo fmt
  • Squash commits

rust/ares/src/mem.rs Show resolved Hide resolved
@drbeefsupreme
Copy link
Contributor Author

drbeefsupreme commented Aug 7, 2023

What's the deal with squashing commits? I've only done it sparingly, such as when I'm really confused and need to test out a lot of different paths and stashing isn't enough so I use WIP commits. Many (most?) of the commits here have commit messages explaining their rationale. Squashing them would create a gigantic confusing commit message and thus make the commit history a lot less useful IMO.

If that's just the style for the Ares repo I'll do it, but I think it is a loss. Unless you mean I should go through all the commits and figure out which ones are appropriate to squash together. Which sounds tiresome, but I'd be fine with doing that.

@drbeefsupreme
Copy link
Contributor Author

Not sure how to deal with this from CI:

https://github.com/urbit/ares/actions/runs/5790048220/job/15692346635

I definitely don't think I should rewrite this using match and the Ordering enum. I think its more clear as written.

@ashelkovnykov
Copy link
Contributor

@drbeefsupreme

If that's just the style for the Ares repo I'll do it, but I think it is a loss.

I don't actually know what the style is regarding squashing commits for Ares - we should confirm with @eamsden.

Unless you mean I should go through all the commits and figure out which ones are appropriate to squash together. Which sounds tiresome, but I'd be fine with doing that.

I meant squash them all down into one commit, to be courteous of your time, but I do personally prefer the practice of every PR in a commit being a logical, (almost) self-contained grouping of changes.

Many (most?) of the commits here have commit messages explaining their rationale.

This is true; I just took a look at them and they're a lot more descriptive than the usual fare one would expect from a PR with nearly 100 commits.

Squashing them would create a gigantic confusing commit message and thus make the commit history a lot less useful IMO.

I disagree. We care about the changes and we care about the discussion around the changes, but we don't care about intermediate work. Any important discoveries or notes made during the process should be documented somewhere far more discoverable than the commit history. 87 commits is massively excessive for what comes down to ~1000 modified lines. If I want to browse a history of the changes, I don't want to have to scroll past 87 commits that are all introducing the same one feature. If I'm doing a git-bisect, I don't want to waste hops because 87 commits are all introducing one feature.

Regardless, the most important thing here is the changes themselves, so I'll go with whatever decision @eamsden makes regarding git + GitHub etiquette.

@ashelkovnykov
Copy link
Contributor

Not sure how to deal with this from CI:

https://github.com/urbit/ares/actions/runs/5790048220/job/15692346635

I definitely don't think I should rewrite this using match and the Ordering enum. I think its more clear as written.

@drbeefsupreme I think our options are:

  1. Do what the linter says
  2. Suppress the warning globally
  3. Suppress this particular warning by adding this right above the if statement: #[allow(clippy::comparison_chain)]

@eamsden
Copy link
Collaborator

eamsden commented Aug 10, 2023

@drbeefsupreme @ashelkovnykov

On squashing: you can always edit the commit messages. Squash together into one or 2 commits of working code

On CI: that particular linter bit is fine to suppress, ifs with comparators are good and fine

@drbeefsupreme
Copy link
Contributor Author

I was just hiding behind laziness on making more than one squash commit. I don't actually know how to do anything more complicated than that yet but I'll figure it out and squash this into a few commits, and suppress the warning.

see docs/stack.md for information about this change.

split: replace Polarity enum with pointer check

we can check polarity with West === FP < AP,
East === FP > AP.

we panic when the pointers are equal.

split: pre_copy step before preserve() calls

we need to save the stored frame_pointer and alloc_pointer before
preserve calls, which will otherwise overwrite them.

split: copy_west(), now with stack_pointer!

when copying, we can forget the lightweight stack in the current frame
and start a new one adjacent to from-space used for traversing nouns
in from-space.

split: working split stack

this commit works with hurray.jam and decrement.jam.

there's at least one thing left: I don't think the implementation of
hamt::preserve() is correct, but hurray.jam and decrement.jam don't use
it so it doesn't detect it. the comment there explains whats wrong.

I also need to double check copy_pma() still.
 This is the commit message #52:

split: hamt::preserve() and free_alloc

hamt::preserve() needs to allocate in the current frame after we've
already said we can no longer allocate (once pre_copy() has been
called). so does e.g. copy_west(), since it moves the lightweight
stack into this same area. we can unify this interface but I want
to explore the ergonomics of making this free allocation stuff usable
for the programmer without having to think about whether or not
pre_copy() has already been called.

split: add boolean for pre_copy()

alternative: use an enum to say whether the lightweight stack grows
westward or eastward, and that the reserved slots for the previous
frame's pointers are adjacent to the frame_pointer or alloc_pointer. not
sure which is the better choice yet.

split: panic when allocating at the wrong time

when pc == false, we aren't in copying mode (basically when we're
copying allocations to the previous frame to get ready to pop the
frame). therefore we should panic if we try to allocate in the previous
frame.

similarly, when pc == true, we're no longer allowed to allocate in the
current frame, but we can allocate in the previous frame. therefore we
should panic if we try to allocate in the current frame.

split: use prev_stack_pointer_pointer()

we'd like the programmer to not have to think about whether the frame is
in copying mode or not, so every time we want the previous frame's stack
pointer we should just called prev_stack_pointer_pointer() instead of
trying to remember if we need slot_pointer or free_slot.

we still use slot_pointer and free_slot for pushing and popping stack
frames, since we're in the midst of changing the pointers that would
determine where prev_stack_pointer_pointer() would look.

split: polarity of lw stack changes in pre_copy

It was waiting until copy_west/copy_east to do this - this makes it more
clear that the lightweight stack is different after pre_copy() is called

split: simplify is_west

asking this to panic in case of equality is assigning it too much
responsibility. it should just say whether or not a frame is west.

 This is the commit message #76:

split: copy_pma isn't in cleanup mode

we don't need to move the lightweight stack since we're not allocating
in a previous frame, but into the PMA

split: revise pre_copy() usage

pre_copy() is no longer public. instead, it is called at the start of
any NockStack method which could potentially be the first function called when
getting ready to pop a stack frame (preserve() and functions that
allocate in the previous frame).

main: trailing bytes of cued jamfile are zeroes

this was already the case, but it was doing so incorrectly.

write_bytes(dest.add(word_len as usize - 1), 0, 8)
copies 64 bytes of zeroes starting at the last word of the indirect
atom. this went unnoticed before split stack since allocation happened
at the west end of the memory arena. now that allocation starts at the
east end, this was causing an overflow. so the correct line is
write_bytes(dest.add(word_len as usize - 1), 0, 1)

split: memory in NockStack::new not mutable

it was before split stack, now its unnecessary and throws a compiler
warning. im confused as to why it was ever there at all, though
@drbeefsupreme
Copy link
Contributor Author

Ok I squashed all the commits and kept the useful sounding commit messages, but it doesn't look like there's a way to see what code they're referring to? Is there any way to maintain that or is it just lost unless I single out the commits with useful messages and squash everything in between them?

Sorry this process is confusing for me - what the right convention is and what the results will look like when I try a new process is unclear to me.

@eamsden
Copy link
Collaborator

eamsden commented Aug 11, 2023

Sorry this process is confusing for me - what the right convention is and what the results will look like when I try a new process is unclear to me.

In urbit we basically do commit-per-feature, so once commit saying this introduces the split-stack is fine

@eamsden eamsden merged commit 0869067 into status Aug 11, 2023
1 check passed
@eamsden eamsden deleted the jon/stack-split-mem-rewrite branch August 11, 2023 21:59
This was referenced Aug 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants