When code is quickened we have two copies of the bytecode instructions.
code->co_code (a bytearray object)
code->co_firstinstr (an array of instructions).
Apart from its length (which would be trivial to add to the code object), code->co_code can be reconstructed from code->co_firstinstr.
I propose changing the code object as follows:
Current:
...
_Py_CODEUNIT *co_firstinstr;
...
PyObject *co_code; /* Points to bytes object */
...
union _cache_or_instruction *co_quickened;
}
Proposed:
int code_length;
...
/* Remove pointer to first instruction */
...
PyObject *co_code; /* Initially NULL, lazily initialized */
...
_Py_CODEUNIT co_instructions[1];
}
Apart from the obvious memory saving, this also allows us to streamline the interpreter a bit.
Pseudo code for generating co_code:
co_code = bytearray(len=co.code_length*2)
index = 0
for inst in co.co_instructions:
opcode = OPCODE_TO_BASE_MAP[inst.opcode]
if has_specialized_cache(inst.opcode):
oparg = get_specialized_cache(inst).original_oparg
elif has_oparg(inst.opcode):
oparg = inst.oparg
else:
oparg = 0
co_code[index] = opcode
co_code[index+1] = oparg
index += 1
return bytes(co_code)
When code is quickened we have two copies of the bytecode instructions.
code->co_code(a bytearray object)code->co_firstinstr(an array of instructions).Apart from its length (which would be trivial to add to the code object),
code->co_codecan be reconstructed fromcode->co_firstinstr.I propose changing the code object as follows:
Current:
... _Py_CODEUNIT *co_firstinstr; ... PyObject *co_code; /* Points to bytes object */ ... union _cache_or_instruction *co_quickened; }Proposed:
Apart from the obvious memory saving, this also allows us to streamline the interpreter a bit.
Pseudo code for generating
co_code: