@ngn my last revision of thoughts on how to do multitype vectorizable arithmetic (before it faded away on a todo list months ago) was to have a separate mini-language where you can define operations on any abstraction level you want, which gets statically compiled to a horrible mess of a lot of code
@ngn I'm interested in SIMD specifically here (which I think requires a different design). But it should be general enough to target NEON or a Wasm vector extension whenever that appears.
it is not unreadable, even comes with documentation: https://raw.githubusercontent.com/ktye/i/master/_/i3/w/readme what you linked to, is a reimplementation of the compiler in k. Because it was so slow, i translated w (which looks close to k) directly to k syntax. Now k can parse it natively (not execute, because it's a scalar language) and translate to machine code: https://raw.githubusercontent.com/ktye/i/master/k.k (currently only the go-code generator is implemented). @Marshall kelas is currently working on a language derived from b: https://github.com/kparc/bcc
how i envisioned multiplication pervasion (mostly just the style of dispatching, not the actual vector language or impls): https://dzaima.github.io/paste#0dVbNbuM2EL77KWYvWymQZWQbBIs6yaJpLhugCxQBWsA3WqQsOrLokpQt7TY99gF66QP1Tfok/YaUtE6CDRyHQw6/@ftmmL2yB@G0aUhqtxe@qJQl3@@V@2HGf0j4HR1UMaO19hR@VjQj0u@jcAtBn19G4YGF79/hFMJHFi4viLYQ7mmxINPUPemSdOMdiUZSWRvBS6vIqb2wwitKdK5yaoyn218@pTMqGUIC4o7NFoPZHyEUo9nfWGCzBYSfWO0MiwN@f53NZjAsVakb7TlKU5KvFO3a2ut9rQsRdvdW73B@ULMvuJ1ImZFV7vrL8ax7Stn1QjhFx0o1tDa@gsebwe1C1MKSNO26Vm7JPs@d72sVVWDtGCLtMkJiFW8MV0JyERzbu7vLOMcn9iB5Y/VnJQfs4MFyRAu2ow4c3WjnlXUZ7YR71M2GsHuskM…
@dzaima (forgot to add (ZZ, vec={w&x}) for bools×bools)
@Adám Sure thing! FYI: the function that creates the title has been changed a bit too, to give title first, then the completions, then the rest of the help
Thanks! It's certainly minimalistic. Credit goes to @RikedyP for making me aware of the "REPL" API :)
Some project management (persistence) might be bolted on top of LocalStorage ... since the API is stateless and you hold all state in a string
Also deleting of specific parts of the session log when you want to copy stuff somewhere, and the shift+enter/bksp keyboard shortcuts... but at that point I'm just looking for excuses to work on this :D
Trivial "Go back in time" (tree?) undo/redo functionality, restoring variable contents... the list goes on and on
Print everything by default :) I see that tryapl.org opens the webpage. Is that how it's intended to be used? if something follows \u08, interpret it as a link and open it? Or is it just hardcoded for that specific input
I think I'll just replace \u08 with \n or something... easiest way to unbreak this
@MartinJaniczek Yeah it was intended to be a bit like F1 in the Dyalog session - and it only applies to ]help (all UCMDs need custom wrapping in TryAPL, we won't be relying on ⎕SE). In the JS we simply response.search(/^ *[\b]help[\b]/)
@dzaima in a real impl you'd probably want to extract the common parts in separate functions. for instance all dyadic arithmetic primitives require casting a pair of numeric objects to their common supertype
(unless for N types you want to implement N*N cases per primitive)
@ngn Casting in a separate function would double or nearly double the cost, I think. And each case will be less than about 100 bytes, so having many cases isn't really a problem as long as they're easy to generate.
And note that for comparisons you can convert the larger argument down: it's easy to do saturating conversions until the larger argument is only one type larger, and possible but maybe not too efficient to convert it another step and store whether it's out of range.
@ngn Nearly double, not triple. What's your plan for vectors? I'm thinking you'd have to write the converted values, then the final result. And memory bandwidth is most of the cost of arithmetic.
@Marshall that's how i do it now - bring the two args to a common supertype first, then perform the requested operation
but i have only one function to do this: use recursion when necessary, throw domain errors for non-numeric types, bring to a common supertype. then it dispatches to a simpler function, based on type and ranks (atom-atom, atom-list, list-atom, list-list).
"atom" means scalar, "list" means vector
some of these simpler functions do clever (but still simple) things, like addiI() (that is, an int atom added to an int list) just swaps the args and delegates to addIi()
I'm thinking currently that the vector language should just build functions that read and write vectors (but can accept and return scalars as well), and assume fixed types. The dispatching would be handled by whatever calls those functions.
the absolutely ideal solution would be a single function Array arith(int op, Array x, Array y) that writes x86_64 code in a buffer as a fused loop (type conversions and operation), and executes it :) but i'm not smart enough
@Marshall if you want to handle the last 5 elements with vector registers of length 8, in x86 do you have to use a mask register on every operation, or is there some other facility for handling this?
@coltim right, that's the simple (and maybe better, i don't know) way out, but i'm curious about the vector case (thinking from the perspective of RISC-V where you can globally set a wanted vector length and that just applies to everything)
@dzaima oh I mean do an (unaligned) vector load at an index, picking up 8 elements (assuming the arrays have some buffer at the end), then do whatever ops, then vector-mask-store the ones you want to keep at the original index. anything beyond short vector lengths would probably invalidate this though
@coltim with error checking/size conversion, you'd want to also ignore the tail elements, so i assume all of those would need to be handled with masks too
@coltim also, stupid question: what is 'unaligned'? If i'm trying to read 64-bit ints into a 256-bit register, does my address need to be a multiple of 8 (64 bits) or 32 (256 bits)?
@dzaima hmm. I'm assuming that a series of operations are performed on each vector chunk, with a separate operation for extracting which "lanes" of the chunk had an issue (e.g. with a movemask). this could either be checked for an issue every vector chunk, or accumulated and checked only at the end. I guess the last iteration (possibly on a non-full vector) would need to mask out the dummy ending indices
@dzaima there's aligned loads and unaligned loads. aligned loads need to be a multiple of either 16 bytes (for 128-bit xmm/SSE), 32 bytes (for 256-bit ymm/AVX), or 64 bytes (for 512-bit zmm/AVX512). unaligned loads can be at any offset. on even relatively old CPUs there's not much of a penalty for using unaligned vs. aligned loads.
I think with AVX512 it gets a bit trickier since a cache line is 512 bits, so if your AVX512 load isn't aligned, it'll need to access two cache lines (versus just one if it was aligned)
my guess is that the latter is part of the motivation for increasing the number of loads/stores that can be executed each clock cycle (in the mostly unreleased intel CPUs supporting AVX512)
@coltim (I think this last iteration masking could be done only on the integer returned by movemask, without having to handle the vectors themselves specially (assuming you have dummy data at the end that's alright to run ops on)
@dzaima yeh, I assume if you're doing ops over the entire array you would be able to use aligned loads. but if you're handling a subset you would likely need to use unaligned loads (or some initial code to peel off indices until you hit something that is aligned)
I think the "peel off things until you get aligned" and the "scalar cleanup at the end" is part of why compiler generated SIMD code is a bit messy
@coltim that's what i've been assuming too; currently i'm thinking a vector language should have a global-ish state of masking, and a for loop iterating through vectors should set that state to whatever is beneficial (possibly also having a scalar definition too)
@dzaima are you familiar with ISPC's approach? I can't say I fully grok it but I think it borrows some concepts from GPU programming to have per-lane masking
]boxing on
Was ON
{⍵}{0=≢⊃⍵:⍬⋄,↑(⍺⍺⊃¨⍵)(∇(1↓¨⍵))} 'ex1' 'ex2' 'ex3'
eee xxx 123 0 0 0
]boxing on
Was ON
{⍵}{0=≢⊃⍵:⍬⋄,↑(⍺⍺⊃¨⍵)(∇(1↓¨⍵))} 'ex1' 'ex2' 'ex3'
eee xxx 123 0 0 0
@ngn x+y and -y should both hit RAM throughput limit, so implementing x-y as x+-y (assuming they don't fit in cache) should still be 2x slower than x-y, no?
my imagination written as hypothetical vector language pseudocode: https://dzaima.github.io/paste/#0lVTNbuM2EL77KWYvhbxwZKQNgqJKnEvaF1jfih4ocWwRK5EGSUtKt@mxD9DLPtC@SZ@k31A/TbIJFgVkSxwOZ7755htut6S8Vw9fPqfXylxe//obtefm8lqpbFz1Gxo/hg39crZVNM6Ssz977/yaPq1o2vZ0S6ppXKUiZzg0rAvabil6w4GiI8/nwGQO@DhU7mxjSO49mbghF2v2vYHDHCIgMJ6D85T1Ff1EfUGDvIeCvLz9GttEHVcRPp8kV1Ure5RsNdOxcaVqqFXho7FHqpyNPEQBIjBUwI59AJajCZF9EItl1qxTVJIct8JE1yE9qsFvLjrVZSwQmxiWDCECdQF7YyxrCnxSHpbmIdUweymrybp5mXI9pv9QqUZNdYCkaYmCdCPn3IHY6kAmUMkRiFGksikOko0khA2MJvlgy51SpyS58FG59mQaOZcYeFGlMV9XOYF7XK22i0wmWJmUAUjoo089C25CPClp/UxK4ZmU/p@QE…
APL (Dyalog Unicode), 107 bytes
{x←' '⍴⍨×⍨2/⌈/⍴⍵⋄(⌈⌿↑⍸' '≠x)↑x⊣{x⊢←(⊃⍵)@((⊃⌽⍵)∘+¨↓0,⍪1-⍨⍳≢⊃⍵)⊢x}¨↓(⊃,/' '(≠⊆⊢)¨↓⍵),⍪⌽¨⍸2{1 0≡' '=⍺⍵}/' ',⍵}
Try it online!
Makes an over-big matrix of spaces, and adds in the required words using @. Then, crops back to the required size.
rando update on my neverending prj for anybody whos interested: fixed heap perf issues. fixed a bug where it wasnt executing the correct block. fixed a bug where it wasn't setting the right number of slots. got permission to open source it. got core primitives implemented (but i suspect a few of them will need to be tweaked to be correct). still need to write a few opcode case, but its feeling closer and closer to "working".
Array languages remind me a bit of the compositional program SCORE that was regarded as one of the best, that some people still use, but despite that, it can't ever go any further because the developer was so insular that right up until he died he refused to collaborate with anyone else
<kritixilithos> @dzaima 'question is, how big that team must get until it gets better than one person' en.wikipedia.org/wiki/Diminishing_returns or how many people do you add before it gets worse than one person
@Razetime yes but it's horrible in practice, some glyphs look horrible stretched -> I only stretch them partway -> inconsistent height -> horrible spacing
right now, hmm 1) APL seems to have a larger user base 2) APL's syntatical choices make more sense to me now 3) J seems to be supported in more places 4) writing APL outside RIDE isn't enjoyable, writing J is as enjoyable as everywhere else 5) J has ⎕io←0 by default and you can't change it so it's better 6) now that i learned APL, glyphs make a lot of sense to me, at least much more than (random letter): or . 7) it seems that you can find employment in APL 8) no good APL implementation is opensource, the best J implementation is opensource
@Wezl but it's still in an acceptable state -- it has all of ascii, I made it more consistent now, and at least it's in a reasonable size so fallback glyphs are visible
for example - i knew a few friends who use Arch, and I tried to get them into APL, they initially liked it, but after they learned that GNU APL is dogshit and Dyalog APL is the only reasonable implementation, and it doesn't support their system and it's closed source, they gave up
that being said, i won't believe until i see it, and until i see it i'll treat APL as a nice golflang/problem prototyping lang for simple or very specific problems/esolang(pls don't eat me)
@dzaima Lots of strategies. In Dyalog arithmetic, I used the fact that array data has a whole number of words, so I only had to handle a small number of words at the end with half writes and quarter writes. Ideally you'd have a whole vector register of the space at the end. Just aligning to vector registers isn't enough in some cases since you might want to pass in an offset pointer, although this tends to affect stores more often than loads.
If you can assume the computation is at least one register long then you can also do a complete step for the last one but align it to the end, so it overlaps with previous ones.
I'm inclined to say that very small lists should just always use the largest type and go in vector registers, but you can only fit four doubles in an AVX register so that doesn't accomplish too much. Maybe you could use multiple registers?
@dzaima @ngn For the SIMD language, it would be nice to write variations on arithmetic using inflections. Maybe saturating arithmetic would be +.-. and overflowing versions +:*:? We'd need special syntax for the two result registers with overflows.
@Marshall i decided not to that in ngn/k (small arrays in registers), it would probably be faster but too complicated for me. i don't have enough experience.
@KamilaSzewczyk It's a little confused about the public domain. There's no legal basis for releasing work into the public domain in the US; you have to use a license. I use BSD-0 when I want to release with no restrictions.
@Marshall I would probably go with CC0; countries like Germany or US have really weird and liberaln't legal systems (impressum, requirement to hold the copyright, etc...)
anyways, why would you care about a public domain project
everyone can do everything already, why is it so hard to relinquish all rights to something, and, more importantly, why would i put so much effort to accomplish that
@KamilaSzewczyk What does it fail to accomplish exactly? If you're talking about moral rights I don't consider that important. There's no reasonable use of source code that would require you to violate those.