Skip to content

RISCV: compressed insn treats the uncompressed version as an alias - #2959

Draft
slate5 wants to merge 2 commits into
capstone-engine:nextfrom
slate5:fix/uncompressed-alias
Draft

RISCV: compressed insn treats the uncompressed version as an alias#2959
slate5 wants to merge 2 commits into
capstone-engine:nextfrom
slate5:fix/uncompressed-alias

Conversation

@slate5

@slate5 slate5 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Your checklist for this pull request

  • I've documented or updated the documentation of every API function and struct this PR changes.
  • I've added tests that prove my fix is effective or that my feature works (if possible)

Detailed description

This draft is connected to #2923.
It's a very simple change. All compressed instructions that have an uncompressed counterpart use that instruction as an alias. A better solution would be to include a new uncompressed_id instead of an alias, but that would require more modifications (cs_insn)...

Test plan

...

Closing issues

...

@github-actions github-actions Bot added the RISCV Arch label Jun 9, 2026
@slate5
slate5 force-pushed the fix/uncompressed-alias branch from 646c2ac to bc9042a Compare June 9, 2026 03:41

@Rot127 Rot127 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add some tests of course.
Otherwise I think we can start with that one.

@slate5

slate5 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Will do, but let's see what @moste00 says

@moste00

moste00 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Hello @Rot127 @slate5, thanks for notifying me of this.

So a couple of problems that I see:

1- We're still keeping the "(un)compressing is the same thing as aliasing" paradigm, and the flags of noalias and noaliascompressed with their old meanings. Didn't we talk about removing them because LLVM doesn't see compression as an aliasing ? will we remove them in a later PR or just give up and keep them for now?

2- The logic assigns a normal CS instruction ID (the ID of the uncompressed instruction) to the alias ID, but are the 2 ID spaces mutually exclusive? What if they intersect in some IDs ?

3- While I didn't run this yet as I'm not on my laptop now, the details logic will probably not solve my problem because when it prints the details for if(is_uncompressed) path, it prints for the MI struct, not McInstr. MI is the original compressed instruction, so this will print the original details of the compressed instruction. I want the details of the uncompressed instruction.

All in all: I think the main problem here is that is that we're just keeping most of the logic as-is and patching 1 problem ad-hoc instead of refactoring enough, but if both of you think we're not ready for the somewhat complicated approach in the other PR, and if (3) is addressed without breaking any existing tests, I'm okay with this PR.

Personally, I would like the other approach I described in the other PR, it just exhaustively lists all possibilities and lays out the exact thing to do for each. I might be biased though and if you think it's unclear or too verbose a description then so will capstone users. Maybe I just mis-explained it ?

@slate5

slate5 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Hi @moste00,

1- We're still keeping the "(un)compressing is the same thing as aliasing" paradigm...

Yes, that is what I also addressed. Maybe it would be better to have a separate cs_insn element for this specifically (eg, uncompressed_id). On the other hand, if we start complicating with noalias, nocompressed, keep this or that, we have to expect that the user is an expert in riscv. I will be the first to forget this "mess" with compressed insn, and its "aliases" in a week. From the perspective of an asm writer, it makes sense like this because in your source file you put addi t0, t0, -1 and the assembler translates that into c.addi (same reg is used and imm is in range of signed 2^6) or not if imm is bigger or there is no riscv C extension on system/assembler. What I'm pointing out with this is that addi is kinda an alias either way because until the assembler decides its encoding, it stands as a logical representation of some functionality and not a raw instruction.

