I'm not surprised, GROK is probably trained on stackoverflow answers to your questions. If you ask it about a problem less common then it will likely struggle to understand your true problems, and give similar-sounding solutions instead.
LLMs are interpolation machines. They are great at giving answers very close to their training data, poor at answers further afield.
That's all true, but ... I just tried an exercise.
Here are my prompts, and very abbreviated excerpts from its responses. It actually wrote a many different version of strllen, strcpy, strcmp as we went:
If you wanted to add a new instruction to the scalar part of an existing ISA such as MIPS to speed up C string handling e.g. strlen, strcpy, strcmp, what would you suggest?
> The key bottleneck in string operations is the sequential scan of bytes, often one per cycle, limited by memory alignment and the need to check for the null terminator or character differences. A single instruction that processes multiple bytes at once, detects the null terminator, or compares strings efficiently would be ideal.
>One effective approach would be to introduce a "string scan" instruction that processes a word (4 bytes in MIPS32) or double-word (8 bytes in MIPS64) at a time, detecting the null terminator and returning its position.
> Proposed Instruction: strscan Reads a word (4 bytes) from the address in rs1. Scans the word for the first null byte (\0). Writes to rd the offset (0–3) of the null byte within the word, or 4 if no null byte is found. Updates rs1 to point to the next word (auto-increment).
I see some problems with strscan. Loading from memory and then doing arithmetic on it violates RISC principles. Also for strcpy or strcmp it requires a second load instruction for the same data.
> Revised Proposal: zdetw rd, rs1 (Zero Detect Word) Writes to rd the offset (0–3) of the first null byte, or 4 if no null byte is found.
strcpy writing the dst past the null is a serious bug, violating the strcpy spec, which must do precise insertion
> New Instruction: zdetwm (Zero Detect Word with Mask) Sets rd to the offset (0–3) of the null, or 4 if none. Sets rs2 to a mask: 1s for bytes up to (but not including) the null, 0s after
This masking is good, but this zdetwm instruction violates r2w1 design.
> Revised Proposal: zdetw rd, rs1 Writes to rd a byte mask: 1s for bytes up to (but not including) the first null, 0s after
> {code using ctz in the loop}
Making all zeros after the first null is good for strcpy but reduces the chances of using it for other things. It is also not a pure SIMD operation but has dependency within bytes, which could slow the instruction on larger words sizes e.g. 64 bits. Also there is no need to know the exact position of a null in the main copy loop but only whether one is present or not
> Revised Proposal: zchk rd, rs1 (Zero Check) Writes rd = 0 if any byte is \0, rd = 1 if no bytes are \0.
Good, but I still like the byte by byte mask, not a single bit 0 or 1 output
> New Proposal: zmask rd, rs1 (Zero Mask) For each byte in rs1, sets corresponding byte in rd to: 0xFF if the byte is non-zero. 0x00 if the byte is zero.
{a few cycles trying to hint that it had missed that ctz counts bits not bytes in the last chunk (with the null in it)
Good .. but please check what you are returning from strlen
It seems like you don't understand what ctz returns
> {notices that MIPS is BE so ctz won't work, switched to byte by byte compares for the tail}
Not really the fix I was looking for. Let's either use clz instead of ctz, or else assume a LE MIPS system and continue with ctz
> {produces almost correct BE and LE versions, using clz or ctz as appropriate. FINALLY adds srl 3 to convert bit count to byte count in both versions}
ok good. Let's stick with LE. Can we keep the loop in the loop, so there is only one taken branch in a normal loop, and final bytes are handled after the loop? Also we can subtract pointers after the loop to determine the 4 bytes per loop counted, not actually have an addi instuction every iteration
> {fixes the loop}
this is looking great now, except you need to count trailing 1s (non-null bytes) not 0s
> {adds nor with zero reg in loop tail before ctz .. all code is now correct and beautiful}
Looks great. How does zmask compare to instructions in RISC-V?
> {nothing good in base ISA, nothing except ctz in B ext, vmseq.v, vfirst.m do the job in V ext, shows the best we can do in each}
Let's ignore RVV and stick to scalar. The B extension was ratified in 2021, it is not draft. Did you consider the orc.b instruction?
> {describes orc.b, tries a sample string "abc\0| with both orc.b and our new MIPS zmask}
> Holy smokes—they’re identical! I missed this entirely. orc.b does exactly what zmask does, down to the byte-wise non-zero check producing 0xFF or 0x00.
> Why I Missed It
> I focused on ctz and clz in Zbb, assuming no single instruction matched zmask’s mask generation. orc.b slipped under my radar—its name (“OR-combine”) didn’t scream “string mask” to me, but its behavior is a dead ringer. My bad for not digging deeper into the ratified spec.
> Verdict
> zmask = orc.b: Functionally, they’re twins. If we’d designed zmask for RISC-V, we’d have reinvented orc.b.
> RISC-V Wins: Since orc.b exists, it’s the go-to for scalar string acceleration in RISC-V, no custom instruction needed.
> MIPS Context: zmask still makes sense as a MIPS extension, as MIPS lacks an orc.b equivalent in its standard set.
> For your original ask—adding a string-handling instruction to MIPS—zmask (or orc.b by another name) is still a great fit, mirroring a proven RISC-V solution. If you were targeting RISC-V instead, you’d just use orc.b off the shelf. Pretty cool convergence! Anything else you want to explore with this?
Those RISC-V designers must have been on some strong stuff, eh? But what a wacky name. It seems this pure SIMD instruction works with both LE and BE, while the earlier suggestion to zero all bytes after the first null is inherently BE.
> Haha, yeah, the RISC-V folks were definitely cooking with some high-octane creativity when they named orc.b—it sounds like a fantasy spell or a grumpy orc’s battle cry, not a string-handling gem! But you’re spot on: its pure SIMD-like nature makes it endian-agnostic, which is a slick advantage over our earlier ideas. Let’s unpack that and see why orc.b (and thus zmask) shines across both little-endian (LE) and big-endian (BE), while the “zero after first null” approach tied us to BE.
> RISC-V’s Strong Stuff
> The designers must’ve been sipping some potent open-source espresso—orc.b sneaks in as a bit-manipulation trick but doubles as a string-handling powerhouse. It’s a testament to RISC-V’s “less is more” ethos: one instruction, broad utility. Our zmask journey landed on the same idea, proving great minds (and AIs!) think alike.
For MIPS, zmask as an orc.b clone is still a killer addition—endian-flexible and lean. Any other RISC-V scalar tricks you want to pit it against?
-----
So ... interesting.
zmask is a pretty novel instruction. As far as I know, orc.b in RISC-V is the first time anyone has done that instruction in an ISA that also has ctz. Lots of SIMD ISAs can do a "compared each element to 0 and make either a byte mask in a vector register or a bit mask in a mask register" but none of them can then do a ctz on the entire register or mask. The best you can do is compress the MSB of each vector element to an integer register, or transfer a mask register to integer, and then do ctz there.
Grok didn't get to zmask by itself, but it got there by prompting relatively general observations about problems with its initial suggestions.
There were a couple of bugs that to be honest a human programmer could easily overlook too, and have to debug: forgetting to invert the mask before doing ctz (in the absense of cto), and forgetting to adjust bit count to byte count. In translating from MIPS to RISC-V it hallucinated a "nor, t4, t2, zero" instruction in RISC-V, which I didn't correct -- "xori t4, t2, -1" is available so nbd.
There were also a couple of correct but not optimal things. Incrementing the byte count in every loop when it could be calculated after the loop by pointer subtraction. Putting the tail processing physically inside the loop, so having a taken conditional branch and an unconditional branch in each loop iteration. I got that to a non-taken conditional, plus an unconditional branch, with contiguous code for the loop body. I didn't ask it to rotate the loop to only have the conditional branch, and start by jumping into the middle. I think it probably could have done that.
Once these problems were pointed out, they didn't recur in later versions of strlen, strcpy, strcmp that used different custom instruction formulations.
The whole process was quite fast. Grok3 responded very quickly, with updated versions of all three functions at every prompt, which were at least close to correct. modulo the confusion about the exact input and output of ctz.
Reasoning about imaginary instructions, it's obviously not possible for the machine to actually execute the code. Symbolic reasoning is all there is.
I think it's useful. You have to supervise it like you would a junior programmer, and prod all the good ideas, but it iterates the mechanics in seconds that a junior would take a day to rewrite. And, within a given session (which you can go back to later) it doesn't make the same mistake again once corrected.