- 03 Apr, 2020 1 commit
-
-
Eric Myhre authored
-
- 02 Apr, 2020 1 commit
-
-
Eric Myhre authored
I was particularly worried about trailing optionals. Happily, they appear to work fine. Representation iterators and smooth stopping included. Starting to wonder how best to organize these tests to continue. 333 lines already? Questionable scalability.
-
- 01 Apr, 2020 13 commits
-
-
Eric Myhre authored
I started doing double-duty in tests to keep the volume down: the block for nullables tests both 'nullable' and 'nullable optional'; and the optionals similarly. Will make failures require more careful reading to discern, but probably a fine trade.
-
Eric Myhre authored
-
Eric Myhre authored
I'm almost a little staggered. We've got structures with maybes both using pointers and not, and everything seems fine. A few fixes were necessary, but they were mostly "d'oh" grade. (The finishCallback logic is a bit hairy, but appears correct now.) More cases for more variations in presence coming up next.
-
Eric Myhre authored
Sanity check level: gen result compiles. Next: time to target tests at all this jazz that actually exercise and run the generated code.
-
Eric Myhre authored
-
Eric Myhre authored
Easier than previous commit message seemed to think. Since we already have finishCallback functions in play in any scene where maybes can also be in play, we can just make those responsible for copying out a pointer from the child assembler to the parent value. That makes the child assembler able to create a new value late in its lifecycle and still expect it can land in a proper home. Tests needed badly; this is *oodles* of edge cases. Unrelated fix needed first. (Might make that elsewhere and rebase this to include that, to reduce the carnage ever so slightly.)
-
Eric Myhre authored
Fix many issues with maybes; adjunct config for if maybe is implemented using a pointer; attempt the finishCallback system for reusable child assemblers. This... almost appears to be on a dang nice track. Except one fairly critical thing. It doesn't work correctly if your maybe IS implemented as containing a pointer. Because that pointer starts life as nil in the parent structure. And that's hard to fix. We can't have a pointer to a pointer in the assembler; that'd cause the type bifructation we've been trying to avoid. (We can give up and do that of course; it would be correct. Just... more code and larger final assembly size. And if we did this, a lot of this diff would be rewritten.) We could have the parent structure allocate a blank of the field, and put a pointer to it in the maybe and also in the child assembler. Except... this is a wasted allocation if it turns out to be null. Rock and hard place detected in a paired configuration. Might have to back out of this approach entirely. Not sure.
-
Eric Myhre authored
We'll see how this works out.
-
Eric Myhre authored
-
Eric Myhre authored
Lots of tasty bitshuffling in this commit. Remaining: creating, embedding, and returning child value assemblers. This might involve a full type per field: doing so would be direct, have pretty excellent verbosity in debugging symbols, and pointers back to parent suffer no interface indirection; overall, it's probably going to have the fastest results. However, it would also mean: we can't save resident memory size if we have more than one field in a struct that has the same type; and we we can't avoid increasing binary size if the same types are used as fields in several places. Going to pause to think about this for a while.
-
Eric Myhre authored
This cleans up a lot of stuff and reduces the amount of boilerplate content that's just generating monomorphizing error method stubs. The nodeGenerator mixins now also use the node mixins. I don't know why I failed to do that from the start; I certainly meant to. It results in shorter generated code, and the compiler turns it into identical assembly.
-
Eric Myhre authored
(That line kind of says a lot about why this project is tricky, doesn't it.)
-
Eric Myhre authored
Compiles and runs; but the results don't. Some small issues; some big issues. Just needed a checkpoint. It's feeling like it's time to deal with breaking down the assembler generators in the same way we did for all the node components, which is going to be kind of a big shakeup.
-
- 29 Mar, 2020 4 commits
-
-
Eric Myhre authored
I *think* the notes on Maybes are now oscillating increasingly closely around a consistent centroid. But getting here has been a one heck of a noodle-scratcher.
-
Eric Myhre authored
-
Eric Myhre authored
-
Eric Myhre authored
In research: I found that there are more low-cost ways to switch which methods are available to call on a value than I thought. Also, these techniques work even for methods of the same name. This is going to improve some code in NodeAssemblers significantly -- there are several situations where this will let us reuse existing pieces of memory instead of needing to allocate new ones; even the basicnode package now already needs updates to improve this. It's also going to make returning representation nodes from our typed nodes *significantly* easier and and lower in performance costs. (Before finding methodsets are in fact so feasible, I was afraid this was going to require our typed nodes to embed yet another small struct with a pointer back to themselves so we can have amortized availability of value that contains the representation's logic for the Node interface... which while it certainly would've worked, would've definitely made me sigh deeply.) Quite exciting for several reasons; only wish I'd noticed this earlier. Also in research: I found a novel way to make it (I believe) impossible to create zero values of a type, whilst also making a symbol available for it in other packages, so that we can do type assertions, etc, with that symbol. This is neat. We're gonna use this to make sure that types in your schema package can never be created without passing through any validation logic that the user applies. In codegen: lots of files disappear. I'm doing a tabula rasa workflow. (A bunch of the old files stick around in my working directory, and are being... "inspirational"... but everything is getting whitelisted before any of it ports over to the new commits. This is an effective way to force myself to do things like naming consistency re-checks across the board. And there's *very* little that's getting zero change since the changes to pointer strategy and assembler interface are so sweeping, so... there's very little reason *not* to tabula rasa.) Strings are reimplemented already. *With* representations. Most of the codegen interfaces stay roughly the same so far. I've exported more things this time around. Lots of "mixins" based on lessons learned in the prior joust. (Also a bunch of those kind-based rejections look *much* nicer now, since we also made those standard across the other node packages.) Some parts of the symbol munging still up in the air a bit. I think I'm going to go for getting all the infrastructure in place for allowing symbol-rename adjunct configuration this time. (I doubt I'll wire it all the way up to real usable configuration yet, but it'll be nice to get as many of the interventions as possible into topologically the right places to minimize future effort required.) There's a HACKME_wip.md file which contains some other notes on priorities/goals/lessoned-learned-now-being-applied in this rewrite which may contain some information about what's changing at a higher level than trying to track the diffs. (But, caveat: I'm not really writing it for an audience; more my own tracking. So, it comes with no guarantee it will make sense or be useful.)
-
- 27 Feb, 2020 1 commit
-
-
Eric Myhre authored
Previously it was in the 'impl/typed' package, next to the runtime-wrapper implementation of the interface. This was strange. Not only should those two things be separated just on principle, this was also causing more import cycle problems down the road: for example, the traversal package needs to consider the *interface* for a schema-typed node in order to gracefully handle some features... and if this also brings in a *concrete* dependency on the runtime-wrapper implementation of typed nodes, not only is that incorrect bloat, it becomes a show stopper because (currently, at least) that implementation also in turn transitively imports the ipldfree package for some of its scalars. Ouchouch. So. Now the interface lives over in the 'schema' package, with all the other interfaces for that feature set. Where it probably always should have been. ('typed.Maybe' also became known as 'schema.Maybe', which... does not roll off the tongue as nicely. But this is a minor concern and we might reconsider the naming and appearance of that thing later anyway.)
-
- 30 Jan, 2020 1 commit
-
-
hannahhoward authored
rename EmitTypedLinkNodeMethodReferencedLinkBuilder to EmitTypedLinkNodeMethodReferencedNodeBuilder
-
- 29 Jan, 2020 1 commit
-
-
hannahhoward authored
-
- 07 Dec, 2019 4 commits
-
-
Eric Myhre authored
-
Eric Myhre authored
And expand on the need for both general and specific methods, even though they both can accomplish the same goals.
-
Eric Myhre authored
-
Eric Myhre authored
I'm still scratching out prototypes in another directory to make it easier to focus on the details I'm interested in rather than get wrapped up with the kerfuffling details of the existing full interfaces... as well as easier to try out different interfaces. And at some point, of course, we want *codegen* for these things, while what I'm plunking about with here is a sketch of the expected *output*. So, this is a large step removed from the final code... but it's easier to sketch this way and "imagine" where the templating can later appear. This solution approach seems to be going largely well. And we have some tests which count allocs very carefully to confirm the desired result! Still a lot to go. There's large hunks of comments and unresolved details still in this commit; I just need a checkpoint. Some things around creation of maps are still looking tricky to support optimizations for. Research needed there. A comment hunk describes the questions, but so far there's no tradeoff-free answer. Perhaps most usefully: here's also a checkpoint of the "HACKME_memorylayout" document! It also still has a few todos, but it's the most comprehensive block of prose I've got in one place so far. Hopefully it will be useful reading for anyone else wanting to get up to speed on the in-depth in "why" -- it's awfully, awfully hard to infer this kind of thing from the eschatology of just observing where pointers are in a finished product! Signed-off-by: Eric Myhre <hash@exultant.us>
-
- 08 Nov, 2019 1 commit
-
-
hannahhoward authored
Add a disclaimer to the header for generated code to prevent linters from complaining
-
- 07 Nov, 2019 1 commit
-
-
hannahhoward authored
Map/List should return an actual node type, not a pointer to a node. Otherwise, breaks when nested in other map/list type
-
- 25 Oct, 2019 6 commits
-
-
Eric Myhre authored
Using pointers for optional or nullable fields forces a lot of things to need allocations on the heap. And maybe that's not so good. So here's a very different take. 'Maybe' structures for any optional or nullable fields store the extra information about null or absent values. This takes more linear memory... but should tend to result in fewer allocs. It's also much easier to code against, I suspect. (And we expect it's not unreasonable for users to add more methods in the generated output package, which would mean we should be believable to code against.)
-
hannahhoward authored
Make emit methods public to allow a preliminary method for generating code outside this module
-
Eric Myhre authored
Signed-off-by: Eric Myhre <hash@exultant.us>
-
Eric Myhre authored
Rearrange to match 68b3383d, and add methods to match 41d79374. Everything compiles again, and resultant code passes what tests it has. Signed-off-by: Eric Myhre <hash@exultant.us>
-
Eric Myhre authored
(n.b. I've already tried implementing the suggested thing for structs, but won't link to it here as that commit is almost certainly going to end up rebased. Jury's still out on if the idea is good or not. Seems like the answer is almost certainly "it depends".)
-
Eric Myhre authored
-
- 24 Oct, 2019 2 commits
-
-
hannahhoward authored
fill in generated type for list
-
hannahhoward authored
Fill in generated types for int, link, and bytes nodes
-
- 17 Oct, 2019 2 commits
-
-
Eric Myhre authored
The previous names started to seem more questionable as we start generating additional code which is natively typed rather than interface constrained. Now the latter stuff is in a file that includes 'Node' as part of the filename. This seems better.
-
Eric Myhre authored
This introduces several new methods to the type generator. The new comment at the top of 'gen.go' explains the direction. There are several (sizable impact) TODOs in the methods for structs; this is because some other research I've been doing on performance is going to result in a re-think of how we regard pointers, and a *lot* of the struct code is going to get a shakeup shortly. Should be coming up in about two commits or so. (The 'Maybe' structs getting their first mention here will have something to do with it!) Some more file split-ups to keep node interface generation separate from native-typed API generation will be coming up next commit. You can also see here some TODOs regarding the future possibility of "validate" methods. This is something I want to pursue, but the implementation work will be nontrivial -- those TODOs will probably stay there a good while. Signed-off-by: Eric Myhre <hash@exultant.us>
-
- 15 Oct, 2019 1 commit
-
-
Eric Myhre authored
-
- 14 Oct, 2019 1 commit
-
-
Eric Myhre authored
(At long last! These child-builder methods are a step that was known to be needed for some time now, but I've intentionally been pushing off the implementation of it until we finally reached some *other* piece of functionality that would depend on it and force the issue. Finally, that's happened: that issue is unmarshalling into natively-typed codegenerated nodes, and that's now. Woo!) (Though the current focus of this is for natively-typed codegen'd nodes, if we put further effort into reflective-bind node implementations, they'll also lean heavily on these child-builder-getters for internally tracking the correct `reflect.Type` to create new values of -- very similar to what our schema-typed things are doing, just with the Golang type system instead of our schema types.) The child-builder getter methods more or less explain themselves. They yield a new NodeBuilder that's correctly specialized for child nodes in recursive structures. This is necessary for both keys and values in maps, and for values in lists. For values in lists and maps, getting the child-builder requires takes a parameter -- this is so typed nodes (namely, structs; but some unions will also lean on this) may have discriminated contents that differ per key or index. The BuilderForValue functions are able to take primitive strings and ints as parameters rather than Node, because all uses that have discriminated results are places where complex keys are not acceptable. Therefore primitives are fine, and preferred since it avoids boxing. The 'free' node implementation is updated to include this feature, as are codegenerated nodes. (The runtime wrapper typed node implementations have partial support, but stubbed methods where the path forward is clear but the implementation effort nontrivial and not currently on-priority-path.) Unmarshalling now uses these child-builder-getters to correctly handle the passing down of constraints and typeinfo. This addresses several TODO's that have been in the unmarshalling code for aeons -- hooray! (Somewhat remarkably, our solution here is much better than the one proposed in those ancient TODO's -- we actually got this done *without* direct abstraction-breaking examination for typed nodes. Nice!) Unmarshalling tests are also in this same diff. There's even a wee little benchmark using a snippet of JSON as fixture. This is the first time we see unmarshalling and marshalling end-to-end connected with codegen'd nodes. This is very exciting! There's still some questions to be answered about the most correct (and most performance-friendly) place to put error handling against asks for a child NodeBuilder with an invalid key or index, which is a very real invalid state that's reachable for typed nodes that are structs. Comments about that are inline in the diff. We'll probably have to come back to that soon, but I'm gonna let it stew on the back burner for a bit. Currently, panics are used, but this may not be acceptable ergonomics. --- It's somewhat effortful to even detail how many thoughts about performance and the future optimization potentials go into this diff. Some comments I had in the area of the child-builder specialization functions included concerns on: - where common-subexpression-elimination will and won't apply - where inlining is possible vs facing function call stack shuffle overhead - ... as well as how that affects common-subexpression-elimination - ... as well as how that affects escape analysis and shifts to heap - whether an isDiscriminated property will make any of these things better - can BuilderForValue hang onto a builder, boxed into interface already, when appropriate? - ... for the maps case, should amortize in a way comparable to isDiscriminated, yes? verify. - ... can we do that without the builder keeping another two words of memory just... around? i don't think so. question is if it pays for itself. - question: do zero value no-fields structs get a bonus? reading runtime/iface.go -- doesn't look like it. but needs confirmation. - ... revised statement: they do, it's just nonobvious. iface.go doesn't have a specialization, but the mallocgc call hits a special path internally for zero, and then the typedmemmove call 'coincidentally' hits its "src == dst" short-circuit, thus also being fast. ... all in all, as many of these as possible of these concerns have been considered and this design should be optimization-friendly; some of them are bucketed into "we'll see", because we need to build nontrivial programs to see how significant the needs are, as well as to see how clever the compiler can get with what we've already done. There are some possible alternative ways we might re-engineer the builders in the future to make them more performance-friendly. In particular, the fact that many builders carry a previous value with them in anticipation of one of the 'Amend' calls -- which is not necessarily going to be used! -- is a potential obstruction to optimization, since it makes many structures go from zero to more-than-zero size, and that hits significantly different paths in the runtime with very different performance characteristics. However, that is a bit much to change today; so the thought lives here as a note for potential future work. Adding a `BuilderForValueIsDiscriminated() bool` method later seems still seems on the table, but since it wouldn't require substantial restructurings or headaches, can be deferred until benchmarks show us exactly how much it matters. (A single `type ChildBuilders interface { Style() Complexity; ... }` with some enum for `Compleixty` resembling `SimpleList | SimpleMap | DiscriminatedMap | DiscriminatedList` might also be a tempting way to go about this.) --- An alternative design considered that deserves a quick note: we could've added a `ChildBuilders()` method to the MapBuilder and ListBuilder, and attached the further getter methods there. This might still be viable, if we found that there are more methods involved in the future and we want to group them: it *should* always land on the zero-fields struct path, which means it can be done without hitting an expensive malloc. However, at present this doesn't seem warranted: we don't have enough methods to make it feel important (especially after the recognition that Node variants of methods aren't needed in addition to the primitive args variants, described above). --- Whew. Lines of code in diff does not correlate to hours of thought required. Glad to have this landing. Even performance minutae aside, there was a significant period of time where I was terrified that all these abstractions were going to shatter magnificently when it came time to do the final end-to-end unification of all this massive arc of work. But now it's done. And it works. Whew. Next: more fleshing out of more types; perhaps more benchmarks; and also it's time to do some more drafts of the native accessors and builders. Some of that already has been done in parallel in gists while this diff was incubating, but it's time to commit it too.
-