fglock/perlonjava/custom-bytecode
Claude Code and agent skill by fglock/perlonjava
- community
Checking that the SKILL.md file is still in the repository.
Install
npx vskill@latest install fglock/perlonjava/custom-bytecodeSKILL.md
PerlOnJava Interpreter Developer Guide
- name all test files /tmp/test.pl
Quick Reference
Performance: 46.84M ops/sec (1.75x slower than compiler ✓) Opcodes: 0-157 (contiguous) for JVM tableswitch optimization Runtime: 100% API compatibility with compiler (zero duplication)
Testing Modes
JPERL_EVAL_USE_INTERPRETER=1 - Forces all eval STRING to use the interpreter
- Used for testing interpreter implementation of operators in eval context
- Compiler still used for main code, only eval STRING uses interpreter
- Example:
JPERL_EVAL_USE_INTERPRETER=1 ./jperl test.pl
JPERL_EVAL_VERBOSE=1 - Enable verbose eval error reporting
- By default, eval failures are silent (errors only stored in $@)
- With verbose mode, eval compilation errors print to stderr
- Useful for debugging interpreter eval issues
- Example:
JPERL_EVAL_USE_INTERPRETER=1 JPERL_EVAL_VERBOSE=1 ./jperl test.pl
--interpreter - Forces the interpreter EVERYWHERE
- All code (main and eval) runs in interpreter mode
- Used for full interpreter testing and development
- Example:
./jperl --interpreter test.pl
Core Files
Opcodes.java- Opcode constants (0-157, contiguous)BytecodeInterpreter.java- Execution loop with range-based delegationBytecodeCompiler.java- AST to bytecode with register allocationInterpretedCode.java- Bytecode container with disassemblerSlowOpcodeHandler.java- Handlers for rare operations (151-154)
Code Generation Tool
Location: dev/tools/generate_opcode_handlers.pl
Automates creation of opcode handlers for built-in functions with simple signatures.
Quick Start
What Gets Generated
Automatically:
- Handler classes with zero-overhead dispatch pattern
- Opcode constants in Opcodes.java
- Dispatch cases in BytecodeInterpreter.java
- Disassembly cases in InterpretedCode.java
Still Manual:
- Emit cases in BytecodeCompiler.java (between
// GENERATED_OPERATORS_START/END)
Eligibility Criteria
Included:
- Scalar unary:
(RuntimeScalar) → RuntimeScalar - Scalar binary:
(RuntimeScalar, RuntimeScalar) → RuntimeScalar - Scalar ternary:
(RuntimeScalar, RuntimeScalar, RuntimeScalar) → RuntimeScalar
Excluded:
- Varargs signatures:
(int, RuntimeBase...)- getc - Array/List/Hash parameters
- Primitive parameters (except in skipped varargs)
- Already existing opcodes (rand=91, length=30, rindex=173, index=172, require=170, isa=105, bless=104, ref=103, join=88, prototype=158)
Adding BytecodeCompiler Cases
Tool prints list of operators needing emit cases. Add between markers:
Gotchas
3. Import Path Conversion
- Tool auto-converts:
org/perlonjava/operators/...→org.perlonjava.operators.... - Works correctly for all Java imports
4. BytecodeCompiler Not Automated
- Tool can't automatically add emit cases (too many variations)
- Must add manually between markers
- Tool prints list of operators needing implementation
5. Signature Mismatches
- Tool skips complex signatures silently
- Check tool output for "Skipping X" messages
- These need manual implementation
Testing Generated Opcodes
Regenerating After Changes
Manual Implementation Still Needed For
- Varargs functions: getc, printf, sprintf
- List operators: map, grep, sort, push, pop
- Hash operators: keys, values, each
- Array operators: splice (complex signature)
- Special forms: defined, wantarray (already manual)
Adding New Operators
1. Decide: Fast Opcode or slow opcode?
Use Fast Opcode when:
- Operation is used frequently (>1% of execution)
- Simple 1-3 operand format
- Performance-critical (loops, arithmetic)
Use slow opcode when:
- Operation is rarely used (<1% of execution)
- Complex argument handling
- System calls, I/O operations
2. Adding a Fast Opcode
Example: Unary + operator (forces numeric/scalar context)
Step 2.1: Define in Opcodes.java
Critical: Keep opcodes contiguous! No gaps allowed.
Step 2.2: Implement in BytecodeInterpreter.java
Step 2.3: Emit in BytecodeCompiler.java
Step 2.4: Add Disassembly (InterpretedCode.java)
WARNING: Missing disassembly cases cause PC misalignment! When disassembler hits unknown opcode, it doesn't advance PC for operands, corrupting all subsequent instructions.
3. Adding STORE_GLOBAL_* Opcodes
Example: STORE_GLOBAL_ARRAY (13), STORE_GLOBAL_HASH (15)
These opcodes already existed but lacked interpreter/disassembly support.
Step 3.1: Implement Runtime in BytecodeInterpreter.java
Key Insight: Match compiler semantics exactly. Check compiler's EmitterVisitor or runtime methods to understand expected behavior.
Step 3.2: Add Disassembly
4. Lvalue Subroutine Assignment
Perl Feature: f() = "X" where f returns mutable reference
Parse Structure:
Implementation in BytecodeCompiler.java:
How It Works:
- Lvalue subroutines return RuntimeBaseProxy (extends RuntimeScalar)
- RuntimeBaseProxy has
lvaluefield pointing to actual mutable location - SET_SCALAR calls
.set()on the proxy, which delegates to the lvalue - Example:
substr($x,0,1)returns proxy to first character of $x
5. Testing New Operators
Must see `tableswitch`, not `lookupswitch`!
Example output showing tableswitch:
If you see `lookupswitch` instead, you've introduced gaps in opcode numbering!
Critical Lessons Learned
1. Disassembly is NOT Optional
- Missing disassembly cases cause PC misalignment
- All subsequent bytecode appears corrupted
- Manifests as "Index N out of bounds" or "Unknown opcode"
- Always add disassembly case when adding opcode
2. Match Compiler Semantics Exactly
- Check EmitterVisitor or runtime methods
- Don't guess - read the code
- Example:
local $xmust callmakeLocal(), not just assign
3. Never Hide Problems
- Null checks can mask real bugs
- If registers[N] is null, find why it wasn't initialized
- Don't paper over the issue
4. Opcode Contiguity is Performance-Critical
- JVM uses tableswitch (O(1)) for dense opcodes
- Gaps cause lookupswitch (O(log n)) - 10-15% slowdown
- Always use next sequential number
5. Error Messages Must Include Context
- Use
throwCompilerException(message, tokenIndex) - Shows filename, line number, and code snippet
- Makes debugging 10x easier
Common Pitfalls
1. Forgetting PC Increment:
2. Opcode Gaps:
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingdev/custom_bytecode/SKILL.md
Popularity
- Repository stars
- 53
- Repository forks
- 4
- 7-day trend score
- 20
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 19 Mar 2026, 7 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/fglock/perlonjava/custom-bytecode)