-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathInterpretedCode.java
More file actions
345 lines (314 loc) · 15.2 KB
/
InterpretedCode.java
File metadata and controls
345 lines (314 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package org.perlonjava.backend.bytecode;
import org.perlonjava.runtime.runtimetypes.*;
import java.util.BitSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Interpreted bytecode that extends RuntimeCode.
* <p>
* This class represents Perl code that is interpreted rather than compiled to JVM bytecode.
* It is COMPLETELY INDISTINGUISHABLE from compiled RuntimeCode to the rest of the system:
* - Can be stored in global variables ($::func)
* - Can be passed as code references
* - Can capture variables (closures work both directions)
* - Can be used in method dispatch, overload, @ISA, etc.
* <p>
* The ONLY difference is the execution engine:
* - Compiled RuntimeCode uses MethodHandle to invoke JVM bytecode
* - InterpretedCode overrides apply() to dispatch to BytecodeInterpreter
*/
public class InterpretedCode extends RuntimeCode {
// Bytecode and metadata
public final int[] bytecode; // Instruction stream (opcodes + operands as ints)
public final Object[] constants; // Constant pool (RuntimeBase objects)
public final String[] stringPool; // String constants (variable names, etc.)
public final int maxRegisters; // Number of registers needed
public final RuntimeBase[] capturedVars; // Closure support (captured from outer scope)
public final Map<String, Integer> variableRegistry; // Variable name → register index (for eval STRING)
public final List<Map<String, Integer>> evalSiteRegistries; // Per-eval-site variable registries
public final List<int[]> evalSitePragmaFlags; // Per-eval-site [strictOptions, featureFlags]
// Optimization flags (set by compiler after construction)
// If false, we can skip DynamicVariableManager.getLocalLevel/popToLocalLevel calls
public boolean usesLocalization = true;
// Pre-created InterpreterFrame to avoid allocation on every call
// Created lazily on first use (after packageName/subName are set)
public volatile InterpreterState.InterpreterFrame cachedFrame;
// Lexical pragma state (for eval STRING to inherit)
public final int strictOptions; // Strict flags at compile time
public final int featureFlags; // Feature flags at compile time
public final BitSet warningFlags; // Warning flags at compile time
public final String compilePackage; // Package at compile time (for eval STRING name resolution)
// Debug information (optional)
public final String sourceName; // Source file name (for stack traces)
public final int sourceLine; // Source line number
public final TreeMap<Integer, Integer> pcToTokenIndex; // Map bytecode PC to tokenIndex for error reporting (TreeMap for floorEntry lookup)
public final ErrorMessageUtil errorUtil; // For converting token index to line numbers
/**
* Constructor for InterpretedCode.
*
* @param bytecode The bytecode instructions
* @param constants Constant pool (RuntimeBase objects)
* @param stringPool String constants (variable names, etc.)
* @param maxRegisters Number of registers needed for execution
* @param capturedVars Captured variables for closure support (may be null)
* @param sourceName Source file name for debugging
* @param sourceLine Source line number for debugging
* @param pcToTokenIndex Map from bytecode PC to AST tokenIndex for error reporting
* @param variableRegistry Variable name → register index mapping (for eval STRING)
* @param errorUtil Error message utility for line number lookup
* @param strictOptions Strict flags at compile time (for eval STRING inheritance)
* @param featureFlags Feature flags at compile time (for eval STRING inheritance)
* @param warningFlags Warning flags at compile time (for eval STRING inheritance)
*/
public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool,
int maxRegisters, RuntimeBase[] capturedVars,
String sourceName, int sourceLine,
TreeMap<Integer, Integer> pcToTokenIndex,
Map<String, Integer> variableRegistry,
ErrorMessageUtil errorUtil,
int strictOptions, int featureFlags, BitSet warningFlags) {
this(bytecode, constants, stringPool, maxRegisters, capturedVars,
sourceName, sourceLine, pcToTokenIndex, variableRegistry, errorUtil,
strictOptions, featureFlags, warningFlags, "main", null, null);
}
public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool,
int maxRegisters, RuntimeBase[] capturedVars,
String sourceName, int sourceLine,
TreeMap<Integer, Integer> pcToTokenIndex,
Map<String, Integer> variableRegistry,
ErrorMessageUtil errorUtil,
int strictOptions, int featureFlags, BitSet warningFlags,
String compilePackage) {
this(bytecode, constants, stringPool, maxRegisters, capturedVars,
sourceName, sourceLine, pcToTokenIndex, variableRegistry, errorUtil,
strictOptions, featureFlags, warningFlags, compilePackage, null, null);
}
public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool,
int maxRegisters, RuntimeBase[] capturedVars,
String sourceName, int sourceLine,
TreeMap<Integer, Integer> pcToTokenIndex,
Map<String, Integer> variableRegistry,
ErrorMessageUtil errorUtil,
int strictOptions, int featureFlags, BitSet warningFlags,
String compilePackage,
List<Map<String, Integer>> evalSiteRegistries,
List<int[]> evalSitePragmaFlags) {
super(null, new java.util.ArrayList<>());
this.bytecode = bytecode;
this.constants = constants;
this.stringPool = stringPool;
this.maxRegisters = maxRegisters;
this.capturedVars = capturedVars;
this.sourceName = sourceName;
this.sourceLine = sourceLine;
this.pcToTokenIndex = pcToTokenIndex;
this.variableRegistry = variableRegistry;
this.evalSiteRegistries = evalSiteRegistries;
this.evalSitePragmaFlags = evalSitePragmaFlags;
this.errorUtil = errorUtil;
this.strictOptions = strictOptions;
this.featureFlags = featureFlags;
this.warningFlags = warningFlags;
this.compilePackage = compilePackage;
if (this.packageName == null && compilePackage != null) {
this.packageName = compilePackage;
}
}
// Legacy constructor for backward compatibility
public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool,
int maxRegisters, RuntimeBase[] capturedVars,
String sourceName, int sourceLine,
java.util.Map<Integer, Integer> pcToTokenIndex) {
this(bytecode, constants, stringPool, maxRegisters, capturedVars,
sourceName, sourceLine,
pcToTokenIndex instanceof TreeMap ? (TreeMap<Integer, Integer>) pcToTokenIndex : new TreeMap<>(pcToTokenIndex),
null, null, 0, 0, new BitSet());
}
// Legacy constructor with variableRegistry but no errorUtil
public InterpretedCode(int[] bytecode, Object[] constants, String[] stringPool,
int maxRegisters, RuntimeBase[] capturedVars,
String sourceName, int sourceLine,
java.util.Map<Integer, Integer> pcToTokenIndex,
Map<String, Integer> variableRegistry) {
this(bytecode, constants, stringPool, maxRegisters, capturedVars,
sourceName, sourceLine,
pcToTokenIndex instanceof TreeMap ? (TreeMap<Integer, Integer>) pcToTokenIndex : new TreeMap<>(pcToTokenIndex),
variableRegistry, null, 0, 0, new BitSet());
}
/**
* Read a 32-bit integer from bytecode (stored as 1 int slot).
* With int[] storage a full int fits in a single slot.
*/
static int readInt(int[] bytecode, int pc) {
return bytecode[pc];
}
/**
* Get or create the cached InterpreterFrame for this code.
* Uses double-checked locking for thread safety with minimal overhead.
*
* @param packageName The package name (usually from this.packageName)
* @param subroutineName The subroutine name (usually from this.subName)
* @return The cached frame if names match, or a new frame if they don't
*/
public InterpreterState.InterpreterFrame getOrCreateFrame(String packageName, String subroutineName) {
InterpreterState.InterpreterFrame frame = cachedFrame;
if (frame != null && frame.packageName().equals(packageName) &&
java.util.Objects.equals(frame.subroutineName(), subroutineName)) {
return frame;
}
// Create new frame (either first time, or names don't match)
frame = new InterpreterState.InterpreterFrame(this, packageName, subroutineName);
// Cache it if this is the "normal" case (using code's own names)
String defaultPkg = this.packageName != null ? this.packageName : "main";
String defaultSub = this.subName != null ? this.subName : "(eval)";
if (packageName.equals(defaultPkg) && java.util.Objects.equals(subroutineName, defaultSub)) {
cachedFrame = frame;
}
return frame;
}
/**
* Override RuntimeCode.apply() to dispatch to interpreter.
*
* <p>This is the ONLY method that differs from compiled RuntimeCode.
* The API signature is IDENTICAL, ensuring perfect compatibility.
*
* <p>Regex state save/restore is handled inside {@code BytecodeInterpreter.execute()}
* (via {@code savedRegexState}/finally), not here.
*
* @param args The arguments array (@_)
* @param callContext The calling context (VOID/SCALAR/LIST)
* @return RuntimeList containing the result (may be RuntimeControlFlowList)
*/
@Override
public RuntimeList apply(RuntimeArray args, int callContext) {
return BytecodeInterpreter.execute(this, args, callContext);
}
@Override
public RuntimeList apply(String subroutineName, RuntimeArray args, int callContext) {
return BytecodeInterpreter.execute(this, args, callContext, subroutineName);
}
/**
* Override RuntimeCode.defined() to return true for InterpretedCode.
* InterpretedCode doesn't use methodHandle, so the parent defined() check fails.
* But InterpretedCode instances are always "defined" - they contain executable bytecode.
*/
@Override
public boolean defined() {
return true;
}
/**
* Create an InterpretedCode with captured variables (for closures).
*
* @param capturedVars The variables to capture from outer scope
* @return A new InterpretedCode with captured variables
*/
public InterpretedCode withCapturedVars(RuntimeBase[] capturedVars) {
InterpretedCode copy = new InterpretedCode(
this.bytecode,
this.constants,
this.stringPool,
this.maxRegisters,
capturedVars,
this.sourceName,
this.sourceLine,
this.pcToTokenIndex,
this.variableRegistry,
this.errorUtil,
this.strictOptions,
this.featureFlags,
this.warningFlags,
this.compilePackage,
this.evalSiteRegistries,
this.evalSitePragmaFlags
);
copy.prototype = this.prototype;
copy.attributes = this.attributes;
copy.subName = this.subName;
copy.packageName = this.packageName;
return copy;
}
/**
* Register this InterpretedCode as a global named subroutine.
* This allows compiled code to call interpreted closures seamlessly.
*
* @param name Subroutine name (e.g., "main::closure_123")
* @return RuntimeScalar CODE reference to this InterpretedCode
*/
public RuntimeScalar registerAsNamedSub(String name) {
// Extract package and sub name
int lastColonIndex = name.lastIndexOf("::");
if (lastColonIndex > 0) {
this.packageName = name.substring(0, lastColonIndex);
this.subName = name.substring(lastColonIndex + 2);
} else {
this.packageName = "main";
this.subName = name;
}
// Register in global code refs (creates or gets existing RuntimeScalar)
// Then set its value to this InterpretedCode
RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(name);
codeRef.set(new RuntimeScalar(this));
return codeRef;
}
/**
* Get a human-readable representation for debugging.
*/
@Override
public String toString() {
return "InterpretedCode{" +
"sourceName='" + sourceName + '\'' +
", sourceLine=" + sourceLine +
", bytecode.length=" + bytecode.length +
", maxRegisters=" + maxRegisters +
", hasCapturedVars=" + (capturedVars != null && capturedVars.length > 0) +
'}';
}
/**
* Builder class for constructing InterpretedCode instances.
*/
public static class Builder {
private int[] bytecode;
private Object[] constants = new Object[0];
private String[] stringPool = new String[0];
private int maxRegisters = 10;
private RuntimeBase[] capturedVars = null;
private String sourceName = "<eval>";
private int sourceLine = 1;
public Builder bytecode(int[] bytecode) {
this.bytecode = bytecode;
return this;
}
public Builder constants(Object[] constants) {
this.constants = constants;
return this;
}
public Builder stringPool(String[] stringPool) {
this.stringPool = stringPool;
return this;
}
public Builder maxRegisters(int maxRegisters) {
this.maxRegisters = maxRegisters;
return this;
}
public Builder capturedVars(RuntimeBase[] capturedVars) {
this.capturedVars = capturedVars;
return this;
}
public Builder sourceName(String sourceName) {
this.sourceName = sourceName;
return this;
}
public Builder sourceLine(int sourceLine) {
this.sourceLine = sourceLine;
return this;
}
public InterpretedCode build() {
if (bytecode == null) {
throw new IllegalStateException("Bytecode is required");
}
return new InterpretedCode(bytecode, constants, stringPool, maxRegisters,
capturedVars, sourceName, sourceLine, null, null);
}
}
}