Skip to content

feat(vm): implement TIP-7939 CLZ opcode#6656

Open
yanghang8612 wants to merge 1 commit intotronprotocol:developfrom
yanghang8612:implement-tip-7939
Open

feat(vm): implement TIP-7939 CLZ opcode#6656
yanghang8612 wants to merge 1 commit intotronprotocol:developfrom
yanghang8612:implement-tip-7939

Conversation

@yanghang8612
Copy link
Copy Markdown
Collaborator

Summary

Implement TIP-7939 CLZ opcode (0x1e), gated behind the existing allowTvmOsaka flag.

  • New opcode: pops 1 value, pushes the number of leading zero bits (256 if zero)
  • Gas cost: 5 (LOW_TIER, matching MUL)
  • Added TRON_V1_5 version with appendOsakaOperations to support Osaka opcodes
  • Test cases cover all TIP test vectors and energy cost verification

Related: tronprotocol/tips#838

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread actuator/src/main/java/org/tron/core/vm/OperationActions.java
@halibobo1205 halibobo1205 added the topic:vm VM, smart contract label Apr 9, 2026
if (clz == 256) {
program.stackPush(new DataWord(256));
} else {
program.stackPush(DataWord.of((byte) clz));
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.

Nice work overall! One minor suggestion: since DataWord.of(byte num) directly assigns bb[31] = num, casting clz to (byte) when it's in [128, 255] introduces a subtle signed/unsigned ambiguity. Would it be worth simplifying both branches into one using new DataWord(int), which handles the full range cleanly?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the review! The (byte) cast here is safe — DataWord.of(byte) assigns the raw bit pattern to bb[31], and DataWord.value() reads it back as unsigned via new BigInteger(1, data). This is consistent with how byte arrays are used throughout the codebase (e.g., DataWord.of((byte) 0xFF) correctly represents 255). The split avoids the double ByteBuffer.allocate overhead in DataWord(int) for the common non-zero path.

Assert.assertEquals(new DataWord(255), program.getStack().pop());

// Verify energy cost = LOW_TIER(5) + PUSH32 cost(3) = 8
Assert.assertEquals(8, program.getResult().getEnergyUsed());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The test coverage is really solid — the chosen vectors (0, 1, 255, 256, boundary bits) cover the most critical scenarios nicely! 🎯

One thing that might be worth adding: a couple of test cases where the CLZ result falls in the 128-255 range (e.g., byte[16]=0x01 → CLZ=128, byte[30]=0x01 → CLZ=247). This would explicitly exercise the (byte) cast path for values above 127, giving extra confidence in the signed/unsigned handling. Would make the already-great test suite even more bulletproof!

// Verify energy cost = LOW_TIER(5) + PUSH32 cost(3) = 8
Assert.assertEquals(8, program.getResult().getEnergyUsed());

VMConfig.initAllowTvmOsaka(0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice work on properly resetting VMConfig.initAllowTvmOsaka(0) in cleanup — shows good testing discipline!

Would it be worth adding a small negative test to verify that CLZ is correctly rejected when allowTvmOsaka is disabled? This would serve as a safety net to ensure the feature gate can never be accidentally bypassed. Just a suggestion to make the gating even more robust 🙂

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

Labels

topic:vm VM, smart contract

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants