[3.10] bpo-45056: Remove trailing unused constants from co_consts (GH-28109) (GH-28125)

(cherry picked from commit 55c4a92fc1abfe388335071f1d64b3addfa5793f)

Co-authored-by: Inada Naoki <songofacandy@gmail.com>
diff --git a/Python/compile.c b/Python/compile.c
index baea494..b007859 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -6986,6 +6986,9 @@ normalize_basic_block(basicblock *bb);
 static int
 optimize_cfg(struct compiler *c, struct assembler *a, PyObject *consts);
 
+static int
+trim_unused_consts(struct compiler *c, struct assembler *a, PyObject *consts);
+
 /* Duplicates exit BBs, so that line numbers can be propagated to them */
 static int
 duplicate_exits_without_lineno(struct compiler *c);
@@ -7127,6 +7130,9 @@ assemble(struct compiler *c, int addNone)
     if (duplicate_exits_without_lineno(c)) {
         return NULL;
     }
+    if (trim_unused_consts(c, &a, consts)) {
+        goto error;
+    }
     propagate_line_numbers(&a);
     guarantee_lineno_for_exits(&a, c->u->u_firstlineno);
     /* Can't modify the bytecode after computing jump offsets. */
@@ -7809,6 +7815,33 @@ optimize_cfg(struct compiler *c, struct assembler *a, PyObject *consts)
     return 0;
 }
 
+// Remove trailing unused constants.
+static int
+trim_unused_consts(struct compiler *c, struct assembler *a, PyObject *consts)
+{
+    assert(PyList_CheckExact(consts));
+
+    // The first constant may be docstring; keep it always.
+    int max_const_index = 0;
+    for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
+        for (int i = 0; i < b->b_iused; i++) {
+            if (b->b_instr[i].i_opcode == LOAD_CONST &&
+                    b->b_instr[i].i_oparg > max_const_index) {
+                max_const_index = b->b_instr[i].i_oparg;
+            }
+        }
+    }
+    if (max_const_index+1 < PyList_GET_SIZE(consts)) {
+        //fprintf(stderr, "removing trailing consts: max=%d, size=%d\n",
+        //        max_const_index, (int)PyList_GET_SIZE(consts));
+        if (PyList_SetSlice(consts, max_const_index+1,
+                            PyList_GET_SIZE(consts), NULL) < 0) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
 static inline int
 is_exit_without_lineno(basicblock *b) {
     return b->b_exit && b->b_instr[0].i_lineno < 0;