01:34 jnap joined 01:52 woosley joined 02:00 lizmat joined 02:50 FROGGS_ joined 03:46 woosley1 joined 03:49 nwc10 joined 04:23 eternaleye joined 04:35 woosley joined
timotimo gist.github.com/timo/1f4fa4bc76b270829382 06:50
the size bucket 32-39 gets *quite* a lot of filling 06:51
i'm glad to report, however, that this program i'm running there has a peak rss of only 111 megabytes 06:53
i ought to try to get some more rest :\
dalek arVM: 4bfc5eb | (Timo Paulssen)++ | src/gc/gen2.c:
bump the cur_page of MVMGen2SizeClass
06:54
arVM/gdb-support: f0d7fa9 | (Timo Paulssen)++ | moar-gdb.py:
with the power of hilbert, display page usage
JimmyZ timotimo++ 06:56
07:48 woosley joined 07:52 FROGGS joined 08:05 cognominal joined 08:30 lizmat joined 08:43 odc joined 09:23 woosley joined 09:30 woolfy joined 09:52 woolfy left
timotimo i'm finding an object that's nulled in one of the pages :\ 14:14
there's actually a whole lot of objects that have their size set to 0 o_O 14:19
WTH.
i must be miscasting or something
jnthn Or reading freelist objects? 14:22
timotimo no, i made extra sure not to do that 14:23
even then, why did i sample a few objects that have too large sizes?
24 [================================================== 63 1.512
88 [ 1 88
160 [ 1 160
not terribly unlikely values for sizes, but also unexpected
14:26 FROGGS[mobile] joined 14:30 jnap joined 15:50 lizmat joined 16:06 woolfy joined 16:18 tgt joined
lizmat fwiw, it appears nqp on moar has test failures 16:21
./nqp t/serialization/01-basic.t
*snip*
ok 893 - for integers around 2 ** 30, integer -1073741825 serialization round trip (11)
Segmentation fault: 11
jnthn lizmat: Need moar newer moar? 16:24
lizmat: The tests cover a segfault that (should be) fixed.
timotimo lizmat: \o
lizmat fetching newer 16:25
all tests successful!, sorry for the noise 16:27
timotimo that's all right 16:28
i made a silly mistake with the size bucket thingie 16:29
jnthn lizmat: phew :) 16:30
timotimo i still do find objects that claim to be 0 bytes big. that's weird. 16:31
P6int [================================================== 14033 ← this is the vast majority of what's in the size bucket for 32 16:32
hm. i may still be stepping wrongly. 16:33
timotimo is trying to get the data out of the MVMP6int objects, but gets No struct type named MVMP6int. from gdb 16:44
>_>
hm. P6int isn't immutable, is it? 16:48
jnthn It's immutable 16:50
timotimo gist.github.com/timo/ecfca78434a844d7c4d5 ← a histogram of sampled P6int's values
time for a constant pool? :) 16:51
jnthn timotimo: Hmmm
Well, yeah, we could in HLLConfig do that.
timotimo clearly it would even make a big difference if we limited ourselves to 0-16
jnthn I was thinking 0..8
timotimo even that would help
timotimo opens up a quest on questhub 16:52
dalek arVM/gdb-support: 17997b1 | (Timo Paulssen)++ | src/gc/gen2.c:
bump the cur_page of MVMGen2SizeClass
16:54
arVM/gdb-support: e1998b2 | (Timo Paulssen)++ | moar-gdb.py:
a whole lot of work on gen2 analysis
timotimo i shall make the "bump the cur_page" thing obsolete soon 16:56
jnthn: i should allocate these constants in the gen2, right? 16:57
jnthn timotimo: That'd be a nice touch, yeah 16:58
timotimo: Do the creation of them at set_hll_config time
timotimo thank you, i was just about to ask that 16:59
16:59 lizmat joined
jnthn timotimo: Then we'll never have to check for their existence and lazily generate them. 16:59
timotimo yup
it's only 0 through 8 (inclusive) anyway
that's hardly an overhead
jnthn yeah
16:59 woolfy joined
jnthn Right, and givnen what it's going to save... 16:59
That means you'll generate them "for free" for Rakudo also. 17:00
timotimo aye
should i refer to these constants from MVM_hll_map? 17:01
jnthn Not sure I follow 17:02
timotimo well, i'll have to grab the ints from the constant pool in some place
otherwise it'll just merrily build more and more ints
jnthn It's set_config and then the places that box that need it
timotimo all the places, then?
jnthn Which is box_i in interp.c and then the MVM_repr_box_int 17:03
I think those are the only two.
timotimo ah, so i wouldn't skip MVM_repr_box_int in MVM_hll_map, but instead implement the cache in MVM_repr_box_int
gotcha
jnthn Any places doing it manually besides the one in the interp can do so
Well, you build the cache in MVM_hll_set_config 17:04
And then access it from the other places.
timotimo yes, that part was clear to me
what i was wondering about was when exactly to look for values in the cached range 17:05
jnthn Oh, hm.
Maybe, looking at this a bit more, HLL ain't the place to hang it... 17:06
timotimo it isn't? because that'd already have the association to the right type object to box with 17:08
jnthn yeah, but box_i doesn't mean "current HLL"... 17:09
It gets a type object.
timotimo oh!
that it does
so obviously the type object for P6int should have the cache :P
jnthn Well, that won't quite work out either... 17:10
timotimo i feared so
jnthn But yeah, it is kinda like we want to do something along those lines.
timotimo how terrible would it be to check if the given type object is the one from the current hll and use the hll's cache in that case? seems pretty darn wonky 17:11
17:11 tgt joined
jnthn Yeah, that is a bit off. 17:11
Would probably work though :)
timotimo it wouldn't crash, but it wouldn't be nice, either 17:12
at least these checks are fairly cheap
17:14 tgt_ joined
jnthn It may be the most expedient way. Guess we should just encapsulate it in one place, so if/when we figure a nicer way we can sue that. 17:15
And the p6box_i has it easy.
17:17 benabik joined
timotimo well, set_hll_config doesn't seem to be the right place; apparently that doesn't get called when we're building NQP at least 17:19
maybe it ought to go into the if (!entry) inside hll_get_config_for 17:20
jnthn It must do...
timotimo or, it ought to go there, too
jnthn NQPCORE.setting uses it.
17:20 tgt joined
timotimo oh. circularity saw problem mayhaps? 17:21
jnthn Nope
timotimo oh. now i get a null pointer access when i try to get interp->current_cu (because i'm in get_hll_config_for) 17:22
so that's also wrong
jnthn Oh, wait
NQP doesn't supply its own box types
timotimo oh
jnthn It just uses the default BOOTInt.
timotimo fair enough 17:23
jnthn So yeah, that really tells us that we're hanging it on the wrong peg, I guess...
timotimo yes.
the win is *very* juicy, though :) 17:24
jnthn Well, we can always keep a cache elsewhere...
Hang it off tc or something.
Or even instance, if we're careful.. 17:25
timotimo huh. now i'm getting This representation (P6num) cannot unbox to a native int
(i gave it a check for null to skip the cache if it hasn't been initialised, just to see how much it's actually worth)
wait. should i be using int_box_type or foreign_type_int in set_config? 17:28
jnthn int_box_type 17:30
But I really don't think hll_config is the right place after all...
timotimo fair enough. 17:31
so if i put it into tc, what's the right place to fill it?
could just lazily fill it
jnthn We could, but there's a better way, I think.
The cache can be 17:32
an array of some struct with an MVMObject *type_object followed by an array MVMObject*[8] 17:33
And we write a function "MVM_intcache_for(tc, some_type_object)" 17:34
Which adds a cache entry
And then we just scan through the cache.
timotimo and when we compose something with a repr that can box_int, we can call the intcache from there? 17:35
jnthn In fact, cache-better is to keep an array of 4 type objects, and 4*8 array for the values
And then it's one cache line to scan.
Right. We just look through the first array to see if any entries match the type we have, and if one does then we look up the value.
Note that we make the cache fixed size. 17:36
timotimo that hangs off of the tc then?
jnthn Off instance, I guess, so all threads can share it.
timotimo OK
jnthn The trick is we *never* resize it, or change things we added.
timotimo yes 17:37
jnthn So we get easy thread safety
Maybe a mutex for adding is wise.
timotimo that ought to be easy.
jnthn Though unlikely to matter in practice.
Yeah, just need to take care with the layout so it's cache-friendly is all :)
timotimo hm. so if i make it [8], it'd not keep the 8 17:39
but having 9 is wonky, cache-wise
jnthn True :)
Could make it 15 :)
timotimo i *suppose* we could cache 0, 1, 2, 4, 8
jnthn Nah...then the lookup gets costly.
timotimo since 4 is still more common than 3 is
fair enough
jnthn Remember we'd like to JIT the lookup some day...
:) 17:40
timotimo ah, aye
do i have to be careful WRT moving if i hold MVMObject * in there for the type objects?
jnthn Oh, you have the GC mark this. 17:45
I'd put all the logic related to this in an intcache.c 17:46
timotimo mhm
would that be src/core? 17:47
jnthn yeah, can be 17:48
timotimo since the contention is probably crazy-low, should i just make a static mutex in that .c file? 17:49
jnthn No, 'cus that screws up running multiple instances... 17:50
timotimo will it, though?
ah well. doesn't hurt to make it.
jnthn Yes. Aside from totally immutable things like callsites, nothing should be static really.
timotimo is taking baby steps 18:01
okay. i have no cache now, but i'm trying to read from the empty-initialized cache and it doesn't crash 18:06
that's a good start, methinks
should i put the intcache_for call into the hll function again?
jnthn Yeah 18:07
And also do it for BOOTInt
timotimo sure thing. 18:08
would that be in instance_create or in the hll thing?
jnthn Neither
Probably in bootstrap, after BOOTInt is set up
timotimo bootstrap, as in ... called from NQP code? 18:09
oh, i forgot to add any marking whatsoever 18:10
i suppose instance has a function that marks stuff it likes?
or should i just add it to the gen2 roots?
wait. if i instantiate it in gen2 anyway, won't it stay put for ever?
18:10 tgt joined
timotimo hm. the cache didn't make a difference yet. 18:11
ah, haha 18:12
hm. that wasn't the fix yet :\
now i get cache hits, but This representation (VMHash) cannot unbox to a native int ← wtf 18:13
moritz well, sounds sensible to me 18:14
timotimo why does it even try that? 18:15
moritz I have no idea
probably a bug :-)
timotimo is that so! :)
trying to build nqp's QRegex now gives me This representation (P6num) cannot unbox to a native int 18:16
... again
perhaps the values of the ints i have in the cache don't correspond to the values that ought to be in there? 18:20
and it runs into a problem where P6num is in some list that it indexes into with a wrong int?
18:22 FROGGS[mobile] joined
jnthn timotimo: I hope you are GC marking the cache? 18:22
timotimo oops, yikes :)
jnthn timotimo: So the memory doesn't end up pointing to other objects later?
timotimo i'm not, but i've done something quite dumb :)
hm, actually, that shouldn't have exploded
jnthn s/something/two things/, sounds like :P
timotimo yeah. 18:23
jnthn OK, nom time...then Moar hacking time... :)
timotimo \o/ 18:24
i don't see a place from which to mark the pool cache 18:26
doesn't seem like instance gets a mark function at all?
you were right. it was gc trouble ... of course 18:28
jnthn See roots.c
timotimo now i have the cached ints as permanent roots, which isn't that much better
jnthn That also works
timotimo ah, there!
it would probably be more memory efficient if i implemented it in the add_instance_roots function, eh? 18:29
jnthn yeah
timotimo that's my next step, then
i'll have a look at the memory usage of -e 'say 1' again
maybe there's a tiny improvement :3
jnthn Yeah. I suspect it'll be a time improvement though as less GC churn.
timotimo hm. now we have a bunch of P6opaques in the cache; those aren't immutable, though 18:30
that could be trouble, eh?
aaw. no memory usage improvement? :( 18:31
still at 92 megabytes of ram usage for -e 'say 1'
jnthn huh, where'd the P6opaques come from?
Oh, Rakudo's Ints
but yeah, Int is immutable too 18:32
timotimo what if someone builds their own class with an int box target? how do we handle that?
ah, there's still lots of P6ints in the gen2 with values that ought to be cached 18:34
jnthn timotimo: We won't register their type as one to cache, though? 18:37
At the moment we're only doing BOOTInt and anything you set_hll_config on?
timotimo oh, that's right
(well, BOOTInt is still missing)
(because i didn't understand where you wanted me to put it; is bootstrap actually a moarvm thing?)
jnthn Yes 18:38
src/6model/bootstrap.c is where BOOTInt is set up
timotimo oh, great! 18:39
jnthn bbi20
timotimo forgot to fix up interp.c until now 18:53
18:53 tgt joined
timotimo oh, hm. i may be mistakenly clearing the "allocate in gen2" bit after creating the cache 18:56
the memory usage has now ... gotten more :\
still a whole bunch of low-value integers >:( 18:57
though when i still had debug output, i saw lots and lots of "cache hit" messages 18:58
jnthn timotimo: Yeah, don't put the gen2 allocation on/off in the cache logic itself 19:04
19:04 benabik joined
jnthn timotimo: I can take a look at the patch, if you like 19:05
timotimo sure, gimme a sec :)
gist.github.com/timo/64ccbdb18bdf11b705cf 19:07
maybe i have to teach rakudo about it, too?
accidentally got some hunks from the gdb script in there 19:09
hm. perhaps those come from deserialization or something? 19:12
indeed, it needs to learn about that, oto. 19:13
too*
jnthn + res = MVM_intcache_get(tc, type, val);
+ if (res == 0) {
== NULL will get rid of a warning 19:14
Or just if (!res)
+ /* By far the most common integers are between 0 and 8, but we cache up to 16
15, no? :)
timotimo er, yes. 19:15
jnthn In MVM_intcache_for it seems you never set right_slot? 19:16
Again, == NULL is better in the loop there too 19:17
timotimo er, i do? maybe you have an out of date patch >_<
jnthn + int right_slot = -1; 19:18
+ for (type_index = 0; type_index < 4; type_index++) {
+ if (tc->instance->int_const_cache->types[type_index] == 0) {
+ }
+ else if (tc->instance->int_const_cache->types[type_index] == type) {
+ return;
+ }
+ }
+ if (right_slot != -1) {
timotimo how the ...
there was a right_slot=type_index; break; in there
i'll get the code to you via git in a jiffy
jnthn Some -/NULL confusion. 19:19
0/NULl I mean
dalek arVM/gdb-support: f1d1511 | (Timo Paulssen)++ | / (12 files):
implement a constant cache for ints 0 to 16
arVM/gdb-support: d881070 | (Timo Paulssen)++ | src/6model/serialization.c:
teach serialization about the int cache
arVM/gdb-support: 269193a | (Timo Paulssen)++ | src/core/intcache.c:
avoid a warning comparing == 0
benabik timotimo: You appear to have a very slow timer, if a jiffy is that long. ;-)
timotimo benabik: line delay :) 19:20
timotimo replaces more 0 with NULL
oh. now i b0rked it completely it seems 19:21
haha, silly me.
jnthn Also it seems the type objct entry in the cache ain't marked, in the last version I saw...maybe you fixed it already
dalek arVM/gdb-support: 55ca81d | (Timo Paulssen)++ | src/ (2 files):
oops in serialization, more 0/NULL
19:22
timotimo i had that problem long ago
oh, wait
the *type* object. good catch!
also i was inconditionally adding the root in that same function
rather than only if we found a slot
dalek arVM/gdb-support: 5a8fe6d | (Timo Paulssen)++ | src/core/intcache.c:
add type object to roots.
19:23
timotimo it occurs to me that this ought to help startup time, too. since the deserializer will not have to allocate as much stuff 19:25
jnthn timotimo: In serialization.c you coulda probably called the REPR convenience function
timotimo: Meaning the intcache needs to be mentioned in one less place, and we get shorter code
timotimo 0.20user 0.02system 0:00.23elapsed 98%CPU (0avgtext+0avgdata 89916maxresident)k 19:26
finally a visible change!
but only like 3 megabytes saved :(
jnthn Uh, 3MB is quite a bit! 19:27
timotimo the most common integers in the gen2 are now 95 and 58
(and 675 completely filled pages) (and 2 empty pages) (freelist with 1 entries out of an allocd 1 )
REPRs (sampled):
P6num [================================================== 5471
P6int [====================== 2479
time to analyze what nums we have lying around :D
jnthn if (right_slot != -1) {
You have two conditionals like that one below the other in MVM_intcache_for that can be made into one. 19:28
timotimo OK
jnthn intcache.h wants adding to le makefile too
timotimo will do.
are you on the very newest code? because i only see one of those conditionals there 19:29
jnthn oh, maybe you pushed again while I was reading the diff...
No, it says all up to date here...
timotimo ... weird o_O 19:30
jnthn oh, it is gone
Hm, huh :)
19:30 FROGGS[mobile] joined
jnthn anyway, looks pretty good now 19:30
timotimo well, i'm happy :)
kind of surprised to see absolutely no negative values in the gen2 19:31
dalek arVM/gdb-support: 64e92cf | (Timo Paulssen)++ | build/Makefile.in:
add the intcache header to the makefile
19:35
arVM/gdb-support: 69529ee | (Timo Paulssen)++ | src/6model/serialization.c:
serializer uses convenience MVM_repr_box_int function now
timotimo my delusions of grandeur regarding the int cache benefits are shattered ;( 19:40
haha. how fitting! :) 19:41
the most common double value is 6.0 [================================================== 252
4.0 [===================================== 191
1.0 [================ 85
19:41 tgt joined
timotimo very, very, very, very many double values that could just as well be integers. huh. 19:42
i must be measuring wrong?! 19:43
haven't hit a single P6num in my sampling that doesn't end in .0 19:44
P6num [================================================== 5494 19:45
more than twice as many as P6int
jnthn timotimo: remember that NQP does a lot of stuff with _n ops 19:46
timotimo hm. oh well.
i suppose so.
i shall squash the commits 19:47
are you OK with merging it into master?
(though i'll keep the gdb stuff in my branch for a bit longer) 19:48
jnthn timotimo: Yes, provided it passes spectest 19:50
(and nqp test)
un, well, doesn't regress spectest :)
timotimo i should test that, aye.
hmm. i wonder, though 19:51
how did all these many many double values make it into gen2?
jnthn Not sure. 19:52
timotimo probably harmless.
jnthn Possibly by being closure-captured...
timotimo oh, that could definitely be a thing 19:53
jnthn We don't lower any lexicals right now.
timotimo lower lexicals to locals, you mean?
jnthn yeah
It's not just a speed thing, and a block flattening thing, but also an object lifetime thing.
timotimo i'll put it back onto my long-term goals list :)
the *hash tests are known to fail on rakudo-m? 19:55
jnthn Which ones, specifically? 19:57
baghash fails 2 tests for me. 19:58
timotimo they all abort here :\ 20:01
waiting for the socket tests to finish being dumb before i get the summar
summary
dalek arVM/eieio: 305df11 | jnthn++ | / (7 files):
Correct Latin-1; add Windows-1252.

The Latin-1 implementation we had actually did Windows-1252. We're not HTML 5, so should actually Latin-1 when asked to. Keep the original code around as a Windows-1252 implementation.
20:04
benabik ... Why is the branch eieio? Just to get Old McDonald stuck in my head? 20:05
jnthn Yes. :) 20:06
moritz benabik: try to get www.youtube.com/watch?v=SQfMg0ejewk into your head instead :-)
benabik moritz: Well, it displaced www.youtube.com/watch?v=StTqXEQ2l-Y
timotimo the *hash tests abort on master, too 20:09
perhaps i have an out-of-date master.
enhanced implementation of ephemeral input/output ← eieio 20:12
ephermal* apparently
benabik Backronyms are fun.
jnthn eradicating irrationalities encumbering IO 20:14
timotimo :D 20:15
moritz enraged, idiotic, essential IO
my backronyms have been better before :-) 20:16
time for some sleep&
timotimo gnite moritz!
jnthn: no spectest regressions, the maxrss of a 4-test-job spectest run has gone down by about 5 megabytes 20:17
also like 7 seconds less time, which may be a measuring error
jnthn \o/ 20:18
timotimo so ... add the mutex before merging it onto master? 20:21
that mutex shoud reside in MVMIntConstCache, yes?
jnthn Yeah, it can do. 20:22
Oh
Well, convention so far is mutexes for things in instance hang off instance
So maybe follow that convention.
timotimo that's okay
jnthn Not it's only for changing.
*note
timotimo aye.
since putting the type object itself into place is the last thing i do, it shouldn't race, i *hope* 20:23
jnthn Yeah, in thory.
In practice, we may want a volatile in there.
And/or a memory barrier.
timotimo well ... maybe someone who's already worked with this kind of thing could do that :)
jnthn k
dalek arVM/eieio: 9ab2194 | jnthn++ | src/strings/ (6 files):
Implement streaming decode for Latin-1 and ASCII.
20:24
20:24 FROGGS joined
dalek arVM: 96e7aaf | (Timo Paulssen)++ | / (12 files):
implement a constant cache for ints 0 to 16
20:27
arVM: fdf7c35 | (Timo Paulssen)++ | src/ (3 files):
protect adding new types to int cache with mutex
timotimo merged \o/
jnthn \o/ 20:31
20:32 jnap joined
dalek arVM/gdb-support: e7f378c | (Timo Paulssen)++ | moar-gdb.py:
look at doubles
20:33
timotimo gdb-support branch cleared of int cache stuff
FROGGS good evening 20:37
20:48 benabik joined
timotimo jnthn: should i kill the check that verifies that the int object we got ouf of the cache actually does have the right value? 20:52
jnthn timotimo: Oh, yes 20:53
timotimo: I thought that was just for debuggering while you built it
uh, debugging.
timotimo yeah. i forgot about it for a little bit ;)
dalek arVM: 761a00a | (Timo Paulssen)++ | src/core/intcache.c:
remove unnecessary safety check/debugging thingie
20:54
arVM/eieio: 3f65a5b | jnthn++ | src/strings/ (4 files):
Assorted DecodeStream fixes and tweaks.

With this, we're back to no regressed spectests in Rakudo for this branch.
21:08
timotimo yay! :D 21:09
benabik \o/
jnthn Now everything's fixed, time to break more stuff :)
benabik "If it ain't broke, fix it anyway." 21:10
timotimo "if it ain't broke, break it so you can fix it" 21:11
FROGGS \o/ 21:16
jnthn++
dalek arVM/eieio: 349d789 | jnthn++ | src/6model/reprs/MVMOSHandle.h:
Toss an unused constant.
21:23
arVM/eieio: f75c631 | jnthn++ | src/io/fileops.c:
Toss code too wrong to have ever worked.
21:26 jnap joined
timotimo is REPR an acronym for something? 21:31
jnthn No 21:34
Just means "representation"
timotimo i should stop spelling it that way then :)
hey, it seems like string constants on the string heap are sometimes in there a whole bunch of times 21:41
jnthn They're only interned per compilation unit at the moment. 21:43
So yeah, if two compilation units have the same string...
It'll appear twice.
Interning is a cost too, of course...
Any way you can calculate the benefit? 21:44
It complicates memory management too, I suspect.
So I'm not really in a hurry to implement it...
timotimo hm. 21:47
i don't think i can.
right now i'm talking about the section with constant strings in the .moarvm files, btw 21:48
we may have miscommunicated there
jnthn Oh...
timotimo in that case, interning should be much cheaper and memory management less of a problem
jnthn Yeah, but they shouldn't be duplicated within the constant strings section in the MoarVM file 21:49
timotimo in that case i must be looking wrong
jnthn Well, the de-dupe may be wrong too
timotimo ah, there's already de-dupe code, then?
jnthn See src/mast/compiler.c:get_string_heap_index
timotimo thanks 21:50
jnthn That hsould mean a string only appears once in a given .moarvm file
However, two .moarvm files may end up mentioning the same constant string.
timotimo hmm.
jnthn We do nothing about that.
timotimo i see at least &infix:<...> at least 5 times 21:51
jnthn If you're seeing the same one twice in the same .moarvm, then that de-dupe is busted...
On the heap, or in the .moarvm?
timotimo 10 times? 20 times?
in the .moarvm
jnthn o.O
timotimo at first i thought the cuids were in there multiple times, but they differ at the beginning rather than at the end
jnthn Worth looking into 21:52
timotimo ← le tired 21:53
← le sniffy nose
jnthn :( 21:54
You seem to have not been well for a while :(
timotimo only a few days 21:55
i'll be fine in a bit. though the timing of my flu or whatever it is is kind of unfortunate
anyway 21:56
good luck with eieio :)
jnthn Thanks :)
timotimo hoping to wake up to some cool commits in the morning :3
jnthn Get well!
timotimo aye aye
22:27 colomon joined
dalek arVM/eieio: 928fe39 | jnthn++ | src/ (3 files):
Shuffle bytes reads over to DecodeStream too.

This means we won't get out of sync if doing mixed string/bytes reads, but additionally lets us remove another place we directly call libuv to read data.
22:28
23:33 tgt joined