Modified: trunk/Source/_javascript_Core/ChangeLog (243529 => 243530)
--- trunk/Source/_javascript_Core/ChangeLog 2019-03-27 00:02:55 UTC (rev 243529)
+++ trunk/Source/_javascript_Core/ChangeLog 2019-03-27 00:08:46 UTC (rev 243530)
@@ -1,3 +1,31 @@
+2019-03-26 Saam Barati <sbar...@apple.com>
+
+ FTL: Emit code to validate AI's state when running the compiled code
+ https://bugs.webkit.org/show_bug.cgi?id=195924
+ <rdar://problem/49003422>
+
+ Reviewed by Filip Pizlo.
+
+ This patch adds code that between the execution of each node that validates
+ the types that AI proves. This option is too expensive to turn on for our
+ regression testing, but we think it will be valuable in other types of running
+ modes, such as when running with a fuzzer.
+
+ This patch also adds options to only probabilistically run this validation
+ after the execution of each node. As the probability is lowered, there is
+ less of a perf hit.
+
+ This patch just adds this validation in the FTL. A follow-up patch will land
+ it in the DFG too: https://bugs.webkit.org/show_bug.cgi?id=196219
+
+ * ftl/FTLLowerDFGToB3.cpp:
+ (JSC::FTL::DFG::LowerDFGToB3::LowerDFGToB3):
+ (JSC::FTL::DFG::LowerDFGToB3::compileBlock):
+ (JSC::FTL::DFG::LowerDFGToB3::validateAIState):
+ (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+ (JSC::FTL::DFG::LowerDFGToB3::lowJSValue):
+ * runtime/Options.h:
+
2019-03-26 Tadeu Zagallo <tzaga...@apple.com>
WebAssembly: Fix f32.min, f64.min and f64.max operations on NaN
Modified: trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp (243529 => 243530)
--- trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp 2019-03-27 00:02:55 UTC (rev 243529)
+++ trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp 2019-03-27 00:08:46 UTC (rev 243530)
@@ -46,6 +46,7 @@
#include "DFGDoesGC.h"
#include "DFGDominators.h"
#include "DFGInPlaceAbstractState.h"
+#include "DFGLivenessAnalysisPhase.h"
#include "DFGMayExit.h"
#include "DFGOSRAvailabilityAnalysisPhase.h"
#include "DFGOSRExitFuzz.h"
@@ -82,6 +83,7 @@
#include "JSLexicalEnvironment.h"
#include "JSMap.h"
#include "OperandsInlines.h"
+#include "ProbeContext.h"
#include "RegExpObject.h"
#include "ScopedArguments.h"
#include "ScopedArgumentsTable.h"
@@ -155,6 +157,29 @@
, m_interpreter(state.graph, m_state)
, m_indexMaskingMode(Options::enableSpectreMitigations() ? IndexMaskingEnabled : IndexMaskingDisabled)
{
+ if (Options::validateAbstractInterpreterState()) {
+ performLivenessAnalysis(m_graph);
+
+ // We only use node liveness here, not combined liveness, as we only track
+ // AI state for live nodes.
+ for (DFG::BasicBlock* block : m_graph.blocksInNaturalOrder()) {
+ NodeSet live;
+
+ for (NodeFlowProjection node : block->ssa->liveAtTail) {
+ if (node.kind() == NodeFlowProjection::Primary)
+ live.addVoid(node.node());
+ }
+
+ for (unsigned i = block->size(); i--; ) {
+ Node* node = block->at(i);
+ live.remove(node);
+ m_graph.doToChildren(node, [&] (Edge child) {
+ live.addVoid(child.node());
+ });
+ m_liveInToNode.add(node, live);
+ }
+ }
+ }
}
void lower()
@@ -473,6 +498,8 @@
crash(m_highBlock, nullptr);
return;
}
+
+ m_aiCheckedNodes.clear();
m_availabilityCalculator.beginBlock(m_highBlock);
@@ -508,6 +535,129 @@
}
}
+ void validateAIState(Node* node)
+ {
+ if (!m_graphDump) {
+ StringPrintStream out;
+ m_graph.dump(out);
+ m_graphDump = out.toString();
+ }
+
+ switch (node->op()) {
+ case MovHint:
+ case ZombieHint:
+ case JSConstant:
+ case LazyJSConstant:
+ case DoubleConstant:
+ case Int52Constant:
+ case GetStack:
+ case PutStack:
+ case KillStack:
+ case ExitOK:
+ return;
+ default:
+ break;
+ }
+
+ // Before we execute node.
+ NodeSet& live = m_liveInToNode.find(node)->value;
+ unsigned highParentIndex = node->index();
+ {
+ uint64_t hash = WTF::intHash(highParentIndex);
+ if (hash >= static_cast<uint64_t>((static_cast<double>(std::numeric_limits<unsigned>::max()) + 1) * Options::validateAbstractInterpreterStateProbability()))
+ return;
+ }
+
+ for (Node* node : live) {
+ if (node->isPhantomAllocation())
+ continue;
+
+ if (node->op() == CheckInBounds)
+ continue;
+
+ AbstractValue value = m_interpreter.forNode(node);
+ {
+ auto iter = m_aiCheckedNodes.find(node);
+ if (iter != m_aiCheckedNodes.end()) {
+ AbstractValue checkedValue = iter->value;
+ if (checkedValue == value) {
+ if (!(value.m_type & SpecCell))
+ continue;
+ }
+ }
+ m_aiCheckedNodes.set(node, value);
+ }
+
+ FlushFormat flushFormat;
+ LValue input;
+ if (node->hasJSResult()) {
+ input = lowJSValue(Edge(node, UntypedUse));
+ flushFormat = FlushedJSValue;
+ } else if (node->hasDoubleResult()) {
+ input = lowDouble(Edge(node, DoubleRepUse));
+ flushFormat = FlushedDouble;
+ } else if (node->hasInt52Result()) {
+ input = strictInt52ToJSValue(lowStrictInt52(Edge(node, Int52RepUse)));
+ flushFormat = FlushedInt52;
+ } else
+ continue;
+
+ unsigned highChildIndex = node->index();
+
+ String graphDump = m_graphDump;
+
+ PatchpointValue* patchpoint = m_out.patchpoint(Void);
+ patchpoint->effects = Effects::none();
+ patchpoint->effects.writesLocalState = true;
+ patchpoint->appendSomeRegister(input);
+ patchpoint->setGenerator([=] (CCallHelpers& jit, const StackmapGenerationParams& params) {
+ GPRReg reg = InvalidGPRReg;
+ FPRReg fpReg = InvalidFPRReg;
+ if (flushFormat == FlushedDouble)
+ fpReg = params[0].fpr();
+ else
+ reg = params[0].gpr();
+ jit.probe([=] (Probe::Context& context) {
+ JSValue input;
+ double doubleInput;
+
+ auto dumpAndCrash = [&] {
+ dataLogLn("Validation failed at node: @", highParentIndex);
+ dataLogLn("Failed validating live value: @", highChildIndex);
+ dataLogLn();
+ dataLogLn("Expected AI value = ", value);
+ if (flushFormat != FlushedDouble)
+ dataLogLn("Unexpected value = ", input);
+ else
+ dataLogLn("Unexpected double value = ", doubleInput);
+ dataLogLn();
+ dataLogLn(graphDump);
+ CRASH();
+ };
+
+ if (flushFormat == FlushedDouble) {
+ doubleInput = context.fpr(fpReg);
+ SpeculatedType type;
+ if (!std::isnan(doubleInput))
+ type = speculationFromValue(jsDoubleNumber(doubleInput));
+ else if (isImpureNaN(doubleInput))
+ type = SpecDoubleImpureNaN;
+ else
+ type = SpecDoublePureNaN;
+
+ if (!value.couldBeType(type))
+ dumpAndCrash();
+ } else {
+ input = JSValue::decode(context.gpr(reg));
+ if (!value.validateOSREntryValue(input, flushFormat))
+ dumpAndCrash();
+ }
+
+ });
+ });
+ }
+ }
+
bool compileNode(unsigned nodeIndex)
{
if (!m_state.isValid()) {
@@ -527,6 +677,9 @@
m_interpreter.startExecuting();
m_interpreter.executeKnownEdgeTypes(m_node);
+ if (Options::validateAbstractInterpreterState())
+ validateAIState(m_node);
+
if (validateDFGDoesGC) {
bool expectDoesGC = doesGC(m_graph, m_node);
m_out.store(m_out.constBool(expectDoesGC), m_out.absolute(vm().heap.addressOfExpectDoesGC()));
@@ -15182,7 +15335,7 @@
return result;
}
- DFG_CRASH(m_graph, m_node, "Value not defined");
+ DFG_CRASH(m_graph, m_node, makeString("Value not defined: ", String::number(edge.node()->index())).ascii().data());
return 0;
}
@@ -17208,6 +17361,11 @@
NodeOrigin m_origin;
unsigned m_nodeIndex;
Node* m_node;
+
+ // These are used for validating AI state.
+ HashMap<Node*, NodeSet> m_liveInToNode;
+ HashMap<Node*, AbstractValue> m_aiCheckedNodes;
+ String m_graphDump;
};
} // anonymous namespace
Modified: trunk/Source/_javascript_Core/runtime/Options.h (243529 => 243530)
--- trunk/Source/_javascript_Core/runtime/Options.h 2019-03-27 00:02:55 UTC (rev 243529)
+++ trunk/Source/_javascript_Core/runtime/Options.h 2019-03-27 00:08:46 UTC (rev 243530)
@@ -510,6 +510,8 @@
v(unsigned, thresholdForGlobalLexicalBindingEpoch, UINT_MAX, Normal, "Threshold for global lexical binding epoch. If the epoch reaches to this value, CodeBlock metadata for scope operations will be revised globally. It needs to be greater than 1.") \
v(optionString, diskCachePath, nullptr, Restricted, nullptr) \
v(bool, forceDiskCache, false, Restricted, nullptr) \
+ v(bool, validateAbstractInterpreterState, false, Restricted, nullptr) \
+ v(double, validateAbstractInterpreterStateProbability, 0.5, Normal, nullptr) \
enum OptionEquivalence {