I'm making this long... I lean towards fewer features (flags) that tune options without the explicit need of end users. It's clutter, and i'm not sure if there is demand for it. To me, this differentiation between +noaliascompressed and +noalias (as it is now) is irrelevant. Either i want aliases or not, i don't care if insn is compressed. As you said in the other PR, "Or you can do uncompression alone but stop at just the alias mapping, using +noaliascompressed" makes more sense to redefine the purpose of it (it would stop the logic of this PR, no uncompressed aliases, e.g., c.addi doesn't have an alias ID).

2- The logic assigns a normal CS instruction ID (the ID of the uncompressed instruction) to the alias ID, but are the 2 ID spaces mutually exclusive? What if they intersect in some IDs ?

They are in the same enum riscv_insn, separated by RISCV_INS_ENDING and RISCV_INS_ALIAS_BEGIN, if that is your question

3- While I didn't run this yet as I'm not on my laptop now...

But if you want real details, specify -r or what am i missing?

I don't think this PR solution is great, but i'm trying to stick to occam's razor :) And yes, I think if you explain to me a bit simpler in terms of what the end goal of your approach is, and what problem it solves (or what benefit it gives), it would help me a lot

@moste00

moste00 commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

3- While I didn't run this yet as I'm not on my laptop now...

But if you want real details, specify -r or what am i missing?

Hey, sorry for saying that I wil test this at the time then delaying it so much. I was busy.

So what I mean is: I want to be able to give the -r flag and that would mean the "real" details of compressed instruction (those of the uncompressed equivalent) would appear. This is very useful to me in (e.g.) Rizin lifting, where I can just lift all the non-compressed instructions and then every c. instruction is just a special case, but this only works if the c. instruction can be made to always output the details of their uncompressed equivalents.

In some sense, this is the opposite POV to what you're saying. You're thinking of addi as the fictitious alias and the c_addi as the real instruction, and you're right in the sense that when a programmer writes "addi" in the asm file but the assembler writes "c_addi" then indeed it IS "addi" that is the fictional alias that doesn't really exist in this situation.

However, my POV is that the "original" 4-byte instructions are the "real" ones, and the C style instructions are just aliases for shorthand operations. It's kinda how in Bash you define alias commands to achieve the same thing as a longer command but with shorter text, both truly fictitious aliases (like ret) and the C extension are like aliases in this way. The fictitious aliases are shorter in text but not in bytes, and the C extension instruction are shorter in bytes but not in text or description. In both cases, they achieve the same effect as an plain 4-byte instruction but being "shorter" in some way. That's why I see them both as the same kind of thing.

All in all, your PR doesn't break the test suite either way, even if you change MI to McInstr in the way that would achieve my use case (as suggested in review). So maybe we can call this whole thing done and simplify the logic another day.

Comment thread arch/RISCV/RISCVInstPrinter.c Outdated
MI->flat_insn->detail->riscv.op_count = 0;
// re-disassemble again with no printing in order to obtain the full details
// including the whole operands array
printInstruction(MI, MI->address, O);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

usse McInstr here instead of MI, so that if the instruction is uncompressed the details would be those of the uncompressed equivalent.

Comment thread arch/RISCV/RISCVInstPrinter.c Outdated
MI->flat_insn->detail->riscv.op_count = 0;
// re-disassemble again with no printing in order to obtain the full details
// including the whole operands array
printInstruction(MI, MI->address, O);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same as above.

@moste00

moste00 commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

I left some comments on the code, if you apply them and no tests are broken, then LGTM.

(I already applied them on my local branch and they didn't break any tests, but check again just in case.)

@slate5

slate5 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Now I am sorry for the delay, hehe
Thank you for clarifying things and the reasoning behind it.

So what I mean is: I want to be able to give the -r flag and that would mean the "real" details of compressed instruction (those of the uncompressed equivalent) would appear. This is very useful to me in (e.g.) Rizin lifting, where I can just lift all the non-compressed instructions and then every c. instruction is just a special case, but this only works if the c. instruction can be made to always output the details of their uncompressed equivalents.

Ok, so you want the opposite behaviour for uncompressed ones with -r, so that operands are consistent between compressed and full instructions. From the ISA perspective, that doesn't make a lot of sense, but I understand your POV, and it makes sense. Now i'm thinking about new flag, haha... nvm

Your explanations about aliases and Bash are okay with me. I cannot see it fully like that, but ofc c. can be seen as a shorthand alias for the real insn. Thinking about those writebacks reaffirms this perspective (c.addi sp, -16, sp is RW, which means first cpu reads from it, adds imm, then writes to it. So, it's still addi sp,sp,-16).

This is set then, but i would like to hear your opinion on @Rot127's last msg in #2923. I would like to replace noaliascompressed with keepcompressed, and the role would be to just ignore this uncompressed complexity. Is that ok?

Also, how much complexity would be added to Rizin if we don't use alias_id for this uncompressed stuff, but instead add a new one, uncompressed_id?

I'm just gonna swap those two (MI and McInstr) as u proposed, but to get this opposite functionality completely (even with +noalias), we have to do more changes.

@slate5
slate5 force-pushed the fix/uncompressed-alias branch from bc9042a to 66b162f Compare June 16, 2026 05:34
@moste00

moste00 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

@slate5 Thank you!

Yes, I read Rot's comment on the other PR and I like it, I was planning to implement it. If you want to implement it then of course be my welcome guest, although don't feel pressured to, it was originally my idea to do the refactoring so I don't want to just throw work on you!

That said, if you want to implement it, of course I have no objections. If you don't feel like it then it's totally OK and I can resume the unfinished other PR.

While you're at it, you can also seperate the details from the text, i.e. make it so the text is completely independent from details, such that I can request -r while still using +keepcompressed and everything will work fine: the compressed text is used but -r makes the details like those of the uncompressed equivalent. This can be implemented by just making the details-checking a seperate pass done after the text printing and indepedent from it, like the code in the other PR does.

So in short: yes, definitely feel free to implement Rot's modification, but don't feel pressured because it might be a hassle and I don't want to assign boring work to you. (in particular, tests might break and require passing extra flags for it to work.)

Also, how much complexity would be added to Rizin if we don't use alias_id for this uncompressed stuff, but instead add a new one, uncompressed_id?

No complexity either way, Rizin doesn't use any of those IDs to drive any interesting analysis as far as I'm aware, but adding uncompressed_id will be cleaner in general, this is because uncompression and aliases are orthogonal, for some instructions both can happen, so two fields encode the full detail of what happened to the instruction.

@slate5

slate5 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@moste00, you're welcome :)

I'm going through this implementation, but one last thing that we misunderstood each other. For user POV simplicity, I would treat c. text and details always as an alias by default (c.addi and sp, -16), so no need to use +keepcompressed to toggle text format. That flag i would just simply use if someone (me) wants to get c. instruction as it really is (text and details), just to revert this "opposite" logic.
How I have it now:

$ cstool -d riscv64 4111
 0  41 11        c.addi	sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with ALIAS operand set
	op_count: 2
		operands[0].type: REG = sp
		operands[0].access: READ | WRITE
		operands[1].type: IMM = 0xfffffffffffffff0
		operands[1].access: READ

	Groups: HasStdExtCOrZca 

$ cstool -dr riscv64+noalias 4111
 0  41 11        addi	sp, sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with REAL operand set
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0xfffffffffffffff0
		operands[2].access: READ

	Groups: HasStdExtCOrZca 

$ cstool -d riscv64+keepcompressed 4111
 0  41 11        c.addi	sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with REAL operand set
	op_count: 2
		operands[0].type: REG = sp
		operands[0].access: READ | WRITE
		operands[1].type: IMM = 0xfffffffffffffff0
		operands[1].access: READ

	Groups: HasStdExtCOrZca 

While you're at it, you can also seperate the details from the text, i.e. make it so the text is completely...

Yes, separating +noalias and -r is a good idea.

What do you think?

@Rot127

Rot127 commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

@slate5 Could you change the with ALIAS operand set to with COMPRESSED operand set when applicable. So it is easier to distinguish.

@slate5

slate5 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Oh yea, i like that idea @Rot127

@moste00

moste00 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

I'm going through this implementation, but one last thing that we misunderstood each other. For user POV simplicity, I would treat c. text and details always as an alias by default (c.addi and sp, -16), so no need to use +keepcompressed to toggle text format.

If I understand you correctly, did you mean to say that without the user giving any flags, c. instructions and details will default to transform into the alias equivalent (so they uncompress first and then go through printAliasInstr) ? If this is what you meant, then no misunerstanding, this is exactly how I understand it too. Everything will try to transform into an alias via printAliasInstr unless you explicitly stand in its way via flags.

It's also how I unerstand Rot's diagram from the other discussion.
image

That flag i would just simply use if someone (me) wants to get c. instruction as it really is (text and details), just to revert this "opposite" logic.

Yup, no surprises there. c. text is second-class and the user must request it explictly to get it. Otherwise they don't get it.

$ cstool -d riscv64 4111
 0  41 11        c.addi	sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with ALIAS operand set
	op_count: 2
		operands[0].type: REG = sp
		operands[0].access: READ | WRITE
		operands[1].type: IMM = 0xfffffffffffffff0
		operands[1].access: READ

	Groups: HasStdExtCOrZca 

I think this output is wrong, isn't it ? The cstool command didn't give +keepcompressed and yet the text is compressed.

$ cstool -dr riscv64+noalias 4111
0 41 11 addi sp, sp, -0x10
ID: 492 (c_addi)
Uncompressed: 40 (addi) with REAL operand set
op_count: 3
operands[0].type: REG = sp
operands[0].access: WRITE
operands[1].type: REG = sp
operands[1].access: READ
operands[2].type: IMM = 0xfffffffffffffff0
operands[2].access: READ

Groups: HasStdExtCOrZca

I think this is correct, the absence of +keepcompressed made the compressed instruction print the uncompressed equivalent, but +noalias prevents it from going any further. (assuming there is an alias for the printed instruction). That said, uncompressed should give you the Original id, it's really uncompressed_from, it gives you a hint as to what original instruction this instruction used to be.

$ cstool -d riscv64+keepcompressed 4111
0 41 11 c.addi sp, -0x10
ID: 492 (c_addi)
Uncompressed: 40 (addi) with REAL operand set
op_count: 2
operands[0].type: REG = sp
operands[0].access: READ | WRITE
operands[1].type: IMM = 0xfffffffffffffff0
operands[1].access: READ

Groups: HasStdExtCOrZca

Exactly. And the uncompressed flag here has two possiblities (1) Null, empty, whatever, the instruction was never uncompressed, so it's invalid (2) Optionally, you can make it mean "uncompresses to" in this case, so in fact it has two possible meanings, on compressed instructions it means "this instruction can uncompress to ...., but didn't", on uncompressed instruction it means "this instruction was uncompressed from ....", so it's always pointing in the other direction. But (2) can be confusing to users, so I prefer (1) , it always means "this uncompressed instruction originally come from this compressed ID", and it's undefined on compressed instructions.

I think that's it. Don't forget to seperate the details flags from the text flags please, it's very important.

@moste00

moste00 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

@slate5 Hey, any progress ? The lifting PR depends on this to have a green CI.

@slate5

slate5 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

@moste00, I'm doing this in my free time, and I'm also traveling these days (so no extra monitor, just laptop. It's like programming on a Tamagotchi)
I think I finished the implementation, but before converting tests and documenting this change, let's check one (hopefully) more time if everything is as expected. I'll just drop all the possible outputs from cstool, so "enjoy" the dump :)
I specifically picked these 7 insns because they should cover all the combinations (full insns with or without alias, c. with or/and without alias/uncompresed):

$ cstool -d riscv64+zcmp-t-e 1301813E,6F100000,01A8,9680,4111,8280,BEA1
00:  13 01 81 3e  addi	sp, sp, 0x3e8
	ID: 40 (addi)
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0x3e8
		operands[2].access: READ


04:  6f 10 00 00  j	0x1004
	ID: 694 (jal)
	Is alias: 1694 (j) with ALIAS operand set
	op_count: 1
		operands[0].type: IMM = 0x1004
		operands[0].access: READ

	Groups: branch_relative 

08:  01 a8        j	0x18
	ID: 510 (c_j)
	Is alias: 1694 (j) with ALIAS operand set
	Uncompressed: 694 (jal) 
	op_count: 1
		operands[0].type: IMM = 0x18
		operands[0].access: READ

	Groups: jump branch_relative HasStdExtCOrZca 

0a:  96 80        mv	ra, t0
	ID: 524 (c_mv)
	Is alias: 1679 (mv) with ALIAS operand set
	Uncompressed: 40 (addi) 
	op_count: 2
		operands[0].type: REG = ra
		operands[0].access: WRITE
		operands[1].type: REG = t0
		operands[1].access: READ

	Groups: HasStdExtCOrZca 

0c:  41 11        addi	sp, sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with UNCOMPRESSED operand set
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0xfffffffffffffff0
		operands[2].access: READ

	Groups: HasStdExtCOrZca 

0e:  82 80        ret	
	ID: 513 (c_jr)
	Is alias: 1698 (ret) with ALIAS operand set
	Uncompressed: 31 (jalr) 

	Groups: HasStdExtCOrZca jump 

10:  be a1        cm.jalt	0x6f
	ID: 175 (cm_jalt)
	op_count: 1
		operands[0].type: IMM = 0x6f
		operands[0].access: READ

	Groups: HasStdExtZcmt 





$ cstool -d riscv64+zcmp-t-e+noalias 1301813E,6F100000,01A8,9680,4111,8280,BEA1
00:  13 01 81 3e  addi	sp, sp, 0x3e8
	ID: 40 (addi)
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0x3e8
		operands[2].access: READ


04:  6f 10 00 00  jal	zero, 0x1004
	ID: 694 (jal)
	op_count: 1
		operands[0].type: IMM = 0x1004
		operands[0].access: READ

	Groups: branch_relative 

08:  01 a8        jal	zero, 0x18
	ID: 510 (c_j)
	Uncompressed: 694 (jal) with COMPRESSED operand set
	op_count: 1
		operands[0].type: IMM = 0x18
		operands[0].access: READ

	Groups: jump branch_relative HasStdExtCOrZca 

0a:  96 80        addi	ra, t0, 0
	ID: 524 (c_mv)
	Uncompressed: 40 (addi) with COMPRESSED operand set
	op_count: 2
		operands[0].type: REG = ra
		operands[0].access: WRITE
		operands[1].type: REG = t0
		operands[1].access: READ

	Groups: HasStdExtCOrZca 

0c:  41 11        addi	sp, sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with UNCOMPRESSED operand set
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0xfffffffffffffff0
		operands[2].access: READ

	Groups: HasStdExtCOrZca 

0e:  82 80        jalr	zero, 0(ra)
	ID: 513 (c_jr)
	Uncompressed: 31 (jalr) with COMPRESSED operand set

	Groups: HasStdExtCOrZca jump 

10:  be a1        cm.jalt	0x6f
	ID: 175 (cm_jalt)
	op_count: 1
		operands[0].type: IMM = 0x6f
		operands[0].access: READ

	Groups: HasStdExtZcmt 





$ cstool -dr riscv64+zcmp-t-e 1301813E,6F100000,01A8,9680,4111,8280,BEA1
00:  13 01 81 3e  addi	sp, sp, 0x3e8
	ID: 40 (addi)
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0x3e8
		operands[2].access: READ


04:  6f 10 00 00  j	0x1004
	ID: 694 (jal)
	Is alias: 1694 (j) with REAL operand set
	op_count: 2
		operands[0].type: REG = zero
		operands[0].access: WRITE
		operands[1].type: IMM = 0x1004
		operands[1].access: READ

	Groups: branch_relative 

08:  01 a8        j	0x18
	ID: 510 (c_j)
	Is alias: 1694 (j) with REAL operand set
	Uncompressed: 694 (jal) 
	op_count: 2
		operands[0].type: REG = zero
		operands[0].access: WRITE
		operands[1].type: IMM = 0x18
		operands[1].access: READ

	Groups: jump branch_relative HasStdExtCOrZca 

0a:  96 80        mv	ra, t0
	ID: 524 (c_mv)
	Is alias: 1679 (mv) with REAL operand set
	Uncompressed: 40 (addi) 
	op_count: 3
		operands[0].type: REG = ra
		operands[0].access: WRITE
		operands[1].type: REG = t0
		operands[1].access: READ
		operands[2].type: IMM = 0x0
		operands[2].access: READ

	Groups: HasStdExtCOrZca 

0c:  41 11        addi	sp, sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with UNCOMPRESSED operand set
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0xfffffffffffffff0
		operands[2].access: READ

	Groups: HasStdExtCOrZca 

0e:  82 80        ret	
	ID: 513 (c_jr)
	Is alias: 1698 (ret) with REAL operand set
	Uncompressed: 31 (jalr) 
	op_count: 3
		operands[0].type: REG = zero
		operands[0].access: WRITE
		operands[1].type: REG = ra
		operands[1].access: READ
		operands[2].type: IMM = 0x0
		operands[2].access: READ

	Groups: HasStdExtCOrZca jump 

10:  be a1        cm.jalt	0x6f
	ID: 175 (cm_jalt)
	op_count: 1
		operands[0].type: IMM = 0x6f
		operands[0].access: READ

	Groups: HasStdExtZcmt 




$ cstool -dr riscv64+zcmp-t-e+noalias 1301813E,6F100000,01A8,9680,4111,8280,BEA1
00:  13 01 81 3e  addi	sp, sp, 0x3e8
	ID: 40 (addi)
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0x3e8
		operands[2].access: READ


04:  6f 10 00 00  jal	zero, 0x1004
	ID: 694 (jal)
	op_count: 2
		operands[0].type: REG = zero
		operands[0].access: WRITE
		operands[1].type: IMM = 0x1004
		operands[1].access: READ

	Groups: branch_relative 

08:  01 a8        jal	zero, 0x18
	ID: 510 (c_j)
	Uncompressed: 694 (jal) with UNCOMPRESSED operand set
	op_count: 2
		operands[0].type: REG = zero
		operands[0].access: WRITE
		operands[1].type: IMM = 0x18
		operands[1].access: READ

	Groups: jump branch_relative HasStdExtCOrZca 

0a:  96 80        addi	ra, t0, 0
	ID: 524 (c_mv)
	Uncompressed: 40 (addi) with UNCOMPRESSED operand set
	op_count: 3
		operands[0].type: REG = ra
		operands[0].access: WRITE
		operands[1].type: REG = t0
		operands[1].access: READ
		operands[2].type: IMM = 0x0
		operands[2].access: READ

	Groups: HasStdExtCOrZca 

0c:  41 11        addi	sp, sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with UNCOMPRESSED operand set
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0xfffffffffffffff0
		operands[2].access: READ

	Groups: HasStdExtCOrZca 

0e:  82 80        jalr	zero, 0(ra)
	ID: 513 (c_jr)
	Uncompressed: 31 (jalr) with UNCOMPRESSED operand set
	op_count: 3
		operands[0].type: REG = zero
		operands[0].access: WRITE
		operands[1].type: REG = ra
		operands[1].access: READ
		operands[2].type: IMM = 0x0
		operands[2].access: READ

	Groups: HasStdExtCOrZca jump 

10:  be a1        cm.jalt	0x6f
	ID: 175 (cm_jalt)
	op_count: 1
		operands[0].type: IMM = 0x6f
		operands[0].access: READ

	Groups: HasStdExtZcmt 




$ cstool -dr riscv64+zcmp-t-e+noalias+keepcompressed 1301813E,6F100000,01A8,9680,4111,8280,BEA1
00:  13 01 81 3e  addi	sp, sp, 0x3e8
	ID: 40 (addi)
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0x3e8
		operands[2].access: READ


04:  6f 10 00 00  jal	zero, 0x1004
	ID: 694 (jal)
	op_count: 2
		operands[0].type: REG = zero
		operands[0].access: WRITE
		operands[1].type: IMM = 0x1004
		operands[1].access: READ

	Groups: branch_relative 

08:  01 a8        c.j	0x18
	ID: 510 (c_j)
	Uncompressed: 694 (jal) with COMPRESSED operand set
	op_count: 1
		operands[0].type: IMM = 0x18
		operands[0].access: READ

	Groups: jump branch_relative HasStdExtCOrZca 

0a:  96 80        c.mv	ra, t0
	ID: 524 (c_mv)
	Uncompressed: 40 (addi) with COMPRESSED operand set
	op_count: 2
		operands[0].type: REG = ra
		operands[0].access: WRITE
		operands[1].type: REG = t0
		operands[1].access: READ

	Groups: HasStdExtCOrZca 

0c:  41 11        c.addi	sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with COMPRESSED operand set
	op_count: 2
		operands[0].type: REG = sp
		operands[0].access: READ | WRITE
		operands[1].type: IMM = 0xfffffffffffffff0
		operands[1].access: READ

	Groups: HasStdExtCOrZca 

0e:  82 80        c.jr	ra
	ID: 513 (c_jr)
	Uncompressed: 31 (jalr) with COMPRESSED operand set
	op_count: 1
		operands[0].type: REG = ra
		operands[0].access: READ

	Groups: HasStdExtCOrZca ret 

10:  be a1        cm.jalt	0x6f
	ID: 175 (cm_jalt)
	op_count: 1
		operands[0].type: IMM = 0x6f
		operands[0].access: READ

	Groups: HasStdExtZcmt 

I made c.addi (or any instruction that is not an alias but has an uncompressed counterpart) to act exactly as a full instruction would (i.e., being unfazed by -r or +noalias)
Opinion?

@moste00

moste00 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

@slate5 Sounds good, thanks! please push the code. Also please make a note of which tests break and need fixing, it will appear in the diff but you could make things more obvious by just noticing their names and files, if possible and they're not too many. I always review all the broken tests to make sure we didn't break too much.

I made c.addi (or any instruction that is not an alias but has an uncompressed counterpart) to act exactly as a full instruction would (i.e., being unfazed by -r or +noalias)

By "unfazed" do you mean that the instruction doesn't change whether those flags are present or not ? If so then

1- In the case +noalias that makes sense, since there is no alias to begin with, so whether or not +noalias is present c.addi will always print as itself.

2- But with -r it's different, -r should ALWAYS make the details those of the full, 4-byte, non-alias equivalent. In the case of aliases the operands will be those of the original pre-alias instruction if the pre-alias instruction is a 4-byte instruction, and in the case of compressed instructions that displays the operands of the uncompressed equivalent.

The general rule is that -r basically allows you to pretend everything is a vanilla 4-byte instruction, all operands are explicit, all read/write access is seperate, basically just forget that aliases and compressed instructions exist at all. This is why it's so useful in my lifting PRs. This is different from the text flags +noalias and +keepcompressed which allow you to control everything.

@slate5

slate5 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

@moste00, to avoid these kinds of theoretical discrepancies between which case should do what, I gave the full cstool output of all possible cases. The output above is unambiguous.
Right now i have to read what u are saying to me, and it seems like it already complies with the output. For simplicity, can u just take my dump and say "this is not what is desired on this exact line"?

@moste00

moste00 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

@slate5 In the command where you do cstool -dr riscv64+zcmp-t-e+noalias ...

The c_addi command is printed as

0c:  41 11        addi	sp, sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with UNCOMPRESSED operand set
	op_count: 3
		operands[0].type: REG = sp
		operands[0].access: WRITE
		operands[1].type: REG = sp
		operands[1].access: READ
		operands[2].type: IMM = 0xfffffffffffffff0
		operands[2].access: READ

	Groups: HasStdExtCOrZca 

which is correct, the full operand set as expected.

But in the command that does
cstool -dr riscv64+zcmp-t-e+noalias+keepcompressed

the c_addi

0c:  41 11        c.addi	sp, -0x10
	ID: 492 (c_addi)
	Uncompressed: 40 (addi) with COMPRESSED operand set
	op_count: 2
		operands[0].type: REG = sp
		operands[0].access: READ | WRITE
		operands[1].type: IMM = 0xfffffffffffffff0
		operands[1].access: READ

	Groups: HasStdExtCOrZca 

which is the compressed way of spelling the details (READ | WRITE instead of two operands).

Both have the -r flag, both should have the full details of the uncompressed instruction, the details should not be affected by the text settings.

@slate5

slate5 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Can u just tell me exactly what the output of cstool -dr riscv64+zcmp-t-e+noalias+keepcompressed should be for c.addi? Also, all other combinations would reduce the headache (without -r and/or without +noalias).
Thank you

P.S.

which is correct, the full operand set as expected.

The misunderstanding between us comes from none of these being "correct", but a matter of preference...

@slate5

slate5 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@moste00 I reread your answer, and it is clear that we did perceive keepcompressed differently.
keepcompressed, according to you, should only affect text, and I preferred that it affect both (text and details). So, I assume this would be c.mv how you see it for both text and details being real:

0:  96 80        c.mv  ra, t0
        ID: 524 (c_mv)
        Uncompressed: 40 (addi) with UNCOMPRESSED operand set
        op_count: 3
                operands[0].type: REG = ra
                operands[0].access: WRITE
                operands[1].type: REG = t0
                operands[1].access: READ
                operands[2].type: IMM = 0x0
                operands[2].access: READ

        Groups: HasStdExtCOrZca

And this is how I see it:

0:  96 80        c.mv  ra, t0
        ID: 524 (c_mv)
        Uncompressed: 40 (addi) with COMPRESSED operand set
        op_count: 2
                operands[0].type: REG = ra
                operands[0].access: WRITE
                operands[1].type: REG = t0
                operands[1].access: READ

        Groups: HasStdExtCOrZca

If that is the case, we could just keep the old flag CS_OPT_SYNTAX_NO_ALIAS_TEXT_COMPRESSED (or something like that) that explicitly states that it affects text only. In my head, keepcompressed was supposed to honor ISA logic of what is an alias and what is not (meaning, it cancels out this opposite logic that we created).

@moste00

moste00 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

@slate5 Yes, sorry if I didn't do a good job of making this clearer much earlier, I might have buried it during the very long discussion.

But yes, that's essentially all what this PR is about: text and details is and should be different. One compelling reason for this is Rizin's way of consuming Capstone: instruction text is a user-facing thing printed as a result of disassembly commands, there might even be ways for Rizin's user to tweak it (Not very sure about doing that right now, but it's plausible and could be added in the future). Details, however, are internal structs that the lifting and the analysis code depends on.

If we couple text to details, this means a slight tweak to the text (e.g. changing how the c. instructions are displayed) could affect the internal Capstone structs that other logic depends on, which is messy and requires constant defensive programming. It's much easier on Rizin's code to just assume that the details are completely indepedent from how the instruction is displayed.

CS_OPT_SYNTAX_NO_ALIAS_TEXT_COMPRESSED

I'm opposed to the naming of this flag just because it conflates being compressed and being an alias, I would prefer if you found a better naming, possibly CS_OPT_SYNTAX_KEEP_COMPRESSED_TEXT ? in short form it's +keepcompressedtext, but that's very awkward to spell without underscores or camel case. +keepcompressed is already pushing it on length.


To avoid more misunderstanding, here's the entire algorithm I'm thinking of:

To output the text of instruction I:
    let printed = I;
    
    if I is a compressed instruction and `+keepcompressed` is not given:
         printed = uncompressInst(I);
    
    if `+noalias` is given:
         printInstruction(printed);
    else:
        if (!printAliasInstr(printed)):
             printInstruction(printed);

I think this does all what I want it to do:

  • If +keepcompressed is given, the first conditional will not fire, and the instruction will be printed as compressed no matter +noalias: printInstruction will print it normally in the first path, and printAliasInstr will return false since almost no compressed instruction is ever an alias (with one weird exception we don't care about), so printInstruction will still be called in the second path as well.

  • If +keepcompressed is not given, the first conditional will fire for a compressed instruction, and the instruction will transform to an uncompressed equivalent. From then on it will be treated like a normal non-compressed instruction and might be printed as an alias or not.

  • Regardless of what happens in the first conditional, the if-else structure after that is correct with respect to aliases: +noalias given means never calling printAliasInstr, otherwise we call it to see if it succeeds and then falling back to printInstruction if it doesn't find an alias.

  • This is all about the text, after we're done, we fill the details as follows:

  • We disable the printing stream to avoid touching text, then:

if the real details flag `-r` is given:
     let printed_details = I;
     if I is compressed:
            printed_details = uncompressInst(I);
     printInstruction(printed_details);
else if the new "real details except for compressed instructions" flag is given:
     printInstruction(I); // the original instruction, no matter if it's compressed or not
  • This means creating a new details flag, not sure what to name it, which will request the "real details" but only for aliases, the compressed details of a compressed instructions stays as is

  • If both details flags are given, the more aggressive original -r takes priority, it's a user error to give both anyway

  • If none of the details flags are given, then the details are whatever was filled during text printing

Notice that I'm writing if I is compressed in the pseudo code to make it clearer, but in the actual code this is done using uncompressInst immediately, which both uncompresses and returns success or failure. Also, you probably don't want to call uncompressInst twice, but if it makes the code easier, then please do.

If you think there is something unintuitive here, what's an example case I missed ?

Again, the only thing I care about here is the seperation between text and details, the exact logic governing the text printing is the one that @Rot127 described in his comment on the other issue, but I have no problem if you don't think it's intuitive and want to change it, the only thing blocking me in Rizin is that the details MUST be seperate from the text printing, right now they aren't.

@Rot127

Rot127 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@moste00

I think this does all what I want it to do:

Please keep in mind that for Capstone development we do not focus on what is best for Rizin, but what makes most sense for all Capstone developers. Rizin is just one use case, although a pretty comprehensive one.

To avoid more misunderstanding, here's the entire algorithm I'm thinking of:

Would you mind drawing it as a flow chart, showing which flag does what?
It is easier to follow and to discuss.

@slate5

slate5 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

is that the details MUST be seperate from the text printing

Text and details are already separated as u can see in my previous cstool output (the complete one with all combinations of real/alias text/details).

The only misunderstanding is +keepcompressed, in your comment, it seems that you are contradicting yourself:

I'm opposed to the naming of this flag just because it conflates being compressed and being an alias, I would prefer if you found a better naming, possibly CS_OPT_SYNTAX_KEEP_COMPRESSED_TEXT ? in short form it's +keepcompressedtext, but that's very awkward to spell without underscores or camel case. +keepcompressed is already pushing it on length.

When I mentioned CS_OPT_SYNTAX_KEEP_COMPRESSED_TEXT (which we used before and was replaced by keepcompressed), it wasn't meant to start a discussion about naming, but about "does +keepcompressed affect only text or both". Nevertheless, I would interpret this paragraph as if you want it to affect only the text.

If +keepcompressed is given, the first conditional will not fire, and the instruction will be printed as compressed no matter +noalias

And now, this I would interpret as if you want +keepcompressed to affect both text and details.

Do you know what you want with the +keepcompressed flag, or should I implement it as I said before (make c. insns act according to ISA when keepcompressed is present)?

@slate5

slate5 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

To make it very simple, c.mv is an example and in ISA that is a real instruction, while mv is a pseudo-instruction.
Here is how ISA would represent c.mv if you ask for real text/details:

0a:  96 80        c.mv	ra, t0
	ID: 524 (c_mv)
	Uncompressed: 40 (addi) with COMPRESSED operand set
	op_count: 2
		operands[0].type: REG = ra
		operands[0].access: WRITE
		operands[1].type: REG = t0
		operands[1].access: READ

	Groups: HasStdExtCOrZca 

This is what we do for real/real:

0a:  96 80        addi	ra, t0, 0
	ID: 524 (c_mv)
	Uncompressed: 40 (addi) with UNCOMPRESSED operand set
	op_count: 3
		operands[0].type: REG = ra
		operands[0].access: WRITE
		operands[1].type: REG = t0
		operands[1].access: READ
		operands[2].type: IMM = 0x0
		operands[2].access: READ

	Groups: HasStdExtCOrZca 

Now the question is what happens when we provide +keepcompressed:

Option 1

0:  96 80        c.mv  ra, t0
        ID: 524 (c_mv)
        Uncompressed: 40 (addi) with UNCOMPRESSED operand set
        op_count: 3
                operands[0].type: REG = ra
                operands[0].access: WRITE
                operands[1].type: REG = t0
                operands[1].access: READ
                operands[2].type: IMM = 0x0
                operands[2].access: READ

        Groups: HasStdExtCOrZca

Option 2 (same as ISA)

0:  96 80        c.mv  ra, t0
        ID: 524 (c_mv)
        Uncompressed: 40 (addi) with COMPRESSED operand set
        op_count: 2
                operands[0].type: REG = ra
                operands[0].access: WRITE
                operands[1].type: REG = t0
                operands[1].access: READ

        Groups: HasStdExtCOrZca

@moste00

moste00 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@slate5

I would interpret this paragraph as if you want it to affect only the text.

Yup, that's my intention. Naming is a side issue.

If +keepcompressed is given, the first conditional will not fire, and the instruction will be printed as compressed no matter +noalias

And now, this I would interpret as if you want +keepcompressed to affect both text and details.

Why :'D ? The entire algorithm is only for text, there is a separate pass that will print details after it's done. It's really two runs of printInstruction, the second one disables the print stream so that it only fills a details array and not clobber the actual text printed by the first run. (Yes, I think that's an ugly hack too, we're constrained by the fact that printInstruction runs a state-machine-like thingie that will always both prints text and calls details callbacks. When we want details, we disable the print stream so that all prints are no-ops.)

So tldr; No, I only meant text, this entire paragraph you're quoting is only ever talking about text, never details. When I say "printed as", it's text, not the details or the operands array.

Here is how ISA would represent c.mv if you ask for real text/details:

By "ISA" do you mean some GNU util or llvm-mc, or some table in the ISA description? as far as I know the ISA doesn't define what's the "details" of an instruction as separate from its text representation, every tool has its own notion of that.

Now the question is what happens when we provide +keepcompressed
Option 1
...
Option 2
...

Your point is taken, my previous algorithm would choose option 1, since it always chooses the details of the full non-alias non-compressed 4-byte instruction as the "canonical" details when r is given.

But I agree that c_mv makes this ugly, when I made the decision above, I was more thinking of c.add and c.and, where the difference in details representation is 2 operands vs 3 operands for the non-compressed version (and this breaks my lifting code and requires special cases). So the 3 regular operands sound like a clear win. But for c_mv, it's indeed uglier and less canonical to have the addi representation of its details like that.

The only thing I would say is: let's bite the bullet. Option 1.

c_mv could be better, but it's a single instruction while all other instructions I can think of right now looks better and more regular under option 1, which is always choosing the full 4-byte details of the equivalent non-compressed instruction. Maybe it's worth sacrificing the clarity of c_mv for this.

It's okay to do a non-perfect thing now and revisit later when someone gets annoyed.

@slate5

slate5 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@moste00

Why :'D ? The entire algorithm is only for text, there is a separate pass that will print details after it's done. It's really two runs of printInstruction, the second one disables the print stream so that it only fills a details array and not clobber the actual text printed by the first run.

Ohhhh ok, I missed that. I wasn't thorough enough in reading the pseudo-implementation because I just wanted a simple answer (it should affect only text or not) XD

By "ISA" do you mean some GNU util or llvm-mc, or some table in the ISA description? as far as I know the ISA doesn't define what's the "details" of an instruction as separate from its text representation, every tool has its own notion of that.

If we are talking about the same ISA document (https://github.com/riscv/riscv-isa-manual), then there is a definition of details, aka operands. But, as much as I can see, the confusion between us comes from POV. I guess you take text (mnemonics + operand string) as part of the ISA, but not the details because that is C struct metadata. I prefer them to be linked together to minimize confusion due to capstone-specific logic/convention.

But I agree that c_mv makes this ugly, when I made the decision above, I was more thinking of c.add and c.and

Personally, I find it very confusing to have, even after using +keepcompressed, a mnemonic c.addi sp, -16 and then 3 operands (sp, sp, -16). The same applies to c.add ra,t0 and then 3 ops (ra, ra, t0). It becomes even more unintuitive for insns with an implicit operand: c.j 0x20 with ops (reg zero, imm 0x20), c.jr, c.jalr, etc

I have both implementations ready, it's just a matter of preference now. Give me a final decision, and then let's wrap this up soon :)

@Rot127

Rot127 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

@moste00 @slate5

It's okay to do a non-perfect thing now and revisit later when someone gets annoyed.

Sorry, that is not possible. It is unlikely someone will go down that path in this detail again. Especially before the Beta release.
And Capstone is not Rizin. In Rizin we live like API stability does not exist.
But this is unacceptable for Capstone.

After the Beta is released, I don't want big API changes like these for RISCV anymore.
This means the implementation now will be fix for all of v6.

Could you folks make a table what outcome the different flags have (like the one already shown in https://github.com/capstone-engine/capstone/blob/next/docs/cs_v6_release_guide.md under the RISV section)?
The table would need to be part of the docs anyways (frankly I think even of cstool -h). So it is no waste.

And one more alternative:

@slate5 Do you think it is possible to let the user choose freely what asm text they want and what details? So people can do things like:

--asm_text:real --details:compressed
--asm_text:alias --details:uncompressed
--asm_text:compressed --details:alias
# etc...

I had something like that planned before as well. But never found time to implement it yet.

@slate5

slate5 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@Rot127

Do you think it is possible to let the user choose freely what asm text they want and what details?

From a programming perspective, nothing stops us from implementing this. I would gladly switch to that approach, it's more explicit from the users' POV.

If @moste00 likes it too, then we should decide upon flag names (both API's and cstool's). And please just confirm to me that the default (without explicit flags) should still show uncompressed text and details for compressed insns.

As soon as I have confirmation, I'll change the current implementation, and that table in docs will come last when we fully agree how this all should look like...

@moste00

moste00 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

@Rot127 fair point, maybe that's a thorny enough issue that we need to completely decide on behaviour and freeze it here.

@slate5 Here's the text table I'm imagining, the default mode when nothing is given is asm_text_alias, and giving 2 different modes is a hard error.

Flag Short form 4-byte instruction is printed as 2-byte instruction is printed as Conditions for printing an alias Examples
asm_text_real asm:tr Itself (Never a pseudo-instruction alias.) Uncompressed into its 4-byte equivalent, then printed as that 4-byte instruction (Never a pseudo-instruction alias.) FALSE, aliases are never printed in this mode (1) addi t0, t1, 4addi t0, t1, 4; (2) c.mv a0, a1add a0, zero, a1
asm_text_compressed asm:tc Itself, never compressed into a 2-byte form even if one exists Itself, never a pseudo-instruction alias FALSE, aliases are never printed in this mode c.mv a0, a1c.mv a0, a1; addi sp, sp, 16addi sp, sp, 16 (NOT as c.addi sp, sp, 16)
asm_text_alias asm:al Its pseudo-instruction alias if one applies to the current operand values, otherwise itself (1) Its own pseudo-instruction alias if one applies; (2) Otherwise uncompressed to its 4-byte form and printed as that form's alias if one applies; (3) Otherwise printed as the uncompressed 4-byte instruction itself (or as itself, if it has no valid uncompressed form) (1) For 4-byte instructions, the instruction's opcode and operand values (registers/immediates) must exactly match the pattern defined for one of its pseudo-instructions — e.g. rd=x0 for jalj; rd=x0, rs1=x1, imm=0 for jalrret; rd=rs1=x0, imm=0 for addinop; rs1=x0 for addimv. (2) For 2-byte instructions, first the psuedo-instruction aliases for it are tried, then if no alias found then the instruction is uncompressed and the 4-byte equivalent form is searched for psuedo-instruction aliases. If no aliases are found, the compressed instruction is left as a 4-byte expanded form. If the compressed instruction has no 4-byte form, it's printed as its alias if it exists or as itself. jalr x0, 0(x1)ret (4-byte, has direct alias); addi x0, x0, 0nop (4-byte, has direct alias); c.nopnop (2-byte, has its own direct alias); c.mv a0, a1mv a0, a1 (2-byte, uncompresses to add a0, x0, a1, which has alias mv); c.add a0, a1add a0, a0, a1 (2-byte, uncompresses to add a0, a0, a1, which has no alias, so printed as the uncompressed form); 0x0000 (reserved all-zero 2-byte pattern) → printed as itself (2-byte, has no alias and no valid 4-byte uncompressed form to fall back to)

It's a bit verbose because I was thinking about Capstone's user, but here's the tldr in non-table
(1) real mode is real, everything tries to be real, a real 4-byte instruction always stays as is, and a 2-byte instruction tries to uncompress.

(2) compressed mode doesn't make real instruction compresss, real instruction stay is even if there is a compressed equivalent, but compressed instructions print as themselves in that mode, again never as aliases

(3) In alias mode, everything tries to be an alias, 4-byte instruction are printed as aliases if possible (else as themselves), and 2-byte instruction are first attempted to be printed as aliases, if no aliases exist (the vast majority of them) they're then uncompressed to 4-byte form and printed as aliases, if still no aliases exist, then they're left as 4-byte uncompressed forms (or themselves if no such uncompression exist, which Claude tells me is extremly rare).

A similar table can be done for details but let's approve this one first or discuss it. Again, this table doesn't imply ANYTHING about details, details are different configuration knob entirely. They have a table similar to this in my mind.

What do you all think ?

@moste00

moste00 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

@slate5 @Rot127 Here's some diagrams courtesy of Claude to dilute the wall of text

1- Real mode, everything tries to be real and no alises are ever printed

image

2- Compressed mode, everything stays as itself, no aliases are ever printed

image

3- Alias mode, the default, everything tries to be an alias, and the compressed instruction be both its own aliases and the its expanded form aliases
image

@Rot127

Rot127 commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

@slate5

I would gladly switch to that approach, it's more explicit from the users' POV.

As I understand @moste00 summary above (@moste00 or did I miss something), an implementation which lets the user choose freely which asm text and details are emitted is covering that and any other case.
So feel free to go ahead with it.

Most importantly though: Thanks a lot for spending all your free time on it. I really appreciate that! RISCV is a very important architecture, and will be in the future as it looks like.

then we should decide upon flag names (both API's and cstool's).

Pick some you find intuitive and which are not too verbose.
Design decisions should be KISS.

And please just confirm to me that the default (without explicit flags) should still show uncompressed text and details for compressed insns.

The default behavior should be the one of llvm-mc. I think this is what is currently printed (@moste00 or did you change anything about that in your refactor?).

@slate5

slate5 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@moste00, I assume these flags like asm_real_text should be replicated (more-or-less) for details as well (ie, asm_real_detail)? Btw, thanks for trying to simplify your idea, it helps

Most importantly though: Thanks a lot for spending all your free time on it. I really appreciate that! RISCV is a very important architecture, and will be in the future as it looks like.

You are welcome, @Rot127. It's in all our interest to make this good, Capstone is an important project

The default behavior should be the one of llvm-mc.

llvm-mc does the opposite of what we aim to do, alias and real:

echo '0xC5 0x12' | llvm-mc -disassemble -triple=riscv64 -mattr=+c
	.text
	addi	t0, t0, -15
echo '0xC5 0x12' | llvm-mc -disassemble -triple=riscv64 -mattr=+c -M no-aliases
	.text
	c.addi	t0, -15

Last thing, would it make more sense to make asm_real_* show the real instruction, including c. (showing c.addi), asm_uncompressed_* show what we do with real now (everything is a full instruction if it can be), and asm_alias_* stay the same?

@Rot127

Rot127 commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

llvm-mc does the opposite of what we aim to do, alias and real:

I wasn't aware it allows to print the non-alias instruction 😮
Generally CS behavior should be as close as possible to llvm-mc. Because giving the same output as our ground truth (LLVM) can eases debugging and enhances portability and such.
But I think in this case it is reasonable to diverge. Since people can force CS to output llvm-mc matching text by selecting the correct set of flags.

@moste00

moste00 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

I assume these flags like asm_real_text should be replicated (more-or-less) for details as well (ie, asm_real_detail)?

Pretty much yeah, although I struggle to imagine why would someone want asm_alias_details, but for consistency let's add the 3 flags for details with similar semantics.

llvm-mc does the opposite of what we aim to do, alias and real:

Hmmm, what you posted seems pretty-aligned with what we do in the default case, which is asm_alias_text:

  1. The instruction is compressed, try to find an alias for it, none will be found (because almost no compressed instruction has a direct alias)
  2. Uncompress it to a 4-byte equivalent and try to find an alias for that, none is found
  3. Print the uncompressed 4-byte form as-is

This looks pretty in-line with the 3rd diagram I posted above, which should be the one governing the behaviour in case user didn't give any flags.

Just for curiosity, I told GPT 5.5 to actually look at the source of llvm-mc and tell me the decision logic, it does it in an extremly similar way:

image

This is almost the same sequence of decisions we do in asm_alias_text, except llvm-mc doesn't look for an alias for the 2-byte instruction first (and for a good reason, only C_ADD_HINT has an alias like this), but other than that, it's the same "uncompress then look for an alias and print it if found, otherwise print the expanded form".

Last thing, would it make more sense to make asm_real_* show the real instruction, including c. (showing c.addi), asm_uncompressed_* show what we do with real now (everything is a full instruction if it can be), and asm_alias_* stay the same?

That's valid, my bias is to always regard the compressed instruction as a form of aliases, but you're right, they're actually real instructions, So you can go ahead with this, no problem.

@moste00

moste00 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor
image

Traced the snippet in a real editor just to verify GPT's explanations.

@slate5

slate5 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Pretty much yeah, although I struggle to imagine why would someone want asm_alias_details, but for consistency let's add the 3 flags for details with similar semantics.

I don't see why this would be used either, but if we are already implementing max flexibility, let's be flexible... :)

Hmmm, what you posted seems pretty-aligned with what we do in the default case, which is asm_alias_text

Yes, the default case (yes-aliases) is the same. But the difference is when you request no-aliases (aka, NoAliases, or real, as we call it), llvm shows c.addi (literally), and we show uncompressed. Just look at the code, it literally does the opposite of Capstone (this PR) when users request "real", aka no-alias text.

That's valid, my bias is to always regard the compressed instruction as a form of aliases, but you're right, they're actually real instructions, So you can go ahead with this, no problem.

I'm glad we are exactly aligned now. I'll try to reimplement this in the following days.

@moste00

moste00 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

@slate5 Hey, any updates ?

If you have any initial implementation you can push and I can refine it further by the way.

@moste00

moste00 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

@slate5 Hey!, The lifting PR is blocked on this PR, so we genuinely need it done.

I'm planning to implement it all tomrrow, if you have something you want to push please let me know.

@slate5

slate5 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Hi @moste00, I didn't have time to do this. I was planning to do it this weekend, but if you cannot wait, feel free to finish it today

@moste00

moste00 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Hi @moste00, I didn't have time to do this. I was planning to do it this weekend, but if you cannot wait, feel free to finish it today

No worries, will do it today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

RISCV Arch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants