This guide explains how to add support for new intrinsic functions in KMIR. Intrinsics are compiler built-in functions that don't have regular MIR bodies and require special semantic rules.
Intrinsics use optimized execution:
- Direct Execution:
#execIntrinsic(IntrinsicFunction(symbol("name")), ARGS, DEST)bypasses regular function call setup - Pattern Matching: Rules match on specific operand patterns for each intrinsic
- Operand Evaluation: Each intrinsic rule handles its own operand evaluation as needed
See implementation in kmir.md
Create a Rust test file in kmir/src/tests/integration/data/exec-smir/intrinsic/ that uses your intrinsic and verifies its behavior with assertions.
Add entry to EXEC_DATA in test_integration.py:
(
'your_intrinsic',
EXEC_DATA_DIR / 'intrinsic' / 'your_intrinsic.smir.json',
EXEC_DATA_DIR / 'intrinsic' / 'your_intrinsic.state',
65, # Start with small depth, increase if needed
),# Generate initial state showing where execution gets stuck
make test-integration TEST_ARGS="-k 'exec_smir and your_intrinsic' --update-expected-output"
# This will create your_intrinsic.state showing execution stuck at:
# #execIntrinsic(IntrinsicFunction(symbol("your_intrinsic")), ARGS, DEST)
# Save this for comparison
cp kmir/src/tests/integration/data/exec-smir/intrinsic/your_intrinsic.state \
your_intrinsic.state.initialAdd rules to kmir.md.
To find implementation patterns for your intrinsic:
# Search for existing intrinsic implementations
grep -A10 "#execIntrinsic(IntrinsicFunction" kmir/src/kmir/kdist/mir-semantics/kmir.md
# Look for helper functions and evaluation patterns
grep -B2 -A5 "seqstrict" kmir/src/kmir/kdist/mir-semantics/kmir.mdStudy existing intrinsics like black_box (simple value operation) and raw_eq (reference dereferencing with helper functions) as reference implementations.
Document your intrinsic in the K semantics file with its implementation.
# Rebuild K semantics
make build
# Run test again and update the state with working implementation
make test-integration TEST_ARGS="-k 'exec_smir and your_intrinsic' --update-expected-output"
# Compare to see the progress
diff your_intrinsic.state.initial \
kmir/src/tests/integration/data/exec-smir/intrinsic/your_intrinsic.state# Test with both LLVM and Haskell backends
make test-integration TEST_ARGS="-k 'exec_smir and your_intrinsic'"
# Both should pass with consistent results
# If not, you may need backend-specific state filesFor implementation examples, see existing intrinsics in kmir.md.
Use when the intrinsic needs to evaluate its operands directly.
Use the #withDeref helper function to add dereferencing to operands.
Use [seqstrict(2,3)] attribute to automatically evaluate operand arguments.
Match specific operand patterns directly in the #execIntrinsic rule.
- Start Simple: Test with primitive types first
- Save Initial State: Keep the stuck state for comparison
- Use Correct Test Filter: Always use
exec_smir and your_intrinsicto ensure correct test runs - Check Both Backends: Ensure LLVM and Haskell produce same results
- Document Limitations: Note what cases aren't handled yet
- Create Issue for Future Work: Track enhancements needed (like #666 for
raw_eq)
# See where execution is stuck with verbose output
make test-integration TEST_ARGS="-k 'exec_smir and your_intrinsic' -vv"
# Look for the K cell content to see what values are presentThe state file shows the complete execution state. Key sections to check:
<k>: Shows current execution point<locals>: Shows local variable values<functions>: Should containIntrinsicFunction(symbol("your_intrinsic"))
# Check SMIR JSON to confirm intrinsic is recognized
cat kmir/src/tests/integration/data/exec-smir/intrinsic/your_intrinsic.smir.json | grep -A5 your_intrinsicSolution: Use -k 'exec_smir and your_intrinsic' to ensure test_exec_smir runs, not other tests.
Solution: The intrinsic should be automatically recognized if it appears in SMIR. Check the SMIR JSON to confirm.
Solution: Your rule pattern doesn't match. Check:
- The exact intrinsic name in the symbol
- Value types on K cell (use
ListItem(VAL:Value)to match any value) - Number of arguments expected
Solution: Use helper functions to separate evaluation stages, avoid calling #execIntrinsic within itself.
Solution:
- Increase execution depth if needed
- Check for backend-specific evaluation order issues
- May need backend-specific expected files (
.llvm.state,.haskell.state)
Solution:
- Start with smaller depth (e.g., 65 instead of 1000)
- Optimize your rule to avoid unnecessary computation
- Check for infinite loops in your implementation
The current architecture passes operands directly to intrinsic rules:
- Direct Access: Rules receive operands directly in
#execIntrinsic - Custom Evaluation: Each intrinsic controls its own operand evaluation
- Better Indexing: K can better index rules with explicit operand patterns
Always use a helper function when:
- You need automatic operand evaluation (with
seqstrict) - The logic is complex enough to benefit from separation
- You want to transform operands before evaluation (like with
#withDeref)
- Write the test with expected behavior first
- Generate initial state to see where it gets stuck
- Implement the minimal rule needed
- Update state to verify progress
- Iterate to handle edge cases
- Document limitations for future work
- Recent intrinsic PRs in repository history
- Rust Intrinsics Documentation