Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
36a7d56
gh-105481: add haslocal to _opcode.py. Generate most oplists in opcod…
iritkatriel Jul 14, 2023
75019ca
added HAS_FREE flag. Removed HAS_FREE from the HAS_LOCAL list
iritkatriel Jul 14, 2023
494680a
add hasjump, soft deprecate hasjrel and hasjabs. Fix build bug
iritkatriel Jul 15, 2023
24786fb
generate hasexc from the C macros instead of defining it again
iritkatriel Jul 15, 2023
563b431
typo
iritkatriel Jul 15, 2023
227756f
Merge remote-tracking branch 'upstream/main' into opcode_py
iritkatriel Jul 17, 2023
1b13b96
ascii quotes
iritkatriel Jul 17, 2023
0f5ae32
make clinic
iritkatriel Jul 17, 2023
289fad1
remove oplists
iritkatriel Jul 17, 2023
598ba2a
import less from opcode in build script. _opcode.has_* don't exist pr…
iritkatriel Jul 17, 2023
d2d355e
hascompare in old versions as well
iritkatriel Jul 17, 2023
27eb2cc
remove redundant init
iritkatriel Jul 17, 2023
998024a
📜🤖 Added by blurb_it.
blurb-it[bot] Jul 17, 2023
ffa4ee3
Merge branch 'main' into opcode_py
iritkatriel Jul 17, 2023
ce76a60
remove unnecessary comment
iritkatriel Jul 17, 2023
4194a12
make the tests more precise
iritkatriel Jul 17, 2023
6b8c46b
sort the lists
iritkatriel Jul 17, 2023
e15f7d5
address some of the code review comments
iritkatriel Jul 18, 2023
21fe0a4
remove hard-coded lists from the tests
iritkatriel Jul 18, 2023
75795db
remove opcode metadata file from generated files list so diff are vis…
iritkatriel Jul 18, 2023
1a0a2b2
import _opcode directly into dis, rather than via opcode
iritkatriel Jul 18, 2023
f530469
add stack effect to dis.__all__
iritkatriel Jul 18, 2023
71ffe34
Merge branch 'main' into opcode_py
iritkatriel Jul 18, 2023
d525c53
import _specializations, _specialized_instructions directly from _opc…
iritkatriel Jul 18, 2023
5e1c4a4
update Tools/scripts/summarize_stats.py to use _opcode_metadata inste…
iritkatriel Jul 18, 2023
6077263
Revert "update Tools/scripts/summarize_stats.py to use _opcode_metada…
iritkatriel Jul 18, 2023
98ba11f
Revert "import _specializations, _specialized_instructions directly f…
iritkatriel Jul 18, 2023
cc8e5e1
Revert "add stack effect to dis.__all__"
iritkatriel Jul 18, 2023
31dbfec
Revert "import _opcode directly into dis, rather than via opcode"
iritkatriel Jul 18, 2023
1abf6ca
Merge branch 'main' into opcode_py
iritkatriel Jul 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
make the tests more precise
  • Loading branch information
iritkatriel committed Jul 17, 2023
commit 4194a127c7ab2d469b1aa2280fde1ef0940d8f71
135 changes: 81 additions & 54 deletions Lib/test/test__opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,70 @@
from _opcode import stack_effect


class OpcodeTests(unittest.TestCase):

def check_bool_function_result(self, func, ops, expected):
for op in ops:
if isinstance(op, str):
op = dis.opmap[op]
with self.subTest(opcode=op, func=func):
self.assertIsInstance(func(op), bool)
self.assertEqual(func(op), expected)

