package org.jruby.ir.instructions;
import org.jruby.RubySymbol;
import org.jruby.anno.FrameField;
import org.jruby.ir.IRFlags;
import org.jruby.ir.IRManager;
import org.jruby.ir.IRScope;
import org.jruby.ir.Operation;
import org.jruby.ir.operands.*;
import org.jruby.ir.operands.Float;
import org.jruby.ir.persistence.IRWriterEncoder;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.ir.transformations.inlining.CloneInfo;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callsite.RefinedCachingCallSite;
import org.jruby.util.ArraySupport;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import static org.jruby.ir.IRFlags.*;
public abstract class CallBase extends NOperandInstr implements ClosureAcceptingInstr, Site {
public static long callSiteCounter = 1;
private static final EnumSet<FrameField> ALL = EnumSet.allOf(FrameField.class);
public transient long callSiteId;
private final CallType callType;
protected RubySymbol name;
protected final transient CallSite callSite;
protected final transient int argsCount;
protected final transient boolean hasClosure;
private transient boolean flagsComputed;
private transient boolean canBeEval;
private transient boolean targetRequiresCallersBinding;
private transient boolean targetRequiresCallersFrame;
private transient boolean dontInline;
private transient boolean[] splatMap;
private transient boolean procNew;
private boolean potentiallyRefined;
private transient Set<FrameField> frameReads;
private transient Set<FrameField> frameWrites;
protected CallBase(IRScope scope, Operation op, CallType callType, RubySymbol name, Operand receiver,
Operand[] args, Operand closure, boolean potentiallyRefined) {
this(scope, op, callType, name, receiver, args, closure, potentiallyRefined, null, callSiteCounter++);
}
protected CallBase(IRScope scope, Operation op, CallType callType, RubySymbol name, Operand receiver,
Operand[] args, Operand closure, boolean potentiallyRefined, CallSite callSite, long callSiteId) {
super(op, arrayifyOperands(receiver, args, closure));
this.callSiteId = callSiteId;
argsCount = args.length;
hasClosure = closure != null;
this.name = name;
this.callType = callType;
this.callSite = callSite == null ? getCallSiteFor(scope, callType, name.idString(), callSiteId, hasLiteralClosure(), potentiallyRefined) : callSite;
splatMap = IRRuntimeHelpers.buildSplatMap(args);
flagsComputed = false;
canBeEval = true;
targetRequiresCallersBinding = true;
targetRequiresCallersFrame = true;
dontInline = false;
procNew = false;
this.potentiallyRefined = potentiallyRefined;
captureFrameReadsAndWrites();
}
@Override
public void encode(IRWriterEncoder e) {
super.encode(e);
e.encode(getCallType().ordinal());
e.encode(getName());
e.encode(getReceiver());
e.encode(calculateArity());
for (Operand arg: getCallArgs()) {
e.encode(arg);
}
if (hasClosure) e.encode(getClosureArg(null));
}
private int calculateArity() {
return hasClosure ? -1*(argsCount + 1) : argsCount;
}
public String getId() {
return name.idString();
}
public long getCallSiteId() {
return callSiteId;
}
public void setCallSiteId(long callSiteId) {
this.callSiteId = callSiteId;
}
public RubySymbol getName() {
return name;
}
public Operand getClosureArg() {
return hasClosure ? operands[argsCount + 1] : null;
}
public Operand getClosureArg(Operand ifUnspecified) {
return hasClosure ? getClosureArg() : ifUnspecified;
}
public Operand getReceiver() {
return operands[0];
}
public abstract Variable getResult();
public Operand getArg1() {
return operands[1];
}
public int getArgsCount() {
return argsCount;
}
public Operand[] getCallArgs() {
Operand[] callArgs = new Operand[argsCount];
ArraySupport.copy(operands, 1, callArgs, 0, argsCount);
return callArgs;
}
public CallSite getCallSite() {
return callSite;
}
public CallType getCallType() {
return callType;
}
public boolean[] splatMap() {
return splatMap;
}
public void setProcNew(boolean procNew) {
this.procNew = procNew;
}
public void blockInlining() {
dontInline = true;
}
public boolean inliningBlocked() {
return dontInline;
}
protected static CallSite getCallSiteFor(IRScope scope, CallType callType, String name, long callsiteId, boolean hasLiteralClosure, boolean potentiallyRefined) {
assert callType != null: "Calltype should never be null";
if (potentiallyRefined) return new RefinedCachingCallSite(name, scope.getStaticScope(), callType);
switch (callType) {
case NORMAL:
if (IRManager.IR_INLINER && hasLiteralClosure) {
return MethodIndex.getProfilingCallSite(name, scope, callsiteId);
} else {
return MethodIndex.getCallSite(name);
}
case FUNCTIONAL: return MethodIndex.getFunctionalCallSite(name);
case VARIABLE: return MethodIndex.getVariableCallSite(name);
case SUPER: return MethodIndex.getSuperCallSite();
case UNKNOWN:
}
return null;
}
public boolean hasLiteralClosure() {
return getClosureArg() instanceof WrappedIRClosure;
}
public static boolean isAllFixnums(Operand[] args) {
for (Operand argument : args) {
if (!(argument instanceof Fixnum)) return false;
}
return true;
}
public static boolean isAllFloats(Operand[] args) {
for (Operand argument : args) {
if (!(argument instanceof Float)) return false;
}
return true;
}
public boolean isPotentiallyRefined() {
return potentiallyRefined;
}
@Override
public boolean computeScopeFlags(IRScope scope) {
boolean modifiedScope = super.computeScopeFlags(scope);
EnumSet<IRFlags> flags = scope.getFlags();
if (targetRequiresCallersBinding()) {
modifiedScope = true;
flags.add(BINDING_HAS_ESCAPED);
}
modifiedScope |= setIRFlagsFromFrameFields(flags, frameReads);
modifiedScope |= setIRFlagsFromFrameFields(flags, frameWrites);
if (hasLiteralClosure()) {
modifiedScope = true;
flags.addAll(IRFlags.REQUIRE_ALL_FRAME_FIELDS);
}
if (procNew) {
modifiedScope = true;
flags.add(IRFlags.REQUIRES_BLOCK);
}
if (canBeEval()) {
modifiedScope = true;
flags.add(USES_EVAL);
flags.add(CAN_RECEIVE_NONLOCAL_RETURNS);
if (flags.contains(RECEIVES_CLOSURE_ARG) && argsCount > 1) {
flags.add(CAN_CAPTURE_CALLERS_BINDING);
}
}
if (potentiallySend(getId(), argsCount)) {
Operand meth = getArg1();
if (meth instanceof StringLiteral) {
String sendName = ((StringLiteral) meth).getString();
if (MethodIndex.SCOPE_AWARE_METHODS.contains(sendName)) {
modifiedScope = true;
flags.add(REQUIRES_DYNSCOPE);
}
if (MethodIndex.FRAME_AWARE_METHODS.contains(sendName)) {
modifiedScope = true;
flags.addAll(IRFlags.REQUIRE_ALL_FRAME_EXCEPT_SCOPE);
}
} else {
modifiedScope = true;
flags.addAll(IRFlags.REQUIRE_ALL_FRAME_FIELDS);
}
}
return modifiedScope;
}
private boolean setIRFlagsFromFrameFields(EnumSet<IRFlags> flags, Set<FrameField> frameFields) {
boolean modifiedScope = false;
for (FrameField field : frameFields) {
modifiedScope = true;
switch (field) {
case LASTLINE: flags.add(IRFlags.REQUIRES_LASTLINE); break;
case BACKREF: flags.add(IRFlags.REQUIRES_BACKREF); break;
case VISIBILITY: flags.add(IRFlags.REQUIRES_VISIBILITY); break;
case BLOCK: flags.add(IRFlags.REQUIRES_BLOCK); break;
case SELF: flags.add(IRFlags.REQUIRES_SELF); break;
case METHODNAME: flags.add(IRFlags.REQUIRES_METHODNAME); break;
case LINE: flags.add(IRFlags.REQUIRES_LINE); break;
case CLASS: flags.add(IRFlags.REQUIRES_CLASS); break;
case FILENAME: flags.add(IRFlags.REQUIRES_FILENAME); break;
case SCOPE: flags.add(IRFlags.REQUIRES_SCOPE); break;
}
}
return modifiedScope;
}
@Override
public void simplifyOperands(Map<Operand, Operand> valueMap, boolean force) {
super.simplifyOperands(valueMap, force);
splatMap = IRRuntimeHelpers.buildSplatMap(getCallArgs());
flagsComputed = false;
}
public Operand[] cloneCallArgs(CloneInfo ii) {
Operand[] clonedArgs = new Operand[argsCount];
for (int i = 0; i < argsCount; i++) {
clonedArgs[i] = operands[i+1].cloneForInlining(ii);
}
return clonedArgs;
}
private boolean computeEvalFlag() {
String mname = getId();
if (getArgsCount() != 0 && (mname.equals("eval") || mname.equals("module_eval") ||
mname.equals("class_eval") || mname.equals("instance_eval"))) {
return true;
}
if (potentiallySend(mname, argsCount)) {
Operand meth = getArg1();
if (!(meth instanceof StringLiteral)) return true;
String name = ((StringLiteral) meth).getString();
return name.equals("call") || name.equals("eval") || mname.equals("module_eval") ||
mname.equals("class_eval") || mname.equals("instance_eval") || name.equals("send") ||
name.equals("__send__");
}
return false;
}
private boolean computeRequiresCallersBindingFlag() {
if (canBeEval()) return true;
if (hasLiteralClosure()) return true;
String mname = getId();
if (MethodIndex.SCOPE_AWARE_METHODS.contains(mname)) {
return true;
} else if (potentiallySend(mname, argsCount)) {
Operand meth = getArg1();
if (!(meth instanceof StringLiteral)) return true;
return MethodIndex.SCOPE_AWARE_METHODS.contains(((StringLiteral) meth).getString());
}
return false;
}
private boolean computeRequiresCallersFrameFlag() {
if (canBeEval()) return true;
if (hasLiteralClosure()) return true;
if (procNew) return true;
String mname = getId();
if (frameReads.size() > 0 || frameWrites.size() > 0) {
return true;
} else if (potentiallySend(mname, argsCount)) {
Operand meth = getArg1();
String name;
if (meth instanceof Stringable) {
name = ((Stringable) meth).getString();
} else {
return true;
}
frameReads = MethodIndex.METHOD_FRAME_READS.getOrDefault(name, Collections.EMPTY_SET);
frameWrites = MethodIndex.METHOD_FRAME_WRITES.getOrDefault(name, Collections.EMPTY_SET);
if (frameReads.size() > 0 || frameWrites.size() > 0) {
return true;
}
}
return false;
}
private static boolean potentiallySend(String name, int argsCount) {
return (name.equals("send") || name.equals("__send__") || name.equals("public_send")) && argsCount >= 1;
}
private void captureFrameReadsAndWrites() {
if (potentiallySend(getId(), argsCount)) {
Operand meth = getArg1();
String aliasName;
if (meth instanceof Stringable) {
aliasName = ((Stringable) meth).getString();
frameReads = MethodIndex.METHOD_FRAME_READS.getOrDefault(aliasName, Collections.EMPTY_SET);
frameWrites = MethodIndex.METHOD_FRAME_WRITES.getOrDefault(aliasName, Collections.EMPTY_SET);
} else {
frameReads = ALL;
frameWrites = ALL;
}
} else {
frameReads = MethodIndex.METHOD_FRAME_READS.getOrDefault(getId(), Collections.EMPTY_SET);
frameWrites = MethodIndex.METHOD_FRAME_WRITES.getOrDefault(getId(), Collections.EMPTY_SET);
}
}
private void computeFlags() {
flagsComputed = true;
canBeEval = computeEvalFlag();
targetRequiresCallersBinding = canBeEval || computeRequiresCallersBindingFlag();
targetRequiresCallersFrame = canBeEval || computeRequiresCallersFrameFlag();
}
public boolean canBeEval() {
if (!flagsComputed) computeFlags();
return canBeEval;
}
public boolean targetRequiresCallersBinding() {
if (!flagsComputed) computeFlags();
return targetRequiresCallersBinding;
}
public boolean targetRequiresCallersFrame() {
if (!flagsComputed) computeFlags();
return targetRequiresCallersFrame;
}
@Override
public String[] toStringNonOperandArgs() {
return new String[] { "n:" + getName(), "t:" + callType.toString().substring(0, 2), "cl:"+ hasClosure};
}
public static boolean containsArgSplat(Operand[] arguments) {
for (Operand argument : arguments) {
if (argument instanceof Splat) return true;
}
return false;
}
private final static int REQUIRED_OPERANDS = 1;
private static Operand[] arrayifyOperands(Operand receiver, Operand[] callArgs, Operand closure) {
Operand[] allArgs = new Operand[callArgs.length + REQUIRED_OPERANDS + (closure != null ? 1 : 0)];
assert receiver != null : "RECEIVER is null";
allArgs[0] = receiver;
for (int i = 0; i < callArgs.length; i++) {
assert callArgs[i] != null : "ARG " + i + " is null";
allArgs[i + REQUIRED_OPERANDS] = callArgs[i];
}
if (closure != null) allArgs[callArgs.length + REQUIRED_OPERANDS] = closure;
return allArgs;
}
@Override
public Object interpret(ThreadContext context, StaticScope currScope, DynamicScope dynamicScope, IRubyObject self, Object[] temp) {
IRubyObject object = (IRubyObject) getReceiver().retrieve(context, self, currScope, dynamicScope, temp);
IRubyObject[] values = prepareArguments(context, self, currScope, dynamicScope, temp);
Block preparedBlock = prepareBlock(context, self, currScope, dynamicScope, temp);
if (hasLiteralClosure()) {
return callSite.callIter(context, self, object, values, preparedBlock);
}
return callSite.call(context, self, object, values, preparedBlock);
}
protected IRubyObject[] prepareArguments(ThreadContext context, IRubyObject self, StaticScope currScope, DynamicScope dynamicScope, Object[] temp) {
return splatMap != null ?
prepareArgumentsComplex(context, self, currScope, dynamicScope, temp) :
prepareArgumentsSimple(context, self, currScope, dynamicScope, temp);
}
protected IRubyObject[] prepareArgumentsSimple(ThreadContext context, IRubyObject self, StaticScope currScope, DynamicScope currDynScope, Object[] temp) {
IRubyObject[] newArgs = new IRubyObject[argsCount];
for (int i = 0; i < argsCount; i++) {
newArgs[i] = (IRubyObject) operands[i+1].retrieve(context, self, currScope, currDynScope, temp);
}
return newArgs;
}
protected IRubyObject[] prepareArgumentsComplex(ThreadContext context, IRubyObject self, StaticScope currScope, DynamicScope currDynScope, Object[] temp) {
return IRRuntimeHelpers.splatArguments(
prepareArgumentsSimple(context, self, currScope, currDynScope, temp),
splatMap);
}
public Block prepareBlock(ThreadContext context, IRubyObject self, StaticScope currScope, DynamicScope currDynScope, Object[] temp) {
if (getClosureArg() == null) return Block.NULL_BLOCK;
if (potentiallyRefined) {
return IRRuntimeHelpers.getRefinedBlockFromObject(context, currScope, getClosureArg().retrieve(context, self, currScope, currDynScope, temp));
} else {
return IRRuntimeHelpers.getBlockFromObject(context, getClosureArg().retrieve(context, self, currScope, currDynScope, temp));
}
}
}