Repository: Decompollaborate/rabbitizer (language: python)

Issue:
Some doc on py Instruction



Discussion / hints:

Fix patch:
diff --git a/.gitattributes b/.gitattributes
index f5776cd3..3347d52e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -24,3 +24,4 @@ rabbitizer/RegGprN32.pyi linguist-generated=true
 rabbitizer/RegCop1O32.pyi linguist-generated=true
 rabbitizer/RegCop1N32.pyi linguist-generated=true
 rabbitizer/RegCop1N64.pyi linguist-generated=true
+rabbitizer/TrinaryValue.pyi linguist-generated=true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45af064b..06b245c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+## [1.9.0] - 2024-02-18
+
+### Added
+
+- Implement `neg` pseudo.
+
+### Changed
+
+- `Instruction.getProcessedImmediate` now raises an exception if the
+  instruction does not contain an immediate field.
+- `Instruction.getInstrIndexAsVram` now raises an exception if the
+  instruction does not contain an jump label field.
+- `Instruction.getBranchOffset` now raises an exception if the
+  instruction does not contain an branch label field.
+- Extend Python's `Instruction` documentation.
+  - Thanks to @Dragorn421 (PR #53)
+
+### Deprecated
+
+- `Instruction.isImplemented`: Use `Instruction.isValid` instead.
+
 ## [1.8.3] - 2024-01-28
 
 ### Fixed
@@ -455,6 +476,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Fix the wrong returned value on `Utils.From2Complement` when `bits` equals 32.
 
+### Deprecated
+
+- `RegistersTracker.getLuiOffsetForLo`. Use
+  `RegistersTracker.preprocessLoAndGetInfo` instead.
+
 ## [1.1.0] - 2022-08-27
 
 1.1.0: RSP and R5900 support
@@ -516,6 +542,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - First version
 
 [unreleased]: https://github.com/Decompollaborate/rabbitizer/compare/master...develop
+[1.9.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.8.3...1.9.0
+[1.8.3]: https://github.com/Decompollaborate/rabbitizer/compare/1.8.2...1.8.3
 [1.8.2]: https://github.com/Decompollaborate/rabbitizer/compare/1.8.1...1.8.2
 [1.8.1]: https://github.com/Decompollaborate/rabbitizer/compare/1.8.0...1.8.1
 [1.8.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.10...1.8.0
diff --git a/Cargo.toml b/Cargo.toml
index 93c5be24..b2e152ef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@
 [package]
 name = "rabbitizer"
 # Version should be synced with include/common/RabbitizerVersion.h
-version = "1.8.3"
+version = "1.9.0"
 edition = "2021"
 authors = ["Anghelo Carvajal <angheloalf95@gmail.com>"]
 description = "MIPS instruction decoder"
diff --git a/README.md b/README.md
index 7a8f7d65..344f876c 100644
--- a/README.md
+++ b/README.md
@@ -82,7 +82,7 @@ cargo add rabbitizer
 Or you can add it manually to your `Cargo.toml`:
 
 ```toml
-rabbitizer = "1.8.3"
+rabbitizer = "1.9.0"
 ```
 
 See this crate at <https://crates.io/crates/rabbitizer>.
diff --git a/cplusplus/include/generated/UniqueId_enum_class.hpp b/cplusplus/include/generated/UniqueId_enum_class.hpp
index 3af44bad..fad5c9ac 100644
--- a/cplusplus/include/generated/UniqueId_enum_class.hpp
+++ b/cplusplus/include/generated/UniqueId_enum_class.hpp
@@ -123,6 +123,7 @@ enum class UniqueId {
     cpu_nop,
     cpu_move,
     cpu_not,
+    cpu_neg,
     cpu_negu,
     cpu_bltz,
     cpu_bgez,
@@ -385,6 +386,7 @@ enum class UniqueId {
     rsp_nop,
     rsp_move,
     rsp_not,
+    rsp_neg,
     rsp_negu,
     rsp_bltz,
     rsp_bgez,
diff --git a/cplusplus/include/instructions/InstructionBase.hpp b/cplusplus/include/instructions/InstructionBase.hpp
index a88c7cd8..520960fb 100644
--- a/cplusplus/include/instructions/InstructionBase.hpp
+++ b/cplusplus/include/instructions/InstructionBase.hpp
@@ -193,6 +193,7 @@ namespace rabbitizer {
 
         /* Instruction examination */
 
+        //! @deprecated
         bool isImplemented() const;
         bool isLikelyHandwritten() const;
         bool isNop() const;
diff --git a/include/common/RabbitizerConfig.h b/include/common/RabbitizerConfig.h
index 97add912..1be6ae7f 100644
--- a/include/common/RabbitizerConfig.h
+++ b/include/common/RabbitizerConfig.h
@@ -32,6 +32,7 @@ typedef struct RabbitizerConfig_PseudoInstr {
     bool pseudoB;
     bool pseudoMove;
     bool pseudoNot;
+    bool pseudoNeg;
     bool pseudoNegu;
     bool pseudoBal;
 } RabbitizerConfig_PseudoInstr;
diff --git a/include/common/RabbitizerVersion.h b/include/common/RabbitizerVersion.h
index 6cad1a0f..06345ade 100644
--- a/include/common/RabbitizerVersion.h
+++ b/include/common/RabbitizerVersion.h
@@ -13,8 +13,8 @@ extern "C" {
 
 // Header version
 #define RAB_VERSION_MAJOR 1
-#define RAB_VERSION_MINOR 8
-#define RAB_VERSION_PATCH 3
+#define RAB_VERSION_MINOR 9
+#define RAB_VERSION_PATCH 0
 
 #define RAB_VERSION_STR RAB_STRINGIFY(RAB_VERSION_MAJOR) "." RAB_STRINGIFY(RAB_VERSION_MINOR) "." RAB_STRINGIFY(RAB_VERSION_PATCH)
 
diff --git a/include/generated/InstrDescriptor_Descriptors_array.h b/include/generated/InstrDescriptor_Descriptors_array.h
index c3293b8a..1ea79b10 100644
--- a/include/generated/InstrDescriptor_Descriptors_array.h
+++ b/include/generated/InstrDescriptor_Descriptors_array.h
@@ -123,6 +123,7 @@ const RabbitizerInstrDescriptor RabbitizerInstrDescriptor_Descriptors[] = {
     [RABBITIZER_INSTR_ID_cpu_nop] = { .operands={0}, .instrType=RABBITIZER_INSTR_TYPE_R, .isPseudo=true },
     [RABBITIZER_INSTR_ID_cpu_move] = { .operands={RAB_OPERAND_cpu_rd, RAB_OPERAND_cpu_rs}, .instrType=RABBITIZER_INSTR_TYPE_R, .modifiesRd=true, .readsRs=true, .maybeIsMove=true, .isPseudo=true },
     [RABBITIZER_INSTR_ID_cpu_not] = { .operands={RAB_OPERAND_cpu_rd, RAB_OPERAND_cpu_rs}, .instrType=RABBITIZER_INSTR_TYPE_R, .modifiesRd=true, .readsRs=true, .isPseudo=true },
+    [RABBITIZER_INSTR_ID_cpu_neg] = { .operands={RAB_OPERAND_cpu_rd, RAB_OPERAND_cpu_rt}, .instrType=RABBITIZER_INSTR_TYPE_R, .notEmittedByCompilers=true, .modifiesRd=true, .readsRt=true, .isPseudo=true },
     [RABBITIZER_INSTR_ID_cpu_negu] = { .operands={RAB_OPERAND_cpu_rd, RAB_OPERAND_cpu_rt}, .instrType=RABBITIZER_INSTR_TYPE_R, .modifiesRd=true, .readsRt=true, .isPseudo=true },
     [RABBITIZER_INSTR_ID_cpu_bltz] = { .operands={RAB_OPERAND_cpu_rs, RAB_OPERAND_cpu_branch_target_label}, .instrType=RABBITIZER_INSTR_TYPE_REGIMM, .isBranch=true, .readsRs=true },
     [RABBITIZER_INSTR_ID_cpu_bgez] = { .operands={RAB_OPERAND_cpu_rs, RAB_OPERAND_cpu_branch_target_label}, .instrType=RABBITIZER_INSTR_TYPE_REGIMM, .isBranch=true, .readsRs=true },
@@ -385,6 +386,7 @@ const RabbitizerInstrDescriptor RabbitizerInstrDescriptor_Descriptors[] = {
     [RABBITIZER_INSTR_ID_rsp_nop] = { .operands={0}, .instrType=RABBITIZER_INSTR_TYPE_R, .isPseudo=true },
     [RABBITIZER_INSTR_ID_rsp_move] = { .operands={RAB_OPERAND_rsp_rd, RAB_OPERAND_rsp_rs}, .instrType=RABBITIZER_INSTR_TYPE_R, .modifiesRd=true, .readsRs=true, .isPseudo=true },
     [RABBITIZER_INSTR_ID_rsp_not] = { .operands={RAB_OPERAND_rsp_rd, RAB_OPERAND_rsp_rs}, .instrType=RABBITIZER_INSTR_TYPE_R, .modifiesRd=true, .readsRs=true, .isPseudo=true },
+    [RABBITIZER_INSTR_ID_rsp_neg] = { .operands={RAB_OPERAND_rsp_rd, RAB_OPERAND_rsp_rt}, .instrType=RABBITIZER_INSTR_TYPE_R, .notEmittedByCompilers=true, .modifiesRd=true, .readsRt=true, .isPseudo=true },
     [RABBITIZER_INSTR_ID_rsp_negu] = { .operands={RAB_OPERAND_rsp_rd, RAB_OPERAND_rsp_rt}, .instrType=RABBITIZER_INSTR_TYPE_R, .modifiesRd=true, .readsRt=true, .isPseudo=true },
     [RABBITIZER_INSTR_ID_rsp_bltz] = { .operands={RAB_OPERAND_rsp_rs, RAB_OPERAND_cpu_branch_target_label}, .instrType=RABBITIZER_INSTR_TYPE_REGIMM, .readsRs=true, .isBranch=true },
     [RABBITIZER_INSTR_ID_rsp_bgez] = { .operands={RAB_OPERAND_rsp_rs, RAB_OPERAND_cpu_branch_target_label}, .instrType=RABBITIZER_INSTR_TYPE_REGIMM, .readsRs=true, .isBranch=true },
diff --git a/include/generated/InstrId_Names_array.h b/include/generated/InstrId_Names_array.h
index bb5ca50c..5aae7a05 100644
--- a/include/generated/InstrId_Names_array.h
+++ b/include/generated/InstrId_Names_array.h
@@ -123,6 +123,7 @@ const char *RabbitizerInstrId_Names[] = {
     [RABBITIZER_INSTR_ID_cpu_nop] = "nop",
     [RABBITIZER_INSTR_ID_cpu_move] = "move",
     [RABBITIZER_INSTR_ID_cpu_not] = "not",
+    [RABBITIZER_INSTR_ID_cpu_neg] = "neg",
     [RABBITIZER_INSTR_ID_cpu_negu] = "negu",
     [RABBITIZER_INSTR_ID_cpu_bltz] = "bltz",
     [RABBITIZER_INSTR_ID_cpu_bgez] = "bgez",
@@ -385,6 +386,7 @@ const char *RabbitizerInstrId_Names[] = {
     [RABBITIZER_INSTR_ID_rsp_nop] = "nop",
     [RABBITIZER_INSTR_ID_rsp_move] = "move",
     [RABBITIZER_INSTR_ID_rsp_not] = "not",
+    [RABBITIZER_INSTR_ID_rsp_neg] = "neg",
     [RABBITIZER_INSTR_ID_rsp_negu] = "negu",
     [RABBITIZER_INSTR_ID_rsp_bltz] = "bltz",
     [RABBITIZER_INSTR_ID_rsp_bgez] = "bgez",
diff --git a/include/generated/InstrId_enum.h b/include/generated/InstrId_enum.h
index 32e27c55..31e4efa7 100644
--- a/include/generated/InstrId_enum.h
+++ b/include/generated/InstrId_enum.h
@@ -123,6 +123,7 @@ typedef enum RabbitizerInstrId {
     RABBITIZER_INSTR_ID_cpu_nop,
     RABBITIZER_INSTR_ID_cpu_move,
     RABBITIZER_INSTR_ID_cpu_not,
+    RABBITIZER_INSTR_ID_cpu_neg,
     RABBITIZER_INSTR_ID_cpu_negu,
     RABBITIZER_INSTR_ID_cpu_bltz,
     RABBITIZER_INSTR_ID_cpu_bgez,
@@ -385,6 +386,7 @@ typedef enum RabbitizerInstrId {
     RABBITIZER_INSTR_ID_rsp_nop,
     RABBITIZER_INSTR_ID_rsp_move,
     RABBITIZER_INSTR_ID_rsp_not,
+    RABBITIZER_INSTR_ID_rsp_neg,
     RABBITIZER_INSTR_ID_rsp_negu,
     RABBITIZER_INSTR_ID_rsp_bltz,
     RABBITIZER_INSTR_ID_rsp_bgez,
diff --git a/pyproject.toml b/pyproject.toml
index 3b29fca7..1cd04012 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@
 [project]
 name = "rabbitizer"
 # Version should be synced with include/common/RabbitizerVersion.h
-version = "1.8.3"
+version = "1.9.0"
 description = "MIPS instruction decoder"
 # license = "MIT"
 readme = "README.md"
diff --git a/rabbitizer/Config.pyi b/rabbitizer/Config.pyi
index 450e288e..662b9c39 100644
--- a/rabbitizer/Config.pyi
+++ b/rabbitizer/Config.pyi
@@ -22,10 +22,11 @@ class _RabbitizerConfig:
     pseudos_pseudoB: bool = True
     pseudos_pseudoMove: bool = True
     pseudos_pseudoNot: bool = True
+    pseudos_pseudoNeg: bool = True
     pseudos_pseudoNegu: bool = True
     pseudos_pseudoBal: bool = True
 
-    toolchainTweaks_treatJAsUnconditionalBranch: bool = False
+    toolchainTweaks_treatJAsUnconditionalBranch: bool = True
     toolchainTweaks_sn64DivFix: bool = False
     toolchainTweaks_gnuMode: bool = True
 
diff --git a/rabbitizer/InstrId.pyi b/rabbitizer/InstrId.pyi
index 2e86e71b..48658bfa 100644
--- a/rabbitizer/InstrId.pyi
+++ b/rabbitizer/InstrId.pyi
@@ -124,6 +124,7 @@ class InstrId:
     cpu_nop: Enum
     cpu_move: Enum
     cpu_not: Enum
+    cpu_neg: Enum
     cpu_negu: Enum
     cpu_bltz: Enum
     cpu_bgez: Enum
@@ -366,6 +367,7 @@ class InstrId:
     rsp_nop: Enum
     rsp_move: Enum
     rsp_not: Enum
+    rsp_neg: Enum
     rsp_negu: Enum
     rsp_bltz: Enum
     rsp_bgez: Enum
diff --git a/rabbitizer/RegistersTracker.pyi b/rabbitizer/RegistersTracker.pyi
index 381887a5..20d6ac42 100644
--- a/rabbitizer/RegistersTracker.pyi
+++ b/rabbitizer/RegistersTracker.pyi
@@ -22,7 +22,8 @@ class RegistersTracker:
     def processGpLoad(self, instr: Instruction, instrOffset: int) -> None: ...
     def getLuiOffsetForConstant(self, instr: Instruction) -> int|None: ...
     def processConstant(self, instr: Instruction, value: int, offset: int) -> None: ...
-    def getLuiOffsetForLo(self, instr: Instruction, instrOffset: int) -> tuple[int, bool, bool]: ... #! deprecated
+    def getLuiOffsetForLo(self, instr: Instruction, instrOffset: int) -> tuple[int, bool, bool]: #! deprecated
+        """Use `.preprocessLoAndGetInfo()` instead"""
     def preprocessLoAndGetInfo(self, instr: Instruction, instrOffset: int) -> LoPairingInfo: ...
     def processLo(self, instr: Instruction, value: int, offset: int) -> None: ...
     def hasLoButNoHi(self, instr: Instruction) -> bool: ...
diff --git a/rabbitizer/rabbitizer.pyi b/rabbitizer/rabbitizer.pyi
index 424fdfad..3039d19a 100644
--- a/rabbitizer/rabbitizer.pyi
+++ b/rabbitizer/rabbitizer.pyi
@@ -80,56 +80,189 @@ class Instruction:
     """
 
 
-    def __init__(self, word: int, vram: int=0, category: Enum=InstrCategory.CPU) -> None: ...
+    def __init__(self, word: int, vram: int=0, category: Enum=InstrCategory.CPU) -> None:
+        """Decode an Instruction encoded as the 4 bytes `word`, located at `vram`.
 
-    def getRaw(self) -> int: ...
-    def getImmediate(self) -> int: ... #! deprecated
-    def getProcessedImmediate(self) -> int: ...
-    def getInstrIndexAsVram(self) -> int: ...
-    def getBranchOffset(self) -> int: ...
-    def getGenericBranchOffset(self, currentVram: int) -> int: ... #! deprecated
-    def getBranchOffsetGeneric(self) -> int: ...
-    def getBranchVramGeneric(self) -> int: ...
-    def getDestinationGpr(self) -> Enum|None: ...
-    def outputsToGprZero(self) -> bool: ...
-    def getOpcodeName(self) -> str: ...
+        `vram` is used to decode jump instructions that use the PC value to get the upper bits of the target address.
+        """
 
-    def blankOut(self) -> None: ...
+    def getRaw(self) -> int:
+        """Get the word value encoding the instruction.
 
-    def isImplemented(self) -> bool: ...
-    def isLikelyHandwritten(self) -> bool: ...
-    def isNop(self) -> bool: ...
-    def isUnconditionalBranch(self) -> bool: ...
+        The returned value may not be the same as the one to instance this Instruction
+        if a method that modifies the word has been used, like `instr.blankOut()`.
+        """
+    def getImmediate(self) -> int: #! deprecated
+        """Use `getProcessedImmediate()` instead"""
+    def getProcessedImmediate(self) -> int:
+        """Get the (possibly signed) immediate value for this instruction.
 
-    def isReturn(self) -> bool: ...
-    def isJumptableJump(self) -> bool: ...
+        This only makes sense for an instruction with an immediate,
+        which can be checked with `instr.hasOperandAlias(OperandType.cpu_immediate)`.
 
-    def isJrRa(self) -> bool: ... #! deprecated
-    def isJrNotRa(self) -> bool: ... #! deprecated
+        An exception will be raised if the instruction does not contain an immediate field.
+        """
+    def getInstrIndexAsVram(self) -> int:
+        """Get the target vram address this instruction jumps to.
+        This method is intended only for direct jump instructions.
 
-    def hasDelaySlot(self) -> bool: ...
-    def mapInstrToType(self) -> str|None: ... #! deprecated
+        This only makes sense if the instruction is a direct jump,
+        which can be checked with `instr.isJumpWithAddress()`.
 
-    def sameOpcode(self, other: Instruction) -> bool: ...
-    def sameOpcodeButDifferentArguments(self, other: Instruction) -> bool: ...
+        An exception will be raised if the instruction is not a jump instruction.
+        """
+    def getBranchOffset(self) -> int:
+        """Returns the offset (in bytes) that the branch instruction would branch,
+        relative to the instruction itself. This method is intended only for branch
+        instructions.
 
-    def hasOperand(self, operand: Enum) -> bool: ...
-    def hasOperandAlias(self, operand: Enum) -> bool: ...
+        The returned value can be negative, meaning the branch instructions does
+        a backwards branch.
 
-    def isValid(self) -> bool: ...
+        This only makes sense for an instruction is a branch,
+        which can be checked with `instr.isBranch()`.
+
+        To get the branch offset of either a branch instruction or a jump instruction
+        use `instr.getBranchOffsetGeneric()` instead.
+
+        An exception will be raised if the instruction is not a branch instruction.
+        """
+    def getGenericBranchOffset(self, currentVram: int) -> int: #! deprecated
+        """Use `getBranchOffsetGeneric()` instead"""
+    def getBranchOffsetGeneric(self) -> int:
+        """Returns the offset (in bytes) that the instruction would branch,
+        relative to the instruction itself. This method is intended for both branch
+        and jump instructions.
+
+        The returned value can be either positive or negative.
+
+        This only makes sense for an instruction is a branch or a direct jump,
+        which can be checked with `instr.isBranch()` or `instr.isJumpWithAddress()`.
+
+        An exception will be raised if the instruction is neither a branch or a
+        jump instruction.
+        """
+    def getBranchVramGeneric(self) -> int:
+        """Get the target vram address this instruction jumps to.
+        This method is intended only for branch or direct jump instructions.
+
+        This only makes sense for an instruction is a branch or a direct jump,
+        which can be checked with `instr.isBranch()` or `instr.isJumpWithAddress()`.
+
+        An exception will be raised if the instruction is neither a branch or a
+        jump instruction.
+        """
+    def getDestinationGpr(self) -> Enum|None:
+        """
+        Returns the general purpose register (GPR) which this instruction modifies,
+        or `None` if the instruction does not modify the state of any GPR
+        """
+    def outputsToGprZero(self) -> bool:
+        """
+        Returns `True` if the GPR which is modified by this register is $zero,
+        `False` otherwise.
+        Returns `false` if this instruction does not modify a GPR.
+        """
+    def getOpcodeName(self) -> str:
+        """Returns the mnemonic of the instruction.
+        """
+
+    def blankOut(self) -> None:
+        """Zero'es out every field (registers and immediate) of the instruction
+        leaving only the mnemonic.
+        """
+
+    def isImplemented(self) -> bool: #! deprecated
+        """Use `instr.isValid()` instead"""
+    def isLikelyHandwritten(self) -> bool:
+        """Try to guess if the given instruction was handwritten by a human
+        instead of generated by a C compiler.
+        """
+    def isNop(self) -> bool:
+        """Check if the instruction is literally the `nop` instruction."""
+    def isUnconditionalBranch(self) -> bool:
+        """Check if the instruction is an instruction that will always (unconditionally).
+
+        This is always true for the `b` instruction.
+
+        Some compilers use the `j` instruction for unconditional branches instead
+        of the `b` instruction. Treating this instruction as an unconditional branch
+        can be configured with the `config.toolchainTweaks_treatJAsUnconditionalBranch`
+        option.
+        """
+
+    def isReturn(self) -> bool:
+        """Check if the instruction and its register is the one usually used for
+        returning from a function.
+
+        Specfically, this checks if the instruction is a `jr $ra`.
+
+        Returns `False` if the instruction is not a `jr` or if it is a `jr` but
+        the register is not `$ra`.
+        """
+    def isJumptableJump(self) -> bool:
+        """Check if the instruction and its register is the one usually used for
+        jumping with jumptables.
+
+        Specfically, this checks if the instruction is a `jr` but not its register
+        is not `$ra`.
+
+        Returns `False` if the instruction is not a `jr` or if it is a `jr` but
+        the register is `$ra`.
+        """
+
+    def isJrRa(self) -> bool: #! deprecated
+        """Use `isReturn()` instead"""
+    def isJrNotRa(self) -> bool: #! deprecated
+        """Use `isJumptableJump()` instead"""
+
+    def hasDelaySlot(self) -> bool:
+        """Check if the instruction has a delay slot."""
+    def mapInstrToType(self) -> str|None: #! deprecated
+        """use `getAccessType()` instead"""
+
+    def sameOpcode(self, other: Instruction) -> bool:
+        """Check if two instructions have the same mnemonic"""
+    def sameOpcodeButDifferentArguments(self, other: Instruction) -> bool:
+        """Check if two instructions have the same mnemonic but their arguments
+        are different.
+        """
+
+    def hasOperand(self, operand: Enum) -> bool:
+        """Check if the instruction has specifically the `operand`.
+
+        `operand` should be from the `OperandType` enum.
+        """
+    def hasOperandAlias(self, operand: Enum) -> bool:
+        """Check if the instruction has the `operand` or an alias of it.
+
+        (if unsure whether to use `hasOperand` or `hasOperandAlias`, use `hasOperandAlias`)
+
+        `operand` should be from the `OperandType` enum.
+        """
+
+    def isValid(self) -> bool:
+        """Check the word corresponds to a valid instruction."""
 
     #! deprecated
     def isUnknownType(self) -> bool: ...
     #! deprecated
-    def isJType(self) -> bool: ...
+    def isJType(self) -> bool:
+        """Use `isJumpWithAddress()` instead"""
     #! deprecated
-    def isIType(self) -> bool: ...
+    def isIType(self) -> bool:
+        """Use `hasOperandAlias(OperandType.cpu_immediate)` instead"""
     #! deprecated
     def isRType(self) -> bool: ...
     #! deprecated
-    def isRegimmType(self) -> bool: ...
+    def isRegimmType(self) -> bool:
+        """Use `hasOperandAlias(OperandType.cpu_immediate)` instead"""
+
+    def isBranch(self) -> bool:
+        """Instruction is a branch instruction.
 
-    def isBranch(self) -> bool: ...
+        This is also true for branch likely instructions.
+        """
     def isBranchLikely(self) -> bool: ...
     def isJump(self) -> bool: ...
     def isJumpWithAddress(self) -> bool: ...
@@ -153,7 +286,8 @@ class Instruction:
     def readsFs(self) -> bool: ...
     def readsFt(self) -> bool: ...
     def readsFd(self) -> bool: ...
-    def notEmitedByCompilers(self) -> bool: ... #! deprecated
+    def notEmitedByCompilers(self) -> bool: #! deprecated
+        """Use `notEmittedByCompilers()` instead"""
     def notEmittedByCompilers(self) -> bool: ...
     def canBeHi(self) -> bool: ...
     def canBeLo(self) -> bool: ...
@@ -166,7 +300,20 @@ class Instruction:
     def getAccessType(self) -> Enum: ...
     def doesUnsignedMemoryAccess(self) -> bool: ...
 
-    def disassemble(self, immOverride: str|None=None, extraLJust: int=0) -> str: ...
+    def disassemble(self, immOverride: str|None=None, extraLJust: int=0) -> str:
+        """Returns a string that can be assembled back to the instruction raw word.
+        `immOverride` can be a string that replaces the immediate in the disassembly,
+        or the jump target, if any. If the instruction has neither, it is ignored.
+        Examples:
+        >>> Instruction(0x3C0A0042).disassemble()
+        'lui         $t2, 0x42'
+        >>> Instruction(0x3C0A0042).disassemble("hex_answer")
+        'lui         $t2, hex_answer'
+        >>> Instruction(0x0C000042).disassemble()
+        'jal         func_80000108'
+        >>> Instruction(0x0C000042).disassemble("my_target")
+        'jal         my_target'
+        """
 
     def __reduce__(self) -> tuple: ...
 
diff --git a/rabbitizer/rabbitizer_global_config.c b/rabbitizer/rabbitizer_global_config.c
index 07ba6b18..55151eac 100644
--- a/rabbitizer/rabbitizer_global_config.c
+++ b/rabbitizer/rabbitizer_global_config.c
@@ -113,6 +113,7 @@ DEF_MEMBER_GET_SET_BOOL(pseudos, pseudoBnez)
 DEF_MEMBER_GET_SET_BOOL(pseudos, pseudoB)
 DEF_MEMBER_GET_SET_BOOL(pseudos, pseudoMove)
 DEF_MEMBER_GET_SET_BOOL(pseudos, pseudoNot)
+DEF_MEMBER_GET_SET_BOOL(pseudos, pseudoNeg)
 DEF_MEMBER_GET_SET_BOOL(pseudos, pseudoNegu)
 DEF_MEMBER_GET_SET_BOOL(pseudos, pseudoBal)
 
@@ -140,6 +141,7 @@ static PyGetSetDef rabbitizer_global_config_GetSets[] = {
     MEMBER_GET_SET(pseudos, pseudoB, "", NULL),
     MEMBER_GET_SET(pseudos, pseudoMove, "", NULL),
     MEMBER_GET_SET(pseudos, pseudoNot, "", NULL),
+    MEMBER_GET_SET(pseudos, pseudoNeg, "", NULL),
     MEMBER_GET_SET(pseudos, pseudoNegu, "", NULL),
     MEMBER_GET_SET(pseudos, pseudoBal, "", NULL),
 
diff --git a/rabbitizer/rabbitizer_type_Instruction.c b/rabbitizer/rabbitizer_type_Instruction.c
index 52ec73ff..51035fed 100644
--- a/rabbitizer/rabbitizer_type_Instruction.c
+++ b/rabbitizer/rabbitizer_type_Instruction.c
@@ -289,12 +289,42 @@ static PyGetSetDef rabbitizer_type_Instruction_getsetters[] = {
     }
 
 DEF_METHOD_GET_UINT(getRaw)
+
 static PyObject *rabbitizer_type_Instruction_getImmediate(PyRabbitizerInstruction *self, UNUSED PyObject *closure) {
+    if (!RabbitizerInstruction_hasOperandAlias(&self->instr, RAB_OPERAND_cpu_immediate)) {
+        PyErr_Format(PyExc_RuntimeError, "'%s' instruction does not have an 'immediate' field", RabbitizerInstrId_getOpcodeName(self->instr.uniqueId));
+        return NULL;
+    }
+
     return PyLong_FromUnsignedLong(RAB_INSTR_GET_immediate(&self->instr));
 }
-DEF_METHOD_GET_INT(getProcessedImmediate)
-DEF_METHOD_GET_UINT(getInstrIndexAsVram)
-DEF_METHOD_GET_INT(getBranchOffset)
+
+static PyObject *rabbitizer_type_Instruction_getProcessedImmediate(PyRabbitizerInstruction *self,UNUSED PyObject *closure) {
+    if (!RabbitizerInstruction_hasOperandAlias(&self->instr, RAB_OPERAND_cpu_immediate)) {
+        PyErr_Format(PyExc_RuntimeError, "'%s' instruction does not have an 'immediate' field", RabbitizerInstrId_getOpcodeName(self->instr.uniqueId));
+        return NULL;
+    }
+
+    return PyLong_FromLong(RabbitizerInstruction_getProcessedImmediate(&self->instr)); 
+}
+
+static PyObject *rabbitizer_type_Instruction_getInstrIndexAsVram(PyRabbitizerInstruction *self, UNUSED PyObject *closure) {
+    if (!RabbitizerInstruction_hasOperandAlias(&self->instr, RAB_OPERAND_cpu_label)) {
+        PyErr_Format(PyExc_RuntimeError, "'%s' instruction does not have an 'jump label' field", RabbitizerInstrId_getOpcodeName(self->instr.uniqueId));
+        return NULL;
+    }
+
+    return PyLong_FromUnsignedLong(RabbitizerInstruction_getInstrIndexAsVram(&self->instr));
+}
+
+static PyObject *rabbitizer_type_Instruction_getBranchOffset(PyRabbitizerInstruction *self, UNUSED PyObject *closure) {
+    if (!RabbitizerInstruction_hasOperand(&self->instr, RAB_OPERAND_cpu_branch_target_label)) {
+        PyErr_Format(PyExc_RuntimeError, "'%s' instruction does not have an 'branch label' field", RabbitizerInstrId_getOpcodeName(self->instr.uniqueId));
+        return NULL;
+    }
+
+    return PyLong_FromLong(RabbitizerInstruction_getBranchOffset(&self->instr));
+}
 
 static PyObject *rabbitizer_type_Instruction_getOpcodeName(PyRabbitizerInstruction *self, UNUSED PyObject *closure) {
     return PyUnicode_FromString(RabbitizerInstrId_getOpcodeName(self->instr.uniqueId));
@@ -311,8 +341,23 @@ static PyObject *rabbitizer_type_Instruction_getGenericBranchOffset(PyRabbitizer
     return PyLong_FromLong(RabbitizerInstruction_getGenericBranchOffset(&self->instr, currentVram));
 }
 
-DEF_METHOD_GET_INT(getBranchOffsetGeneric)
-DEF_METHOD_GET_UINT(getBranchVramGeneric)
+static PyObject *rabbitizer_type_Instruction_getBranchOffsetGeneric(PyRabbitizerInstruction *self, UNUSED PyObject *closure) {
+    if (!RabbitizerInstruction_hasOperandAlias(&self->instr, RAB_OPERAND_cpu_label) && !RabbitizerInstruction_hasOperand(&self->instr, RAB_OPERAND_cpu_branch_target_label)) {
+        PyErr_Format(PyExc_RuntimeError, "'%s' instruction does not not perform a branch or a jump.", RabbitizerInstrId_getOpcodeName(self->instr.uniqueId));
+        return NULL;
+    }
+
+    return PyLong_FromLong(RabbitizerInstruction_getBranchOffsetGeneric(&self->instr));
+}
+
+static PyObject *rabbitizer_type_Instruction_getBranchVramGeneric(PyRabbitizerInstruction *self, UNUSED PyObject *closure) {
+    if (!RabbitizerInstruction_hasOperandAlias(&self->instr, RAB_OPERAND_cpu_label) && !RabbitizerInstruction_hasOperand(&self->instr, RAB_OPERAND_cpu_branch_target_label)) {
+        PyErr_Format(PyExc_RuntimeError, "'%s' instruction does not not perform a branch or a jump.", RabbitizerInstrId_getOpcodeName(self->instr.uniqueId));
+        return NULL;
+    }
+
+    return PyLong_FromUnsignedLong(RabbitizerInstruction_getBranchVramGeneric(&self->instr));
+}
 
 static PyObject *rabbitizer_type_Instruction_getDestinationGpr(PyRabbitizerInstruction *self, UNUSED PyObject *closure) {
     int8_t reg = RabbitizerInstruction_getDestinationGpr(&self->instr);
diff --git a/rust/src/config.rs b/rust/src/config.rs
index 686fa31a..86e749c3 100644
--- a/rust/src/config.rs
+++ b/rust/src/config.rs
@@ -25,6 +25,7 @@ pub struct PseudoInstr {
     pub pseudo_b: bool,
     pub pseudo_move: bool,
     pub pseudo_not: bool,
+    pub pseudo_neg: bool,
     pub pseudo_negu: bool,
     pub pseudo_bal: bool,
 }
diff --git a/rust/src/instr_id_enum.rs b/rust/src/instr_id_enum.rs
index 0c191290..776dd507 100644
--- a/rust/src/instr_id_enum.rs
+++ b/rust/src/instr_id_enum.rs
@@ -123,6 +123,7 @@ pub enum InstrId {
     cpu_nop,
     cpu_move,
     cpu_not,
+    cpu_neg,
     cpu_negu,
     cpu_bltz,
     cpu_bgez,
@@ -385,6 +386,7 @@ pub enum InstrId {
     rsp_nop,
     rsp_move,
     rsp_not,
+    rsp_neg,
     rsp_negu,
     rsp_bltz,
     rsp_bgez,
diff --git a/rust/src/instruction.rs b/rust/src/instruction.rs
index f89f2452..de279b1e 100644
--- a/rust/src/instruction.rs
+++ b/rust/src/instruction.rs
@@ -519,6 +519,7 @@ impl Instruction {
         unsafe { RabbitizerInstruction_blankOut(&mut self) }
     }
 
+    #[deprecated(since = "1.8.4", note = "please use `is_valid` instead")]
     pub fn is_implemented(&self) -> bool {
         unsafe { RabbitizerInstruction_isImplemented(self) }
     }
diff --git a/src/common/RabbitizerConfig.c b/src/common/RabbitizerConfig.c
index 23f58870..351be722 100644
--- a/src/common/RabbitizerConfig.c
+++ b/src/common/RabbitizerConfig.c
@@ -34,6 +34,7 @@ RabbitizerConfig RabbitizerConfig_Cfg = {
         .pseudoB = true,
         .pseudoMove = true,
         .pseudoNot = true,
+        .pseudoNeg = true,
         .pseudoNegu = true,
         .pseudoBal = true,
     },
diff --git a/src/instructions/RabbitizerInstrId.c b/src/instructions/RabbitizerInstrId.c
index 046064eb..4ea6f420 100644
--- a/src/instructions/RabbitizerInstrId.c
+++ b/src/instructions/RabbitizerInstrId.c
@@ -7,6 +7,9 @@
 
 #include "generated/InstrId_Names_array.h"
 
+/**
+ * Check the word corresponds to a valid instruction.
+ */
 bool RabbitizerInstrId_isValid(RabbitizerInstrId uniqueId) {
     switch (uniqueId) {
         case RABBITIZER_INSTR_ID_cpu_INVALID:
diff --git a/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c b/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c
index 33e04b6b..27846284 100644
--- a/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c
+++ b/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c
@@ -33,10 +33,23 @@ void RabbitizerInstruction_destroy(UNUSED RabbitizerInstruction *self) {
 
 /* General getters */
 
+/**
+ * Get the word value encoding the instruction.
+ *
+ * The returned value may not be the same as the one to instance this Instruction
+ * if a method that modifies the word has been used, like `RabbitizerInstruction_blankOut`.
+ */
 uint32_t RabbitizerInstruction_getRaw(const RabbitizerInstruction *self) {
     return self->word;
 }
 
+/**
+ * Get the (possibly signed) immediate value for this instruction.
+
+ * This only makes sense for an instruction with an immediate,
+ * which can be checked with
+ * `RabbitizerInstruction_hasOperandAlias(&instr, RAB_OPERAND_cpu_immediate)`
+ */
 int32_t RabbitizerInstruction_getProcessedImmediate(const RabbitizerInstruction *self) {
     if (RabbitizerInstrDescriptor_isUnsigned(self->descriptor)) {
         return RAB_INSTR_GET_immediate(self);
@@ -44,6 +57,13 @@ int32_t RabbitizerInstruction_getProcessedImmediate(const RabbitizerInstruction
     return RabbitizerUtils_From2Complement(RAB_INSTR_GET_immediate(self), 16);
 }
 
+/**
+ * Get the target vram address this instruction jumps to.
+ * This method is intended only for direct jump instructions.
+
+ * This only makes sense if the instruction is a direct jump,
+ * which can be checked with `RabbitizerInstrDescriptor_isJumpWithAddress`.
+ */
 uint32_t RabbitizerInstruction_getInstrIndexAsVram(const RabbitizerInstruction *self) {
     uint32_t vram = RAB_INSTR_GET_instr_index(self) << 2;
 
@@ -56,12 +76,27 @@ uint32_t RabbitizerInstruction_getInstrIndexAsVram(const RabbitizerInstruction *
     return vram;
 }
 
+/**
+ * Returns the offset (in bytes) that the branch instruction would branch,
+ * relative to the instruction itself. This method is intended only for branch
+ * instructions.
+ *
+ * The returned value can be negative, meaning the branch instructions does
+ * a backwards branch.
+ *
+ * This only makes sense for an instruction is a branch,
+ * which can be checked with `RabbitizerInstrDescriptor_isBranch`.
+ *
+ * To get the branch offset of either a branch instruction or a jump instruction
+ * use `RabbitizerInstruction_getBranchOffsetGeneric` instead.
+ */
 int32_t RabbitizerInstruction_getBranchOffset(const RabbitizerInstruction *self) {
     int32_t diff = RabbitizerUtils_From2Complement(RAB_INSTR_GET_immediate(self), 16);
 
     return diff * 4 + 4;
 }
 
+//! @deprecated
 int32_t RabbitizerInstruction_getGenericBranchOffset(const RabbitizerInstruction *self, uint32_t currentVram) {
     if (self->uniqueId == RABBITIZER_INSTR_ID_cpu_j) {
         return RabbitizerInstruction_getInstrIndexAsVram(self) - currentVram;
@@ -69,6 +104,17 @@ int32_t RabbitizerInstruction_getGenericBranchOffset(const RabbitizerInstruction
     return RabbitizerInstruction_getBranchOffset(self);
 }
 
+/**
+ * Returns the offset (in bytes) that the instruction would branch,
+ * relative to the instruction itself. This method is intended for both branch
+ * and jump instructions.
+ *
+ * The returned value can be either positive or negative.
+ *
+ * This only makes sense for an instruction is a branch or a direct jump,
+ * which can be checked with `RabbitizerInstruction_getBranchOffsetGeneric`
+ * or `RabbitizerInstrDescriptor_isJumpWithAddress`.
+ */
 int32_t RabbitizerInstruction_getBranchOffsetGeneric(const RabbitizerInstruction *self) {
     if (RabbitizerInstruction_hasOperandAlias(self, RAB_OPERAND_cpu_label)) {
         return RabbitizerInstruction_getInstrIndexAsVram(self) - self->vram;
@@ -76,6 +122,14 @@ int32_t RabbitizerInstruction_getBranchOffsetGeneric(const RabbitizerInstruction
     return RabbitizerInstruction_getBranchOffset(self);
 }
 
+/**
+ * Get the target vram address this instruction jumps to.
+ * This method is intended only for branch or direct jump instructions.
+ *
+ * This only makes sense for an instruction is a branch or a direct jump,
+ * which can be checked with `RabbitizerInstruction_getBranchOffsetGeneric`
+ * or `RabbitizerInstrDescriptor_isJumpWithAddress`.
+ */
 uint32_t RabbitizerInstruction_getBranchVramGeneric(const RabbitizerInstruction *self) {
     if (RabbitizerInstruction_hasOperandAlias(self, RAB_OPERAND_cpu_label)) {
         return RabbitizerInstruction_getInstrIndexAsVram(self);
@@ -84,7 +138,7 @@ uint32_t RabbitizerInstruction_getBranchVramGeneric(const RabbitizerInstruction
 }
 
 /**
- * @brief Returns the general purpose register (GPR) which this instruction modifies,
+ * Returns the general purpose register (GPR) which this instruction modifies,
  * or a negative value if the instruction does not modify the state of any GPR
  */
 int8_t RabbitizerInstruction_getDestinationGpr(const RabbitizerInstruction *self) {
@@ -98,7 +152,7 @@ int8_t RabbitizerInstruction_getDestinationGpr(const RabbitizerInstruction *self
 }
 
 /**
- * @brief Returns `true` if the GPR which is modified by this register is $zero, `false` otherwise.
+ * Returns `true` if the GPR which is modified by this register is $zero, `false` otherwise.
  * Returns `false` if this instruction does not modify a GPR.
  */
 bool RabbitizerInstruction_outputsToGprZero(const RabbitizerInstruction *self) {
diff --git a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c
index 5892e44e..106ed760 100644
--- a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c
+++ b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c
@@ -186,7 +186,7 @@ bool RabbitizerInstruction_mustDisasmAsData(const RabbitizerInstruction *self) {
 
 size_t RabbitizerInstruction_getSizeForBuffer(const RabbitizerInstruction *self, size_t immOverrideLength,
                                               int extraLJust) {
-    if (!RabbitizerInstruction_isImplemented(self) || RabbitizerInstruction_mustDisasmAsData(self)) {
+    if (!RabbitizerInstruction_isValid(self) || RabbitizerInstruction_mustDisasmAsData(self)) {
         size_t totalSize = RabbitizerInstruction_getSizeForBufferDataDisasm(self, extraLJust);
 
         if (RabbitizerConfig_Cfg.misc.unknownInstrComment) {
@@ -208,7 +208,7 @@ size_t RabbitizerInstruction_disassemble(const RabbitizerInstruction *self, char
                                          size_t immOverrideLength, int extraLJust) {
     assert(dst != NULL);
 
-    if (!RabbitizerInstruction_isImplemented(self) || RabbitizerInstruction_mustDisasmAsData(self)) {
+    if (!RabbitizerInstruction_isValid(self) || RabbitizerInstruction_mustDisasmAsData(self)) {
         size_t totalSize = 0;
 
         RABUTILS_BUFFER_ADVANCE(dst, totalSize, RabbitizerInstruction_disassembleAsData(self, dst, extraLJust));
diff --git a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c
index e20084f4..f6ad03a7 100644
--- a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c
+++ b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c
@@ -11,11 +11,15 @@
 #include "instructions/RabbitizerInstructionR5900.h"
 #include "instructions/RabbitizerRegister.h"
 
-// TODO: deprecate
+//! @deprecated: use RabbitizerInstrId_isValid instead
 bool RabbitizerInstruction_isImplemented(const RabbitizerInstruction *self) {
     return RabbitizerInstrId_isValid(self->uniqueId);
 }
 
+/**
+ * Try to guess if the given instruction was handwritten by a human
+ * instead of generated by a C compiler.
+ */
 bool RabbitizerInstruction_isLikelyHandwritten(const RabbitizerInstruction *self) {
     if (self->_handwrittenCategory) {
         return true;
@@ -39,10 +43,23 @@ bool RabbitizerInstruction_isLikelyHandwritten(const RabbitizerInstruction *self
     return false;
 }
 
+/**
+ * Check if the instruction is literally the `nop` instruction.
+ */
 bool RabbitizerInstruction_isNop(const RabbitizerInstruction *self) {
     return self->word == 0;
 }
 
+/**
+ * Check if the instruction is an instruction that will always (unconditionally).
+ *
+ * This is always true for the `b` instruction.
+ *
+ * Some compilers use the `j` instruction for unconditional branches instead
+ * of the `b` instruction. Treating this instruction as an unconditional branch
+ * can be configured with the `config.toolchainTweaks_treatJAsUnconditionalBranch`
+ * option.
+ */
 bool RabbitizerInstruction_isUnconditionalBranch(const RabbitizerInstruction *self) {
     switch (self->uniqueId) {
         case RABBITIZER_INSTR_ID_cpu_b:
@@ -63,6 +80,15 @@ bool RabbitizerInstruction_isUnconditionalBranch(const RabbitizerInstruction *se
     }
 }
 
+/**
+ * Check if the instruction and its register is the one usually used for
+ * returning from a function.
+ *
+ * Specfically, this checks if the instruction is a `jr $ra`.
+ *
+ * Returns `False` if the instruction is not a `jr` or if it is a `jr` but
+ * the register is not `$ra`.
+ */
 bool RabbitizerInstruction_isReturn(const RabbitizerInstruction *self) {
     switch (self->uniqueId) {
         case RABBITIZER_INSTR_ID_cpu_jr:
@@ -78,6 +104,16 @@ bool RabbitizerInstruction_isReturn(const RabbitizerInstruction *self) {
     }
 }
 
+/**
+ * Check if the instruction and its register is the one usually used for
+ * jumping with jumptables.
+ *
+ * Specfically, this checks if the instruction is a `jr` but not its register
+ * is not `$ra`.
+ *
+ * Returns `False` if the instruction is not a `jr` or if it is a `jr` but
+ * the register is `$ra`.
+ */
 bool RabbitizerInstruction_isJumptableJump(const RabbitizerInstruction *self) {
     switch (self->uniqueId) {
         case RABBITIZER_INSTR_ID_cpu_jr:
@@ -93,10 +129,14 @@ bool RabbitizerInstruction_isJumptableJump(const RabbitizerInstruction *self) {
     }
 }
 
+/**
+ * Check if the instruction has a delay slot.
+ */
 bool RabbitizerInstruction_hasDelaySlot(const RabbitizerInstruction *self) {
     return RabbitizerInstrDescriptor_isBranch(self->descriptor) || RabbitizerInstrDescriptor_isJump(self->descriptor);
 }
 
+//! @deprecated: Use `RabbitizerInstrDescriptor_getAccessType` instead.
 const char *RabbitizerInstruction_mapInstrToType(const RabbitizerInstruction *self) {
     if (RabbitizerInstrDescriptor_isDouble(self->descriptor)) {
         return "f64";
@@ -125,6 +165,9 @@ const char *RabbitizerInstruction_mapInstrToType(const RabbitizerInstruction *se
     return NULL;
 }
 
+/**
+ * Check if two instructions have the same mnemonic
+ */
 bool RabbitizerInstruction_sameOpcode(const RabbitizerInstruction *self, const RabbitizerInstruction *other) {
     if (!RabbitizerInstrId_isValid(self->uniqueId) || !RabbitizerInstrId_isValid(other->uniqueId)) {
         return false;
@@ -132,6 +175,10 @@ bool RabbitizerInstruction_sameOpcode(const RabbitizerInstruction *self, const R
     return self->uniqueId == other->uniqueId;
 }
 
+/**
+ * Check if two instructions have the same mnemonic but their arguments
+ * are different.
+ */
 bool RabbitizerInstruction_sameOpcodeButDifferentArguments(const RabbitizerInstruction *self,
                                                            const RabbitizerInstruction *other) {
     if (!RabbitizerInstruction_sameOpcode(self, other)) {
@@ -140,11 +187,18 @@ bool RabbitizerInstruction_sameOpcodeButDifferentArguments(const RabbitizerInstr
     return RabbitizerInstruction_getRaw(self) != RabbitizerInstruction_getRaw(other);
 }
 
-// TODO: deprecate?
+/**
+ * Check if the instruction has specifically the `operand`.
+ */
 bool RabbitizerInstruction_hasOperand(const RabbitizerInstruction *self, RabbitizerOperandType operand) {
     return RabbitizerInstrDescriptor_hasSpecificOperand(self->descriptor, operand);
 }
 
+/**
+ * Check if the instruction has the `operand` or an alias of it.
+ *
+ * (if unsure whether to use `hasOperand` or `hasOperandAlias`, use `hasOperandAlias`)
+ */
 bool RabbitizerInstruction_hasOperandAlias(const RabbitizerInstruction *self, RabbitizerOperandType operand) {
     return RabbitizerInstrDescriptor_hasOperandAlias(self->descriptor, operand);
 }
diff --git a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c
index c3f64554..3b1266a2 100644
--- a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c
+++ b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c
@@ -86,6 +86,14 @@ void RabbitizerInstruction_processUniqueId_Special(RabbitizerInstruction *self)
                 }
                 break;
 
+            case RABBITIZER_INSTR_ID_cpu_sub:
+                if (RAB_INSTR_GET_rs(self) == 0) {
+                    if (RabbitizerConfig_Cfg.pseudos.pseudoNeg) {
+                        self->uniqueId = RABBITIZER_INSTR_ID_cpu_neg;
+                    }
+                }
+                break;
+
             case RABBITIZER_INSTR_ID_cpu_subu:
                 if (RAB_INSTR_GET_rs(self) == 0) {
                     if (RabbitizerConfig_Cfg.pseudos.pseudoNegu) {
diff --git a/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c b/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c
index adb8017c..774c8f21 100644
--- a/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c
+++ b/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c
@@ -115,6 +115,12 @@ void RabbitizerInstructionRsp_processUniqueId_Special(RabbitizerInstruction *sel
                     self->uniqueId = RABBITIZER_INSTR_ID_rsp_not;
                 }
             }
+        } else if (self->uniqueId == RABBITIZER_INSTR_ID_rsp_sub) {
+            if (RAB_INSTR_GET_rs(self) == 0) {
+                if (RabbitizerConfig_Cfg.pseudos.pseudoNeg) {
+                    self->uniqueId = RABBITIZER_INSTR_ID_rsp_neg;
+                }
+            }
         } else if (self->uniqueId == RABBITIZER_INSTR_ID_rsp_subu) {
             if (RAB_INSTR_GET_rs(self) == 0) {
                 if (RabbitizerConfig_Cfg.pseudos.pseudoNegu) {
diff --git a/tables/tables/instr_id/cpu/cpu_special.inc b/tables/tables/instr_id/cpu/cpu_special.inc
index 0ffd98df..e414f334 100644
--- a/tables/tables/instr_id/cpu/cpu_special.inc
+++ b/tables/tables/instr_id/cpu/cpu_special.inc
@@ -524,6 +524,17 @@
         .isPseudo=true
     ) // Not
 
+    // OP rd, rt
+    RABBITIZER_DEF_INSTR_ID(
+        cpu, -0x22, neg,
+        .operands={RAB_OPERAND_cpu_rd, RAB_OPERAND_cpu_rt},
+        .instrType=RABBITIZER_INSTR_TYPE_R,
+        .notEmittedByCompilers=true,
+        .modifiesRd=true,
+        .readsRt=true,
+        .isPseudo=true
+    )
+
     // OP rd, rt
     RABBITIZER_DEF_INSTR_ID(
         cpu, -0x23, negu,
diff --git a/tables/tables/instr_id/rsp/rsp_special.inc b/tables/tables/instr_id/rsp/rsp_special.inc
index 571d37a7..59547169 100644
--- a/tables/tables/instr_id/rsp/rsp_special.inc
+++ b/tables/tables/instr_id/rsp/rsp_special.inc
@@ -204,6 +204,17 @@
         .isPseudo=true
     ) // Not
 
+    // OP rd, rt
+    RABBITIZER_DEF_INSTR_ID(
+        rsp, -0x22, neg,
+        .operands={RAB_OPERAND_rsp_rd, RAB_OPERAND_rsp_rt},
+        .instrType=RABBITIZER_INSTR_TYPE_R,
+        .notEmittedByCompilers=true,
+        .modifiesRd=true,
+        .readsRt=true,
+        .isPseudo=true
+    )
+
     // OP rd, rt
     RABBITIZER_DEF_INSTR_ID(
         rsp, -0x23, negu,


Test patch:
diff --git a/tests/c/instruction_checks/plain_disassembly.c b/tests/c/instruction_checks/plain_disassembly.c
index 80bd90d5..ff14bf46 100644
--- a/tests/c/instruction_checks/plain_disassembly.c
+++ b/tests/c/instruction_checks/plain_disassembly.c
@@ -54,6 +54,12 @@ const TestEntry entries[] = {
     { 0x8F858028, NULL,                 "lw          $a1, -0x7FD8($gp)"},
     { 0x8F858028, "%got(STR_10007C90)", "lw          $a1, %got(STR_10007C90)($gp)"},
 
+    { 0x00435022, NULL, "sub         $t2, $v0, $v1", },
+    { 0x00025022, NULL, "neg         $t2, $v0", },
+
+    { 0x00E41823, NULL, "subu        $v1, $a3, $a0" },
+    { 0x00041823, NULL, "negu        $v1, $a0" },
+
     // Invalid instructions
     { 0x44444444, NULL, ".word       0x44444444                   # cfc1        $a0, $8 # 00000444 <InstrIdType: CPU_COP1>" },
     { 0x77777777, NULL, ".word       0x77777777                   # INVALID     $k1, $s7, 0x7777 # 00000000 <InstrIdType: CPU_NORMAL>" },