EXPECTED_OPLISTS = {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this mean that each time we add or remove an opcode we need to update this test? That feels tedious. (Even though it looks like it was always done like this and you're just refactoring that code a bit.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In the past the list was built in opcode.py so we would need to make sure we added i there. Now the lists are derived automatically from bytecodes.c via heuristics, and you will need to update the test (which you are less likely to be able to do incorrectly). I'm not sure it's safe to not have these full tests.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hm. So the point of the heuristics is that we don't have to manually maintain it, but the tests are manually maintained to check that the heuristics are still working. If they are, and if an instruction is added (or deleted) that changes one of these lists, we'll get a bogus test failure, and hopefully the contributor who gets to debug the test failure understands they have to fix the test. I think this warrants a comment right there where we expect the test to fail (I think on line 102, self.assertEqual(res, op in expected)).

Copy link
Copy Markdown
Member Author

@iritkatriel iritkatriel Jul 18, 2023

Choose a reason for hiding this comment

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

The manually maintained lists were not tested and had some errors. We could remove the tests and trust people to examine the diff in the metadata file when they make changes. If we do that we should remove it from the autogenerated list so that it's not collapsed in a PR diff.

'HAS_ARG': [
'BINARY_OP', 'BUILD_CONST_KEY_MAP', 'BUILD_LIST', 'BUILD_MAP',
'BUILD_SET', 'BUILD_SLICE', 'BUILD_STRING', 'BUILD_TUPLE', 'CALL',
'CALL_FUNCTION_EX', 'CALL_INTRINSIC_1', 'CALL_INTRINSIC_2', 'COMPARE_OP',
'CONTAINS_OP', 'CONVERT_VALUE', 'COPY', 'COPY_FREE_VARS', 'DELETE_ATTR',
'DELETE_DEREF', 'DELETE_FAST', 'DELETE_GLOBAL', 'DELETE_NAME',
'DICT_MERGE', 'DICT_UPDATE', 'ENTER_EXECUTOR', 'EXTENDED_ARG',
'FOR_ITER', 'GET_AWAITABLE', 'IMPORT_FROM', 'IMPORT_NAME',
'INSTRUMENTED_CALL', 'INSTRUMENTED_FOR_ITER',
'INSTRUMENTED_JUMP_BACKWARD', 'INSTRUMENTED_JUMP_FORWARD',
'INSTRUMENTED_LOAD_SUPER_ATTR', 'INSTRUMENTED_POP_JUMP_IF_FALSE',
'INSTRUMENTED_POP_JUMP_IF_NONE', 'INSTRUMENTED_POP_JUMP_IF_NOT_NONE',
'INSTRUMENTED_POP_JUMP_IF_TRUE', 'INSTRUMENTED_RESUME',
'INSTRUMENTED_RETURN_CONST', 'INSTRUMENTED_YIELD_VALUE', 'IS_OP', 'JUMP',
'JUMP_BACKWARD', 'JUMP_BACKWARD_NO_INTERRUPT', 'JUMP_FORWARD',
'JUMP_NO_INTERRUPT', 'KW_NAMES', 'LIST_APPEND', 'LIST_EXTEND',
'LOAD_ATTR', 'LOAD_CLOSURE', 'LOAD_CONST', 'LOAD_DEREF', 'LOAD_FAST',
'LOAD_FAST_AND_CLEAR', 'LOAD_FAST_CHECK', 'LOAD_FAST_LOAD_FAST',
'LOAD_FROM_DICT_OR_DEREF', 'LOAD_FROM_DICT_OR_GLOBALS', 'LOAD_GLOBAL',
'LOAD_METHOD', 'LOAD_NAME', 'LOAD_SUPER_ATTR', 'LOAD_SUPER_METHOD',
'LOAD_ZERO_SUPER_ATTR', 'LOAD_ZERO_SUPER_METHOD', 'MAKE_CELL', 'MAP_ADD',
'MATCH_CLASS', 'POP_JUMP_IF_FALSE', 'POP_JUMP_IF_NONE',
'POP_JUMP_IF_NOT_NONE', 'POP_JUMP_IF_TRUE', 'RAISE_VARARGS', 'RERAISE',
'RESUME', 'RETURN_CONST', 'SEND', 'SET_ADD', 'SET_FUNCTION_ATTRIBUTE',
'SET_UPDATE', 'STORE_ATTR', 'STORE_DEREF', 'STORE_FAST',
'STORE_FAST_LOAD_FAST', 'STORE_FAST_MAYBE_NULL', 'STORE_FAST_STORE_FAST',
'STORE_GLOBAL', 'STORE_NAME', 'SWAP', 'UNPACK_EX', 'UNPACK_SEQUENCE',
'YIELD_VALUE'],

'HAS_CONST': [
'LOAD_CONST', 'RETURN_CONST', 'KW_NAMES', 'INSTRUMENTED_RETURN_CONST'],

'HAS_NAME': [
'STORE_NAME', 'DELETE_NAME', 'STORE_ATTR', 'DELETE_ATTR', 'STORE_GLOBAL',
'DELETE_GLOBAL', 'LOAD_NAME', 'LOAD_ATTR', 'IMPORT_NAME', 'IMPORT_FROM',
'LOAD_GLOBAL', 'LOAD_SUPER_ATTR', 'LOAD_FROM_DICT_OR_GLOBALS',
'LOAD_METHOD', 'LOAD_SUPER_METHOD', 'LOAD_ZERO_SUPER_METHOD',
'LOAD_ZERO_SUPER_ATTR'],

'HAS_CONST': [
'LOAD_CONST', 'RETURN_CONST', 'KW_NAMES', 'INSTRUMENTED_RETURN_CONST'],

'HAS_JUMP': [
'FOR_ITER', 'JUMP_FORWARD', 'POP_JUMP_IF_FALSE', 'POP_JUMP_IF_TRUE',
'SEND', 'POP_JUMP_IF_NOT_NONE', 'POP_JUMP_IF_NONE',
'JUMP_BACKWARD_NO_INTERRUPT', 'JUMP_BACKWARD', 'ENTER_EXECUTOR', 'JUMP',
'JUMP_NO_INTERRUPT'],

'HAS_FREE': [
'MAKE_CELL', 'LOAD_DEREF', 'STORE_DEREF', 'DELETE_DEREF',
'LOAD_FROM_DICT_OR_DEREF'],

'HAS_LOCAL': [
'LOAD_FAST', 'STORE_FAST', 'DELETE_FAST', 'LOAD_FAST_CHECK',
'LOAD_FAST_AND_CLEAR', 'LOAD_FAST_LOAD_FAST', 'STORE_FAST_LOAD_FAST',
'STORE_FAST_STORE_FAST', 'STORE_FAST_MAYBE_NULL', 'LOAD_CLOSURE'],

'HAS_EXC': ['SETUP_FINALLY', 'SETUP_CLEANUP', 'SETUP_WITH'],

'HAS_COMPARE': ['COMPARE_OP'],
}

class OpListTests(unittest.TestCase):
def test_invalid_opcodes(self):
invalid = [-100, -1, 255, 512, 513, 1000]
self.check_bool_function_result(_opcode.is_valid, invalid, False)
Expand All @@ -39,51 +93,24 @@ def test_is_valid(self):
opcodes = [dis.opmap[opname] for opname in names]
self.check_bool_function_result(_opcode.is_valid, opcodes, True)

def test_has_arg(self):
has_arg = ['SWAP', 'LOAD_FAST', 'INSTRUMENTED_POP_JUMP_IF_TRUE', 'JUMP']
no_arg = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
self.check_bool_function_result(_opcode.has_arg, has_arg, True)
self.check_bool_function_result(_opcode.has_arg, no_arg, False)

def test_has_const(self):
has_const = ['LOAD_CONST', 'RETURN_CONST', 'KW_NAMES']
no_const = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
self.check_bool_function_result(_opcode.has_const, has_const, True)
self.check_bool_function_result(_opcode.has_const, no_const, False)

def test_has_name(self):
has_name = ['STORE_NAME', 'DELETE_ATTR', 'STORE_GLOBAL', 'IMPORT_FROM',
'LOAD_FROM_DICT_OR_GLOBALS']
no_name = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
self.check_bool_function_result(_opcode.has_name, has_name, True)
self.check_bool_function_result(_opcode.has_name, no_name, False)

def test_has_jump(self):
has_jump = ['FOR_ITER', 'JUMP_FORWARD', 'JUMP', 'POP_JUMP_IF_TRUE', 'SEND']
no_jump = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
self.check_bool_function_result(_opcode.has_jump, has_jump, True)
self.check_bool_function_result(_opcode.has_jump, no_jump, False)

def test_has_free(self):
has_free = ['MAKE_CELL', 'LOAD_DEREF', 'STORE_DEREF', 'DELETE_DEREF',
'LOAD_FROM_DICT_OR_DEREF']
no_free = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
self.check_bool_function_result(_opcode.has_free, has_free, True)
self.check_bool_function_result(_opcode.has_free, no_free, False)

def test_has_local(self):
has_local = ['LOAD_FAST', 'LOAD_FAST_CHECK', 'LOAD_FAST_AND_CLEAR',
'STORE_FAST_MAYBE_NULL', 'LOAD_CLOSURE']
no_local = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
self.check_bool_function_result(_opcode.has_local, has_local, True)
self.check_bool_function_result(_opcode.has_local, no_local, False)

def test_has_exc(self):
has_exc = ['SETUP_FINALLY', 'SETUP_WITH', 'SETUP_CLEANUP']
no_exc = ['DELETE_DEREF', 'POP_TOP', 'NOP', 'CACHE']
self.check_bool_function_result(_opcode.has_exc, has_exc, True)
self.check_bool_function_result(_opcode.has_exc, no_exc, False)
def test_oplists(self):
def check_function(self, func, expected):
for op in [-10, 520]:
with self.subTest(opcode=op, func=func):
res = func(op)
self.assertIsInstance(res, bool)
self.assertEqual(res, op in expected)

check_function(self, _opcode.has_arg, EXPECTED_OPLISTS['HAS_ARG'])
check_function(self, _opcode.has_const, EXPECTED_OPLISTS['HAS_CONST'])
check_function(self, _opcode.has_name, EXPECTED_OPLISTS['HAS_NAME'])
check_function(self, _opcode.has_jump, EXPECTED_OPLISTS['HAS_JUMP'])
check_function(self, _opcode.has_free, EXPECTED_OPLISTS['HAS_FREE'])
check_function(self, _opcode.has_local, EXPECTED_OPLISTS['HAS_LOCAL'])
check_function(self, _opcode.has_exc, EXPECTED_OPLISTS['HAS_EXC'])


class OpListTests(unittest.TestCase):
def test_stack_effect(self):
self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1)
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
Expand Down