Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,8 @@ private static RuntimeCode compileToExecutable(Node ast, EmitterContext ctx) thr
return compiled;

} catch (RuntimeException e) {
// Check if this is a "Method too large" error from ASM
if (e.getMessage() != null && e.getMessage().contains("Method too large")) {
// Check if this is a recoverable compilation error that can use interpreter fallback
if (needsInterpreterFallback(e)) {
// Interpreter fallback is enabled by default and can be disabled with JPERL_DISABLE_INTERPRETER_FALLBACK
// automatically fall back to the interpreter backend
boolean showFallback = System.getenv("JPERL_SHOW_FALLBACK") != null;
Expand Down Expand Up @@ -397,6 +397,21 @@ private static RuntimeCode compileToExecutable(Node ast, EmitterContext ctx) thr
}
}

private static boolean needsInterpreterFallback(Throwable e) {
for (Throwable t = e; t != null; t = t.getCause()) {
String msg = t.getMessage();
if (msg != null && (
msg.contains("Method too large") ||
msg.contains("ASM frame computation failed") ||
msg.contains("Unexpected runtime error during bytecode generation") ||
msg.contains("dstFrame") ||
msg.contains("requires interpreter fallback"))) {
return true;
}
}
return false;
}

/**
* Compiles Perl code to RuntimeCode without executing it.
* This allows compilation once and execution multiple times for better performance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -854,8 +854,8 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.emitReg(resultReg);
bytecodeCompiler.emitInt(1);
bytecodeCompiler.lastResultReg = resultReg;
} else if (op.equals("eval")) {
// eval $string;
} else if (op.equals("eval") || op.equals("evalbytes")) {
// eval $string; / evalbytes $string;
if (node.operand != null) {
node.operand.accept(bytecodeCompiler);
int stringReg = bytecodeCompiler.lastResultReg;
Expand Down
39 changes: 33 additions & 6 deletions src/main/java/org/perlonjava/backend/jvm/EmitBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,39 @@ private static void collectStateDeclSigilNodes(Node node, Set<OperatorNode> out)

private static void collectStatementLabelNames(List<Node> elements, List<String> out) {
for (Node element : elements) {
if (element instanceof LabelNode labelNode) {
out.add(labelNode.label);
collectStatementLabelNamesRecursive(element, out);
}
}

private static void collectStatementLabelNamesRecursive(Node node, List<String> out) {
if (node == null) return;
if (node instanceof LabelNode labelNode) {
out.add(labelNode.label);
} else if (node instanceof BlockNode block) {
for (Node child : block.elements) {
collectStatementLabelNamesRecursive(child, out);
}
}
}

static void collectIfChainLabels(IfNode ifNode, List<String> out) {
collectStatementLabelNamesRecursive(ifNode.thenBranch, out);
if (ifNode.elseBranch instanceof IfNode elseIf) {
collectIfChainLabels(elseIf, out);
} else {
collectStatementLabelNamesRecursive(ifNode.elseBranch, out);
}
}

static int pushNewGotoLabels(JavaClassInfo javaClassInfo, List<String> labelNames) {
int pushed = 0;
for (String labelName : labelNames) {
if (javaClassInfo.findGotoLabelsByName(labelName) == null) {
javaClassInfo.pushGotoLabels(labelName, new Label());
pushed++;
}
}
return pushed;
}

/**
Expand Down Expand Up @@ -143,9 +172,7 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) {
// their own EmitBlock invocation and maintain proper scoping/shadowing via the stack.
List<String> statementLabelNames = new ArrayList<>();
collectStatementLabelNames(list, statementLabelNames);
for (String labelName : statementLabelNames) {
emitterVisitor.ctx.javaClassInfo.pushGotoLabels(labelName, new Label());
}
int statementLabelsPushed = pushNewGotoLabels(emitterVisitor.ctx.javaClassInfo, statementLabelNames);

// Create labels used inside the block, like `{ L1: ... }`
for (int i = 0; i < node.labels.size(); i++) {
Expand Down Expand Up @@ -264,7 +291,7 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) {
}

// Pop statement labels registered for this block
for (int i = 0; i < statementLabelNames.size(); i++) {
for (int i = 0; i < statementLabelsPushed; i++) {
emitterVisitor.ctx.javaClassInfo.popGotoLabels();
}

Expand Down
46 changes: 37 additions & 9 deletions src/main/java/org/perlonjava/backend/jvm/EmitOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -630,17 +630,45 @@ static void handleSplit(EmitterVisitor emitterVisitor, BinaryOperatorNode node)

// Handles the 'repeat' operator, which repeats a string or list a specified number of times.
static void handleRepeat(EmitterVisitor emitterVisitor, BinaryOperatorNode node) {
// Determine the context for the left operand.
// When x operator is in scalar context, left operand must be in scalar context too
if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR) {
node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
} else if (node.left instanceof ListNode) {
node.left.accept(emitterVisitor.with(RuntimeContextType.LIST));
MethodVisitor mv = emitterVisitor.ctx.mv;
// Spill the left operand before evaluating the right side so non-local control flow
// propagation can't jump to returnLabel with an extra value on the JVM operand stack.
if (ENABLE_SPILL_BINARY_LHS) {
int leftCtx;
if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR) {
leftCtx = RuntimeContextType.SCALAR;
} else if (node.left instanceof ListNode) {
leftCtx = RuntimeContextType.LIST;
} else {
leftCtx = RuntimeContextType.SCALAR;
}
node.left.accept(emitterVisitor.with(leftCtx));

int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot();
boolean pooled = leftSlot >= 0;
if (!pooled) {
leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
}
mv.visitVarInsn(Opcodes.ASTORE, leftSlot);

node.right.accept(emitterVisitor.with(RuntimeContextType.SCALAR));

mv.visitVarInsn(Opcodes.ALOAD, leftSlot);
mv.visitInsn(Opcodes.SWAP);

if (pooled) {
emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}
} else {
node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR) {
node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
} else if (node.left instanceof ListNode) {
node.left.accept(emitterVisitor.with(RuntimeContextType.LIST));
} else {
node.left.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
}
node.right.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
}
// Accept the right operand in SCALAR context.
node.right.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
emitterVisitor.pushCallContext();
// Invoke the static method for the 'repeat' operator.
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/perlonjava/runtime/operators/Operator",
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/perlonjava/backend/jvm/EmitStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import org.perlonjava.frontend.astnode.IfNode;
import org.perlonjava.frontend.astnode.OperatorNode;
import org.perlonjava.frontend.astnode.TryNode;

import java.util.ArrayList;
import java.util.List;
import org.perlonjava.frontend.analysis.EmitterVisitor;
import org.perlonjava.frontend.analysis.RegexUsageDetector;
import org.perlonjava.runtime.runtimetypes.RuntimeContextType;
Expand Down Expand Up @@ -41,6 +44,10 @@ public static void emitSignalCheck(MethodVisitor mv) {
public static void emitIf(EmitterVisitor emitterVisitor, IfNode node) {
emitterVisitor.ctx.logDebug("IF start: " + node.operator);

List<String> branchLabels = new ArrayList<>();
EmitBlock.collectIfChainLabels(node, branchLabels);
int branchLabelsPushed = EmitBlock.pushNewGotoLabels(emitterVisitor.ctx.javaClassInfo, branchLabels);

// Enter a new scope in the symbol table
int scopeIndex = emitterVisitor.ctx.symbolTable.enterScope();

Expand Down Expand Up @@ -82,6 +89,10 @@ public static void emitIf(EmitterVisitor emitterVisitor, IfNode node) {
// Exit the scope in the symbol table
emitterVisitor.ctx.symbolTable.exitScope(scopeIndex);

for (int i = 0; i < branchLabelsPushed; i++) {
emitterVisitor.ctx.javaClassInfo.popGotoLabels();
}

emitterVisitor.ctx.logDebug("IF end");
}

Expand Down
40 changes: 33 additions & 7 deletions src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -1574,14 +1574,19 @@ public static RuntimeCode createRuntimeCode(
}
throw new RuntimeException(e);
} catch (PerlCompilerException e) {
if (USE_INTERPRETER_FALLBACK && e.getMessage() != null) {
String msg = e.getMessage();
if (msg.contains("ASM frame computation failed") || msg.contains("requires interpreter fallback")) {
if (SHOW_FALLBACK) {
System.err.println("Note: JVM compilation needs interpreter fallback (" + msg.split("\n")[0] + ").");
}
return compileToInterpreter(ast, ctx, useTryCatch);
if (USE_INTERPRETER_FALLBACK && needsInterpreterFallback(e)) {
if (SHOW_FALLBACK) {
System.err.println("Note: JVM compilation needs interpreter fallback (" + e.getMessage().split("\n")[0] + ").");
}
return compileToInterpreter(ast, ctx, useTryCatch);
}
throw e;
} catch (RuntimeException e) {
if (USE_INTERPRETER_FALLBACK && needsInterpreterFallback(e)) {
if (SHOW_FALLBACK) {
System.err.println("Note: JVM compilation needs interpreter fallback (" + getRootMessage(e) + ").");
}
return compileToInterpreter(ast, ctx, useTryCatch);
}
throw e;
}
Expand Down Expand Up @@ -1667,6 +1672,27 @@ private static CompiledCode wrapAsCompiledCode(Class<?> generatedClass, EmitterC
* @param useTryCatch Whether to use try-catch (for eval)
* @return InterpretedCode ready to execute
*/
private static boolean needsInterpreterFallback(Throwable e) {
for (Throwable t = e; t != null; t = t.getCause()) {
String msg = t.getMessage();
if (msg != null && (
msg.contains("ASM frame computation failed") ||
msg.contains("requires interpreter fallback") ||
msg.contains("Unexpected runtime error during bytecode generation") ||
msg.contains("dstFrame"))) {
return true;
}
}
return false;
}

private static String getRootMessage(Throwable e) {
Throwable root = e;
while (root.getCause() != null) root = root.getCause();
String msg = root.getMessage();
return msg != null ? msg.split("\n")[0] : e.getClass().getSimpleName();
}

private static InterpretedCode compileToInterpreter(
Node ast, EmitterContext ctx, boolean useTryCatch) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,12 @@ public static boolean processBlock(EmitterVisitor emitterVisitor, BlockNode node
* @return true if the block should be refactored
*/
private static boolean shouldRefactorBlock(BlockNode node, EmitterVisitor emitterVisitor) {
return false;
if (node.elements.size() <= MIN_CHUNK_SIZE) {
return false;
}
long estimatedSize = estimateTotalBytecodeSizeCapped(
node.elements, (long) LARGE_BYTECODE_SIZE * 2);
return estimatedSize > LARGE_BYTECODE_SIZE;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ public static RuntimeScalar integerBitwiseNot(RuntimeScalar runtimeScalar) {
// Must use 32-bit int (not long) to match ivsize=4 in Config.pm.
// Using long would make ~3 return -4 as a 64-bit value, breaking bop.t tests
// that expect 32-bit signed integer semantics under "use integer".

int value = (int) val.getLong();
int result = ~value;
return new RuntimeScalar(result);
Expand Down Expand Up @@ -511,6 +512,7 @@ public static RuntimeScalar integerShiftLeft(RuntimeScalar runtimeScalar, Runtim
runtimeScalar = NumberParser.parseNumber(runtimeScalar);
}

// Use (int) getLong() — see integerBitwiseNot comment for why not getInt().
int value = (int) runtimeScalar.getLong();
long shift = arg2.getLong();

Expand Down Expand Up @@ -554,6 +556,7 @@ public static RuntimeScalar integerShiftRight(RuntimeScalar runtimeScalar, Runti
runtimeScalar = NumberParser.parseNumber(runtimeScalar);
}

// Use (int) getLong() — see integerBitwiseNot comment for why not getInt().
int value = (int) runtimeScalar.getLong();
long shift = arg2.getLong();

Expand Down
23 changes: 23 additions & 0 deletions src/main/java/org/perlonjava/runtime/perlmodule/Encode.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public static void initialize() {
encode.registerMethod("is_utf8", null);
encode.registerMethod("find_encoding", null);
encode.registerMethod("from_to", null);
encode.registerMethod("_utf8_on", null);
encode.registerMethod("_utf8_off", null);
} catch (NoSuchMethodException e) {
System.err.println("Warning: Missing Encode method: " + e.getMessage());
}
Expand Down Expand Up @@ -248,6 +250,27 @@ public static RuntimeList from_to(RuntimeArray args, int ctx) {
}
}

public static RuntimeList _utf8_on(RuntimeArray args, int ctx) {
if (args.isEmpty()) {
throw new IllegalStateException("Bad number of arguments for _utf8_on");
}
return scalarUndef.getList();
}

public static RuntimeList _utf8_off(RuntimeArray args, int ctx) {
if (args.isEmpty()) {
throw new IllegalStateException("Bad number of arguments for _utf8_off");
}
RuntimeScalar arg = args.get(0);
if (arg.type != BYTE_STRING) {
String s = arg.toString();
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
arg.set(new String(bytes, StandardCharsets.ISO_8859_1));
arg.type = BYTE_STRING;
}
return scalarUndef.getList();
}

/**
* Helper method to get a Charset from an encoding name.
* Handles common aliases and Perl-style encoding names.
Expand Down