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
18 changes: 18 additions & 0 deletions core/src/main/java/org/jruby/RubyFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.FileTime;
Expand Down Expand Up @@ -237,6 +238,23 @@ public RubyFile(Ruby runtime, String path, InputStream in) {
this.setPath(path);
}


private RubyFile(Ruby runtime, String path, SeekableByteChannel in) {
super(runtime, runtime.getFile(), in);
this.setPath(path);
}

/**
* Only used by parser (prism) so that we can get a File
* @param runtime the runtime
* @param path the path of DATA
* @param in the channel to use
* @return a Ruby file
*/
public static RubyFile DATAFile(Ruby runtime, String path, SeekableByteChannel in) {
return new RubyFile(runtime, path, in);
}

public RubyFile(Ruby runtime, String path, FileChannel channel) {
super(runtime, runtime.getFile(), channel);
this.setPath(path);
Expand Down
34 changes: 19 additions & 15 deletions core/src/main/java/org/jruby/ir/builder/EnsureBlockInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@
* run the stack of ensure blocks in the right order.
* ----------------------------------------------------------------------------------- */
class EnsureBlockInfo {
final Label regionStart;
final Label start;
final Label end;
final Label dummyRescueBlockLabel;
final Label start; // Beginning of this ensure region
final Label bodyStart; // Beginning of any actual instrs in the ensure body (will not be emitted if none)
final Label end; // End of this ensure region
final Label ensureRescue; // Where exceptions in an ensure flow through
Variable savedGlobalException;
boolean needsBacktrace;

Expand All @@ -65,13 +65,13 @@ class EnsureBlockInfo {
// This ensure block's instructions
final List<Instr> instrs;

public EnsureBlockInfo(IRScope s, IRLoop l, Label bodyRescuer) {
public EnsureBlockInfo(IRScope s, IRLoop l, Label bodyRescuer, int sourceLine) {
// this technically may be any block and not specifically rescue but for the sake of looking at the CFG
// it is more or less a begin block with exception handling around it.
regionStart = s.getNewLabel("BEGIN");
start = s.getNewLabel("RESC_START");
end = s.getNewLabel("AFTER_RESC");
dummyRescueBlockLabel = s.getNewLabel("RESC_DUMMY");
start = s.getNewLabel("ENSURE_BEGIN_@" + sourceLine);
bodyStart = s.getNewLabel("ENSURE_BODY_:@" + sourceLine);
end = s.getNewLabel("ENSURE_END_:@" + sourceLine);
ensureRescue = s.getNewLabel("ENSURE_CATCH_@" + sourceLine);
instrs = new ArrayList<>(4);
savedGlobalException = null;
innermostLoop = l;
Expand All @@ -87,10 +87,14 @@ public void addInstrAtBeginning(Instr i) {
instrs.add(0, i);
}

public void emitBody(IRBuilder b) {
b.addInstr(new LabelInstr(start));
/**
* Emit the saved instrs for the ensure body into the current location in the provided builder.
* @param builder
*/
public void emitEnsureBody(IRBuilder builder) {
builder.addInstr(new LabelInstr(bodyStart));
for (Instr i: instrs) {
b.addInstr(i);
builder.addInstr(i);
}
}

Expand All @@ -106,20 +110,20 @@ public void cloneIntoHostScope(IRBuilder builder) {
// there are no actual instrs pushed yet (but ebi has reserved a frame for it -- e.g. the rescue/ensure
// the next is in). Since it is doing nothing we have nothing to clone. By skipping this we prevent
// setting exception regions and simplify CFG construction.
if (instrs.size() == 0) return;
if (instrs.isEmpty()) return;

SimpleCloneInfo ii = new SimpleCloneInfo(builder.scope, true);

// Clone required labels.
// During normal cloning below, labels not found in the rename map
// are not cloned.
ii.renameLabel(start);
ii.renameLabel(bodyStart);
for (Instr i: instrs) {
if (i instanceof LabelInstr) ii.renameLabel(((LabelInstr)i).getLabel());
}

// Clone instructions now
builder.addInstr(new LabelInstr(ii.getRenamedLabel(start)));
builder.addInstr(new LabelInstr(ii.getRenamedLabel(bodyStart)));
builder.addInstr(new ExceptionRegionStartMarkerInstr(bodyRescuer));
for (Instr instr: instrs) {
Instr clonedInstr = instr.clone(ii);
Expand Down
129 changes: 81 additions & 48 deletions core/src/main/java/org/jruby/ir/builder/IRBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import org.jruby.RubySymbol;
import org.jruby.ast.IterNode;
import org.jruby.ast.StrNode;
import org.jruby.common.IRubyWarnings;
import org.jruby.ext.coverage.CoverageData;
import org.jruby.ir.IRClassBody;
import org.jruby.ir.IRClosure;
Expand Down Expand Up @@ -167,44 +166,58 @@ public static InterpreterContext buildRoot(IRManager manager, ParseResult rootNo
return manager.getBuilderFactory().topIRBuilder(manager, script, rootNode).buildRootInner(rootNode);
}

protected int currentLine() {
return lastProcessedLineNum;
}

// FIXME: When legacy parser goes away this should be totally rewritten as prism represents the whole structure as one node vs
// legacy which has ensure as the parent to any rescue.
// FIXME: consider mod_rescue, rescue, and pure ensure as separate entries
// Note: reference is only passed in via Prism on legacy this is desugared into AST.
protected Operand buildEnsureInternal(U body, U elseNode, U[] exceptions, U rescueBody, X optRescue, boolean isModifier,
U ensureNode, boolean isRescue, U reference) {
// Save $!
final Variable savedGlobalException = temp();
addInstr(new GetGlobalVariableInstr(savedGlobalException, symbol("$!")));

// ------------ Build the body of the ensure block ------------
//
/**
* @param body the main statements to be executed
* @param elseNode else path in a begin
* @param exceptions possible list of excpetions which will jump to rescueBody
* @param rescueBody the statements executed if the matching exception(s) occurs
* @param optRescue possible other rescue in the same begin
* @param isModifier foo rescue bar
* @param ensureNode ensure block
* @param reference variable for the rescue (NameError => name)
* @return return last operand of begin execution
*
* Note: reference is only passed in via Prism on legacy this is desugared into AST.
*/
protected Operand buildEnsureInternal(U body, U elseNode, U[] exceptions, U rescueBody, X optRescue,
boolean isModifier, U ensureNode, boolean isRescue, U reference) {
var savedGlobalException = addResultInstr(new GetGlobalVariableInstr(temp(), symbol("$!")));

// The ensure code is built first so that when the protected body is being built,
// the ensure code can be cloned at break/next/return sites in the protected body.
EnsureBlockInfo ebi = new EnsureBlockInfo(scope, getCurrentLoop(), activeRescuers.peek(), currentLine() + 1);

// Push a new ensure block node onto the stack of ensure bodies being built
// The body's instructions are stashed and emitted later.
EnsureBlockInfo ebi = new EnsureBlockInfo(scope, getCurrentLoop(), activeRescuers.peek());
// Rescue will change $! but we need to restore $! later. prism: ensure and isRescue means it is both (we
// call this method again below to rip those two things apart.
if (isRescue && ensureNode == null) ebi.savedGlobalException = savedGlobalException;

// Record $! save var if we had a non-empty rescue node.
// $! will be restored from it where required.
if (isRescue) ebi.savedGlobalException = savedGlobalException;

ensureBodyBuildStack.push(ebi);
Operand ensureRetVal = ensureNode == null ? nil() : build(ensureNode);
ensureBodyBuildStack.pop();
// Record body of ensure and push to ensure body stack if there is an actual ensure body.
Operand ensureRetVal = processEnsureBody(ensureNode, ebi);

// ------------ Build the protected region ------------
activeEnsureBlockStack.push(ebi);

// Start of protected region
addInstr(new LabelInstr(ebi.regionStart));
addInstr(new ExceptionRegionStartMarkerInstr(ebi.dummyRescueBlockLabel));
activeRescuers.push(ebi.dummyRescueBlockLabel);
addInstr(new LabelInstr(ebi.start));
addInstr(new ExceptionRegionStartMarkerInstr(ebi.ensureRescue));
activeRescuers.push(ebi.ensureRescue);

// Generate IR for code being protected
Variable ensureExprValue = temp();
Operand rv;
if (isRescue) {
rv = buildRescueInternal(body, elseNode, exceptions, rescueBody, optRescue, isModifier, ebi, reference);
if (ensureNode != null) {
// both were passed in via Prism. Let's pretend they are nested like legacy where ensures enclose the rescue.
rv = buildEnsureInternal(body, elseNode, exceptions, rescueBody, optRescue, isModifier, null, isRescue, reference);
} else {
rv = buildRescueInternal(body, elseNode, exceptions, rescueBody, optRescue, isModifier, ebi, reference);
}
} else {
rv = build(body);
}
Expand All @@ -215,8 +228,9 @@ protected Operand buildEnsureInternal(U body, U elseNode, U[] exceptions, U resc

// Is this a begin..(rescue..)?ensure..end node that actually computes a value?
// (vs. returning from protected body)
boolean isEnsureExpr = ensureNode != null && rv != U_NIL && !isRescue;
boolean isEnsureExpr = ensureNode != null && rv != U_NIL;

Variable ensureExprValue = temp();
// Clone the ensure body and jump to the end
if (isEnsureExpr) {
addInstr(new CopyInstr(ensureExprValue, rv));
Expand All @@ -230,27 +244,36 @@ protected Operand buildEnsureInternal(U body, U elseNode, U[] exceptions, U resc
// ------------ Emit the ensure body alongwith dummy rescue block ------------
// Now build the dummy rescue block that
// catches all exceptions thrown by the body
addInstr(new LabelInstr(ebi.dummyRescueBlockLabel));
addInstr(new LabelInstr(ebi.ensureRescue));
Variable exc = addResultInstr(new ReceiveJRubyExceptionInstr(temp()));

// Now emit the ensure body's stashed instructions
if (ensureNode != null) ebi.emitBody(this);
if (ensureNode != null) ebi.emitEnsureBody(this);

// 1. Ensure block has no explicit return => the result of the entire ensure expression is the result of the protected body.
// 2. Ensure block has an explicit return => the result of the protected body is ignored.
// U_NIL => there was a return from within the ensure block!
// FIXME: This U_NIL case is wrong in a few cases: 'ensure; return 1 if true; end' or weirder 'ensure; return 1; true; end'
if (ensureRetVal == U_NIL) rv = U_NIL;

// Return (rethrow exception/end)
// rethrows the caught exception from the dummy ensure block
addInstr(new ThrowExceptionInstr(exc));

// End label for the exception region
addInstr(new LabelInstr(ebi.end));
addInstr(new ThrowExceptionInstr(exc)); // rethrows the caught exception from the dummy ensure block
addInstr(new LabelInstr(ebi.end)); // End label for the exception region

return isEnsureExpr ? ensureExprValue : rv;
}

private Operand processEnsureBody(U ensureNode, EnsureBlockInfo ebi) {
Operand ensureRetVal;
if (ensureNode != null) {
ensureBodyBuildStack.push(ebi);
ensureRetVal = build(ensureNode);
ensureBodyBuildStack.pop();
} else {
ensureRetVal = nil();
}
return ensureRetVal;
}

public InterpreterContext buildEvalRoot(ParseResult rootNode) {
executesOnce = false;
coverageMode = rootNode.getCoverageMode();
Expand Down Expand Up @@ -1932,8 +1955,8 @@ protected Operand buildOpAsgnConstDecl(Y left, U right, RubySymbol operator) {
return copy(temp(), putConstant(left, result));
}

protected Operand buildOpAsgnConstDecl(Y left, RubySymbol name, U right, RubySymbol operator) {
Operand parent = buildColon2ForConstAsgnDeclNode((U) left, temp(), false);
protected Operand buildOpAsgnConstDecl(U left, RubySymbol name, U right, RubySymbol operator) {
Operand parent = buildColon2ForConstAsgnDeclNode(left, temp(), false);
Operand lhs = searchModuleForConst(temp(), parent, name);
Operand rhs = build(right);
Variable result = call(temp(), lhs, operator, rhs);
Expand Down Expand Up @@ -2499,7 +2522,6 @@ protected Operand buildRedo(int line) {
protected void buildRescueBodyInternal(U[] exceptions, U body, X consequent, Variable rv, Variable exc, Label endLabel,
U reference) {
// Compare and branch as necessary!
Label uncaughtLabel = getNewLabel("MISSED");
Label caughtLabel = getNewLabel("RESCUE");
if (exceptions == null || exceptions.length == 0) {
outputExceptionCheck(getManager().getStandardError(), exc, caughtLabel);
Expand All @@ -2510,7 +2532,6 @@ protected void buildRescueBodyInternal(U[] exceptions, U body, X consequent, Var
}

// Uncaught exception -- build other rescue nodes or rethrow!
addInstr(new LabelInstr(uncaughtLabel));
if (consequent != null) {
buildRescueBodyInternal(exceptionNodesFor(consequent), bodyFor(consequent), optRescueFor(consequent), rv,
exc, endLabel, referenceFor(consequent));
Expand Down Expand Up @@ -2579,21 +2600,33 @@ protected Operand buildAttrAssign(Variable result, U receiver, U argsNode, U blo

protected abstract Operand[] buildAttrAssignCallArgs(U args, Operand[] rhs, boolean containsAssignment);

/**
* @param bodyNode the main statements to be executed
* @param elseNode else path in a begin
* @param exceptions possible list of excpetions which will jump to rescueBody
* @param rescueBody the statements executed if the matching exception(s) occurs
* @param optRescue possible other rescue in the same begin
* @param isModifier foo rescue bar
* @param ensure ensure block
* @param reference variable for the rescue (NameError => name)
* @return return last operand of begin execution
*/
protected Operand buildRescueInternal(U bodyNode, U elseNode, U[] exceptions, U rescueBody,
X optRescue, boolean isModifier, EnsureBlockInfo ensure, U reference) {
boolean needsBacktrace = !canBacktraceBeRemoved(exceptions, rescueBody, optRescue, elseNode, isModifier);

// Labels marking start, else, end of the begin-rescue(-ensure)-end block
Label rBeginLabel = getNewLabel();
Label rEndLabel = ensure.end;
Label rescueLabel = getNewLabel("RESC_TEST"); // Label marking start of the first rescue code.
int line = currentLine() + 1;
Label beginLabel = getNewLabel("BEGIN_@" + line);
Label endLabel = ensure.end;
Label rescueTestLabel = getNewLabel("RESCUE_TEST_@" + line); // Label marking start of the first rescue code.
ensure.needsBacktrace = needsBacktrace;

addInstr(new LabelInstr(rBeginLabel));
addInstr(new LabelInstr(beginLabel));

// Placeholder rescue instruction that tells rest of the compiler passes the boundaries of the rescue block.
addInstr(new ExceptionRegionStartMarkerInstr(rescueLabel));
activeRescuers.push(rescueLabel);
addInstr(new ExceptionRegionStartMarkerInstr(rescueTestLabel));
activeRescuers.push(rescueTestLabel);
addInstr(getManager().needsBacktrace(needsBacktrace));

// Body
Expand Down Expand Up @@ -2628,7 +2661,7 @@ protected Operand buildRescueInternal(U bodyNode, U elseNode, U[] exceptions, U
//
// The retry should jump to 1, not 2.
// If we push the rescue block before building the body, we will jump to 2.
RescueBlockInfo rbi = new RescueBlockInfo(rBeginLabel, ensure.savedGlobalException);
RescueBlockInfo rbi = new RescueBlockInfo(beginLabel, ensure.savedGlobalException);
activeRescueBlockStack.push(rbi);

if (tmp != U_NIL) {
Expand All @@ -2638,7 +2671,7 @@ protected Operand buildRescueInternal(U bodyNode, U elseNode, U[] exceptions, U
// - If we dont have any ensure blocks, simply jump to the end of the rescue block
// - If we do, execute the ensure code.
ensure.cloneIntoHostScope(this);
addInstr(new JumpInstr(rEndLabel));
addInstr(new JumpInstr(endLabel));
} //else {
// If the body had an explicit return, the return instruction IR build takes care of setting
// up execution of all necessary ensure blocks. So, nothing to do here!
Expand All @@ -2650,7 +2683,7 @@ protected Operand buildRescueInternal(U bodyNode, U elseNode, U[] exceptions, U
//}

// Start of rescue logic
addInstr(new LabelInstr(rescueLabel));
addInstr(new LabelInstr(rescueTestLabel));

// This is optimized no backtrace path so we need to reenable backtraces since we are
// exiting that region.
Expand All @@ -2660,7 +2693,7 @@ protected Operand buildRescueInternal(U bodyNode, U elseNode, U[] exceptions, U
Variable exc = addResultInstr(new ReceiveRubyExceptionInstr(temp()));

// Build the actual rescue block(s)
buildRescueBodyInternal(exceptions, rescueBody, optRescue, rv, exc, rEndLabel, reference);
buildRescueBodyInternal(exceptions, rescueBody, optRescue, rv, exc, endLabel, reference);

activeRescueBlockStack.pop();
return rv;
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/ir/builder/IRBuilderAST.java
Original file line number Diff line number Diff line change
Expand Up @@ -2515,7 +2515,7 @@ public Operand buildOpAsgnConstDeclNode(OpAsgnConstDeclNode node) {
return buildOpAsgnConstDeclAnd(node.getFirstNode(), node.getSecondNode(), ((Colon3Node) node.getFirstNode()).getName());
}

return buildOpAsgnConstDecl((Colon3Node) node.getFirstNode(), ((Colon3Node) node.getFirstNode()).getName(), node.getSecondNode(), node.getSymbolOperator());
return buildOpAsgnConstDecl(node.getFirstNode(), ((Colon3Node) node.getFirstNode()).getName(), node.getSecondNode(), node.getSymbolOperator());
}

public Operand buildOpAsgnAnd(OpAsgnAndNode node) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,17 @@ public String toStringInstrs() {
StringBuilder b = new StringBuilder();
int length = instructions.length;

var indent = 0;
for (int i = 0; i < length; i++) {
if (i > 0) b.append("\n");
b.append(String.format("%6d",i)).append('\t').append(instructions[i]);
var instr = instructions[i];
if (instr instanceof ExceptionRegionEndMarkerInstr) {
indent -= 1;
}
b.append(String.format("%6d",i+1)).append('\t').append(" ".repeat(indent)).append(instr);
if (instr instanceof ExceptionRegionStartMarkerInstr) {
indent += 1;
}
}

/* ENEBO: I this this is too much output espectially for ic and not fic
Expand Down
Loading