|
| 1 | +package org.perlonjava.codegen; |
| 2 | + |
| 3 | +import org.perlonjava.astnode.*; |
| 4 | +import org.perlonjava.astvisitor.ControlFlowDetectorVisitor; |
| 5 | +import org.perlonjava.astvisitor.EmitterVisitor; |
| 6 | + |
| 7 | +import java.util.ArrayList; |
| 8 | +import java.util.List; |
| 9 | + |
| 10 | +/** |
| 11 | + * Helper class for refactoring large blocks to avoid JVM's "Method too large" error. |
| 12 | + * |
| 13 | + * This class encapsulates all logic for detecting and transforming large blocks, |
| 14 | + * including smart chunking strategies and control flow analysis. |
| 15 | + */ |
| 16 | +public class LargeBlockRefactorer { |
| 17 | + |
| 18 | + // Configuration thresholds |
| 19 | + private static final int LARGE_BLOCK_ELEMENT_COUNT = 8; // Lowered from 16 for more aggressive refactoring |
| 20 | + private static final int LARGE_BYTECODE_SIZE = 30000; |
| 21 | + private static final int MIN_CHUNK_SIZE = 4; // Minimum statements to extract as a chunk |
| 22 | + |
| 23 | + // Reusable visitor for control flow detection |
| 24 | + private static final ControlFlowDetectorVisitor controlFlowDetector = new ControlFlowDetectorVisitor(); |
| 25 | + |
| 26 | + /** |
| 27 | + * Process a block and refactor it if necessary to avoid method size limits. |
| 28 | + * |
| 29 | + * @param emitterVisitor The emitter visitor context |
| 30 | + * @param node The block to process |
| 31 | + * @return true if the block was refactored and emitted, false if no refactoring was needed |
| 32 | + */ |
| 33 | + public static boolean processBlock(EmitterVisitor emitterVisitor, BlockNode node) { |
| 34 | + // CRITICAL: Skip if this block was already refactored to prevent infinite recursion |
| 35 | + if (node.getBooleanAnnotation("blockAlreadyRefactored")) { |
| 36 | + return false; |
| 37 | + } |
| 38 | + |
| 39 | + // Check if refactoring is enabled via environment variable |
| 40 | + String largeCodeMode = System.getenv("JPERL_LARGECODE"); |
| 41 | + boolean refactorEnabled = "refactor".equals(largeCodeMode); |
| 42 | + |
| 43 | + // Skip if block is already a subroutine or is a special block |
| 44 | + if (node.getBooleanAnnotation("blockIsSubroutine")) { |
| 45 | + return false; |
| 46 | + } |
| 47 | + |
| 48 | + // Determine if we need to refactor |
| 49 | + boolean needsRefactoring = shouldRefactorBlock(node, emitterVisitor, refactorEnabled); |
| 50 | + |
| 51 | + if (!needsRefactoring) { |
| 52 | + return false; |
| 53 | + } |
| 54 | + |
| 55 | + // Skip refactoring for special blocks (BEGIN, END, INIT, CHECK, UNITCHECK) |
| 56 | + // These blocks have special compilation semantics and cannot be refactored |
| 57 | + if (isSpecialContext(node)) { |
| 58 | + return false; |
| 59 | + } |
| 60 | + |
| 61 | + // TEMPORARILY DISABLED: Smart chunking has timing issues with special blocks (BEGIN/require) |
| 62 | + // Causes NPE in SpecialBlockParser when functions aren't defined yet during compilation |
| 63 | + // if (trySmartChunking(node)) { |
| 64 | + // // Block was successfully chunked, continue with normal emission |
| 65 | + // return false; |
| 66 | + // } |
| 67 | + |
| 68 | + // Fallback: Try whole-block refactoring |
| 69 | + if (tryWholeBlockRefactoring(emitterVisitor, node)) { |
| 70 | + return true; // Block was refactored and emitted |
| 71 | + } |
| 72 | + |
| 73 | + // No refactoring was possible |
| 74 | + return false; |
| 75 | + } |
| 76 | + |
| 77 | + /** |
| 78 | + * Determine if a block should be refactored based on size and context. |
| 79 | + */ |
| 80 | + private static boolean shouldRefactorBlock(BlockNode node, EmitterVisitor emitterVisitor, boolean refactorEnabled) { |
| 81 | + // Check element count threshold |
| 82 | + if (node.elements.size() <= LARGE_BLOCK_ELEMENT_COUNT) { |
| 83 | + return false; |
| 84 | + } |
| 85 | + |
| 86 | + // Check if we're in a context that allows refactoring |
| 87 | + return refactorEnabled || !emitterVisitor.ctx.javaClassInfo.gotoLabelStack.isEmpty(); |
| 88 | + } |
| 89 | + |
| 90 | + /** |
| 91 | + * Check if the block is in a special context where smart chunking should be avoided. |
| 92 | + */ |
| 93 | + private static boolean isSpecialContext(BlockNode node) { |
| 94 | + return node.getBooleanAnnotation("blockIsSpecial") || |
| 95 | + node.getBooleanAnnotation("blockIsBegin") || |
| 96 | + node.getBooleanAnnotation("blockIsRequire") || |
| 97 | + node.getBooleanAnnotation("blockIsInit"); |
| 98 | + } |
| 99 | + |
| 100 | + /** |
| 101 | + * Try to apply smart chunking to reduce the number of top-level elements. |
| 102 | + * |
| 103 | + * @param node The block to chunk |
| 104 | + * @return true if chunking was successful, false otherwise |
| 105 | + */ |
| 106 | + private static boolean trySmartChunking(BlockNode node) { |
| 107 | + List<Node> processedElements = new ArrayList<>(); |
| 108 | + List<Node> currentChunk = new ArrayList<>(); |
| 109 | + |
| 110 | + for (Node element : node.elements) { |
| 111 | + if (shouldBreakChunk(element)) { |
| 112 | + // This element cannot be in a chunk |
| 113 | + processChunk(currentChunk, processedElements, node.tokenIndex); |
| 114 | + currentChunk.clear(); |
| 115 | + |
| 116 | + // Add the unsafe element directly |
| 117 | + processedElements.add(element); |
| 118 | + } else if (isCompleteBlock(element)) { |
| 119 | + // Complete blocks are already scoped |
| 120 | + processChunk(currentChunk, processedElements, node.tokenIndex); |
| 121 | + currentChunk.clear(); |
| 122 | + processedElements.add(element); |
| 123 | + } else { |
| 124 | + // Safe element, add to current chunk |
| 125 | + currentChunk.add(element); |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + // Process any remaining chunk |
| 130 | + processChunk(currentChunk, processedElements, node.tokenIndex); |
| 131 | + |
| 132 | + // Apply chunking if we reduced the element count |
| 133 | + if (processedElements.size() < node.elements.size()) { |
| 134 | + node.elements.clear(); |
| 135 | + node.elements.addAll(processedElements); |
| 136 | + return true; |
| 137 | + } |
| 138 | + |
| 139 | + return false; |
| 140 | + } |
| 141 | + |
| 142 | + /** |
| 143 | + * Determine if an element should break the current chunk. |
| 144 | + */ |
| 145 | + private static boolean shouldBreakChunk(Node element) { |
| 146 | + // Labels break chunks |
| 147 | + if (element instanceof LabelNode) { |
| 148 | + return true; |
| 149 | + } |
| 150 | + |
| 151 | + // Control flow statements break chunks |
| 152 | + controlFlowDetector.reset(); |
| 153 | + element.accept(controlFlowDetector); |
| 154 | + if (controlFlowDetector.hasUnsafeControlFlow()) { |
| 155 | + return true; |
| 156 | + } |
| 157 | + |
| 158 | + // Top-level variable declarations break chunks (unless in a block) |
| 159 | + if (!isCompleteBlock(element) && hasVariableDeclaration(element)) { |
| 160 | + return true; |
| 161 | + } |
| 162 | + |
| 163 | + return false; |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * Process accumulated chunk statements. |
| 168 | + */ |
| 169 | + private static void processChunk(List<Node> chunk, List<Node> processedElements, int tokenIndex) { |
| 170 | + if (chunk.isEmpty()) { |
| 171 | + return; |
| 172 | + } |
| 173 | + |
| 174 | + if (chunk.size() >= MIN_CHUNK_SIZE) { |
| 175 | + // Create a closure for this chunk: sub { ... }->() |
| 176 | + BlockNode chunkBlock = new BlockNode(new ArrayList<>(chunk), tokenIndex); |
| 177 | + BinaryOperatorNode closure = new BinaryOperatorNode( |
| 178 | + "->", |
| 179 | + new SubroutineNode(null, null, null, chunkBlock, false, tokenIndex), |
| 180 | + new ListNode(tokenIndex), // Empty args - closures capture outer scope |
| 181 | + tokenIndex |
| 182 | + ); |
| 183 | + processedElements.add(closure); |
| 184 | + } else { |
| 185 | + // Chunk too small, add elements directly |
| 186 | + processedElements.addAll(chunk); |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + /** |
| 191 | + * Try to refactor the entire block as a subroutine. |
| 192 | + */ |
| 193 | + private static boolean tryWholeBlockRefactoring(EmitterVisitor emitterVisitor, BlockNode node) { |
| 194 | + // Check for unsafe control flow |
| 195 | + controlFlowDetector.reset(); |
| 196 | + node.accept(controlFlowDetector); |
| 197 | + if (controlFlowDetector.hasUnsafeControlFlow()) { |
| 198 | + return false; |
| 199 | + } |
| 200 | + |
| 201 | + // Create sub {...}->(@_) for whole block |
| 202 | + int index = node.tokenIndex; |
| 203 | + ListNode args = new ListNode(index); |
| 204 | + args.elements.add(new OperatorNode("@", new IdentifierNode("_", index), index)); |
| 205 | + |
| 206 | + // IMPORTANT: Mark the original block as already refactored to prevent recursion |
| 207 | + node.setAnnotation("blockAlreadyRefactored", true); |
| 208 | + |
| 209 | + // Create a wrapper block containing the original block |
| 210 | + BlockNode innerBlock = new BlockNode(List.of(node), index); |
| 211 | + |
| 212 | + BinaryOperatorNode subr = new BinaryOperatorNode( |
| 213 | + "->", |
| 214 | + new SubroutineNode( |
| 215 | + null, null, null, |
| 216 | + innerBlock, |
| 217 | + false, |
| 218 | + index |
| 219 | + ), |
| 220 | + args, |
| 221 | + index |
| 222 | + ); |
| 223 | + |
| 224 | + // Emit the refactored block |
| 225 | + subr.accept(emitterVisitor); |
| 226 | + return true; |
| 227 | + } |
| 228 | + |
| 229 | + /** |
| 230 | + * Check if a node contains variable declarations (my, our, local). |
| 231 | + */ |
| 232 | + private static boolean hasVariableDeclaration(Node node) { |
| 233 | + // Pattern 1: Direct declaration without assignment |
| 234 | + if (node instanceof OperatorNode) { |
| 235 | + OperatorNode op = (OperatorNode) node; |
| 236 | + return "my".equals(op.operator) || "our".equals(op.operator) || "local".equals(op.operator); |
| 237 | + } |
| 238 | + |
| 239 | + // Pattern 2: Declaration with assignment |
| 240 | + if (node instanceof BinaryOperatorNode) { |
| 241 | + BinaryOperatorNode bin = (BinaryOperatorNode) node; |
| 242 | + if ("=".equals(bin.operator) && bin.left instanceof OperatorNode) { |
| 243 | + OperatorNode left = (OperatorNode) bin.left; |
| 244 | + return "my".equals(left.operator) || "our".equals(left.operator) || "local".equals(left.operator); |
| 245 | + } |
| 246 | + } |
| 247 | + |
| 248 | + return false; |
| 249 | + } |
| 250 | + |
| 251 | + /** |
| 252 | + * Check if a node is a complete block/loop with its own scope. |
| 253 | + */ |
| 254 | + private static boolean isCompleteBlock(Node node) { |
| 255 | + return node instanceof BlockNode || |
| 256 | + node instanceof For1Node || |
| 257 | + node instanceof For3Node || |
| 258 | + node instanceof IfNode || |
| 259 | + node instanceof TryNode; |
| 260 | + } |
| 261 | +} |
0 commit comments